diff options
448 files changed, 13508 insertions, 5403 deletions
diff --git a/Android.bp b/Android.bp index 20ca1b7c1020..9374c01066e6 100644 --- a/Android.bp +++ b/Android.bp @@ -581,9 +581,11 @@ java_library { "android.hardware.vibrator-V1.1-java", "android.hardware.vibrator-V1.2-java", "android.hardware.vibrator-V1.3-java", + "android.hardware.vibrator-V2-java", "android.security.apc-java", "android.security.authorization-java", "android.security.usermanager-java", + "android.security.vpnprofilestore-java", "android.system.keystore2-V1-java", "android.system.suspend.control.internal-java", "cameraprotosnano", diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java index e83c64c37678..5a4596108aa5 100644 --- a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java +++ b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java @@ -44,7 +44,7 @@ import java.io.OutputStream; @RunWith(AndroidJUnit4.class) public class TypefaceCreatePerfTest { // A font file name in asset directory. - private static final String TEST_FONT_NAME = "DancingScript-Regular.ttf"; + private static final String TEST_FONT_NAME = "DancingScript.ttf"; @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); diff --git a/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt index 1d94d7e8eca2..d5ed95f18f93 100644 --- a/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt +++ b/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt @@ -97,11 +97,21 @@ class PackageParsingPerfTest { private val state: BenchmarkState get() = perfStatusReporter.benchmarkState private val apks: List<File> get() = params.apks + private fun safeParse(parser: ParallelParser<*>, file: File) { + try { + parser.parse(file) + } catch (e: Exception) { + // ignore + } + } + @Test fun sequentialNoCache() { params.cacheDirToParser(null).use { parser -> while (state.keepRunning()) { - apks.forEach { parser.parse(it) } + apks.forEach { + safeParse(parser, it) + } } } } @@ -110,10 +120,10 @@ class PackageParsingPerfTest { fun sequentialCached() { params.cacheDirToParser(testFolder.newFolder()).use { parser -> // Fill the cache - apks.forEach { parser.parse(it) } + apks.forEach { safeParse(parser, it) } while (state.keepRunning()) { - apks.forEach { parser.parse(it) } + apks.forEach { safeParse(parser, it) } } } } @@ -132,7 +142,7 @@ class PackageParsingPerfTest { fun parallelCached() { params.cacheDirToParser(testFolder.newFolder()).use { parser -> // Fill the cache - apks.forEach { parser.parse(it) } + apks.forEach { safeParse(parser, it) } while (state.keepRunning()) { apks.forEach { parser.submit(it) } diff --git a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java index df690d00a322..b1b733a599c6 100644 --- a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java +++ b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java @@ -108,6 +108,8 @@ public class PowerWhitelistManager { * @hide */ public static final int REASON_DENIED = -1; + + /* Reason code range 0-9 are reserved for default reasons */ /** * The default reason code if reason is unknown. */ @@ -116,6 +118,8 @@ public class PowerWhitelistManager { * Use REASON_OTHER if there is no better choice. */ public static final int REASON_OTHER = 1; + + /* Reason code range 10-49 are reserved for BG-FGS-launch allowed proc states */ /** @hide */ public static final int REASON_PROC_STATE_PERSISTENT = 10; /** @hide */ @@ -128,6 +132,8 @@ public class PowerWhitelistManager { public static final int REASON_PROC_STATE_FGS = 14; /** @hide */ public static final int REASON_PROC_STATE_BFGS = 15; + + /* Reason code range 50-99 are reserved for BG-FGS-launch allowed reasons */ /** @hide */ public static final int REASON_UID_VISIBLE = 50; /** @hide */ @@ -166,114 +172,126 @@ public class PowerWhitelistManager { public static final int REASON_EXEMPTED_PACKAGE = 64; /** @hide */ public static final int REASON_ALLOWLISTED_PACKAGE = 65; - /** - * If it's because of a role, - * @hide - */ + /** @hide */ public static final int REASON_APPOP = 66; /* BG-FGS-launch is allowed by temp-allowlist or system-allowlist. - Reason code for temp and system allowlist starts here. - */ + Reason code for temp and system allowlist starts here. + Reason code range 100-199 are reserved for public reasons. */ + /** + * Set temp-allowlist for location geofence purpose. + */ public static final int REASON_GEOFENCING = 100; + /** + * Set temp-allowlist for server push messaging. + */ public static final int REASON_PUSH_MESSAGING = 101; - public static final int REASON_ACTIVITY_RECOGNITION = 102; + /** + * Set temp-allowlist for server push messaging over the quota. + */ + public static final int REASON_PUSH_MESSAGING_OVER_QUOTA = 102; + /** + * Set temp-allowlist for activity recognition. + */ + public static final int REASON_ACTIVITY_RECOGNITION = 103; + /* Reason code range 200-299 are reserved for broadcast actions */ /** * Broadcast ACTION_BOOT_COMPLETED. * @hide */ - public static final int REASON_BOOT_COMPLETED = 103; + public static final int REASON_BOOT_COMPLETED = 200; /** * Broadcast ACTION_PRE_BOOT_COMPLETED. * @hide */ - public static final int REASON_PRE_BOOT_COMPLETED = 104; - + public static final int REASON_PRE_BOOT_COMPLETED = 201; /** * Broadcast ACTION_LOCKED_BOOT_COMPLETED. * @hide */ - public static final int REASON_LOCKED_BOOT_COMPLETED = 105; + public static final int REASON_LOCKED_BOOT_COMPLETED = 202; + + /* Reason code range 300-399 are reserved for other internal reasons */ /** * Device idle system allowlist, including EXCEPT-IDLE * @hide */ - public static final int REASON_SYSTEM_ALLOW_LISTED = 106; + public static final int REASON_SYSTEM_ALLOW_LISTED = 300; /** @hide */ - public static final int REASON_ALARM_MANAGER_ALARM_CLOCK = 107; + public static final int REASON_ALARM_MANAGER_ALARM_CLOCK = 301; /** * AlarmManagerService. * @hide */ - public static final int REASON_ALARM_MANAGER_WHILE_IDLE = 108; + public static final int REASON_ALARM_MANAGER_WHILE_IDLE = 302; /** * ActiveServices. * @hide */ - public static final int REASON_SERVICE_LAUNCH = 109; + public static final int REASON_SERVICE_LAUNCH = 303; /** * KeyChainSystemService. * @hide */ - public static final int REASON_KEY_CHAIN = 110; + public static final int REASON_KEY_CHAIN = 304; /** * PackageManagerService. * @hide */ - public static final int REASON_PACKAGE_VERIFIER = 111; + public static final int REASON_PACKAGE_VERIFIER = 305; /** * SyncManager. * @hide */ - public static final int REASON_SYNC_MANAGER = 112; + public static final int REASON_SYNC_MANAGER = 306; /** * DomainVerificationProxyV1. * @hide */ - public static final int REASON_DOMAIN_VERIFICATION_V1 = 113; + public static final int REASON_DOMAIN_VERIFICATION_V1 = 307; /** * DomainVerificationProxyV2. * @hide */ - public static final int REASON_DOMAIN_VERIFICATION_V2 = 114; + public static final int REASON_DOMAIN_VERIFICATION_V2 = 308; /** @hide */ - public static final int REASON_VPN = 115; + public static final int REASON_VPN = 309; /** * NotificationManagerService. * @hide */ - public static final int REASON_NOTIFICATION_SERVICE = 116; + public static final int REASON_NOTIFICATION_SERVICE = 310; /** * Broadcast ACTION_MY_PACKAGE_REPLACED. * @hide */ - public static final int REASON_PACKAGE_REPLACED = 117; + public static final int REASON_PACKAGE_REPLACED = 311; /** * LocationProviderManager. * @hide */ - public static final int REASON_LOCATION_PROVIDER = 118; + public static final int REASON_LOCATION_PROVIDER = 312; /** * MediaButtonReceiver. * @hide */ - public static final int REASON_MEDIA_BUTTON = 119; + public static final int REASON_MEDIA_BUTTON = 313; /** * InboundSmsHandler. * @hide */ - public static final int REASON_EVENT_SMS = 120; + public static final int REASON_EVENT_SMS = 314; /** * InboundSmsHandler. * @hide */ - public static final int REASON_EVENT_MMS = 121; + public static final int REASON_EVENT_MMS = 315; /** * Shell app. * @hide */ - public static final int REASON_SHELL = 122; + public static final int REASON_SHELL = 316; /** * The list of BG-FGS-Launch and temp-allowlist reason code. @@ -310,6 +328,7 @@ public class PowerWhitelistManager { // temp and system allowlist reasons. REASON_GEOFENCING, REASON_PUSH_MESSAGING, + REASON_PUSH_MESSAGING_OVER_QUOTA, REASON_ACTIVITY_RECOGNITION, REASON_BOOT_COMPLETED, REASON_PRE_BOOT_COMPLETED, @@ -589,6 +608,8 @@ public class PowerWhitelistManager { return "GEOFENCING"; case REASON_PUSH_MESSAGING: return "PUSH_MESSAGING"; + case REASON_PUSH_MESSAGING_OVER_QUOTA: + return "PUSH_MESSAGING_OVER_QUOTA"; case REASON_ACTIVITY_RECOGNITION: return "ACTIVITY_RECOGNITION"; case REASON_BOOT_COMPLETED: diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java b/apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java index 34ba753b3daa..862d8b7cac50 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java @@ -25,7 +25,9 @@ import com.android.server.job.controllers.JobStatus; public interface JobCompletedListener { /** * Callback for when a job is completed. + * + * @param stopReason The stop reason provided to JobParameters. * @param needsReschedule Whether the implementing class should reschedule this job. */ - void onJobCompletedLocked(JobStatus jobStatus, boolean needsReschedule); + void onJobCompletedLocked(JobStatus jobStatus, int stopReason, boolean needsReschedule); } 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 e8e2c27f1554..0308d68d6a56 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -73,20 +73,48 @@ class JobConcurrencyManager { CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms"; private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000; - // Try to give higher priority types lower values. + /** + * Set of possible execution types that a job can have. The actual type(s) of a job are based + * on the {@link JobStatus#lastEvaluatedPriority}, which is typically evaluated right before + * execution (when we're trying to determine which jobs to run next) and won't change after the + * job has started executing. + * + * Try to give higher priority types lower values. + * + * @see #getJobWorkTypes(JobStatus) + */ + + /** Job shouldn't run or qualify as any other work type. */ static final int WORK_TYPE_NONE = 0; + /** The job is for an app in the TOP state for a currently active user. */ static final int WORK_TYPE_TOP = 1 << 0; - static final int WORK_TYPE_EJ = 1 << 1; - static final int WORK_TYPE_BG = 1 << 2; - static final int WORK_TYPE_BGUSER = 1 << 3; + /** + * The job is for an app in a {@link ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE} or higher + * state (excluding {@link ActivityManager#PROCESS_STATE_TOP} for a currently active user. + */ + static final int WORK_TYPE_FGS = 1 << 1; + /** The job is allowed to run as an expedited job for a currently active user. */ + static final int WORK_TYPE_EJ = 1 << 2; + /** + * The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP}, + * {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a currently active user, so + * can run as a background job. + */ + static final int WORK_TYPE_BG = 1 << 3; + /** + * The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP}, + * {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a completely background user, + * so can run as a background user job. + */ + static final int WORK_TYPE_BGUSER = 1 << 4; @VisibleForTesting - static final int NUM_WORK_TYPES = 4; - private static final int ALL_WORK_TYPES = - WORK_TYPE_TOP | WORK_TYPE_EJ | WORK_TYPE_BG | WORK_TYPE_BGUSER; + static final int NUM_WORK_TYPES = 5; + private static final int ALL_WORK_TYPES = (1 << NUM_WORK_TYPES) - 1; @IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = { WORK_TYPE_NONE, WORK_TYPE_TOP, + WORK_TYPE_FGS, WORK_TYPE_EJ, WORK_TYPE_BG, WORK_TYPE_BGUSER @@ -95,12 +123,15 @@ class JobConcurrencyManager { public @interface WorkType { } - private static String workTypeToString(@WorkType int workType) { + @VisibleForTesting + static String workTypeToString(@WorkType int workType) { switch (workType) { case WORK_TYPE_NONE: return "NONE"; case WORK_TYPE_TOP: return "TOP"; + case WORK_TYPE_FGS: + return "FGS"; case WORK_TYPE_EJ: return "EJ"; case WORK_TYPE_BG: @@ -131,58 +162,60 @@ class JobConcurrencyManager { new WorkConfigLimitsPerMemoryTrimLevel( new WorkTypeConfig("screen_on_normal", 11, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_EJ, 3), - Pair.create(WORK_TYPE_BG, 2)), + List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_FGS, 1), + Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2)), // defaultMax List.of(Pair.create(WORK_TYPE_BG, 6), Pair.create(WORK_TYPE_BGUSER, 4)) ), new WorkTypeConfig("screen_on_moderate", 9, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 2), - Pair.create(WORK_TYPE_BG, 2)), + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), + Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2)), // defaultMax List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)) ), new WorkTypeConfig("screen_on_low", 6, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 1), - Pair.create(WORK_TYPE_BG, 1)), + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), + Pair.create(WORK_TYPE_EJ, 1)), // defaultMax List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1)) ), - new WorkTypeConfig("screen_on_critical", 5, + new WorkTypeConfig("screen_on_critical", 6, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 1)), + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), + Pair.create(WORK_TYPE_EJ, 1)), // defaultMax List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1)) ) ); private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_OFF = new WorkConfigLimitsPerMemoryTrimLevel( - new WorkTypeConfig("screen_off_normal", 13, + new WorkTypeConfig("screen_off_normal", 15, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 3), - Pair.create(WORK_TYPE_BG, 2)), + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 2), + Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2)), // defaultMax List.of(Pair.create(WORK_TYPE_BG, 6), Pair.create(WORK_TYPE_BGUSER, 4)) ), - new WorkTypeConfig("screen_off_moderate", 13, + new WorkTypeConfig("screen_off_moderate", 15, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_EJ, 3), - Pair.create(WORK_TYPE_BG, 2)), + List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_FGS, 2), + Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2)), // defaultMax List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)) ), - new WorkTypeConfig("screen_off_low", 7, + new WorkTypeConfig("screen_off_low", 9, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 2), - Pair.create(WORK_TYPE_BG, 1)), + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), + Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1)), // defaultMax List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1)) ), - new WorkTypeConfig("screen_off_critical", 5, + new WorkTypeConfig("screen_off_critical", 6, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 1)), + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), + Pair.create(WORK_TYPE_EJ, 1)), // defaultMax List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1)) ) @@ -977,10 +1010,11 @@ class JobConcurrencyManager { int getJobWorkTypes(@NonNull JobStatus js) { int classification = 0; - // TODO: create dedicated work type for FGS if (shouldRunAsFgUserJob(js)) { if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { classification |= WORK_TYPE_TOP; + } else if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_FOREGROUND_SERVICE) { + classification |= WORK_TYPE_FGS; } else { classification |= WORK_TYPE_BG; } @@ -1001,11 +1035,13 @@ class JobConcurrencyManager { private static final String KEY_PREFIX_MAX_TOTAL = CONFIG_KEY_PREFIX_CONCURRENCY + "max_total_"; private static final String KEY_PREFIX_MAX_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "max_top_"; + private static final String KEY_PREFIX_MAX_FGS = CONFIG_KEY_PREFIX_CONCURRENCY + "max_fgs_"; private static final String KEY_PREFIX_MAX_EJ = CONFIG_KEY_PREFIX_CONCURRENCY + "max_ej_"; private static final String KEY_PREFIX_MAX_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "max_bg_"; private static final String KEY_PREFIX_MAX_BGUSER = CONFIG_KEY_PREFIX_CONCURRENCY + "max_bguser_"; private static final String KEY_PREFIX_MIN_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "min_top_"; + private static final String KEY_PREFIX_MIN_FGS = CONFIG_KEY_PREFIX_CONCURRENCY + "min_fgs_"; private static final String KEY_PREFIX_MIN_EJ = CONFIG_KEY_PREFIX_CONCURRENCY + "min_ej_"; private static final String KEY_PREFIX_MIN_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "min_bg_"; private static final String KEY_PREFIX_MIN_BGUSER = @@ -1053,6 +1089,10 @@ class JobConcurrencyManager { properties.getInt(KEY_PREFIX_MAX_TOP + mConfigIdentifier, mDefaultMaxAllowedSlots.get(WORK_TYPE_TOP, mMaxTotal)))); mMaxAllowedSlots.put(WORK_TYPE_TOP, maxTop); + final int maxFgs = Math.max(1, Math.min(mMaxTotal, + properties.getInt(KEY_PREFIX_MAX_FGS + mConfigIdentifier, + mDefaultMaxAllowedSlots.get(WORK_TYPE_FGS, mMaxTotal)))); + mMaxAllowedSlots.put(WORK_TYPE_FGS, maxFgs); final int maxEj = Math.max(1, Math.min(mMaxTotal, properties.getInt(KEY_PREFIX_MAX_EJ + mConfigIdentifier, mDefaultMaxAllowedSlots.get(WORK_TYPE_EJ, mMaxTotal)))); @@ -1074,6 +1114,12 @@ class JobConcurrencyManager { mDefaultMinReservedSlots.get(WORK_TYPE_TOP)))); mMinReservedSlots.put(WORK_TYPE_TOP, minTop); remaining -= minTop; + // Ensure fgs is in the range [0, min(maxFgs, remaining)] + final int minFgs = Math.max(0, Math.min(Math.min(maxFgs, remaining), + properties.getInt(KEY_PREFIX_MIN_FGS + mConfigIdentifier, + mDefaultMinReservedSlots.get(WORK_TYPE_FGS)))); + mMinReservedSlots.put(WORK_TYPE_FGS, minFgs); + remaining -= minFgs; // Ensure ej is in the range [0, min(maxEj, remaining)] final int minEj = Math.max(0, Math.min(Math.min(maxEj, remaining), properties.getInt(KEY_PREFIX_MIN_EJ + mConfigIdentifier, @@ -1111,6 +1157,10 @@ class JobConcurrencyManager { .println(); pw.print(KEY_PREFIX_MAX_TOP + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_TOP)) .println(); + pw.print(KEY_PREFIX_MIN_FGS + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_FGS)) + .println(); + pw.print(KEY_PREFIX_MAX_FGS + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_FGS)) + .println(); pw.print(KEY_PREFIX_MIN_EJ + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_EJ)) .println(); pw.print(KEY_PREFIX_MAX_EJ + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_EJ)) @@ -1205,6 +1255,7 @@ class JobConcurrencyManager { private int mConfigMaxTotal; private final SparseIntArray mConfigNumReservedSlots = new SparseIntArray(NUM_WORK_TYPES); private final SparseIntArray mConfigAbsoluteMaxSlots = new SparseIntArray(NUM_WORK_TYPES); + private final SparseIntArray mRecycledReserved = new SparseIntArray(NUM_WORK_TYPES); /** * Numbers may be lower in this than in {@link #mConfigNumReservedSlots} if there aren't @@ -1220,11 +1271,14 @@ class JobConcurrencyManager { mConfigMaxTotal = workTypeConfig.getMaxTotal(); mConfigNumReservedSlots.put(WORK_TYPE_TOP, workTypeConfig.getMinReserved(WORK_TYPE_TOP)); + mConfigNumReservedSlots.put(WORK_TYPE_FGS, + workTypeConfig.getMinReserved(WORK_TYPE_FGS)); mConfigNumReservedSlots.put(WORK_TYPE_EJ, workTypeConfig.getMinReserved(WORK_TYPE_EJ)); mConfigNumReservedSlots.put(WORK_TYPE_BG, workTypeConfig.getMinReserved(WORK_TYPE_BG)); mConfigNumReservedSlots.put(WORK_TYPE_BGUSER, workTypeConfig.getMinReserved(WORK_TYPE_BGUSER)); mConfigAbsoluteMaxSlots.put(WORK_TYPE_TOP, workTypeConfig.getMax(WORK_TYPE_TOP)); + mConfigAbsoluteMaxSlots.put(WORK_TYPE_FGS, workTypeConfig.getMax(WORK_TYPE_FGS)); mConfigAbsoluteMaxSlots.put(WORK_TYPE_EJ, workTypeConfig.getMax(WORK_TYPE_EJ)); mConfigAbsoluteMaxSlots.put(WORK_TYPE_BG, workTypeConfig.getMax(WORK_TYPE_BG)); mConfigAbsoluteMaxSlots.put(WORK_TYPE_BGUSER, workTypeConfig.getMax(WORK_TYPE_BGUSER)); @@ -1260,15 +1314,10 @@ class JobConcurrencyManager { // We don't need to adjust reservations if only one work type was modified // because that work type is the one we're using. - // 0 is WORK_TYPE_NONE. - int workType = 1; - int rem = workTypes; - while (rem > 0) { - if ((rem & 1) != 0) { + for (int workType = 1; workType <= workTypes; workType <<= 1) { + if ((workType & workTypes) == workType) { maybeAdjustReservations(workType); } - rem = rem >>> 1; - workType = workType << 1; } } } @@ -1280,21 +1329,11 @@ class JobConcurrencyManager { int numAdj = 0; // We don't know which type we'll classify the job as when we run it yet, so make sure // we have space in all applicable slots. - if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) { - mNumPendingJobs.put(WORK_TYPE_TOP, mNumPendingJobs.get(WORK_TYPE_TOP) + adj); - numAdj++; - } - if ((workTypes & WORK_TYPE_EJ) == WORK_TYPE_EJ) { - mNumPendingJobs.put(WORK_TYPE_EJ, mNumPendingJobs.get(WORK_TYPE_EJ) + adj); - numAdj++; - } - if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) { - mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + adj); - numAdj++; - } - if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) { - mNumPendingJobs.put(WORK_TYPE_BGUSER, mNumPendingJobs.get(WORK_TYPE_BGUSER) + adj); - numAdj++; + for (int workType = 1; workType <= workTypes; workType <<= 1) { + if ((workTypes & workType) == workType) { + mNumPendingJobs.put(workType, mNumPendingJobs.get(workType) + adj); + numAdj++; + } } return numAdj; @@ -1388,105 +1427,45 @@ class JobConcurrencyManager { mNumUnspecializedRemaining = mConfigMaxTotal; // Step 1 - int runTop = mNumRunningJobs.get(WORK_TYPE_TOP); - int resTop = runTop; - mNumUnspecializedRemaining -= resTop; - int runEj = mNumRunningJobs.get(WORK_TYPE_EJ); - int resEj = runEj; - mNumUnspecializedRemaining -= resEj; - int runBg = mNumRunningJobs.get(WORK_TYPE_BG); - int resBg = runBg; - mNumUnspecializedRemaining -= resBg; - int runBgUser = mNumRunningJobs.get(WORK_TYPE_BGUSER); - int resBgUser = runBgUser; - mNumUnspecializedRemaining -= resBgUser; + for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) { + int run = mNumRunningJobs.get(workType); + mRecycledReserved.put(workType, run); + mNumUnspecializedRemaining -= run; + } // Step 2 - final int numTop = runTop + mNumPendingJobs.get(WORK_TYPE_TOP); - int fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining, - Math.min(numTop, mConfigNumReservedSlots.get(WORK_TYPE_TOP) - resTop))); - resTop += fillUp; - mNumUnspecializedRemaining -= fillUp; - final int numEj = runEj + mNumPendingJobs.get(WORK_TYPE_EJ); - fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining, - Math.min(numEj, mConfigNumReservedSlots.get(WORK_TYPE_EJ) - resEj))); - resEj += fillUp; - mNumUnspecializedRemaining -= fillUp; - final int numBg = runBg + mNumPendingJobs.get(WORK_TYPE_BG); - fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining, - Math.min(numBg, mConfigNumReservedSlots.get(WORK_TYPE_BG) - resBg))); - resBg += fillUp; - mNumUnspecializedRemaining -= fillUp; - final int numBgUser = runBgUser + mNumPendingJobs.get(WORK_TYPE_BGUSER); - fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining, - Math.min(numBgUser, - mConfigNumReservedSlots.get(WORK_TYPE_BGUSER) - resBgUser))); - resBgUser += fillUp; - mNumUnspecializedRemaining -= fillUp; + for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) { + int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType); + int res = mRecycledReserved.get(workType); + int fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining, + Math.min(num, mConfigNumReservedSlots.get(workType) - res))); + res += fillUp; + mRecycledReserved.put(workType, res); + mNumUnspecializedRemaining -= fillUp; + } // Step 3 - int unspecializedAssigned = Math.max(0, - Math.min(mNumUnspecializedRemaining, - Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP), numTop) - resTop)); - mNumActuallyReservedSlots.put(WORK_TYPE_TOP, resTop + unspecializedAssigned); - mNumUnspecializedRemaining -= unspecializedAssigned; - - unspecializedAssigned = Math.max(0, - Math.min(mNumUnspecializedRemaining, - Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_EJ), numEj) - resEj)); - mNumActuallyReservedSlots.put(WORK_TYPE_EJ, resEj + unspecializedAssigned); - mNumUnspecializedRemaining -= unspecializedAssigned; - - unspecializedAssigned = Math.max(0, - Math.min(mNumUnspecializedRemaining, - Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG), numBg) - resBg)); - mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg + unspecializedAssigned); - mNumUnspecializedRemaining -= unspecializedAssigned; - - unspecializedAssigned = Math.max(0, - Math.min(mNumUnspecializedRemaining, - Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BGUSER), numBgUser) - - resBgUser)); - mNumActuallyReservedSlots.put(WORK_TYPE_BGUSER, resBgUser + unspecializedAssigned); - mNumUnspecializedRemaining -= unspecializedAssigned; + for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) { + int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType); + int res = mRecycledReserved.get(workType); + int unspecializedAssigned = Math.max(0, + Math.min(mNumUnspecializedRemaining, + Math.min(mConfigAbsoluteMaxSlots.get(workType), num) - res)); + mNumActuallyReservedSlots.put(workType, res + unspecializedAssigned); + mNumUnspecializedRemaining -= unspecializedAssigned; + } } int canJobStart(int workTypes) { - if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) { - final int maxAllowed = Math.min( - mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP), - mNumActuallyReservedSlots.get(WORK_TYPE_TOP) + mNumUnspecializedRemaining); - if (mNumRunningJobs.get(WORK_TYPE_TOP) + mNumStartingJobs.get(WORK_TYPE_TOP) - < maxAllowed) { - return WORK_TYPE_TOP; - } - } - if ((workTypes & WORK_TYPE_EJ) == WORK_TYPE_EJ) { - final int maxAllowed = Math.min( - mConfigAbsoluteMaxSlots.get(WORK_TYPE_EJ), - mNumActuallyReservedSlots.get(WORK_TYPE_EJ) + mNumUnspecializedRemaining); - if (mNumRunningJobs.get(WORK_TYPE_EJ) + mNumStartingJobs.get(WORK_TYPE_EJ) - < maxAllowed) { - return WORK_TYPE_EJ; - } - } - if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) { - final int maxAllowed = Math.min( - mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG), - mNumActuallyReservedSlots.get(WORK_TYPE_BG) + mNumUnspecializedRemaining); - if (mNumRunningJobs.get(WORK_TYPE_BG) + mNumStartingJobs.get(WORK_TYPE_BG) - < maxAllowed) { - return WORK_TYPE_BG; - } - } - if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) { - final int maxAllowed = Math.min( - mConfigAbsoluteMaxSlots.get(WORK_TYPE_BGUSER), - mNumActuallyReservedSlots.get(WORK_TYPE_BGUSER) - + mNumUnspecializedRemaining); - if (mNumRunningJobs.get(WORK_TYPE_BGUSER) + mNumStartingJobs.get(WORK_TYPE_BGUSER) - < maxAllowed) { - return WORK_TYPE_BGUSER; + for (int workType = 1; workType <= workTypes; workType <<= 1) { + if ((workTypes & workType) == workType) { + final int maxAllowed = Math.min( + mConfigAbsoluteMaxSlots.get(workType), + mNumActuallyReservedSlots.get(workType) + mNumUnspecializedRemaining); + if (mNumRunningJobs.get(workType) + mNumStartingJobs.get(workType) + < maxAllowed) { + return workType; + } } } return WORK_TYPE_NONE; diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 8bb03e911528..515cb747a99e 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -1745,11 +1745,12 @@ public class JobSchedulerService extends com.android.server.SystemService * A job just finished executing. We fetch the * {@link com.android.server.job.controllers.JobStatus} from the store and depending on * whether we want to reschedule we re-add it to the controllers. - * @param jobStatus Completed job. + * + * @param jobStatus Completed job. * @param needsReschedule Whether the implementing class should reschedule this job. */ @Override - public void onJobCompletedLocked(JobStatus jobStatus, boolean needsReschedule) { + public void onJobCompletedLocked(JobStatus jobStatus, int stopReason, boolean needsReschedule) { if (DEBUG) { Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule); } @@ -1767,6 +1768,11 @@ public class JobSchedulerService extends com.android.server.SystemService // we stop it. final JobStatus rescheduledJob = needsReschedule ? getRescheduleJobForFailureLocked(jobStatus) : null; + if (rescheduledJob != null + && (stopReason == JobParameters.REASON_TIMEOUT + || stopReason == JobParameters.REASON_PREEMPT)) { + rescheduledJob.disallowRunInBatterySaverAndDoze(); + } // Do not write back immediately if this is a periodic job. The job may get lost if system // shuts down before it is added back. diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index 2a23d60d8af6..96734499ee20 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -273,10 +273,12 @@ public final class JobServiceContext implements ServiceConnection { // another binding flag for that. bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_ALMOST_PERCEPTIBLE - | Context.BIND_ALLOW_NETWORK_ACCESS; + | Context.BIND_ALLOW_NETWORK_ACCESS + | Context.BIND_NOT_APP_COMPONENT_USAGE; } else { bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND - | Context.BIND_NOT_PERCEPTIBLE; + | Context.BIND_NOT_PERCEPTIBLE + | Context.BIND_NOT_APP_COMPONENT_USAGE; } binding = mContext.bindServiceAsUser(intent, this, bindFlags, UserHandle.of(job.getUserId())); @@ -379,8 +381,8 @@ public final class JobServiceContext implements ServiceConnection { } boolean isWithinExecutionGuaranteeTime() { - return mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis - < sElapsedRealtimeClock.millis(); + return sElapsedRealtimeClock.millis() + < mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis; } @GuardedBy("mLock") @@ -848,11 +850,12 @@ public final class JobServiceContext implements ServiceConnection { } applyStoppedReasonLocked(reason); completedJob = mRunningJob; - mJobPackageTracker.noteInactive(completedJob, mParams.getStopReason(), reason); + final int stopReason = mParams.getStopReason(); + mJobPackageTracker.noteInactive(completedJob, stopReason, reason); FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED, completedJob.getSourceUid(), null, completedJob.getBatteryName(), FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__FINISHED, - mParams.getStopReason(), completedJob.getStandbyBucket(), completedJob.getJobId(), + stopReason, completedJob.getStandbyBucket(), completedJob.getJobId(), completedJob.hasChargingConstraint(), completedJob.hasBatteryNotLowConstraint(), completedJob.hasStorageNotLowConstraint(), @@ -863,7 +866,7 @@ public final class JobServiceContext implements ServiceConnection { completedJob.hasContentTriggerConstraint()); try { mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(), - mParams.getStopReason()); + stopReason); } catch (RemoteException e) { // Whatever. } @@ -882,7 +885,7 @@ public final class JobServiceContext implements ServiceConnection { service = null; mAvailable = true; removeOpTimeOutLocked(); - mCompletedListener.onJobCompletedLocked(completedJob, reschedule); + mCompletedListener.onJobCompletedLocked(completedJob, stopReason, reschedule); mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java index eaf8f4d96331..aa8d98c01853 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java @@ -954,7 +954,7 @@ public final class JobStore { appBucket, sourceTag, elapsedRuntimes.first, elapsedRuntimes.second, lastSuccessfulRunTime, lastFailedRunTime, - (rtcIsGood) ? null : rtcRuntimes, internalFlags); + (rtcIsGood) ? null : rtcRuntimes, internalFlags, /* dynamicConstraints */ 0); return js; } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java index 192f5e66255d..79ef321eaf07 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java @@ -64,7 +64,7 @@ public final class DeviceIdleJobsController extends StateController { * when the app is temp whitelisted or in the foreground. */ private final ArraySet<JobStatus> mAllowInIdleJobs; - private final SparseBooleanArray mForegroundUids; + private final SparseBooleanArray mForegroundUids = new SparseBooleanArray(); private final DeviceIdleUpdateFunctor mDeviceIdleUpdateFunctor; private final DeviceIdleJobsDelayHandler mHandler; private final PowerManager mPowerManager; @@ -77,7 +77,6 @@ public final class DeviceIdleJobsController extends StateController { private int[] mDeviceIdleWhitelistAppIds; private int[] mPowerSaveTempWhitelistAppIds; - // onReceive private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -120,6 +119,10 @@ public final class DeviceIdleJobsController extends StateController { } }; + /** Criteria for whether or not we should a job's rush evaluation when the device exits Doze. */ + private final Predicate<JobStatus> mShouldRushEvaluation = (jobStatus) -> + jobStatus.isRequestedExpeditedJob() || mForegroundUids.get(jobStatus.getSourceUid()); + public DeviceIdleJobsController(JobSchedulerService service) { super(service); @@ -133,7 +136,6 @@ public final class DeviceIdleJobsController extends StateController { mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds(); mDeviceIdleUpdateFunctor = new DeviceIdleUpdateFunctor(); mAllowInIdleJobs = new ArraySet<>(); - mForegroundUids = new SparseBooleanArray(); final IntentFilter filter = new IntentFilter(); filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED); @@ -156,14 +158,9 @@ public final class DeviceIdleJobsController extends StateController { mHandler.removeMessages(PROCESS_BACKGROUND_JOBS); mService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor); } else { - // When coming out of doze, process all foreground uids immediately, while others - // will be processed after a delay of 3 seconds. - for (int i = 0; i < mForegroundUids.size(); i++) { - if (mForegroundUids.valueAt(i)) { - mService.getJobStore().forEachJobForSourceUid( - mForegroundUids.keyAt(i), mDeviceIdleUpdateFunctor); - } - } + // When coming out of doze, process all foreground uids and EJs immediately, + // while others will be processed after a delay of 3 seconds. + mService.getJobStore().forEachJob(mShouldRushEvaluation, mDeviceIdleUpdateFunctor); mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY); } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 5bdeb38a1424..bad8dc1ad1cb 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -91,6 +91,12 @@ public final class JobStatus { static final int CONSTRAINT_WITHIN_EXPEDITED_QUOTA = 1 << 23; // Implicit constraint static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint + // The following set of dynamic constraints are for specific use cases (as explained in their + // relative naming and comments). Right now, they apply different constraints, which is fine, + // but if in the future, we have overlapping dynamic constraint sets, removing one constraint + // set may accidentally remove a constraint applied by another dynamic set. + // TODO: properly handle overlapping dynamic constraint sets + /** * The additional set of dynamic constraints that must be met if the job's effective bucket is * {@link JobSchedulerService#RESTRICTED_INDEX}. Connectivity can be ignored if the job doesn't @@ -103,6 +109,13 @@ public final class JobStatus { | CONSTRAINT_IDLE; /** + * The additional set of dynamic constraints that must be met if this is an expedited job that + * had a long enough run while the device was Dozing or in battery saver. + */ + private static final int DYNAMIC_EXPEDITED_DEFERRAL_CONSTRAINTS = + CONSTRAINT_DEVICE_NOT_DOZING | CONSTRAINT_BACKGROUND_NOT_RESTRICTED; + + /** * Standard media URIs that contain the media files that might be important to the user. * @see #mHasMediaBackupExemption */ @@ -426,7 +439,8 @@ public final class JobStatus { private JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId, int standbyBucket, String tag, int numFailures, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis, - long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags) { + long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags, + int dynamicConstraints) { this.job = job; this.callingUid = callingUid; this.standbyBucket = standbyBucket; @@ -487,6 +501,7 @@ public final class JobStatus { } this.requiredConstraints = requiredConstraints; mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST; + addDynamicConstraints(dynamicConstraints); mReadyNotDozing = canRunInDoze(); if (standbyBucket == RESTRICTED_INDEX) { addDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS); @@ -521,7 +536,7 @@ public final class JobStatus { jobStatus.getSourceTag(), jobStatus.getNumFailures(), jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(), jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(), - jobStatus.getInternalFlags()); + jobStatus.getInternalFlags(), jobStatus.mDynamicConstraints); mPersistedUtcTimes = jobStatus.mPersistedUtcTimes; if (jobStatus.mPersistedUtcTimes != null) { if (DEBUG) { @@ -543,12 +558,12 @@ public final class JobStatus { long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis, long lastSuccessfulRunTime, long lastFailedRunTime, Pair<Long, Long> persistedExecutionTimesUTC, - int innerFlags) { + int innerFlags, int dynamicConstraints) { this(job, callingUid, sourcePkgName, sourceUserId, standbyBucket, sourceTag, 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, - lastSuccessfulRunTime, lastFailedRunTime, innerFlags); + lastSuccessfulRunTime, lastFailedRunTime, innerFlags, dynamicConstraints); // Only during initial inflation do we record the UTC-timebase execution bounds // read from the persistent store. If we ever have to recreate the JobStatus on @@ -572,7 +587,8 @@ public final class JobStatus { rescheduling.getStandbyBucket(), rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis, newLatestRuntimeElapsedMillis, - lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags()); + lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags(), + rescheduling.mDynamicConstraints); } /** @@ -609,7 +625,7 @@ public final class JobStatus { standbyBucket, tag, 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */, - /*innerFlags=*/ 0); + /*innerFlags=*/ 0, /* dynamicConstraints */ 0); } public void enqueueWorkLocked(JobWorkItem work) { @@ -1083,12 +1099,15 @@ public final class JobStatus { * in Doze. */ public boolean canRunInDoze() { - return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0 || shouldTreatAsExpeditedJob(); + return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0 + || (shouldTreatAsExpeditedJob() + && (mDynamicConstraints & CONSTRAINT_DEVICE_NOT_DOZING) == 0); } boolean canRunInBatterySaver() { return (getInternalFlags() & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0 - || shouldTreatAsExpeditedJob(); + || (shouldTreatAsExpeditedJob() + && (mDynamicConstraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) == 0); } boolean shouldIgnoreNetworkBlocking() { @@ -1245,6 +1264,14 @@ public final class JobStatus { } /** + * Add additional constraints to prevent this job from running when doze or battery saver are + * active. + */ + public void disallowRunInBatterySaverAndDoze() { + addDynamicConstraints(DYNAMIC_EXPEDITED_DEFERRAL_CONSTRAINTS); + } + + /** * Indicates that this job cannot run without the specified constraints. This is evaluated * separately from the job's explicitly requested constraints and MUST be satisfied before * the job can run if the app doesn't have quota. diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp index b18a22b408f5..1d6f20dd4b27 100644 --- a/apex/media/framework/Android.bp +++ b/apex/media/framework/Android.bp @@ -47,6 +47,7 @@ java_library { static_libs: [ "exoplayer2-extractor", "mediatranscoding_aidl_interface-java", + "modules-annotation-minsdk", "modules-utils-build", ], jarjar_rules: "jarjar_rules.txt", @@ -108,7 +109,7 @@ filegroup { filegroup { name: "mediaparser-srcs", srcs: [ - "java/android/media/MediaParser.java" + "java/android/media/MediaParser.java", ], path: "java", } diff --git a/apex/media/framework/jarjar_rules.txt b/apex/media/framework/jarjar_rules.txt index eb71fddc05cb..91489dcee0a1 100644 --- a/apex/media/framework/jarjar_rules.txt +++ b/apex/media/framework/jarjar_rules.txt @@ -1,2 +1,2 @@ -rule com.android.modules.utils.** android.media.internal.utils.@1 +rule com.android.modules.** android.media.internal.@1 rule com.google.android.exoplayer2.** android.media.internal.exo.@1 diff --git a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java index 685cf0dc7f77..906071fe4c7b 100644 --- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java +++ b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java @@ -39,10 +39,12 @@ import java.util.Set; for handling newer video codec format and media features. <p> - Android 12 introduces seamless media transcoding feature. By default, Android assumes apps can - support playback of all media formats. Apps that would like to request that media be transcoded - into a more compatible format should declare their media capabilities in a media_capabilities - .xml resource file and add it as a property tag in the AndroidManifest.xml file. Here is a example: + Android 12 introduces Compatible media transcoding feature. See + <a href="https://developer.android.com/about/versions/12/features#compatible_media_transcoding"> + Compatible media transcoding</a>. By default, Android assumes apps can support playback of all + media formats. Apps that would like to request that media be transcoded into a more compatible + format should declare their media capabilities in a media_capabilities.xml resource file and add it + as a property tag in the AndroidManifest.xml file. Here is a example: <pre> {@code <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/apex/media/framework/java/android/media/MediaCommunicationManager.java b/apex/media/framework/java/android/media/MediaCommunicationManager.java index 9ec25fe48a2e..f39bcfb267bf 100644 --- a/apex/media/framework/java/android/media/MediaCommunicationManager.java +++ b/apex/media/framework/java/android/media/MediaCommunicationManager.java @@ -27,12 +27,14 @@ import android.annotation.SystemService; import android.content.Context; import android.media.session.MediaSession; import android.media.session.MediaSessionManager; +import android.os.Build; import android.os.RemoteException; import android.os.UserHandle; import android.service.media.MediaBrowserService; import android.util.Log; import com.android.internal.annotations.GuardedBy; +import com.android.modules.annotation.MinSdk; import com.android.modules.utils.build.SdkLevel; import java.util.Collections; @@ -45,6 +47,7 @@ import java.util.concurrent.Executor; * Provides support for interacting with {@link android.media.MediaSession2 MediaSession2s} * that applications have published to express their ongoing media playback state. */ +@MinSdk(Build.VERSION_CODES.S) @SystemService(Context.MEDIA_COMMUNICATION_SERVICE) public class MediaCommunicationManager { private static final String TAG = "MediaCommunicationManager"; diff --git a/apex/media/framework/java/android/media/MediaSession2.java b/apex/media/framework/java/android/media/MediaSession2.java index 6397ba3996f3..7697359e7caf 100644 --- a/apex/media/framework/java/android/media/MediaSession2.java +++ b/apex/media/framework/java/android/media/MediaSession2.java @@ -32,6 +32,7 @@ import android.annotation.Nullable; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.media.session.MediaSessionManager; import android.media.session.MediaSessionManager.RemoteUserInfo; import android.os.BadParcelableException; import android.os.Bundle; @@ -43,6 +44,8 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import com.android.modules.utils.build.SdkLevel; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -86,6 +89,7 @@ public class MediaSession2 implements AutoCloseable { private final String mSessionId; private final PendingIntent mSessionActivity; private final Session2Token mSessionToken; + private final MediaSessionManager mMediaSessionManager; private final MediaCommunicationManager mCommunicationManager; private final Handler mResultHandler; @@ -114,7 +118,13 @@ public class MediaSession2 implements AutoCloseable { mSessionStub = new Session2Link(this); mSessionToken = new Session2Token(Process.myUid(), TYPE_SESSION, context.getPackageName(), mSessionStub, tokenExtras); - mCommunicationManager = mContext.getSystemService(MediaCommunicationManager.class); + if (SdkLevel.isAtLeastS()) { + mCommunicationManager = mContext.getSystemService(MediaCommunicationManager.class); + mMediaSessionManager = null; + } else { + mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class); + mCommunicationManager = null; + } // NOTE: mResultHandler uses main looper, so this MUST NOT be blocked. mResultHandler = new Handler(context.getMainLooper()); mClosed = false; @@ -315,6 +325,14 @@ public class MediaSession2 implements AutoCloseable { return mCallback; } + boolean isTrustedForMediaControl(RemoteUserInfo remoteUserInfo) { + if (SdkLevel.isAtLeastS()) { + return mCommunicationManager.isTrustedForMediaControl(remoteUserInfo); + } else { + return mMediaSessionManager.isTrustedForMediaControl(remoteUserInfo); + } + } + void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) { synchronized (mLock) { if (mForegroundServiceEventCallback == callback) { @@ -350,7 +368,7 @@ public class MediaSession2 implements AutoCloseable { final ControllerInfo controllerInfo = new ControllerInfo( remoteUserInfo, - mCommunicationManager.isTrustedForMediaControl(remoteUserInfo), + isTrustedForMediaControl(remoteUserInfo), controller, connectionHints); mCallbackExecutor.execute(() -> { @@ -606,9 +624,15 @@ public class MediaSession2 implements AutoCloseable { // Notify framework about the newly create session after the constructor is finished. // Otherwise, framework may access the session before the initialization is finished. try { - MediaCommunicationManager manager = - mContext.getSystemService(MediaCommunicationManager.class); - manager.notifySession2Created(session2.getToken()); + if (SdkLevel.isAtLeastS()) { + MediaCommunicationManager manager = + mContext.getSystemService(MediaCommunicationManager.class); + manager.notifySession2Created(session2.getToken()); + } else { + MediaSessionManager manager = + mContext.getSystemService(MediaSessionManager.class); + manager.notifySession2Created(session2.getToken()); + } } catch (Exception e) { session2.close(); throw e; diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java index f85e30d38f86..9c044b5e632e 100644 --- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java +++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java @@ -66,6 +66,7 @@ public final class Telecom extends BaseCommand { private static final String COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT = "set-phone-acct-suggestion-component"; private static final String COMMAND_UNREGISTER_PHONE_ACCOUNT = "unregister-phone-account"; + private static final String COMMAND_SET_CALL_DIAGNOSTIC_SERVICE = "set-call-diagnostic-service"; private static final String COMMAND_SET_DEFAULT_DIALER = "set-default-dialer"; private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer"; private static final String COMMAND_STOP_BLOCK_SUPPRESSION = "stop-block-suppression"; @@ -112,6 +113,7 @@ public final class Telecom extends BaseCommand { + "usage: telecom register-sim-phone-account <COMPONENT> <ID> <USER_SN>" + " <LABEL> <ADDRESS>\n" + "usage: telecom unregister-phone-account <COMPONENT> <ID> <USER_SN>\n" + + "usage: telecom set-call-diagnostic-service <PACKAGE>\n" + "usage: telecom set-default-dialer <PACKAGE>\n" + "usage: telecom get-default-dialer\n" + "usage: telecom get-system-dialer\n" @@ -131,6 +133,7 @@ public final class Telecom extends BaseCommand { + "telecom set-phone-account-disabled: Disables the given phone account, if it" + " has already been registered with telecom.\n" + "\n" + + "telecom set-call-diagnostic-service: overrides call diagnostic service.\n" + "telecom set-default-dialer: Sets the override default dialer to the given" + " component; this will override whatever the dialer role is set to.\n" + "\n" @@ -206,6 +209,9 @@ public final class Telecom extends BaseCommand { case COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT: runSetTestPhoneAcctSuggestionComponent(); break; + case COMMAND_SET_CALL_DIAGNOSTIC_SERVICE: + runSetCallDiagnosticService(); + break; case COMMAND_REGISTER_SIM_PHONE_ACCOUNT: runRegisterSimPhoneAccount(); break; @@ -323,6 +329,13 @@ public final class Telecom extends BaseCommand { mTelecomService.addOrRemoveTestCallCompanionApp(packageName, isAddedBool); } + private void runSetCallDiagnosticService() throws RemoteException { + String packageName = nextArg(); + if ("default".equals(packageName)) packageName = null; + mTelecomService.setTestCallDiagnosticService(packageName); + System.out.println("Success - " + packageName + " set as call diagnostic service."); + } + private void runSetTestPhoneAcctSuggestionComponent() throws RemoteException { final String componentName = nextArg(); mTelecomService.setTestPhoneAcctSuggestionComponent(componentName); diff --git a/core/api/current.txt b/core/api/current.txt index 61d30de25147..a06f9943bde2 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -11768,6 +11768,7 @@ package android.content.pm { method public boolean isResourceOverlay(); method public boolean isVirtualPreload(); method public CharSequence loadDescription(android.content.pm.PackageManager); + field public static final int CATEGORY_ACCESSIBILITY = 8; // 0x8 field public static final int CATEGORY_AUDIO = 1; // 0x1 field public static final int CATEGORY_GAME = 0; // 0x0 field public static final int CATEGORY_IMAGE = 3; // 0x3 @@ -12947,6 +12948,27 @@ package android.content.pm { } +package android.content.pm.verify.domain { + + public final class DomainVerificationManager { + method @Nullable public android.content.pm.verify.domain.DomainVerificationUserState getDomainVerificationUserState(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + } + + public final class DomainVerificationUserState implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getHostToStateMap(); + method @NonNull public String getPackageName(); + method @NonNull public android.os.UserHandle getUser(); + method @NonNull public boolean isLinkHandlingAllowed(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationUserState> CREATOR; + field public static final int DOMAIN_STATE_NONE = 0; // 0x0 + field public static final int DOMAIN_STATE_SELECTED = 1; // 0x1 + field public static final int DOMAIN_STATE_VERIFIED = 2; // 0x2 + } + +} + package android.content.res { public class AssetFileDescriptor implements java.io.Closeable android.os.Parcelable { @@ -17535,6 +17557,9 @@ package android.hardware.biometrics { public class BiometricManager { method @Deprecated @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate(); method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate(int); + method @Nullable @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public CharSequence getButtonLabel(int); + method @Nullable @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public CharSequence getPromptMessage(int); + method @Nullable @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public CharSequence getSettingName(int); field public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; // 0x1 field public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11; // 0xb field public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12; // 0xc @@ -20300,6 +20325,10 @@ package android.media { field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_BIT_WIDTH; field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_CHANNEL_MASK; field @NonNull public static final android.media.AudioMetadata.Key<java.lang.String> KEY_MIME; + field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_PRESENTATION_CONTENT_CLASSIFIER; + field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_PRESENTATION_ID; + field @NonNull public static final android.media.AudioMetadata.Key<java.lang.String> KEY_PRESENTATION_LANGUAGE; + field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_PROGRAM_ID; field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_SAMPLE_RATE; } @@ -20354,6 +20383,15 @@ package android.media { method public boolean hasAudioDescription(); method public boolean hasDialogueEnhancement(); method public boolean hasSpokenSubtitles(); + field public static final int CONTENT_COMMENTARY = 5; // 0x5 + field public static final int CONTENT_DIALOG = 4; // 0x4 + field public static final int CONTENT_EMERGENCY = 6; // 0x6 + field public static final int CONTENT_HEARING_IMPAIRED = 3; // 0x3 + field public static final int CONTENT_MAIN = 0; // 0x0 + field public static final int CONTENT_MUSIC_AND_EFFECTS = 1; // 0x1 + field public static final int CONTENT_UNKNOWN = -1; // 0xffffffff + field public static final int CONTENT_VISUALLY_IMPAIRED = 2; // 0x2 + field public static final int CONTENT_VOICEOVER = 7; // 0x7 field public static final int MASTERED_FOR_3D = 3; // 0x3 field public static final int MASTERED_FOR_HEADPHONE = 4; // 0x4 field public static final int MASTERED_FOR_STEREO = 1; // 0x1 @@ -25887,6 +25925,7 @@ package android.net { method @NonNull public static java.util.Set<java.lang.String> getSupportedAlgorithms(); method public int getTruncationLengthBits(); method public void writeToParcel(android.os.Parcel, int); + field public static final String AUTH_AES_CMAC = "cmac(aes)"; field public static final String AUTH_AES_XCBC = "xcbc(aes)"; field public static final String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))"; field public static final String AUTH_CRYPT_CHACHA20_POLY1305 = "rfc7539esp(chacha20,poly1305)"; @@ -26028,6 +26067,16 @@ package android.net { field public static final int TYPE_IKEV2_IPSEC_USER_PASS = 6; // 0x6 } + public final class Proxy { + ctor public Proxy(); + method @Deprecated public static String getDefaultHost(); + method @Deprecated public static int getDefaultPort(); + method @Deprecated public static String getHost(android.content.Context); + method @Deprecated public static int getPort(android.content.Context); + field @Deprecated public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO"; + field public static final String PROXY_CHANGE_ACTION = "android.intent.action.PROXY_CHANGE"; + } + @Deprecated public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory { ctor @Deprecated public SSLCertificateSocketFactory(int); method @Deprecated public java.net.Socket createSocket(java.net.Socket, String, int, boolean) throws java.io.IOException; @@ -26699,7 +26748,22 @@ package android.net.vcn { public class VcnManager { method @RequiresPermission("carrier privileges") public void clearVcnConfig(@NonNull android.os.ParcelUuid) throws java.io.IOException; + method public void registerVcnStatusCallback(@NonNull android.os.ParcelUuid, @NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnStatusCallback); method @RequiresPermission("carrier privileges") public void setVcnConfig(@NonNull android.os.ParcelUuid, @NonNull android.net.vcn.VcnConfig) throws java.io.IOException; + method public void unregisterVcnStatusCallback(@NonNull android.net.vcn.VcnManager.VcnStatusCallback); + field public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1; // 0x1 + field public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0; // 0x0 + field public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2; // 0x2 + field public static final int VCN_STATUS_CODE_ACTIVE = 2; // 0x2 + field public static final int VCN_STATUS_CODE_INACTIVE = 1; // 0x1 + field public static final int VCN_STATUS_CODE_NOT_CONFIGURED = 0; // 0x0 + field public static final int VCN_STATUS_CODE_SAFE_MODE = 3; // 0x3 + } + + public abstract static class VcnManager.VcnStatusCallback { + ctor public VcnManager.VcnStatusCallback(); + method public abstract void onGatewayConnectionError(@NonNull int[], int, @Nullable Throwable); + method public abstract void onVcnStatusChanged(int); } } @@ -30249,6 +30313,7 @@ package android.os { field public static final String HARDWARE; field public static final String HOST; field public static final String ID; + field public static final boolean IS_DEBUGGABLE; field public static final String MANUFACTURER; field public static final String MODEL; field @NonNull public static final String ODM_SKU; @@ -30407,19 +30472,10 @@ package android.os { public abstract class CombinedVibrationEffect implements android.os.Parcelable { method @NonNull public static android.os.CombinedVibrationEffect createSynced(@NonNull android.os.VibrationEffect); method public int describeContents(); - method @NonNull public static android.os.CombinedVibrationEffect.SequentialCombination startSequential(); method @NonNull public static android.os.CombinedVibrationEffect.SyncedCombination startSynced(); field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect> CREATOR; } - public static final class CombinedVibrationEffect.SequentialCombination { - method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(int, @NonNull android.os.VibrationEffect); - method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(int, @NonNull android.os.VibrationEffect, int); - method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(@NonNull android.os.CombinedVibrationEffect); - method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(@NonNull android.os.CombinedVibrationEffect, int); - method @NonNull public android.os.CombinedVibrationEffect combine(); - } - public static final class CombinedVibrationEffect.SyncedCombination { method @NonNull public android.os.CombinedVibrationEffect.SyncedCombination addVibrator(int, @NonNull android.os.VibrationEffect); method @NonNull public android.os.CombinedVibrationEffect combine(); @@ -31844,6 +31900,7 @@ package android.os.storage { method @WorkerThread public long getAllocatableBytes(@NonNull java.util.UUID) throws java.io.IOException; method @WorkerThread public long getCacheQuotaBytes(@NonNull java.util.UUID) throws java.io.IOException; method @WorkerThread public long getCacheSizeBytes(@NonNull java.util.UUID) throws java.io.IOException; + method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) public android.app.PendingIntent getManageSpaceActivityIntent(@NonNull String, int); method public String getMountedObbPath(String); method @NonNull public android.os.storage.StorageVolume getPrimaryStorageVolume(); method @NonNull public java.util.List<android.os.storage.StorageVolume> getRecentStorageVolumes(); @@ -38969,6 +39026,10 @@ package android.telecom { method public void unhold(); method public void unregisterCallback(android.telecom.Call.Callback); field @Deprecated public static final String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts"; + field public static final String EVENT_CLEAR_DIAGNOSTIC_MESSAGE = "android.telecom.event.CLEAR_DIAGNOSTIC_MESSAGE"; + field public static final String EVENT_DISPLAY_DIAGNOSTIC_MESSAGE = "android.telecom.event.DISPLAY_DIAGNOSTIC_MESSAGE"; + field public static final String EXTRA_DIAGNOSTIC_MESSAGE = "android.telecom.extra.DIAGNOSTIC_MESSAGE"; + field public static final String EXTRA_DIAGNOSTIC_MESSAGE_ID = "android.telecom.extra.DIAGNOSTIC_MESSAGE_ID"; field public static final String EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS = "android.telecom.extra.LAST_EMERGENCY_CALLBACK_TIME_MILLIS"; field public static final String EXTRA_SILENT_RINGING_REQUESTED = "android.telecom.extra.SILENT_RINGING_REQUESTED"; field public static final String EXTRA_SUGGESTED_PHONE_ACCOUNTS = "android.telecom.extra.SUGGESTED_PHONE_ACCOUNTS"; @@ -42219,7 +42280,6 @@ package android.telephony { field public static final int AUTHTYPE_EAP_SIM = 128; // 0x80 field public static final int CALL_COMPOSER_STATUS_OFF = 0; // 0x0 field public static final int CALL_COMPOSER_STATUS_ON = 1; // 0x1 - field public static final int CALL_COMPOSER_STATUS_ON_NO_PICTURES = 2; // 0x2 field public static final int CALL_STATE_IDLE = 0; // 0x0 field public static final int CALL_STATE_OFFHOOK = 2; // 0x2 field public static final int CALL_STATE_RINGING = 1; // 0x1 @@ -50053,7 +50113,7 @@ package android.view.accessibility { method public boolean canOpenPopup(); method public int describeContents(); method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String); - method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(String); + method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(@NonNull String); method public android.view.accessibility.AccessibilityNodeInfo findFocus(int); method public android.view.accessibility.AccessibilityNodeInfo focusSearch(int); method public java.util.List<android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction> getActionList(); @@ -50955,6 +51015,7 @@ package android.view.displayhash { method public void onDisplayHashError(int); method public void onDisplayHashResult(@NonNull android.view.displayhash.DisplayHash); field public static final int DISPLAY_HASH_ERROR_INVALID_BOUNDS = -2; // 0xfffffffe + field public static final int DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM = -5; // 0xfffffffb field public static final int DISPLAY_HASH_ERROR_MISSING_WINDOW = -3; // 0xfffffffd field public static final int DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN = -4; // 0xfffffffc field public static final int DISPLAY_HASH_ERROR_UNKNOWN = -1; // 0xffffffff diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 7ea7d61ac3c5..d6786f8a3f8e 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -44,6 +44,14 @@ package android.app { } +package android.app.usage { + + public class NetworkStatsManager { + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void notifyNetworkStatus(@NonNull java.util.List<android.net.Network>, @NonNull java.util.List<android.net.NetworkStateSnapshot>, @Nullable String, @NonNull java.util.List<android.net.UnderlyingNetworkInfo>); + } + +} + package android.content { public abstract class Context { @@ -167,10 +175,26 @@ package android.net { method public int getResourceId(); } + public final class NetworkStateSnapshot implements android.os.Parcelable { + ctor public NetworkStateSnapshot(@NonNull android.net.Network, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, @Nullable String, int); + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStateSnapshot> CREATOR; + field public final int legacyType; + field @NonNull public final android.net.LinkProperties linkProperties; + field @NonNull public final android.net.Network network; + field @NonNull public final android.net.NetworkCapabilities networkCapabilities; + field @Nullable public final String subscriberId; + } + public class NetworkWatchlistManager { method @Nullable public byte[] getWatchlistConfigHash(); } + public final class Proxy { + method public static void setHttpProxyConfiguration(@Nullable android.net.ProxyInfo); + } + public final class UnderlyingNetworkInfo implements android.os.Parcelable { ctor public UnderlyingNetworkInfo(int, @NonNull String, @NonNull java.util.List<java.lang.String>); method public int describeContents(); @@ -217,8 +241,8 @@ package android.os { package android.os.storage { public class StorageManager { - method public void notifyAppIoBlocked(@NonNull String, int, int, int); - method public void notifyAppIoResumed(@NonNull String, int, int, int); + method public void notifyAppIoBlocked(@NonNull java.util.UUID, int, int, int); + method public void notifyAppIoResumed(@NonNull java.util.UUID, int, int, int); field public static final int APP_IO_BLOCKED_REASON_TRANSCODING = 0; // 0x0 } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 5050a1c8b86d..b4f3698861f0 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -32,6 +32,7 @@ package android { field public static final String BATTERY_PREDICTION = "android.permission.BATTERY_PREDICTION"; field public static final String BIND_ATTENTION_SERVICE = "android.permission.BIND_ATTENTION_SERVICE"; field public static final String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE"; + field public static final String BIND_CALL_DIAGNOSTIC_SERVICE = "android.permission.BIND_CALL_DIAGNOSTIC_SERVICE"; field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE"; field @Deprecated public static final String BIND_CONNECTION_SERVICE = "android.permission.BIND_CONNECTION_SERVICE"; field public static final String BIND_CONTENT_CAPTURE_SERVICE = "android.permission.BIND_CONTENT_CAPTURE_SERVICE"; @@ -153,6 +154,7 @@ package android { field public static final String MANAGE_USB = "android.permission.MANAGE_USB"; field public static final String MANAGE_USERS = "android.permission.MANAGE_USERS"; field public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE"; + field public static final String MANAGE_WIFI_COUNTRY_CODE = "android.permission.MANAGE_WIFI_COUNTRY_CODE"; field public static final String MODIFY_APPWIDGET_BIND_PERMISSIONS = "android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"; field public static final String MODIFY_AUDIO_ROUTING = "android.permission.MODIFY_AUDIO_ROUTING"; field public static final String MODIFY_CELL_BROADCASTS = "android.permission.MODIFY_CELL_BROADCASTS"; @@ -356,6 +358,7 @@ package android { field public static final int config_systemGallery = 17039399; // 0x1040027 field public static final int config_systemShell = 17039402; // 0x104002a field public static final int config_systemSpeechRecognizer = 17039406; // 0x104002e + field public static final int config_systemWifiCoexManager = 17039407; // 0x104002f } public static final class R.style { @@ -422,6 +425,9 @@ package android.app { method @Nullable public static String opToPermission(@NonNull String); method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(@NonNull String, int, @Nullable String, int); method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setUidMode(@NonNull String, int, int); + field public static final int HISTORY_FLAGS_ALL = 3; // 0x3 + field public static final int HISTORY_FLAG_AGGREGATE = 1; // 0x1 + field public static final int HISTORY_FLAG_DISCRETE = 2; // 0x2 field public static final String OPSTR_ACCEPT_HANDOVER = "android:accept_handover"; field public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility"; field public static final String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications"; @@ -535,9 +541,14 @@ package android.app { method public long getAccessDuration(int, int, int); method public long getBackgroundAccessCount(int); method public long getBackgroundAccessDuration(int); + method @NonNull public java.util.List<android.app.AppOpsManager.AttributedOpEntry> getBackgroundDiscreteAccesses(int); method public long getBackgroundRejectCount(int); + method @NonNull public android.app.AppOpsManager.AttributedOpEntry getDiscreteAccessAt(@IntRange(from=0) int); + method @IntRange(from=0) public int getDiscreteAccessCount(); + method @NonNull public java.util.List<android.app.AppOpsManager.AttributedOpEntry> getDiscreteAccesses(int, int, int); method public long getForegroundAccessCount(int); method public long getForegroundAccessDuration(int); + method @NonNull public java.util.List<android.app.AppOpsManager.AttributedOpEntry> getForegroundDiscreteAccesses(int); method public long getForegroundRejectCount(int); method @NonNull public String getOpName(); method public long getRejectCount(int, int, int); @@ -564,6 +575,7 @@ package android.app { method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest build(); method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setAttributionTag(@Nullable String); method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setFlags(int); + method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setHistoryFlags(int); method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setOpNames(@Nullable java.util.List<java.lang.String>); method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setPackageName(@Nullable String); method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setUid(int); @@ -1887,11 +1899,18 @@ package android.bluetooth { field public static final int ACCESS_REJECTED = 2; // 0x2 field public static final int ACCESS_UNKNOWN = 0; // 0x0 field public static final String ACTION_SILENCE_MODE_CHANGED = "android.bluetooth.device.action.SILENCE_MODE_CHANGED"; + field public static final String DEVICE_TYPE_DEFAULT = "Default"; + field public static final String DEVICE_TYPE_UNTETHERED_HEADSET = "Untethered Headset"; + field public static final String DEVICE_TYPE_WATCH = "Watch"; field public static final int METADATA_COMPANION_APP = 4; // 0x4 + field public static final int METADATA_DEVICE_TYPE = 17; // 0x11 field public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; // 0x10 field public static final int METADATA_HARDWARE_VERSION = 3; // 0x3 field public static final int METADATA_IS_UNTETHERED_HEADSET = 6; // 0x6 + field public static final int METADATA_MAIN_BATTERY = 18; // 0x12 + field public static final int METADATA_MAIN_CHARGING = 19; // 0x13 field public static final int METADATA_MAIN_ICON = 5; // 0x5 + field public static final int METADATA_MAIN_LOW_BATTERY_THRESHOLD = 20; // 0x14 field public static final int METADATA_MANUFACTURER_NAME = 0; // 0x0 field public static final int METADATA_MAX_LENGTH = 2048; // 0x800 field public static final int METADATA_MODEL_NAME = 1; // 0x1 @@ -1899,12 +1918,15 @@ package android.bluetooth { field public static final int METADATA_UNTETHERED_CASE_BATTERY = 12; // 0xc field public static final int METADATA_UNTETHERED_CASE_CHARGING = 15; // 0xf field public static final int METADATA_UNTETHERED_CASE_ICON = 9; // 0x9 + field public static final int METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD = 23; // 0x17 field public static final int METADATA_UNTETHERED_LEFT_BATTERY = 10; // 0xa field public static final int METADATA_UNTETHERED_LEFT_CHARGING = 13; // 0xd field public static final int METADATA_UNTETHERED_LEFT_ICON = 7; // 0x7 + field public static final int METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD = 21; // 0x15 field public static final int METADATA_UNTETHERED_RIGHT_BATTERY = 11; // 0xb field public static final int METADATA_UNTETHERED_RIGHT_CHARGING = 14; // 0xe field public static final int METADATA_UNTETHERED_RIGHT_ICON = 8; // 0x8 + field public static final int METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD = 22; // 0x16 } public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile { @@ -2552,8 +2574,7 @@ package android.content.pm { field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio"; field public static final String FEATURE_CAMERA_TOGGLE = "android.hardware.camera.toggle"; field public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub"; - field @Deprecated public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery"; - field public static final String FEATURE_INCREMENTAL_DELIVERY_VERSION = "android.software.incremental_delivery_version"; + field public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery"; field public static final String FEATURE_MICROPHONE_TOGGLE = "android.hardware.microphone.toggle"; field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow"; field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock"; @@ -2769,13 +2790,12 @@ package android.content.pm.verify.domain { field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationInfo> CREATOR; } - public interface DomainVerificationManager { + public final class DomainVerificationManager { method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) public android.content.pm.verify.domain.DomainVerificationInfo getDomainVerificationInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; - method @Nullable @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public android.content.pm.verify.domain.DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public java.util.List<android.content.pm.verify.domain.DomainOwner> getOwnersForDomain(@NonNull String); - method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> getValidVerificationPackageNames(); method public static boolean isStateModifiable(int); method public static boolean isStateVerified(int); + method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> queryValidVerificationPackageNames(); method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationLinkHandlingAllowed(@NonNull String, boolean) throws android.content.pm.PackageManager.NameNotFoundException; method @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public void setDomainVerificationStatus(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, int) throws android.content.pm.PackageManager.NameNotFoundException; method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationUserSelection(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, boolean) throws android.content.pm.PackageManager.NameNotFoundException; @@ -2792,18 +2812,8 @@ package android.content.pm.verify.domain { field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationRequest> CREATOR; } - public final class DomainVerificationUserSelection implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getHostToStateMap(); + public final class DomainVerificationUserState implements android.os.Parcelable { method @NonNull public java.util.UUID getIdentifier(); - method @NonNull public String getPackageName(); - method @NonNull public android.os.UserHandle getUser(); - method @NonNull public boolean isLinkHandlingAllowed(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationUserSelection> CREATOR; - field public static final int DOMAIN_STATE_NONE = 0; // 0x0 - field public static final int DOMAIN_STATE_SELECTED = 1; // 0x1 - field public static final int DOMAIN_STATE_VERIFIED = 2; // 0x2 } } @@ -8205,10 +8215,11 @@ package android.os { field public static final int EVENT_MMS = 2; // 0x2 field public static final int EVENT_SMS = 1; // 0x1 field public static final int EVENT_UNSPECIFIED = 0; // 0x0 - field public static final int REASON_ACTIVITY_RECOGNITION = 102; // 0x66 + field public static final int REASON_ACTIVITY_RECOGNITION = 103; // 0x67 field public static final int REASON_GEOFENCING = 100; // 0x64 field public static final int REASON_OTHER = 1; // 0x1 field public static final int REASON_PUSH_MESSAGING = 101; // 0x65 + field public static final int REASON_PUSH_MESSAGING_OVER_QUOTA = 102; // 0x66 field public static final int REASON_UNKNOWN = 0; // 0x0 field public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0; // 0x0 field public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1; // 0x1 @@ -8824,6 +8835,8 @@ package android.provider { field public static final String NAMESPACE_RUNTIME_NATIVE = "runtime_native"; field public static final String NAMESPACE_RUNTIME_NATIVE_BOOT = "runtime_native_boot"; field public static final String NAMESPACE_SCHEDULER = "scheduler"; + field public static final String NAMESPACE_STATSD_JAVA = "statsd_java"; + field public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot"; field public static final String NAMESPACE_STATSD_NATIVE = "statsd_native"; field public static final String NAMESPACE_STATSD_NATIVE_BOOT = "statsd_native_boot"; field @Deprecated public static final String NAMESPACE_STORAGE = "storage"; @@ -10339,6 +10352,16 @@ package android.telecom { ctor @Deprecated public Call.Listener(); } + public abstract class CallDiagnosticService extends android.app.Service { + ctor public CallDiagnosticService(); + method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); + method public abstract void onBluetoothCallQualityReportReceived(@NonNull android.telecom.BluetoothCallQualityReport); + method public abstract void onCallAudioStateChanged(@NonNull android.telecom.CallAudioState); + method @NonNull public abstract android.telecom.DiagnosticCall onInitializeDiagnosticCall(@NonNull android.telecom.Call.Details); + method public abstract void onRemoveDiagnosticCall(@NonNull android.telecom.DiagnosticCall); + field public static final String SERVICE_INTERFACE = "android.telecom.CallDiagnosticService"; + } + public static class CallScreeningService.CallResponse.Builder { method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public android.telecom.CallScreeningService.CallResponse.Builder setShouldScreenCallViaAudioProcessing(boolean); } @@ -10371,6 +10394,9 @@ package android.telecom { method public void setTelecomCallId(@NonNull String); field public static final int CAPABILITY_CONFERENCE_HAS_NO_CHILDREN = 2097152; // 0x200000 field public static final int CAPABILITY_SPEED_UP_MT_AUDIO = 262144; // 0x40000 + field public static final String EVENT_DEVICE_TO_DEVICE_MESSAGE = "android.telecom.event.DEVICE_TO_DEVICE_MESSAGE"; + field public static final String EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE = "android.telecom.extra.DEVICE_TO_DEVICE_MESSAGE_TYPE"; + field public static final String EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE = "android.telecom.extra.DEVICE_TO_DEVICE_MESSAGE_VALUE"; field public static final String EXTRA_DISABLE_ADD_CALL = "android.telecom.extra.DISABLE_ADD_CALL"; field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 1; // 0x1 field public static final int PROPERTY_GENERIC_CONFERENCE = 2; // 0x2 @@ -10396,6 +10422,34 @@ package android.telecom { method public final void addExistingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.Connection, @NonNull android.telecom.Conference); } + public abstract class DiagnosticCall { + ctor public DiagnosticCall(); + method public final void clearDiagnosticMessage(int); + method public final void displayDiagnosticMessage(int, @NonNull CharSequence); + method @NonNull public android.telecom.Call.Details getCallDetails(); + method public abstract void onCallDetailsChanged(@NonNull android.telecom.Call.Details); + method @Nullable public abstract CharSequence onCallDisconnected(int, int); + method @Nullable public abstract CharSequence onCallDisconnected(@NonNull android.telephony.ims.ImsReasonInfo); + method public abstract void onCallQualityReceived(@NonNull android.telephony.CallQuality); + method public abstract void onReceiveDeviceToDeviceMessage(int, int); + method public final void sendDeviceToDeviceMessage(int, int); + field public static final int AUDIO_CODEC_AMR_NB = 3; // 0x3 + field public static final int AUDIO_CODEC_AMR_WB = 2; // 0x2 + field public static final int AUDIO_CODEC_EVS = 1; // 0x1 + field public static final int BATTERY_STATE_CHARGING = 3; // 0x3 + field public static final int BATTERY_STATE_GOOD = 2; // 0x2 + field public static final int BATTERY_STATE_LOW = 1; // 0x1 + field public static final int COVERAGE_GOOD = 2; // 0x2 + field public static final int COVERAGE_POOR = 1; // 0x1 + field public static final int MESSAGE_CALL_AUDIO_CODEC = 2; // 0x2 + field public static final int MESSAGE_CALL_NETWORK_TYPE = 1; // 0x1 + field public static final int MESSAGE_DEVICE_BATTERY_STATE = 3; // 0x3 + field public static final int MESSAGE_DEVICE_NETWORK_COVERAGE = 4; // 0x4 + field public static final int NETWORK_TYPE_IWLAN = 2; // 0x2 + field public static final int NETWORK_TYPE_LTE = 1; // 0x1 + field public static final int NETWORK_TYPE_NR = 3; // 0x3 + } + public abstract class InCallService extends android.app.Service { method @Deprecated public android.telecom.Phone getPhone(); method @Deprecated public void onPhoneCreated(android.telecom.Phone); @@ -12997,7 +13051,7 @@ package android.telephony.ims { method @NonNull public String getServiceId(); method @NonNull public String getServiceVersion(); method @NonNull public String getStatus(); - method @Nullable public String getTimestamp(); + method @Nullable public java.time.Instant getTime(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsContactPresenceTuple> CREATOR; field public static final String SERVICE_ID_CALL_COMPOSER = "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.gsma.callcomposer"; @@ -13024,7 +13078,7 @@ package android.telephony.ims { method @NonNull public android.telephony.ims.RcsContactPresenceTuple.Builder setContactUri(@NonNull android.net.Uri); method @NonNull public android.telephony.ims.RcsContactPresenceTuple.Builder setServiceCapabilities(@NonNull android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities); method @NonNull public android.telephony.ims.RcsContactPresenceTuple.Builder setServiceDescription(@NonNull String); - method @NonNull public android.telephony.ims.RcsContactPresenceTuple.Builder setTimestamp(@NonNull String); + method @NonNull public android.telephony.ims.RcsContactPresenceTuple.Builder setTime(@NonNull java.time.Instant); } public static final class RcsContactPresenceTuple.ServiceCapabilities implements android.os.Parcelable { @@ -13080,7 +13134,7 @@ package android.telephony.ims { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getUcePublishState() throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void removeOnPublishStateChangedListener(@NonNull android.telephony.ims.RcsUceAdapter.OnPublishStateChangedListener) throws android.telephony.ims.ImsException; method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, android.Manifest.permission.READ_CONTACTS}) public void requestAvailability(@NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException; - method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, android.Manifest.permission.READ_CONTACTS}) public void requestCapabilities(@NonNull java.util.List<android.net.Uri>, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException; + method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, android.Manifest.permission.READ_CONTACTS}) public void requestCapabilities(@NonNull java.util.Collection<android.net.Uri>, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUceSettingEnabled(boolean) throws android.telephony.ims.ImsException; field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2 field public static final int CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED = 1; // 0x1 @@ -13556,7 +13610,7 @@ package android.telephony.ims.stub { ctor public RcsCapabilityExchangeImplBase(@NonNull java.util.concurrent.Executor); method public void publishCapabilities(@NonNull String, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.PublishResponseCallback); method public void sendOptionsCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.List<java.lang.String>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.OptionsResponseCallback); - method public void subscribeForCapabilities(@NonNull java.util.List<android.net.Uri>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.SubscribeResponseCallback); + method public void subscribeForCapabilities(@NonNull java.util.Collection<android.net.Uri>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.SubscribeResponseCallback); field public static final int COMMAND_CODE_FETCH_ERROR = 3; // 0x3 field public static final int COMMAND_CODE_GENERIC_FAILURE = 1; // 0x1 field public static final int COMMAND_CODE_INSUFFICIENT_MEMORY = 5; // 0x5 @@ -14025,9 +14079,13 @@ package android.view.translation { public final class UiTranslationManager { method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(int); + method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(@NonNull android.app.assist.ActivityId); method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(int); + method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(@NonNull android.app.assist.ActivityId); method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(int); + method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(@NonNull android.app.assist.ActivityId); method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.autofill.AutofillId>, int); + method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.autofill.AutofillId>, @NonNull android.app.assist.ActivityId); } } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 1e5a6f12f96b..e4757e6204b7 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -224,6 +224,9 @@ package android.app { field public static final int HISTORICAL_MODE_DISABLED = 0; // 0x0 field public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1; // 0x1 field public static final int HISTORICAL_MODE_ENABLED_PASSIVE = 2; // 0x2 + field public static final int HISTORY_FLAGS_ALL = 3; // 0x3 + field public static final int HISTORY_FLAG_AGGREGATE = 1; // 0x1 + field public static final int HISTORY_FLAG_DISCRETE = 2; // 0x2 field public static final String KEY_BG_STATE_SETTLE_TIME = "bg_state_settle_time"; field public static final String KEY_FG_SERVICE_STATE_SETTLE_TIME = "fg_service_state_settle_time"; field public static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time"; @@ -238,6 +241,7 @@ package android.app { public static final class AppOpsManager.HistoricalOps implements android.os.Parcelable { ctor public AppOpsManager.HistoricalOps(long, long); + method public void addDiscreteAccess(int, int, @NonNull String, @Nullable String, int, int, long, long); method public void increaseAccessCount(int, int, @NonNull String, @Nullable String, int, int, long); method public void increaseAccessDuration(int, int, @NonNull String, @Nullable String, int, int, long); method public void increaseRejectCount(int, int, @NonNull String, @Nullable String, int, int, long); @@ -1485,6 +1489,7 @@ package android.os { public abstract class CombinedVibrationEffect implements android.os.Parcelable { method public abstract long getDuration(); + method @NonNull public static android.os.CombinedVibrationEffect.SequentialCombination startSequential(); } public static final class CombinedVibrationEffect.Mono extends android.os.CombinedVibrationEffect { @@ -1502,6 +1507,14 @@ package android.os { field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Sequential> CREATOR; } + public static final class CombinedVibrationEffect.SequentialCombination { + method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(int, @NonNull android.os.VibrationEffect); + method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(int, @NonNull android.os.VibrationEffect, int); + method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(@NonNull android.os.CombinedVibrationEffect); + method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(@NonNull android.os.CombinedVibrationEffect, int); + method @NonNull public android.os.CombinedVibrationEffect combine(); + } + public static final class CombinedVibrationEffect.Stereo extends android.os.CombinedVibrationEffect { method public long getDuration(); method @NonNull public android.util.SparseArray<android.os.VibrationEffect> getEffects(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 730fce9449d0..e7751b861037 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -176,6 +176,8 @@ import android.view.Window; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.autofill.AutofillId; +import android.view.contentcapture.IContentCaptureManager; +import android.view.contentcapture.IContentCaptureOptionsCallback; import android.view.translation.TranslationSpec; import android.webkit.WebView; import android.window.SplashScreen; @@ -512,6 +514,8 @@ public final class ActivityThread extends ClientTransactionHandler { boolean mHasImeComponent = false; + private IContentCaptureOptionsCallback.Stub mContentCaptureOptionsCallback = null; + /** Activity client record, used for bookkeeping for the real {@link Activity} instance. */ public static final class ActivityClientRecord { @UnsupportedAppUsage @@ -1939,6 +1943,7 @@ public final class ActivityThread extends ClientTransactionHandler { public static final int PURGE_RESOURCES = 161; public static final int ATTACH_STARTUP_AGENTS = 162; public static final int UPDATE_UI_TRANSLATION_STATE = 163; + public static final int SET_CONTENT_CAPTURE_OPTIONS_CALLBACK = 164; public static final int INSTRUMENT_WITHOUT_RESTART = 170; public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171; @@ -1988,6 +1993,8 @@ public final class ActivityThread extends ClientTransactionHandler { case PURGE_RESOURCES: return "PURGE_RESOURCES"; case ATTACH_STARTUP_AGENTS: return "ATTACH_STARTUP_AGENTS"; case UPDATE_UI_TRANSLATION_STATE: return "UPDATE_UI_TRANSLATION_STATE"; + case SET_CONTENT_CAPTURE_OPTIONS_CALLBACK: + return "SET_CONTENT_CAPTURE_OPTIONS_CALLBACK"; case INSTRUMENT_WITHOUT_RESTART: return "INSTRUMENT_WITHOUT_RESTART"; case FINISH_INSTRUMENTATION_WITHOUT_RESTART: return "FINISH_INSTRUMENTATION_WITHOUT_RESTART"; @@ -2180,6 +2187,9 @@ public final class ActivityThread extends ClientTransactionHandler { (TranslationSpec) args.arg3, (TranslationSpec) args.arg4, (List<AutofillId>) args.arg5); break; + case SET_CONTENT_CAPTURE_OPTIONS_CALLBACK: + handleSetContentCaptureOptionsCallback((String) msg.obj); + break; case INSTRUMENT_WITHOUT_RESTART: handleInstrumentWithoutRestart((AppBindData) msg.obj); break; @@ -6795,6 +6805,7 @@ public final class ActivityThread extends ClientTransactionHandler { // Propagate Content Capture options app.setContentCaptureOptions(data.contentCaptureOptions); + sendMessage(H.SET_CONTENT_CAPTURE_OPTIONS_CALLBACK, data.appInfo.packageName); mInitialApplication = app; @@ -6856,6 +6867,36 @@ public final class ActivityThread extends ClientTransactionHandler { } } + private void handleSetContentCaptureOptionsCallback(String packageName) { + if (mContentCaptureOptionsCallback != null) { + return; + } + + IBinder b = ServiceManager.getService(Context.CONTENT_CAPTURE_MANAGER_SERVICE); + if (b == null) { + return; + } + + IContentCaptureManager service = IContentCaptureManager.Stub.asInterface(b); + mContentCaptureOptionsCallback = new IContentCaptureOptionsCallback.Stub() { + @Override + public void setContentCaptureOptions(ContentCaptureOptions options) + throws RemoteException { + if (mInitialApplication != null) { + mInitialApplication.setContentCaptureOptions(options); + } + } + }; + try { + service.registerContentCaptureOptionsCallback(packageName, + mContentCaptureOptionsCallback); + } catch (RemoteException e) { + Slog.w(TAG, "registerContentCaptureOptionsCallback() failed: " + + packageName, e); + mContentCaptureOptionsCallback = null; + } + } + private void handleInstrumentWithoutRestart(AppBindData data) { try { data.compatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 160844aacc46..dd1bc7c61547 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -16,6 +16,8 @@ package android.app; +import static java.lang.Long.max; + import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.IntDef; @@ -3385,6 +3387,13 @@ public class AppOpsManager { @DataClass.ParcelWith(LongSparseArrayParceling.class) private final @Nullable LongSparseArray<NoteOpEvent> mRejectEvents; + private AttributedOpEntry(@NonNull AttributedOpEntry other) { + mOp = other.mOp; + mRunning = other.mRunning; + mAccessEvents = other.mAccessEvents == null ? null : other.mAccessEvents.clone(); + mRejectEvents = other.mRejectEvents == null ? null : other.mRejectEvents.clone(); + } + /** * Returns all keys for which we have events. * @@ -3749,6 +3758,15 @@ public class AppOpsManager { return lastEvent.getProxy(); } + @NonNull + String getOpName() { + return AppOpsManager.opToPublicName(mOp); + } + + int getOp() { + return mOp; + } + private static class LongSparseArrayParceling implements Parcelling<LongSparseArray<NoteOpEvent>> { @Override @@ -4571,6 +4589,50 @@ public class AppOpsManager { } /** + * Flag for querying app op history: get only aggregate information and no + * discrete accesses. + * + * @see #getHistoricalOps(HistoricalOpsRequest, Executor, Consumer) + * + * @hide + */ + @TestApi + @SystemApi + public static final int HISTORY_FLAG_AGGREGATE = 1 << 0; + + /** + * Flag for querying app op history: get only discrete information and no + * aggregate accesses. + * + * @see #getHistoricalOps(HistoricalOpsRequest, Executor, Consumer) + * + * @hide + */ + @TestApi + @SystemApi + public static final int HISTORY_FLAG_DISCRETE = 1 << 1; + + /** + * Flag for querying app op history: get all types of historical accesses. + * + * @see #getHistoricalOps(HistoricalOpsRequest, Executor, Consumer) + * + * @hide + */ + @TestApi + @SystemApi + public static final int HISTORY_FLAGS_ALL = HISTORY_FLAG_AGGREGATE + | HISTORY_FLAG_DISCRETE; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "HISTORY_FLAG_" }, value = { + HISTORY_FLAG_AGGREGATE, + HISTORY_FLAG_DISCRETE + }) + public @interface OpHistoryFlags {} + + /** * Specifies what parameters to filter historical appop requests for * * @hide @@ -4625,6 +4687,7 @@ public class AppOpsManager { private final @Nullable String mPackageName; private final @Nullable String mAttributionTag; private final @Nullable List<String> mOpNames; + private final @OpHistoryFlags int mHistoryFlags; private final @HistoricalOpsRequestFilter int mFilter; private final long mBeginTimeMillis; private final long mEndTimeMillis; @@ -4632,12 +4695,13 @@ public class AppOpsManager { private HistoricalOpsRequest(int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable List<String> opNames, - @HistoricalOpsRequestFilter int filter, long beginTimeMillis, - long endTimeMillis, @OpFlags int flags) { + @OpHistoryFlags int historyFlags, @HistoricalOpsRequestFilter int filter, + long beginTimeMillis, long endTimeMillis, @OpFlags int flags) { mUid = uid; mPackageName = packageName; mAttributionTag = attributionTag; mOpNames = opNames; + mHistoryFlags = historyFlags; mFilter = filter; mBeginTimeMillis = beginTimeMillis; mEndTimeMillis = endTimeMillis; @@ -4655,6 +4719,7 @@ public class AppOpsManager { private @Nullable String mPackageName; private @Nullable String mAttributionTag; private @Nullable List<String> mOpNames; + private @OpHistoryFlags int mHistoryFlags; private @HistoricalOpsRequestFilter int mFilter; private final long mBeginTimeMillis; private final long mEndTimeMillis; @@ -4676,6 +4741,7 @@ public class AppOpsManager { "beginTimeMillis must be non negative and lesser than endTimeMillis"); mBeginTimeMillis = beginTimeMillis; mEndTimeMillis = endTimeMillis; + mHistoryFlags = HISTORY_FLAG_AGGREGATE; } /** @@ -4772,11 +4838,25 @@ public class AppOpsManager { } /** + * Specifies what type of historical information to query. + * + * @param flags Flags for the historical types to fetch which are any + * combination of {@link #HISTORY_FLAG_AGGREGATE}, {@link #HISTORY_FLAG_DISCRETE}, + * {@link #HISTORY_FLAGS_ALL}. The default is {@link #HISTORY_FLAG_AGGREGATE}. + * @return This builder. + */ + public @NonNull Builder setHistoryFlags(@OpHistoryFlags int flags) { + Preconditions.checkFlagsArgument(flags, HISTORY_FLAGS_ALL); + mHistoryFlags = flags; + return this; + } + + /** * @return a new {@link HistoricalOpsRequest}. */ public @NonNull HistoricalOpsRequest build() { return new HistoricalOpsRequest(mUid, mPackageName, mAttributionTag, mOpNames, - mFilter, mBeginTimeMillis, mEndTimeMillis, mFlags); + mHistoryFlags, mFilter, mBeginTimeMillis, mEndTimeMillis, mFlags); } } } @@ -4943,7 +5023,8 @@ public class AppOpsManager { * @hide */ public void filter(int uid, @Nullable String packageName, @Nullable String attributionTag, - @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter, + @Nullable String[] opNames, @OpHistoryFlags int historyFilter, + @HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis) { final long durationMillis = getDurationMillis(); mBeginTimeMillis = Math.max(mBeginTimeMillis, beginTimeMillis); @@ -4956,7 +5037,8 @@ public class AppOpsManager { if ((filter & FILTER_BY_UID) != 0 && uid != uidOp.getUid()) { mHistoricalUidOps.removeAt(i); } else { - uidOp.filter(packageName, attributionTag, opNames, filter, scaleFactor); + uidOp.filter(packageName, attributionTag, opNames, filter, historyFilter, + scaleFactor, mBeginTimeMillis, mEndTimeMillis); if (uidOp.getPackageCount() == 0) { mHistoricalUidOps.removeAt(i); } @@ -5013,6 +5095,16 @@ public class AppOpsManager { /** @hide */ @TestApi + public void addDiscreteAccess(int opCode, int uid, @NonNull String packageName, + @Nullable String attributionTag, @UidState int uidState, @OpFlags int opFlag, + long discreteAccessTime, long discreteAccessDuration) { + getOrCreateHistoricalUidOps(uid).addDiscreteAccess(opCode, packageName, attributionTag, + uidState, opFlag, discreteAccessTime, discreteAccessDuration); + }; + + + /** @hide */ + @TestApi public void offsetBeginAndEndTime(long offsetMillis) { mBeginTimeMillis += offsetMillis; mEndTimeMillis += offsetMillis; @@ -5288,7 +5380,8 @@ public class AppOpsManager { private void filter(@Nullable String packageName, @Nullable String attributionTag, @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter, - double fractionToRemove) { + @OpHistoryFlags int historyFilter, double fractionToRemove, long beginTimeMillis, + long endTimeMillis) { final int packageCount = getPackageCount(); for (int i = packageCount - 1; i >= 0; i--) { final HistoricalPackageOps packageOps = getPackageOpsAt(i); @@ -5296,7 +5389,8 @@ public class AppOpsManager { packageOps.getPackageName())) { mHistoricalPackageOps.removeAt(i); } else { - packageOps.filter(attributionTag, opNames, filter, fractionToRemove); + packageOps.filter(attributionTag, opNames, filter, historyFilter, + fractionToRemove, beginTimeMillis, endTimeMillis); if (packageOps.getAttributedOpsCount() == 0) { mHistoricalPackageOps.removeAt(i); } @@ -5336,6 +5430,13 @@ public class AppOpsManager { opCode, attributionTag, uidState, flags, increment); } + private void addDiscreteAccess(int opCode, @NonNull String packageName, + @Nullable String attributionTag, @UidState int uidState, + @OpFlags int flag, long discreteAccessTime, long discreteAccessDuration) { + getOrCreateHistoricalPackageOps(packageName).addDiscreteAccess(opCode, attributionTag, + uidState, flag, discreteAccessTime, discreteAccessDuration); + }; + /** * @return The UID for which the data is related. */ @@ -5540,7 +5641,8 @@ public class AppOpsManager { } private void filter(@Nullable String attributionTag, @Nullable String[] opNames, - @HistoricalOpsRequestFilter int filter, double fractionToRemove) { + @HistoricalOpsRequestFilter int filter, @OpHistoryFlags int historyFilter, + double fractionToRemove, long beginTimeMillis, long endTimeMillis) { final int attributionCount = getAttributedOpsCount(); for (int i = attributionCount - 1; i >= 0; i--) { final AttributedHistoricalOps attributionOps = getAttributedOpsAt(i); @@ -5548,7 +5650,8 @@ public class AppOpsManager { attributionOps.getTag())) { mAttributedHistoricalOps.removeAt(i); } else { - attributionOps.filter(opNames, filter, fractionToRemove); + attributionOps.filter(opNames, filter, historyFilter, fractionToRemove, + beginTimeMillis, endTimeMillis); if (attributionOps.getOpCount() == 0) { mAttributedHistoricalOps.removeAt(i); } @@ -5593,6 +5696,13 @@ public class AppOpsManager { opCode, uidState, flags, increment); } + private void addDiscreteAccess(int opCode, @Nullable String attributionTag, + @UidState int uidState, @OpFlags int flag, long discreteAccessTime, + long discreteAccessDuration) { + getOrCreateAttributedHistoricalOps(attributionTag).addDiscreteAccess(opCode, uidState, + flag, discreteAccessTime, discreteAccessDuration); + } + /** * Gets the package name which the data represents. * @@ -5870,7 +5980,8 @@ public class AppOpsManager { } private void filter(@Nullable String[] opNames, @HistoricalOpsRequestFilter int filter, - double scaleFactor) { + @OpHistoryFlags int historyFilter, double scaleFactor, long beginTimeMillis, + long endTimeMillis) { final int opCount = getOpCount(); for (int i = opCount - 1; i >= 0; i--) { final HistoricalOp op = mHistoricalOps.valueAt(i); @@ -5878,7 +5989,7 @@ public class AppOpsManager { op.getOpName())) { mHistoricalOps.removeAt(i); } else { - op.filter(scaleFactor); + op.filter(historyFilter, scaleFactor, beginTimeMillis, endTimeMillis); } } } @@ -5909,6 +6020,12 @@ public class AppOpsManager { getOrCreateHistoricalOp(opCode).increaseAccessDuration(uidState, flags, increment); } + private void addDiscreteAccess(int opCode, @UidState int uidState, @OpFlags int flag, + long discreteAccessTime, long discreteAccessDuration) { + getOrCreateHistoricalOp(opCode).addDiscreteAccess(uidState,flag, discreteAccessTime, + discreteAccessDuration); + } + /** * Gets number historical app ops. * @@ -5970,8 +6087,6 @@ public class AppOpsManager { return op; } - - // Code below generated by codegen v1.0.14. // // DO NOT MODIFY! @@ -6121,6 +6236,9 @@ public class AppOpsManager { private @Nullable LongSparseLongArray mRejectCount; private @Nullable LongSparseLongArray mAccessDuration; + /** Discrete Ops for this Op */ + private @Nullable List<AttributedOpEntry> mDiscreteAccesses; + /** @hide */ public HistoricalOp(int op) { mOp = op; @@ -6137,6 +6255,12 @@ public class AppOpsManager { if (other.mAccessDuration != null) { mAccessDuration = other.mAccessDuration.clone(); } + final int historicalOpCount = other.getDiscreteAccessCount(); + for (int i = 0; i < historicalOpCount; i++) { + final AttributedOpEntry origOp = other.getDiscreteAccessAt(i); + final AttributedOpEntry cloneOp = new AttributedOpEntry(origOp); + getOrCreateDiscreteAccesses().add(cloneOp); + } } private HistoricalOp(@NonNull Parcel parcel) { @@ -6144,22 +6268,45 @@ public class AppOpsManager { mAccessCount = readLongSparseLongArrayFromParcel(parcel); mRejectCount = readLongSparseLongArrayFromParcel(parcel); mAccessDuration = readLongSparseLongArrayFromParcel(parcel); + mDiscreteAccesses = readDiscreteAccessArrayFromParcel(parcel); } - private void filter(double scaleFactor) { - scale(mAccessCount, scaleFactor); - scale(mRejectCount, scaleFactor); - scale(mAccessDuration, scaleFactor); + private void filter(@OpHistoryFlags int historyFlag, double scaleFactor, + long beginTimeMillis, long endTimeMillis) { + if ((historyFlag & HISTORY_FLAG_AGGREGATE) == 0) { + mAccessCount = null; + mRejectCount = null; + mAccessDuration = null; + } else { + scale(mAccessCount, scaleFactor); + scale(mRejectCount, scaleFactor); + scale(mAccessDuration, scaleFactor); + } + if ((historyFlag & HISTORY_FLAG_DISCRETE) == 0) { + mDiscreteAccesses = null; + return; + } + final int discreteOpCount = getDiscreteAccessCount(); + for (int i = discreteOpCount - 1; i >= 0; i--) { + final AttributedOpEntry op = mDiscreteAccesses.get(i); + long opBeginTime = op.getLastAccessTime(OP_FLAGS_ALL); + long opEndTime = opBeginTime + op.getLastDuration(OP_FLAGS_ALL); + opEndTime = max(opBeginTime, opEndTime); + if (opEndTime < beginTimeMillis || opBeginTime > endTimeMillis) { + mDiscreteAccesses.remove(i); + } + } } private boolean isEmpty() { return !hasData(mAccessCount) && !hasData(mRejectCount) - && !hasData(mAccessDuration); + && !hasData(mAccessDuration) + && (mDiscreteAccesses == null); } private boolean hasData(@NonNull LongSparseLongArray array) { - return (array != null && array.size() > 0); + return array != null && array.size() > 0; } private @Nullable HistoricalOp splice(double fractionToRemove) { @@ -6191,6 +6338,32 @@ public class AppOpsManager { merge(this::getOrCreateAccessCount, other.mAccessCount); merge(this::getOrCreateRejectCount, other.mRejectCount); merge(this::getOrCreateAccessDuration, other.mAccessDuration); + + if (other.mDiscreteAccesses == null) { + return; + } + if (mDiscreteAccesses == null) { + mDiscreteAccesses = new ArrayList(other.mDiscreteAccesses); + return; + } + List<AttributedOpEntry> historicalDiscreteAccesses = new ArrayList<>(); + final int otherHistoricalOpCount = other.getDiscreteAccessCount(); + final int historicalOpCount = getDiscreteAccessCount(); + int i = 0; + int j = 0; + while (i < otherHistoricalOpCount || j < historicalOpCount) { + if (i == otherHistoricalOpCount) { + historicalDiscreteAccesses.add(mDiscreteAccesses.get(j++)); + } else if (j == historicalOpCount) { + historicalDiscreteAccesses.add(other.mDiscreteAccesses.get(i++)); + } else if (mDiscreteAccesses.get(j).getLastAccessTime(OP_FLAGS_ALL) + < other.mDiscreteAccesses.get(i).getLastAccessTime(OP_FLAGS_ALL)) { + historicalDiscreteAccesses.add(mDiscreteAccesses.get(j++)); + } else { + historicalDiscreteAccesses.add(other.mDiscreteAccesses.get(i++)); + } + } + mDiscreteAccesses = historicalDiscreteAccesses; } private void increaseAccessCount(@UidState int uidState, @OpFlags int flags, @@ -6218,6 +6391,23 @@ public class AppOpsManager { } } + private void addDiscreteAccess(@UidState int uidState, @OpFlags int flag, + long discreteAccessTime, long discreteAccessDuration) { + List<AttributedOpEntry> discreteAccesses = getOrCreateDiscreteAccesses(); + LongSparseArray<NoteOpEvent> accessEvents = new LongSparseArray<>(); + long key = makeKey(uidState, flag); + NoteOpEvent note = new NoteOpEvent(discreteAccessTime, discreteAccessDuration, null); + accessEvents.append(key, note); + AttributedOpEntry access = new AttributedOpEntry(mOp, false, accessEvents, null); + for (int i = discreteAccesses.size() - 1; i >= 0; i--) { + if (discreteAccesses.get(i).getLastAccessTime(OP_FLAGS_ALL) < discreteAccessTime) { + discreteAccesses.add(i + 1, access); + return; + } + } + discreteAccesses.add(0, access); + } + /** * Gets the op name. * @@ -6233,6 +6423,33 @@ public class AppOpsManager { } /** + * Gets number of discrete historical app ops. + * + * @return The number historical app ops. + * @see #getOpAt(int) + */ + public @IntRange(from = 0) int getDiscreteAccessCount() { + if (mDiscreteAccesses == null) { + return 0; + } + return mDiscreteAccesses.size(); + } + + /** + * Gets the historical op at a given index. + * + * @param index The index to lookup. + * @return The op at the given index. + * @see #getOpCount() + */ + public @NonNull AttributedOpEntry getDiscreteAccessAt(@IntRange(from = 0) int index) { + if (mDiscreteAccesses == null) { + throw new IndexOutOfBoundsException(); + } + return mDiscreteAccesses.get(index); + } + + /** * Gets the number times the op was accessed (performed) in the foreground. * * @param flags The flags which are any combination of @@ -6251,6 +6468,25 @@ public class AppOpsManager { } /** + * Gets the discrete events the op was accessed (performed) in the foreground. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return The list of discrete ops accessed in the foreground. + * + * @see #getBackgroundDiscreteAccesses(int) + * @see #getDiscreteAccesses(int, int, int) + */ + @NonNull + public List<AttributedOpEntry> getForegroundDiscreteAccesses(@OpFlags int flags) { + return listForFlagsInStates(mDiscreteAccesses, MAX_PRIORITY_UID_STATE, + resolveFirstUnrestrictedUidState(mOp), flags); + } + + /** * Gets the number times the op was accessed (performed) in the background. * * @param flags The flags which are any combination of @@ -6269,6 +6505,25 @@ public class AppOpsManager { } /** + * Gets the discrete events the op was accessed (performed) in the background. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return The list of discrete ops accessed in the background. + * + * @see #getForegroundDiscreteAccesses(int) + * @see #getDiscreteAccesses(int, int, int) + */ + @NonNull + public List<AttributedOpEntry> getBackgroundDiscreteAccesses(@OpFlags int flags) { + return listForFlagsInStates(mDiscreteAccesses, resolveLastRestrictedUidState(mOp), + MIN_PRIORITY_UID_STATE, flags); + } + + /** * Gets the number times the op was accessed (performed) for a * range of uid states. * @@ -6294,6 +6549,26 @@ public class AppOpsManager { } /** + * Gets the discrete events the op was accessed (performed) for a + * range of uid states. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return The discrete the op was accessed in the background. + * + * @see #getBackgroundDiscreteAccesses(int) + * @see #getForegroundDiscreteAccesses(int) + */ + @NonNull + public List<AttributedOpEntry> getDiscreteAccesses(@UidState int fromUidState, + @UidState int toUidState, @OpFlags int flags) { + return listForFlagsInStates(mDiscreteAccesses, fromUidState, toUidState, flags); + } + + /** * Gets the number times the op was rejected in the foreground. * * @param flags The flags which are any combination of @@ -6427,6 +6702,7 @@ public class AppOpsManager { writeLongSparseLongArrayToParcel(mAccessCount, parcel); writeLongSparseLongArrayToParcel(mRejectCount, parcel); writeLongSparseLongArrayToParcel(mAccessDuration, parcel); + writeDiscreteAccessArrayToParcel(mDiscreteAccesses, parcel); } @Override @@ -6447,7 +6723,11 @@ public class AppOpsManager { if (!equalsLongSparseLongArray(mRejectCount, other.mRejectCount)) { return false; } - return equalsLongSparseLongArray(mAccessDuration, other.mAccessDuration); + if (!equalsLongSparseLongArray(mAccessDuration, other.mAccessDuration)) { + return false; + } + return mDiscreteAccesses == null ? (other.mDiscreteAccesses == null ? true + : false) : mDiscreteAccesses.equals(other.mDiscreteAccesses); } @Override @@ -6456,6 +6736,7 @@ public class AppOpsManager { result = 31 * result + Objects.hashCode(mAccessCount); result = 31 * result + Objects.hashCode(mRejectCount); result = 31 * result + Objects.hashCode(mAccessDuration); + result = 31 * result + Objects.hashCode(mDiscreteAccesses); return result; } @@ -6484,6 +6765,13 @@ public class AppOpsManager { return mAccessDuration; } + private @NonNull List<AttributedOpEntry> getOrCreateDiscreteAccesses() { + if (mDiscreteAccesses == null) { + mDiscreteAccesses = new ArrayList<>(); + } + return mDiscreteAccesses; + } + /** * Multiplies the entries in the array with the passed in scale factor and * rounds the result at up 0.5 boundary. @@ -6574,6 +6862,32 @@ public class AppOpsManager { } /** + * Returns list of events filtered by UidState and UID flags. + * + * @param accesses The events list. + * @param beginUidState The beginning UID state (inclusive). + * @param endUidState The end UID state (inclusive). + * @param flags The UID flags. + * @return filtered list of events. + */ + private static List<AttributedOpEntry> listForFlagsInStates(List<AttributedOpEntry> accesses, + @UidState int beginUidState, @UidState int endUidState, @OpFlags int flags) { + List<AttributedOpEntry> result = new ArrayList<>(); + if (accesses == null) { + return result; + } + int nAccesses = accesses.size(); + for (int i = 0; i < nAccesses; i++) { + AttributedOpEntry entry = accesses.get(i); + if (entry.getLastAccessTime(beginUidState, endUidState, flags) == -1) { + continue; + } + result.add(entry); + } + return result; + } + + /** * Callback for notification of changes to operation state. */ public interface OnOpChangedListener { @@ -6796,8 +7110,9 @@ public class AppOpsManager { Objects.requireNonNull(callback, "callback cannot be null"); try { mService.getHistoricalOps(request.mUid, request.mPackageName, request.mAttributionTag, - request.mOpNames, request.mFilter, request.mBeginTimeMillis, - request.mEndTimeMillis, request.mFlags, new RemoteCallback((result) -> { + request.mOpNames, request.mHistoryFlags, request.mFilter, + request.mBeginTimeMillis, request.mEndTimeMillis, request.mFlags, + new RemoteCallback((result) -> { final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS); final long identity = Binder.clearCallingIdentity(); try { @@ -6835,9 +7150,9 @@ public class AppOpsManager { Objects.requireNonNull(callback, "callback cannot be null"); try { mService.getHistoricalOpsFromDiskRaw(request.mUid, request.mPackageName, - request.mAttributionTag, request.mOpNames, request.mFilter, - request.mBeginTimeMillis, request.mEndTimeMillis, request.mFlags, - new RemoteCallback((result) -> { + request.mAttributionTag, request.mOpNames, request.mHistoryFlags, + request.mFilter, request.mBeginTimeMillis, request.mEndTimeMillis, + request.mFlags, new RemoteCallback((result) -> { final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS); final long identity = Binder.clearCallingIdentity(); try { @@ -9072,6 +9387,32 @@ public class AppOpsManager { return array; } + private static void writeDiscreteAccessArrayToParcel( + @Nullable List<AttributedOpEntry> array, @NonNull Parcel parcel) { + if (array != null) { + final int size = array.size(); + parcel.writeInt(size); + for (int i = 0; i < size; i++) { + array.get(i).writeToParcel(parcel, 0); + } + } else { + parcel.writeInt(-1); + } + } + + private static @Nullable List<AttributedOpEntry> readDiscreteAccessArrayFromParcel( + @NonNull Parcel parcel) { + final int size = parcel.readInt(); + if (size < 0) { + return null; + } + final List<AttributedOpEntry> array = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + array.add(new AttributedOpEntry(parcel)); + } + return array; + } + /** * Collects the keys from an array to the result creating the result if needed. * diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 8167622ff13c..bc24e9767944 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -24,6 +24,7 @@ import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast; import static java.util.Objects.requireNonNull; +import android.annotation.AttrRes; import android.annotation.ColorInt; import android.annotation.ColorRes; import android.annotation.DimenRes; @@ -98,6 +99,7 @@ import android.widget.RemoteViews; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.graphics.ColorUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ContrastColorUtil; @@ -3649,11 +3651,6 @@ public class Notification implements Parcelable private int mCachedContrastColorIsFor = COLOR_INVALID; /** - * A neutral color color that can be used for icons. - */ - private int mNeutralColor = COLOR_INVALID; - - /** * Caches an instance of StandardTemplateParams. Note that this may have been used before, * so make sure to call {@link StandardTemplateParams#reset()} before using it. */ @@ -3666,6 +3663,7 @@ public class Notification implements Parcelable private boolean mRebuildStyledRemoteViews; private boolean mTintActionButtons; + private boolean mTintWithThemeAccent; private boolean mInNightMode; /** @@ -3701,6 +3699,7 @@ public class Notification implements Parcelable mContext = context; Resources res = mContext.getResources(); mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons); + mTintWithThemeAccent = res.getBoolean(R.bool.config_tintNotificationsWithTheme); if (res.getBoolean(R.bool.config_enableNightMode)) { Configuration currentConfig = res.getConfiguration(); @@ -4891,12 +4890,10 @@ public class Notification implements Parcelable } private void bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p) { - // TODO(b/180334837): Get buy-in on this color, or make sure to give this the - // accent color, while still accommodating the colorized state. contentView.setDrawableTint( R.id.phishing_alert, false /* targetBackground */, - getPrimaryTextColor(p), + getErrorColor(p), PorterDuff.Mode.SRC_ATOP); } @@ -4943,7 +4940,7 @@ public class Notification implements Parcelable contentView.setDrawableTint( R.id.alerted_icon, false /* targetBackground */, - getNeutralColor(p), + getHeaderIconColor(p), PorterDuff.Mode.SRC_ATOP); } @@ -5057,10 +5054,9 @@ public class Notification implements Parcelable return text; } - private void setTextViewColorPrimary(RemoteViews contentView, int id, + private void setTextViewColorPrimary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p) { - ensureColors(p); - contentView.setTextColor(id, mPrimaryTextColor); + contentView.setTextColor(id, getPrimaryTextColor(p)); } private boolean hasForegroundColor() { @@ -5068,53 +5064,34 @@ public class Notification implements Parcelable } /** - * Return the primary text color using the existing template params - * @hide - */ - @VisibleForTesting - public int getPrimaryTextColor() { - return getPrimaryTextColor(mParams); - } - - /** * @param p the template params to inflate this with * @return the primary text color * @hide */ @VisibleForTesting - public int getPrimaryTextColor(StandardTemplateParams p) { + public @ColorInt int getPrimaryTextColor(StandardTemplateParams p) { ensureColors(p); return mPrimaryTextColor; } /** - * Return the secondary text color using the existing template params - * @hide - */ - @VisibleForTesting - public int getSecondaryTextColor() { - return getSecondaryTextColor(mParams); - } - - /** * @param p the template params to inflate this with * @return the secondary text color * @hide */ @VisibleForTesting - public int getSecondaryTextColor(StandardTemplateParams p) { + public @ColorInt int getSecondaryTextColor(StandardTemplateParams p) { ensureColors(p); return mSecondaryTextColor; } - private void setTextViewColorSecondary(RemoteViews contentView, int id, + private void setTextViewColorSecondary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p) { - ensureColors(p); - contentView.setTextColor(id, mSecondaryTextColor); + contentView.setTextColor(id, getSecondaryTextColor(p)); } private void ensureColors(StandardTemplateParams p) { - int backgroundColor = getBackgroundColor(p); + int backgroundColor = getUnresolvedBackgroundColor(p); if (mPrimaryTextColor == COLOR_INVALID || mSecondaryTextColor == COLOR_INVALID || mTextColorsAreForBackground != backgroundColor) { @@ -5217,7 +5194,7 @@ public class Notification implements Parcelable R.id.progress, ColorStateList.valueOf(mContext.getColor( R.color.notification_progress_background_color))); if (getRawColor(p) != COLOR_DEFAULT) { - int color = isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p); + int color = getAccentColor(p); ColorStateList colorStateList = ColorStateList.valueOf(color); contentView.setProgressTintList(R.id.progress, colorStateList); contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList); @@ -5326,11 +5303,18 @@ public class Notification implements Parcelable } private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) { - int color = isColorized(p) ? getPrimaryTextColor(p) : getSecondaryTextColor(p); - contentView.setDrawableTint(R.id.expand_button, false, color, - PorterDuff.Mode.SRC_ATOP); - contentView.setInt(R.id.expand_button, "setOriginalNotificationColor", - color); + // set default colors + int textColor = getPrimaryTextColor(p); + int pillColor = getProtectionColor(p); + contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor); + contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor); + // Use different highlighted colors except when low-priority mode prevents that + if (!p.forceDefaultColor) { + textColor = getBackgroundColor(p); + pillColor = getAccentColor(p); + } + contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor); + contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor); } private void bindHeaderChronometerAndTime(RemoteViews contentView, @@ -5461,11 +5445,7 @@ public class Notification implements Parcelable } contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE); contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName()); - if (isColorized(p)) { - setTextViewColorPrimary(contentView, R.id.app_name_text, p); - } else { - contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p)); - } + contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p)); return true; } @@ -5555,6 +5535,10 @@ public class Notification implements Parcelable resetStandardTemplateWithActions(big); bindSnoozeAction(big, p); + // color the snooze and bubble actions with the theme color + ColorStateList actionColor = ColorStateList.valueOf(getStandardActionColor(p)); + big.setColorStateList(R.id.snooze_button, "setImageTintList", actionColor); + big.setColorStateList(R.id.bubble_button, "setImageTintList", actionColor); boolean validRemoteInput = false; @@ -5604,8 +5588,7 @@ public class Notification implements Parcelable showSpinner ? View.VISIBLE : View.GONE); big.setProgressIndeterminateTintList( R.id.notification_material_reply_progress, - ColorStateList.valueOf( - isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p))); + ColorStateList.valueOf(getAccentColor(p))); if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText()) && p.maxRemoteInputHistory > 1) { @@ -6021,14 +6004,14 @@ public class Notification implements Parcelable // change the background bgColor CharSequence title = action.title; ColorStateList[] outResultColor = new ColorStateList[1]; - int background = resolveBackgroundColor(p); + int background = getBackgroundColor(p); if (isLegacy()) { title = ContrastColorUtil.clearColorSpans(title); } else { title = ensureColorSpanContrast(title, background, outResultColor); } button.setTextViewText(R.id.action0, processTextSpans(title)); - int textColor = getPrimaryTextColor(p); + final int textColor; boolean hasColorOverride = outResultColor[0] != null; if (hasColorOverride) { // There's a span spanning the full text, let's take it and use it as the @@ -6036,9 +6019,11 @@ public class Notification implements Parcelable background = outResultColor[0].getDefaultColor(); textColor = ContrastColorUtil.resolvePrimaryColor(mContext, background, mInNightMode); - } else if (getRawColor(p) != COLOR_DEFAULT && !isColorized(p) - && mTintActionButtons && !mInNightMode) { - textColor = resolveContrastColor(p); + } else if (mTintActionButtons && !mInNightMode + && getRawColor(p) != COLOR_DEFAULT && !isColorized(p)) { + textColor = getAccentColor(p); + } else { + textColor = getPrimaryTextColor(p); } button.setTextColor(R.id.action0, textColor); // We only want about 20% alpha for the ripple @@ -6056,11 +6041,7 @@ public class Notification implements Parcelable } else { button.setTextViewText(R.id.action0, processTextSpans( processLegacyText(action.title))); - if (isColorized(p)) { - setTextViewColorPrimary(button, R.id.action0, p); - } else if (getRawColor(p) != COLOR_DEFAULT && mTintActionButtons) { - button.setTextColor(R.id.action0, resolveContrastColor(p)); - } + button.setTextColor(R.id.action0, getStandardActionColor(p)); } // CallStyle notifications add action buttons which don't actually exist in mActions, // so we have to omit the index in that case. @@ -6170,9 +6151,9 @@ public class Notification implements Parcelable private void processSmallIconColor(Icon smallIcon, RemoteViews contentView, StandardTemplateParams p) { boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon); - int color = isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p); + int color = getSmallIconColor(p); contentView.setInt(R.id.icon, "setBackgroundColor", - resolveBackgroundColor(p)); + getBackgroundColor(p)); contentView.setInt(R.id.icon, "setOriginalIconColor", colorable ? color : COLOR_INVALID); } @@ -6187,7 +6168,7 @@ public class Notification implements Parcelable if (largeIcon != null && isLegacy() && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) { // resolve color will fall back to the default when legacy - int color = resolveContrastColor(p); + int color = getContrastColor(p); contentView.setInt(R.id.icon, "setOriginalIconColor", color); } } @@ -6198,14 +6179,94 @@ public class Notification implements Parcelable } } - int resolveContrastColor(StandardTemplateParams p) { + /** + * Gets the standard action button color + */ + private @ColorInt int getStandardActionColor(Notification.StandardTemplateParams p) { + return mTintActionButtons || isColorized(p) ? getAccentColor(p) : getNeutralColor(p); + } + + /** + * Gets a neutral color that can be used for icons or similar that should not stand out. + */ + private @ColorInt int getHeaderIconColor(StandardTemplateParams p) { + return isColorized(p) ? getSecondaryTextColor(p) : getNeutralColor(p); + } + + /** + * Gets the foreground color of the small icon. If the notification is colorized, this + * is the primary text color, otherwise it's the contrast-adjusted app-provided color. + */ + private @ColorInt int getSmallIconColor(StandardTemplateParams p) { + return isColorized(p) ? getPrimaryTextColor(p) : getContrastColor(p); + } + + /** + * Gets the accent color for colored UI elements. If we're tinting with the theme + * accent, this is the theme accent color, otherwise this would be identical to + * {@link #getSmallIconColor(StandardTemplateParams)}. + */ + private @ColorInt int getAccentColor(StandardTemplateParams p) { + if (isColorized(p)) { + return getPrimaryTextColor(p); + } + if (mTintWithThemeAccent) { + int color = obtainThemeColor(R.attr.colorAccent, COLOR_INVALID); + if (color != COLOR_INVALID) { + return color; + } + } + return getContrastColor(p); + } + + /** + * Gets the "surface protection" color from the theme, or a variant of the normal background + * color when colorized, or when not using theme color tints. + */ + private @ColorInt int getProtectionColor(StandardTemplateParams p) { + if (mTintWithThemeAccent && !isColorized(p)) { + int color = obtainThemeColor(R.attr.colorBackgroundFloating, COLOR_INVALID); + if (color != COLOR_INVALID) { + return color; + } + } + // TODO(b/181048615): What color should we use for the expander pill when colorized + return ColorUtils.blendARGB(getPrimaryTextColor(p), getBackgroundColor(p), 0.8f); + } + + /** + * Gets the theme's error color, or the primary text color for colorized notifications. + */ + private @ColorInt int getErrorColor(StandardTemplateParams p) { + if (!isColorized(p)) { + int color = obtainThemeColor(R.attr.colorError, COLOR_INVALID); + if (color != COLOR_INVALID) { + return color; + } + } + return getPrimaryTextColor(p); + } + + /** + * Gets the theme's background color + */ + private @ColorInt int getDefaultBackgroundColor() { + return obtainThemeColor(R.attr.colorBackground, + mInNightMode ? Color.BLACK : Color.WHITE); + } + + /** + * Gets the contrast-adjusted version of the color provided by the app. + */ + private @ColorInt int getContrastColor(StandardTemplateParams p) { int rawColor = getRawColor(p); if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) { return mCachedContrastColor; } int color; - int background = obtainBackgroundColor(); + // TODO: Maybe use getBackgroundColor(p) instead -- but doing so could break the cache + int background = getDefaultBackgroundColor(); if (rawColor == COLOR_DEFAULT) { ensureColors(p); color = ContrastColorUtil.resolveDefaultColor(mContext, background, mInNightMode); @@ -6224,28 +6285,29 @@ public class Notification implements Parcelable /** * Return the raw color of this Notification, which doesn't necessarily satisfy contrast. * - * @see #resolveContrastColor(StandardTemplateParams) for the contrasted color + * @see #getContrastColor(StandardTemplateParams) for the contrasted color * @param p the template params to inflate this with */ - private int getRawColor(StandardTemplateParams p) { + private @ColorInt int getRawColor(StandardTemplateParams p) { if (p.forceDefaultColor) { return COLOR_DEFAULT; } return mN.color; } - int resolveNeutralColor() { - if (mNeutralColor != COLOR_INVALID) { - return mNeutralColor; - } - int background = obtainBackgroundColor(); - mNeutralColor = ContrastColorUtil.resolveDefaultColor(mContext, background, + /** + * Gets a neutral palette color; this is a contrast-satisfied version of the default color. + * @param p the template params to inflate this with + */ + private @ColorInt int getNeutralColor(StandardTemplateParams p) { + int background = getBackgroundColor(p); + int neutralColor = ContrastColorUtil.resolveDefaultColor(mContext, background, mInNightMode); - if (Color.alpha(mNeutralColor) < 255) { + if (Color.alpha(neutralColor) < 255) { // alpha doesn't go well for color filters, so let's blend it manually - mNeutralColor = ContrastColorUtil.compositeColors(mNeutralColor, background); + neutralColor = ContrastColorUtil.compositeColors(neutralColor, background); } - return mNeutralColor; + return neutralColor; } /** @@ -6389,8 +6451,11 @@ public class Notification implements Parcelable return mN; } - private @ColorInt int obtainBackgroundColor() { - int defaultColor = mInNightMode ? Color.BLACK : Color.WHITE; + /** + * Returns the color for the given Theme.DeviceDefault.DayNight attribute, or + * defValue if that could not be completed + */ + private @ColorInt int obtainThemeColor(@AttrRes int attrRes, @ColorInt int defaultColor) { Resources.Theme theme = mContext.getTheme(); if (theme == null) { // Running unit tests with mocked context @@ -6398,7 +6463,7 @@ public class Notification implements Parcelable } theme = new ContextThemeWrapper(mContext, R.style.Theme_DeviceDefault_DayNight) .getTheme(); - TypedArray ta = theme.obtainStyledAttributes(new int[]{R.attr.colorBackground}); + TypedArray ta = theme.obtainStyledAttributes(new int[]{attrRes}); if (ta == null) { return defaultColor; } @@ -6517,42 +6582,30 @@ public class Notification implements Parcelable return R.layout.notification_material_action_tombstone; } - private int getBackgroundColor(StandardTemplateParams p) { - if (isColorized(p)) { - return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : getRawColor(p); - } else { - return COLOR_DEFAULT; - } - } - /** - * Gets a neutral color that can be used for icons or similar that should not stand out. - * @param p the template params to inflate this with + * Gets the background color, with {@link #COLOR_DEFAULT} being a valid return value, + * which must be resolved by the caller before being used. */ - private int getNeutralColor(StandardTemplateParams p) { + private @ColorInt int getUnresolvedBackgroundColor(StandardTemplateParams p) { if (isColorized(p)) { - return getSecondaryTextColor(p); + return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : getRawColor(p); } else { - return resolveNeutralColor(); + return COLOR_DEFAULT; } } /** - * Same as getBackgroundColor but also resolved the default color to the background. - * @param p the template params to inflate this with + * Same as {@link #getUnresolvedBackgroundColor(StandardTemplateParams)} except that it + * also resolves the default color to the background. */ - private int resolveBackgroundColor(StandardTemplateParams p) { - int backgroundColor = getBackgroundColor(p); + private @ColorInt int getBackgroundColor(StandardTemplateParams p) { + int backgroundColor = getUnresolvedBackgroundColor(p); if (backgroundColor == COLOR_DEFAULT) { - backgroundColor = obtainBackgroundColor(); + backgroundColor = getDefaultBackgroundColor(); } return backgroundColor; } - private boolean shouldTintActionButtons() { - return mTintActionButtons; - } - private boolean textColorsNeedInversion() { if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) { return false; @@ -6570,7 +6623,7 @@ public class Notification implements Parcelable * * @hide */ - public void setColorPalette(int backgroundColor, int foregroundColor) { + public void setColorPalette(@ColorInt int backgroundColor, @ColorInt int foregroundColor) { mBackgroundColor = backgroundColor; mForegroundColor = foregroundColor; mTextColorsAreForBackground = COLOR_INVALID; @@ -8200,16 +8253,14 @@ public class Notification implements Parcelable TypedValue.COMPLEX_UNIT_DIP); } contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", - mBuilder.isColorized(p) - ? mBuilder.getPrimaryTextColor(p) - : mBuilder.resolveContrastColor(p)); + mBuilder.getSmallIconColor(p)); contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor", mBuilder.getPrimaryTextColor(p)); contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor", mBuilder.getSecondaryTextColor(p)); contentView.setInt(R.id.status_bar_latest_event_content, "setNotificationBackgroundColor", - mBuilder.resolveBackgroundColor(p)); + mBuilder.getBackgroundColor(p)); contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed", isCollapsed); contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement", @@ -8964,14 +9015,7 @@ public class Notification implements Parcelable // If the action buttons should not be tinted, then just use the default // notification color. Otherwise, just use the passed-in color. - Resources resources = mBuilder.mContext.getResources(); - Configuration currentConfig = resources.getConfiguration(); - boolean inNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) - == Configuration.UI_MODE_NIGHT_YES; - int tintColor = mBuilder.shouldTintActionButtons() || mBuilder.isColorized(p) - ? getActionColor(p) - : ContrastColorUtil.resolveColor(mBuilder.mContext, - Notification.COLOR_DEFAULT, inNightMode); + int tintColor = mBuilder.getStandardActionColor(p); container.setDrawableTint(buttonId, false, tintColor, PorterDuff.Mode.SRC_ATOP); @@ -9027,11 +9071,6 @@ public class Notification implements Parcelable return view; } - private int getActionColor(StandardTemplateParams p) { - return mBuilder.isColorized(p) ? mBuilder.getPrimaryTextColor(p) - : mBuilder.resolveContrastColor(p); - } - private RemoteViews makeMediaBigContentView() { final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS); // Dont add an expanded view if there is no more content to be revealed @@ -9373,7 +9412,6 @@ public class Notification implements Parcelable .hideLargeIcon(true) .text(text) .summaryText(mBuilder.processLegacyText(mVerificationText)); - // TODO(b/179178086): hide the snooze button RemoteViews contentView = mBuilder.applyStandardTemplate( mBuilder.getCallLayoutResource(), p, null /* result */); @@ -9390,11 +9428,9 @@ public class Notification implements Parcelable // Bind some custom CallLayout properties contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", - mBuilder.isColorized(p) - ? mBuilder.getPrimaryTextColor(p) - : mBuilder.resolveContrastColor(p)); + mBuilder.getSmallIconColor(p)); contentView.setInt(R.id.status_bar_latest_event_content, - "setNotificationBackgroundColor", mBuilder.resolveBackgroundColor(p)); + "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p)); contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", mBuilder.mN.mLargeIcon); contentView.setBundle(R.id.status_bar_latest_event_content, "setData", diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index 1ff64dbe6d2e..e0e9b62d3809 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -73,6 +73,7 @@ per-file ClientTransactionHandler.java = file:/services/core/java/com/android/se per-file Fragment.java = file:/services/core/java/com/android/server/wm/OWNERS per-file *Task* = file:/services/core/java/com/android/server/wm/OWNERS per-file Window* = file:/services/core/java/com/android/server/wm/OWNERS +per-file ConfigurationController.java = file:/services/core/java/com/android/server/wm/OWNERS # TODO(b/174932174): determine the ownership of KeyguardManager.java diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java index ea7eab2a2877..358ce6a83a21 100644 --- a/core/java/android/app/PictureInPictureParams.java +++ b/core/java/android/app/PictureInPictureParams.java @@ -202,7 +202,7 @@ public final class PictureInPictureParams implements Parcelable { } if (in.readInt() != 0) { mUserActions = new ArrayList<>(); - in.readParcelableList(mUserActions, RemoteAction.class.getClassLoader()); + in.readTypedList(mUserActions, RemoteAction.CREATOR); } if (in.readInt() != 0) { mSourceRectHint = Rect.CREATOR.createFromParcel(in); @@ -386,7 +386,7 @@ public final class PictureInPictureParams implements Parcelable { } if (mUserActions != null) { out.writeInt(1); - out.writeParcelableList(mUserActions, 0); + out.writeTypedList(mUserActions, 0); } else { out.writeInt(0); } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index e16e40b6d572..43c14a99b221 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -71,7 +71,6 @@ import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.ShortcutManager; import android.content.pm.verify.domain.DomainVerificationManager; -import android.content.pm.verify.domain.DomainVerificationManagerImpl; import android.content.pm.verify.domain.IDomainVerificationManager; import android.content.res.Resources; import android.content.rollback.RollbackManagerFrameworkInitializer; @@ -1422,7 +1421,6 @@ public final class SystemServiceRegistry { } }); - // TODO(b/159952358): Only register this service for the domain verification agent? registerService(Context.DOMAIN_VERIFICATION_SERVICE, DomainVerificationManager.class, new CachedServiceFetcher<DomainVerificationManager>() { @Override @@ -1432,7 +1430,7 @@ public final class SystemServiceRegistry { Context.DOMAIN_VERIFICATION_SERVICE); IDomainVerificationManager service = IDomainVerificationManager.Stub.asInterface(binder); - return new DomainVerificationManagerImpl(context, service); + return new DomainVerificationManager(context, service); } }); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index b919bfc92e79..0635bd08e22b 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2997,6 +2997,7 @@ public class DevicePolicyManager { */ // TODO(b/173541467): should it throw SecurityException if caller is not admin? public boolean isSafeOperation(@OperationSafetyReason int reason) { + throwIfParentInstance("isSafeOperation"); if (mService == null) return false; try { diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java index 22492ccd0373..94a4fde0131e 100644 --- a/core/java/android/app/backup/BackupAgent.java +++ b/core/java/android/app/backup/BackupAgent.java @@ -403,7 +403,7 @@ public abstract class BackupAgent extends ContextWrapper { public void onFullBackup(FullBackupDataOutput data) throws IOException { FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this, mOperationType); - if (!isDeviceToDeviceMigration() && !backupScheme.isFullBackupContentEnabled()) { + if (!backupScheme.isFullBackupEnabled(data.getTransportFlags())) { return; } @@ -911,7 +911,7 @@ public abstract class BackupAgent extends ContextWrapper { } FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this, mOperationType); - if (!bs.isFullBackupContentEnabled()) { + if (!bs.isFullRestoreEnabled()) { if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { Log.v(FullBackup.TAG_XML_PARSER, "onRestoreFile \"" + destination.getCanonicalPath() diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java index 829b6cd43934..9b543b571a44 100644 --- a/core/java/android/app/backup/FullBackup.java +++ b/core/java/android/app/backup/FullBackup.java @@ -99,6 +99,8 @@ public class FullBackup { public static final String FLAG_REQUIRED_DEVICE_TO_DEVICE_TRANSFER = "deviceToDeviceTransfer"; public static final String FLAG_REQUIRED_FAKE_CLIENT_SIDE_ENCRYPTION = "fakeClientSideEncryption"; + private static final String FLAG_DISABLE_IF_NO_ENCRYPTION_CAPABILITIES + = "disableIfNoEncryptionCapabilities"; /** * When this change is enabled, include / exclude rules specified via @@ -307,6 +309,10 @@ public class FullBackup { // lazy initialized, only when needed private StorageVolume[] mVolumes = null; + // Properties the transport must have (e.g. encryption) for the operation to go ahead. + @Nullable private Integer mRequiredTransportFlags; + @Nullable private Boolean mIsUsingNewScheme; + /** * Parse out the semantic domains into the correct physical location. */ @@ -453,6 +459,35 @@ public class FullBackup { } } + boolean isFullBackupEnabled(int transportFlags) { + try { + if (isUsingNewScheme()) { + int requiredTransportFlags = getRequiredTransportFlags(); + // All bits that are set in requiredTransportFlags must be set in + // transportFlags. + return (transportFlags & requiredTransportFlags) == requiredTransportFlags; + } + } catch (IOException | XmlPullParserException e) { + Slog.w(TAG, "Failed to interpret the backup scheme: " + e); + return false; + } + + return isFullBackupContentEnabled(); + } + + boolean isFullRestoreEnabled() { + try { + if (isUsingNewScheme()) { + return true; + } + } catch (IOException | XmlPullParserException e) { + Slog.w(TAG, "Failed to interpret the backup scheme: " + e); + return false; + } + + return isFullBackupContentEnabled(); + } + boolean isFullBackupContentEnabled() { if (mFullBackupContent < 0) { // android:fullBackupContent="false", bail. @@ -491,10 +526,30 @@ public class FullBackup { return mExcludes; } + private synchronized int getRequiredTransportFlags() + throws IOException, XmlPullParserException { + if (mRequiredTransportFlags == null) { + maybeParseBackupSchemeLocked(); + } + + return mRequiredTransportFlags; + } + + private synchronized boolean isUsingNewScheme() + throws IOException, XmlPullParserException { + if (mIsUsingNewScheme == null) { + maybeParseBackupSchemeLocked(); + } + + return mIsUsingNewScheme; + } + private void maybeParseBackupSchemeLocked() throws IOException, XmlPullParserException { // This not being null is how we know that we've tried to parse the xml already. mIncludes = new ArrayMap<String, Set<PathWithRequiredFlags>>(); mExcludes = new ArraySet<PathWithRequiredFlags>(); + mRequiredTransportFlags = 0; + mIsUsingNewScheme = false; if (mFullBackupContent == 0 && mDataExtractionRules == 0) { // No scheme specified via either new or legacy config, will copy everything. @@ -535,12 +590,14 @@ public class FullBackup { } if (!mExcludes.isEmpty() || !mIncludes.isEmpty()) { // Found configuration in the new config, we will use it. + mIsUsingNewScheme = true; return; } } if (operationType == OperationType.MIGRATION && CompatChanges.isChangeEnabled(IGNORE_FULL_BACKUP_CONTENT_IN_D2D)) { + mIsUsingNewScheme = true; return; } @@ -584,13 +641,24 @@ public class FullBackup { continue; } - // TODO(b/180523028): Parse required attributes for rules (e.g. encryption). + parseRequiredTransportFlags(parser, configSection); parseRules(parser, excludes, includes, Optional.of(0), configSection); } logParsingResults(excludes, includes); } + private void parseRequiredTransportFlags(XmlPullParser parser, + @ConfigSection String configSection) { + if (ConfigSection.CLOUD_BACKUP.equals(configSection)) { + String encryptionAttribute = parser.getAttributeValue(/* namespace */ null, + FLAG_DISABLE_IF_NO_ENCRYPTION_CAPABILITIES); + if ("true".equals(encryptionAttribute)) { + mRequiredTransportFlags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED; + } + } + } + @VisibleForTesting public void parseBackupSchemeFromXmlLocked(XmlPullParser parser, Set<PathWithRequiredFlags> excludes, diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java index 1d5dc1d5df1c..098d8b6c6058 100644 --- a/core/java/android/app/usage/NetworkStatsManager.java +++ b/core/java/android/app/usage/NetworkStatsManager.java @@ -16,6 +16,8 @@ package android.app.usage; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -28,8 +30,11 @@ import android.content.Context; import android.net.ConnectivityManager; import android.net.DataUsageRequest; import android.net.INetworkStatsService; +import android.net.Network; import android.net.NetworkStack; +import android.net.NetworkStateSnapshot; import android.net.NetworkTemplate; +import android.net.UnderlyingNetworkInfo; import android.net.netstats.provider.INetworkStatsProviderCallback; import android.net.netstats.provider.NetworkStatsProvider; import android.os.Binder; @@ -48,6 +53,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.net.module.util.NetworkIdentityUtils; +import java.util.List; import java.util.Objects; /** @@ -633,6 +639,50 @@ public class NetworkStatsManager { return template; } + /** + * Notify {@code NetworkStatsService} about network status changed. + * + * Notifies NetworkStatsService of network state changes for data usage accounting purposes. + * + * To avoid races that attribute data usage to wrong network, such as new network with + * the same interface after SIM hot-swap, this function will not return until + * {@code NetworkStatsService} finishes its work of retrieving traffic statistics from + * all data sources. + * + * @param defaultNetworks the list of all networks that could be used by network traffic that + * does not explicitly select a network. + * @param networkStateSnapshots a list of {@link NetworkStateSnapshot}s, one for + * each network that is currently connected. + * @param activeIface the active (i.e., connected) default network interface for the calling + * uid. Used to determine on which network future calls to + * {@link android.net.TrafficStats#incrementOperationCount} applies to. + * @param underlyingNetworkInfos the list of underlying network information for all + * currently-connected VPNs. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) + public void notifyNetworkStatus( + @NonNull List<Network> defaultNetworks, + @NonNull List<NetworkStateSnapshot> networkStateSnapshots, + @Nullable String activeIface, + @NonNull List<UnderlyingNetworkInfo> underlyingNetworkInfos) { + try { + Objects.requireNonNull(defaultNetworks); + Objects.requireNonNull(networkStateSnapshots); + Objects.requireNonNull(underlyingNetworkInfos); + // TODO: Change internal namings after the name is decided. + mService.forceUpdateIfaces(defaultNetworks.toArray(new Network[0]), + networkStateSnapshots.toArray(new NetworkStateSnapshot[0]), activeIface, + underlyingNetworkInfos.toArray(new UnderlyingNetworkInfo[0])); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private static class CallbackHandler extends Handler { private final int mNetworkType; private final String mSubscriberId; diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index 081f4fdc1b12..e6a4656bdbc5 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -334,10 +334,19 @@ public final class UsageEvents implements Parcelable { public static final int LOCUS_ID_SET = 30; /** + * An event type denoting that a component in the package has been used (e.g. broadcast + * receiver, service, content provider). This generally matches up with usage that would + * cause an app to leave force stop. The component itself is not provided as we are only + * interested in whether the package is used, not the component itself. + * @hide + */ + public static final int APP_COMPONENT_USED = 31; + + /** * Keep in sync with the greatest event type value. * @hide */ - public static final int MAX_EVENT_TYPE = 30; + public static final int MAX_EVENT_TYPE = 31; /** @hide */ public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0; diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index e7661dbad749..ec94faa544d3 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -374,6 +374,35 @@ public final class BluetoothDevice implements Parcelable { public static final String ACTION_SDP_RECORD = "android.bluetooth.device.action.SDP_RECORD"; + /** @hide */ + @IntDef(prefix = "METADATA_", value = { + METADATA_MANUFACTURER_NAME, + METADATA_MODEL_NAME, + METADATA_SOFTWARE_VERSION, + METADATA_HARDWARE_VERSION, + METADATA_COMPANION_APP, + METADATA_MAIN_ICON, + METADATA_IS_UNTETHERED_HEADSET, + METADATA_UNTETHERED_LEFT_ICON, + METADATA_UNTETHERED_RIGHT_ICON, + METADATA_UNTETHERED_CASE_ICON, + METADATA_UNTETHERED_LEFT_BATTERY, + METADATA_UNTETHERED_RIGHT_BATTERY, + METADATA_UNTETHERED_CASE_BATTERY, + METADATA_UNTETHERED_LEFT_CHARGING, + METADATA_UNTETHERED_RIGHT_CHARGING, + METADATA_UNTETHERED_CASE_CHARGING, + METADATA_ENHANCED_SETTINGS_UI_URI, + METADATA_DEVICE_TYPE, + METADATA_MAIN_BATTERY, + METADATA_MAIN_CHARGING, + METADATA_MAIN_LOW_BATTERY_THRESHOLD, + METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD, + METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD, + METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD}) + @Retention(RetentionPolicy.SOURCE) + public @interface MetadataKey{} + /** * Maximum length of a metadata entry, this is to avoid exploding Bluetooth * disk usage @@ -523,6 +552,89 @@ public final class BluetoothDevice implements Parcelable { public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; /** + * Type of the Bluetooth device, must be within the list of + * BluetoothDevice.DEVICE_TYPE_* + * Data type should be {@String} as {@link Byte} array. + * @hide + */ + @SystemApi + public static final int METADATA_DEVICE_TYPE = 17; + + /** + * Battery level of the Bluetooth device, use when the Bluetooth device + * does not support HFP battery indicator. + * Data type should be {@String} as {@link Byte} array. + * @hide + */ + @SystemApi + public static final int METADATA_MAIN_BATTERY = 18; + + /** + * Whether the device is charging. + * Data type should be {@String} as {@link Byte} array. + * @hide + */ + @SystemApi + public static final int METADATA_MAIN_CHARGING = 19; + + /** + * The battery threshold of the Bluetooth device to show low battery icon. + * Data type should be {@String} as {@link Byte} array. + * @hide + */ + @SystemApi + public static final int METADATA_MAIN_LOW_BATTERY_THRESHOLD = 20; + + /** + * The battery threshold of the left headset to show low battery icon. + * Data type should be {@String} as {@link Byte} array. + * @hide + */ + @SystemApi + public static final int METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD = 21; + + /** + * The battery threshold of the right headset to show low battery icon. + * Data type should be {@String} as {@link Byte} array. + * @hide + */ + @SystemApi + public static final int METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD = 22; + + /** + * The battery threshold of the case to show low battery icon. + * Data type should be {@String} as {@link Byte} array. + * @hide + */ + @SystemApi + public static final int METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD = 23; + + /** + * Device type which is used in METADATA_DEVICE_TYPE + * Indicates this Bluetooth device is a standard Bluetooth accessory or + * not listed in METADATA_DEVICE_TYPE_*. + * @hide + */ + @SystemApi + public static final String DEVICE_TYPE_DEFAULT = "Default"; + + /** + * Device type which is used in METADATA_DEVICE_TYPE + * Indicates this Bluetooth device is a watch. + * @hide + */ + @SystemApi + public static final String DEVICE_TYPE_WATCH = "Watch"; + + /** + * Device type which is used in METADATA_DEVICE_TYPE + * Indicates this Bluetooth device is an untethered headset. + * @hide + */ + @SystemApi + public static final String DEVICE_TYPE_UNTETHERED_HEADSET = "Untethered Headset"; + + /** * Broadcast Action: This intent is used to broadcast the {@link UUID} * wrapped as a {@link android.os.ParcelUuid} of the remote device after it * has been fetched. This intent is sent only when the UUIDs of the remote @@ -2316,7 +2428,7 @@ public final class BluetoothDevice implements Parcelable { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public boolean setMetadata(int key, @NonNull byte[] value) { + public boolean setMetadata(@MetadataKey int key, @NonNull byte[] value) { final IBluetooth service = sService; if (service == null) { Log.e(TAG, "Bluetooth is not enabled. Cannot set metadata"); @@ -2344,7 +2456,7 @@ public final class BluetoothDevice implements Parcelable { @SystemApi @Nullable @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public byte[] getMetadata(int key) { + public byte[] getMetadata(@MetadataKey int key) { final IBluetooth service = sService; if (service == null) { Log.e(TAG, "Bluetooth is not enabled. Cannot get metadata"); @@ -2357,4 +2469,14 @@ public final class BluetoothDevice implements Parcelable { return null; } } + + /** + * Get the maxinum metadata key ID. + * + * @return the last supported metadata key + * @hide + */ + public static @MetadataKey int getMaxMetadataKey() { + return METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD; + } } diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java index e3a130c4b436..4e64dbed7017 100644 --- a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java +++ b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java @@ -22,7 +22,7 @@ import android.os.Parcelable; /** * The {@link PeriodicAdvertisingParameters} provide a way to adjust periodic * advertising preferences for each Bluetooth LE advertising set. Use {@link - * AdvertisingSetParameters.Builder} to create an instance of this class. + * PeriodicAdvertisingParameters.Builder} to create an instance of this class. */ public final class PeriodicAdvertisingParameters implements Parcelable { diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java index 102c98ff9329..17bdd42a0f45 100644 --- a/core/java/android/companion/AssociationRequest.java +++ b/core/java/android/companion/AssociationRequest.java @@ -60,6 +60,10 @@ public final class AssociationRequest implements Parcelable { /** * Device profile: watch. * + * If specified, the current request may have a modified UI to highlight that the device being + * set up is a specific kind of device, and some extra permissions may be granted to the app + * as a result. + * * @see AssociationRequest.Builder#setDeviceProfile */ public static final String DEVICE_PROFILE_WATCH = diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index f3a4e1f79955..02e86cd4a863 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -370,6 +370,15 @@ public abstract class Context { /*********** Hidden flags below this line ***********/ /** + * Flag for {@link #bindService}: This flag is only intended to be used by the system to + * indicate that a service binding is not considered as real package component usage and should + * not generate a {@link android.app.usage.UsageEvents.Event#APP_COMPONENT_USED} event in usage + * stats. + * @hide + */ + public static final int BIND_NOT_APP_COMPONENT_USAGE = 0x00008000; + + /** * Flag for {@link #bindService}: allow the process hosting the target service to be treated * as if it's as important as a perceptible app to the user and avoid the oom killer killing * this process in low memory situations until there aren't any other processes left but the diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 0aa1be94d279..1a5dad5f7596 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -1205,7 +1205,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { CATEGORY_SOCIAL, CATEGORY_NEWS, CATEGORY_MAPS, - CATEGORY_PRODUCTIVITY + CATEGORY_PRODUCTIVITY, + CATEGORY_ACCESSIBILITY }) @Retention(RetentionPolicy.SOURCE) public @interface Category { @@ -1281,6 +1282,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public static final int CATEGORY_PRODUCTIVITY = 7; /** + * Category for apps which are primarily accessibility apps, such as screen-readers. + * + * @see #category + */ + public static final int CATEGORY_ACCESSIBILITY = 8; + + /** * Return a concise, localized title for the given * {@link ApplicationInfo#category} value, or {@code null} for unknown * values such as {@link #CATEGORY_UNDEFINED}. @@ -1305,6 +1313,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { return context.getText(com.android.internal.R.string.app_category_maps); case ApplicationInfo.CATEGORY_PRODUCTIVITY: return context.getText(com.android.internal.R.string.app_category_productivity); + case ApplicationInfo.CATEGORY_ACCESSIBILITY: + return context.getText(com.android.internal.R.string.app_category_accessibility); default: return null; } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 3bc61444f1d1..d79b66c1cf56 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3584,30 +3584,18 @@ public abstract class PackageManager { * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has * the requisite kernel support to support incremental delivery aka Incremental FileSystem. * - * @see IncrementalManager#isFeatureEnabled - * @hide - * - * @deprecated Use {@link #FEATURE_INCREMENTAL_DELIVERY_VERSION} instead. - */ - @Deprecated - @SystemApi - @SdkConstant(SdkConstantType.FEATURE) - public static final String FEATURE_INCREMENTAL_DELIVERY = - "android.software.incremental_delivery"; - - /** - * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: * feature not present - IncFs is not present on the device. * 1 - IncFs v1, core features, no PerUid support. Optional in R. * 2 - IncFs v2, PerUid support, fs-verity support. Required in S. * + * @see IncrementalManager#isFeatureEnabled * @see IncrementalManager#getVersion() * @hide */ @SystemApi @SdkConstant(SdkConstantType.FEATURE) - public static final String FEATURE_INCREMENTAL_DELIVERY_VERSION = - "android.software.incremental_delivery_version"; + public static final String FEATURE_INCREMENTAL_DELIVERY = + "android.software.incremental_delivery"; /** * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: @@ -7029,7 +7017,7 @@ public abstract class PackageManager { * domain to an application, use * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)}, * passing in all of the domains returned inside - * {@link DomainVerificationManager#getDomainVerificationUserSelection(String)}. + * {@link DomainVerificationManager#getDomainVerificationUserState(String)}. * * @hide */ diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java index 7ecb11248d23..7696cbe0b631 100644 --- a/core/java/android/content/pm/RegisteredServicesCache.java +++ b/core/java/android/content/pm/RegisteredServicesCache.java @@ -42,6 +42,7 @@ import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import libcore.io.IoUtils; @@ -161,18 +162,20 @@ public abstract class RegisteredServicesCache<V> { intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); intentFilter.addDataScheme("package"); - mContext.registerReceiverAsUser(mPackageReceiver, UserHandle.ALL, intentFilter, null, null); + Handler handler = BackgroundThread.getHandler(); + mContext.registerReceiverAsUser( + mPackageReceiver, UserHandle.ALL, intentFilter, null, handler); // Register for events related to sdcard installation. IntentFilter sdFilter = new IntentFilter(); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); - mContext.registerReceiver(mExternalReceiver, sdFilter); + mContext.registerReceiver(mExternalReceiver, sdFilter, null, handler); // Register for user-related events IntentFilter userFilter = new IntentFilter(); sdFilter.addAction(Intent.ACTION_USER_REMOVED); - mContext.registerReceiver(mUserRemovedReceiver, userFilter); + mContext.registerReceiver(mUserRemovedReceiver, userFilter, null, handler); } private void handlePackageEvent(Intent intent, int userId) { @@ -265,7 +268,7 @@ public abstract class RegisteredServicesCache<V> { public void setListener(RegisteredServicesCacheListener<V> listener, Handler handler) { if (handler == null) { - handler = new Handler(mContext.getMainLooper()); + handler = BackgroundThread.getHandler(); } synchronized (this) { mHandler = handler; diff --git a/core/java/android/content/pm/permission/OWNERS b/core/java/android/content/pm/permission/OWNERS index d302b0ae1ea8..cf7e6890876a 100644 --- a/core/java/android/content/pm/permission/OWNERS +++ b/core/java/android/content/pm/permission/OWNERS @@ -1,10 +1,8 @@ # Bug component: 137825 +include platform/frameworks/base:/core/java/android/permission/OWNERS + toddke@android.com toddke@google.com patb@google.com -svetoslavganov@android.com -svetoslavganov@google.com -zhanghai@google.com -evanseverson@google.com -ntmyren@google.com + diff --git a/core/java/android/content/pm/verify/domain/DomainOwner.java b/core/java/android/content/pm/verify/domain/DomainOwner.java index b050f5da7928..5bf2c0983a9a 100644 --- a/core/java/android/content/pm/verify/domain/DomainOwner.java +++ b/core/java/android/content/pm/verify/domain/DomainOwner.java @@ -66,16 +66,7 @@ public final class DomainOwner implements Parcelable { * @param packageName * Package name of that owns the domain. * @param overrideable - * Whether or not this owner can be automatically overridden. If all owners for a domain are - * overrideable, then calling - * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, - * Set, boolean)} to enable the domain will disable all other owners. On the other hand, if any - * of the owners are non-overrideable, then - * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String, - * boolean)} must be called with false to disable all of the other owners before this domain can - * be taken by a new owner through - * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, - * Set, boolean)}. + * Whether or not this owner can be automatically overridden. */ @DataClass.Generated.Member public DomainOwner( @@ -98,16 +89,9 @@ public final class DomainOwner implements Parcelable { } /** - * Whether or not this owner can be automatically overridden. If all owners for a domain are - * overrideable, then calling - * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, - * Set, boolean)} to enable the domain will disable all other owners. On the other hand, if any - * of the owners are non-overrideable, then - * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String, - * boolean)} must be called with false to disable all of the other owners before this domain can - * be taken by a new owner through - * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, - * Set, boolean)}. + * Whether or not this owner can be automatically overridden. + * + * @see DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean) */ @DataClass.Generated.Member public boolean isOverrideable() { @@ -205,7 +189,7 @@ public final class DomainOwner implements Parcelable { }; @DataClass.Generated( - time = 1614119379978L, + time = 1614721802044L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainOwner.java", inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final boolean mOverrideable\nclass DomainOwner extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genEqualsHashCode=true, genAidl=true, genToString=true)") diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java index 809587524f58..7c335b1d26dd 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java @@ -94,7 +94,7 @@ public final class DomainVerificationInfo implements Parcelable { private Map<String, Integer> unparcelHostToStateMap(Parcel in) { return DomainVerificationUtils.readHostMap(in, new ArrayMap<>(), - DomainVerificationUserSelection.class.getClassLoader()); + DomainVerificationUserState.class.getClassLoader()); } @@ -105,8 +105,7 @@ public final class DomainVerificationInfo implements Parcelable { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain - // /DomainVerificationInfo.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -321,7 +320,7 @@ public final class DomainVerificationInfo implements Parcelable { }; @DataClass.Generated( - time = 1613002530369L, + time = 1614721812023L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java", inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)") diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java index 11402afac8b6..f7c81bcffda3 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java @@ -25,6 +25,8 @@ import android.annotation.SystemService; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager.NameNotFoundException; +import android.os.RemoteException; +import android.os.ServiceSpecificException; import android.os.UserHandle; import java.util.List; @@ -32,55 +34,63 @@ import java.util.Set; import java.util.UUID; /** - * System service to access the domain verification APIs. + * System service to access domain verification APIs. * - * Allows the approved domain verification - * agent on the device (the sole holder of - * {@link android.Manifest.permission#DOMAIN_VERIFICATION_AGENT}) to update the approval status - * of domains declared by applications in their AndroidManifest.xml, to allow them to open those - * links inside the app when selected by the user. This is done through querying - * {@link #getDomainVerificationInfo(String)} and calling - * {@link #setDomainVerificationStatus(UUID, Set, int)}. - * - * Also allows the domain preference settings (holder of - * {@link android.Manifest.permission#UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) to update the - * preferences of the user, when they have chosen to explicitly allow an application to open links. - * This is done through querying {@link #getDomainVerificationUserSelection(String)} and calling - * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)} and - * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}. - * - * @hide + * Applications should use {@link #getDomainVerificationUserState(String)} if necessary to + * check if/how they are verified for a domain, which is required starting from platform + * {@link android.os.Build.VERSION_CODES#S} in order to open {@link Intent}s which declare + * {@link Intent#CATEGORY_BROWSABLE} or no category and also match against + * {@link Intent#CATEGORY_DEFAULT} {@link android.content.IntentFilter}s, either through an + * explicit declaration of {@link Intent#CATEGORY_DEFAULT} or through the use of + * {@link android.content.pm.PackageManager#MATCH_DEFAULT_ONLY}, which is usually added for the + * caller when using {@link Context#startActivity(Intent)} and similar. */ -@SystemApi @SystemService(Context.DOMAIN_VERIFICATION_SERVICE) -public interface DomainVerificationManager { +public final class DomainVerificationManager { /** - * Extra field name for a {@link DomainVerificationRequest} for the requested packages. - * Passed to an the domain verification agent that handles + * Extra field name for a {@link DomainVerificationRequest} for the requested packages. Passed + * to an the domain verification agent that handles * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION}. + * + * @hide */ - String EXTRA_VERIFICATION_REQUEST = + @SystemApi + public static final String EXTRA_VERIFICATION_REQUEST = "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST"; /** * No response has been recorded by either the system or any verification agent. + * + * @hide */ - int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE; + @SystemApi + public static final int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE; - /** The verification agent has explicitly verified the domain at some point. */ - int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS; + /** + * The verification agent has explicitly verified the domain at some point. + * + * @hide + */ + @SystemApi + public static final int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS; /** - * The first available custom response code. This and any greater integer, along with - * {@link #STATE_SUCCESS} are the only values settable by the verification agent. All values - * will be treated as if the domain is unverified. + * The first available custom response code. This and any greater integer, along with {@link + * #STATE_SUCCESS} are the only values settable by the verification agent. All values will be + * treated as if the domain is unverified. + * + * @hide */ - int STATE_FIRST_VERIFIER_DEFINED = DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED; + @SystemApi + public static final int STATE_FIRST_VERIFIER_DEFINED = + DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED; - /** @hide */ + /** + * @hide + */ @NonNull - static String stateToDebugString(@DomainVerificationState.State int state) { + public static String stateToDebugString(@DomainVerificationState.State int state) { switch (state) { case DomainVerificationState.STATE_NO_RESPONSE: return "none"; @@ -104,10 +114,13 @@ public interface DomainVerificationManager { } /** - * Checks if a state considers the corresponding domain to be successfully verified. The - * domain verification agent may use this to determine whether or not to re-verify a domain. + * Checks if a state considers the corresponding domain to be successfully verified. The domain + * verification agent may use this to determine whether or not to re-verify a domain. + * + * @hide */ - static boolean isStateVerified(@DomainVerificationState.State int state) { + @SystemApi + public static boolean isStateVerified(@DomainVerificationState.State int state) { switch (state) { case DomainVerificationState.STATE_SUCCESS: case DomainVerificationState.STATE_APPROVED: @@ -126,10 +139,13 @@ public interface DomainVerificationManager { /** * Checks if a state is modifiable by the domain verification agent. This is useful as the * platform may add new state codes in newer versions, and older verification agents can use - * this method to determine if a state can be changed without having to be aware of what the - * new state means. + * this method to determine if a state can be changed without having to be aware of what the new + * state means. + * + * @hide */ - static boolean isStateModifiable(@DomainVerificationState.State int state) { + @SystemApi + public static boolean isStateModifiable(@DomainVerificationState.State int state) { switch (state) { case DomainVerificationState.STATE_NO_RESPONSE: case DomainVerificationState.STATE_SUCCESS: @@ -147,11 +163,12 @@ public interface DomainVerificationManager { } /** - * For determine re-verify policy. This is hidden from the domain verification agent so that - * no behavior is made based on the result. + * For determine re-verify policy. This is hidden from the domain verification agent so that no + * behavior is made based on the result. + * * @hide */ - static boolean isStateDefault(@DomainVerificationState.State int state) { + public static boolean isStateDefault(@DomainVerificationState.State int state) { switch (state) { case DomainVerificationState.STATE_NO_RESPONSE: case DomainVerificationState.STATE_MIGRATED: @@ -168,14 +185,72 @@ public interface DomainVerificationManager { } /** + * @hide + */ + public static final int ERROR_INVALID_DOMAIN_SET = 1; + /** + * @hide + */ + public static final int ERROR_NAME_NOT_FOUND = 2; + + /** + * @hide + */ + @IntDef(prefix = {"ERROR_"}, value = { + ERROR_INVALID_DOMAIN_SET, + ERROR_NAME_NOT_FOUND, + }) + private @interface Error { + } + + private final Context mContext; + + private final IDomainVerificationManager mDomainVerificationManager; + + + /** + * System service to access the domain verification APIs. + * <p> + * Allows the approved domain verification agent on the device (the sole holder of {@link + * android.Manifest.permission#DOMAIN_VERIFICATION_AGENT}) to update the approval status of + * domains declared by applications in their AndroidManifest.xml, to allow them to open those + * links inside the app when selected by the user. This is done through querying {@link + * #getDomainVerificationInfo(String)} and calling {@link #setDomainVerificationStatus(UUID, + * Set, int)}. + * <p> + * Also allows the domain preference settings (holder of + * {@link android.Manifest.permission#UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) + * to update the preferences of the user, when they have chosen to explicitly allow an + * application to open links. This is done through querying + * {@link #getDomainVerificationUserState(String)} and calling + * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)} and + * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}. + * + * @hide + */ + public DomainVerificationManager(Context context, + IDomainVerificationManager domainVerificationManager) { + mContext = context; + mDomainVerificationManager = domainVerificationManager; + } + + /** * Used to iterate all {@link DomainVerificationInfo} values to do cleanup or retries. This is * usually a heavy workload and should be done infrequently. * * @return the current snapshot of package names with valid autoVerify URLs. + * @hide */ + @SystemApi @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) - List<String> getValidVerificationPackageNames(); + public List<String> queryValidVerificationPackageNames() { + try { + return mDomainVerificationManager.queryValidVerificationPackageNames(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } /** * Retrieves the domain verification state for a given package. @@ -183,61 +258,106 @@ public interface DomainVerificationManager { * @return the data for the package, or null if it does not declare any autoVerify domains * @throws NameNotFoundException If the package is unavailable. This is an unrecoverable error * and should not be re-tried except on a time scheduled basis. + * @hide */ + @SystemApi @Nullable @RequiresPermission(anyOf = { android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION }) - DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName) - throws NameNotFoundException; + public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName) + throws NameNotFoundException { + try { + return mDomainVerificationManager.getDomainVerificationInfo(packageName); + } catch (Exception e) { + Exception converted = rethrow(e, packageName); + if (converted instanceof NameNotFoundException) { + throw (NameNotFoundException) converted; + } else if (converted instanceof RuntimeException) { + throw (RuntimeException) converted; + } else { + throw new RuntimeException(converted); + } + } + } /** - * Change the verification status of the {@param domains} of the package associated with - * {@param domainSetId}. + * Change the verification status of the {@param domains} of the package associated with {@param + * domainSetId}. * * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}. * @param domains List of host names to change the state of. * @param state See {@link DomainVerificationInfo#getHostToStateMap()}. * @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are * invalid. This usually means the work being processed by the - * verification agent is outdated and a new request should - * be scheduled, if one has not already been done as part of - * the {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} - * broadcast. + * verification agent is outdated and a new request should be + * scheduled, if one has not already been done as part of the + * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast. * @throws NameNotFoundException If the ID is known to be good, but the package is - * unavailable. This may be because the package is - * installed on a volume that is no longer mounted. This - * error is unrecoverable until the package is available - * again, and should not be re-tried except on a time - * scheduled basis. + * unavailable. This may be because the package is installed on + * a volume that is no longer mounted. This error is + * unrecoverable until the package is available again, and + * should not be re-tried except on a time scheduled basis. + * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) - void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains, - @DomainVerificationState.State int state) throws NameNotFoundException; + public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains, + @DomainVerificationState.State int state) throws NameNotFoundException { + try { + mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(), + new DomainSet(domains), state); + } catch (Exception e) { + Exception converted = rethrow(e, domainSetId); + if (converted instanceof NameNotFoundException) { + throw (NameNotFoundException) converted; + } else if (converted instanceof RuntimeException) { + throw (RuntimeException) converted; + } else { + throw new RuntimeException(converted); + } + } + } /** - * TODO(b/178525735): This documentation is incorrect in the context of UX changes. - * Change whether the given {@param packageName} is allowed to automatically open verified - * HTTP/HTTPS domains. The final state is determined along with the verification status for the - * specific domain being opened and other system state. An app with this enabled is not - * guaranteed to be the sole link handler for its domains. + * Change whether the given packageName is allowed to handle BROWSABLE and DEFAULT category web + * (HTTP/HTTPS) {@link Intent} Activity open requests. The final state is determined along with + * the verification status for the specific domain being opened and other system state. An app + * with this enabled is not guaranteed to be the sole link handler for its domains. + * <p> + * By default, all apps are allowed to open links. Users must disable them explicitly. * - * By default, all apps are allowed to open verified links. Users must disable them explicitly. + * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) - void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, boolean allowed) - throws NameNotFoundException; + public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, + boolean allowed) throws NameNotFoundException { + try { + mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(packageName, + allowed, mContext.getUserId()); + } catch (Exception e) { + Exception converted = rethrow(e, packageName); + if (converted instanceof NameNotFoundException) { + throw (NameNotFoundException) converted; + } else if (converted instanceof RuntimeException) { + throw (RuntimeException) converted; + } else { + throw new RuntimeException(converted); + } + } + } /** * Update the recorded user selection for the given {@param domains} for the given {@param * domainSetId}. This state is recorded for the lifetime of a domain for a package on device, * and will never be reset by the system short of an app data clear. - * + * <p> * This state is stored per device user. If another user needs to be changed, the appropriate - * permissions must be acquired and - * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used. - * + * permissions must be acquired and {@link Context#createContextAsUser(UserHandle, int)} should + * be used. + * <p> * Enabling an unverified domain will allow an application to open it, but this can only occur * if no other app on the device is approved for a higher approval level. This can queried * using {@link #getOwnersForDomain(String)}. @@ -255,33 +375,55 @@ public interface DomainVerificationManager { * @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are * invalid. * @throws NameNotFoundException If the ID is known to be good, but the package is - * unavailable. This may be because the package is - * installed on a volume that is no longer mounted. This - * error is unrecoverable until the package is available - * again, and should not be re-tried except on a time - * scheduled basis. + * unavailable. This may be because the package is installed on + * a volume that is no longer mounted. This error is + * unrecoverable until the package is available again, and + * should not be re-tried except on a time scheduled basis. + * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) - void setDomainVerificationUserSelection(@NonNull UUID domainSetId, - @NonNull Set<String> domains, boolean enabled) throws NameNotFoundException; + public void setDomainVerificationUserSelection(@NonNull UUID domainSetId, + @NonNull Set<String> domains, boolean enabled) throws NameNotFoundException { + try { + mDomainVerificationManager.setDomainVerificationUserSelection(domainSetId.toString(), + new DomainSet(domains), enabled, mContext.getUserId()); + } catch (Exception e) { + Exception converted = rethrow(e, domainSetId); + if (converted instanceof NameNotFoundException) { + throw (NameNotFoundException) converted; + } else if (converted instanceof RuntimeException) { + throw (RuntimeException) converted; + } else { + throw new RuntimeException(converted); + } + } + } /** * Retrieve the user selection data for the given {@param packageName} and the current user. - * It is the responsibility of the caller to ensure that the - * {@link DomainVerificationUserSelection#getIdentifier()} matches any prior API calls. - * - * This state is stored per device user. If another user needs to be accessed, the appropriate - * permissions must be acquired and - * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used. * * @param packageName The app to query state for. - * @return the user selection verification data for the given package for the current user, - * or null if the package does not declare any HTTP/HTTPS domains. + * @return the user selection verification data for the given package for the current user, or + * null if the package does not declare any HTTP/HTTPS domains. */ @Nullable - @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) - DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String packageName) - throws NameNotFoundException; + public DomainVerificationUserState getDomainVerificationUserState( + @NonNull String packageName) throws NameNotFoundException { + try { + return mDomainVerificationManager.getDomainVerificationUserState(packageName, + mContext.getUserId()); + } catch (Exception e) { + Exception converted = rethrow(e, packageName); + if (converted instanceof NameNotFoundException) { + throw (NameNotFoundException) converted; + } else if (converted instanceof RuntimeException) { + throw (RuntimeException) converted; + } else { + throw new RuntimeException(converted); + } + } + } /** * For the given domain, return all apps which are approved to open it in a @@ -291,21 +433,65 @@ public interface DomainVerificationManager { * * By default the list will be returned ordered from lowest to highest * priority. + * + * @hide */ + @SystemApi @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) - List<DomainOwner> getOwnersForDomain(@NonNull String domain); + public List<DomainOwner> getOwnersForDomain(@NonNull String domain) { + try { + return mDomainVerificationManager.getOwnersForDomain(domain, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private Exception rethrow(Exception exception, @Nullable UUID domainSetId) { + return rethrow(exception, domainSetId, null); + } + + private Exception rethrow(Exception exception, @Nullable String packageName) { + return rethrow(exception, null, packageName); + } + + private Exception rethrow(Exception exception, @Nullable UUID domainSetId, + @Nullable String packageName) { + if (exception instanceof ServiceSpecificException) { + int packedErrorCode = ((ServiceSpecificException) exception).errorCode; + if (packageName == null) { + packageName = exception.getMessage(); + } + + @Error int managerErrorCode = packedErrorCode & 0xFFFF; + switch (managerErrorCode) { + case ERROR_INVALID_DOMAIN_SET: + int errorSpecificCode = packedErrorCode >> 16; + return new IllegalArgumentException(InvalidDomainSetException.buildMessage( + domainSetId, packageName, errorSpecificCode)); + case ERROR_NAME_NOT_FOUND: + return new NameNotFoundException(packageName); + default: + return exception; + } + } else if (exception instanceof RemoteException) { + return ((RemoteException) exception).rethrowFromSystemServer(); + } else { + return exception; + } + } /** * Thrown if a {@link DomainVerificationInfo#getIdentifier()}} or an associated set of domains * provided by the caller is no longer valid. This may be recoverable, and the caller should * re-query the package name associated with the ID using - * {@link #getDomainVerificationInfo(String)} in order to check. If that also fails, then the - * package is no longer known to the device and thus all pending work for it should be dropped. + * {@link #getDomainVerificationInfo(String)} + * in order to check. If that also fails, then the package is no longer known to the device and + * thus all pending work for it should be dropped. * * @hide */ - class InvalidDomainSetException extends IllegalArgumentException { + public static class InvalidDomainSetException extends IllegalArgumentException { public static final int REASON_ID_NULL = 1; public static final int REASON_ID_INVALID = 2; @@ -313,7 +499,9 @@ public interface DomainVerificationManager { public static final int REASON_UNKNOWN_DOMAIN = 4; public static final int REASON_UNABLE_TO_APPROVE = 5; - /** @hide */ + /** + * @hide + */ @IntDef({ REASON_ID_NULL, REASON_ID_INVALID, @@ -352,7 +540,9 @@ public interface DomainVerificationManager { @Nullable private final String mPackageName; - /** @hide */ + /** + * @hide + */ public InvalidDomainSetException(@Nullable UUID domainSetId, @Nullable String packageName, @Reason int reason) { super(buildMessage(domainSetId, packageName, reason)); diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java b/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java deleted file mode 100644 index 8b9865c2b436..000000000000 --- a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.content.pm.verify.domain; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.RemoteException; -import android.os.ServiceSpecificException; - -import java.util.List; -import java.util.Set; -import java.util.UUID; - -/** - * @hide - */ -@SuppressWarnings("RedundantThrows") -public class DomainVerificationManagerImpl implements DomainVerificationManager { - - public static final int ERROR_INVALID_DOMAIN_SET = 1; - public static final int ERROR_NAME_NOT_FOUND = 2; - - @IntDef(prefix = { "ERROR_" }, value = { - ERROR_INVALID_DOMAIN_SET, - ERROR_NAME_NOT_FOUND, - }) - private @interface Error { - } - - private final Context mContext; - - private final IDomainVerificationManager mDomainVerificationManager; - - public DomainVerificationManagerImpl(Context context, - IDomainVerificationManager domainVerificationManager) { - mContext = context; - mDomainVerificationManager = domainVerificationManager; - } - - @NonNull - @Override - public List<String> getValidVerificationPackageNames() { - try { - return mDomainVerificationManager.getValidVerificationPackageNames(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - @Nullable - @Override - public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName) - throws NameNotFoundException { - try { - return mDomainVerificationManager.getDomainVerificationInfo(packageName); - } catch (Exception e) { - Exception converted = rethrow(e, packageName); - if (converted instanceof NameNotFoundException) { - throw (NameNotFoundException) converted; - } else if (converted instanceof RuntimeException) { - throw (RuntimeException) converted; - } else { - throw new RuntimeException(converted); - } - } - } - - @Override - public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains, - int state) throws IllegalArgumentException, NameNotFoundException { - try { - mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(), - new DomainSet(domains), state); - } catch (Exception e) { - Exception converted = rethrow(e, domainSetId); - if (converted instanceof NameNotFoundException) { - throw (NameNotFoundException) converted; - } else if (converted instanceof RuntimeException) { - throw (RuntimeException) converted; - } else { - throw new RuntimeException(converted); - } - } - } - - @Override - public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, - boolean allowed) throws NameNotFoundException { - try { - mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(packageName, - allowed, mContext.getUserId()); - } catch (Exception e) { - Exception converted = rethrow(e, packageName); - if (converted instanceof NameNotFoundException) { - throw (NameNotFoundException) converted; - } else if (converted instanceof RuntimeException) { - throw (RuntimeException) converted; - } else { - throw new RuntimeException(converted); - } - } - } - - @Override - public void setDomainVerificationUserSelection(@NonNull UUID domainSetId, - @NonNull Set<String> domains, boolean enabled) - throws IllegalArgumentException, NameNotFoundException { - try { - mDomainVerificationManager.setDomainVerificationUserSelection(domainSetId.toString(), - new DomainSet(domains), enabled, mContext.getUserId()); - } catch (Exception e) { - Exception converted = rethrow(e, domainSetId); - if (converted instanceof NameNotFoundException) { - throw (NameNotFoundException) converted; - } else if (converted instanceof RuntimeException) { - throw (RuntimeException) converted; - } else { - throw new RuntimeException(converted); - } - } - } - - @Nullable - @Override - public DomainVerificationUserSelection getDomainVerificationUserSelection( - @NonNull String packageName) throws NameNotFoundException { - try { - return mDomainVerificationManager.getDomainVerificationUserSelection(packageName, - mContext.getUserId()); - } catch (Exception e) { - Exception converted = rethrow(e, packageName); - if (converted instanceof NameNotFoundException) { - throw (NameNotFoundException) converted; - } else if (converted instanceof RuntimeException) { - throw (RuntimeException) converted; - } else { - throw new RuntimeException(converted); - } - } - } - - @NonNull - @Override - public List<DomainOwner> getOwnersForDomain(@NonNull String domain) { - try { - return mDomainVerificationManager.getOwnersForDomain(domain, mContext.getUserId()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - private Exception rethrow(Exception exception, @Nullable UUID domainSetId) { - return rethrow(exception, domainSetId, null); - } - - private Exception rethrow(Exception exception, @Nullable String packageName) { - return rethrow(exception, null, packageName); - } - - private Exception rethrow(Exception exception, @Nullable UUID domainSetId, - @Nullable String packageName) { - if (exception instanceof ServiceSpecificException) { - int packedErrorCode = ((ServiceSpecificException) exception).errorCode; - if (packageName == null) { - packageName = exception.getMessage(); - } - - @Error int managerErrorCode = packedErrorCode & 0xFFFF; - switch (managerErrorCode) { - case ERROR_INVALID_DOMAIN_SET: - int errorSpecificCode = packedErrorCode >> 16; - return new IllegalArgumentException(InvalidDomainSetException.buildMessage( - domainSetId, packageName, errorSpecificCode)); - case ERROR_NAME_NOT_FOUND: - return new NameNotFoundException(packageName); - default: - return exception; - } - } else if (exception instanceof RemoteException) { - return ((RemoteException) exception).rethrowFromSystemServer(); - } else { - return exception; - } - } -} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.aidl index ddb5ef85382a..94690c1dae93 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl +++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.aidl @@ -16,4 +16,4 @@ package android.content.pm.verify.domain; -parcelable DomainVerificationUserSelection; +parcelable DomainVerificationUserState; diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java index d23f5f133841..1e60abb30011 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.Context; +import android.content.Intent; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; @@ -29,39 +30,36 @@ import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; import java.util.Map; -import java.util.Set; import java.util.UUID; /** * Contains the user selection state for a package. This means all web HTTP(S) domains declared by a * package in its manifest, whether or not they were marked for auto verification. * <p> - * By default, all apps are allowed to automatically open links with domains that they've - * successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}. The user - * can decide to disable this, disallowing the application from opening all links. Note that the - * toggle affects <b>all</b> links and is not based on the verification state of the domains. + * Applications should use {@link #getHostToStateMap()} if necessary to + * check if/how they are verified for a domain, which is required starting from platform + * {@link android.os.Build.VERSION_CODES#S} in order to open {@link Intent}s which declare + * {@link Intent#CATEGORY_BROWSABLE} or no category and also match against + * {@link Intent#CATEGORY_DEFAULT} {@link android.content.IntentFilter}s, either through an + * explicit declaration of {@link Intent#CATEGORY_DEFAULT} or through the use of + * {@link android.content.pm.PackageManager#MATCH_DEFAULT_ONLY}, which is usually added for the + * caller when using {@link Context#startActivity(Intent)} and similar. + * <p> + * By default, all apps are allowed to automatically open links for the above case for domains that + * they've successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}. + * The user can decide to disable this, disallowing the application from opening all links. Note + * that the toggle affects <b>all</b> links and is not based on the verification state of the + * domains. * <p> * Assuming the toggle is enabled, the user can also select additional unverified domains to grant * to the application to open, which is reflected in {@link #getHostToStateMap()}. But only a single * application can be approved for a domain unless the applications are both approved. If another * application is approved, the user will not be allowed to enable the domain. - * <p> - * These values can be changed through the - * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String, - * boolean)} and {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, - * boolean)} APIs. - * <p> - * Note that because state is per user, if a different user needs to be changed, one will need to - * use {@link Context#createContextAsUser(UserHandle, int)} and hold the {@link - * android.Manifest.permission#INTERACT_ACROSS_USERS} permission. - * - * @hide */ -@SystemApi @SuppressWarnings("DefaultAnnotationParam") @DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true, genEqualsHashCode = true, genHiddenConstDefs = true) -public final class DomainVerificationUserSelection implements Parcelable { +public final class DomainVerificationUserState implements Parcelable { /** * The domain is unverified and unselected, and the application is unable to open web links @@ -70,9 +68,8 @@ public final class DomainVerificationUserSelection implements Parcelable { public static final int DOMAIN_STATE_NONE = 0; /** - * The domain has been selected through the - * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)} - * API, under the assumption it has not been reset by the system. + * The domain has been selected by the user. This may be reset to {@link #DOMAIN_STATE_NONE} if + * another application is selected or verified for the same domain. */ public static final int DOMAIN_STATE_SELECTED = 1; @@ -119,7 +116,16 @@ public final class DomainVerificationUserSelection implements Parcelable { @NonNull private Map<String, Integer> unparcelHostToStateMap(Parcel in) { return DomainVerificationUtils.readHostMap(in, new ArrayMap<>(), - DomainVerificationUserSelection.class.getClassLoader()); + DomainVerificationUserState.class.getClassLoader()); + } + + /** + * @see DomainVerificationInfo#getIdentifier + * @hide + */ + @SystemApi + public @NonNull UUID getIdentifier() { + return mIdentifier; } @@ -130,7 +136,7 @@ public final class DomainVerificationUserSelection implements Parcelable { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -162,7 +168,7 @@ public final class DomainVerificationUserSelection implements Parcelable { } /** - * Creates a new DomainVerificationUserSelection. + * Creates a new DomainVerificationUserState. * * @param packageName * The package name that this data corresponds to. @@ -175,7 +181,7 @@ public final class DomainVerificationUserSelection implements Parcelable { * @hide */ @DataClass.Generated.Member - public DomainVerificationUserSelection( + public DomainVerificationUserState( @NonNull UUID identifier, @NonNull String packageName, @NonNull UserHandle user, @@ -201,14 +207,6 @@ public final class DomainVerificationUserSelection implements Parcelable { } /** - * @see DomainVerificationInfo#getIdentifier - */ - @DataClass.Generated.Member - public @NonNull UUID getIdentifier() { - return mIdentifier; - } - - /** * The package name that this data corresponds to. */ @DataClass.Generated.Member @@ -246,7 +244,7 @@ public final class DomainVerificationUserSelection implements Parcelable { // You can override field toString logic by defining methods like: // String fieldNameToString() { ... } - return "DomainVerificationUserSelection { " + + return "DomainVerificationUserState { " + "identifier = " + mIdentifier + ", " + "packageName = " + mPackageName + ", " + "user = " + mUser + ", " + @@ -259,13 +257,13 @@ public final class DomainVerificationUserSelection implements Parcelable { @DataClass.Generated.Member public boolean equals(@Nullable Object o) { // You can override field equality logic by defining either of the methods like: - // boolean fieldNameEquals(DomainVerificationUserSelection other) { ... } + // boolean fieldNameEquals(DomainVerificationUserState other) { ... } // boolean fieldNameEquals(FieldType otherValue) { ... } if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @SuppressWarnings("unchecked") - DomainVerificationUserSelection that = (DomainVerificationUserSelection) o; + DomainVerificationUserState that = (DomainVerificationUserState) o; //noinspection PointlessBooleanExpression return true && java.util.Objects.equals(mIdentifier, that.mIdentifier) @@ -323,7 +321,7 @@ public final class DomainVerificationUserSelection implements Parcelable { /** @hide */ @SuppressWarnings({"unchecked", "RedundantCast"}) @DataClass.Generated.Member - /* package-private */ DomainVerificationUserSelection(@NonNull Parcel in) { + /* package-private */ DomainVerificationUserState(@NonNull Parcel in) { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } @@ -354,24 +352,24 @@ public final class DomainVerificationUserSelection implements Parcelable { } @DataClass.Generated.Member - public static final @NonNull Parcelable.Creator<DomainVerificationUserSelection> CREATOR - = new Parcelable.Creator<DomainVerificationUserSelection>() { + public static final @NonNull Parcelable.Creator<DomainVerificationUserState> CREATOR + = new Parcelable.Creator<DomainVerificationUserState>() { @Override - public DomainVerificationUserSelection[] newArray(int size) { - return new DomainVerificationUserSelection[size]; + public DomainVerificationUserState[] newArray(int size) { + return new DomainVerificationUserState[size]; } @Override - public DomainVerificationUserSelection createFromParcel(@NonNull Parcel in) { - return new DomainVerificationUserSelection(in); + public DomainVerificationUserState createFromParcel(@NonNull Parcel in) { + return new DomainVerificationUserState(in); } }; @DataClass.Generated( - time = 1613683603297L, + time = 1614721840152L, codegenVersion = "1.0.22", - sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java", - inputSignatures = "public static final int DOMAIN_STATE_NONE\npublic static final int DOMAIN_STATE_SELECTED\npublic static final int DOMAIN_STATE_VERIFIED\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationUserSelection extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)") + sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java", + inputSignatures = "public static final int DOMAIN_STATE_NONE\npublic static final int DOMAIN_STATE_SELECTED\npublic static final int DOMAIN_STATE_VERIFIED\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\npublic @android.annotation.SystemApi @android.annotation.NonNull java.util.UUID getIdentifier()\nclass DomainVerificationUserState extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl index 701af320fb01..332b92544581 100644 --- a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl +++ b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl @@ -19,7 +19,7 @@ package android.content.pm.verify.domain; import android.content.pm.verify.domain.DomainOwner; import android.content.pm.verify.domain.DomainSet; import android.content.pm.verify.domain.DomainVerificationInfo; -import android.content.pm.verify.domain.DomainVerificationUserSelection; +import android.content.pm.verify.domain.DomainVerificationUserState; import java.util.List; /** @@ -28,13 +28,13 @@ import java.util.List; */ interface IDomainVerificationManager { - List<String> getValidVerificationPackageNames(); + List<String> queryValidVerificationPackageNames(); @nullable DomainVerificationInfo getDomainVerificationInfo(String packageName); @nullable - DomainVerificationUserSelection getDomainVerificationUserSelection(String packageName, + DomainVerificationUserState getDomainVerificationUserState(String packageName, int userId); @nullable diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java index fd98d37bb7f4..31d1b69182f1 100644 --- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java +++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java @@ -62,10 +62,13 @@ public interface BiometricAuthenticator { * @hide */ int TYPE_FACE = 1 << 3; - @IntDef({TYPE_NONE, + + @IntDef(flag = true, value = { + TYPE_NONE, TYPE_CREDENTIAL, TYPE_FINGERPRINT, - TYPE_IRIS}) + TYPE_IRIS + }) @Retention(RetentionPolicy.SOURCE) @interface Modality {} diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 5b28e0035b09..1fdce5e773b1 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -23,6 +23,7 @@ import static android.Manifest.permission.WRITE_DEVICE_CONFIG; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -193,15 +194,15 @@ public class BiometricManager { int DEVICE_CREDENTIAL = 1 << 15; } - private final Context mContext; - private final IAuthService mService; + @NonNull private final Context mContext; + @NonNull private final IAuthService mService; /** * @hide * @param context * @param service */ - public BiometricManager(Context context, IAuthService service) { + public BiometricManager(@NonNull Context context, @NonNull IAuthService service) { mContext = context; mService = service; } @@ -274,7 +275,8 @@ public class BiometricManager { */ @Deprecated @RequiresPermission(USE_BIOMETRIC) - public @BiometricError int canAuthenticate() { + @BiometricError + public int canAuthenticate() { return canAuthenticate(Authenticators.BIOMETRIC_WEAK); } @@ -304,7 +306,8 @@ public class BiometricManager { * authenticators can currently be used (enrolled and available). */ @RequiresPermission(USE_BIOMETRIC) - public @BiometricError int canAuthenticate(@Authenticators.Types int authenticators) { + @BiometricError + public int canAuthenticate(@Authenticators.Types int authenticators) { return canAuthenticate(mContext.getUserId(), authenticators); } @@ -312,8 +315,10 @@ public class BiometricManager { * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) - public @BiometricError int canAuthenticate(int userId, - @Authenticators.Types int authenticators) { + @BiometricError + public int canAuthenticate( + int userId, @Authenticators.Types int authenticators) { + if (mService != null) { try { final String opPackageName = mContext.getOpPackageName(); @@ -322,7 +327,7 @@ public class BiometricManager { throw e.rethrowFromSystemServer(); } } else { - Slog.w(TAG, "hasEnrolledBiometrics(): Service not connected"); + Slog.w(TAG, "canAuthenticate(): Service not connected"); return BIOMETRIC_ERROR_HW_UNAVAILABLE; } } @@ -404,5 +409,115 @@ public class BiometricManager { } } + /** + * Provides a localized string that may be used as the label for a button that invokes + * {@link BiometricPrompt}. + * + * <p>When possible, this method should use the given authenticator requirements to more + * precisely specify the authentication type that will be used. For example, if + * <strong>Class 3</strong> biometric authentication is requested on a device with a + * <strong>Class 3</strong> fingerprint sensor and a <strong>Class 2</strong> face sensor, the + * returned string should indicate that fingerprint authentication will be used. + * + * <p>This method should also try to specify which authentication method(s) will be used in + * practice when multiple authenticators meet the given requirements. For example, if biometric + * authentication is requested on a device with both face and fingerprint sensors but the user + * has selected face as their preferred method, the returned string should indicate that face + * authentication will be used. + * + * @param authenticators A bit field representing the types of {@link Authenticators} that may + * be used for authentication. + * @return The label for a button that invokes {@link BiometricPrompt} for authentication. + */ + @RequiresPermission(USE_BIOMETRIC) + @Nullable + public CharSequence getButtonLabel(@Authenticators.Types int authenticators) { + if (mService != null) { + final int userId = mContext.getUserId(); + final String opPackageName = mContext.getOpPackageName(); + try { + return mService.getButtonLabel(userId, opPackageName, authenticators); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "getButtonLabel(): Service not connected"); + return null; + } + } + + /** + * Provides a localized string that may be shown while the user is authenticating with + * {@link BiometricPrompt}. + * + * <p>When possible, this method should use the given authenticator requirements to more + * precisely specify the authentication type that will be used. For example, if + * <strong>Class 3</strong> biometric authentication is requested on a device with a + * <strong>Class 3</strong> fingerprint sensor and a <strong>Class 2</strong> face sensor, the + * returned string should indicate that fingerprint authentication will be used. + * + * <p>This method should also try to specify which authentication method(s) will be used in + * practice when multiple authenticators meet the given requirements. For example, if biometric + * authentication is requested on a device with both face and fingerprint sensors but the user + * has selected face as their preferred method, the returned string should indicate that face + * authentication will be used. + * + * @param authenticators A bit field representing the types of {@link Authenticators} that may + * be used for authentication. + * @return The label for a button that invokes {@link BiometricPrompt} for authentication. + */ + @RequiresPermission(USE_BIOMETRIC) + @Nullable + public CharSequence getPromptMessage(@Authenticators.Types int authenticators) { + if (mService != null) { + final int userId = mContext.getUserId(); + final String opPackageName = mContext.getOpPackageName(); + try { + return mService.getPromptMessage(userId, opPackageName, authenticators); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "getPromptMessage(): Service not connected"); + return null; + } + } + + /** + * Provides a localized string that may be shown as the title for an app setting that enables + * biometric authentication. + * + * <p>When possible, this method should use the given authenticator requirements to more + * precisely specify the authentication type that will be used. For example, if + * <strong>Class 3</strong> biometric authentication is requested on a device with a + * <strong>Class 3</strong> fingerprint sensor and a <strong>Class 2</strong> face sensor, the + * returned string should indicate that fingerprint authentication will be used. + * + * <p>This method should <em>not</em> try to specify which authentication method(s) will be used + * in practice when multiple authenticators meet the given requirements. For example, if + * biometric authentication is requested on a device with both face and fingerprint sensors, the + * returned string should indicate that either face or fingerprint authentication may be used, + * regardless of whether the user has enrolled or selected either as their preferred method. + * + * @param authenticators A bit field representing the types of {@link Authenticators} that may + * be used for authentication. + * @return The label for a button that invokes {@link BiometricPrompt} for authentication. + */ + @RequiresPermission(USE_BIOMETRIC) + @Nullable + public CharSequence getSettingName(@Authenticators.Types int authenticators) { + if (mService != null) { + final int userId = mContext.getUserId(); + final String opPackageName = mContext.getOpPackageName(); + try { + return mService.getSettingName(userId, opPackageName, authenticators); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "getSettingName(): Service not connected"); + return null; + } + } } diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl index d8c9dbc849a9..1472bb940be5 100644 --- a/core/java/android/hardware/biometrics/IAuthService.aidl +++ b/core/java/android/hardware/biometrics/IAuthService.aidl @@ -68,4 +68,16 @@ interface IAuthService { // the requirements for integrating with Keystore. The AuthenticatorID are known in Keystore // land as SIDs, and are used during key generation. long[] getAuthenticatorIds(); + + // Provides a localized string that may be used as the label for a button that invokes + // BiometricPrompt. + CharSequence getButtonLabel(int userId, String opPackageName, int authenticators); + + // Provides a localized string that may be shown while the user is authenticating with + // BiometricPrompt. + CharSequence getPromptMessage(int userId, String opPackageName, int authenticators); + + // Provides a localized string that may be shown as the title for an app setting that enables + // biometric authentication. + CharSequence getSettingName(int userId, String opPackageName, int authenticators); } diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index 24331863a05f..6d8bf0fb5543 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -75,4 +75,10 @@ interface IBiometricService { long[] getAuthenticatorIds(int callingUserId); int getCurrentStrength(int sensorId); + + // Returns a bit field of the modality (or modalities) that are will be used for authentication. + int getCurrentModality(String opPackageName, int userId, int callingUserId, int authenticators); + + // Returns a bit field of the authentication modalities that are supported by this device. + int getSupportedModalities(int authenticators); } diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java index 06b5b6745bd1..a5c9a7fafbd8 100644 --- a/core/java/android/hardware/soundtrigger/ConversionUtil.java +++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java @@ -34,10 +34,7 @@ import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; import android.media.soundtrigger_middleware.SoundTriggerModuleProperties; import android.os.ParcelFileDescriptor; import android.os.SharedMemory; -import android.system.ErrnoException; -import java.io.FileDescriptor; -import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.UUID; @@ -111,13 +108,9 @@ class ConversionUtil { aidlModel.type = apiModel.getType(); aidlModel.uuid = api2aidlUuid(apiModel.getUuid()); aidlModel.vendorUuid = api2aidlUuid(apiModel.getVendorUuid()); - try { - aidlModel.data = ParcelFileDescriptor.dup( - byteArrayToSharedMemory(apiModel.getData(), "SoundTrigger SoundModel")); - } catch (IOException e) { - throw new RuntimeException(e); - } - aidlModel.dataSize = apiModel.getData().length; + byte[] data = apiModel.getData(); + aidlModel.data = byteArrayToSharedMemory(data, "SoundTrigger SoundModel"); + aidlModel.dataSize = data.length; return aidlModel; } @@ -379,7 +372,7 @@ class ConversionUtil { return result; } - private static @Nullable FileDescriptor byteArrayToSharedMemory(byte[] data, String name) { + private static @Nullable ParcelFileDescriptor byteArrayToSharedMemory(byte[] data, String name) { if (data.length == 0) { return null; } @@ -389,8 +382,10 @@ class ConversionUtil { ByteBuffer buffer = shmem.mapReadWrite(); buffer.put(data); shmem.unmap(buffer); - return shmem.getFileDescriptor(); - } catch (ErrnoException e) { + ParcelFileDescriptor fd = shmem.getFdDup(); + shmem.close(); + return fd; + } catch (Exception e) { throw new RuntimeException(e); } } diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl index 0baf11e850c7..dc3b88a7c3be 100644 --- a/core/java/android/net/INetworkStatsService.aidl +++ b/core/java/android/net/INetworkStatsService.aidl @@ -19,7 +19,7 @@ package android.net; import android.net.DataUsageRequest; import android.net.INetworkStatsSession; import android.net.Network; -import android.net.NetworkState; +import android.net.NetworkStateSnapshot; import android.net.NetworkStats; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; @@ -68,7 +68,7 @@ interface INetworkStatsService { /** Force update of ifaces. */ void forceUpdateIfaces( in Network[] defaultNetworks, - in NetworkState[] networkStates, + in NetworkStateSnapshot[] snapshots, in String activeIface, in UnderlyingNetworkInfo[] underlyingNetworkInfos); /** Force update of statistics. */ diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java index 183f500572bd..cc1312bac180 100644 --- a/core/java/android/net/Ikev2VpnProfile.java +++ b/core/java/android/net/Ikev2VpnProfile.java @@ -24,10 +24,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.content.pm.PackageManager; -import android.os.Process; import android.security.Credentials; -import android.security.KeyStore; -import android.security.keystore.AndroidKeyStoreProvider; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.net.VpnProfile; @@ -35,7 +32,9 @@ import com.android.internal.net.VpnProfile; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; +import java.security.Key; import java.security.KeyFactory; +import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.CertificateEncodingException; @@ -66,6 +65,7 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { /** Prefix for when a Private Key is stored directly in the profile @hide */ public static final String PREFIX_INLINE = "INLINE:"; + private static final String ANDROID_KEYSTORE_PROVIDER = "AndroidKeyStore"; private static final String MISSING_PARAM_MSG_TMPL = "Required parameter was not provided: %s"; private static final String EMPTY_CERT = ""; @@ -430,32 +430,31 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { return profile; } - /** - * Constructs a Ikev2VpnProfile from an internal-use VpnProfile instance. - * - * <p>Redundant authentication information (not related to profile type) will be discarded. - * - * @hide - */ - @NonNull - public static Ikev2VpnProfile fromVpnProfile(@NonNull VpnProfile profile) - throws IOException, GeneralSecurityException { - return fromVpnProfile(profile, null); + private static PrivateKey getPrivateKeyFromAndroidKeystore(String alias) { + try { + final KeyStore keystore = KeyStore.getInstance(ANDROID_KEYSTORE_PROVIDER); + keystore.load(null); + final Key key = keystore.getKey(alias, null); + if (!(key instanceof PrivateKey)) { + throw new IllegalStateException( + "Unexpected key type returned from android keystore."); + } + return (PrivateKey) key; + } catch (Exception e) { + throw new IllegalStateException("Failed to load key from android keystore.", e); + } } /** * Builds the Ikev2VpnProfile from the given profile. * * @param profile the source VpnProfile to build from - * @param keyStore the Android Keystore instance to use to retrieve the private key, or null if - * the private key is PEM-encoded into the profile. * @return The IKEv2/IPsec VPN profile * @hide */ @NonNull - public static Ikev2VpnProfile fromVpnProfile( - @NonNull VpnProfile profile, @Nullable KeyStore keyStore) - throws IOException, GeneralSecurityException { + public static Ikev2VpnProfile fromVpnProfile(@NonNull VpnProfile profile) + throws GeneralSecurityException { final Builder builder = new Builder(profile.server, profile.ipsecIdentifier); builder.setProxy(profile.proxy); builder.setAllowedAlgorithms(profile.getAllowedAlgorithms()); @@ -479,12 +478,9 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { case TYPE_IKEV2_IPSEC_RSA: final PrivateKey key; if (profile.ipsecSecret.startsWith(PREFIX_KEYSTORE_ALIAS)) { - Objects.requireNonNull(keyStore, "Missing Keystore for aliased PrivateKey"); - final String alias = profile.ipsecSecret.substring(PREFIX_KEYSTORE_ALIAS.length()); - key = AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore( - keyStore, alias, Process.myUid()); + key = getPrivateKeyFromAndroidKeystore(alias); } else if (profile.ipsecSecret.startsWith(PREFIX_INLINE)) { key = getPrivateKey(profile.ipsecSecret.substring(PREFIX_INLINE.length())); } else { diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java index e89451e4f4ef..268002f1dd52 100644 --- a/core/java/android/net/IpSecAlgorithm.java +++ b/core/java/android/net/IpSecAlgorithm.java @@ -146,6 +146,25 @@ public final class IpSecAlgorithm implements Parcelable { public static final String AUTH_AES_XCBC = "xcbc(aes)"; /** + * AES-CMAC Authentication/Integrity Algorithm. + * + * <p>Keys for this algorithm must be 128 bits in length. + * + * <p>The only valid truncation length is 96 bits. + * + * <p>This algorithm may be available on the device. Caller MUST check if it is supported before + * using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is + * included in the returned algorithm set. The returned algorithm set will not change unless the + * device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is + * requested on an unsupported device. + * + * <p>@see {@link #getSupportedAlgorithms()} + */ + // This algorithm may be available on devices released before Android 12, and is guaranteed + // to be available on devices first shipped with Android 12 or later. + public static final String AUTH_AES_CMAC = "cmac(aes)"; + + /** * AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm. * * <p>Valid lengths for keying material are {160, 224, 288}. @@ -191,6 +210,7 @@ public final class IpSecAlgorithm implements Parcelable { AUTH_HMAC_SHA384, AUTH_HMAC_SHA512, AUTH_AES_XCBC, + AUTH_AES_CMAC, AUTH_CRYPT_AES_GCM, AUTH_CRYPT_CHACHA20_POLY1305 }) @@ -212,10 +232,10 @@ public final class IpSecAlgorithm implements Parcelable { ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA512, SDK_VERSION_ZERO); ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_AES_GCM, SDK_VERSION_ZERO); - // STOPSHIP: b/170424293 Use Build.VERSION_CODES.S when it is defined - ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CTR, Build.VERSION_CODES.R + 1); - ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_XCBC, Build.VERSION_CODES.R + 1); - ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_CHACHA20_POLY1305, Build.VERSION_CODES.R + 1); + ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CTR, Build.VERSION_CODES.S); + ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_XCBC, Build.VERSION_CODES.S); + ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_CMAC, Build.VERSION_CODES.S); + ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_CHACHA20_POLY1305, Build.VERSION_CODES.S); } private static final Set<String> ENABLED_ALGOS = @@ -383,6 +403,10 @@ public final class IpSecAlgorithm implements Parcelable { isValidLen = keyLen == 128; isValidTruncLen = truncLen == 96; break; + case AUTH_AES_CMAC: + isValidLen = keyLen == 128; + isValidTruncLen = truncLen == 96; + break; case AUTH_CRYPT_AES_GCM: // The keying material for GCM is a key plus a 32-bit salt isValidLen = keyLen == 128 + 32 || keyLen == 192 + 32 || keyLen == 256 + 32; @@ -416,6 +440,7 @@ public final class IpSecAlgorithm implements Parcelable { case AUTH_HMAC_SHA384: case AUTH_HMAC_SHA512: case AUTH_AES_XCBC: + case AUTH_AES_CMAC: return true; default: return false; diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java index 303a40755d4e..a5ece7b713c7 100644 --- a/core/java/android/net/NetworkIdentity.java +++ b/core/java/android/net/NetworkIdentity.java @@ -18,7 +18,6 @@ package android.net; import static android.net.ConnectivityManager.TYPE_WIFI; -import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.net.wifi.WifiInfo; @@ -180,29 +179,42 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { } /** - * Build a {@link NetworkIdentity} from the given {@link NetworkState} and {@code subType}, - * assuming that any mobile networks are using the current IMSI. The subType if applicable, - * should be set as one of the TelephonyManager.NETWORK_TYPE_* constants, or - * {@link android.telephony.TelephonyManager#NETWORK_TYPE_UNKNOWN} if not. + * Build a {@link NetworkIdentity} from the given {@link NetworkState} and + * {@code subType}, assuming that any mobile networks are using the current IMSI. + * The subType if applicable, should be set as one of the TelephonyManager.NETWORK_TYPE_* + * constants, or {@link android.telephony.TelephonyManager#NETWORK_TYPE_UNKNOWN} if not. */ - public static NetworkIdentity buildNetworkIdentity(Context context, NetworkState state, - boolean defaultNetwork, @NetworkType int subType) { - final int legacyType = state.legacyNetworkType; + // TODO: Delete this function after NetworkPolicyManagerService finishes the migration. + public static NetworkIdentity buildNetworkIdentity(Context context, + NetworkState state, boolean defaultNetwork, @NetworkType int subType) { + final NetworkStateSnapshot snapshot = new NetworkStateSnapshot(state.network, + state.networkCapabilities, state.linkProperties, state.subscriberId, + state.legacyNetworkType); + return buildNetworkIdentity(context, snapshot, defaultNetwork, subType); + } - String subscriberId = null; + /** + * Build a {@link NetworkIdentity} from the given {@link NetworkStateSnapshot} and + * {@code subType}, assuming that any mobile networks are using the current IMSI. + * The subType if applicable, should be set as one of the TelephonyManager.NETWORK_TYPE_* + * constants, or {@link android.telephony.TelephonyManager#NETWORK_TYPE_UNKNOWN} if not. + */ + public static NetworkIdentity buildNetworkIdentity(Context context, + NetworkStateSnapshot snapshot, boolean defaultNetwork, @NetworkType int subType) { + final int legacyType = snapshot.legacyType; + + final String subscriberId = snapshot.subscriberId; String networkId = null; - boolean roaming = !state.networkCapabilities.hasCapability( + boolean roaming = !snapshot.networkCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); - boolean metered = !state.networkCapabilities.hasCapability( + boolean metered = !snapshot.networkCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_NOT_METERED); - subscriberId = state.subscriberId; - - final int oemManaged = getOemBitfield(state.networkCapabilities); + final int oemManaged = getOemBitfield(snapshot.networkCapabilities); if (legacyType == TYPE_WIFI) { - if (state.networkCapabilities.getSsid() != null) { - networkId = state.networkCapabilities.getSsid(); + if (snapshot.networkCapabilities.getSsid() != null) { + networkId = snapshot.networkCapabilities.getSsid(); if (networkId == null) { // TODO: Figure out if this code path never runs. If so, remove them. final WifiManager wifi = (WifiManager) context.getSystemService( diff --git a/core/java/android/net/NetworkStateSnapshot.java b/core/java/android/net/NetworkStateSnapshot.java index 881b373fa241..b3d8d4e614da 100644 --- a/core/java/android/net/NetworkStateSnapshot.java +++ b/core/java/android/net/NetworkStateSnapshot.java @@ -16,8 +16,11 @@ package android.net; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -28,31 +31,49 @@ import java.util.Objects; * * @hide */ +@SystemApi(client = MODULE_LIBRARIES) public final class NetworkStateSnapshot implements Parcelable { + /** The network associated with this snapshot. */ @NonNull - public final LinkProperties linkProperties; + public final Network network; + + /** The {@link NetworkCapabilities} of the network associated with this snapshot. */ @NonNull public final NetworkCapabilities networkCapabilities; + + /** The {@link LinkProperties} of the network associated with this snapshot. */ @NonNull - public final Network network; + public final LinkProperties linkProperties; + + /** + * The Subscriber Id of the network associated with this snapshot. See + * {@link android.telephony.TelephonyManager#getSubscriberId()}. + */ @Nullable public final String subscriberId; + + /** + * The legacy type of the network associated with this snapshot. See + * {@code ConnectivityManager#TYPE_*}. + */ public final int legacyType; - public NetworkStateSnapshot(@NonNull LinkProperties linkProperties, - @NonNull NetworkCapabilities networkCapabilities, @NonNull Network network, + public NetworkStateSnapshot(@NonNull Network network, + @NonNull NetworkCapabilities networkCapabilities, + @NonNull LinkProperties linkProperties, @Nullable String subscriberId, int legacyType) { - this.linkProperties = Objects.requireNonNull(linkProperties); - this.networkCapabilities = Objects.requireNonNull(networkCapabilities); this.network = Objects.requireNonNull(network); + this.networkCapabilities = Objects.requireNonNull(networkCapabilities); + this.linkProperties = Objects.requireNonNull(linkProperties); this.subscriberId = subscriberId; this.legacyType = legacyType; } + /** @hide */ public NetworkStateSnapshot(@NonNull Parcel in) { - linkProperties = in.readParcelable(null); - networkCapabilities = in.readParcelable(null); network = in.readParcelable(null); + networkCapabilities = in.readParcelable(null); + linkProperties = in.readParcelable(null); subscriberId = in.readString(); legacyType = in.readInt(); } @@ -64,9 +85,9 @@ public final class NetworkStateSnapshot implements Parcelable { @Override public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeParcelable(linkProperties, flags); - out.writeParcelable(networkCapabilities, flags); out.writeParcelable(network, flags); + out.writeParcelable(networkCapabilities, flags); + out.writeParcelable(linkProperties, flags); out.writeString(subscriberId); out.writeInt(legacyType); } @@ -93,14 +114,14 @@ public final class NetworkStateSnapshot implements Parcelable { if (!(o instanceof NetworkStateSnapshot)) return false; NetworkStateSnapshot that = (NetworkStateSnapshot) o; return legacyType == that.legacyType - && Objects.equals(linkProperties, that.linkProperties) - && Objects.equals(networkCapabilities, that.networkCapabilities) && Objects.equals(network, that.network) + && Objects.equals(networkCapabilities, that.networkCapabilities) + && Objects.equals(linkProperties, that.linkProperties) && Objects.equals(subscriberId, that.subscriberId); } @Override public int hashCode() { - return Objects.hash(linkProperties, networkCapabilities, network, subscriberId, legacyType); + return Objects.hash(network, networkCapabilities, linkProperties, subscriberId, legacyType); } } diff --git a/core/java/android/net/OemNetworkPreferences.java b/core/java/android/net/OemNetworkPreferences.java index b4034556f66e..48bd29769f83 100644 --- a/core/java/android/net/OemNetworkPreferences.java +++ b/core/java/android/net/OemNetworkPreferences.java @@ -29,7 +29,15 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; -/** @hide */ +/** + * Network preferences to set the default active network on a per-application basis as per a given + * {@link OemNetworkPreference}. An example of this would be to set an application's network + * preference to {@link #OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK} which would have the default + * network for that application set to an unmetered network first if available and if not, it then + * set that application's default network to an OEM managed network if available. + * + * @hide + */ @SystemApi public final class OemNetworkPreferences implements Parcelable { /** @@ -64,6 +72,10 @@ public final class OemNetworkPreferences implements Parcelable { @NonNull private final Bundle mNetworkMappings; + /** + * Return the currently built application package name to {@link OemNetworkPreference} mappings. + * @return the current network preferences map. + */ @NonNull public Map<String, Integer> getNetworkPreferences() { return convertToUnmodifiableMap(mNetworkMappings); @@ -105,6 +117,11 @@ public final class OemNetworkPreferences implements Parcelable { mNetworkMappings = new Bundle(); } + /** + * Constructor to populate the builder's values with an already built + * {@link OemNetworkPreferences}. + * @param preferences the {@link OemNetworkPreferences} to populate with. + */ public Builder(@NonNull final OemNetworkPreferences preferences) { Objects.requireNonNull(preferences); mNetworkMappings = (Bundle) preferences.mNetworkMappings.clone(); diff --git a/packages/Connectivity/framework/src/android/net/PacProxySelector.java b/core/java/android/net/PacProxySelector.java index 326943a27d4e..326943a27d4e 100644 --- a/packages/Connectivity/framework/src/android/net/PacProxySelector.java +++ b/core/java/android/net/PacProxySelector.java diff --git a/packages/Connectivity/framework/src/android/net/Proxy.java b/core/java/android/net/Proxy.java index 77c8a4f4579b..77c8a4f4579b 100644 --- a/packages/Connectivity/framework/src/android/net/Proxy.java +++ b/core/java/android/net/Proxy.java diff --git a/core/java/android/net/vcn/IVcnStatusCallback.aidl b/core/java/android/net/vcn/IVcnStatusCallback.aidl index d91cef592d10..236ae8bb11b2 100644 --- a/core/java/android/net/vcn/IVcnStatusCallback.aidl +++ b/core/java/android/net/vcn/IVcnStatusCallback.aidl @@ -18,7 +18,6 @@ package android.net.vcn; /** @hide */ oneway interface IVcnStatusCallback { - void onEnteredSafeMode(); void onVcnStatusChanged(int statusCode); void onGatewayConnectionError( in int[] gatewayNetworkCapabilities, diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index eb8c251fec78..8ebf757760c3 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -359,8 +359,6 @@ public class VcnManager { /** * Value indicating that the VCN for the subscription group is not configured, or that the * callback is not privileged for the subscription group. - * - * @hide */ public static final int VCN_STATUS_CODE_NOT_CONFIGURED = 0; @@ -369,8 +367,6 @@ public class VcnManager { * * <p>A VCN is inactive if a {@link VcnConfig} is present for the subscription group, but the * provisioning package is not privileged. - * - * @hide */ public static final int VCN_STATUS_CODE_INACTIVE = 1; @@ -380,8 +376,6 @@ public class VcnManager { * <p>A VCN is active if a {@link VcnConfig} is present for the subscription, the provisioning * package is privileged, and the VCN is not in Safe Mode. In other words, a VCN is considered * active while it is connecting, fully connected, and disconnecting. - * - * @hide */ public static final int VCN_STATUS_CODE_ACTIVE = 2; @@ -391,8 +385,6 @@ public class VcnManager { * <p>A VCN will be put into Safe Mode if any of the gateway connections were unable to * establish a connection within a system-determined timeout (while underlying networks were * available). - * - * @hide */ public static final int VCN_STATUS_CODE_SAFE_MODE = 3; @@ -407,8 +399,6 @@ public class VcnManager { /** * Value indicating that an internal failure occurred in this Gateway Connection. - * - * @hide */ public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0; @@ -416,8 +406,6 @@ public class VcnManager { * Value indicating that an error with this Gateway Connection's configuration occurred. * * <p>For example, this error code will be returned after authentication failures. - * - * @hide */ public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1; @@ -427,38 +415,19 @@ public class VcnManager { * <p>For example, this error code will be returned if an underlying {@link android.net.Network} * for this Gateway Connection is lost, or if an error occurs while resolving the connection * endpoint address. - * - * @hide */ public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2; - // TODO: make VcnStatusCallback @SystemApi /** * VcnStatusCallback is the interface for Carrier apps to receive updates for their VCNs. * * <p>VcnStatusCallbacks may be registered before {@link VcnConfig}s are provided for a * subscription group. - * - * @hide */ public abstract static class VcnStatusCallback { private VcnStatusCallbackBinder mCbBinder; /** - * Invoked when the VCN for this Callback's subscription group enters safe mode. - * - * <p>A VCN will be put into safe mode if any of the gateway connections were unable to - * establish a connection within a system-determined timeout (while underlying networks were - * available). - * - * <p>A VCN-configuring app may opt to exit safe mode by (re)setting the VCN configuration - * via {@link #setVcnConfig(ParcelUuid, VcnConfig)}. - * - * @hide - */ - public void onEnteredSafeMode() {} - - /** * Invoked when status of the VCN for this callback's subscription group changes. * * @param statusCode the code for the status change encountered by this {@link @@ -467,15 +436,16 @@ public class VcnManager { public abstract void onVcnStatusChanged(@VcnStatusCode int statusCode); /** - * Invoked when a VCN Gateway Connection corresponding to this callback's subscription + * Invoked when a VCN Gateway Connection corresponding to this callback's subscription group * encounters an error. * - * @param networkCapabilities an array of underlying NetworkCapabilities for the Gateway - * Connection that encountered the error for identification purposes. These will be a - * sorted list with no duplicates, matching one of the {@link + * @param networkCapabilities an array of NetworkCapabilities.NET_CAPABILITY_* capabilities + * for the Gateway Connection that encountered the error, for identification purposes. + * These will be a sorted list with no duplicates and will match {@link + * VcnGatewayConnectionConfig#getRequiredUnderlyingCapabilities()} for one of the {@link * VcnGatewayConnectionConfig}s set in the {@link VcnConfig} for this subscription * group. - * @param errorCode {@link VcnErrorCode} to indicate the error that occurred + * @param errorCode the code to indicate the error that occurred * @param detail Throwable to provide additional information about the error, or {@code * null} if none */ @@ -496,6 +466,10 @@ public class VcnManager { * <p>A {@link VcnStatusCallback} will only be invoked if the registering package has carrier * privileges for the specified subscription at the time of invocation. * + * <p>A {@link VcnStatusCallback} is eligible to begin receiving callbacks once it is registered + * and there is a VCN active for its specified subscription group (this may happen after the + * callback is registered). + * * <p>{@link VcnStatusCallback#onVcnStatusChanged(int)} will be invoked on registration with the * current status for the specified subscription group's VCN. If the registrant is not * privileged for this subscription group, {@link #VCN_STATUS_CODE_NOT_CONFIGURED} will be @@ -505,7 +479,6 @@ public class VcnManager { * @param executor The {@link Executor} to be used for invoking callbacks * @param callback The VcnStatusCallback to be registered * @throws IllegalStateException if callback is currently registered with VcnManager - * @hide */ public void registerVcnStatusCallback( @NonNull ParcelUuid subscriptionGroup, @@ -538,7 +511,6 @@ public class VcnManager { * was registered with. * * @param callback The callback to be unregistered - * @hide */ public void unregisterVcnStatusCallback(@NonNull VcnStatusCallback callback) { requireNonNull(callback, "callback must not be null"); @@ -599,12 +571,6 @@ public class VcnManager { } @Override - public void onEnteredSafeMode() { - Binder.withCleanCallingIdentity( - () -> mExecutor.execute(() -> mCallback.onEnteredSafeMode())); - } - - @Override public void onVcnStatusChanged(@VcnStatusCode int statusCode) { Binder.withCleanCallingIdentity( () -> mExecutor.execute(() -> mCallback.onVcnStatusChanged(statusCode))); diff --git a/core/java/android/net/vcn/persistablebundleutils/CertUtils.java b/core/java/android/net/vcn/persistablebundleutils/CertUtils.java new file mode 100644 index 000000000000..b6036b4a6fd1 --- /dev/null +++ b/core/java/android/net/vcn/persistablebundleutils/CertUtils.java @@ -0,0 +1,46 @@ +/* + * 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 android.net.vcn.persistablebundleutils; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Objects; + +/** + * CertUtils provides utility methods for constructing Certificate. + * + * @hide + */ +public class CertUtils { + private static final String CERT_TYPE_X509 = "X.509"; + + /** Decodes an ASN.1 DER encoded Certificate */ + public static X509Certificate certificateFromByteArray(byte[] derEncoded) { + Objects.requireNonNull(derEncoded, "derEncoded is null"); + + try { + CertificateFactory certFactory = CertificateFactory.getInstance(CERT_TYPE_X509); + InputStream in = new ByteArrayInputStream(derEncoded); + return (X509Certificate) certFactory.generateCertificate(in); + } catch (CertificateException e) { + throw new IllegalArgumentException("Fail to decode certificate", e); + } + } +} diff --git a/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java b/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java new file mode 100644 index 000000000000..ce5ec75f01a2 --- /dev/null +++ b/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java @@ -0,0 +1,73 @@ +/* + * 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 android.net.vcn.persistablebundleutils; + +import static com.android.internal.annotations.VisibleForTesting.Visibility; + +import android.annotation.NonNull; +import android.net.ipsec.ike.ChildSaProposal; +import android.os.PersistableBundle; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.vcn.util.PersistableBundleUtils; + +import java.util.List; +import java.util.Objects; + +/** + * Provides utility methods to convert ChildSaProposal to/from PersistableBundle. + * + * @hide + */ +@VisibleForTesting(visibility = Visibility.PRIVATE) +public final class ChildSaProposalUtils extends SaProposalUtilsBase { + /** Serializes a ChildSaProposal to a PersistableBundle. */ + @NonNull + public static PersistableBundle toPersistableBundle(ChildSaProposal proposal) { + return SaProposalUtilsBase.toPersistableBundle(proposal); + } + + /** Constructs a ChildSaProposal by deserializing a PersistableBundle. */ + @NonNull + public static ChildSaProposal fromPersistableBundle(@NonNull PersistableBundle in) { + Objects.requireNonNull(in, "PersistableBundle was null"); + + final ChildSaProposal.Builder builder = new ChildSaProposal.Builder(); + + final PersistableBundle encryptionBundle = in.getPersistableBundle(ENCRYPT_ALGO_KEY); + Objects.requireNonNull(encryptionBundle, "Encryption algo bundle was null"); + final List<EncryptionAlgoKeyLenPair> encryptList = + PersistableBundleUtils.toList(encryptionBundle, EncryptionAlgoKeyLenPair::new); + for (EncryptionAlgoKeyLenPair t : encryptList) { + builder.addEncryptionAlgorithm(t.encryptionAlgo, t.keyLen); + } + + final int[] integrityAlgoIdArray = in.getIntArray(INTEGRITY_ALGO_KEY); + Objects.requireNonNull(integrityAlgoIdArray, "Integrity algo array was null"); + for (int algo : integrityAlgoIdArray) { + builder.addIntegrityAlgorithm(algo); + } + + final int[] dhGroupArray = in.getIntArray(DH_GROUP_KEY); + Objects.requireNonNull(dhGroupArray, "DH Group array was null"); + for (int dh : dhGroupArray) { + builder.addDhGroup(dh); + } + + return builder.build(); + } +} diff --git a/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java b/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java new file mode 100644 index 000000000000..853a52da766a --- /dev/null +++ b/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java @@ -0,0 +1,276 @@ +/* + * 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 android.net.vcn.persistablebundleutils; + + +import static com.android.internal.annotations.VisibleForTesting.Visibility; + +import android.annotation.NonNull; +import android.net.eap.EapSessionConfig; +import android.net.eap.EapSessionConfig.EapAkaConfig; +import android.net.eap.EapSessionConfig.EapAkaPrimeConfig; +import android.net.eap.EapSessionConfig.EapMethodConfig; +import android.net.eap.EapSessionConfig.EapMsChapV2Config; +import android.net.eap.EapSessionConfig.EapSimConfig; +import android.net.eap.EapSessionConfig.EapTtlsConfig; +import android.net.eap.EapSessionConfig.EapUiccConfig; +import android.os.PersistableBundle; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.vcn.util.PersistableBundleUtils; + +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.Objects; + +/** + * Provides utility methods to convert EapSessionConfig to/from PersistableBundle. + * + * @hide + */ +@VisibleForTesting(visibility = Visibility.PRIVATE) +public final class EapSessionConfigUtils { + private static final String EAP_ID_KEY = "EAP_ID_KEY"; + private static final String EAP_SIM_CONFIG_KEY = "EAP_SIM_CONFIG_KEY"; + private static final String EAP_TTLS_CONFIG_KEY = "EAP_TTLS_CONFIG_KEY"; + private static final String EAP_AKA_CONFIG_KEY = "EAP_AKA_CONFIG_KEY"; + private static final String EAP_MSCHAP_V2_CONFIG_KEY = "EAP_MSCHAP_V2_CONFIG_KEY"; + private static final String EAP_AKA_PRIME_CONFIG_KEY = "EAP_AKA_PRIME_CONFIG_KEY"; + + /** Serializes an EapSessionConfig to a PersistableBundle. */ + @NonNull + public static PersistableBundle toPersistableBundle(@NonNull EapSessionConfig config) { + final PersistableBundle result = new PersistableBundle(); + + result.putPersistableBundle( + EAP_ID_KEY, PersistableBundleUtils.fromByteArray(config.getEapIdentity())); + + if (config.getEapSimConfig() != null) { + result.putPersistableBundle( + EAP_SIM_CONFIG_KEY, + EapSimConfigUtils.toPersistableBundle(config.getEapSimConfig())); + } + + if (config.getEapTtlsConfig() != null) { + result.putPersistableBundle( + EAP_TTLS_CONFIG_KEY, + EapTtlsConfigUtils.toPersistableBundle(config.getEapTtlsConfig())); + } + + if (config.getEapAkaConfig() != null) { + result.putPersistableBundle( + EAP_AKA_CONFIG_KEY, + EapAkaConfigUtils.toPersistableBundle(config.getEapAkaConfig())); + } + + if (config.getEapMsChapV2Config() != null) { + result.putPersistableBundle( + EAP_MSCHAP_V2_CONFIG_KEY, + EapMsChapV2ConfigUtils.toPersistableBundle(config.getEapMsChapV2Config())); + } + + if (config.getEapAkaPrimeConfig() != null) { + result.putPersistableBundle( + EAP_AKA_PRIME_CONFIG_KEY, + EapAkaPrimeConfigUtils.toPersistableBundle(config.getEapAkaPrimeConfig())); + } + + return result; + } + + /** Constructs an EapSessionConfig by deserializing a PersistableBundle. */ + @NonNull + public static EapSessionConfig fromPersistableBundle(@NonNull PersistableBundle in) { + Objects.requireNonNull(in, "PersistableBundle was null"); + + final EapSessionConfig.Builder builder = new EapSessionConfig.Builder(); + + final PersistableBundle eapIdBundle = in.getPersistableBundle(EAP_ID_KEY); + Objects.requireNonNull(eapIdBundle, "EAP ID was null"); + builder.setEapIdentity(PersistableBundleUtils.toByteArray(eapIdBundle)); + + final PersistableBundle simBundle = in.getPersistableBundle(EAP_SIM_CONFIG_KEY); + if (simBundle != null) { + EapSimConfigUtils.setBuilderByReadingPersistableBundle(simBundle, builder); + } + + final PersistableBundle ttlsBundle = in.getPersistableBundle(EAP_TTLS_CONFIG_KEY); + if (ttlsBundle != null) { + EapTtlsConfigUtils.setBuilderByReadingPersistableBundle(ttlsBundle, builder); + } + + final PersistableBundle akaBundle = in.getPersistableBundle(EAP_AKA_CONFIG_KEY); + if (akaBundle != null) { + EapAkaConfigUtils.setBuilderByReadingPersistableBundle(akaBundle, builder); + } + + final PersistableBundle msChapV2Bundle = in.getPersistableBundle(EAP_MSCHAP_V2_CONFIG_KEY); + if (msChapV2Bundle != null) { + EapMsChapV2ConfigUtils.setBuilderByReadingPersistableBundle(msChapV2Bundle, builder); + } + + final PersistableBundle akaPrimeBundle = in.getPersistableBundle(EAP_AKA_PRIME_CONFIG_KEY); + if (akaPrimeBundle != null) { + EapAkaPrimeConfigUtils.setBuilderByReadingPersistableBundle(akaPrimeBundle, builder); + } + + return builder.build(); + } + + private static class EapMethodConfigUtils { + private static final String METHOD_TYPE = "METHOD_TYPE"; + + /** Serializes an EapMethodConfig to a PersistableBundle. */ + @NonNull + public static PersistableBundle toPersistableBundle(@NonNull EapMethodConfig config) { + final PersistableBundle result = new PersistableBundle(); + result.putInt(METHOD_TYPE, config.getMethodType()); + return result; + } + } + + private static class EapUiccConfigUtils extends EapMethodConfigUtils { + static final String SUB_ID_KEY = "SUB_ID_KEY"; + static final String APP_TYPE_KEY = "APP_TYPE_KEY"; + + @NonNull + protected static PersistableBundle toPersistableBundle(@NonNull EapUiccConfig config) { + final PersistableBundle result = EapMethodConfigUtils.toPersistableBundle(config); + result.putInt(SUB_ID_KEY, config.getSubId()); + result.putInt(APP_TYPE_KEY, config.getAppType()); + + return result; + } + } + + private static final class EapSimConfigUtils extends EapUiccConfigUtils { + @NonNull + public static PersistableBundle toPersistableBundle(EapSimConfig config) { + return EapUiccConfigUtils.toPersistableBundle(config); + } + + public static void setBuilderByReadingPersistableBundle( + @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) { + Objects.requireNonNull(in, "PersistableBundle was null"); + builder.setEapSimConfig(in.getInt(SUB_ID_KEY), in.getInt(APP_TYPE_KEY)); + } + } + + private static class EapAkaConfigUtils extends EapUiccConfigUtils { + @NonNull + public static PersistableBundle toPersistableBundle(@NonNull EapAkaConfig config) { + return EapUiccConfigUtils.toPersistableBundle(config); + } + + public static void setBuilderByReadingPersistableBundle( + @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) { + Objects.requireNonNull(in, "PersistableBundle was null"); + builder.setEapAkaConfig(in.getInt(SUB_ID_KEY), in.getInt(APP_TYPE_KEY)); + } + } + + private static final class EapAkaPrimeConfigUtils extends EapAkaConfigUtils { + private static final String NETWORK_NAME_KEY = "NETWORK_NAME_KEY"; + private static final String ALL_MISMATCHED_NETWORK_KEY = "ALL_MISMATCHED_NETWORK_KEY"; + + @NonNull + public static PersistableBundle toPersistableBundle(@NonNull EapAkaPrimeConfig config) { + final PersistableBundle result = EapUiccConfigUtils.toPersistableBundle(config); + result.putString(NETWORK_NAME_KEY, config.getNetworkName()); + result.putBoolean(ALL_MISMATCHED_NETWORK_KEY, config.allowsMismatchedNetworkNames()); + + return result; + } + + public static void setBuilderByReadingPersistableBundle( + @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) { + Objects.requireNonNull(in, "PersistableBundle was null"); + builder.setEapAkaPrimeConfig( + in.getInt(SUB_ID_KEY), + in.getInt(APP_TYPE_KEY), + in.getString(NETWORK_NAME_KEY), + in.getBoolean(ALL_MISMATCHED_NETWORK_KEY)); + } + } + + private static final class EapMsChapV2ConfigUtils extends EapMethodConfigUtils { + private static final String USERNAME_KEY = "USERNAME_KEY"; + private static final String PASSWORD_KEY = "PASSWORD_KEY"; + + @NonNull + public static PersistableBundle toPersistableBundle(@NonNull EapMsChapV2Config config) { + final PersistableBundle result = EapMethodConfigUtils.toPersistableBundle(config); + result.putString(USERNAME_KEY, config.getUsername()); + result.putString(PASSWORD_KEY, config.getPassword()); + + return result; + } + + public static void setBuilderByReadingPersistableBundle( + @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) { + Objects.requireNonNull(in, "PersistableBundle was null"); + builder.setEapMsChapV2Config(in.getString(USERNAME_KEY), in.getString(PASSWORD_KEY)); + } + } + + private static final class EapTtlsConfigUtils extends EapMethodConfigUtils { + private static final String TRUST_CERT_KEY = "TRUST_CERT_KEY"; + private static final String EAP_SESSION_CONFIG_KEY = "EAP_SESSION_CONFIG_KEY"; + + @NonNull + public static PersistableBundle toPersistableBundle(@NonNull EapTtlsConfig config) { + final PersistableBundle result = EapMethodConfigUtils.toPersistableBundle(config); + try { + if (config.getServerCaCert() != null) { + final PersistableBundle caBundle = + PersistableBundleUtils.fromByteArray( + config.getServerCaCert().getEncoded()); + result.putPersistableBundle(TRUST_CERT_KEY, caBundle); + } + } catch (CertificateEncodingException e) { + throw new IllegalStateException("Fail to encode the certificate"); + } + + result.putPersistableBundle( + EAP_SESSION_CONFIG_KEY, + EapSessionConfigUtils.toPersistableBundle(config.getInnerEapSessionConfig())); + + return result; + } + + public static void setBuilderByReadingPersistableBundle( + @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) { + Objects.requireNonNull(in, "PersistableBundle was null"); + + final PersistableBundle caBundle = in.getPersistableBundle(TRUST_CERT_KEY); + X509Certificate caCert = null; + if (caBundle != null) { + caCert = + CertUtils.certificateFromByteArray( + PersistableBundleUtils.toByteArray(caBundle)); + } + + final PersistableBundle eapSessionConfigBundle = + in.getPersistableBundle(EAP_SESSION_CONFIG_KEY); + Objects.requireNonNull(eapSessionConfigBundle, "Inner EAP Session Config was null"); + final EapSessionConfig eapSessionConfig = + EapSessionConfigUtils.fromPersistableBundle(eapSessionConfigBundle); + + builder.setEapTtlsConfig(caCert, eapSessionConfig); + } + } +} diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java new file mode 100644 index 000000000000..6acb34ebb78e --- /dev/null +++ b/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java @@ -0,0 +1,143 @@ +/* + * 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 android.net.vcn.persistablebundleutils; + +import static com.android.internal.annotations.VisibleForTesting.Visibility; + +import android.annotation.NonNull; +import android.net.InetAddresses; +import android.net.ipsec.ike.IkeDerAsn1DnIdentification; +import android.net.ipsec.ike.IkeFqdnIdentification; +import android.net.ipsec.ike.IkeIdentification; +import android.net.ipsec.ike.IkeIpv4AddrIdentification; +import android.net.ipsec.ike.IkeIpv6AddrIdentification; +import android.net.ipsec.ike.IkeKeyIdIdentification; +import android.net.ipsec.ike.IkeRfc822AddrIdentification; +import android.os.PersistableBundle; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.vcn.util.PersistableBundleUtils; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.util.Objects; + +import javax.security.auth.x500.X500Principal; + +/** + * Abstract utility class to convert IkeIdentification to/from PersistableBundle. + * + * @hide + */ +@VisibleForTesting(visibility = Visibility.PRIVATE) +public final class IkeIdentificationUtils { + private static final String ID_TYPE_KEY = "ID_TYPE_KEY"; + + private static final String DER_ASN1_DN_KEY = "DER_ASN1_DN_KEY"; + private static final String FQDN_KEY = "FQDN_KEY"; + private static final String KEY_ID_KEY = "KEY_ID_KEY"; + private static final String IP4_ADDRESS_KEY = "IP4_ADDRESS_KEY"; + private static final String IP6_ADDRESS_KEY = "IP6_ADDRESS_KEY"; + private static final String RFC822_ADDRESS_KEY = "RFC822_ADDRESS_KEY"; + + private static final int ID_TYPE_DER_ASN1_DN = 1; + private static final int ID_TYPE_FQDN = 2; + private static final int ID_TYPE_IPV4_ADDR = 3; + private static final int ID_TYPE_IPV6_ADDR = 4; + private static final int ID_TYPE_KEY_ID = 5; + private static final int ID_TYPE_RFC822_ADDR = 6; + + /** Serializes an IkeIdentification to a PersistableBundle. */ + @NonNull + public static PersistableBundle toPersistableBundle(@NonNull IkeIdentification ikeId) { + if (ikeId instanceof IkeDerAsn1DnIdentification) { + final PersistableBundle result = createPersistableBundle(ID_TYPE_DER_ASN1_DN); + IkeDerAsn1DnIdentification id = (IkeDerAsn1DnIdentification) ikeId; + result.putPersistableBundle( + DER_ASN1_DN_KEY, + PersistableBundleUtils.fromByteArray(id.derAsn1Dn.getEncoded())); + return result; + } else if (ikeId instanceof IkeFqdnIdentification) { + final PersistableBundle result = createPersistableBundle(ID_TYPE_FQDN); + IkeFqdnIdentification id = (IkeFqdnIdentification) ikeId; + result.putString(FQDN_KEY, id.fqdn); + return result; + } else if (ikeId instanceof IkeIpv4AddrIdentification) { + final PersistableBundle result = createPersistableBundle(ID_TYPE_IPV4_ADDR); + IkeIpv4AddrIdentification id = (IkeIpv4AddrIdentification) ikeId; + result.putString(IP4_ADDRESS_KEY, id.ipv4Address.getHostAddress()); + return result; + } else if (ikeId instanceof IkeIpv6AddrIdentification) { + final PersistableBundle result = createPersistableBundle(ID_TYPE_IPV6_ADDR); + IkeIpv6AddrIdentification id = (IkeIpv6AddrIdentification) ikeId; + result.putString(IP6_ADDRESS_KEY, id.ipv6Address.getHostAddress()); + return result; + } else if (ikeId instanceof IkeKeyIdIdentification) { + final PersistableBundle result = createPersistableBundle(ID_TYPE_KEY_ID); + IkeKeyIdIdentification id = (IkeKeyIdIdentification) ikeId; + result.putPersistableBundle(KEY_ID_KEY, PersistableBundleUtils.fromByteArray(id.keyId)); + return result; + } else if (ikeId instanceof IkeRfc822AddrIdentification) { + final PersistableBundle result = createPersistableBundle(ID_TYPE_RFC822_ADDR); + IkeRfc822AddrIdentification id = (IkeRfc822AddrIdentification) ikeId; + result.putString(RFC822_ADDRESS_KEY, id.rfc822Name); + return result; + } else { + throw new IllegalStateException("Unrecognized IkeIdentification subclass"); + } + } + + private static PersistableBundle createPersistableBundle(int idType) { + final PersistableBundle result = new PersistableBundle(); + result.putInt(ID_TYPE_KEY, idType); + return result; + } + + /** Constructs an IkeIdentification by deserializing a PersistableBundle. */ + @NonNull + public static IkeIdentification fromPersistableBundle(@NonNull PersistableBundle in) { + Objects.requireNonNull(in, "PersistableBundle was null"); + int idType = in.getInt(ID_TYPE_KEY); + switch (idType) { + case ID_TYPE_DER_ASN1_DN: + final PersistableBundle dnBundle = in.getPersistableBundle(DER_ASN1_DN_KEY); + Objects.requireNonNull(dnBundle, "ASN1 DN was null"); + return new IkeDerAsn1DnIdentification( + new X500Principal(PersistableBundleUtils.toByteArray(dnBundle))); + case ID_TYPE_FQDN: + return new IkeFqdnIdentification(in.getString(FQDN_KEY)); + case ID_TYPE_IPV4_ADDR: + final String v4AddressStr = in.getString(IP4_ADDRESS_KEY); + Objects.requireNonNull(v4AddressStr, "IPv4 address was null"); + return new IkeIpv4AddrIdentification( + (Inet4Address) InetAddresses.parseNumericAddress(v4AddressStr)); + case ID_TYPE_IPV6_ADDR: + final String v6AddressStr = in.getString(IP6_ADDRESS_KEY); + Objects.requireNonNull(v6AddressStr, "IPv6 address was null"); + return new IkeIpv6AddrIdentification( + (Inet6Address) InetAddresses.parseNumericAddress(v6AddressStr)); + case ID_TYPE_KEY_ID: + final PersistableBundle keyIdBundle = in.getPersistableBundle(KEY_ID_KEY); + Objects.requireNonNull(in, "Key ID was null"); + return new IkeKeyIdIdentification(PersistableBundleUtils.toByteArray(keyIdBundle)); + case ID_TYPE_RFC822_ADDR: + return new IkeRfc822AddrIdentification(in.getString(RFC822_ADDRESS_KEY)); + default: + throw new IllegalStateException("Unrecognized IKE ID type: " + idType); + } + } +} diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java new file mode 100644 index 000000000000..1459671f4136 --- /dev/null +++ b/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java @@ -0,0 +1,87 @@ +/* + * 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 android.net.vcn.persistablebundleutils; + +import static com.android.internal.annotations.VisibleForTesting.Visibility; + +import android.annotation.NonNull; +import android.net.ipsec.ike.IkeSaProposal; +import android.os.PersistableBundle; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.vcn.util.PersistableBundleUtils; + +import java.util.List; +import java.util.Objects; + +/** + * Provides utility methods to convert IkeSaProposal to/from PersistableBundle. + * + * @hide + */ +@VisibleForTesting(visibility = Visibility.PRIVATE) +public final class IkeSaProposalUtils extends SaProposalUtilsBase { + private static final String PRF_KEY = "PRF_KEY"; + + /** Serializes an IkeSaProposal to a PersistableBundle. */ + @NonNull + public static PersistableBundle toPersistableBundle(IkeSaProposal proposal) { + final PersistableBundle result = SaProposalUtilsBase.toPersistableBundle(proposal); + + final int[] prfArray = + proposal.getPseudorandomFunctions().stream().mapToInt(i -> i).toArray(); + result.putIntArray(PRF_KEY, prfArray); + + return result; + } + + /** Constructs an IkeSaProposal by deserializing a PersistableBundle. */ + @NonNull + public static IkeSaProposal fromPersistableBundle(@NonNull PersistableBundle in) { + Objects.requireNonNull(in, "PersistableBundle was null"); + + final IkeSaProposal.Builder builder = new IkeSaProposal.Builder(); + + final PersistableBundle encryptionBundle = in.getPersistableBundle(ENCRYPT_ALGO_KEY); + Objects.requireNonNull(encryptionBundle, "Encryption algo bundle was null"); + final List<EncryptionAlgoKeyLenPair> encryptList = + PersistableBundleUtils.toList(encryptionBundle, EncryptionAlgoKeyLenPair::new); + for (EncryptionAlgoKeyLenPair t : encryptList) { + builder.addEncryptionAlgorithm(t.encryptionAlgo, t.keyLen); + } + + final int[] integrityAlgoIdArray = in.getIntArray(INTEGRITY_ALGO_KEY); + Objects.requireNonNull(integrityAlgoIdArray, "Integrity algo array was null"); + for (int algo : integrityAlgoIdArray) { + builder.addIntegrityAlgorithm(algo); + } + + final int[] dhGroupArray = in.getIntArray(DH_GROUP_KEY); + Objects.requireNonNull(dhGroupArray, "DH Group array was null"); + for (int dh : dhGroupArray) { + builder.addDhGroup(dh); + } + + final int[] prfArray = in.getIntArray(PRF_KEY); + Objects.requireNonNull(prfArray, "PRF array was null"); + for (int prf : prfArray) { + builder.addPseudorandomFunction(prf); + } + + return builder.build(); + } +} diff --git a/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java b/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java new file mode 100644 index 000000000000..0c9ee8432798 --- /dev/null +++ b/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java @@ -0,0 +1,96 @@ +/* + * 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 android.net.vcn.persistablebundleutils; + +import android.annotation.NonNull; +import android.net.ipsec.ike.SaProposal; +import android.os.PersistableBundle; +import android.util.Pair; + +import com.android.server.vcn.util.PersistableBundleUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Abstract utility class to convert SaProposal to/from PersistableBundle. + * + * @hide + */ +abstract class SaProposalUtilsBase { + static final String ENCRYPT_ALGO_KEY = "ENCRYPT_ALGO_KEY"; + static final String INTEGRITY_ALGO_KEY = "INTEGRITY_ALGO_KEY"; + static final String DH_GROUP_KEY = "DH_GROUP_KEY"; + + static class EncryptionAlgoKeyLenPair { + private static final String ALGO_KEY = "ALGO_KEY"; + private static final String KEY_LEN_KEY = "KEY_LEN_KEY"; + + public final int encryptionAlgo; + public final int keyLen; + + EncryptionAlgoKeyLenPair(int encryptionAlgo, int keyLen) { + this.encryptionAlgo = encryptionAlgo; + this.keyLen = keyLen; + } + + EncryptionAlgoKeyLenPair(PersistableBundle in) { + Objects.requireNonNull(in, "PersistableBundle was null"); + + this.encryptionAlgo = in.getInt(ALGO_KEY); + this.keyLen = in.getInt(KEY_LEN_KEY); + } + + public PersistableBundle toPersistableBundle() { + final PersistableBundle result = new PersistableBundle(); + + result.putInt(ALGO_KEY, encryptionAlgo); + result.putInt(KEY_LEN_KEY, keyLen); + + return result; + } + } + + /** + * Serializes common info of a SaProposal to a PersistableBundle. + * + * @hide + */ + @NonNull + static PersistableBundle toPersistableBundle(SaProposal proposal) { + final PersistableBundle result = new PersistableBundle(); + + final List<EncryptionAlgoKeyLenPair> encryptAlgoKeyLenPairs = new ArrayList<>(); + for (Pair<Integer, Integer> pair : proposal.getEncryptionAlgorithms()) { + encryptAlgoKeyLenPairs.add(new EncryptionAlgoKeyLenPair(pair.first, pair.second)); + } + final PersistableBundle encryptionBundle = + PersistableBundleUtils.fromList( + encryptAlgoKeyLenPairs, EncryptionAlgoKeyLenPair::toPersistableBundle); + result.putPersistableBundle(ENCRYPT_ALGO_KEY, encryptionBundle); + + final int[] integrityAlgoIdArray = + proposal.getIntegrityAlgorithms().stream().mapToInt(i -> i).toArray(); + result.putIntArray(INTEGRITY_ALGO_KEY, integrityAlgoIdArray); + + final int[] dhGroupArray = proposal.getDhGroups().stream().mapToInt(i -> i).toArray(); + result.putIntArray(DH_GROUP_KEY, dhGroupArray); + + return result; + } +} diff --git a/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java new file mode 100644 index 000000000000..e62acac14bd7 --- /dev/null +++ b/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java @@ -0,0 +1,285 @@ +/* + * 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 android.net.vcn.persistablebundleutils; + +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; + +import static com.android.internal.annotations.VisibleForTesting.Visibility; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.InetAddresses; +import android.net.ipsec.ike.ChildSaProposal; +import android.net.ipsec.ike.IkeTrafficSelector; +import android.net.ipsec.ike.TunnelModeChildSessionParams; +import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4Address; +import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4DhcpServer; +import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4DnsServer; +import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4Netmask; +import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv6Address; +import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv6DnsServer; +import android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest; +import android.os.PersistableBundle; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.vcn.util.PersistableBundleUtils; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Provides utility methods to convert TunnelModeChildSessionParams to/from PersistableBundle. + * + * @hide + */ +@VisibleForTesting(visibility = Visibility.PRIVATE) +public final class TunnelModeChildSessionParamsUtils { + private static final String TAG = TunnelModeChildSessionParamsUtils.class.getSimpleName(); + + private static final String INBOUND_TS_KEY = "INBOUND_TS_KEY"; + private static final String OUTBOUND_TS_KEY = "OUTBOUND_TS_KEY"; + private static final String SA_PROPOSALS_KEY = "SA_PROPOSALS_KEY"; + private static final String HARD_LIFETIME_SEC_KEY = "HARD_LIFETIME_SEC_KEY"; + private static final String SOFT_LIFETIME_SEC_KEY = "SOFT_LIFETIME_SEC_KEY"; + private static final String CONFIG_REQUESTS_KEY = "CONFIG_REQUESTS_KEY"; + + private static class ConfigRequest { + private static final int TYPE_IPV4_ADDRESS = 1; + private static final int TYPE_IPV6_ADDRESS = 2; + private static final int TYPE_IPV4_DNS = 3; + private static final int TYPE_IPV6_DNS = 4; + private static final int TYPE_IPV4_DHCP = 5; + private static final int TYPE_IPV4_NETMASK = 6; + + private static final String TYPE_KEY = "type"; + private static final String VALUE_KEY = "address"; + private static final String IP6_PREFIX_LEN = "ip6PrefixLen"; + + private static final int PREFIX_LEN_UNUSED = -1; + + public final int type; + public final int ip6PrefixLen; + + // Null when it is an empty request + @Nullable public final InetAddress address; + + ConfigRequest(TunnelModeChildConfigRequest config) { + int prefixLen = PREFIX_LEN_UNUSED; + + if (config instanceof ConfigRequestIpv4Address) { + type = TYPE_IPV4_ADDRESS; + address = ((ConfigRequestIpv4Address) config).getAddress(); + } else if (config instanceof ConfigRequestIpv6Address) { + type = TYPE_IPV6_ADDRESS; + address = ((ConfigRequestIpv6Address) config).getAddress(); + if (address != null) { + prefixLen = ((ConfigRequestIpv6Address) config).getPrefixLength(); + } + } else if (config instanceof ConfigRequestIpv4DnsServer) { + type = TYPE_IPV4_DNS; + address = null; + } else if (config instanceof ConfigRequestIpv6DnsServer) { + type = TYPE_IPV6_DNS; + address = null; + } else if (config instanceof ConfigRequestIpv4DhcpServer) { + type = TYPE_IPV4_DHCP; + address = null; + } else if (config instanceof ConfigRequestIpv4Netmask) { + type = TYPE_IPV4_NETMASK; + address = null; + } else { + throw new IllegalStateException("Unknown TunnelModeChildConfigRequest"); + } + + ip6PrefixLen = prefixLen; + } + + ConfigRequest(PersistableBundle in) { + Objects.requireNonNull(in, "PersistableBundle was null"); + + type = in.getInt(TYPE_KEY); + ip6PrefixLen = in.getInt(IP6_PREFIX_LEN); + + String addressStr = in.getString(VALUE_KEY); + if (addressStr == null) { + address = null; + } else { + address = InetAddresses.parseNumericAddress(addressStr); + } + } + + @NonNull + public PersistableBundle toPersistableBundle() { + final PersistableBundle result = new PersistableBundle(); + + result.putInt(TYPE_KEY, type); + result.putInt(IP6_PREFIX_LEN, ip6PrefixLen); + + if (address != null) { + result.putString(VALUE_KEY, address.getHostAddress()); + } + + return result; + } + } + + /** Serializes a TunnelModeChildSessionParams to a PersistableBundle. */ + @NonNull + public static PersistableBundle toPersistableBundle( + @NonNull TunnelModeChildSessionParams params) { + final PersistableBundle result = new PersistableBundle(); + + final PersistableBundle saProposalBundle = + PersistableBundleUtils.fromList( + params.getSaProposals(), ChildSaProposalUtils::toPersistableBundle); + result.putPersistableBundle(SA_PROPOSALS_KEY, saProposalBundle); + + final PersistableBundle inTsBundle = + PersistableBundleUtils.fromList( + params.getInboundTrafficSelectors(), + IkeTrafficSelectorUtils::toPersistableBundle); + result.putPersistableBundle(INBOUND_TS_KEY, inTsBundle); + + final PersistableBundle outTsBundle = + PersistableBundleUtils.fromList( + params.getOutboundTrafficSelectors(), + IkeTrafficSelectorUtils::toPersistableBundle); + result.putPersistableBundle(OUTBOUND_TS_KEY, outTsBundle); + + result.putInt(HARD_LIFETIME_SEC_KEY, params.getHardLifetimeSeconds()); + result.putInt(SOFT_LIFETIME_SEC_KEY, params.getSoftLifetimeSeconds()); + + final List<ConfigRequest> reqList = new ArrayList<>(); + for (TunnelModeChildConfigRequest req : params.getConfigurationRequests()) { + reqList.add(new ConfigRequest(req)); + } + final PersistableBundle configReqListBundle = + PersistableBundleUtils.fromList(reqList, ConfigRequest::toPersistableBundle); + result.putPersistableBundle(CONFIG_REQUESTS_KEY, configReqListBundle); + + return result; + } + + private static List<IkeTrafficSelector> getTsFromPersistableBundle( + PersistableBundle in, String key) { + PersistableBundle tsBundle = in.getPersistableBundle(key); + Objects.requireNonNull(tsBundle, "Value for key " + key + " was null"); + return PersistableBundleUtils.toList( + tsBundle, IkeTrafficSelectorUtils::fromPersistableBundle); + } + + /** Constructs a TunnelModeChildSessionParams by deserializing a PersistableBundle. */ + @NonNull + public static TunnelModeChildSessionParams fromPersistableBundle( + @NonNull PersistableBundle in) { + Objects.requireNonNull(in, "PersistableBundle was null"); + + final TunnelModeChildSessionParams.Builder builder = + new TunnelModeChildSessionParams.Builder(); + + final PersistableBundle proposalBundle = in.getPersistableBundle(SA_PROPOSALS_KEY); + Objects.requireNonNull(proposalBundle, "SA proposal was null"); + final List<ChildSaProposal> proposals = + PersistableBundleUtils.toList( + proposalBundle, ChildSaProposalUtils::fromPersistableBundle); + for (ChildSaProposal p : proposals) { + builder.addSaProposal(p); + } + + for (IkeTrafficSelector ts : getTsFromPersistableBundle(in, INBOUND_TS_KEY)) { + builder.addInboundTrafficSelectors(ts); + } + + for (IkeTrafficSelector ts : getTsFromPersistableBundle(in, OUTBOUND_TS_KEY)) { + builder.addOutboundTrafficSelectors(ts); + } + + builder.setLifetimeSeconds( + in.getInt(HARD_LIFETIME_SEC_KEY), in.getInt(SOFT_LIFETIME_SEC_KEY)); + final PersistableBundle configReqListBundle = in.getPersistableBundle(CONFIG_REQUESTS_KEY); + Objects.requireNonNull(configReqListBundle, "Config request list was null"); + final List<ConfigRequest> reqList = + PersistableBundleUtils.toList(configReqListBundle, ConfigRequest::new); + + boolean hasIpv4AddressReq = false; + boolean hasIpv4NetmaskReq = false; + for (ConfigRequest req : reqList) { + switch (req.type) { + case ConfigRequest.TYPE_IPV4_ADDRESS: + hasIpv4AddressReq = true; + if (req.address == null) { + builder.addInternalAddressRequest(AF_INET); + } else { + builder.addInternalAddressRequest((Inet4Address) req.address); + } + break; + case ConfigRequest.TYPE_IPV6_ADDRESS: + if (req.address == null) { + builder.addInternalAddressRequest(AF_INET6); + } else { + builder.addInternalAddressRequest( + (Inet6Address) req.address, req.ip6PrefixLen); + } + break; + case ConfigRequest.TYPE_IPV4_NETMASK: + // Do not need to set netmask because it will be automatically set by the + // builder when an IPv4 internal address request is set. + hasIpv4NetmaskReq = true; + break; + case ConfigRequest.TYPE_IPV4_DNS: + if (req.address != null) { + Log.w(TAG, "Requesting a specific IPv4 DNS server is unsupported"); + } + builder.addInternalDnsServerRequest(AF_INET); + break; + case ConfigRequest.TYPE_IPV6_DNS: + if (req.address != null) { + Log.w(TAG, "Requesting a specific IPv6 DNS server is unsupported"); + } + builder.addInternalDnsServerRequest(AF_INET6); + break; + case ConfigRequest.TYPE_IPV4_DHCP: + if (req.address != null) { + Log.w(TAG, "Requesting a specific IPv4 DHCP server is unsupported"); + } + builder.addInternalDhcpServerRequest(AF_INET); + break; + default: + throw new IllegalArgumentException( + "Unrecognized config request type: " + req.type); + } + } + + if (hasIpv4AddressReq != hasIpv4NetmaskReq) { + Log.w( + TAG, + String.format( + "Expect IPv4 address request and IPv4 netmask request either both" + + " exist or both absent, but found hasIpv4AddressReq exists? %b," + + " hasIpv4AddressReq exists? %b, ", + hasIpv4AddressReq, hasIpv4NetmaskReq)); + } + + return builder.build(); + } +} diff --git a/core/java/android/os/BatterySaverPolicyConfig.java b/core/java/android/os/BatterySaverPolicyConfig.java index 81c781bff06d..a999e658ce21 100644 --- a/core/java/android/os/BatterySaverPolicyConfig.java +++ b/core/java/android/os/BatterySaverPolicyConfig.java @@ -247,6 +247,7 @@ public final class BatterySaverPolicyConfig implements Parcelable { /** * Get the SoundTrigger mode while in Battery Saver. */ + @PowerManager.SoundTriggerPowerSaveMode public int getSoundTriggerMode() { return mSoundTriggerMode; } diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 74df1b2b9194..a5b0e8d149ef 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -1288,10 +1288,12 @@ public class Build { public static final String HOST = getString("ro.build.host"); /** - * Returns true if we are running a debug build such as "user-debug" or "eng". - * @hide + * Returns true if the device is running a debuggable build such as "userdebug" or "eng". + * + * Debuggable builds allow users to gain root access via local shell, attach debuggers to any + * application regardless of whether they have the "debuggable" attribute set, or downgrade + * selinux into "permissive" mode in particular. */ - @UnsupportedAppUsage public static final boolean IS_DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1; diff --git a/core/java/android/os/CombinedVibrationEffect.java b/core/java/android/os/CombinedVibrationEffect.java index c8e682c86ea7..e068772e954a 100644 --- a/core/java/android/os/CombinedVibrationEffect.java +++ b/core/java/android/os/CombinedVibrationEffect.java @@ -76,7 +76,9 @@ public abstract class CombinedVibrationEffect implements Parcelable { * A sequential vibration effect should be performed by multiple vibrators in order. * * @see CombinedVibrationEffect.SequentialCombination + * @hide */ + @TestApi @NonNull public static SequentialCombination startSequential() { return new SequentialCombination(); @@ -162,7 +164,9 @@ public abstract class CombinedVibrationEffect implements Parcelable { * A combination of haptic effects that should be played in multiple vibrators in sequence. * * @see CombinedVibrationEffect#startSequential() + * @hide */ + @TestApi public static final class SequentialCombination { private final ArrayList<CombinedVibrationEffect> mEffects = new ArrayList<>(); diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index f2d67411ef3a..91d6a9bf69cb 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -23,7 +23,6 @@ import android.net.ITetheringStatsProvider; import android.net.Network; import android.net.NetworkStats; import android.net.RouteInfo; -import android.net.UidRange; /** * @hide diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index 0587610630a6..03e5f1d59b86 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -503,6 +503,20 @@ public abstract class VibrationEffect implements Parcelable { } /** @hide */ + public static String effectStrengthToString(int effectStrength) { + switch (effectStrength) { + case EFFECT_STRENGTH_LIGHT: + return "LIGHT"; + case EFFECT_STRENGTH_MEDIUM: + return "MEDIUM"; + case EFFECT_STRENGTH_STRONG: + return "STRONG"; + default: + return Integer.toString(effectStrength); + } + } + + /** @hide */ @TestApi public static class OneShot extends VibrationEffect implements Parcelable { private final long mDuration; @@ -936,8 +950,8 @@ public abstract class VibrationEffect implements Parcelable { @Override public String toString() { - return "Prebaked{mEffectId=" + mEffectId - + ", mEffectStrength=" + mEffectStrength + return "Prebaked{mEffectId=" + effectIdToString(mEffectId) + + ", mEffectStrength=" + effectStrengthToString(mEffectStrength) + ", mFallback=" + mFallback + ", mFallbackEffect=" + mFallbackEffect + "}"; diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java index 07272e756e77..50d2de3da965 100644 --- a/core/java/android/os/VibratorInfo.java +++ b/core/java/android/os/VibratorInfo.java @@ -18,6 +18,7 @@ package android.os; import android.annotation.NonNull; import android.annotation.Nullable; +import android.hardware.vibrator.IVibrator; import android.util.SparseBooleanArray; import java.util.ArrayList; @@ -33,20 +34,7 @@ import java.util.Objects; * @hide */ public final class VibratorInfo implements Parcelable { - - /** - * Capability to set amplitude values to vibrations. - * @hide - */ - // Internally this maps to the HAL constant IVibrator::CAP_AMPLITUDE_CONTROL - public static final int CAPABILITY_AMPLITUDE_CONTROL = 4; - - /** - * Capability to compose primitives into a single effect. - * @hide - */ - // Internally this maps to the HAL constant IVibrator::CAP_COMPOSE_EFFECTS - public static final int CAPABILITY_COMPOSE_EFFECTS = 32; + private static final String TAG = "VibratorInfo"; private final int mId; private final long mCapabilities; @@ -108,7 +96,7 @@ public final class VibratorInfo implements Parcelable { return "VibratorInfo{" + "mId=" + mId + ", mCapabilities=" + Arrays.toString(getCapabilitiesNames()) - + ", mCapabilities flags=" + mCapabilities + + ", mCapabilities flags=" + Long.toBinaryString(mCapabilities) + ", mSupportedEffects=" + Arrays.toString(getSupportedEffectsNames()) + ", mSupportedPrimitives=" + Arrays.toString(getSupportedPrimitivesNames()) + '}'; @@ -125,7 +113,7 @@ public final class VibratorInfo implements Parcelable { * @return True if the hardware can control the amplitude of the vibrations, otherwise false. */ public boolean hasAmplitudeControl() { - return hasCapability(CAPABILITY_AMPLITUDE_CONTROL); + return hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL); } /** @@ -153,7 +141,7 @@ public final class VibratorInfo implements Parcelable { * @return Whether the primitive is supported. */ public boolean isPrimitiveSupported(@VibrationEffect.Composition.Primitive int primitiveId) { - return hasCapability(CAPABILITY_COMPOSE_EFFECTS) && mSupportedPrimitives != null + return hasCapability(IVibrator.CAP_COMPOSE_EFFECTS) && mSupportedPrimitives != null && mSupportedPrimitives.get(primitiveId, false); } @@ -170,12 +158,27 @@ public final class VibratorInfo implements Parcelable { private String[] getCapabilitiesNames() { List<String> names = new ArrayList<>(); - if (hasCapability(CAPABILITY_AMPLITUDE_CONTROL)) { - names.add("AMPLITUDE_CONTROL"); + if (hasCapability(IVibrator.CAP_ON_CALLBACK)) { + names.add("ON_CALLBACK"); + } + if (hasCapability(IVibrator.CAP_PERFORM_CALLBACK)) { + names.add("PERFORM_CALLBACK"); } - if (hasCapability(CAPABILITY_COMPOSE_EFFECTS)) { + if (hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) { names.add("COMPOSE_EFFECTS"); } + if (hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) { + names.add("ALWAYS_ON_CONTROL"); + } + if (hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) { + names.add("AMPLITUDE_CONTROL"); + } + if (hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) { + names.add("EXTERNAL_CONTROL"); + } + if (hasCapability(IVibrator.CAP_EXTERNAL_AMPLITUDE_CONTROL)) { + names.add("EXTERNAL_AMPLITUDE_CONTROL"); + } return names.toArray(new String[names.size()]); } diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index 592e98abae63..87dced8a3437 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -155,22 +155,21 @@ public final class IncrementalManager { } /** - * Set up an app's code path. The expected outcome of this method is: + * Link an app's files from the stage dir to the final installation location. + * The expected outcome of this method is: * 1) The actual apk directory under /data/incremental is bind-mounted to the parent directory * of {@code afterCodeFile}. * 2) All the files under {@code beforeCodeFile} will show up under {@code afterCodeFile}. * * @param beforeCodeFile Path that is currently bind-mounted and have APKs under it. - * Should no longer have any APKs after this method is called. * Example: /data/app/vmdl*tmp * @param afterCodeFile Path that should will have APKs after this method is called. Its parent * directory should be bind-mounted to a directory under /data/incremental. * Example: /data/app/~~[randomStringA]/[packageName]-[randomStringB] * @throws IllegalArgumentException * @throws IOException - * TODO(b/147371381): add unit tests */ - public void renameCodePath(File beforeCodeFile, File afterCodeFile) + public void linkCodePath(File beforeCodeFile, File afterCodeFile) throws IllegalArgumentException, IOException { final File beforeCodeAbsolute = beforeCodeFile.getAbsoluteFile(); final IncrementalStorage apkStorage = openStorage(beforeCodeAbsolute.toString()); @@ -188,7 +187,6 @@ public final class IncrementalManager { try { final String afterCodePathName = afterCodeFile.getName(); linkFiles(apkStorage, beforeCodeAbsolute, "", linkedApkStorage, afterCodePathName); - apkStorage.unBind(beforeCodeAbsolute.toString()); } catch (Exception e) { linkedApkStorage.unBind(targetStorageDir); throw e; diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl index 0041699df9ef..98b4e0b4f402 100644 --- a/core/java/android/os/storage/IStorageManager.aidl +++ b/core/java/android/os/storage/IStorageManager.aidl @@ -28,6 +28,8 @@ import android.os.storage.StorageVolume; import android.os.storage.VolumeInfo; import android.os.storage.VolumeRecord; import com.android.internal.os.AppFuseMount; +import android.app.PendingIntent; + /** * WARNING! Update IMountService.h and IMountService.cpp if you change this @@ -198,4 +200,5 @@ interface IStorageManager { void disableAppDataIsolation(in String pkgName, int pid, int userId) = 90; void notifyAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 91; void notifyAppIoResumed(in String volumeUuid, int uid, int tid, int reason) = 92; + PendingIntent getManageSpaceActivityIntent(in String packageName, int requestCode) = 93; } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 7c8874cc1ea7..c967deb5e810 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -49,6 +49,7 @@ import android.app.Activity; import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.content.Context; @@ -701,6 +702,33 @@ public class StorageManager { } } + /** + * Returns a {@link PendingIntent} that can be used by Apps with + * {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission + * to launch the manageSpaceActivity for any App that implements it, irrespective of its + * exported status. + * <p> + * Caller has the responsibility of supplying a valid packageName which has + * manageSpaceActivity implemented. + * + * @param packageName package name for the App for which manageSpaceActivity is to be launched + * @param requestCode for launching the activity + * @return PendingIntent to launch the manageSpaceActivity if successful, null if the + * packageName doesn't have a manageSpaceActivity. + * @throws IllegalArgumentException an invalid packageName is supplied. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) + @Nullable + public PendingIntent getManageSpaceActivityIntent( + @NonNull String packageName, int requestCode) { + try { + return mStorageManager.getManageSpaceActivityIntent(packageName, + requestCode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private ObbInfo getObbInfo(String canonicalPath) { try { final ObbInfo obbInfo = ObbScanner.getObbInfo(canonicalPath); @@ -2738,10 +2766,11 @@ public class StorageManager { * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public void notifyAppIoBlocked(@NonNull String volumeUuid, int uid, int tid, + public void notifyAppIoBlocked(@NonNull UUID volumeUuid, int uid, int tid, @AppIoBlockedReason int reason) { + Objects.requireNonNull(volumeUuid); try { - mStorageManager.notifyAppIoBlocked(volumeUuid, uid, tid, reason); + mStorageManager.notifyAppIoBlocked(convert(volumeUuid), uid, tid, reason); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2764,10 +2793,11 @@ public class StorageManager { * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public void notifyAppIoResumed(@NonNull String volumeUuid, int uid, int tid, + public void notifyAppIoResumed(@NonNull UUID volumeUuid, int uid, int tid, @AppIoBlockedReason int reason) { + Objects.requireNonNull(volumeUuid); try { - mStorageManager.notifyAppIoResumed(volumeUuid, uid, tid, reason); + mStorageManager.notifyAppIoResumed(convert(volumeUuid), uid, tid, reason); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java index b12bb2ece4c2..396ba2d3cea5 100644 --- a/core/java/android/os/storage/StorageManagerInternal.java +++ b/core/java/android/os/storage/StorageManagerInternal.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.IVold; +import java.util.List; import java.util.Set; /** @@ -112,4 +113,10 @@ public abstract class StorageManagerInternal { * @param bytes number of bytes which need to be freed */ public abstract void freeCache(@Nullable String volumeUuid, long bytes); + + /** + * Returns the {@link VolumeInfo#getId()} values for the volumes matching + * {@link VolumeInfo#isPrimary()} + */ + public abstract List<String> getPrimaryVolumeIds(); } diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS index b32346848a69..19a3a8bd514a 100644 --- a/core/java/android/permission/OWNERS +++ b/core/java/android/permission/OWNERS @@ -1,7 +1,13 @@ # Bug component: 137825 +eugenesusla@google.com evanseverson@google.com +evanxinchen@google.com +ewol@google.com +guojing@google.com +jaysullivan@google.com ntmyren@google.com -zhanghai@google.com svetoslavganov@android.com svetoslavganov@google.com +theianchen@google.com +zhanghai@google.com diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index 084b18eb2999..913b827332bf 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -668,7 +668,7 @@ public final class PermissionControllerManager { public void getPrivilegesDescriptionStringForProfile( @NonNull String profileName, @NonNull @CallbackExecutor Executor executor, - @NonNull Consumer<String> callback) { + @NonNull Consumer<CharSequence> callback) { mRemoteService.postAsync(service -> { AndroidFuture<String> future = new AndroidFuture<>(); service.getPrivilegesDescriptionStringForProfile(profileName, future); diff --git a/core/java/android/permissionpresenterservice/OWNERS b/core/java/android/permissionpresenterservice/OWNERS index b32346848a69..fb6099cf7e5a 100644 --- a/core/java/android/permissionpresenterservice/OWNERS +++ b/core/java/android/permissionpresenterservice/OWNERS @@ -1,7 +1,3 @@ # Bug component: 137825 -evanseverson@google.com -ntmyren@google.com -zhanghai@google.com -svetoslavganov@android.com -svetoslavganov@google.com +include platform/frameworks/base:/core/java/android/permission/OWNERS diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 6e89faf9c2ed..e9bbcc79e9df 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -505,6 +505,22 @@ public final class DeviceConfig { "connectivity_thermal_power_manager"; /** + * Namespace for all statsd java features that can be applied immediately. + * + * @hide + */ + @SystemApi + public static final String NAMESPACE_STATSD_JAVA = "statsd_java"; + + /** + * Namespace for all statsd java features that are applied on boot. + * + * @hide + */ + @SystemApi + public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot"; + + /** * Namespace for all statsd native features that can be applied immediately. * * @hide diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 7996f090b1a4..8a4812a42c8a 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -5302,5 +5302,13 @@ public final class Telephony { * @hide */ public static final String COLUMN_RCS_CONFIG = "rcs_config"; + + /** + * TelephonyProvider column name for VoIMS provisioning. Default is 0. + * <P>Type: INTEGER </P> + * + * @hide + */ + public static final String COLUMN_VOIMS_OPT_IN_STATUS = "voims_opt_in_status"; } } diff --git a/core/java/android/service/storage/ExternalStorageService.java b/core/java/android/service/storage/ExternalStorageService.java index 1e07a8748af9..bbe184bd1a8c 100644 --- a/core/java/android/service/storage/ExternalStorageService.java +++ b/core/java/android/service/storage/ExternalStorageService.java @@ -239,14 +239,13 @@ public abstract class ExternalStorageService extends Service { } @Override - public void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason, - RemoteCallback callback) throws RemoteException { + public void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason) + throws RemoteException { mHandler.post(() -> { try { onAnrDelayStarted(packageName, uid, tid, reason); - sendResult(packageName, null /* throwable */, callback); } catch (Throwable t) { - sendResult(packageName, t, callback); + // Ignored } }); } diff --git a/core/java/android/service/storage/IExternalStorageService.aidl b/core/java/android/service/storage/IExternalStorageService.aidl index ba98efa58f7c..0766b754e57d 100644 --- a/core/java/android/service/storage/IExternalStorageService.aidl +++ b/core/java/android/service/storage/IExternalStorageService.aidl @@ -32,6 +32,5 @@ oneway interface IExternalStorageService in RemoteCallback callback); void freeCache(@utf8InCpp String sessionId, in String volumeUuid, long bytes, in RemoteCallback callback); - void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason, - in RemoteCallback callback); + void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason); }
\ No newline at end of file diff --git a/core/java/android/util/OWNERS b/core/java/android/util/OWNERS index 14aa38682d2b..5425c214de1f 100644 --- a/core/java/android/util/OWNERS +++ b/core/java/android/util/OWNERS @@ -2,5 +2,7 @@ per-file FeatureFlagUtils.java = sbasi@google.com per-file FeatureFlagUtils.java = tmfang@google.com per-file FeatureFlagUtils.java = asapperstein@google.com -per-file TypedValue.java = file:/core/java/android/content/res/OWNERS per-file AttributeSet.java = file:/core/java/android/content/res/OWNERS +per-file TypedValue.java = file:/core/java/android/content/res/OWNERS + +per-file PackageUtils.java = file:/core/java/android/content/pm/OWNERS diff --git a/core/java/android/util/apk/OWNERS b/core/java/android/util/apk/OWNERS new file mode 100644 index 000000000000..52c95501e541 --- /dev/null +++ b/core/java/android/util/apk/OWNERS @@ -0,0 +1 @@ +include /core/java/android/content/pm/OWNERS diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 0499f39f2fe4..09452828057e 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -86,19 +86,12 @@ public final class AccessibilityInteractionController { // accessibility from hanging private static final long REQUEST_PREPARER_TIMEOUT_MS = 500; - // Callbacks should have the same configuration of the flags below to allow satisfying a pending - // node request on prefetch - private static final int FLAGS_AFFECTING_REPORTED_DATA = - AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS - | AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS; - private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList = new ArrayList<AccessibilityNodeInfo>(); private final Object mLock = new Object(); - @VisibleForTesting - public final PrivateHandler mHandler; + private final PrivateHandler mHandler; private final ViewRootImpl mViewRootImpl; @@ -121,9 +114,6 @@ public final class AccessibilityInteractionController { private AddNodeInfosForViewId mAddNodeInfosForViewId; @GuardedBy("mLock") - private ArrayList<Message> mPendingFindNodeByIdMessages; - - @GuardedBy("mLock") private int mNumActiveRequestPreparers; @GuardedBy("mLock") private List<MessageHolder> mMessagesWaitingForRequestPreparer; @@ -138,7 +128,6 @@ public final class AccessibilityInteractionController { mViewRootImpl = viewRootImpl; mPrefetcher = new AccessibilityNodePrefetcher(); mA11yManager = mViewRootImpl.mContext.getSystemService(AccessibilityManager.class); - mPendingFindNodeByIdMessages = new ArrayList<>(); } private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid, @@ -188,9 +177,6 @@ public final class AccessibilityInteractionController { args.arg4 = arguments; message.obj = args; - synchronized (mLock) { - mPendingFindNodeByIdMessages.add(message); - } scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS); } @@ -329,9 +315,6 @@ public final class AccessibilityInteractionController { } private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) { - synchronized (mLock) { - mPendingFindNodeByIdMessages.remove(message); - } final int flags = message.arg1; SomeArgs args = (SomeArgs) message.obj; @@ -346,58 +329,22 @@ public final class AccessibilityInteractionController { args.recycle(); - View rootView = null; - AccessibilityNodeInfo rootNode = null; + List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; + infos.clear(); try { if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { return; } mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; - rootView = findViewByAccessibilityId(accessibilityViewId); - if (rootView != null && isShown(rootView)) { - rootNode = populateAccessibilityNodeInfoForView( - rootView, arguments, virtualDescendantId); + final View root = findViewByAccessibilityId(accessibilityViewId); + if (root != null && isShown(root)) { + mPrefetcher.prefetchAccessibilityNodeInfos( + root, virtualDescendantId, flags, infos, arguments); } } finally { - updateInfoForViewportAndReturnFindNodeResult( - rootNode == null ? null : AccessibilityNodeInfo.obtain(rootNode), - callback, interactionId, spec, interactiveRegion); - } - ArrayList<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; - infos.clear(); - mPrefetcher.prefetchAccessibilityNodeInfos( - rootView, rootNode == null ? null : AccessibilityNodeInfo.obtain(rootNode), - virtualDescendantId, flags, infos); - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; - updateInfosForViewPort(infos, spec, interactiveRegion); - returnPrefetchResult(interactionId, infos, callback); - returnPendingFindAccessibilityNodeInfosInPrefetch(rootNode, infos, flags); - } - - private AccessibilityNodeInfo populateAccessibilityNodeInfoForView( - View view, Bundle arguments, int virtualViewId) { - AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); - // Determine if we'll be populating extra data - final String extraDataRequested = (arguments == null) ? null - : arguments.getString(EXTRA_DATA_REQUESTED_KEY); - AccessibilityNodeInfo root = null; - if (provider == null) { - root = view.createAccessibilityNodeInfo(); - if (root != null) { - if (extraDataRequested != null) { - view.addExtraDataToAccessibilityNodeInfo(root, extraDataRequested, arguments); - } - } - } else { - root = provider.createAccessibilityNodeInfo(virtualViewId); - if (root != null) { - if (extraDataRequested != null) { - provider.addExtraDataToAccessibilityNodeInfo( - virtualViewId, root, extraDataRequested, arguments); - } - } + updateInfosForViewportAndReturnFindNodeResult( + infos, callback, interactionId, spec, interactiveRegion); } - return root; } public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId, @@ -436,7 +383,8 @@ public final class AccessibilityInteractionController { final List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; infos.clear(); try { - if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { + if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null + || viewId == null) { return; } mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; @@ -455,7 +403,6 @@ public final class AccessibilityInteractionController { mAddNodeInfosForViewId.reset(); } } finally { - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; updateInfosForViewportAndReturnFindNodeResult( infos, callback, interactionId, spec, interactiveRegion); } @@ -538,7 +485,6 @@ public final class AccessibilityInteractionController { } } } finally { - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; updateInfosForViewportAndReturnFindNodeResult( infos, callback, interactionId, spec, interactiveRegion); } @@ -630,7 +576,6 @@ public final class AccessibilityInteractionController { } } } finally { - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; updateInfoForViewportAndReturnFindNodeResult( focused, callback, interactionId, spec, interactiveRegion); } @@ -685,7 +630,6 @@ public final class AccessibilityInteractionController { } } } finally { - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; updateInfoForViewportAndReturnFindNodeResult( next, callback, interactionId, spec, interactiveRegion); } @@ -842,6 +786,33 @@ public final class AccessibilityInteractionController { } } + private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos, + MagnificationSpec spec) { + if (infos == null) { + return; + } + final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; + if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) { + final int infoCount = infos.size(); + for (int i = 0; i < infoCount; i++) { + AccessibilityNodeInfo info = infos.get(i); + applyAppScaleAndMagnificationSpecIfNeeded(info, spec); + } + } + } + + private void adjustIsVisibleToUserIfNeeded(List<AccessibilityNodeInfo> infos, + Region interactiveRegion) { + if (interactiveRegion == null || infos == null) { + return; + } + final int infoCount = infos.size(); + for (int i = 0; i < infoCount; i++) { + AccessibilityNodeInfo info = infos.get(i); + adjustIsVisibleToUserIfNeeded(info, interactiveRegion); + } + } + private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info, Region interactiveRegion) { if (interactiveRegion == null || info == null) { @@ -862,6 +833,17 @@ public final class AccessibilityInteractionController { return false; } + private void adjustBoundsInScreenIfNeeded(List<AccessibilityNodeInfo> infos) { + if (infos == null || shouldBypassAdjustBoundsInScreen()) { + return; + } + final int infoCount = infos.size(); + for (int i = 0; i < infoCount; i++) { + final AccessibilityNodeInfo info = infos.get(i); + adjustBoundsInScreenIfNeeded(info); + } + } + private void adjustBoundsInScreenIfNeeded(AccessibilityNodeInfo info) { if (info == null || shouldBypassAdjustBoundsInScreen()) { return; @@ -909,6 +891,17 @@ public final class AccessibilityInteractionController { return screenMatrix == null || screenMatrix.isIdentity(); } + private void associateLeashedParentIfNeeded(List<AccessibilityNodeInfo> infos) { + if (infos == null || shouldBypassAssociateLeashedParent()) { + return; + } + final int infoCount = infos.size(); + for (int i = 0; i < infoCount; i++) { + final AccessibilityNodeInfo info = infos.get(i); + associateLeashedParentIfNeeded(info); + } + } + private void associateLeashedParentIfNeeded(AccessibilityNodeInfo info) { if (info == null || shouldBypassAssociateLeashedParent()) { return; @@ -982,46 +975,18 @@ public final class AccessibilityInteractionController { return (appScale != 1.0f || (spec != null && !spec.isNop())); } - private void updateInfosForViewPort(List<AccessibilityNodeInfo> infos, MagnificationSpec spec, - Region interactiveRegion) { - for (int i = 0; i < infos.size(); i++) { - updateInfoForViewPort(infos.get(i), spec, interactiveRegion); - } - } - - private void updateInfoForViewPort(AccessibilityNodeInfo info, MagnificationSpec spec, - Region interactiveRegion) { - associateLeashedParentIfNeeded(info); - applyScreenMatrixIfNeeded(info); - adjustBoundsInScreenIfNeeded(info); - // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node, - // then impact the visibility result, we need to adjust visibility before apply scale. - adjustIsVisibleToUserIfNeeded(info, interactiveRegion); - applyAppScaleAndMagnificationSpecIfNeeded(info, spec); - } - private void updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos, IAccessibilityInteractionConnectionCallback callback, int interactionId, MagnificationSpec spec, Region interactiveRegion) { - if (infos != null) { - updateInfosForViewPort(infos, spec, interactiveRegion); - } - returnFindNodesResult(infos, callback, interactionId); - } - - private void returnFindNodeResult(AccessibilityNodeInfo info, - IAccessibilityInteractionConnectionCallback callback, - int interactionId) { - try { - callback.setFindAccessibilityNodeInfoResult(info, interactionId); - } catch (RemoteException re) { - /* ignore - the other side will time out */ - } - } - - private void returnFindNodesResult(List<AccessibilityNodeInfo> infos, - IAccessibilityInteractionConnectionCallback callback, int interactionId) { try { + mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + associateLeashedParentIfNeeded(infos); + applyScreenMatrixIfNeeded(infos); + adjustBoundsInScreenIfNeeded(infos); + // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node, + // then impact the visibility result, we need to adjust visibility before apply scale. + adjustIsVisibleToUserIfNeeded(infos, interactiveRegion); + applyAppScaleAndMagnificationSpecIfNeeded(infos, spec); callback.setFindAccessibilityNodeInfosResult(infos, interactionId); if (infos != null) { infos.clear(); @@ -1031,80 +996,22 @@ public final class AccessibilityInteractionController { } } - private void returnPendingFindAccessibilityNodeInfosInPrefetch(AccessibilityNodeInfo rootNode, - List<AccessibilityNodeInfo> infos, int flags) { - - AccessibilityNodeInfo satisfiedPendingRequestPrefetchedNode = null; - IAccessibilityInteractionConnectionCallback satisfiedPendingRequestCallback = null; - int satisfiedPendingRequestInteractionId = AccessibilityInteractionClient.NO_ID; - - synchronized (mLock) { - for (int i = 0; i < mPendingFindNodeByIdMessages.size(); i++) { - final Message pendingMessage = mPendingFindNodeByIdMessages.get(i); - final int pendingFlags = pendingMessage.arg1; - if ((pendingFlags & FLAGS_AFFECTING_REPORTED_DATA) - != (flags & FLAGS_AFFECTING_REPORTED_DATA)) { - continue; - } - SomeArgs args = (SomeArgs) pendingMessage.obj; - final int accessibilityViewId = args.argi1; - final int virtualDescendantId = args.argi2; - - satisfiedPendingRequestPrefetchedNode = nodeWithIdFromList(rootNode, - infos, AccessibilityNodeInfo.makeNodeId( - accessibilityViewId, virtualDescendantId)); - - if (satisfiedPendingRequestPrefetchedNode != null) { - satisfiedPendingRequestCallback = - (IAccessibilityInteractionConnectionCallback) args.arg1; - satisfiedPendingRequestInteractionId = args.argi3; - mHandler.removeMessages( - PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID, - pendingMessage.obj); - args.recycle(); - break; - } - } - mPendingFindNodeByIdMessages.clear(); - } - - if (satisfiedPendingRequestPrefetchedNode != null) { - returnFindNodeResult( - AccessibilityNodeInfo.obtain(satisfiedPendingRequestPrefetchedNode), - satisfiedPendingRequestCallback, satisfiedPendingRequestInteractionId); - } - } - - private AccessibilityNodeInfo nodeWithIdFromList(AccessibilityNodeInfo rootNode, - List<AccessibilityNodeInfo> infos, long nodeId) { - if (rootNode != null && rootNode.getSourceNodeId() == nodeId) { - return rootNode; - } - for (int j = 0; j < infos.size(); j++) { - AccessibilityNodeInfo info = infos.get(j); - if (info.getSourceNodeId() == nodeId) { - return info; - } - } - return null; - } - - private void returnPrefetchResult(int interactionId, List<AccessibilityNodeInfo> infos, - IAccessibilityInteractionConnectionCallback callback) { - if (infos.size() > 0) { - try { - callback.setPrefetchAccessibilityNodeInfoResult(infos, interactionId); - } catch (RemoteException re) { - /* ignore - other side isn't too bothered if this doesn't arrive */ - } - } - } - private void updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info, IAccessibilityInteractionConnectionCallback callback, int interactionId, MagnificationSpec spec, Region interactiveRegion) { - updateInfoForViewPort(info, spec, interactiveRegion); - returnFindNodeResult(info, callback, interactionId); + try { + mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + associateLeashedParentIfNeeded(info); + applyScreenMatrixIfNeeded(info); + adjustBoundsInScreenIfNeeded(info); + // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node, + // then impact the visibility result, we need to adjust visibility before apply scale. + adjustIsVisibleToUserIfNeeded(info, interactiveRegion); + applyAppScaleAndMagnificationSpecIfNeeded(info, spec); + callback.setFindAccessibilityNodeInfoResult(info, interactionId); + } catch (RemoteException re) { + /* ignore - the other side will time out */ + } } private boolean handleClickableSpanActionUiThread( @@ -1147,45 +1054,56 @@ public final class AccessibilityInteractionController { private final ArrayList<View> mTempViewList = new ArrayList<View>(); - public void prefetchAccessibilityNodeInfos(View view, AccessibilityNodeInfo root, - int virtualViewId, int fetchFlags, List<AccessibilityNodeInfo> outInfos) { - if (root == null) { - return; - } + public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags, + List<AccessibilityNodeInfo> outInfos, Bundle arguments) { AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); + // Determine if we'll be populating extra data + final String extraDataRequested = (arguments == null) ? null + : arguments.getString(EXTRA_DATA_REQUESTED_KEY); if (provider == null) { - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { - prefetchPredecessorsOfRealNode(view, outInfos); - } - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { - prefetchSiblingsOfRealNode(view, outInfos); - } - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { - prefetchDescendantsOfRealNode(view, outInfos); + AccessibilityNodeInfo root = view.createAccessibilityNodeInfo(); + if (root != null) { + if (extraDataRequested != null) { + view.addExtraDataToAccessibilityNodeInfo( + root, extraDataRequested, arguments); + } + outInfos.add(root); + if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { + prefetchPredecessorsOfRealNode(view, outInfos); + } + if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { + prefetchSiblingsOfRealNode(view, outInfos); + } + if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { + prefetchDescendantsOfRealNode(view, outInfos); + } } } else { - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { - prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos); - } - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { - prefetchSiblingsOfVirtualNode(root, view, provider, outInfos); - } - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { - prefetchDescendantsOfVirtualNode(root, provider, outInfos); + final AccessibilityNodeInfo root = + provider.createAccessibilityNodeInfo(virtualViewId); + if (root != null) { + if (extraDataRequested != null) { + provider.addExtraDataToAccessibilityNodeInfo( + virtualViewId, root, extraDataRequested, arguments); + } + outInfos.add(root); + if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { + prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos); + } + if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { + prefetchSiblingsOfVirtualNode(root, view, provider, outInfos); + } + if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { + prefetchDescendantsOfVirtualNode(root, provider, outInfos); + } } } if (ENFORCE_NODE_TREE_CONSISTENT) { - enforceNodeTreeConsistent(root, outInfos); + enforceNodeTreeConsistent(outInfos); } } - private boolean shouldStopPrefetching(List prefetchededInfos) { - return mHandler.hasUserInteractiveMessagesWaiting() - || prefetchededInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE; - } - - private void enforceNodeTreeConsistent( - AccessibilityNodeInfo root, List<AccessibilityNodeInfo> nodes) { + private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) { LongSparseArray<AccessibilityNodeInfo> nodeMap = new LongSparseArray<AccessibilityNodeInfo>(); final int nodeCount = nodes.size(); @@ -1196,6 +1114,7 @@ public final class AccessibilityInteractionController { // If the nodes are a tree it does not matter from // which node we start to search for the root. + AccessibilityNodeInfo root = nodeMap.valueAt(0); AccessibilityNodeInfo parent = root; while (parent != null) { root = parent; @@ -1262,11 +1181,9 @@ public final class AccessibilityInteractionController { private void prefetchPredecessorsOfRealNode(View view, List<AccessibilityNodeInfo> outInfos) { - if (shouldStopPrefetching(outInfos)) { - return; - } ViewParent parent = view.getParentForAccessibility(); - while (parent instanceof View && !shouldStopPrefetching(outInfos)) { + while (parent instanceof View + && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { View parentView = (View) parent; AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo(); if (info != null) { @@ -1278,9 +1195,6 @@ public final class AccessibilityInteractionController { private void prefetchSiblingsOfRealNode(View current, List<AccessibilityNodeInfo> outInfos) { - if (shouldStopPrefetching(outInfos)) { - return; - } ViewParent parent = current.getParentForAccessibility(); if (parent instanceof ViewGroup) { ViewGroup parentGroup = (ViewGroup) parent; @@ -1290,7 +1204,7 @@ public final class AccessibilityInteractionController { parentGroup.addChildrenForAccessibility(children); final int childCount = children.size(); for (int i = 0; i < childCount; i++) { - if (shouldStopPrefetching(outInfos)) { + if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { return; } View child = children.get(i); @@ -1318,7 +1232,7 @@ public final class AccessibilityInteractionController { private void prefetchDescendantsOfRealNode(View root, List<AccessibilityNodeInfo> outInfos) { - if (shouldStopPrefetching(outInfos) || !(root instanceof ViewGroup)) { + if (!(root instanceof ViewGroup)) { return; } HashMap<View, AccessibilityNodeInfo> addedChildren = @@ -1329,7 +1243,7 @@ public final class AccessibilityInteractionController { root.addChildrenForAccessibility(children); final int childCount = children.size(); for (int i = 0; i < childCount; i++) { - if (shouldStopPrefetching(outInfos)) { + if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { return; } View child = children.get(i); @@ -1354,7 +1268,7 @@ public final class AccessibilityInteractionController { } finally { children.clear(); } - if (!shouldStopPrefetching(outInfos)) { + if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) { View addedChild = entry.getKey(); AccessibilityNodeInfo virtualRoot = entry.getValue(); @@ -1376,7 +1290,7 @@ public final class AccessibilityInteractionController { long parentNodeId = root.getParentNodeId(); int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { - if (shouldStopPrefetching(outInfos)) { + if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { return; } final int virtualDescendantId = @@ -1421,7 +1335,7 @@ public final class AccessibilityInteractionController { if (parent != null) { final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { - if (shouldStopPrefetching(outInfos)) { + if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { return; } final long childNodeId = parent.getChildId(i); @@ -1446,7 +1360,7 @@ public final class AccessibilityInteractionController { final int initialOutInfosSize = outInfos.size(); final int childCount = root.getChildCount(); for (int i = 0; i < childCount; i++) { - if (shouldStopPrefetching(outInfos)) { + if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { return; } final long childNodeId = root.getChildId(i); @@ -1456,7 +1370,7 @@ public final class AccessibilityInteractionController { outInfos.add(child); } } - if (!shouldStopPrefetching(outInfos)) { + if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { final int addedChildCount = outInfos.size() - initialOutInfosSize; for (int i = 0; i < addedChildCount; i++) { AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i); @@ -1565,10 +1479,6 @@ public final class AccessibilityInteractionController { boolean hasAccessibilityCallback(Message message) { return message.what < FIRST_NO_ACCESSIBILITY_CALLBACK_MSG ? true : false; } - - boolean hasUserInteractiveMessagesWaiting() { - return hasMessagesOrCallbacks(); - } } private final class AddNodeInfosForViewId implements Predicate<View> { diff --git a/core/java/android/view/AppTransitionAnimationSpec.java b/core/java/android/view/AppTransitionAnimationSpec.java index 877bb5684910..3215f2bc2a50 100644 --- a/core/java/android/view/AppTransitionAnimationSpec.java +++ b/core/java/android/view/AppTransitionAnimationSpec.java @@ -28,8 +28,8 @@ public class AppTransitionAnimationSpec implements Parcelable { public AppTransitionAnimationSpec(Parcel in) { taskId = in.readInt(); - rect = in.readParcelable(null); - buffer = in.readParcelable(null); + rect = in.readTypedObject(Rect.CREATOR); + buffer = in.readTypedObject(HardwareBuffer.CREATOR); } @Override @@ -40,8 +40,8 @@ public class AppTransitionAnimationSpec implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(taskId); - dest.writeParcelable(rect, 0 /* flags */); - dest.writeParcelable(buffer, 0); + dest.writeTypedObject(rect, 0 /* flags */); + dest.writeTypedObject(buffer, 0 /* flags */); } public static final @android.annotation.NonNull Parcelable.Creator<AppTransitionAnimationSpec> CREATOR diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index 2a00b5a2e513..655f42308a1f 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -810,6 +810,9 @@ public final class DisplayInfo implements Parcelable { if ((flags & Display.FLAG_TRUSTED) != 0) { result.append(", FLAG_TRUSTED"); } + if ((flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0) { + result.append(", FLAG_OWN_DISPLAY_GROUP"); + } return result.toString(); } } diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java index 4a5c95f43d46..d23a1e5992cc 100644 --- a/core/java/android/view/ImeFocusController.java +++ b/core/java/android/view/ImeFocusController.java @@ -116,8 +116,9 @@ public final class ImeFocusController { if (!hasWindowFocus || !mHasImeFocus || isInLocalFocusMode(windowAttribute)) { return; } + View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView; if (DEBUG) { - Log.v(TAG, "onWindowFocus: " + focusedView + Log.v(TAG, "onWindowFocus: " + viewForWindowFocus + " softInputMode=" + InputMethodDebug.softInputModeToString( windowAttribute.softInputMode)); } @@ -128,8 +129,8 @@ public final class ImeFocusController { if (DEBUG) Log.v(TAG, "Restarting due to isRestartOnNextWindowFocus as true"); forceFocus = true; } + // Update mNextServedView when focusedView changed. - final View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView; onViewFocusChanged(viewForWindowFocus, true); // Starting new input when the next focused view is same as served view but the currently diff --git a/core/java/android/view/InputEventAssigner.java b/core/java/android/view/InputEventAssigner.java new file mode 100644 index 000000000000..c159a127f4eb --- /dev/null +++ b/core/java/android/view/InputEventAssigner.java @@ -0,0 +1,92 @@ +/* + * 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 android.view; + +import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID; +import static android.view.InputDevice.SOURCE_TOUCHSCREEN; + +/** + * Process input events and assign input event id to a specific frame. + * + * The assigned input event id is determined by where the current gesture is relative to the vsync. + * In the middle of the gesture (we already processed some input events, and already received at + * least 1 vsync), the latest InputEvent is assigned to the next frame. + * If a gesture just started, then the ACTION_DOWN event will be assigned to the next frame. + * + * Consider the following sequence: + * DOWN -> VSYNC 1 -> MOVE 1 -> MOVE 2 -> VSYNC 2. + * + * For VSYNC 1, we will assign the "DOWN" input event. + * For VSYNC 2, we will assign the "MOVE 2" input event. + * + * Consider another sequence: + * DOWN -> MOVE 1 -> MOVE 2 -> VSYNC 1 -> MOVE 3 -> VSYNC 2. + * + * For VSYNC 1, we will still assign the "DOWN" input event. That means that "MOVE 1" and "MOVE 2" + * events are not attributed to any frame. + * For VSYNC 2, the "MOVE 3" input event will be assigned. + * + * @hide + */ +public class InputEventAssigner { + private static final String TAG = "InputEventAssigner"; + private boolean mHasUnprocessedDown = false; + private int mEventId = INVALID_INPUT_EVENT_ID; + + /** + * Notify InputEventAssigner that the Choreographer callback has been processed. This will reset + * the 'down' state to assign the latest input event to the current frame. + */ + public void onChoreographerCallback() { + // Mark completion of this frame. Use newest input event from now on. + mHasUnprocessedDown = false; + } + + /** + * Process the provided input event to determine which event id to assign to the current frame. + * @param event the input event currently being processed + * @return the id of the input event to use for the current frame + */ + public int processEvent(InputEvent event) { + if (event instanceof KeyEvent) { + // We will not do any special handling for key events + return event.getId(); + } + + if (event instanceof MotionEvent) { + MotionEvent motionEvent = (MotionEvent) event; + final int action = motionEvent.getActionMasked(); + + if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { + mHasUnprocessedDown = false; + } + if (motionEvent.isFromSource(SOURCE_TOUCHSCREEN) && action == MotionEvent.ACTION_DOWN) { + mHasUnprocessedDown = true; + mEventId = event.getId(); + // This will remain 'true' even if we receive a MOVE event, as long as choreographer + // hasn't invoked the 'CALLBACK_INPUT' callback. + } + // Don't update the event id if we haven't processed DOWN yet. + if (!mHasUnprocessedDown) { + mEventId = event.getId(); + } + return mEventId; + } + + throw new IllegalArgumentException("Received unexpected " + event); + } +} diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index d67439cc9de2..6801c27851a9 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -3589,6 +3589,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { msg.append(", deviceId=").append(getDeviceId()); msg.append(", source=0x").append(Integer.toHexString(getSource())); msg.append(", displayId=").append(getDisplayId()); + msg.append(", eventId=").append(getId()); } msg.append(" }"); return msg.toString(); diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index 5ce4c50cc85c..6c8753b95cc1 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -27,7 +27,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; -import android.widget.FrameLayout; +import android.widget.RelativeLayout; import android.widget.RemoteViews; import com.android.internal.R; @@ -42,7 +42,7 @@ import java.util.ArrayList; * @hide */ @RemoteViews.RemoteView -public class NotificationHeaderView extends FrameLayout { +public class NotificationHeaderView extends RelativeLayout { private final int mHeadingEndMargin; private final int mTouchableHeight; private OnClickListener mExpandClickListener; @@ -159,7 +159,7 @@ public class NotificationHeaderView extends FrameLayout { * @param extraMarginEnd extra margin in px */ public void setTopLineExtraMarginEnd(int extraMarginEnd) { - mTopLineView.setHeaderTextMarginEnd(extraMarginEnd + mHeadingEndMargin); + mTopLineView.setHeaderTextMarginEnd(extraMarginEnd); } /** @@ -181,12 +181,15 @@ public class NotificationHeaderView extends FrameLayout { * @return extra margin */ public int getTopLineExtraMarginEnd() { - return mTopLineView.getHeaderTextMarginEnd() - mHeadingEndMargin; + return mTopLineView.getHeaderTextMarginEnd(); } /** * Get the base margin at the end of the top line view. * Add this to {@link #getTopLineExtraMarginEnd()} to get the total margin of the top line. + * <p> + * NOTE: This method's result is only valid if the expander does not have a number. Currently + * only groups headers and conversations have numbers, so this is safe to use by MediaStyle. * * @return base margin */ diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS index e66b17aa4426..cbb86de4785f 100644 --- a/core/java/android/view/OWNERS +++ b/core/java/android/view/OWNERS @@ -57,6 +57,7 @@ per-file ViewRootImpl.java = file:/graphics/java/android/graphics/OWNERS per-file ViewRootImpl.java = file:/services/core/java/com/android/server/input/OWNERS per-file ViewRootImpl.java = file:/services/core/java/com/android/server/wm/OWNERS per-file ViewRootImpl.java = file:/core/java/android/view/inputmethod/OWNERS +per-file AccessibilityInteractionController.java = file:/services/accessibility/OWNERS # WindowManager per-file DisplayCutout.aidl = file:/services/core/java/com/android/server/wm/OWNERS diff --git a/core/java/android/view/RemoteAnimationDefinition.java b/core/java/android/view/RemoteAnimationDefinition.java index a5ff19ee3312..ea9799584e20 100644 --- a/core/java/android/view/RemoteAnimationDefinition.java +++ b/core/java/android/view/RemoteAnimationDefinition.java @@ -184,13 +184,13 @@ public class RemoteAnimationDefinition implements Parcelable { } private RemoteAnimationAdapterEntry(Parcel in) { - adapter = in.readParcelable(RemoteAnimationAdapter.class.getClassLoader()); + adapter = in.readTypedObject(RemoteAnimationAdapter.CREATOR); activityTypeFilter = in.readInt(); } @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeParcelable(adapter, flags); + dest.writeTypedObject(adapter, flags); dest.writeInt(activityTypeFilter); } diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java index 258a72cbcab4..b1b670f5e0c9 100644 --- a/core/java/android/view/RemoteAnimationTarget.java +++ b/core/java/android/view/RemoteAnimationTarget.java @@ -222,20 +222,20 @@ public class RemoteAnimationTarget implements Parcelable { public RemoteAnimationTarget(Parcel in) { taskId = in.readInt(); mode = in.readInt(); - leash = in.readParcelable(null); + leash = in.readTypedObject(SurfaceControl.CREATOR); isTranslucent = in.readBoolean(); - clipRect = in.readParcelable(null); - contentInsets = in.readParcelable(null); + clipRect = in.readTypedObject(Rect.CREATOR); + contentInsets = in.readTypedObject(Rect.CREATOR); prefixOrderIndex = in.readInt(); - position = in.readParcelable(null); - localBounds = in.readParcelable(null); - sourceContainerBounds = in.readParcelable(null); - screenSpaceBounds = in.readParcelable(null); - windowConfiguration = in.readParcelable(null); + position = in.readTypedObject(Point.CREATOR); + localBounds = in.readTypedObject(Rect.CREATOR); + sourceContainerBounds = in.readTypedObject(Rect.CREATOR); + screenSpaceBounds = in.readTypedObject(Rect.CREATOR); + windowConfiguration = in.readTypedObject(WindowConfiguration.CREATOR); isNotInRecents = in.readBoolean(); - startLeash = in.readParcelable(null); - startBounds = in.readParcelable(null); - pictureInPictureParams = in.readParcelable(null); + startLeash = in.readTypedObject(SurfaceControl.CREATOR); + startBounds = in.readTypedObject(Rect.CREATOR); + pictureInPictureParams = in.readTypedObject(PictureInPictureParams.CREATOR); } @Override @@ -247,20 +247,20 @@ public class RemoteAnimationTarget implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(taskId); dest.writeInt(mode); - dest.writeParcelable(leash, 0 /* flags */); + dest.writeTypedObject(leash, 0 /* flags */); dest.writeBoolean(isTranslucent); - dest.writeParcelable(clipRect, 0 /* flags */); - dest.writeParcelable(contentInsets, 0 /* flags */); + dest.writeTypedObject(clipRect, 0 /* flags */); + dest.writeTypedObject(contentInsets, 0 /* flags */); dest.writeInt(prefixOrderIndex); - dest.writeParcelable(position, 0 /* flags */); - dest.writeParcelable(localBounds, 0 /* flags */); - dest.writeParcelable(sourceContainerBounds, 0 /* flags */); - dest.writeParcelable(screenSpaceBounds, 0 /* flags */); - dest.writeParcelable(windowConfiguration, 0 /* flags */); + dest.writeTypedObject(position, 0 /* flags */); + dest.writeTypedObject(localBounds, 0 /* flags */); + dest.writeTypedObject(sourceContainerBounds, 0 /* flags */); + dest.writeTypedObject(screenSpaceBounds, 0 /* flags */); + dest.writeTypedObject(windowConfiguration, 0 /* flags */); dest.writeBoolean(isNotInRecents); - dest.writeParcelable(startLeash, 0 /* flags */); - dest.writeParcelable(startBounds, 0 /* flags */); - dest.writeParcelable(pictureInPictureParams, 0 /* flags */); + dest.writeTypedObject(startLeash, 0 /* flags */); + dest.writeTypedObject(startBounds, 0 /* flags */); + dest.writeTypedObject(pictureInPictureParams, 0 /* flags */); } public void dump(PrintWriter pw, String prefix) { diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index c2f17c310363..ec23a29b2070 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -310,6 +310,18 @@ public class ViewConfiguration { */ private static final float AMBIGUOUS_GESTURE_MULTIPLIER = 2f; + /** + * The timeout value in milliseconds to adjust the selection span and actions for the selected + * text when TextClassifier has been initialized. + */ + private static final int SMART_SELECTION_INITIALIZED_TIMEOUT_IN_MILLISECOND = 200; + + /** + * The timeout value in milliseconds to adjust the selection span and actions for the selected + * text when TextClassifier has not been initialized. + */ + private static final int SMART_SELECTION_INITIALIZING_TIMEOUT_IN_MILLISECOND = 500; + private final boolean mConstructedWithContext; private final int mEdgeSlop; private final int mFadingEdgeLength; @@ -335,6 +347,8 @@ public class ViewConfiguration { private final float mHorizontalScrollFactor; private final boolean mShowMenuShortcutsWhenKeyboardPresent; private final long mScreenshotChordKeyTimeout; + private final int mSmartSelectionInitializedTimeout; + private final int mSmartSelectionInitializingTimeout; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768915) private boolean sHasPermanentMenuKey; @@ -378,6 +392,8 @@ public class ViewConfiguration { // Getter throws if mConstructedWithContext is false so doesn't matter what // this value is. mMinScalingSpan = 0; + mSmartSelectionInitializedTimeout = SMART_SELECTION_INITIALIZED_TIMEOUT_IN_MILLISECOND; + mSmartSelectionInitializingTimeout = SMART_SELECTION_INITIALIZING_TIMEOUT_IN_MILLISECOND; } /** @@ -488,6 +504,11 @@ public class ViewConfiguration { mScreenshotChordKeyTimeout = res.getInteger( com.android.internal.R.integer.config_screenshotChordKeyTimeout); + + mSmartSelectionInitializedTimeout = res.getInteger( + com.android.internal.R.integer.config_smartSelectionInitializedTimeoutMillis); + mSmartSelectionInitializingTimeout = res.getInteger( + com.android.internal.R.integer.config_smartSelectionInitializingTimeoutMillis); } /** @@ -1069,6 +1090,24 @@ public class ViewConfiguration { } /** + * @return the timeout value in milliseconds to adjust the selection span and actions for the + * selected text when TextClassifier has been initialized. + * @hide + */ + public int getSmartSelectionInitializedTimeout() { + return mSmartSelectionInitializedTimeout; + } + + /** + * @return the timeout value in milliseconds to adjust the selection span and actions for the + * selected text when TextClassifier has not been initialized. + * @hide + */ + public int getSmartSelectionInitializingTimeout() { + return mSmartSelectionInitializingTimeout; + } + + /** * @return the duration in milliseconds before an end of a long press causes a tooltip to be * hidden * @hide diff --git a/core/java/android/view/ViewFrameInfo.java b/core/java/android/view/ViewFrameInfo.java index d4aaa611f800..36bf53201e6f 100644 --- a/core/java/android/view/ViewFrameInfo.java +++ b/core/java/android/view/ViewFrameInfo.java @@ -17,6 +17,7 @@ package android.view; import android.graphics.FrameInfo; +import android.os.IInputConstants; /** * The timing information of events taking place in ViewRootImpl @@ -24,32 +25,14 @@ import android.graphics.FrameInfo; */ public class ViewFrameInfo { public long drawStart; - public long oldestInputEventTime; // the time of the oldest input event consumed for this frame - public long newestInputEventTime; // the time of the newest input event consumed for this frame + + // Various flags set to provide extra metadata about the current frame. See flag definitions // inside FrameInfo. // @see android.graphics.FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED public long flags; - /** - * Update the oldest event time. - * @param eventTime the time of the input event - */ - public void updateOldestInputEvent(long eventTime) { - if (oldestInputEventTime == 0 || eventTime < oldestInputEventTime) { - oldestInputEventTime = eventTime; - } - } - - /** - * Update the newest event time. - * @param eventTime the time of the input event - */ - public void updateNewestInputEvent(long eventTime) { - if (newestInputEventTime == 0 || eventTime > newestInputEventTime) { - newestInputEventTime = eventTime; - } - } + private int mInputEventId; /** * Populate the missing fields using the data from ViewFrameInfo @@ -58,8 +41,7 @@ public class ViewFrameInfo { public void populateFrameInfo(FrameInfo frameInfo) { frameInfo.frameInfo[FrameInfo.FLAGS] |= flags; frameInfo.frameInfo[FrameInfo.DRAW_START] = drawStart; - // TODO(b/169866723): Use InputEventAssigner - frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID] = newestInputEventTime; + frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID] = mInputEventId; } /** @@ -67,8 +49,7 @@ public class ViewFrameInfo { */ public void reset() { drawStart = 0; - oldestInputEventTime = 0; - newestInputEventTime = 0; + mInputEventId = IInputConstants.INVALID_INPUT_EVENT_ID; flags = 0; } @@ -78,4 +59,12 @@ public class ViewFrameInfo { public void markDrawStart() { drawStart = System.nanoTime(); } + + /** + * Assign the value for input event id + * @param eventId the id of the input event + */ + public void setInputEvent(int eventId) { + mInputEventId = eventId; + } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index f8e65bd0d056..390e3ae78143 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -457,6 +457,7 @@ public final class ViewRootImpl implements ViewParent, FallbackEventHandler mFallbackEventHandler; final Choreographer mChoreographer; protected final ViewFrameInfo mViewFrameInfo = new ViewFrameInfo(); + private final InputEventAssigner mInputEventAssigner = new InputEventAssigner(); /** * Update the Choreographer's FrameInfo object with the timing information for the current @@ -8352,16 +8353,7 @@ public final class ViewRootImpl implements ViewParent, Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName, mPendingInputEventCount); - long eventTime = q.mEvent.getEventTimeNano(); - long oldestEventTime = eventTime; - if (q.mEvent instanceof MotionEvent) { - MotionEvent me = (MotionEvent)q.mEvent; - if (me.getHistorySize() > 0) { - oldestEventTime = me.getHistoricalEventTimeNano(0); - } - } - mViewFrameInfo.updateOldestInputEvent(oldestEventTime); - mViewFrameInfo.updateNewestInputEvent(eventTime); + mViewFrameInfo.setInputEvent(mInputEventAssigner.processEvent(q.mEvent)); deliverInputEvent(q); } @@ -8497,6 +8489,11 @@ public final class ViewRootImpl implements ViewParent, consumedBatches = false; } doProcessInputEvents(); + if (consumedBatches) { + // Must be done after we processed the input events, to mark the completion of the frame + // from the input point of view + mInputEventAssigner.onChoreographerCallback(); + } return consumedBatches; } diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 9556c25575cd..f63749be6df2 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -23,9 +23,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.os.Binder; import android.os.Build; import android.os.Bundle; -import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteException; @@ -115,8 +113,6 @@ public final class AccessibilityInteractionClient private final Object mInstanceLock = new Object(); - private Handler mMainHandler; - private volatile int mInteractionId = -1; private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult; @@ -127,11 +123,6 @@ public final class AccessibilityInteractionClient private Message mSameThreadMessage; - private int mInteractionIdWaitingForPrefetchResult; - private int mConnectionIdWaitingForPrefetchResult; - private String[] mPackageNamesForNextPrefetchResult; - private Runnable mPrefetchResultRunnable; - /** * @return The client for the current thread. */ @@ -206,9 +197,6 @@ public final class AccessibilityInteractionClient private AccessibilityInteractionClient() { /* reducing constructor visibility */ - if (Looper.getMainLooper() != null) { - mMainHandler = new Handler(Looper.getMainLooper()); - } } /** @@ -463,16 +451,16 @@ public final class AccessibilityInteractionClient Binder.restoreCallingIdentity(identityToken); } if (packageNames != null) { - AccessibilityNodeInfo info = - getFindAccessibilityNodeInfoResultAndClear(interactionId); - if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0 - && info != null) { - setInteractionWaitingForPrefetchResult(interactionId, connectionId, - packageNames); - } - finalizeAndCacheAccessibilityNodeInfo(info, connectionId, + List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( + interactionId); + finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, bypassCache, packageNames); - return info; + if (infos != null && !infos.isEmpty()) { + for (int i = 1; i < infos.size(); i++) { + infos.get(i).recycle(); + } + return infos.get(0); + } } } else { if (DEBUG) { @@ -486,15 +474,6 @@ public final class AccessibilityInteractionClient return null; } - private void setInteractionWaitingForPrefetchResult(int interactionId, int connectionId, - String[] packageNames) { - synchronized (mInstanceLock) { - mInteractionIdWaitingForPrefetchResult = interactionId; - mConnectionIdWaitingForPrefetchResult = connectionId; - mPackageNamesForNextPrefetchResult = packageNames; - } - } - private static String idToString(int accessibilityWindowId, long accessibilityNodeId) { return accessibilityWindowId + "/" + AccessibilityNodeInfo.idToString(accessibilityNodeId); @@ -850,60 +829,6 @@ public final class AccessibilityInteractionClient } /** - * {@inheritDoc} - */ - @Override - public void setPrefetchAccessibilityNodeInfoResult(@NonNull List<AccessibilityNodeInfo> infos, - int interactionId) { - List<AccessibilityNodeInfo> infosCopy = null; - int mConnectionIdWaitingForPrefetchResultCopy = -1; - String[] mPackageNamesForNextPrefetchResultCopy = null; - - synchronized (mInstanceLock) { - if (!infos.isEmpty() && mInteractionIdWaitingForPrefetchResult == interactionId) { - if (mMainHandler != null) { - if (mPrefetchResultRunnable != null) { - mMainHandler.removeCallbacks(mPrefetchResultRunnable); - mPrefetchResultRunnable = null; - } - /** - * TODO(b/180957109): AccessibilityCache is prone to deadlocks - * We post caching the prefetched nodes in the main thread. Using the binder - * thread results in "Long monitor contention with owner main" logs where - * service response times may exceed 5 seconds. This is due to the cache calling - * out to the system when refreshing nodes with the lock held. - */ - mPrefetchResultRunnable = () -> finalizeAndCacheAccessibilityNodeInfos( - infos, mConnectionIdWaitingForPrefetchResult, false, - mPackageNamesForNextPrefetchResult); - mMainHandler.post(mPrefetchResultRunnable); - - } else { - for (AccessibilityNodeInfo info : infos) { - infosCopy.add(new AccessibilityNodeInfo(info)); - } - mConnectionIdWaitingForPrefetchResultCopy = - mConnectionIdWaitingForPrefetchResult; - mPackageNamesForNextPrefetchResultCopy = - new String[mPackageNamesForNextPrefetchResult.length]; - for (int i = 0; i < mPackageNamesForNextPrefetchResult.length; i++) { - mPackageNamesForNextPrefetchResultCopy[i] = - mPackageNamesForNextPrefetchResult[i]; - - } - } - } - - } - - if (infosCopy != null) { - finalizeAndCacheAccessibilityNodeInfos( - infosCopy, mConnectionIdWaitingForPrefetchResultCopy, false, - mPackageNamesForNextPrefetchResultCopy); - } - } - - /** * Gets the result of a request to perform an accessibility action. * * @param interactionId The interaction id to match the result with the request. diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 97ce92cd90aa..ab46170792a5 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -1781,8 +1781,12 @@ public class AccessibilityNodeInfo implements Parcelable { * @param viewId The fully qualified resource name of the view id to find. * @return A list of node info. */ - public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(String viewId) { + public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(@NonNull String viewId) { enforceSealed(); + if (viewId == null) { + Log.e(TAG, "returns empty list due to null viewId."); + return Collections.emptyList(); + } if (!canPerformRequestOverConnection(mConnectionId, mWindowId, mSourceNodeId)) { return Collections.emptyList(); } diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl index 231e75a19a06..049bb31adbb1 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl @@ -47,15 +47,6 @@ oneway interface IAccessibilityInteractionConnectionCallback { int interactionId); /** - * Sets the result of a prefetch request that returns {@link AccessibilityNodeInfo}s. - * - * @param root The {@link AccessibilityNodeInfo} for which the prefetching is based off of. - * @param infos The result {@link AccessibilityNodeInfo}s. - */ - void setPrefetchAccessibilityNodeInfoResult( - in List<AccessibilityNodeInfo> infos, int interactionId); - - /** * Sets the result of a request to perform an accessibility action. * * @param Whether the action was performed. diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl index b1b443f919d9..a64111069c9b 100644 --- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl +++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl @@ -22,6 +22,7 @@ import android.view.contentcapture.ContentCaptureEvent; import android.view.contentcapture.DataRemovalRequest; import android.view.contentcapture.DataShareRequest; import android.view.contentcapture.IDataShareWriteAdapter; +import android.view.contentcapture.IContentCaptureOptionsCallback; import android.os.IBinder; import android.os.ICancellationSignal; @@ -101,4 +102,10 @@ oneway interface IContentCaptureManager { * Sets whether the default service should be used. */ void setDefaultServiceEnabled(int userId, boolean enabled); + + /** + * Registers a listener to handle updates ContentCaptureOptions from server. + */ + void registerContentCaptureOptionsCallback(String packageName, + in IContentCaptureOptionsCallback callback); } diff --git a/core/java/android/view/contentcapture/IContentCaptureOptionsCallback.aidl b/core/java/android/view/contentcapture/IContentCaptureOptionsCallback.aidl new file mode 100644 index 000000000000..b0f062de3bb9 --- /dev/null +++ b/core/java/android/view/contentcapture/IContentCaptureOptionsCallback.aidl @@ -0,0 +1,31 @@ +/** + * 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 android.view.contentcapture; + +import android.content.ContentCaptureOptions; + +/** + * Callback for changes to content capture options made by ContentCaptureService. + * Callback interface used by IContentCaptureManager to send asynchronous + * notifications back to its clients. Note that this is a + * one-way interface so the server does not block waiting for the client. + * + * @hide + */ +oneway interface IContentCaptureOptionsCallback { + void setContentCaptureOptions(in ContentCaptureOptions options); +} diff --git a/core/java/android/view/displayhash/DisplayHashResultCallback.java b/core/java/android/view/displayhash/DisplayHashResultCallback.java index 15b29adafddd..04d29ee3d48a 100644 --- a/core/java/android/view/displayhash/DisplayHashResultCallback.java +++ b/core/java/android/view/displayhash/DisplayHashResultCallback.java @@ -66,12 +66,19 @@ public interface DisplayHashResultCallback { */ int DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN = -4; + /** + * The hash algorithm sent to generate the hash was invalid. This means the value is not one + * of the supported values in {@link DisplayHashManager#getSupportedHashAlgorithms()} + */ + int DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM = -5; + /** @hide */ @IntDef(prefix = {"DISPLAY_HASH_ERROR_"}, value = { DISPLAY_HASH_ERROR_UNKNOWN, DISPLAY_HASH_ERROR_INVALID_BOUNDS, DISPLAY_HASH_ERROR_MISSING_WINDOW, - DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN + DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN, + DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM }) @Retention(RetentionPolicy.SOURCE) @interface DisplayHashErrorCode { diff --git a/core/java/android/view/textclassifier/TextClassificationConstants.java b/core/java/android/view/textclassifier/TextClassificationConstants.java index 2975afc084e6..d0959f97e438 100644 --- a/core/java/android/view/textclassifier/TextClassificationConstants.java +++ b/core/java/android/view/textclassifier/TextClassificationConstants.java @@ -90,6 +90,12 @@ public final class TextClassificationConstants { static final String SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND = "system_textclassifier_api_timeout_in_second"; + /** + * The max amount of characters before and after the selected text that are passed to the + * TextClassifier for the smart selection. + */ + private static final String SMART_SELECTION_TRIM_DELTA = "smart_selection_trim_delta"; + private static final String DEFAULT_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE = null; private static final boolean LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT = true; private static final boolean SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT = true; @@ -100,6 +106,7 @@ public final class TextClassificationConstants { private static final boolean SMART_SELECT_ANIMATION_ENABLED_DEFAULT = true; private static final int GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT = 100 * 1000; private static final long SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND_DEFAULT = 60; + private static final int SMART_SELECTION_TRIM_DELTA_DEFAULT = 120; @Nullable public String getTextClassifierServicePackageOverride() { @@ -155,6 +162,12 @@ public final class TextClassificationConstants { SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND_DEFAULT); } + public int getSmartSelectionTrimDelta() { + return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, + SMART_SELECTION_TRIM_DELTA, + SMART_SELECTION_TRIM_DELTA_DEFAULT); + } + void dump(IndentingPrintWriter pw) { pw.println("TextClassificationConstants:"); pw.increaseIndent(); @@ -170,6 +183,7 @@ public final class TextClassificationConstants { getTextClassifierServicePackageOverride()).println(); pw.print(SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND, getSystemTextClassifierApiTimeoutInSecond()).println(); + pw.print(SMART_SELECTION_TRIM_DELTA, getSmartSelectionTrimDelta()).println(); pw.decreaseIndent(); } }
\ No newline at end of file diff --git a/core/java/android/view/translation/ITranslationManager.aidl b/core/java/android/view/translation/ITranslationManager.aidl index e1754531d761..872e15e3d43e 100644 --- a/core/java/android/view/translation/ITranslationManager.aidl +++ b/core/java/android/view/translation/ITranslationManager.aidl @@ -36,6 +36,10 @@ oneway interface ITranslationManager { int sessionId, in IResultReceiver receiver, int userId); void updateUiTranslationState(int state, in TranslationSpec sourceSpec, - in TranslationSpec destSpec, in List<AutofillId> viewIds, in int taskId, + in TranslationSpec destSpec, in List<AutofillId> viewIds, IBinder token, int taskId, + int userId); + // deprecated + void updateUiTranslationStateByTaskId(int state, in TranslationSpec sourceSpec, + in TranslationSpec destSpec, in List<AutofillId> viewIds, int taskId, int userId); } diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java index eeb463ae0ed3..a3a6a2e52138 100644 --- a/core/java/android/view/translation/UiTranslationManager.java +++ b/core/java/android/view/translation/UiTranslationManager.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.app.assist.ActivityId; import android.content.Context; import android.os.RemoteException; import android.view.View; @@ -95,26 +96,61 @@ public final class UiTranslationManager { /** * Request ui translation for a given Views. * + * NOTE: Please use {@code startTranslation(TranslationSpec, TranslationSpec, List<AutofillId>, + * ActivityId)} instead. + * * @param sourceSpec {@link TranslationSpec} for the data to be translated. * @param destSpec {@link TranslationSpec} for the translated data. * @param viewIds A list of the {@link View}'s {@link AutofillId} which needs to be translated * @param taskId the Activity Task id which needs ui translation */ + // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull TranslationSpec sourceSpec, @NonNull TranslationSpec destSpec, @NonNull List<AutofillId> viewIds, int taskId) { - // TODO(b/177789967): Return result code or find a way to notify the status. - // TODO(b/177394471): The is a temparary API, the expected is requestUiTranslation( - // TranslationSpec, TranslationSpec,List<AutofillId>, Binder). We may need more time to - // implement it, use task id as initial version for demo. Objects.requireNonNull(sourceSpec); Objects.requireNonNull(destSpec); Objects.requireNonNull(viewIds); + if (viewIds.size() == 0) { + throw new IllegalArgumentException("Invalid empty views: " + viewIds); + } + try { + mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_STARTED, sourceSpec, + destSpec, viewIds, taskId, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** + * Request ui translation for a given Views. + * + * @param sourceSpec {@link TranslationSpec} for the data to be translated. + * @param destSpec {@link TranslationSpec} for the translated data. + * @param viewIds A list of the {@link View}'s {@link AutofillId} which needs to be translated + * @param activityId the identifier for the Activity which needs ui translation + * @throws IllegalArgumentException if the no {@link View}'s {@link AutofillId} in the list + * @throws NullPointerException the sourceSpec, destSpec, viewIds, activityId or + * {@link android.app.assist.ActivityId#getToken()} is {@code null} + */ + @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + public void startTranslation(@NonNull TranslationSpec sourceSpec, + @NonNull TranslationSpec destSpec, @NonNull List<AutofillId> viewIds, + @NonNull ActivityId activityId) { + // TODO(b/177789967): Return result code or find a way to notify the status. + Objects.requireNonNull(sourceSpec); + Objects.requireNonNull(destSpec); + Objects.requireNonNull(viewIds); + Objects.requireNonNull(activityId); + Objects.requireNonNull(activityId.getToken()); + if (viewIds.size() == 0) { + throw new IllegalArgumentException("Invalid empty views: " + viewIds); + } try { mService.updateUiTranslationState(STATE_UI_TRANSLATION_STARTED, sourceSpec, - destSpec, viewIds, taskId, mContext.getUserId()); + destSpec, viewIds, activityId.getToken(), activityId.getTaskId(), + mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -124,14 +160,15 @@ public final class UiTranslationManager { * Request to disable the ui translation. It will destroy all the {@link Translator}s and no * longer to show to show the translated text. * + * NOTE: Please use {@code finishTranslation(ActivityId)} instead. + * * @param taskId the Activity Task id which needs ui translation */ + // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(int taskId) { try { - // TODO(b/177394471): The is a temparary API, the expected is finishUiTranslation( - // Binder). We may need more time to implement it, use task id as initial version. - mService.updateUiTranslationState(STATE_UI_TRANSLATION_FINISHED, + mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_FINISHED, null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, taskId, mContext.getUserId()); } catch (RemoteException e) { @@ -140,17 +177,39 @@ public final class UiTranslationManager { } /** + * Request to disable the ui translation. It will destroy all the {@link Translator}s and no + * longer to show to show the translated text. + * + * @param activityId the identifier for the Activity which needs ui translation + * @throws NullPointerException the activityId or + * {@link android.app.assist.ActivityId#getToken()} is {@code null} + */ + @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + public void finishTranslation(@NonNull ActivityId activityId) { + try { + Objects.requireNonNull(activityId); + Objects.requireNonNull(activityId.getToken()); + mService.updateUiTranslationState(STATE_UI_TRANSLATION_FINISHED, + null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, + activityId.getToken(), activityId.getTaskId(), mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Request to pause the current ui translation's {@link Translator} which will switch back to * the original language. * + * NOTE: Please use {@code pauseTranslation(ActivityId)} instead. + * * @param taskId the Activity Task id which needs ui translation */ + // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(int taskId) { try { - // TODO(b/177394471): The is a temparary API, the expected is pauseUiTranslation(Binder) - // We may need more time to implement it, use task id as initial version for demo - mService.updateUiTranslationState(STATE_UI_TRANSLATION_PAUSED, + mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_PAUSED, null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, taskId, mContext.getUserId()); } catch (RemoteException e) { @@ -159,21 +218,64 @@ public final class UiTranslationManager { } /** + * Request to pause the current ui translation's {@link Translator} which will switch back to + * the original language. + * + * @param activityId the identifier for the Activity which needs ui translation + * @throws NullPointerException the activityId or + * {@link android.app.assist.ActivityId#getToken()} is {@code null} + */ + @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + public void pauseTranslation(@NonNull ActivityId activityId) { + try { + Objects.requireNonNull(activityId); + Objects.requireNonNull(activityId.getToken()); + mService.updateUiTranslationState(STATE_UI_TRANSLATION_PAUSED, + null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, + activityId.getToken(), activityId.getTaskId(), mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Request to resume the paused ui translation's {@link Translator} which will switch to the * translated language if the text had been translated. * + * NOTE: Please use {@code resumeTranslation(ActivityId)} instead. + * * @param taskId the Activity Task id which needs ui translation */ + // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(int taskId) { try { - // TODO(b/177394471): The is a temparary API, the expected is resumeUiTranslation( - // Binder). We may need more time to implement it, use task id as initial version. - mService.updateUiTranslationState(STATE_UI_TRANSLATION_RESUMED, + mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_RESUMED, null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, taskId, mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } + + /** + * Request to resume the paused ui translation's {@link Translator} which will switch to the + * translated language if the text had been translated. + * + * @param activityId the identifier for the Activity which needs ui translation + * @throws NullPointerException the activityId or + * {@link android.app.assist.ActivityId#getToken()} is {@code null} + */ + @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + public void resumeTranslation(@NonNull ActivityId activityId) { + try { + Objects.requireNonNull(activityId); + Objects.requireNonNull(activityId.getToken()); + mService.updateUiTranslationState(STATE_UI_TRANSLATION_RESUMED, + null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, + activityId.getToken(), activityId.getTaskId(), mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java index 6f189204434a..eb6bce4a2f59 100644 --- a/core/java/android/widget/SelectionActionModeHelper.java +++ b/core/java/android/widget/SelectionActionModeHelper.java @@ -36,6 +36,7 @@ import android.text.TextUtils; import android.text.util.Linkify; import android.util.Log; import android.view.ActionMode; +import android.view.ViewConfiguration; import android.view.textclassifier.ExtrasUtils; import android.view.textclassifier.SelectionEvent; import android.view.textclassifier.SelectionEvent.InvocationMethod; @@ -1056,10 +1057,12 @@ public final class SelectionActionModeHelper { */ private static final class TextClassificationHelper { - private static final int TRIM_DELTA = 120; // characters + // The fixed upper bound of context size. + private static final int TRIM_DELTA_UPPER_BOUND = 240; private final Context mContext; private Supplier<TextClassifier> mTextClassifier; + private final ViewConfiguration mViewConfiguration; /** The original TextView text. **/ private String mText; @@ -1088,12 +1091,13 @@ public final class SelectionActionModeHelper { private SelectionResult mLastClassificationResult; /** Whether the TextClassifier has been initialized. */ - private boolean mHot; + private boolean mInitialized; TextClassificationHelper(Context context, Supplier<TextClassifier> textClassifier, CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) { init(textClassifier, text, selectionStart, selectionEnd, locales); mContext = Objects.requireNonNull(context); + mViewConfiguration = ViewConfiguration.get(mContext); } @UiThread @@ -1110,13 +1114,13 @@ public final class SelectionActionModeHelper { @WorkerThread public SelectionResult classifyText() { - mHot = true; + mInitialized = true; return performClassification(null /* selection */); } @WorkerThread public SelectionResult suggestSelection() { - mHot = true; + mInitialized = true; trimText(); final TextSelection selection; if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) { @@ -1148,16 +1152,15 @@ public final class SelectionActionModeHelper { /** * Maximum time (in milliseconds) to wait for a textclassifier result before timing out. */ - // TODO: Consider making this a ViewConfiguration. public int getTimeoutDuration() { - if (mHot) { - return 200; + if (mInitialized) { + return mViewConfiguration.getSmartSelectionInitializedTimeout(); } else { // Return a slightly larger number than usual when the TextClassifier is first // initialized. Initialization would usually take longer than subsequent calls to // the TextClassifier. The impact of this on the UI is that we do not show the // selection handles or toolbar until after this timeout. - return 500; + return mViewConfiguration.getSmartSelectionInitializingTimeout(); } } @@ -1205,8 +1208,11 @@ public final class SelectionActionModeHelper { } private void trimText() { - mTrimStart = Math.max(0, mSelectionStart - TRIM_DELTA); - final int referenceEnd = Math.min(mText.length(), mSelectionEnd + TRIM_DELTA); + final int trimDelta = Math.min( + TextClassificationManager.getSettings(mContext).getSmartSelectionTrimDelta(), + TRIM_DELTA_UPPER_BOUND); + mTrimStart = Math.max(0, mSelectionStart - trimDelta); + final int referenceEnd = Math.min(mText.length(), mSelectionEnd + trimDelta); mTrimmedText = mText.subSequence(mTrimStart, referenceEnd); mRelativeStart = mSelectionStart - mTrimStart; mRelativeEnd = mSelectionEnd - mTrimStart; diff --git a/core/java/android/window/TaskSnapshot.java b/core/java/android/window/TaskSnapshot.java index dc07e44d4d98..f1e5fb95ea54 100644 --- a/core/java/android/window/TaskSnapshot.java +++ b/core/java/android/window/TaskSnapshot.java @@ -46,7 +46,7 @@ public class TaskSnapshot implements Parcelable { private final int mOrientation; /** See {@link android.view.Surface.Rotation} */ @Surface.Rotation - private int mRotation; + private final int mRotation; /** The size of the snapshot before scaling */ private final Point mTaskSize; private final Rect mContentInsets; @@ -90,15 +90,15 @@ public class TaskSnapshot implements Parcelable { private TaskSnapshot(Parcel source) { mId = source.readLong(); mTopActivityComponent = ComponentName.readFromParcel(source); - mSnapshot = source.readParcelable(null /* classLoader */); + mSnapshot = source.readTypedObject(HardwareBuffer.CREATOR); int colorSpaceId = source.readInt(); mColorSpace = colorSpaceId >= 0 && colorSpaceId < ColorSpace.Named.values().length ? ColorSpace.get(ColorSpace.Named.values()[colorSpaceId]) : ColorSpace.get(ColorSpace.Named.SRGB); mOrientation = source.readInt(); mRotation = source.readInt(); - mTaskSize = source.readParcelable(null /* classLoader */); - mContentInsets = source.readParcelable(null /* classLoader */); + mTaskSize = source.readTypedObject(Point.CREATOR); + mContentInsets = source.readTypedObject(Rect.CREATOR); mIsLowResolution = source.readBoolean(); mIsRealSnapshot = source.readBoolean(); mWindowingMode = source.readInt(); @@ -235,13 +235,12 @@ public class TaskSnapshot implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeLong(mId); ComponentName.writeToParcel(mTopActivityComponent, dest); - dest.writeParcelable(mSnapshot != null && !mSnapshot.isClosed() ? mSnapshot : null, - 0); + dest.writeTypedObject(mSnapshot != null && !mSnapshot.isClosed() ? mSnapshot : null, 0); dest.writeInt(mColorSpace.getId()); dest.writeInt(mOrientation); dest.writeInt(mRotation); - dest.writeParcelable(mTaskSize, 0); - dest.writeParcelable(mContentInsets, 0); + dest.writeTypedObject(mTaskSize, 0); + dest.writeTypedObject(mContentInsets, 0); dest.writeBoolean(mIsLowResolution); dest.writeBoolean(mIsRealSnapshot); dest.writeInt(mWindowingMode); diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl index 3a9f3b9c1128..eecd0cfe66a4 100644 --- a/core/java/com/android/internal/app/IAppOpsService.aidl +++ b/core/java/com/android/internal/app/IAppOpsService.aidl @@ -74,11 +74,11 @@ interface IAppOpsService { @UnsupportedAppUsage List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, in int[] ops); void getHistoricalOps(int uid, String packageName, String attributionTag, in List<String> ops, - int filter, long beginTimeMillis, long endTimeMillis, int flags, + int historyFlags, int filter, long beginTimeMillis, long endTimeMillis, int flags, in RemoteCallback callback); void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag, - in List<String> ops, int filter, long beginTimeMillis, long endTimeMillis, int flags, - in RemoteCallback callback); + in List<String> ops, int historyFlags, int filter, long beginTimeMillis, + long endTimeMillis, int flags, in RemoteCallback callback); void offsetHistory(long duration); void setHistoryParameters(int mode, long baseSnapshotInterval, int compressionStep); void addHistoricalOps(in AppOpsManager.HistoricalOps ops); diff --git a/core/java/com/android/internal/infra/GlobalWhitelistState.java b/core/java/com/android/internal/infra/GlobalWhitelistState.java index 3c081e27305c..7529536de66b 100644 --- a/core/java/com/android/internal/infra/GlobalWhitelistState.java +++ b/core/java/com/android/internal/infra/GlobalWhitelistState.java @@ -99,6 +99,18 @@ public class GlobalWhitelistState { } /** + * Gets packages that are either entirely allowlisted or have components that are allowlisted + * for the given user. + */ + public ArraySet<String> getWhitelistedPackages(@UserIdInt int userId) { + synchronized (mGlobalWhitelistStateLock) { + if (mWhitelisterHelpers == null) return null; + final WhitelistHelper helper = mWhitelisterHelpers.get(userId); + return helper == null ? null : helper.getWhitelistedPackages(); + } + } + + /** * Resets the allowlist for the given user. */ public void resetWhitelist(@NonNull int userId) { diff --git a/core/java/com/android/internal/infra/WhitelistHelper.java b/core/java/com/android/internal/infra/WhitelistHelper.java index 1d76090f59f3..3e93106822a2 100644 --- a/core/java/com/android/internal/infra/WhitelistHelper.java +++ b/core/java/com/android/internal/infra/WhitelistHelper.java @@ -140,6 +140,15 @@ public final class WhitelistHelper { return mWhitelistedPackages == null ? null : mWhitelistedPackages.get(packageName); } + /** + * Returns a set of all packages that are either entirely allowlisted or have components that + * are allowlisted. + */ + @Nullable + public ArraySet<String> getWhitelistedPackages() { + return mWhitelistedPackages == null ? null : new ArraySet<>(mWhitelistedPackages.keySet()); + } + @Override public String toString() { return "WhitelistHelper[" + mWhitelistedPackages + ']'; diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java index c353de88dc3b..93374ba0cf46 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java +++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java @@ -228,6 +228,8 @@ public final class InputMethodDebug { return "HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR"; case SoftInputShowHideReason.HIDE_REMOVE_CLIENT: return "HIDE_REMOVE_CLIENT"; + case SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY: + return "SHOW_RESTORE_IME_VISIBILITY"; default: return "Unknown=" + reason; } diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java index 1553e2eb0793..f1cdf2b38c4c 100644 --- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java +++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java @@ -49,7 +49,8 @@ import java.lang.annotation.Retention; SoftInputShowHideReason.HIDE_RECENTS_ANIMATION, SoftInputShowHideReason.HIDE_BUBBLES, SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR, - SoftInputShowHideReason.HIDE_REMOVE_CLIENT}) + SoftInputShowHideReason.HIDE_REMOVE_CLIENT, + SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY}) public @interface SoftInputShowHideReason { /** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */ int SHOW_SOFT_INPUT = 0; @@ -167,4 +168,10 @@ public @interface SoftInputShowHideReason { * Hide soft input when a {@link com.android.internal.view.IInputMethodClient} is removed. */ int HIDE_REMOVE_CLIENT = 21; + + /** + * Show soft input when the system invoking + * {@link com.android.server.wm.WindowManagerInternal#shouldRestoreImeVisibility}. + */ + int SHOW_RESTORE_IME_VISIBILITY = 22; } diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index 342456a58091..db0b48e130a3 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -203,6 +203,9 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId); mCancelled = true; removeObservers(); + if (mListener != null) { + mListener.onNotifyCujEvents(mSession, InteractionJankMonitor.ACTION_SESSION_CANCEL); + } } @Override @@ -324,11 +327,8 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } if (info.surfaceControlCallbackFired) { totalFramesCount++; - - // Only count missed frames if it's not stuffed. if ((info.jankType & PREDICTION_ERROR) != 0 - || ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0 - && (info.jankType & BUFFER_STUFFING) == 0)) { + || ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0)) { Log.w(TAG, "Missed App frame:" + info.jankType); missedAppFramesCount++; } diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 0294ec398484..fbc92c1f99c4 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -99,7 +99,7 @@ public class InteractionJankMonitor { private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64; public static final String ACTION_SESSION_BEGIN = ACTION_PREFIX + ".ACTION_SESSION_BEGIN"; - public static final String ACTION_SESSION_END = ACTION_PREFIX + ".ACTION_SESSION_END"; + public static final String ACTION_SESSION_CANCEL = ACTION_PREFIX + ".ACTION_SESSION_CANCEL"; public static final String ACTION_METRICS_LOGGED = ACTION_PREFIX + ".ACTION_METRICS_LOGGED"; public static final String BUNDLE_KEY_CUJ_NAME = ACTION_PREFIX + ".CUJ_NAME"; public static final String BUNDLE_KEY_TIMESTAMP = ACTION_PREFIX + ".TIMESTAMP"; diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index 1b1e0bfb3a58..8ecc80946141 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -32,7 +32,6 @@ import android.app.Person; import android.app.RemoteInputHistoryItem; import android.content.Context; import android.content.res.ColorStateList; -import android.graphics.Color; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.GradientDrawable; @@ -62,11 +61,9 @@ import android.widget.RemoteViews; import android.widget.TextView; import com.android.internal.R; -import com.android.internal.graphics.ColorUtils; import java.util.ArrayList; import java.util.List; -import java.util.Locale; import java.util.Objects; import java.util.function.Consumer; @@ -118,7 +115,6 @@ public class ConversationLayout extends FrameLayout private ViewGroup mExpandButtonAndContentContainer; private NotificationExpandButton mExpandButton; private MessagingLinearLayout mImageMessageContainer; - private int mExpandButtonExpandedTopMargin; private int mBadgedSideMargins; private int mConversationAvatarSize; private int mConversationAvatarSizeExpanded; @@ -147,7 +143,6 @@ public class ConversationLayout extends FrameLayout private int mFacePileProtectionWidth; private int mFacePileProtectionWidthExpanded; private boolean mImportantConversation; - private TextView mUnreadBadge; private View mFeedbackIcon; private float mMinTouchSize; private Icon mConversationIcon; @@ -245,8 +240,6 @@ public class ConversationLayout extends FrameLayout mContentContainer = findViewById(R.id.notification_action_list_margin_target); mExpandButtonAndContentContainer = findViewById(R.id.expand_button_and_content_container); mExpandButton = findViewById(R.id.expand_button); - mExpandButtonExpandedTopMargin = getResources().getDimensionPixelSize( - R.dimen.conversation_expand_button_top_margin_expanded); mNotificationHeaderExpandedPadding = getResources().getDimensionPixelSize( R.dimen.conversation_header_expanded_padding_end); mContentMarginEnd = getResources().getDimensionPixelSize( @@ -286,7 +279,6 @@ public class ConversationLayout extends FrameLayout mAppName.setOnVisibilityChangedListener((visibility) -> { onAppNameVisibilityChanged(); }); - mUnreadBadge = findViewById(R.id.conversation_unread_count); mConversationContentStart = getResources().getDimensionPixelSize( R.dimen.conversation_content_start); mInternalButtonPadding @@ -426,17 +418,7 @@ public class ConversationLayout extends FrameLayout /** @hide */ public void setUnreadCount(int unreadCount) { - boolean visible = mIsCollapsed && unreadCount > 1; - mUnreadBadge.setVisibility(visible ? VISIBLE : GONE); - if (visible) { - CharSequence text = unreadCount >= 100 - ? getResources().getString(R.string.unread_convo_overflow, 99) - : String.format(Locale.getDefault(), "%d", unreadCount); - mUnreadBadge.setText(text); - mUnreadBadge.setBackgroundTintList(ColorStateList.valueOf(mLayoutColor)); - boolean needDarkText = ColorUtils.calculateLuminance(mLayoutColor) > 0.5f; - mUnreadBadge.setTextColor(needDarkText ? Color.BLACK : Color.WHITE); - } + mExpandButton.setNumber(unreadCount); } private void addRemoteInputHistoryToMessages( @@ -1132,15 +1114,16 @@ public class ConversationLayout extends FrameLayout } private void updateExpandButton() { - int gravity; - int topMargin = 0; + int buttonGravity; + int containerHeight; ViewGroup newContainer; if (mIsCollapsed) { - gravity = Gravity.CENTER; + buttonGravity = Gravity.CENTER; + containerHeight = ViewGroup.LayoutParams.WRAP_CONTENT; newContainer = mExpandButtonAndContentContainer; } else { - gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; - topMargin = mExpandButtonExpandedTopMargin; + buttonGravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; + containerHeight = ViewGroup.LayoutParams.MATCH_PARENT; newContainer = this; } mExpandButton.setExpanded(!mIsCollapsed); @@ -1149,14 +1132,14 @@ public class ConversationLayout extends FrameLayout // content when collapsed, but allows the content to flow under it when expanded. if (newContainer != mExpandButtonContainer.getParent()) { ((ViewGroup) mExpandButtonContainer.getParent()).removeView(mExpandButtonContainer); + mExpandButtonContainer.getLayoutParams().height = containerHeight; newContainer.addView(mExpandButtonContainer); } // update if the expand button is centered LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mExpandButton.getLayoutParams(); - layoutParams.gravity = gravity; - layoutParams.topMargin = topMargin; + layoutParams.gravity = buttonGravity; mExpandButton.setLayoutParams(layoutParams); } @@ -1210,6 +1193,7 @@ public class ConversationLayout extends FrameLayout mExpandButtonContainer.setVisibility(GONE); mConversationIconContainer.setOnClickListener(null); } + mExpandButton.setVisibility(VISIBLE); updateContentEndPaddings(); } diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java index 8add34f328bf..fc4cc5764eaf 100644 --- a/core/java/com/android/internal/widget/NotificationExpandButton.java +++ b/core/java/com/android/internal/widget/NotificationExpandButton.java @@ -16,30 +16,42 @@ package com.android.internal.widget; -import static com.android.internal.widget.ColoredIconHelper.applyGrayTint; - +import android.annotation.ColorInt; import android.annotation.Nullable; import android.content.Context; +import android.content.res.ColorStateList; import android.graphics.Rect; import android.util.AttributeSet; import android.view.RemotableViewMethod; +import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.Button; +import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.RemoteViews; +import android.widget.TextView; import com.android.internal.R; +import java.util.Locale; + /** * An expand button in a notification */ @RemoteViews.RemoteView -public class NotificationExpandButton extends ImageView { +public class NotificationExpandButton extends FrameLayout { - private final int mMinTouchTargetSize; + private View mPillView; + private TextView mNumberView; + private ImageView mIconView; private boolean mExpanded; - private int mOriginalNotificationColor; + private int mNumber; + private int mDefaultPillColor; + private int mDefaultTextColor; + private int mHighlightPillColor; + private int mHighlightTextColor; + private boolean mDisallowColor; public NotificationExpandButton(Context context) { this(context, null, 0, 0); @@ -57,7 +69,14 @@ public class NotificationExpandButton extends ImageView { public NotificationExpandButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - mMinTouchTargetSize = (int) (getResources().getDisplayMetrics().density * 48 + 0.5); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mPillView = findViewById(R.id.expand_button_pill); + mNumberView = findViewById(R.id.expand_button_number); + mIconView = findViewById(R.id.expand_button_icon); } /** @@ -72,7 +91,6 @@ public class NotificationExpandButton extends ImageView { } else { super.getBoundsOnScreen(outRect, clipToParent); } - extendRectToMinTouchSize(outRect); } /** @@ -89,32 +107,12 @@ public class NotificationExpandButton extends ImageView { return super.pointInView(localX, localY, slop); } - @RemotableViewMethod - public void setOriginalNotificationColor(int color) { - mOriginalNotificationColor = color; - } - - public int getOriginalNotificationColor() { - return mOriginalNotificationColor; - } - /** - * Set the button's color filter: to gray if true, otherwise colored. - * If this button has no original color, this has no effect. + * Disable the use of the accent colors for this view, if true. */ public void setGrayedOut(boolean shouldApply) { - applyGrayTint(mContext, getDrawable(), shouldApply, mOriginalNotificationColor); - } - - private void extendRectToMinTouchSize(Rect rect) { - if (rect.width() < mMinTouchTargetSize) { - rect.left = rect.centerX() - mMinTouchTargetSize / 2; - rect.right = rect.left + mMinTouchTargetSize; - } - if (rect.height() < mMinTouchTargetSize) { - rect.top = rect.centerY() - mMinTouchTargetSize / 2; - rect.bottom = rect.top + mMinTouchTargetSize; - } + mDisallowColor = shouldApply; + updateColors(); } @Override @@ -129,10 +127,10 @@ public class NotificationExpandButton extends ImageView { @RemotableViewMethod public void setExpanded(boolean expanded) { mExpanded = expanded; - updateExpandButton(); + updateExpandedState(); } - private void updateExpandButton() { + private void updateExpandedState() { int drawableId; int contentDescriptionId; if (mExpanded) { @@ -142,8 +140,89 @@ public class NotificationExpandButton extends ImageView { drawableId = R.drawable.ic_expand_notification; contentDescriptionId = R.string.expand_button_content_description_collapsed; } - setImageDrawable(getContext().getDrawable(drawableId)); - setColorFilter(mOriginalNotificationColor); setContentDescription(mContext.getText(contentDescriptionId)); + mIconView.setImageDrawable(getContext().getDrawable(drawableId)); + + // changing the expanded state can affect the number display + updateNumber(); + } + + private void updateNumber() { + if (shouldShowNumber()) { + CharSequence text = mNumber >= 100 + ? getResources().getString(R.string.unread_convo_overflow, 99) + : String.format(Locale.getDefault(), "%d", mNumber); + mNumberView.setText(text); + mNumberView.setVisibility(VISIBLE); + } else { + mNumberView.setVisibility(GONE); + } + + // changing number can affect the color + updateColors(); + } + + private void updateColors() { + if (shouldShowNumber() && !mDisallowColor) { + mPillView.setBackgroundTintList(ColorStateList.valueOf(mHighlightPillColor)); + mIconView.setColorFilter(mHighlightTextColor); + mNumberView.setTextColor(mHighlightTextColor); + } else { + mPillView.setBackgroundTintList(ColorStateList.valueOf(mDefaultPillColor)); + mIconView.setColorFilter(mDefaultTextColor); + mNumberView.setTextColor(mDefaultTextColor); + } + } + + private boolean shouldShowNumber() { + return !mExpanded && mNumber > 1; + } + + /** + * Set the color used for the expand chevron and the text + */ + @RemotableViewMethod + public void setDefaultTextColor(int color) { + mDefaultTextColor = color; + updateColors(); + } + + /** + * Sets the color used to for the expander when there is no number shown + */ + @RemotableViewMethod + public void setDefaultPillColor(@ColorInt int color) { + mDefaultPillColor = color; + updateColors(); + } + + /** + * Set the color used for the expand chevron and the text + */ + @RemotableViewMethod + public void setHighlightTextColor(int color) { + mHighlightTextColor = color; + updateColors(); + } + + /** + * Sets the color used to highlight the expander when there is a number shown + */ + @RemotableViewMethod + public void setHighlightPillColor(@ColorInt int color) { + mHighlightPillColor = color; + updateColors(); + } + + /** + * Sets the number shown inside the expand button. + * This only appears when the expand button is collapsed, and when greater than 1. + */ + @RemotableViewMethod + public void setNumber(int number) { + if (mNumber != number) { + mNumber = number; + updateNumber(); + } } } diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 58df2be2b944..bac6bbe43c91 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -1234,8 +1234,7 @@ public class SystemConfig { final int incrementalVersion = IncrementalManager.getVersion(); if (incrementalVersion > 0) { - addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY, 0); - addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY_VERSION, incrementalVersion); + addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY, incrementalVersion); } if (PackageManager.APP_ENUMERATION_ENABLED_BY_DEFAULT) { diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp index 1c78750f3610..5e142fd10de0 100644 --- a/core/jni/android_view_InputEventReceiver.cpp +++ b/core/jni/android_view_InputEventReceiver.cpp @@ -45,6 +45,12 @@ static const char* toString(bool value) { return value ? "true" : "false"; } +enum class HandleEventResponse : int { + // Allowed return values of 'handleEvent' function as documented in LooperCallback::handleEvent + REMOVE_CALLBACK = 0, + KEEP_CALLBACK = 1 +}; + static struct { jclass clazz; @@ -70,6 +76,14 @@ static std::string addPrefix(std::string str, std::string_view prefix) { return str; } +/** + * Convert an enumeration to its underlying type. Replace with std::to_underlying when available. + */ +template <class T> +static std::underlying_type_t<T> toUnderlying(const T& t) { + return static_cast<std::underlying_type_t<T>>(t); +} + class NativeInputEventReceiver : public LooperCallback { public: NativeInputEventReceiver(JNIEnv* env, jobject receiverWeak, @@ -106,9 +120,16 @@ private: return mInputConsumer.getChannel()->getName(); } - virtual int handleEvent(int receiveFd, int events, void* data) override; + HandleEventResponse processOutboundEvents(); + // From 'LooperCallback' + int handleEvent(int receiveFd, int events, void* data) override; }; +// Ensure HandleEventResponse underlying type matches the return type of LooperCallback::handleEvent +static_assert(std::is_same<std::underlying_type_t<HandleEventResponse>, + std::invoke_result_t<decltype(&LooperCallback::handleEvent), + NativeInputEventReceiver, int, int, void*>>::value); + NativeInputEventReceiver::NativeInputEventReceiver( JNIEnv* env, jobject receiverWeak, const std::shared_ptr<InputChannel>& inputChannel, const sp<MessageQueue>& messageQueue) @@ -179,10 +200,61 @@ void NativeInputEventReceiver::setFdEvents(int events) { } } +/** + * Receiver's primary role is to receive input events, but it has an additional duty of sending + * 'ack' for events (using the call 'finishInputEvent'). + * + * If we are looking at the communication between InputPublisher and InputConsumer, we can say that + * from the InputConsumer's perspective, InputMessage's that are sent from publisher to consumer are + * called 'inbound / incoming' events, and the InputMessage's sent from InputConsumer to + * InputPublisher are 'outbound / outgoing' events. + * + * NativeInputEventReceiver owns (and acts like) an InputConsumer. So the finish events are outbound + * from InputEventReceiver (and will be sent to the InputPublisher). + * + * In this function, send as many events from 'mFinishQueue' as possible across the socket to the + * InputPublisher. If no events are remaining, let the looper know so that it doesn't wake up + * unnecessarily. + */ +HandleEventResponse NativeInputEventReceiver::processOutboundEvents() { + while (!mFinishQueue.empty()) { + const Finish& finish = *mFinishQueue.begin(); + status_t status = mInputConsumer.sendFinishedSignal(finish.seq, finish.handled); + if (status == OK) { + // Successful send. Erase the entry and keep trying to send more + mFinishQueue.erase(mFinishQueue.begin()); + continue; + } + + // Publisher is busy, try again later. Keep this entry (do not erase) + if (status == WOULD_BLOCK) { + if (kDebugDispatchCycle) { + ALOGD("channel '%s' ~ Remaining outbound events: %zu.", + getInputChannelName().c_str(), mFinishQueue.size()); + } + return HandleEventResponse::KEEP_CALLBACK; // try again later + } + + // Some other error. Give up + ALOGW("Failed to send outbound event on channel '%s'. status=%d", + getInputChannelName().c_str(), status); + if (status != DEAD_OBJECT) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + std::string message = + android::base::StringPrintf("Failed to send outbound event. status=%d", + status); + jniThrowRuntimeException(env, message.c_str()); + mMessageQueue->raiseAndClearException(env, "finishInputEvent"); + } + return HandleEventResponse::REMOVE_CALLBACK; + } + + // The queue is now empty. Tell looper there's no more output to expect. + setFdEvents(ALOOPER_EVENT_INPUT); + return HandleEventResponse::KEEP_CALLBACK; +} + int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) { - // Allowed return values of this function as documented in LooperCallback::handleEvent - constexpr int REMOVE_CALLBACK = 0; - constexpr int KEEP_CALLBACK = 1; if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) { // This error typically occurs when the publisher has closed the input channel // as part of removing a window or finishing an IME session, in which case @@ -191,56 +263,25 @@ int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) ALOGD("channel '%s' ~ Publisher closed input channel or an error occurred. " "events=0x%x", getInputChannelName().c_str(), events); } - return REMOVE_CALLBACK; + return toUnderlying(HandleEventResponse::REMOVE_CALLBACK); } if (events & ALOOPER_EVENT_INPUT) { JNIEnv* env = AndroidRuntime::getJNIEnv(); status_t status = consumeEvents(env, false /*consumeBatches*/, -1, nullptr); mMessageQueue->raiseAndClearException(env, "handleReceiveCallback"); - return status == OK || status == NO_MEMORY ? KEEP_CALLBACK : REMOVE_CALLBACK; + return status == OK || status == NO_MEMORY + ? toUnderlying(HandleEventResponse::KEEP_CALLBACK) + : toUnderlying(HandleEventResponse::REMOVE_CALLBACK); } if (events & ALOOPER_EVENT_OUTPUT) { - for (size_t i = 0; i < mFinishQueue.size(); i++) { - const Finish& finish = mFinishQueue[i]; - status_t status = mInputConsumer.sendFinishedSignal(finish.seq, finish.handled); - if (status != OK) { - mFinishQueue.erase(mFinishQueue.begin(), mFinishQueue.begin() + i); - - if (status == WOULD_BLOCK) { - if (kDebugDispatchCycle) { - ALOGD("channel '%s' ~ Sent %zu queued finish events; %zu left.", - getInputChannelName().c_str(), i, mFinishQueue.size()); - } - return KEEP_CALLBACK; // try again later - } - - ALOGW("Failed to send finished signal on channel '%s'. status=%d", - getInputChannelName().c_str(), status); - if (status != DEAD_OBJECT) { - JNIEnv* env = AndroidRuntime::getJNIEnv(); - std::string message = - android::base::StringPrintf("Failed to finish input event. status=%d", - status); - jniThrowRuntimeException(env, message.c_str()); - mMessageQueue->raiseAndClearException(env, "finishInputEvent"); - } - return REMOVE_CALLBACK; - } - } - if (kDebugDispatchCycle) { - ALOGD("channel '%s' ~ Sent %zu queued finish events; none left.", - getInputChannelName().c_str(), mFinishQueue.size()); - } - mFinishQueue.clear(); - setFdEvents(ALOOPER_EVENT_INPUT); - return KEEP_CALLBACK; + return toUnderlying(processOutboundEvents()); } ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. " "events=0x%x", getInputChannelName().c_str(), events); - return KEEP_CALLBACK; + return toUnderlying(HandleEventResponse::KEEP_CALLBACK); } status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index c9062d8a50bc..bcfb06b15ab8 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -304,15 +304,14 @@ static std::array<UsapTableEntry, USAP_POOL_SIZE_MAX_LIMIT> gUsapTable; static FileDescriptorTable* gOpenFdTable = nullptr; // Must match values in com.android.internal.os.Zygote. -// Note that there are gaps in the constants: -// This is to further keep the values consistent with IVold.aidl +// The values should be consistent with IVold.aidl enum MountExternalKind { MOUNT_EXTERNAL_NONE = 0, MOUNT_EXTERNAL_DEFAULT = 1, - MOUNT_EXTERNAL_INSTALLER = 5, - MOUNT_EXTERNAL_PASS_THROUGH = 7, - MOUNT_EXTERNAL_ANDROID_WRITABLE = 8, - MOUNT_EXTERNAL_COUNT = 9 + MOUNT_EXTERNAL_INSTALLER = 2, + MOUNT_EXTERNAL_PASS_THROUGH = 3, + MOUNT_EXTERNAL_ANDROID_WRITABLE = 4, + MOUNT_EXTERNAL_COUNT = 5 }; // Must match values in com.android.internal.os.Zygote. diff --git a/core/proto/OWNERS b/core/proto/OWNERS index 99fd21592411..e62b5c102a59 100644 --- a/core/proto/OWNERS +++ b/core/proto/OWNERS @@ -15,6 +15,7 @@ per-file settings_enums.proto=tmfang@google.com ogunwale@google.com jjaggi@google.com roosa@google.com +per-file package_item_info.proto = toddke@google.com per-file usagestatsservice.proto, usagestatsservice_v2.proto = mwachens@google.com per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index ec502c3c272f..f26bf7cdb6c1 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -337,7 +337,7 @@ message ActivityRecordProto { optional bool starting_displayed = 20; optional bool starting_moved = 201; optional bool visible_set_from_transferred_starting_window = 22; - repeated .android.graphics.RectProto frozen_bounds = 23; + repeated .android.graphics.RectProto frozen_bounds = 23 [deprecated=true]; optional bool visible = 24; reserved 25; // configuration_container optional IdentifierProto identifier = 26 [deprecated=true]; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index d5f5d28aa7a2..072bb8799e59 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1876,15 +1876,20 @@ <permission android:name="android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE" android:protectionLevel="signature|privileged" /> - <!-- @SystemApi @hide Allows system APK to update Wifi/Cellular coex channels to avoid. + <!-- @SystemApi @hide Allows applications to update Wifi/Cellular coex channels to avoid. <p>Not for use by third-party applications. --> <permission android:name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS" - android:protectionLevel="signature" /> + android:protectionLevel="signature|role" /> <!-- @SystemApi @hide Allows applications to access Wifi/Cellular coex channels being avoided. <p>Not for use by third-party applications. --> <permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|role" /> + + <!-- @SystemApi @hide Allows system APK to manage country code. + <p>Not for use by third-party applications. --> + <permission android:name="android.permission.MANAGE_WIFI_COUNTRY_CODE" + android:protectionLevel="signature" /> <!-- @SystemApi @hide Allows an application to manage an automotive device's application network preference as it relates to OEM_PAID and OEM_PRIVATE capable networks. @@ -2371,6 +2376,15 @@ <permission android:name="android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE" android:protectionLevel="signature" /> + <!-- Must be required by a {@link android.telecom.CallDiagnosticService}, + to ensure that only the system can bind to it. + <p>Protection level: signature + @SystemApi + @hide + --> + <permission android:name="android.permission.BIND_CALL_DIAGNOSTIC_SERVICE" + android:protectionLevel="signature" /> + <!-- Must be required by a {@link android.telecom.CallRedirectionService}, to ensure that only the system can bind to it. <p>Protection level: signature|privileged diff --git a/core/res/res/color/text_color_primary_device_default_dark.xml b/core/res/res/color/text_color_primary_device_default_dark.xml new file mode 100644 index 000000000000..90d6b07b24bd --- /dev/null +++ b/core/res/res/color/text_color_primary_device_default_dark.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<!-- Please see primary_text_material_dark.xml --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?attr/disabledAlpha" + android:color="@color/system_primary_50"/> + <item android:color="@color/system_primary_50"/> +</selector> diff --git a/core/res/res/color/text_color_primary_device_default_light.xml b/core/res/res/color/text_color_primary_device_default_light.xml new file mode 100644 index 000000000000..bdc4fa92b2f2 --- /dev/null +++ b/core/res/res/color/text_color_primary_device_default_light.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<!-- Please see primary_text_material_light.xml --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?attr/disabledAlpha" + android:color="@color/system_primary_900"/> + <item android:color="@color/system_primary_900"/> +</selector> diff --git a/core/res/res/color/text_color_secondary_device_default_dark.xml b/core/res/res/color/text_color_secondary_device_default_dark.xml new file mode 100644 index 000000000000..799636addd4b --- /dev/null +++ b/core/res/res/color/text_color_secondary_device_default_dark.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<!-- Please see secondary_text_material_dark.xml --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?attr/disabledAlpha" + android:color="@color/system_primary_200"/> + <item android:color="@color/system_primary_200"/> +</selector> diff --git a/core/res/res/color/text_color_secondary_device_default_light.xml b/core/res/res/color/text_color_secondary_device_default_light.xml new file mode 100644 index 000000000000..4793bb8e0360 --- /dev/null +++ b/core/res/res/color/text_color_secondary_device_default_light.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<!-- Please see secondary_text_material_light.xml --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?attr/disabledAlpha" + android:color="@color/system_primary_700"/> + <item android:color="@color/system_primary_700"/> +</selector> diff --git a/core/res/res/color/text_color_tertiary_device_default_dark.xml b/core/res/res/color/text_color_tertiary_device_default_dark.xml new file mode 100644 index 000000000000..c82863109e7d --- /dev/null +++ b/core/res/res/color/text_color_tertiary_device_default_dark.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<!-- Please see tertiary_text_material_dark.xml --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?attr/disabledAlpha" + android:color="@color/system_primary_400"/> + <item android:color="@color/system_primary_400"/> +</selector> diff --git a/core/res/res/color/text_color_tertiary_device_default_light.xml b/core/res/res/color/text_color_tertiary_device_default_light.xml new file mode 100644 index 000000000000..82c420ad97fc --- /dev/null +++ b/core/res/res/color/text_color_tertiary_device_default_light.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<!-- Please see tertiary_text_material_light.xml --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?attr/disabledAlpha" + android:color="@color/system_primary_500"/> + <item android:color="@color/system_primary_500"/> +</selector> diff --git a/core/res/res/drawable/conversation_unread_bg.xml b/core/res/res/drawable/expand_button_pill_bg.xml index d3e00cfbf8b1..f95044a7fe76 100644 --- a/core/res/res/drawable/conversation_unread_bg.xml +++ b/core/res/res/drawable/expand_button_pill_bg.xml @@ -14,6 +14,6 @@ ~ limitations under the License. --> <shape xmlns:android="http://schemas.android.com/apk/res/android"> - <corners android:radius="20sp" /> + <corners android:radius="@dimen/notification_expand_button_pill_height" /> <solid android:color="@android:color/white" /> </shape>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_collapse_notification.xml b/core/res/res/drawable/ic_collapse_notification.xml index ca4f0ed27a0b..a06ec9fc813f 100644 --- a/core/res/res/drawable/ic_collapse_notification.xml +++ b/core/res/res/drawable/ic_collapse_notification.xml @@ -21,8 +21,5 @@ Copyright (C) 2020 The Android Open Source Project android:viewportHeight="24.0"> <path android:fillColor="#FF000000" - android:pathData="M18.59,16.41L20.0,15.0l-8.0,-8.0 -8.0,8.0 1.41,1.41L12.0,9.83"/> - <path - android:pathData="M0 0h24v24H0V0z" - android:fillColor="#00000000"/> + android:pathData="M18.59,15.41L20.0,14.0l-8.0,-8.0 -8.0,8.0 1.41,1.41L12.0,8.83"/> </vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_expand_notification.xml b/core/res/res/drawable/ic_expand_notification.xml index a080ce43cfec..160a9c2c1970 100644 --- a/core/res/res/drawable/ic_expand_notification.xml +++ b/core/res/res/drawable/ic_expand_notification.xml @@ -21,8 +21,5 @@ Copyright (C) 2014 The Android Open Source Project android:viewportHeight="24.0"> <path android:fillColor="#FF000000" - android:pathData="M5.41,7.59L4.0,9.0l8.0,8.0 8.0,-8.0 -1.41,-1.41L12.0,14.17"/> - <path - android:pathData="M24 24H0V0h24v24z" - android:fillColor="#00000000"/> + android:pathData="M5.41,8.59L4.0,10.0l8.0,8.0 8.0,-8.0 -1.41,-1.41L12.0,15.17"/> </vector>
\ No newline at end of file diff --git a/core/res/res/layout/notification_expand_button.xml b/core/res/res/layout/notification_expand_button.xml new file mode 100644 index 000000000000..f92e6d63cbe7 --- /dev/null +++ b/core/res/res/layout/notification_expand_button.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ 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 + --> + +<com.android.internal.widget.NotificationExpandButton + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/expand_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top|end" + android:contentDescription="@string/expand_button_content_description_collapsed" + android:padding="16dp" + android:visibility="gone" + > + + <LinearLayout + android:id="@+id/expand_button_pill" + android:layout_width="wrap_content" + android:layout_height="@dimen/notification_expand_button_pill_height" + android:orientation="horizontal" + android:background="@drawable/expand_button_pill_bg" + > + + <TextView + android:id="@+id/expand_button_number" + android:layout_width="wrap_content" + android:layout_height="@dimen/notification_expand_button_pill_height" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info" + android:gravity="center_vertical" + android:paddingLeft="8dp" + /> + + <ImageView + android:id="@+id/expand_button_icon" + android:layout_width="@dimen/notification_expand_button_pill_height" + android:layout_height="@dimen/notification_expand_button_pill_height" + android:padding="2dp" + android:scaleType="fitCenter" + android:importantForAccessibility="no" + /> + + </LinearLayout> + +</com.android.internal.widget.NotificationExpandButton> diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index 1de1d049197c..81a79c50c3ef 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -30,7 +30,8 @@ android:id="@+id/left_icon" android:layout_width="@dimen/notification_left_icon_size" android:layout_height="@dimen/notification_left_icon_size" - android:layout_gravity="center_vertical|start" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" android:layout_marginStart="@dimen/notification_left_icon_start" android:background="@drawable/notification_large_icon_outline" android:clipToOutline="true" @@ -43,7 +44,8 @@ android:id="@+id/icon" android:layout_width="@dimen/notification_icon_circle_size" android:layout_height="@dimen/notification_icon_circle_size" - android:layout_gravity="center_vertical|start" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" android:layout_marginStart="@dimen/notification_icon_circle_start" android:background="@drawable/notification_icon_circle" android:padding="@dimen/notification_icon_circle_padding" @@ -55,10 +57,12 @@ android:id="@+id/notification_top_line" android:layout_width="wrap_content" android:layout_height="match_parent" - android:layout_gravity="center_vertical" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:layout_toStartOf="@id/expand_button" + android:layout_alignWithParentIfMissing="true" android:clipChildren="false" android:gravity="center_vertical" - android:paddingEnd="@dimen/notification_heading_margin_end" android:paddingStart="@dimen/notification_content_margin_start" android:theme="@style/Theme.DeviceDefault.Notification" > @@ -71,19 +75,15 @@ android:id="@+id/alternate_expand_target" android:layout_width="@dimen/notification_content_margin_start" android:layout_height="match_parent" - android:layout_gravity="start" + android:layout_alignParentStart="true" android:importantForAccessibility="no" /> - <com.android.internal.widget.NotificationExpandButton - android:id="@+id/expand_button" - android:layout_width="@dimen/notification_header_expand_icon_size" - android:layout_height="@dimen/notification_header_expand_icon_size" - android:layout_gravity="center_vertical|end" - android:contentDescription="@string/expand_button_content_description_collapsed" - android:paddingTop="@dimen/notification_expand_button_padding_top" - android:scaleType="center" - android:visibility="gone" + <include layout="@layout/notification_expand_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" /> </NotificationHeaderView> diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml index b83611bcc177..bad9a6ba6184 100644 --- a/core/res/res/layout/notification_template_material_base.xml +++ b/core/res/res/layout/notification_template_material_base.xml @@ -74,14 +74,10 @@ android:layout_height="match_parent" android:layout_gravity="end"> - <com.android.internal.widget.NotificationExpandButton - android:id="@+id/expand_button" - android:layout_width="@dimen/notification_header_expand_icon_size" - android:layout_height="@dimen/notification_header_expand_icon_size" + <include layout="@layout/notification_expand_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:layout_gravity="center_vertical|end" - android:contentDescription="@string/expand_button_content_description_collapsed" - android:paddingTop="@dimen/notification_expand_button_padding_top" - android:scaleType="center" /> </FrameLayout> diff --git a/core/res/res/layout/notification_template_material_call.xml b/core/res/res/layout/notification_template_material_call.xml index 471d874c59f5..7b52ec30abe6 100644 --- a/core/res/res/layout/notification_template_material_call.xml +++ b/core/res/res/layout/notification_template_material_call.xml @@ -72,15 +72,10 @@ </LinearLayout> <!-- TODO(b/179178086): remove padding from main column when this is visible --> - <com.android.internal.widget.NotificationExpandButton - android:id="@+id/expand_button" - android:layout_width="@dimen/notification_header_expand_icon_size" - android:layout_height="@dimen/notification_header_expand_icon_size" + <include layout="@layout/notification_expand_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:layout_gravity="top|end" - android:contentDescription="@string/expand_button_content_description_collapsed" - android:paddingTop="@dimen/notification_expand_button_padding_top" - android:scaleType="center" - android:visibility="gone" /> </LinearLayout> diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml index f3aa54066c92..42fb4a26dd3b 100644 --- a/core/res/res/layout/notification_template_material_conversation.xml +++ b/core/res/res/layout/notification_template_material_conversation.xml @@ -104,11 +104,10 @@ <LinearLayout android:id="@+id/expand_button_touch_container" android:layout_width="wrap_content" - android:layout_height="@dimen/conversation_expand_button_size" - android:paddingStart="@dimen/conversation_expand_button_side_margin" + android:layout_height="@dimen/conversation_expand_button_height" android:orientation="horizontal" android:layout_gravity="end|top" - android:paddingEnd="@dimen/conversation_expand_button_side_margin" + android:paddingEnd="0dp" android:clipToPadding="false" android:clipChildren="false" > @@ -118,34 +117,16 @@ android:forceHasOverlappingRendering="false" android:layout_width="40dp" android:layout_height="40dp" - android:layout_marginEnd="11dp" + android:layout_marginStart="@dimen/conversation_image_start_margin" android:spacing="0dp" android:layout_gravity="center" android:clipToPadding="false" android:clipChildren="false" /> - <!-- Unread Count --> - <TextView - android:id="@+id/conversation_unread_count" - android:layout_width="33sp" - android:layout_height="wrap_content" - android:layout_marginEnd="11dp" - android:layout_gravity="center" - android:gravity="center" - android:padding="2dp" - android:visibility="gone" - android:textAppearance="@style/TextAppearance.DeviceDefault.Notification" - android:textColor="#FFFFFF" - android:textSize="12sp" - android:background="@drawable/conversation_unread_bg" - /> - <com.android.internal.widget.NotificationExpandButton - android:id="@+id/expand_button" + <include layout="@layout/notification_expand_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:drawable="@drawable/ic_expand_notification" - android:contentDescription="@string/expand_button_content_description_collapsed" /> </LinearLayout> </FrameLayout> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 45e11ba9820e..bed5c31bd327 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1773,6 +1773,8 @@ <enum name="maps" value="6" /> <!-- Apps which are primarily productivity apps, such as cloud storage or workplace apps. --> <enum name="productivity" value="7" /> + <!-- Apps which are primarily accessibility apps, such as screen-readers. --> + <enum name="accessibility" value="8" /> </attr> <!-- Declares the kind of classloader this application's classes must be loaded with --> diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml index 1020c14ef665..9b5632174e44 100644 --- a/core/res/res/values/colors_device_defaults.xml +++ b/core/res/res/values/colors_device_defaults.xml @@ -42,12 +42,7 @@ <color name="background_floating_device_default_dark">@color/system_primary_900</color> <color name="background_floating_device_default_light">@color/system_primary_100</color> - <color name="text_color_primary_device_default_light">@color/system_primary_900</color> - <color name="text_color_primary_device_default_dark">@color/system_primary_50</color> - <color name="text_color_secondary_device_default_light">@color/system_primary_700</color> - <color name="text_color_secondary_device_default_dark">@color/system_primary_200</color> - <color name="text_color_tertiary_device_default_light">@color/system_primary_500</color> - <color name="text_color_tertiary_device_default_dark">@color/system_primary_400</color> + <!-- Please refer to text_color_[primary]_device_default_[light].xml for text colors--> <color name="foreground_device_default_light">@color/text_color_primary_device_default_light</color> <color name="foreground_device_default_dark">@color/text_color_primary_device_default_dark</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index c242ce9e855d..633e2161b5ee 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1792,7 +1792,7 @@ * SDK level 28 makes the following algorithms mandatory : "cbc(aes)", "hmac(md5)", "hmac(sha1)", "hmac(sha256)", "hmac(sha384)", "hmac(sha512)", "rfc4106(gcm(aes))" * SDK level 31 makes the following algorithms mandatory : "rfc3686(ctr(aes))", - "xcbc(aes)", "rfc7539esp(chacha20,poly1305)" + "xcbc(aes)", "cmac(aes)", "rfc7539esp(chacha20,poly1305)" --> <string-array name="config_optionalIpSecAlgorithms" translatable="false"> <!-- Add algorithm here --> @@ -1964,6 +1964,8 @@ <string name="config_systemContacts" translatable="false">com.android.contacts</string> <!-- The name of the package that will hold the speech recognizer role by default. --> <string name="config_systemSpeechRecognizer" translatable="false"></string> + <!-- The name of the package that will hold the system Wi-Fi coex manager role. --> + <string name="config_systemWifiCoexManager" translateable="false"></string> <!-- The name of the package that will be allowed to change its components' label/icon. --> <string name="config_overrideComponentUiPackage" translatable="false"></string> @@ -3948,6 +3950,10 @@ color supplied by the Notification.Builder if present. --> <bool name="config_tintNotificationActionButtons">true</bool> + <!-- Flag indicating that tinted items (actions, expander, etc) are to be tinted using the + theme color, rather than the notification color. --> + <bool name="config_tintNotificationsWithTheme">true</bool> + <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app --> <bool name="config_showAreaUpdateInfoSettings">false</bool> @@ -4662,15 +4668,6 @@ <!-- WindowsManager JetPack display features --> <string name="config_display_features" translatable="false" /> - <!-- Physical Display IDs of the display-devices that are swapped when a folding device folds. - This list is expected to contain two elements: the first is the display to use - when the device is folded, the second is the display to use when unfolded. If the array - is empty or the display IDs are not recognized, this feature is turned off and the value - ignored. - TODO: b/170470621 - remove once we can have multiple Internal displays in DMS as - well as a notification from DisplayStateManager. --> - <string-array name="config_internalFoldedPhysicalDisplayIds" translatable="false" /> - <!-- Aspect ratio of task level letterboxing. Values <= 1.0 will be ignored. Note: Activity min/max aspect ratio restrictions will still be respected by the activity-level letterboxing (size-compat mode). Therefore this override can control the @@ -4701,6 +4698,14 @@ <!-- If true, hide the display cutout with display area --> <bool name="config_hideDisplayCutoutWithDisplayArea">false</bool> + <!-- The timeout value in milliseconds used by SelectionActionModeHelper for each selections + when TextClassifier has been initialized. --> + <integer name="config_smartSelectionInitializedTimeoutMillis">200</integer> + + <!-- The timeout value in milliseconds used by SelectionActionModeHelper for each selections + when TextClassifier has not been initialized. --> + <integer name="config_smartSelectionInitializingTimeoutMillis">500</integer> + <!-- Indicates that default fitness tracker app needs to request sensor and location permissions. --> <bool name="config_trackerAppNeedsPermissions">false</bool> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index c2b6b99dcc1c..10aa7b3f4354 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -224,7 +224,7 @@ <dimen name="notification_content_margin_end">16dp</dimen> <!-- The margin on the end of the top-line content views (accommodates the expander) --> - <dimen name="notification_heading_margin_end">48dp</dimen> + <dimen name="notification_heading_margin_end">56dp</dimen> <!-- The margin for text at the end of the image view for media notifications --> <dimen name="notification_media_image_margin_end">72dp</dimen> @@ -248,7 +248,7 @@ <dimen name="call_notification_collapsible_indent">64dp</dimen> <!-- The size of icons for visual actions in the notification_material_action_list --> - <dimen name="notification_actions_icon_size">48dp</dimen> + <dimen name="notification_actions_icon_size">56dp</dimen> <!-- The size of icons for visual actions in the notification_material_action_list --> <dimen name="notification_actions_icon_drawable_size">20dp</dimen> @@ -314,10 +314,10 @@ <dimen name="notification_conversation_header_separating_margin">4dp</dimen> <!-- The absolute size of the notification expand icon. --> - <dimen name="notification_header_expand_icon_size">48dp</dimen> + <dimen name="notification_header_expand_icon_size">56dp</dimen> - <!-- The top padding for the notification expand button. --> - <dimen name="notification_expand_button_padding_top">1dp</dimen> + <!-- the height of the expand button pill --> + <dimen name="notification_expand_button_pill_height">24dp</dimen> <!-- Vertical margin for the headerless notification content, when content has 1 line --> <!-- 16 * 2 (margins) + 24 (1 line) = 56 (notification) --> @@ -690,6 +690,13 @@ <!-- The default minimal size of a PiP task, in both dimensions. --> <dimen name="default_minimal_size_pip_resizable_task">108dp</dimen> + <!-- + The overridable minimal size of a PiP task, in both dimensions. + Different from default_minimal_size_pip_resizable_task, this is to limit the dimension + when the pinned stack size is overridden by app via minWidth/minHeight. + --> + <dimen name="overridable_minimal_size_pip_resizable_task">48dp</dimen> + <!-- Height of a task when in minimized mode from the top when launcher is resizable. --> <dimen name="task_height_of_minimized_mode">80dp</dimen> @@ -739,7 +746,7 @@ <dimen name="notification_right_icon_headerless_margin">20dp</dimen> <!-- The top margin of the right icon in the "big" notification states --> <!-- TODO(b/181048615): Move the large icon below the expander in big states --> - <dimen name="notification_right_icon_big_margin_top">16dp</dimen> + <dimen name="notification_right_icon_big_margin_top">20dp</dimen> <!-- The size of the left icon --> <dimen name="notification_left_icon_size">@dimen/notification_icon_circle_size</dimen> <!-- The left padding of the left icon --> @@ -770,13 +777,10 @@ <dimen name="conversation_icon_circle_start">28dp</dimen> <!-- Start of the content in the conversation template --> <dimen name="conversation_content_start">80dp</dimen> - <!-- Size of the expand button in the conversation layout --> - <dimen name="conversation_expand_button_size">80dp</dimen> - <!-- Top margin of the expand button for conversations when expanded --> - <dimen name="conversation_expand_button_top_margin_expanded">18dp</dimen> - <!-- Side margin of the expand button for conversations. - width of expand asset (22) + 2 * this (13) == notification_header_expand_icon_size (48) --> - <dimen name="conversation_expand_button_side_margin">13dp</dimen> + <!-- Height of the expand button in the conversation layout --> + <dimen name="conversation_expand_button_height">80dp</dimen> + <!-- this is the margin between the Conversation image and the content --> + <dimen name="conversation_image_start_margin">12dp</dimen> <!-- Side margins of the conversation badge in relation to the conversation icon --> <dimen name="conversation_badge_side_margin">36dp</dimen> <!-- size of the notification badge when applied to the conversation icon --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 2004d0a8d15c..9bc92e14de53 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3168,6 +3168,8 @@ <public name="config_customMediaSessionPolicyProvider" /> <!-- @hide @SystemApi --> <public name="config_systemSpeechRecognizer" /> + <!-- @hide @SystemApi --> + <public name="config_systemWifiCoexManager" /> </public-group> <public-group type="id" first-id="0x01020055"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 71ba44b0ded1..2b1168f14f21 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1524,8 +1524,15 @@ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_mediaLocation">Allows the app to read locations from your media collection.</string> + <!-- Name for an app setting that lets the user authenticate for that app using biometrics (e.g. fingerprint or face). [CHAR LIMIT=30] --> + <string name="biometric_app_setting_name">Use biometrics</string> + <!-- Name for an app setting that lets the user authenticate for that app using biometrics (e.g. fingerprint or face) or their screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=70] --> + <string name="biometric_or_screen_lock_app_setting_name">Use biometrics or screen lock</string> <!-- Title shown when the system-provided biometric dialog is shown, asking the user to authenticate. [CHAR LIMIT=40] --> <string name="biometric_dialog_default_title">Verify it\u2019s you</string> + <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face). [CHAR LIMIT=70] --> + <string name="biometric_dialog_default_subtitle">Use your biometric to continue</string> + <!-- Message shown when biometric hardware is not available [CHAR LIMIT=50] --> <string name="biometric_error_hw_unavailable">Biometric hardware unavailable</string> <!-- Message shown when biometric authentication was canceled by the user [CHAR LIMIT=50] --> @@ -1539,6 +1546,11 @@ <!-- Message returned to applications when an unexpected/unknown error occurs. [CHAR LIMIT=50]--> <string name="biometric_error_generic">Error authenticating</string> + <!-- Name for an app setting that lets the user authenticate for that app with their screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=30] --> + <string name="screen_lock_app_setting_name">Use screen lock</string> + <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with their screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=70] --> + <string name="screen_lock_dialog_default_subtitle">Enter your device credential to continue</string> + <!-- Message shown during fingerprint acquisision when the fingerprint cannot be recognized --> <string name="fingerprint_acquired_partial">Partial fingerprint detected. Please try again.</string> <!-- Message shown during fingerprint acquisision when the fingerprint cannot be recognized --> @@ -1585,6 +1597,11 @@ <!-- Template to be used to name enrolled fingerprints by default. --> <string name="fingerprint_name_template">Finger <xliff:g id="fingerId" example="1">%d</xliff:g></string> + + <!-- Name for an app setting that lets the user authenticate for that app with their fingerprint. [CHAR LIMIT=30] --> + <string name="fingerprint_app_setting_name">Use fingerprint</string> + <!-- Name for an app setting that lets the user authenticate for that app with their fingerprint or screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=70] --> + <string name="fingerprint_or_screen_lock_app_setting_name">Use fingerprint or screen lock</string> <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with their fingerprint. [CHAR LIMIT=70] --> <string name="fingerprint_dialog_default_subtitle">Use your fingerprint to continue</string> @@ -1681,6 +1698,13 @@ <!-- Template to be used to name enrolled faces by default. [CHAR LIMIT=10] --> <string name="face_name_template">Face <xliff:g id="faceId" example="1">%d</xliff:g></string> + <!-- Name for an app setting that lets the user authenticate for that app with their face. [CHAR LIMIT=30] --> + <string name="face_app_setting_name">Use face unlock</string> + <!-- Name for an app setting that lets the user authenticate for that app with their face or screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=70] --> + <string name="face_or_screen_lock_app_setting_name">Use face or screen lock</string> + <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with their face. [CHAR LIMIT=70] --> + <string name="face_dialog_default_subtitle">Use face unlock to continue</string> + <!-- Array containing custom error messages from vendor. Vendor is expected to add and translate these strings --> <string-array name="face_error_vendor"> </string-array> @@ -5193,6 +5217,8 @@ <string name="app_category_maps">Maps & Navigation</string> <!-- Category title for apps which are primarily productivity apps, such as cloud storage or workplace apps. [CHAR LIMIT=32] --> <string name="app_category_productivity">Productivity</string> + <!-- Category title for apps which are primarily accessibility apps, such as screen-readers. [CHAR LIMIT=32] --> + <string name="app_category_accessibility">Accessibility</string> <!-- Channel name for DeviceStorageMonitor notifications --> <string name="device_storage_monitor_notification_channel">Device storage</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 5d56eb760679..ff9d26fb2363 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -482,6 +482,8 @@ <java-symbol type="array" name="config_integrityRuleProviderPackages" /> <java-symbol type="bool" name="config_useAssistantVolume" /> <java-symbol type="string" name="config_bandwidthEstimateSource" /> + <java-symbol type="integer" name="config_smartSelectionInitializedTimeoutMillis" /> + <java-symbol type="integer" name="config_smartSelectionInitializingTimeoutMillis" /> <java-symbol type="color" name="tab_indicator_text_v4" /> @@ -1893,6 +1895,7 @@ <java-symbol type="bool" name="config_notificationHeaderClickableForExpand" /> <java-symbol type="bool" name="config_enableNightMode" /> <java-symbol type="bool" name="config_tintNotificationActionButtons" /> + <java-symbol type="bool" name="config_tintNotificationsWithTheme" /> <java-symbol type="bool" name="config_dozeAfterScreenOffByDefault" /> <java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" /> <java-symbol type="bool" name="config_enableFusedLocationOverlay" /> @@ -1948,6 +1951,7 @@ <java-symbol type="fraction" name="config_dimBehindFadeDuration" /> <java-symbol type="dimen" name="default_minimal_size_resizable_task" /> <java-symbol type="dimen" name="default_minimal_size_pip_resizable_task" /> + <java-symbol type="dimen" name="overridable_minimal_size_pip_resizable_task" /> <java-symbol type="dimen" name="task_height_of_minimized_mode" /> <java-symbol type="fraction" name="config_screenAutoBrightnessDozeScaleFactor" /> <java-symbol type="bool" name="config_allowPriorityVibrationsInLowPowerMode" /> @@ -2468,7 +2472,10 @@ <java-symbol type="string" name="config_keyguardComponent" /> <!-- Biometric messages --> + <java-symbol type="string" name="biometric_app_setting_name" /> + <java-symbol type="string" name="biometric_or_screen_lock_app_setting_name" /> <java-symbol type="string" name="biometric_dialog_default_title" /> + <java-symbol type="string" name="biometric_dialog_default_subtitle" /> <java-symbol type="string" name="biometric_error_hw_unavailable" /> <java-symbol type="string" name="biometric_error_user_canceled" /> <java-symbol type="string" name="biometric_not_recognized" /> @@ -2476,6 +2483,10 @@ <java-symbol type="string" name="biometric_error_device_not_secured" /> <java-symbol type="string" name="biometric_error_generic" /> + <!-- Device credential strings for BiometricManager --> + <java-symbol type="string" name="screen_lock_app_setting_name" /> + <java-symbol type="string" name="screen_lock_dialog_default_subtitle" /> + <!-- Fingerprint messages --> <java-symbol type="string" name="fingerprint_error_unable_to_process" /> <java-symbol type="string" name="fingerprint_error_hw_not_available" /> @@ -2493,6 +2504,8 @@ <java-symbol type="string" name="fingerprint_error_lockout" /> <java-symbol type="string" name="fingerprint_error_lockout_permanent" /> <java-symbol type="string" name="fingerprint_name_template" /> + <java-symbol type="string" name="fingerprint_app_setting_name" /> + <java-symbol type="string" name="fingerprint_or_screen_lock_app_setting_name" /> <java-symbol type="string" name="fingerprint_dialog_default_subtitle" /> <java-symbol type="string" name="fingerprint_authenticated" /> <java-symbol type="string" name="fingerprint_error_no_fingerprints" /> @@ -2540,6 +2553,9 @@ <java-symbol type="string" name="face_acquired_sensor_dirty" /> <java-symbol type="array" name="face_acquired_vendor" /> <java-symbol type="string" name="face_name_template" /> + <java-symbol type="string" name="face_app_setting_name" /> + <java-symbol type="string" name="face_or_screen_lock_app_setting_name" /> + <java-symbol type="string" name="face_dialog_default_subtitle" /> <java-symbol type="string" name="face_authenticated_no_confirmation_required" /> <java-symbol type="string" name="face_authenticated_confirmation_required" /> <java-symbol type="string" name="face_error_security_update_required" /> @@ -2892,6 +2908,9 @@ <java-symbol type="id" name="header_text" /> <java-symbol type="id" name="header_text_secondary" /> <java-symbol type="id" name="expand_button" /> + <java-symbol type="id" name="expand_button_pill" /> + <java-symbol type="id" name="expand_button_number" /> + <java-symbol type="id" name="expand_button_icon" /> <java-symbol type="id" name="alternate_expand_target" /> <java-symbol type="id" name="notification_header" /> <java-symbol type="id" name="notification_top_line" /> @@ -2912,7 +2931,6 @@ <java-symbol type="dimen" name="notification_header_background_height" /> <java-symbol type="dimen" name="notification_header_touchable_height" /> <java-symbol type="dimen" name="notification_header_expand_icon_size" /> - <java-symbol type="dimen" name="notification_expand_button_padding_top" /> <java-symbol type="dimen" name="notification_header_icon_size" /> <java-symbol type="dimen" name="notification_header_app_name_margin_start" /> <java-symbol type="dimen" name="notification_header_separating_margin" /> @@ -3296,6 +3314,7 @@ <java-symbol type="string" name="app_category_news" /> <java-symbol type="string" name="app_category_maps" /> <java-symbol type="string" name="app_category_productivity" /> + <java-symbol type="string" name="app_category_accessibility" /> <java-symbol type="raw" name="fallback_categories" /> @@ -4032,7 +4051,6 @@ <java-symbol type="id" name="message_icon_container" /> <java-symbol type="id" name="conversation_image_message_container" /> <java-symbol type="id" name="conversation_icon_container" /> - <java-symbol type="dimen" name="conversation_expand_button_top_margin_expanded" /> <java-symbol type="dimen" name="messaging_group_singleline_sender_padding_end" /> <java-symbol type="dimen" name="conversation_badge_side_margin" /> <java-symbol type="dimen" name="conversation_avatar_size" /> @@ -4053,7 +4071,6 @@ <java-symbol type="dimen" name="button_padding_horizontal_material" /> <java-symbol type="dimen" name="button_inset_horizontal_material" /> <java-symbol type="layout" name="conversation_face_pile_layout" /> - <java-symbol type="id" name="conversation_unread_count" /> <java-symbol type="string" name="unread_convo_overflow" /> <java-symbol type="style" name="TextAppearance.DeviceDefault.Notification.Conversation.AppName" /> <java-symbol type="drawable" name="conversation_badge_background" /> @@ -4160,7 +4177,6 @@ <java-symbol type="dimen" name="default_background_blur_radius" /> <java-symbol type="array" name="config_keep_warming_services" /> <java-symbol type="string" name="config_display_features" /> - <java-symbol type="array" name="config_internalFoldedPhysicalDisplayIds" /> <java-symbol type="dimen" name="controls_thumbnail_image_max_height" /> <java-symbol type="dimen" name="controls_thumbnail_image_max_width" /> diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java index 3706e4b3d8e8..b0c1f25ad030 100644 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java @@ -24,6 +24,7 @@ import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.NetworkInfo.State; +import android.net.TetheringManager; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; @@ -141,7 +142,7 @@ public class ConnectivityManagerTestBase extends InstrumentationTestCase { mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); mIntentFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION); mIntentFilter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); - mIntentFilter.addAction(ConnectivityManager.ACTION_TETHER_STATE_CHANGED); + mIntentFilter.addAction(TetheringManager.ACTION_TETHER_STATE_CHANGED); mContext.registerReceiver(mWifiReceiver, mIntentFilter); logv("Clear Wifi before we start the test."); diff --git a/core/tests/coretests/src/android/app/usage/OWNERS b/core/tests/coretests/src/android/app/usage/OWNERS new file mode 100644 index 000000000000..1271fa799808 --- /dev/null +++ b/core/tests/coretests/src/android/app/usage/OWNERS @@ -0,0 +1,2 @@ +# Bug component: 532296 +include /services/usage/OWNERS diff --git a/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java b/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java index 4d04a7af4693..8de9454ddeda 100644 --- a/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java +++ b/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java @@ -78,15 +78,15 @@ public class UsageStatsPersistenceTest { "VALID_FLAG_BITS", "UNASSIGNED_TOKEN", "MAX_EVENT_TYPE"}; // All fields in this list are final constants defining event types and not persisted private static final String[] EVENT_TYPES = {"NONE", "ACTIVITY_DESTROYED", "ACTIVITY_PAUSED", - "ACTIVITY_RESUMED", "ACTIVITY_STOPPED", "CHOOSER_ACTION", "CONFIGURATION_CHANGE", - "CONTINUE_PREVIOUS_DAY", "CONTINUING_FOREGROUND_SERVICE", "DEVICE_SHUTDOWN", - "DEVICE_STARTUP", "END_OF_DAY", "FLUSH_TO_DISK", "FOREGROUND_SERVICE_START", - "FOREGROUND_SERVICE_STOP", "KEYGUARD_HIDDEN", "KEYGUARD_SHOWN", "LOCUS_ID_SET", - "MOVE_TO_BACKGROUND", "MOVE_TO_FOREGROUND", "NOTIFICATION_INTERRUPTION", - "NOTIFICATION_SEEN", "ROLLOVER_FOREGROUND_SERVICE", "SCREEN_INTERACTIVE", - "SCREEN_NON_INTERACTIVE", "SHORTCUT_INVOCATION", "SLICE_PINNED", "SLICE_PINNED_PRIV", - "STANDBY_BUCKET_CHANGED", "SYSTEM_INTERACTION", "USER_INTERACTION", "USER_STOPPED", - "USER_UNLOCKED"}; + "ACTIVITY_RESUMED", "ACTIVITY_STOPPED", "APP_COMPONENT_USED", "CHOOSER_ACTION", + "CONFIGURATION_CHANGE", "CONTINUE_PREVIOUS_DAY", "CONTINUING_FOREGROUND_SERVICE", + "DEVICE_SHUTDOWN", "DEVICE_STARTUP", "END_OF_DAY", "FLUSH_TO_DISK", + "FOREGROUND_SERVICE_START", "FOREGROUND_SERVICE_STOP", "KEYGUARD_HIDDEN", + "KEYGUARD_SHOWN", "LOCUS_ID_SET", "MOVE_TO_BACKGROUND", "MOVE_TO_FOREGROUND", + "NOTIFICATION_INTERRUPTION", "NOTIFICATION_SEEN", "ROLLOVER_FOREGROUND_SERVICE", + "SCREEN_INTERACTIVE", "SCREEN_NON_INTERACTIVE", "SHORTCUT_INVOCATION", "SLICE_PINNED", + "SLICE_PINNED_PRIV", "STANDBY_BUCKET_CHANGED", "SYSTEM_INTERACTION", "USER_INTERACTION", + "USER_STOPPED", "USER_UNLOCKED"}; @Test public void testUsageEventsFields() { diff --git a/core/tests/coretests/src/android/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java index eae41e37e5f3..7bc81cd2f928 100644 --- a/core/tests/coretests/src/android/graphics/FontListParserTest.java +++ b/core/tests/coretests/src/android/graphics/FontListParserTest.java @@ -26,6 +26,8 @@ import static android.text.FontConfig.FontFamily.VARIANT_ELEGANT; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static junit.framework.Assert.fail; + import android.graphics.fonts.FontStyle; import android.os.LocaleList; import android.text.FontConfig; @@ -44,6 +46,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; @@ -221,9 +224,113 @@ public final class FontListParserTest { .that(readFamily(serialized)).isEqualTo(expected); } + @Test + public void invalidXml_unpaired_family() throws Exception { + String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<familyset>" + + " <family name='sans-serif'>" + + " <font index='0'>test.ttc</font>" + + "</familyset>"; + + try (InputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { + FontListParser.parse(is); + fail(); + } catch (IOException | XmlPullParserException e) { + // pass + } + } + + @Test + public void invalidXml_unpaired_font() throws Exception { + String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<familyset>" + + " <family name='sans-serif'>" + + " <font index='0'>test.ttc" + + " </family>" + + "</familyset>"; + + try (InputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { + FontListParser.parse(is); + fail(); + } catch (IOException | XmlPullParserException e) { + // pass + } + } + + @Test + public void invalidXml_unpaired_axis() throws Exception { + String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<familyset>" + + " <family name='sans-serif'>" + + " <font index='0'>test.ttc" + + " <axis tag=\"wght\" styleValue=\"0\" >" + + " </font>" + + " </family>" + + "</familyset>"; + + try (InputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { + FontListParser.parse(is); + fail(); + } catch (IOException | XmlPullParserException e) { + // pass + } + } + + @Test + public void invalidXml_unclosed_family() throws Exception { + String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<familyset>" + + " <family name='sans-serif'" + + " <font index='0'>test.ttc</font>" + + " </family>" + + "</familyset>"; + + try (InputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { + FontListParser.parse(is); + fail(); + } catch (IOException | XmlPullParserException e) { + // pass + } + } + + @Test + public void invalidXml_unclosed_font() throws Exception { + String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<familyset>" + + " <family name='sans-serif'>" + + " <font index='0'" + + " </family>" + + "</familyset>"; + + try (InputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { + FontListParser.parse(is); + fail(); + } catch (IOException | XmlPullParserException e) { + // pass + } + } + + @Test + public void invalidXml_unclosed_axis() throws Exception { + String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<familyset>" + + " <family name='sans-serif'>" + + " <font index='0'>test.ttc" + + " <axis tag=\"wght\" styleValue=\"0\"" + + " </font>" + + " </family>" + + "</familyset>"; + + try (InputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { + FontListParser.parse(is); + fail(); + } catch (IOException | XmlPullParserException e) { + // pass + } + } + private FontConfig.FontFamily readFamily(String xml) throws IOException, XmlPullParserException { - StandardCharsets.UTF_8.name(); ByteArrayInputStream buffer = new ByteArrayInputStream( xml.getBytes(StandardCharsets.UTF_8)); XmlPullParser parser = Xml.newPullParser(); diff --git a/core/tests/coretests/src/android/os/VibratorInfoTest.java b/core/tests/coretests/src/android/os/VibratorInfoTest.java index 89411902bb6b..c06405affc1b 100644 --- a/core/tests/coretests/src/android/os/VibratorInfoTest.java +++ b/core/tests/coretests/src/android/os/VibratorInfoTest.java @@ -20,6 +20,7 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import android.hardware.vibrator.IVibrator; import android.platform.test.annotations.Presubmit; import org.junit.Test; @@ -33,16 +34,16 @@ public class VibratorInfoTest { @Test public void testHasAmplitudeControl() { assertFalse(createInfo(/* capabilities= */ 0).hasAmplitudeControl()); - assertTrue(createInfo(VibratorInfo.CAPABILITY_COMPOSE_EFFECTS - | VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL).hasAmplitudeControl()); + assertTrue(createInfo(IVibrator.CAP_COMPOSE_EFFECTS + | IVibrator.CAP_AMPLITUDE_CONTROL).hasAmplitudeControl()); } @Test public void testHasCapabilities() { - assertTrue(createInfo(VibratorInfo.CAPABILITY_COMPOSE_EFFECTS) - .hasCapability(VibratorInfo.CAPABILITY_COMPOSE_EFFECTS)); - assertFalse(createInfo(VibratorInfo.CAPABILITY_COMPOSE_EFFECTS) - .hasCapability(VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL)); + assertTrue(createInfo(IVibrator.CAP_COMPOSE_EFFECTS) + .hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)); + assertFalse(createInfo(IVibrator.CAP_COMPOSE_EFFECTS) + .hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)); } @Test @@ -59,7 +60,7 @@ public class VibratorInfoTest { @Test public void testIsPrimitiveSupported() { - VibratorInfo info = new VibratorInfo(/* id= */ 0, VibratorInfo.CAPABILITY_COMPOSE_EFFECTS, + VibratorInfo info = new VibratorInfo(/* id= */ 0, IVibrator.CAP_COMPOSE_EFFECTS, null, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}); assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK)); assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK)); @@ -73,30 +74,30 @@ public class VibratorInfoTest { @Test public void testEquals() { VibratorInfo empty = new VibratorInfo(1, 0, null, null); - VibratorInfo complete = new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL, + VibratorInfo complete = new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL, new int[]{VibrationEffect.EFFECT_CLICK}, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}); assertEquals(complete, complete); - assertEquals(complete, new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL, + assertEquals(complete, new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL, new int[]{VibrationEffect.EFFECT_CLICK}, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK})); assertFalse(empty.equals(new VibratorInfo(1, 0, new int[]{}, new int[]{}))); - assertFalse(complete.equals(new VibratorInfo(1, VibratorInfo.CAPABILITY_COMPOSE_EFFECTS, + assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_COMPOSE_EFFECTS, new int[]{VibrationEffect.EFFECT_CLICK}, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}))); - assertFalse(complete.equals(new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL, + assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL, new int[]{}, new int[]{}))); - assertFalse(complete.equals(new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL, + assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL, null, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}))); - assertFalse(complete.equals(new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL, + assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL, new int[]{VibrationEffect.EFFECT_CLICK}, null))); } @Test public void testSerialization() { - VibratorInfo original = new VibratorInfo(1, VibratorInfo.CAPABILITY_COMPOSE_EFFECTS, + VibratorInfo original = new VibratorInfo(1, IVibrator.CAP_COMPOSE_EFFECTS, new int[]{VibrationEffect.EFFECT_CLICK}, null); Parcel parcel = Parcel.obtain(); diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java index 7e1e7f4bdd7f..ab24f89015c7 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java @@ -33,6 +33,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import java.util.Arrays; +import java.util.List; + /** * Tests for AccessibilityInteractionClient */ @@ -62,7 +65,7 @@ public class AccessibilityInteractionClientTest { final long accessibilityNodeId = 0x4321L; AccessibilityNodeInfo nodeFromConnection = AccessibilityNodeInfo.obtain(); nodeFromConnection.setSourceNodeId(accessibilityNodeId, windowId); - mMockConnection.mInfoToReturn = nodeFromConnection; + mMockConnection.mInfosToReturn = Arrays.asList(nodeFromConnection); AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); AccessibilityNodeInfo node = client.findAccessibilityNodeInfoByAccessibilityId( MOCK_CONNECTION_ID, windowId, accessibilityNodeId, true, 0, null); @@ -72,7 +75,7 @@ public class AccessibilityInteractionClientTest { } private static class MockConnection extends AccessibilityServiceConnectionImpl { - AccessibilityNodeInfo mInfoToReturn; + List<AccessibilityNodeInfo> mInfosToReturn; @Override public String[] findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, @@ -80,7 +83,7 @@ public class AccessibilityInteractionClientTest { IAccessibilityInteractionConnectionCallback callback, int flags, long threadId, Bundle arguments) { try { - callback.setFindAccessibilityNodeInfoResult(mInfoToReturn, interactionId); + callback.setFindAccessibilityNodeInfosResult(mInfosToReturn, interactionId); } catch (RemoteException e) { throw new RuntimeException(e); } diff --git a/core/tests/coretests/src/android/view/accessibility/OWNERS b/core/tests/coretests/src/android/view/accessibility/OWNERS new file mode 100644 index 000000000000..b74281edbf52 --- /dev/null +++ b/core/tests/coretests/src/android/view/accessibility/OWNERS @@ -0,0 +1 @@ +include /core/java/android/view/accessibility/OWNERS diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml index 3d964fb9bb87..f2a33de008d6 100644 --- a/data/etc/com.android.systemui.xml +++ b/data/etc/com.android.systemui.xml @@ -70,5 +70,6 @@ <permission name="android.permission.READ_WIFI_CREDENTIAL" /> <permission name="android.permission.USE_BACKGROUND_BLUR" /> <permission name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" /> + <permission name="android.permission.FORCE_STOP_PACKAGES" /> </privapp-permissions> </permissions> diff --git a/data/etc/platform.xml b/data/etc/platform.xml index ea42246e8262..77a38a9bb1a0 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -166,6 +166,7 @@ <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" /> <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" /> <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="audioserver" /> + <assign-permission name="android.permission.OBSERVE_SENSOR_PRIVACY" uid="audioserver" /> <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" /> <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" /> @@ -176,6 +177,7 @@ <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="cameraserver" /> <assign-permission name="android.permission.WATCH_APPOPS" uid="cameraserver" /> <assign-permission name="android.permission.MANAGE_APP_OPS_MODES" uid="cameraserver" /> + <assign-permission name="android.permission.OBSERVE_SENSOR_PRIVACY" uid="cameraserver" /> <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="graphics" /> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index cabfad44cfb9..b7bf8ab75799 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -43,6 +43,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-2093859262": { + "message": "setClientVisible: %s clientVisible=%b Callers=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/WindowToken.java" + }, "-2072089308": { "message": "Attempted to add window with token that is a sub-window: %s. Aborting.", "level": "WARN", @@ -811,6 +817,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/Task.java" }, + "-1159577965": { + "message": "Focus requested for input consumer=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_FOCUS_LIGHT", + "at": "com\/android\/server\/wm\/InputMonitor.java" + }, "-1156118957": { "message": "Updated config=%s", "level": "DEBUG", @@ -823,12 +835,6 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/NonAppWindowAnimationAdapter.java" }, - "-1144293044": { - "message": "SURFACE SET FREEZE LAYER: %s", - "level": "INFO", - "group": "WM_SHOW_TRANSACTIONS", - "at": "com\/android\/server\/wm\/WindowStateAnimator.java" - }, "-1142279614": { "message": "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s", "level": "VERBOSE", @@ -1831,6 +1837,12 @@ "group": "WM_DEBUG_TASKS", "at": "com\/android\/server\/wm\/RootWindowContainer.java" }, + "63329306": { + "message": "commitVisibility: %s: visible=%b mVisibleRequested=%b", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/WallpaperWindowToken.java" + }, "73987756": { "message": "ControlAdapter onAnimationCancelled mSource: %s mControlTarget: %s", "level": "INFO", @@ -2461,6 +2473,12 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, + "691515534": { + "message": " Commit wallpaper becoming invisible: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "693423992": { "message": "setAnimationLocked: setting mFocusMayChange true", "level": "INFO", diff --git a/errorprone/Android.bp b/errorprone/Android.bp index d1e94dfed4a8..a927f53e3b5f 100644 --- a/errorprone/Android.bp +++ b/errorprone/Android.bp @@ -36,11 +36,12 @@ java_library_host { java_test_host { name: "error_prone_android_framework_test", - test_suites: ["general-tests"], srcs: ["tests/java/**/*.java"], java_resource_dirs: ["tests/res"], java_resources: [":error_prone_android_framework_testdata"], static_libs: [ + "truth-prebuilt", + "kxml2-2.3.0", "error_prone_android_framework_lib", "error_prone_test_helpers", "hamcrest-library", @@ -48,6 +49,9 @@ java_test_host { "platform-test-annotations", "junit", ], + test_options: { + unit_test: true, + }, } filegroup { diff --git a/errorprone/TEST_MAPPING b/errorprone/TEST_MAPPING deleted file mode 100644 index ee4552fb3b33..000000000000 --- a/errorprone/TEST_MAPPING +++ /dev/null @@ -1,7 +0,0 @@ -{ - "presubmit": [ - { - "name": "error_prone_android_framework_test" - } - ] -} diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 63df0db99764..95c7715a1688 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -136,7 +136,7 @@ public class FontListParser { customization.getAdditionalNamedFamilies(); parser.require(XmlPullParser.START_TAG, null, "familyset"); - while (parser.next() != XmlPullParser.END_TAG) { + while (keepReading(parser)) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; String tag = parser.getName(); if (tag.equals("family")) { @@ -158,6 +158,12 @@ public class FontListParser { return new FontConfig(families, aliases, lastModifiedDate, configVersion); } + private static boolean keepReading(XmlPullParser parser) + throws XmlPullParserException, IOException { + int next = parser.next(); + return next != XmlPullParser.END_TAG && next != XmlPullParser.END_DOCUMENT; + } + /** * Read family tag in fonts.xml or oem_customization.xml */ @@ -168,7 +174,7 @@ public class FontListParser { final String lang = parser.getAttributeValue("", "lang"); final String variant = parser.getAttributeValue(null, "variant"); final List<FontConfig.Font> fonts = new ArrayList<>(); - while (parser.next() != XmlPullParser.END_TAG) { + while (keepReading(parser)) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; final String tag = parser.getName(); if (tag.equals(TAG_FONT)) { @@ -232,7 +238,7 @@ public class FontListParser { boolean isItalic = STYLE_ITALIC.equals(parser.getAttributeValue(null, ATTR_STYLE)); String fallbackFor = parser.getAttributeValue(null, ATTR_FALLBACK_FOR); StringBuilder filename = new StringBuilder(); - while (parser.next() != XmlPullParser.END_TAG) { + while (keepReading(parser)) { if (parser.getEventType() == XmlPullParser.TEXT) { filename.append(parser.getText()); } @@ -359,6 +365,8 @@ public class FontListParser { case XmlPullParser.END_TAG: depth--; break; + case XmlPullParser.END_DOCUMENT: + return; } } } diff --git a/keystore/java/android/security/LegacyVpnProfileStore.java b/keystore/java/android/security/LegacyVpnProfileStore.java new file mode 100644 index 000000000000..41cfb2707fcf --- /dev/null +++ b/keystore/java/android/security/LegacyVpnProfileStore.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +import android.annotation.NonNull; +import android.os.ServiceManager; +import android.os.ServiceSpecificException; +import android.security.keystore.AndroidKeyStoreProvider; +import android.security.vpnprofilestore.IVpnProfileStore; +import android.util.Log; + +/** + * @hide This class allows legacy VPN access to its profiles that were stored in Keystore. + * The storage of unstructured blobs in Android Keystore is going away, because there is no + * architectural or security benefit of storing profiles in keystore over storing them + * in the file system. This class allows access to the blobs that still exist in keystore. + * And it stores new blob in a database that is still owned by Android Keystore. + */ +public class LegacyVpnProfileStore { + private static final String TAG = "LegacyVpnProfileStore"; + + public static final int SYSTEM_ERROR = IVpnProfileStore.ERROR_SYSTEM_ERROR; + public static final int PROFILE_NOT_FOUND = IVpnProfileStore.ERROR_PROFILE_NOT_FOUND; + + private static final String VPN_PROFILE_STORE_SERVICE_NAME = "android.security.vpnprofilestore"; + + private static IVpnProfileStore getService() { + return IVpnProfileStore.Stub.asInterface( + ServiceManager.checkService(VPN_PROFILE_STORE_SERVICE_NAME)); + } + + /** + * Stores the profile under the alias in the profile database. Existing profiles by the + * same name will be replaced. + * @param alias The name of the profile + * @param profile The profile. + * @return true if the profile was successfully added. False otherwise. + * @hide + */ + public static boolean put(@NonNull String alias, @NonNull byte[] profile) { + try { + if (AndroidKeyStoreProvider.isKeystore2Enabled()) { + getService().put(alias, profile); + return true; + } else { + return KeyStore.getInstance().put( + alias, profile, KeyStore.UID_SELF, 0); + } + } catch (Exception e) { + Log.e(TAG, "Failed to put vpn profile.", e); + return false; + } + } + + /** + * Retrieves a profile by the name alias from the profile database. + * @param alias Name of the profile to retrieve. + * @return The unstructured blob, that is the profile that was stored using + * LegacyVpnProfileStore#put or with + * android.security.Keystore.put(Credentials.VPN + alias). + * Returns null if no profile was found. + * @hide + */ + public static byte[] get(@NonNull String alias) { + try { + if (AndroidKeyStoreProvider.isKeystore2Enabled()) { + return getService().get(alias); + } else { + return KeyStore.getInstance().get(alias, true /* suppressKeyNotFoundWarning */); + } + } catch (ServiceSpecificException e) { + if (e.errorCode != PROFILE_NOT_FOUND) { + Log.e(TAG, "Failed to get vpn profile.", e); + } + } catch (Exception e) { + Log.e(TAG, "Failed to get vpn profile.", e); + } + return null; + } + + /** + * Removes a profile by the name alias from the profile database. + * @param alias Name of the profile to be removed. + * @return True if a profile was removed. False if no such profile was found. + * @hide + */ + public static boolean remove(@NonNull String alias) { + try { + if (AndroidKeyStoreProvider.isKeystore2Enabled()) { + getService().remove(alias); + return true; + } else { + return KeyStore.getInstance().delete(alias); + } + } catch (ServiceSpecificException e) { + if (e.errorCode != PROFILE_NOT_FOUND) { + Log.e(TAG, "Failed to remove vpn profile.", e); + } + } catch (Exception e) { + Log.e(TAG, "Failed to remove vpn profile.", e); + } + return false; + } + + /** + * Lists the vpn profiles stored in the database. + * @return An array of strings representing the aliases stored in the profile database. + * The return value may be empty but never null. + * @hide + */ + public static @NonNull String[] list(@NonNull String prefix) { + try { + if (AndroidKeyStoreProvider.isKeystore2Enabled()) { + final String[] aliases = getService().list(prefix); + for (int i = 0; i < aliases.length; ++i) { + aliases[i] = aliases[i].substring(prefix.length()); + } + return aliases; + } else { + final String[] result = KeyStore.getInstance().list(prefix); + return result != null ? result : new String[0]; + } + } catch (Exception e) { + Log.e(TAG, "Failed to list vpn profiles.", e); + } + return new String[0]; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java index cc353dc991e3..85bd24c1c2bf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java @@ -24,6 +24,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; +import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.startingsurface.StartingSurface; import com.android.wm.shell.transition.Transitions; @@ -42,6 +43,7 @@ public class ShellInitImpl { private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional; private final Optional<SplitScreenController> mSplitScreenOptional; private final Optional<AppPairsController> mAppPairsOptional; + private final Optional<PipTouchHandler> mPipTouchHandlerOptional; private final FullscreenTaskListener mFullscreenTaskListener; private final ShellExecutor mMainExecutor; private final Transitions mTransitions; @@ -56,6 +58,7 @@ public class ShellInitImpl { Optional<SplitScreenController> splitScreenOptional, Optional<AppPairsController> appPairsOptional, Optional<StartingSurface> startingSurfaceOptional, + Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, Transitions transitions, ShellExecutor mainExecutor) { @@ -66,6 +69,7 @@ public class ShellInitImpl { splitScreenOptional, appPairsOptional, startingSurfaceOptional, + pipTouchHandlerOptional, fullscreenTaskListener, transitions, mainExecutor).mImpl; @@ -78,6 +82,7 @@ public class ShellInitImpl { Optional<SplitScreenController> splitScreenOptional, Optional<AppPairsController> appPairsOptional, Optional<StartingSurface> startingSurfaceOptional, + Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, Transitions transitions, ShellExecutor mainExecutor) { @@ -88,6 +93,7 @@ public class ShellInitImpl { mSplitScreenOptional = splitScreenOptional; mAppPairsOptional = appPairsOptional; mFullscreenTaskListener = fullscreenTaskListener; + mPipTouchHandlerOptional = pipTouchHandlerOptional; mTransitions = transitions; mMainExecutor = mainExecutor; mStartingSurfaceOptional = startingSurfaceOptional; @@ -112,6 +118,11 @@ public class ShellInitImpl { if (Transitions.ENABLE_SHELL_TRANSITIONS) { mTransitions.register(mShellTaskOrganizer); } + + // TODO(b/181599115): This should really be the pip controller, but until we can provide the + // controller instead of the feature interface, can just initialize the touch handler if + // needed + mPipTouchHandlerOptional.ifPresent((handler) -> handler.init()); } @ExternalThread diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 1320780bfb8f..d31e637b778e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -327,6 +327,10 @@ public class BubbleController { return mImpl; } + public ShellExecutor getMainExecutor() { + return mMainExecutor; + } + /** * Hides the current input method, wherever it may be focused, via InputMethodManagerInternal. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 2f31acd41408..9ef3fb54fb35 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -340,7 +340,7 @@ public class BubbleExpandedView extends LinearLayout { mSettingsIcon.setVisibility(GONE); } else { mTaskView = new TaskView(mContext, mController.getTaskOrganizer()); - mTaskView.setListener(mContext.getMainExecutor(), mTaskViewListener); + mTaskView.setListener(mController.getMainExecutor(), mTaskViewListener); mExpandedViewContainer.addView(mTaskView); bringChildToFront(mTaskView); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java index ac5d14c802d8..702385ecc3d2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java @@ -54,6 +54,7 @@ public class PipBoundsAlgorithm { private float mMaxAspectRatio; private int mDefaultStackGravity; private int mDefaultMinSize; + private int mOverridableMinSize; private Point mScreenEdgeInsets; public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState) { @@ -78,6 +79,8 @@ public class PipBoundsAlgorithm { com.android.internal.R.integer.config_defaultPictureInPictureGravity); mDefaultMinSize = res.getDimensionPixelSize( com.android.internal.R.dimen.default_minimal_size_pip_resizable_task); + mOverridableMinSize = res.getDimensionPixelSize( + com.android.internal.R.dimen.overridable_minimal_size_pip_resizable_task); final String screenEdgeInsetsDpString = res.getString( com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets); final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty() @@ -155,7 +158,10 @@ public class PipBoundsAlgorithm { // -1 will be populated if an activity specifies defaultWidth/defaultHeight in <layout> // without minWidth/minHeight if (windowLayout.minWidth > 0 && windowLayout.minHeight > 0) { - return new Size(windowLayout.minWidth, windowLayout.minHeight); + // If either dimension is smaller than the allowed minimum, adjust them + // according to mOverridableMinSize + return new Size(Math.max(windowLayout.minWidth, mOverridableMinSize), + Math.max(windowLayout.minHeight, mOverridableMinSize)); } return null; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java index d9a7bdb2eca6..9ee6a221c80c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java @@ -87,7 +87,7 @@ public class PipDismissTargetHandler { SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY); // Allow dragging the PIP to a location to close it - private final boolean mEnableDismissDragToEdge; + private boolean mEnableDismissDragToEdge; private int mDismissAreaHeight; @@ -104,67 +104,66 @@ public class PipDismissTargetHandler { mMotionHelper = motionHelper; mMainExecutor = mainExecutor; mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + } - Resources res = context.getResources(); + public void init() { + Resources res = mContext.getResources(); mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge); mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height); - mMainExecutor.execute(() -> { - mTargetView = new DismissCircleView(context); - mTargetViewContainer = new FrameLayout(context); - mTargetViewContainer.setBackgroundDrawable( - context.getDrawable(R.drawable.floating_dismiss_gradient_transition)); - mTargetViewContainer.setClipChildren(false); - mTargetViewContainer.addView(mTargetView); - - mMagnetizedPip = mMotionHelper.getMagnetizedPip(); - mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0); - updateMagneticTargetSize(); - - mMagnetizedPip.setAnimateStuckToTarget( - (target, velX, velY, flung, after) -> { - if (mEnableDismissDragToEdge) { - mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, - after); - } - return Unit.INSTANCE; - }); - mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() { - @Override - public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) { - // Show the dismiss target, in case the initial touch event occurred within - // the magnetic field radius. + mTargetView = new DismissCircleView(mContext); + mTargetViewContainer = new FrameLayout(mContext); + mTargetViewContainer.setBackgroundDrawable( + mContext.getDrawable(R.drawable.floating_dismiss_gradient_transition)); + mTargetViewContainer.setClipChildren(false); + mTargetViewContainer.addView(mTargetView); + + mMagnetizedPip = mMotionHelper.getMagnetizedPip(); + mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0); + updateMagneticTargetSize(); + + mMagnetizedPip.setAnimateStuckToTarget( + (target, velX, velY, flung, after) -> { if (mEnableDismissDragToEdge) { - showDismissTargetMaybe(); + mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after); } + return Unit.INSTANCE; + }); + mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() { + @Override + public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) { + // Show the dismiss target, in case the initial touch event occurred within + // the magnetic field radius. + if (mEnableDismissDragToEdge) { + showDismissTargetMaybe(); } + } - @Override - public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, - float velX, float velY, boolean wasFlungOut) { - if (wasFlungOut) { - mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */); - hideDismissTargetMaybe(); - } else { - mMotionHelper.setSpringingToTouch(true); - } + @Override + public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, + float velX, float velY, boolean wasFlungOut) { + if (wasFlungOut) { + mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */); + hideDismissTargetMaybe(); + } else { + mMotionHelper.setSpringingToTouch(true); } + } - @Override - public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { - mMainExecutor.executeDelayed(() -> { - mMotionHelper.notifyDismissalPending(); - mMotionHelper.animateDismiss(); - hideDismissTargetMaybe(); - - mPipUiEventLogger.log( - PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE); - }, 0); - } - }); + @Override + public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { + mMainExecutor.executeDelayed(() -> { + mMotionHelper.notifyDismissalPending(); + mMotionHelper.animateDismiss(); + hideDismissTargetMaybe(); - mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView); + mPipUiEventLogger.log( + PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE); + }, 0); + } }); + + mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java index eae8945ce6be..d742aa688fe7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java @@ -102,7 +102,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * PhysicsAnimator instance for animating {@link PipBoundsState#getMotionBoundsState()} * using physics animations. */ - private final PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator; + private PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator; private MagnetizedObject<Rect> mMagnetizedPip; @@ -171,7 +171,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController, PipSnapAlgorithm snapAlgorithm, PipTransitionController pipTransitionController, - FloatingContentCoordinator floatingContentCoordinator, ShellExecutor mainExecutor) { + FloatingContentCoordinator floatingContentCoordinator) { mContext = context; mPipTaskOrganizer = pipTaskOrganizer; mPipBoundsState = pipBoundsState; @@ -179,15 +179,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, mSnapAlgorithm = snapAlgorithm; mFloatingContentCoordinator = floatingContentCoordinator; pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback); - mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance( - mPipBoundsState.getMotionBoundsState().getBoundsInMotion()); - - // Need to get the shell main thread sf vsync animation handler - mainExecutor.execute(() -> { - mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler( - mSfAnimationHandlerThreadLocal.get()); - }); - mResizePipUpdateListener = (target, values) -> { if (mPipBoundsState.getMotionBoundsState().isInMotion()) { mPipTaskOrganizer.scheduleUserResizePip(getBounds(), @@ -196,6 +187,14 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, }; } + public void init() { + // Note: Needs to get the shell main thread sf vsync animation handler + mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance( + mPipBoundsState.getMotionBoundsState().getBoundsInMotion()); + mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler( + mSfAnimationHandlerThreadLocal.get()); + } + @NonNull @Override public Rect getFloatingBoundsOnScreen() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index 78ee1868eee7..31057f8d5fb8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -132,8 +132,10 @@ public class PipResizeGestureHandler { mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable; mPhonePipMenuController = menuActivityController; mPipUiEventLogger = pipUiEventLogger; + } - context.getDisplay().getRealSize(mMaxSize); + public void init() { + mContext.getDisplay().getRealSize(mMaxSize); reloadResources(); mEnablePinchResize = DeviceConfig.getBoolean( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 5e23281b3438..543ecfcf1a33 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -71,12 +71,13 @@ public class PipTouchHandler { private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f; // Allow PIP to resize to a slightly bigger state upon touch - private final boolean mEnableResize; + private boolean mEnableResize; private final Context mContext; private final PipBoundsAlgorithm mPipBoundsAlgorithm; private final @NonNull PipBoundsState mPipBoundsState; private final PipUiEventLogger mPipUiEventLogger; private final PipDismissTargetHandler mPipDismissTargetHandler; + private final ShellExecutor mMainExecutor; private PipResizeGestureHandler mPipResizeGestureHandler; private WeakReference<Consumer<Rect>> mPipExclusionBoundsChangeListener; @@ -166,16 +167,18 @@ public class PipTouchHandler { ShellExecutor mainExecutor) { // Initialize the Pip input consumer mContext = context; + mMainExecutor = mainExecutor; mAccessibilityManager = context.getSystemService(AccessibilityManager.class); mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipBoundsState = pipBoundsState; mMenuController = menuController; mPipUiEventLogger = pipUiEventLogger; + mFloatingContentCoordinator = floatingContentCoordinator; mMenuController.addListener(new PipMenuListener()); mGesture = new DefaultPipTouchGesture(); mMotionHelper = new PipMotionHelper(mContext, pipBoundsState, pipTaskOrganizer, mMenuController, mPipBoundsAlgorithm.getSnapAlgorithm(), pipTransitionController, - floatingContentCoordinator, mainExecutor); + floatingContentCoordinator); mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState, mMotionHelper, pipTaskOrganizer, this::getMovementBounds, @@ -199,22 +202,26 @@ public class PipTouchHandler { }, menuController::hideMenu, mainExecutor); + mConnection = new PipAccessibilityInteractionConnection(mContext, pipBoundsState, + mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(), + this::onAccessibilityShowMenu, this::updateMovementBounds, mainExecutor); + } - Resources res = context.getResources(); + public void init() { + Resources res = mContext.getResources(); mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu); reloadResources(); - mFloatingContentCoordinator = floatingContentCoordinator; - mConnection = new PipAccessibilityInteractionConnection(mContext, pipBoundsState, - mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(), - this::onAccessibilityShowMenu, this::updateMovementBounds, mainExecutor); + mMotionHelper.init(); + mPipResizeGestureHandler.init(); + mPipDismissTargetHandler.init(); mEnableStash = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_SYSTEMUI, PIP_STASHING, /* defaultValue = */ true); DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, - mainExecutor, + mMainExecutor, properties -> { if (properties.getKeyset().contains(PIP_STASHING)) { mEnableStash = properties.getBoolean( @@ -226,7 +233,7 @@ public class PipTouchHandler { PIP_STASH_MINIMUM_VELOCITY_THRESHOLD, DEFAULT_STASH_VELOCITY_THRESHOLD); DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, - mainExecutor, + mMainExecutor, properties -> { if (properties.getKeyset().contains(PIP_STASH_MINIMUM_VELOCITY_THRESHOLD)) { mStashVelocityThreshold = properties.getFloat( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java index 32f3648be19a..c6d994ecde8d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java @@ -280,7 +280,7 @@ class SizeCompatUILayout { : stableBounds.right - taskBounds.left - mButtonSize; final int positionY = stableBounds.bottom - taskBounds.top - mButtonSize; - mSyncQueue.runInSync(t -> t.setPosition(leash, positionX, positionY)); + updateSurfacePosition(leash, positionX, positionY); } void updateHintSurfacePosition() { @@ -303,7 +303,16 @@ class SizeCompatUILayout { final int positionY = stableBounds.bottom - taskBounds.top - mPopupOffsetY - mHint.getMeasuredHeight(); - mSyncQueue.runInSync(t -> t.setPosition(leash, positionX, positionY)); + updateSurfacePosition(leash, positionX, positionY); + } + + private void updateSurfacePosition(SurfaceControl leash, int positionX, int positionY) { + mSyncQueue.runInSync(t -> { + t.setPosition(leash, positionX, positionY); + // The size compat UI should be the topmost child of the Task in case there can be more + // than one children. + t.setLayer(leash, Integer.MAX_VALUE); + }); } int getDisplayId() { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index d10c03677d30..79ec624a1557 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -43,7 +43,6 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.pip.phone.PhonePipMenuController; @@ -125,7 +124,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { @Test public void startSwipePipToHome_updatesOverrideMinSize() { - final Size minSize = new Size(100, 80); + final Size minSize = new Size(400, 320); mSpiedPipTaskOrganizer.startSwipePipToHome(mComponent1, createActivityInfo(minSize), createPipParams(null)); @@ -153,7 +152,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { @Test public void onTaskAppeared_updatesOverrideMinSize() { - final Size minSize = new Size(100, 80); + final Size minSize = new Size(400, 320); mSpiedPipTaskOrganizer.onTaskAppeared( createTaskInfo(mComponent1, createPipParams(null), minSize), @@ -191,7 +190,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1, createPipParams(null)), null /* leash */); - final Size minSize = new Size(100, 80); + final Size minSize = new Size(400, 320); mSpiedPipTaskOrganizer.onTaskInfoChanged(createTaskInfo(mComponent2, createPipParams(null), minSize)); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java index 19930485047c..75ea4ac94257 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java @@ -106,6 +106,7 @@ public class PipTouchHandlerTest extends ShellTestCase { mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer, mMockPipTransitionController, mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor); + mPipTouchHandler.init(); mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper()); mPipResizeGestureHandler = Mockito.spy(mPipTouchHandler.getPipResizeGestureHandler()); mPipTouchHandler.setPipMotionHelper(mMotionHelper); diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 77ceda91898e..d663c52b2c08 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -421,7 +421,6 @@ cc_defaults { "libstatspull", "libstatssocket", "libpdfium", - "libbinder_ndk", ], static_libs: [ "libgif", diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h index 912d04c5d87d..e9b2f4a9bb3b 100644 --- a/libs/hwui/FrameInfo.h +++ b/libs/hwui/FrameInfo.h @@ -80,6 +80,10 @@ public: explicit UiFrameInfoBuilder(int64_t* buffer) : mBuffer(buffer) { memset(mBuffer, 0, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t)); set(FrameInfoIndex::FrameTimelineVsyncId) = INVALID_VSYNC_ID; + // The struct is zeroed by memset above. That also sets FrameInfoIndex::InputEventId to + // equal android::os::IInputConstants::INVALID_INPUT_EVENT_ID == 0. + // Therefore, we can skip setting the value for InputEventId here. If the value for + // INVALID_INPUT_EVENT_ID changes, this code would have to be updated, as well. set(FrameInfoIndex::FrameDeadline) = std::numeric_limits<int64_t>::max(); } diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index af7271e96cb9..61f9960c4d8d 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -176,9 +176,6 @@ void SkiaRecordingCanvas::FilterForImage(SkPaint& paint) { if (sApiLevel <= 27 && paint.getBlendMode() == SkBlendMode::kClear) { paint.setBlendMode(SkBlendMode::kDstOut); } - - // disabling AA on bitmap draws matches legacy HWUI behavior - paint.setAntiAlias(false); } static SkFilterMode Paint_to_filter(const SkPaint& paint) { diff --git a/media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl b/media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl index cee3635a1e3b..8186fb741b59 100644 --- a/media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl @@ -32,8 +32,8 @@ parcelable SoundModel { * Unique vendor ID. Identifies the engine the sound model * was build for */ String vendorUuid; - /** Opaque data transparent to Android framework */ - ParcelFileDescriptor data; + /** Opaque data transparent to Android framework. May be null if dataSize is 0. */ + @nullable ParcelFileDescriptor data; /** Size of the above data, in bytes. */ int dataSize; } diff --git a/media/java/android/media/AudioMetadata.java b/media/java/android/media/AudioMetadata.java index ff9fd4187272..ca175b4853a6 100644 --- a/media/java/android/media/AudioMetadata.java +++ b/media/java/android/media/AudioMetadata.java @@ -195,6 +195,61 @@ public final class AudioMetadata { @NonNull public static final Key<Integer> KEY_AUDIO_ENCODING = createKey("audio-encoding", Integer.class); + + /** + * A key representing the audio presentation id being decoded by a next generation + * audio decoder. + * + * An Integer value representing presentation id. + * + * @see AudioPresentation#getPresentationId() + */ + @NonNull public static final Key<Integer> KEY_PRESENTATION_ID = + createKey("presentation-id", Integer.class); + + /** + * A key representing the audio program id being decoded by a next generation + * audio decoder. + * + * An Integer value representing program id. + * + * @see AudioPresentation#getProgramId() + */ + @NonNull public static final Key<Integer> KEY_PROGRAM_ID = + createKey("program-id", Integer.class); + + + /** + * A key representing the audio presentation content classifier being rendered + * by a next generation audio decoder. + * + * An Integer value representing presentation content classifier. + * + * @see AudioPresentation.ContentClassifier + * One of {@link AudioPresentation#CONTENT_UNKNOWN}, + * {@link AudioPresentation#CONTENT_MAIN}, + * {@link AudioPresentation#CONTENT_MUSIC_AND_EFFECTS}, + * {@link AudioPresentation#CONTENT_VISUALLY_IMPAIRED}, + * {@link AudioPresentation#CONTENT_HEARING_IMPAIRED}, + * {@link AudioPresentation#CONTENT_DIALOG}, + * {@link AudioPresentation#CONTENT_COMMENTARY}, + * {@link AudioPresentation#CONTENT_EMERGENCY}, + * {@link AudioPresentation#CONTENT_VOICEOVER}. + */ + @NonNull public static final Key<Integer> KEY_PRESENTATION_CONTENT_CLASSIFIER = + createKey("presentation-content-classifier", Integer.class); + + /** + * A key representing the audio presentation language being rendered by a next + * generation audio decoder. + * + * A String value representing ISO 639-2 (three letter code). + * + * @see AudioPresentation#getLocale() + */ + @NonNull public static final Key<String> KEY_PRESENTATION_LANGUAGE = + createKey("presentation-language", String.class); + private Format() {} // delete constructor } diff --git a/media/java/android/media/AudioPresentation.java b/media/java/android/media/AudioPresentation.java index 894fbba6c983..47358be3e926 100644 --- a/media/java/android/media/AudioPresentation.java +++ b/media/java/android/media/AudioPresentation.java @@ -57,6 +57,64 @@ public final class AudioPresentation { /** @hide */ @IntDef( value = { + CONTENT_UNKNOWN, + CONTENT_MAIN, + CONTENT_MUSIC_AND_EFFECTS, + CONTENT_VISUALLY_IMPAIRED, + CONTENT_HEARING_IMPAIRED, + CONTENT_DIALOG, + CONTENT_COMMENTARY, + CONTENT_EMERGENCY, + CONTENT_VOICEOVER, + }) + + /** + * The ContentClassifier int definitions represent the AudioPresentation content + * classifier (as per TS 103 190-1 v1.2.1 4.3.3.8.1) + */ + @Retention(RetentionPolicy.SOURCE) + public @interface ContentClassifier {} + + /** + * Audio presentation classifier: Unknown. + */ + public static final int CONTENT_UNKNOWN = -1; + /** + * Audio presentation classifier: Complete main. + */ + public static final int CONTENT_MAIN = 0; + /** + * Audio presentation content classifier: Music and effects. + */ + public static final int CONTENT_MUSIC_AND_EFFECTS = 1; + /** + * Audio presentation content classifier: Visually impaired. + */ + public static final int CONTENT_VISUALLY_IMPAIRED = 2; + /** + * Audio presentation content classifier: Hearing impaired. + */ + public static final int CONTENT_HEARING_IMPAIRED = 3; + /** + * Audio presentation content classifier: Dialog. + */ + public static final int CONTENT_DIALOG = 4; + /** + * Audio presentation content classifier: Commentary. + */ + public static final int CONTENT_COMMENTARY = 5; + /** + * Audio presentation content classifier: Emergency. + */ + public static final int CONTENT_EMERGENCY = 6; + /** + * Audio presentation content classifier: Voice over. + */ + public static final int CONTENT_VOICEOVER = 7; + + /** @hide */ + @IntDef( + value = { MASTERING_NOT_INDICATED, MASTERED_FOR_STEREO, MASTERED_FOR_SURROUND, diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index 56f6c45bb50e..53f6fe24556b 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -445,8 +445,7 @@ static bool throwExceptionAsNecessary( jniThrowException(env, "android/media/DeniedByServerException", msg); return true; } else if (err == DEAD_OBJECT) { - jniThrowException(env, "android/media/MediaDrmResetException", - "mediaserver died"); + jniThrowException(env, "android/media/MediaDrmResetException", msg); return true; } else if (isSessionException(err)) { throwSessionException(env, msg, err); @@ -967,10 +966,12 @@ static void android_media_MediaDrm_native_setup( status_t err = drm->initCheck(); if (err != OK) { + auto logs(DrmUtils::gLogBuf.getLogs()); + auto msg(DrmUtils::GetExceptionMessage(err, "Failed to instantiate drm object", logs)); jniThrowException( env, "android/media/UnsupportedSchemeException", - "Failed to instantiate drm object."); + msg.c_str()); return; } diff --git a/packages/Connectivity/framework/api/current.txt b/packages/Connectivity/framework/api/current.txt index 31b8fc8ae53a..a8f1a4d2a7f8 100644 --- a/packages/Connectivity/framework/api/current.txt +++ b/packages/Connectivity/framework/api/current.txt @@ -401,16 +401,6 @@ package android.net { method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier); } - public final class Proxy { - ctor public Proxy(); - method @Deprecated public static String getDefaultHost(); - method @Deprecated public static int getDefaultPort(); - method @Deprecated public static String getHost(android.content.Context); - method @Deprecated public static int getPort(android.content.Context); - field @Deprecated public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO"; - field public static final String PROXY_CHANGE_ACTION = "android.intent.action.PROXY_CHANGE"; - } - public class ProxyInfo implements android.os.Parcelable { ctor public ProxyInfo(@Nullable android.net.ProxyInfo); method public static android.net.ProxyInfo buildDirectProxy(String, int); diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt index 3af855ec1e11..a9fd6f248560 100644 --- a/packages/Connectivity/framework/api/module-lib-current.txt +++ b/packages/Connectivity/framework/api/module-lib-current.txt @@ -23,10 +23,6 @@ package android.net { field public static final int TRANSPORT_TEST = 7; // 0x7 } - public final class Proxy { - method public static void setHttpProxyConfiguration(@Nullable android.net.ProxyInfo); - } - public final class TcpRepairWindow { ctor public TcpRepairWindow(int, int, int, int, int, int); field public final int maxWindow; diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt index 373fa3c24027..f5972fa34042 100644 --- a/packages/Connectivity/framework/api/system-current.txt +++ b/packages/Connectivity/framework/api/system-current.txt @@ -2,7 +2,7 @@ package android.net { public class CaptivePortal implements android.os.Parcelable { - method public void logEvent(int, @NonNull String); + method @Deprecated public void logEvent(int, @NonNull String); method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork(); method public void useNetwork(); field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64 diff --git a/packages/Connectivity/framework/src/android/net/CaptivePortal.java b/packages/Connectivity/framework/src/android/net/CaptivePortal.java index 269bbf20c8b1..4a7b6016427b 100644 --- a/packages/Connectivity/framework/src/android/net/CaptivePortal.java +++ b/packages/Connectivity/framework/src/android/net/CaptivePortal.java @@ -160,12 +160,11 @@ public class CaptivePortal implements Parcelable { * @param eventId one of the CAPTIVE_PORTAL_LOGIN_* constants in metrics_constants.proto. * @param packageName captive portal application package name. * @hide + * @deprecated The event will not be logged in Android S and above. The + * caller is migrating to statsd. */ + @Deprecated @SystemApi public void logEvent(int eventId, @NonNull String packageName) { - try { - ICaptivePortal.Stub.asInterface(mBinder).logEvent(eventId, packageName); - } catch (RemoteException e) { - } } } diff --git a/packages/Connectivity/framework/src/android/net/ICaptivePortal.aidl b/packages/Connectivity/framework/src/android/net/ICaptivePortal.aidl index fe21905c7002..e35f8d46afe7 100644 --- a/packages/Connectivity/framework/src/android/net/ICaptivePortal.aidl +++ b/packages/Connectivity/framework/src/android/net/ICaptivePortal.aidl @@ -23,5 +23,4 @@ package android.net; oneway interface ICaptivePortal { void appRequest(int request); void appResponse(int response); - void logEvent(int eventId, String packageName); } diff --git a/core/java/android/net/IOnSetOemNetworkPreferenceListener.aidl b/packages/Connectivity/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl index 7979afc54f90..7979afc54f90 100644 --- a/core/java/android/net/IOnSetOemNetworkPreferenceListener.aidl +++ b/packages/Connectivity/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl diff --git a/core/java/android/net/UidRange.aidl b/packages/Connectivity/framework/src/android/net/UidRange.aidl index f70fc8e2fefd..f70fc8e2fefd 100644 --- a/core/java/android/net/UidRange.aidl +++ b/packages/Connectivity/framework/src/android/net/UidRange.aidl diff --git a/core/java/android/net/UidRange.java b/packages/Connectivity/framework/src/android/net/UidRange.java index be5964dca36e..26518d32edcb 100644 --- a/core/java/android/net/UidRange.java +++ b/packages/Connectivity/framework/src/android/net/UidRange.java @@ -16,8 +16,6 @@ package android.net; -import static android.os.UserHandle.PER_USER_RANGE; - import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -60,6 +58,7 @@ public final class UidRange implements Parcelable { return UserHandle.getUserHandleForUid(stop).getIdentifier(); } + /** Returns whether the UidRange contains the specified UID. */ public boolean contains(int uid) { return start <= uid && uid <= stop; } @@ -72,7 +71,7 @@ public final class UidRange implements Parcelable { } /** - * @return {@code true} if this range contains every UID contained by the {@param other} range. + * @return {@code true} if this range contains every UID contained by the {@code other} range. */ public boolean containsRange(UidRange other) { return start <= other.start && other.stop <= stop; @@ -118,18 +117,18 @@ public final class UidRange implements Parcelable { } public static final @android.annotation.NonNull Creator<UidRange> CREATOR = - new Creator<UidRange>() { - @Override - public UidRange createFromParcel(Parcel in) { - int start = in.readInt(); - int stop = in.readInt(); + new Creator<UidRange>() { + @Override + public UidRange createFromParcel(Parcel in) { + int start = in.readInt(); + int stop = in.readInt(); - return new UidRange(start, stop); - } - @Override - public UidRange[] newArray(int size) { - return new UidRange[size]; - } + return new UidRange(start, stop); + } + @Override + public UidRange[] newArray(int size) { + return new UidRange[size]; + } }; /** diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp index f20b89fb842c..e65b7b423bdc 100644 --- a/packages/Connectivity/service/Android.bp +++ b/packages/Connectivity/service/Android.bp @@ -63,6 +63,7 @@ java_library { "unsupportedappusage", ], static_libs: [ + "modules-utils-os", "net-utils-device-common", "net-utils-framework-common", "netd-client", diff --git a/packages/Connectivity/service/jarjar-rules.txt b/packages/Connectivity/service/jarjar-rules.txt index ef53ebb43c40..d8205bf780fd 100644 --- a/packages/Connectivity/service/jarjar-rules.txt +++ b/packages/Connectivity/service/jarjar-rules.txt @@ -1 +1,2 @@ -rule com.android.net.module.util.** com.android.connectivity.util.@1
\ No newline at end of file +rule com.android.net.module.util.** com.android.connectivity.net-utils.@1 +rule com.android.modules.utils.** com.android.connectivity.modules-utils.@1
\ No newline at end of file diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java index 7f19662c6961..c1dca5df1b2f 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java @@ -394,16 +394,20 @@ public class DynamicSystemInstallationService extends Service } private void executeNotifyIfInUseCommand() { - int status = getStatus(); - - if (status == STATUS_IN_USE) { - startForeground(NOTIFICATION_ID, - buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED)); - } else if (status == STATUS_READY) { - startForeground(NOTIFICATION_ID, - buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED)); - } else { - stopSelf(); + switch (getStatus()) { + case STATUS_IN_USE: + startForeground(NOTIFICATION_ID, + buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED)); + break; + case STATUS_READY: + startForeground(NOTIFICATION_ID, + buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED)); + break; + case STATUS_IN_PROGRESS: + break; + case STATUS_NOT_STARTED: + default: + stopSelf(); } } diff --git a/packages/LocalTransport/OWNERS b/packages/LocalTransport/OWNERS new file mode 100644 index 000000000000..d99779e3d9da --- /dev/null +++ b/packages/LocalTransport/OWNERS @@ -0,0 +1 @@ +include /services/backup/OWNERS diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java index 139c8e59a148..63edc776b3cb 100644 --- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java +++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java @@ -165,6 +165,9 @@ public class LocalTransport extends BackupTransport { if (mParameters.isDeviceTransfer()) { flags |= BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER; } + if (mParameters.isEncrypted()) { + flags |= BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED; + } return flags; } diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java index 2946db3cdcf0..1ba1bc6bfec7 100644 --- a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java +++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java @@ -28,10 +28,12 @@ public class LocalTransportParameters extends KeyValueSettingObserver { private static final String KEY_FAKE_ENCRYPTION_FLAG = "fake_encryption_flag"; private static final String KEY_NON_INCREMENTAL_ONLY = "non_incremental_only"; private static final String KEY_IS_DEVICE_TRANSFER = "is_device_transfer"; + private static final String KEY_IS_ENCRYPTED = "is_encrypted"; private boolean mFakeEncryptionFlag; private boolean mIsNonIncrementalOnly; private boolean mIsDeviceTransfer; + private boolean mIsEncrypted; public LocalTransportParameters(Handler handler, ContentResolver resolver) { super(handler, resolver, Settings.Secure.getUriFor(SETTING)); @@ -49,6 +51,10 @@ public class LocalTransportParameters extends KeyValueSettingObserver { return mIsDeviceTransfer; } + boolean isEncrypted() { + return mIsEncrypted; + } + public String getSettingValue(ContentResolver resolver) { return Settings.Secure.getString(resolver, SETTING); } @@ -57,5 +63,6 @@ public class LocalTransportParameters extends KeyValueSettingObserver { mFakeEncryptionFlag = parser.getBoolean(KEY_FAKE_ENCRYPTION_FLAG, false); mIsNonIncrementalOnly = parser.getBoolean(KEY_NON_INCREMENTAL_ONLY, false); mIsDeviceTransfer = parser.getBoolean(KEY_IS_DEVICE_TRANSFER, false); + mIsEncrypted = parser.getBoolean(KEY_IS_ENCRYPTED, false); } } diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml new file mode 100644 index 000000000000..c799b9962828 --- /dev/null +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<!-- The main content view --> +<LinearLayout + android:id="@+id/content_parent" + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fitsSystemWindows="true" + android:transitionGroup="true" + android:orientation="vertical"> + <Toolbar + android:id="@+id/action_bar" + style="?android:attr/actionBarStyle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:theme="?android:attr/actionBarTheme" /> + <FrameLayout + android:id="@+id/content_frame" + android:layout_width="match_parent" + android:layout_height="match_parent"/> +</LinearLayout> diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java index 637805fc81c3..ad94cd0318a7 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java @@ -24,6 +24,7 @@ import android.view.ViewGroup; import android.widget.Toolbar; import androidx.annotation.Nullable; +import androidx.core.os.BuildCompat; import androidx.fragment.app.FragmentActivity; import com.google.android.material.appbar.CollapsingToolbarLayout; @@ -40,8 +41,15 @@ public class CollapsingToolbarBaseActivity extends FragmentActivity { protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - super.setContentView(R.layout.collapsing_toolbar_base_layout); - mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar); + // TODO(b/181723278): Update the version check after SDK for S is finalized + // The collapsing toolbar is only supported if the android platform version is S or higher. + // Otherwise the regular action bar will be shown. + if (BuildCompat.isAtLeastS()) { + super.setContentView(R.layout.collapsing_toolbar_base_layout); + mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar); + } else { + super.setContentView(R.layout.toolbar_base_layout); + } final Toolbar toolbar = findViewById(R.id.action_bar); setActionBar(toolbar); @@ -90,6 +98,14 @@ public class CollapsingToolbarBaseActivity extends FragmentActivity { super.setTitle(titleId); } + @Override + public boolean onNavigateUp() { + if (!super.onNavigateUp()) { + finish(); + } + return true; + } + /** * Returns an instance of collapsing toolbar. */ diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 79fbcc376b3c..96241194402a 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1059,7 +1059,14 @@ <!-- Title for the accessibility preference to configure display color space correction. [CHAR LIMIT=NONE] --> <string name="accessibility_display_daltonizer_preference_title">Color correction</string> <!-- Subtitle for the accessibility preference to configure display color space correction. [CHAR LIMIT=NONE] --> - <string name="accessibility_display_daltonizer_preference_subtitle"><![CDATA[Color correction allows you to adjust how colors are displayed on your device]]></string> + <string name="accessibility_display_daltonizer_preference_subtitle"> + <![CDATA[ + Adjust how colors display on your device. This can be helpful when you want to:<br/><br/> + <ol> + <li> See colors more accurately</li> + <li> Remove colors to help you focus</li> + </ol> + ]]></string> <!-- Summary shown for color space correction preference when its value is overridden by another preference [CHAR LIMIT=35] --> <string name="daltonizer_type_overridden">Overridden by <xliff:g id="title" example="Simulate color space">%1$s</xliff:g></string> @@ -1295,6 +1302,8 @@ <!-- Name of the phone device. [CHAR LIMIT=30] --> <string name="media_transfer_this_device_name">Phone speaker</string> + <!-- Name of the phone device with an active remote session. [CHAR LIMIT=30] --> + <string name="media_transfer_this_phone">This phone</string> <!-- Warning message to tell user is have problem during profile connect, it need to turn off device and back on. [CHAR_LIMIT=NONE] --> <string name="profile_connect_timeout_subtext">Problem connecting. Turn device off & back on</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java index 228de039fc1b..35499c9b449a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java +++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java @@ -80,7 +80,8 @@ public class RecentLocationAccesses { * Fills a list of applications which queried location recently within specified time. * Apps are sorted by recency. Apps with more recent location accesses are in the front. */ - public List<Access> getAppList() { + @VisibleForTesting + List<Access> getAppList(boolean showSystemApps) { // Retrieve a location usage list from AppOps PackageManager pm = mContext.getPackageManager(); AppOpsManager aoManager = @@ -108,22 +109,26 @@ public class RecentLocationAccesses { // Don't show apps that do not have user sensitive location permissions boolean showApp = true; - for (int op : LOCATION_OPS) { - final String permission = AppOpsManager.opToPermission(op); - final int permissionFlags = pm.getPermissionFlags(permission, packageName, user); - if (PermissionChecker.checkPermissionForPreflight(mContext, permission, - PermissionChecker.PID_UNKNOWN, uid, packageName) - == PermissionChecker.PERMISSION_GRANTED) { - if ((permissionFlags - & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) == 0) { - showApp = false; - break; - } - } else { - if ((permissionFlags - & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) == 0) { - showApp = false; - break; + if (!showSystemApps) { + for (int op : LOCATION_OPS) { + final String permission = AppOpsManager.opToPermission(op); + final int permissionFlags = pm.getPermissionFlags(permission, packageName, + user); + if (PermissionChecker.checkPermissionForPreflight(mContext, permission, + PermissionChecker.PID_UNKNOWN, uid, packageName) + == PermissionChecker.PERMISSION_GRANTED) { + if ((permissionFlags + & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) + == 0) { + showApp = false; + break; + } + } else { + if ((permissionFlags + & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) == 0) { + showApp = false; + break; + } } } } @@ -137,8 +142,15 @@ public class RecentLocationAccesses { return accesses; } - public List<Access> getAppListSorted() { - List<Access> accesses = getAppList(); + + /** + * Gets a list of apps that accessed location recently, sorting by recency. + * + * @param showSystemApps whether includes system apps in the list. + * @return the list of apps that recently accessed location. + */ + public List<Access> getAppListSorted(boolean showSystemApps) { + List<Access> accesses = getAppList(showSystemApps); // Sort the list of Access by recency. Most recent accesses first. Collections.sort(accesses, Collections.reverseOrder(new Comparator<Access>() { @Override diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAccessesTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAccessesTest.java index 245b7843110b..16d73a39d551 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAccessesTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAccessesTest.java @@ -86,7 +86,7 @@ public class RecentLocationAccessesTest { @Test @Ignore public void testGetAppList_shouldFilterRecentAccesses() { - List<RecentLocationAccesses.Access> requests = mRecentLocationAccesses.getAppList(); + List<RecentLocationAccesses.Access> requests = mRecentLocationAccesses.getAppList(false); // Only two of the apps have requested location within 15 min. assertThat(requests).hasSize(2); // Make sure apps are ordered by recency @@ -115,7 +115,7 @@ public class RecentLocationAccessesTest { mockTestApplicationInfos( Process.SYSTEM_UID, RecentLocationAccesses.ANDROID_SYSTEM_PACKAGE_NAME); - List<RecentLocationAccesses.Access> requests = mRecentLocationAccesses.getAppList(); + List<RecentLocationAccesses.Access> requests = mRecentLocationAccesses.getAppList(true); // Android OS shouldn't show up in the list of apps. assertThat(requests).hasSize(2); // Make sure apps are ordered by recency diff --git a/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml b/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml index 6f91d770012a..edfc0aba5be7 100644 --- a/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml +++ b/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml @@ -20,6 +20,7 @@ Make the visibility to "gone" to prevent failures. --> <TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/add_new_sound_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="?android:attr/listPreferredItemHeightSmall" diff --git a/packages/SystemUI/res/layout/quick_settings_footer.xml b/packages/SystemUI/res/layout/quick_settings_footer.xml index 13572fa9f4f3..db712e4b9f7a 100644 --- a/packages/SystemUI/res/layout/quick_settings_footer.xml +++ b/packages/SystemUI/res/layout/quick_settings_footer.xml @@ -17,6 +17,7 @@ <com.android.systemui.util.NeverExactlyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:minHeight="48dp" android:clickable="true" android:paddingBottom="@dimen/qs_tile_padding_top" android:paddingTop="@dimen/qs_tile_padding_top" diff --git a/packages/SystemUI/res/layout/udfps_animation_view_enroll.xml b/packages/SystemUI/res/layout/udfps_animation_view_enroll.xml new file mode 100644 index 000000000000..9b5752d2de59 --- /dev/null +++ b/packages/SystemUI/res/layout/udfps_animation_view_enroll.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<com.android.systemui.biometrics.UdfpsAnimationViewEnroll + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/udfps_animation_view" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <!-- Enrollment progress bar--> + <com.android.systemui.biometrics.UdfpsProgressBar + android:id="@+id/progress_bar" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:max="100" + android:padding="@dimen/udfps_enroll_progress_thickness" + android:progress="0" + android:layout_gravity="center" + android:visibility="gone"/> + +</com.android.systemui.biometrics.UdfpsAnimationViewEnroll> diff --git a/packages/SystemUI/res/layout/udfps_animation_view.xml b/packages/SystemUI/res/layout/udfps_animation_view_fpm_other.xml index 380dd855ffe1..f32faa0df867 100644 --- a/packages/SystemUI/res/layout/udfps_animation_view.xml +++ b/packages/SystemUI/res/layout/udfps_animation_view_fpm_other.xml @@ -14,8 +14,9 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.android.systemui.biometrics.UdfpsAnimationView +<com.android.systemui.biometrics.UdfpsAnimationViewFpmOther xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/udfps_animation_view" android:layout_width="match_parent" - android:layout_height="match_parent"/> + android:layout_height="match_parent"> +</com.android.systemui.biometrics.UdfpsAnimationViewFpmOther> diff --git a/packages/SystemUI/res/layout/udfps_animation_view_keyguard.xml b/packages/SystemUI/res/layout/udfps_animation_view_keyguard.xml new file mode 100644 index 000000000000..644d1adac46b --- /dev/null +++ b/packages/SystemUI/res/layout/udfps_animation_view_keyguard.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<com.android.systemui.biometrics.UdfpsAnimationViewKeyguard + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/udfps_animation_view" + android:layout_width="match_parent" + android:layout_height="match_parent"> +</com.android.systemui.biometrics.UdfpsAnimationViewKeyguard> diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml index 6ae306e17209..e24c9e99a405 100644 --- a/packages/SystemUI/res/layout/udfps_view.xml +++ b/packages/SystemUI/res/layout/udfps_view.xml @@ -22,15 +22,10 @@ android:layout_height="match_parent" systemui:sensorTouchAreaCoefficient="0.5"> - <!-- Enrollment progress bar--> - <com.android.systemui.biometrics.UdfpsProgressBar - android:id="@+id/progress_bar" + <com.android.systemui.biometrics.UdfpsSurfaceView + android:id="@+id/hbm_view" android:layout_width="match_parent" android:layout_height="match_parent" - android:max="100" - android:padding="@dimen/udfps_enroll_progress_thickness" - android:progress="0" - android:layout_gravity="center" - android:visibility="gone"/> + android:visibility="invisible"/> </com.android.systemui.biometrics.UdfpsView> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 761512ce3d14..b75a0bc1525a 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -91,9 +91,6 @@ <!-- The number of columns in the QuickSettings --> <integer name="quick_settings_num_columns">3</integer> - <!-- The number of columns in the Quick Settings customizer --> - <integer name="quick_settings_edit_num_columns">@integer/quick_settings_num_columns</integer> - <!-- The number of rows in the QuickSettings --> <integer name="quick_settings_max_rows">3</integer> @@ -415,6 +412,9 @@ <!-- Whether or not child notifications that are part of a group will have shadows. --> <bool name="config_enableShadowOnChildNotifications">true</bool> + <!-- If true, group numbers are shown in the expander instead of via "+N" overflow number --> + <bool name="config_showNotificationGroupCountInExpander">true</bool> + <!-- Whether or not a view containing child notifications will have a custom background when it has been expanded to reveal its children. --> <bool name="config_showGroupNotificationBgWhenExpanded">false</bool> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 2d202fb45bbc..ec26b8d7a6a8 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -608,6 +608,11 @@ <item name="android:windowCloseOnTouchOutside">true</item> </style> + <!-- Privacy dialog --> + <style name="PrivacyDialog" parent="ScreenRecord"> + <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item> + </style> + <!-- USB Contaminant dialog --> <style name ="USBContaminant" /> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index 2373d75cd4ea..83c2d1e7f684 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -405,14 +405,19 @@ public class KeyguardSliceView extends LinearLayout { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } + /** + * Set the amount (ratio) that the device has transitioned to doze. + * + * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake. + */ public void setDarkAmount(float darkAmount) { - boolean isAwake = darkAmount != 0; - boolean wasAwake = mDarkAmount != 0; - if (isAwake == wasAwake) { + boolean isDozing = darkAmount != 0; + boolean wasDozing = mDarkAmount != 0; + if (isDozing == wasDozing) { return; } mDarkAmount = darkAmount; - setLayoutAnimationListener(isAwake ? null : mKeepAwakeListener); + setLayoutAnimationListener(isDozing ? null : mKeepAwakeListener); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java index 3bf75d105b9f..a02900328ae7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java @@ -31,15 +31,18 @@ import com.android.systemui.R; * sensor area. */ public abstract class UdfpsAnimation extends Drawable { - abstract void updateColor(); + protected abstract void updateColor(); + protected abstract void onDestroy(); @NonNull protected final Context mContext; @NonNull protected final Drawable mFingerprintDrawable; @Nullable private View mView; + private boolean mIlluminationShowing; public UdfpsAnimation(@NonNull Context context) { mContext = context; mFingerprintDrawable = context.getResources().getDrawable(R.drawable.ic_fingerprint, null); + mFingerprintDrawable.mutate(); } public void onSensorRectUpdated(@NonNull RectF sensorRect) { @@ -60,6 +63,14 @@ public abstract class UdfpsAnimation extends Drawable { mView = view; } + boolean isIlluminationShowing() { + return mIlluminationShowing; + } + + void setIlluminationShowing(boolean showing) { + mIlluminationShowing = showing; + } + /** * @return The amount of padding that's needed on each side of the sensor, in pixels. */ diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java index 5290986b2a1c..28b57195c5d4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java @@ -58,11 +58,16 @@ public class UdfpsAnimationEnroll extends UdfpsAnimation { } @Override - void updateColor() { + protected void updateColor() { mFingerprintDrawable.setTint(mContext.getColor(R.color.udfps_enroll_icon)); } @Override + protected void onDestroy() { + + } + + @Override public void onSensorRectUpdated(@NonNull RectF sensorRect) { super.onSensorRectUpdated(sensorRect); mSensorRect = sensorRect; @@ -70,6 +75,10 @@ public class UdfpsAnimationEnroll extends UdfpsAnimation { @Override public void draw(@NonNull Canvas canvas) { + if (isIlluminationShowing()) { + return; + } + final boolean isNightMode = (mContext.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) != 0; if (!isNightMode) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java index efc864ade5ff..ef7a34000841 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java @@ -34,12 +34,21 @@ public class UdfpsAnimationFpmOther extends UdfpsAnimation { } @Override - void updateColor() { + protected void updateColor() { + + } + + @Override + protected void onDestroy() { } @Override public void draw(@NonNull Canvas canvas) { + if (isIlluminationShowing()) { + return; + } + mFingerprintDrawable.draw(canvas); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java index 8664e44c9ad2..5f268cfa8fa5 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java @@ -42,6 +42,7 @@ public class UdfpsAnimationKeyguard extends UdfpsAnimation implements DozeReceiv private static final String TAG = "UdfpsAnimationKeyguard"; @NonNull private final Context mContext; + @NonNull private final StatusBarStateController mStatusBarStateController; private final int mMaxBurnInOffsetX; private final int mMaxBurnInOffsetY; @@ -54,6 +55,7 @@ public class UdfpsAnimationKeyguard extends UdfpsAnimation implements DozeReceiv @NonNull StatusBarStateController statusBarStateController) { super(context); mContext = context; + mStatusBarStateController = statusBarStateController; mMaxBurnInOffsetX = context.getResources() .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x); @@ -89,6 +91,10 @@ public class UdfpsAnimationKeyguard extends UdfpsAnimation implements DozeReceiv @Override public void draw(@NonNull Canvas canvas) { + if (isIlluminationShowing()) { + return; + } + canvas.save(); canvas.translate(mBurnInOffsetX, mBurnInOffsetY); mFingerprintDrawable.draw(canvas); @@ -106,11 +112,16 @@ public class UdfpsAnimationKeyguard extends UdfpsAnimation implements DozeReceiv } @Override - public void updateColor() { + protected void updateColor() { final int lockScreenIconColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor); final int ambientDisplayIconColor = Color.WHITE; mFingerprintDrawable.setTint(ColorUtils.blendARGB(lockScreenIconColor, ambientDisplayIconColor, mInterpolatedDarkAmount)); } + + @Override + protected void onDestroy() { + mStatusBarStateController.removeCallback(this); + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java index 44122cba8716..f4dd181eb329 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java @@ -22,41 +22,49 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.RectF; import android.util.AttributeSet; -import android.view.View; +import android.widget.FrameLayout; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.statusbar.phone.StatusBar; /** - * Class that coordinates non-HBM animations (such as enroll, keyguard, BiometricPrompt, - * FingerprintManager). + * Base class for views containing UDFPS animations. Note that this is a FrameLayout so that we + * can support multiple child views drawing on the same region around the sensor location. */ -public class UdfpsAnimationView extends View implements DozeReceiver, +public abstract class UdfpsAnimationView extends FrameLayout implements DozeReceiver, StatusBar.ExpansionChangedListener { private static final String TAG = "UdfpsAnimationView"; + @Nullable protected abstract UdfpsAnimation getUdfpsAnimation(); + @NonNull private UdfpsView mParent; - @Nullable private UdfpsAnimation mUdfpsAnimation; @NonNull private RectF mSensorRect; private int mAlpha; public UdfpsAnimationView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); mSensorRect = new RectF(); + setWillNotDraw(false); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); - if (mUdfpsAnimation != null) { + if (getUdfpsAnimation() != null) { final int alpha = mParent.shouldPauseAuth() ? mAlpha : 255; - mUdfpsAnimation.setAlpha(alpha); - mUdfpsAnimation.draw(canvas); + getUdfpsAnimation().setAlpha(alpha); + getUdfpsAnimation().draw(canvas); } } + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + getUdfpsAnimation().onDestroy(); + } + private int expansionToAlpha(float expansion) { // Fade to 0 opacity when reaching this expansion amount final float maxExpansion = 0.4f; @@ -69,38 +77,38 @@ public class UdfpsAnimationView extends View implements DozeReceiver, return (int) ((1 - percent) * 255); } - void setParent(@NonNull UdfpsView parent) { - mParent = parent; + void onIlluminationStarting() { + getUdfpsAnimation().setIlluminationShowing(true); + postInvalidate(); } - void setAnimation(@Nullable UdfpsAnimation animation) { - if (mUdfpsAnimation != null) { - mUdfpsAnimation.setAnimationView(null); - } + void onIlluminationStopped() { + getUdfpsAnimation().setIlluminationShowing(false); + postInvalidate(); + } - mUdfpsAnimation = animation; - if (mUdfpsAnimation != null) { - mUdfpsAnimation.setAnimationView(this); - } + void setParent(@NonNull UdfpsView parent) { + mParent = parent; } void onSensorRectUpdated(@NonNull RectF sensorRect) { mSensorRect = sensorRect; - if (mUdfpsAnimation != null) { - mUdfpsAnimation.onSensorRectUpdated(mSensorRect); + if (getUdfpsAnimation() != null) { + getUdfpsAnimation().onSensorRectUpdated(mSensorRect); } } void updateColor() { - if (mUdfpsAnimation != null) { - mUdfpsAnimation.updateColor(); + if (getUdfpsAnimation() != null) { + getUdfpsAnimation().updateColor(); } + postInvalidate(); } @Override public void dozeTimeTick() { - if (mUdfpsAnimation instanceof DozeReceiver) { - ((DozeReceiver) mUdfpsAnimation).dozeTimeTick(); + if (getUdfpsAnimation() instanceof DozeReceiver) { + ((DozeReceiver) getUdfpsAnimation()).dozeTimeTick(); } } @@ -111,16 +119,16 @@ public class UdfpsAnimationView extends View implements DozeReceiver, } public int getPaddingX() { - if (mUdfpsAnimation == null) { + if (getUdfpsAnimation() == null) { return 0; } - return mUdfpsAnimation.getPaddingX(); + return getUdfpsAnimation().getPaddingX(); } public int getPaddingY() { - if (mUdfpsAnimation == null) { + if (getUdfpsAnimation() == null) { return 0; } - return mUdfpsAnimation.getPaddingY(); + return getUdfpsAnimation().getPaddingY(); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewEnroll.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewEnroll.java new file mode 100644 index 000000000000..19e774937e20 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewEnroll.java @@ -0,0 +1,84 @@ +/* + * 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.systemui.biometrics; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; + +import com.android.systemui.R; + +/** + * Class that coordinates non-HBM animations during enrollment. + */ +public class UdfpsAnimationViewEnroll extends UdfpsAnimationView + implements UdfpsEnrollHelper.Listener { + + private static final String TAG = "UdfpsAnimationViewEnroll"; + + @NonNull private UdfpsAnimation mUdfpsAnimation; + @NonNull private UdfpsProgressBar mProgressBar; + @Nullable private UdfpsEnrollHelper mEnrollHelper; + + @NonNull + @Override + protected UdfpsAnimation getUdfpsAnimation() { + return mUdfpsAnimation; + } + + public UdfpsAnimationViewEnroll(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + mUdfpsAnimation = new UdfpsAnimationEnroll(context); + } + + public void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) { + mEnrollHelper = helper; + } + + @Override + protected void onFinishInflate() { + mProgressBar = findViewById(R.id.progress_bar); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (mEnrollHelper == null) { + Log.e(TAG, "Enroll helper is null"); + return; + } + + if (mEnrollHelper.shouldShowProgressBar()) { + mProgressBar.setVisibility(View.VISIBLE); + + // Only need enrollment updates if the progress bar is showing :) + mEnrollHelper.setListener(this); + } + } + + @Override + public void onEnrollmentProgress(int remaining, int totalSteps) { + final int interpolatedProgress = mProgressBar.getMax() + * Math.max(0, totalSteps + 1 - remaining) / (totalSteps + 1); + + mProgressBar.setProgress(interpolatedProgress, true); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewFpmOther.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewFpmOther.java new file mode 100644 index 000000000000..3d2f5a0fe5cf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewFpmOther.java @@ -0,0 +1,42 @@ +/* + * 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.systemui.biometrics; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.Nullable; + +/** + * Class that coordinates non-HBM animations during other usage of FingerprintManager (not + * including Keyguard). + */ +public class UdfpsAnimationViewFpmOther extends UdfpsAnimationView { + + private final UdfpsAnimationFpmOther mAnimation; + + public UdfpsAnimationViewFpmOther(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + mAnimation = new UdfpsAnimationFpmOther(context); + } + + @Nullable + @Override + protected UdfpsAnimation getUdfpsAnimation() { + return mAnimation; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewKeyguard.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewKeyguard.java new file mode 100644 index 000000000000..7d0b3e59feb1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewKeyguard.java @@ -0,0 +1,49 @@ +/* + * 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.systemui.biometrics; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.systemui.plugins.statusbar.StatusBarStateController; + +/** + * Class that coordinates non-HBM animations during keyguard authentication. + */ +public class UdfpsAnimationViewKeyguard extends UdfpsAnimationView { + @Nullable private UdfpsAnimationKeyguard mAnimation; + + public UdfpsAnimationViewKeyguard(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + void setStatusBarStateController(@NonNull StatusBarStateController statusBarStateController) { + if (mAnimation == null) { + mAnimation = new UdfpsAnimationKeyguard(getContext(), statusBarStateController); + mAnimation.setAnimationView(this); + } + } + + @Nullable + @Override + protected UdfpsAnimation getUdfpsAnimation() { + return mAnimation; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 71fba3302abc..e1d7eb3cbb19 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -71,7 +71,8 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @NonNull private final LayoutInflater mInflater; private final WindowManager mWindowManager; private final DelayableExecutor mFgExecutor; - private final StatusBarStateController mStatusBarStateController; + @NonNull private final StatusBar mStatusBar; + @NonNull private final StatusBarStateController mStatusBarStateController; // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple // sensors, this, in addition to a lot of the code here, will be updated. @VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps; @@ -110,18 +111,20 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @Override public void onEnrollmentProgress(int sensorId, int remaining) { - if (mView == null) { + if (mEnrollHelper == null) { + Log.e(TAG, "onEnrollProgress received but helper is null"); return; } - mView.onEnrollmentProgress(remaining); + mEnrollHelper.onEnrollmentProgress(remaining); } @Override public void onEnrollmentHelp(int sensorId) { - if (mView == null) { + if (mEnrollHelper == null) { + Log.e(TAG, "onEnrollmentHelp received but helper is null"); return; } - mView.onEnrollmentHelp(); + mEnrollHelper.onEnrollmentHelp(); } @Override @@ -135,20 +138,14 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @VisibleForTesting final StatusBar.ExpansionChangedListener mStatusBarExpansionListener = - (expansion, expanded) -> { - if (mView != null) { - mView.onExpansionChanged(expansion, expanded); - } - }; + (expansion, expanded) -> mView.onExpansionChanged(expansion, expanded); @VisibleForTesting final StatusBarStateController.StateListener mStatusBarStateListener = new StatusBarStateController.StateListener() { @Override public void onStateChanged(int newState) { - if (mView != null) { mView.onStateChanged(newState); - } } }; @@ -189,7 +186,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback { WindowManager windowManager, @NonNull StatusBarStateController statusBarStateController, @Main DelayableExecutor fgExecutor, - @Nullable StatusBar statusBar) { + @NonNull StatusBar statusBar) { mContext = context; mInflater = inflater; // The fingerprint manager is queried for UDFPS before this class is constructed, so the @@ -197,6 +194,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback { mFingerprintManager = checkNotNull(fingerprintManager); mWindowManager = windowManager; mFgExecutor = fgExecutor; + mStatusBar = statusBar; mStatusBarStateController = statusBarStateController; mSensorProps = findFirstUdfps(); @@ -217,9 +215,6 @@ public class UdfpsController implements DozeReceiver, HbmCallback { WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; mCoreLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; - statusBar.addExpansionChangedListener(mStatusBarExpansionListener); - mStatusBarStateController.addCallback(mStatusBarStateListener); - mFingerprintManager.setUdfpsOverlayController(new UdfpsOverlayController()); } @@ -278,7 +273,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback { } } - private WindowManager.LayoutParams computeLayoutParams(@Nullable UdfpsAnimation animation) { + private WindowManager.LayoutParams computeLayoutParams(@Nullable UdfpsAnimationView animation) { final int paddingX = animation != null ? animation.getPaddingX() : 0; final int paddingY = animation != null ? animation.getPaddingY() : 0; @@ -330,13 +325,19 @@ public class UdfpsController implements DozeReceiver, HbmCallback { if (mView == null) { try { Log.v(TAG, "showUdfpsOverlay | adding window"); - + // TODO: Eventually we should refactor the code to inflate an + // operation-specific view here, instead of inflating a generic udfps_view + // and adding operation-specific animations to it. mView = (UdfpsView) mInflater.inflate(R.layout.udfps_view, null, false); mView.setSensorProperties(mSensorProps); mView.setHbmCallback(this); - final UdfpsAnimation animation = getUdfpsAnimationForReason(reason); - mView.setExtras(animation, mEnrollHelper); + final UdfpsAnimationView animation = getUdfpsAnimationViewForReason(reason); + mView.setAnimationView(animation); + + mStatusBar.addExpansionChangedListener(mStatusBarExpansionListener); + mStatusBarStateController.addCallback(mStatusBarStateListener); + mWindowManager.addView(mView, computeLayoutParams(animation)); mView.setOnTouchListener(mOnTouchListener); } catch (RuntimeException e) { @@ -348,17 +349,34 @@ public class UdfpsController implements DozeReceiver, HbmCallback { }); } - @Nullable - private UdfpsAnimation getUdfpsAnimationForReason(int reason) { + @NonNull + private UdfpsAnimationView getUdfpsAnimationViewForReason(int reason) { Log.d(TAG, "getUdfpsAnimationForReason: " + reason); + + final LayoutInflater inflater = LayoutInflater.from(mContext); + switch (reason) { case IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR: - case IUdfpsOverlayController.REASON_ENROLL_ENROLLING: - return new UdfpsAnimationEnroll(mContext); - case IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD: - return new UdfpsAnimationKeyguard(mContext, mStatusBarStateController); - case IUdfpsOverlayController.REASON_AUTH_FPM_OTHER: - return new UdfpsAnimationFpmOther(mContext); + case IUdfpsOverlayController.REASON_ENROLL_ENROLLING: { + final UdfpsAnimationViewEnroll animation = (UdfpsAnimationViewEnroll) + inflater.inflate(R.layout.udfps_animation_view_enroll, null, false); + animation.setEnrollHelper(mEnrollHelper); + return animation; + } + + case IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD: { + final UdfpsAnimationViewKeyguard animation = (UdfpsAnimationViewKeyguard) + inflater.inflate(R.layout.udfps_animation_view_keyguard, null, false); + animation.setStatusBarStateController(mStatusBarStateController); + return animation; + } + + case IUdfpsOverlayController.REASON_AUTH_FPM_OTHER: { + final UdfpsAnimationViewFpmOther animation = (UdfpsAnimationViewFpmOther) + inflater.inflate(R.layout.udfps_animation_view_fpm_other, null, false); + return animation; + } + default: Log.d(TAG, "Animation for reason " + reason + " not supported yet"); return null; @@ -371,6 +389,10 @@ public class UdfpsController implements DozeReceiver, HbmCallback { Log.v(TAG, "hideUdfpsOverlay | removing window"); // Reset the controller back to its starting state. onFingerUp(); + + mStatusBar.removeExpansionChangedListener(mStatusBarExpansionListener); + mStatusBarStateController.removeCallback(mStatusBarStateListener); + mWindowManager.removeView(mView); mView = null; } else { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java index 2442633a4a69..942fa7aae0bc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java @@ -16,22 +16,28 @@ package com.android.systemui.biometrics; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.hardware.fingerprint.IUdfpsOverlayController; -import androidx.annotation.NonNull; - /** * Helps keep track of enrollment state and animates the progress bar accordingly. */ public class UdfpsEnrollHelper { private static final String TAG = "UdfpsEnrollHelper"; + interface Listener { + void onEnrollmentProgress(int remaining, int totalSteps); + } + // IUdfpsOverlayController reason private final int mEnrollReason; private int mTotalSteps = -1; private int mCurrentProgress = 0; + @Nullable Listener mListener; + public UdfpsEnrollHelper(int reason) { mEnrollReason = reason; } @@ -40,21 +46,29 @@ public class UdfpsEnrollHelper { return mEnrollReason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING; } - void onEnrollmentProgress(int remaining, @NonNull UdfpsProgressBar progressBar) { + void onEnrollmentProgress(int remaining) { if (mTotalSteps == -1) { mTotalSteps = remaining; } - mCurrentProgress = progressBar.getMax() * Math.max(0, mTotalSteps + 1 - remaining) - / (mTotalSteps + 1); - progressBar.setProgress(mCurrentProgress, true /* animate */); + if (mListener != null) { + mListener.onEnrollmentProgress(remaining, mTotalSteps); + } } - void updateProgress(@NonNull UdfpsProgressBar progressBar) { - progressBar.setProgress(mCurrentProgress); + void onEnrollmentHelp() { + } - void onEnrollmentHelp() { + void setListener(@NonNull Listener listener) { + mListener = listener; + // Only notify during setListener if enrollment is already in progress, so the progress + // bar can be updated. If enrollment has not started yet, the progress bar will be empty + // anyway. + if (mTotalSteps != -1) { + final int remainingSteps = mTotalSteps - mCurrentProgress; + mListener.onEnrollmentProgress(remainingSteps, mTotalSteps); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java index 97c215e1d75f..61ec127ee946 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java @@ -52,6 +52,12 @@ public class UdfpsSurfaceView extends SurfaceView implements UdfpsIlluminator { public UdfpsSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); + // Make this SurfaceView draw on top of everything else in this window. This allows us to + // 1) Always show the HBM circle on top of everything else, and + // 2) Properly composite this view with any other animations in the same window no matter + // what contents are added in which order to this view hierarchy. + setZOrderOnTop(true); + mHolder = getHolder(); mHolder.setFormat(PixelFormat.RGBA_8888); @@ -61,7 +67,9 @@ public class UdfpsSurfaceView extends SurfaceView implements UdfpsIlluminator { mSensorPaint.setARGB(255, 255, 255, 255); mSensorPaint.setStyle(Paint.Style.FILL); - mIlluminationDotDrawable = canvas -> canvas.drawOval(mSensorRect, mSensorPaint); + mIlluminationDotDrawable = canvas -> { + canvas.drawOval(mSensorRect, mSensorPaint); + }; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java index 399794391700..cd849e63ba9c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java @@ -32,7 +32,6 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; -import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; @@ -51,12 +50,11 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin private static final int DEBUG_TEXT_SIZE_PX = 32; - @NonNull private final UdfpsSurfaceView mHbmSurfaceView; - @NonNull private final UdfpsAnimationView mAnimationView; @NonNull private final RectF mSensorRect; @NonNull private final Paint mDebugTextPaint; - @Nullable private UdfpsProgressBar mProgressBar; + @NonNull private UdfpsSurfaceView mHbmSurfaceView; + @Nullable private UdfpsAnimationView mAnimationView; // Used to obtain the sensor location. @NonNull private FingerprintSensorPropertiesInternal mSensorProps; @@ -66,7 +64,6 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin private boolean mIlluminationRequested; private int mStatusBarState; private boolean mNotificationShadeExpanded; - @Nullable private UdfpsEnrollHelper mEnrollHelper; public UdfpsView(Context context, AttributeSet attrs) { super(context, attrs); @@ -84,19 +81,6 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin a.recycle(); } - // Inflate UdfpsSurfaceView - final LayoutInflater inflater = LayoutInflater.from(context); - mHbmSurfaceView = (UdfpsSurfaceView) inflater.inflate(R.layout.udfps_surface_view, - null, false); - addView(mHbmSurfaceView); - mHbmSurfaceView.setVisibility(View.INVISIBLE); - - // Inflate UdfpsAnimationView - mAnimationView = (UdfpsAnimationView) inflater.inflate(R.layout.udfps_animation_view, - null, false); - mAnimationView.setParent(this); - addView(mAnimationView); - mSensorRect = new RectF(); mDebugTextPaint = new Paint(); @@ -107,22 +91,22 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin mIlluminationRequested = false; } + @Override + protected void onFinishInflate() { + mHbmSurfaceView = findViewById(R.id.hbm_view); + } + void setSensorProperties(@NonNull FingerprintSensorPropertiesInternal properties) { mSensorProps = properties; } - void setExtras(@Nullable UdfpsAnimation animation, @Nullable UdfpsEnrollHelper enrollHelper) { - mAnimationView.setAnimation(animation); - - mEnrollHelper = enrollHelper; + void setAnimationView(@NonNull UdfpsAnimationView animation) { + mAnimationView = animation; + animation.setParent(this); - if (enrollHelper != null) { - mEnrollHelper.updateProgress(mProgressBar); - mProgressBar.setVisibility(enrollHelper.shouldShowProgressBar() - ? View.VISIBLE : View.GONE); - } else { - mProgressBar.setVisibility(View.GONE); - } + // TODO: Consider using a ViewStub placeholder to maintain positioning and inflating it + // after the animation type has been decided. + addView(animation, 0); } @Override @@ -132,6 +116,9 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin @Override public void dozeTimeTick() { + if (mAnimationView == null) { + return; + } mAnimationView.dozeTimeTick(); } @@ -143,12 +130,10 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin @Override public void onExpansionChanged(float expansion, boolean expanded) { mNotificationShadeExpanded = expanded; - mAnimationView.onExpansionChanged(expansion, expanded); - } - @Override - protected void onFinishInflate() { - mProgressBar = findViewById(R.id.progress_bar); + if (mAnimationView != null) { + mAnimationView.onExpansionChanged(expansion, expanded); + } } @Override @@ -229,7 +214,7 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin @Override public void startIllumination(@Nullable Runnable onIlluminatedRunnable) { mIlluminationRequested = true; - mAnimationView.setVisibility(View.INVISIBLE); + mAnimationView.onIlluminationStarting(); mHbmSurfaceView.setVisibility(View.VISIBLE); mHbmSurfaceView.startIllumination(onIlluminatedRunnable); } @@ -237,16 +222,8 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin @Override public void stopIllumination() { mIlluminationRequested = false; - mAnimationView.setVisibility(View.VISIBLE); + mAnimationView.onIlluminationStopped(); mHbmSurfaceView.setVisibility(View.INVISIBLE); mHbmSurfaceView.stopIllumination(); } - - void onEnrollmentProgress(int remaining) { - mEnrollHelper.onEnrollmentProgress(remaining, mProgressBar); - } - - void onEnrollmentHelp() { - - } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index 19e32783f765..35d5ca949f85 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -279,9 +279,11 @@ public class NavigationBarView extends FrameLayout implements return; } + // When in gestural and the IME is showing, don't use the nearest region since it will take + // gesture space away from the IME info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION); info.touchableRegion.set(getButtonLocations(false /* includeFloatingRotationButton */, - false /* inScreen */)); + false /* inScreen */, false /* useNearestRegion */)); }; private final Consumer<Boolean> mRotationButtonListener = (visible) -> { @@ -981,7 +983,8 @@ public class NavigationBarView extends FrameLayout implements */ public void notifyActiveTouchRegions() { mOverviewProxyService.onActiveNavBarRegionChanges( - getButtonLocations(true /* includeFloatingRotationButton */, true /* inScreen */)); + getButtonLocations(true /* includeFloatingRotationButton */, true /* inScreen */, + true /* useNearestRegion */)); } private void updateButtonTouchRegionCache() { @@ -992,35 +995,49 @@ public class NavigationBarView extends FrameLayout implements .findViewById(R.id.nav_buttons)).getFullTouchableChildRegions(); } + /** + * @param includeFloatingRotationButton Whether to include the floating rotation button in the + * region for all the buttons + * @param inScreenSpace Whether to return values in screen space or window space + * @param useNearestRegion Whether to use the nearest region instead of the actual button bounds + * @return + */ private Region getButtonLocations(boolean includeFloatingRotationButton, - boolean inScreenSpace) { + boolean inScreenSpace, boolean useNearestRegion) { + if (useNearestRegion && !inScreenSpace) { + // We currently don't support getting the nearest region in anything but screen space + useNearestRegion = false; + } mTmpRegion.setEmpty(); updateButtonTouchRegionCache(); - updateButtonLocation(getBackButton(), inScreenSpace); - updateButtonLocation(getHomeButton(), inScreenSpace); - updateButtonLocation(getRecentsButton(), inScreenSpace); - updateButtonLocation(getImeSwitchButton(), inScreenSpace); - updateButtonLocation(getAccessibilityButton(), inScreenSpace); + updateButtonLocation(getBackButton(), inScreenSpace, useNearestRegion); + updateButtonLocation(getHomeButton(), inScreenSpace, useNearestRegion); + updateButtonLocation(getRecentsButton(), inScreenSpace, useNearestRegion); + updateButtonLocation(getImeSwitchButton(), inScreenSpace, useNearestRegion); + updateButtonLocation(getAccessibilityButton(), inScreenSpace, useNearestRegion); if (includeFloatingRotationButton && mFloatingRotationButton.isVisible()) { + // Note: this button is floating so the nearest region doesn't apply updateButtonLocation(mFloatingRotationButton.getCurrentView(), inScreenSpace); } else { - updateButtonLocation(getRotateSuggestionButton(), inScreenSpace); + updateButtonLocation(getRotateSuggestionButton(), inScreenSpace, useNearestRegion); } if (mNavBarOverlayController.isNavigationBarOverlayEnabled() && mNavBarOverlayController.isVisible()) { + // Note: this button is floating so the nearest region doesn't apply updateButtonLocation(mNavBarOverlayController.getCurrentView(), inScreenSpace); } return mTmpRegion; } - private void updateButtonLocation(ButtonDispatcher button, boolean inScreenSpace) { + private void updateButtonLocation(ButtonDispatcher button, boolean inScreenSpace, + boolean useNearestRegion) { View view = button.getCurrentView(); if (view == null || !button.isVisible()) { return; } // If the button is tappable from perspective of NearestTouchFrame, then we'll // include the regions where the tap is valid instead of just the button layout location - if (mButtonFullTouchableRegions.containsKey(view)) { + if (useNearestRegion && mButtonFullTouchableRegions.containsKey(view)) { mTmpRegion.op(mButtonFullTouchableRegions.get(view), Op.UNION); return; } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt index 680a617e4d26..8ec9b682ffdc 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt @@ -47,7 +47,7 @@ class PrivacyDialog( context: Context, private val list: List<PrivacyElement>, activityStarter: (String, Int) -> Unit -) : SystemUIDialog(context, R.style.ScreenRecord) { +) : SystemUIDialog(context, R.style.PrivacyDialog) { private val dismissListeners = mutableListOf<WeakReference<OnDialogDismissed>>() private val dismissed = AtomicBoolean(false) @@ -64,6 +64,7 @@ class PrivacyDialog( super.onCreate(savedInstanceState) window?.apply { attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars() + attributes.receiveInsetsIgnoringZOrder = true setLayout(context.resources.getDimensionPixelSize(R.dimen.qs_panel_width), WRAP_CONTENT) setGravity(Gravity.TOP or Gravity.CENTER_HORIZONTAL) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt index c3cc3af10e83..52f111e7ab48 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt @@ -18,7 +18,6 @@ package com.android.systemui.qs import android.content.Context import android.util.AttributeSet -import com.android.systemui.R open class SideLabelTileLayout( context: Context, @@ -28,9 +27,6 @@ open class SideLabelTileLayout( override fun updateResources(): Boolean { return super.updateResources().also { mMaxAllowedRows = 4 - mCellMarginHorizontal = (mCellMarginHorizontal * 1.2).toInt() - mCellMarginVertical = mCellMarginHorizontal - mMaxCellHeight = context.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java index 47cb45b14b9b..ce8f6c1737d7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java @@ -16,19 +16,19 @@ package com.android.systemui.qs.customize; import android.content.Context; import android.view.View; -import android.widget.TextView; import com.android.systemui.plugins.qs.QSIconView; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.tileimpl.QSTileView; -public class CustomizeTileView extends QSTileView { +public class CustomizeTileView extends QSTileView implements TileAdapter.CustomizeView { private boolean mShowAppLabel; public CustomizeTileView(Context context, QSIconView icon) { super(context, icon); } + @Override public void setShowAppLabel(boolean showAppLabel) { mShowAppLabel = showAppLabel; mSecondLine.setVisibility(showAppLabel ? View.VISIBLE : View.GONE); @@ -41,10 +41,6 @@ public class CustomizeTileView extends QSTileView { mSecondLine.setVisibility(mShowAppLabel ? View.VISIBLE : View.GONE); } - public TextView getAppLabel() { - return mSecondLine; - } - @Override protected boolean animationsEnabled() { return false; @@ -54,4 +50,9 @@ public class CustomizeTileView extends QSTileView { public boolean isLongClickable() { return false; } + + @Override + public void changeState(QSTile.State state) { + handleStateChanged(state); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt new file mode 100644 index 000000000000..4ffcd8cdd9ce --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt @@ -0,0 +1,50 @@ +package com.android.systemui.qs.customize + +import android.content.Context +import android.graphics.drawable.Drawable +import android.view.View +import com.android.systemui.plugins.qs.QSIconView +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.tileimpl.QSTileViewHorizontal + +/** + * Class for displaying tiles in [QSCustomizer] with the new design (labels on the side). + * + * This is a class parallel to [CustomizeTileView], but inheriting from [QSTileViewHorizontal]. + */ +class CustomizeTileViewHorizontal( + context: Context, + icon: QSIconView +) : QSTileViewHorizontal(context, icon), + TileAdapter.CustomizeView { + + private var showAppLabel = false + + override fun setShowAppLabel(showAppLabel: Boolean) { + this.showAppLabel = showAppLabel + mSecondLine.visibility = if (showAppLabel) View.VISIBLE else View.GONE + mLabel.isSingleLine = showAppLabel + } + + override fun handleStateChanged(state: QSTile.State) { + super.handleStateChanged(state) + mSecondLine.visibility = if (showAppLabel) View.VISIBLE else View.GONE + } + + override fun animationsEnabled(): Boolean { + return false + } + + override fun isLongClickable(): Boolean { + return false + } + + override fun changeState(state: QSTile.State) { + handleStateChanged(state) + } + + override fun newTileBackground(): Drawable? { + super.newTileBackground() + return paintDrawable + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index 7a91421b00a1..0adc8448b89f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -14,6 +14,8 @@ package com.android.systemui.qs.customize; +import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG; + import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; @@ -30,6 +32,7 @@ import android.widget.FrameLayout; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.core.view.AccessibilityDelegateCompat; import androidx.core.view.ViewCompat; import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup; @@ -41,6 +44,7 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; +import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSEditEvent; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.customize.TileAdapter.Holder; @@ -49,11 +53,13 @@ import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.tileimpl.QSIconViewImpl; +import com.android.systemui.qs.tileimpl.QSTileView; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; +import javax.inject.Named; /** */ @QSScope @@ -75,7 +81,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta private static final int ACTION_ADD = 1; private static final int ACTION_MOVE = 2; - private static final int NUM_COLUMNS_ID = R.integer.quick_settings_edit_num_columns; + private static final int NUM_COLUMNS_ID = R.integer.quick_settings_num_columns; private final Context mContext; @@ -102,9 +108,11 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta private final AccessibilityDelegateCompat mAccessibilityDelegate; private RecyclerView mRecyclerView; private int mNumColumns; + private final boolean mUseHorizontalTiles; @Inject - public TileAdapter(Context context, QSTileHost qsHost, UiEventLogger uiEventLogger) { + public TileAdapter(Context context, QSTileHost qsHost, UiEventLogger uiEventLogger, + @Named(QS_LABELS_FLAG) boolean useHorizontalTiles) { mContext = context; mHost = qsHost; mUiEventLogger = uiEventLogger; @@ -114,6 +122,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta mMinNumTiles = context.getResources().getInteger(R.integer.quick_settings_min_num_tiles); mNumColumns = context.getResources().getInteger(NUM_COLUMNS_ID); mAccessibilityDelegate = new TileAdapterDelegate(); + mUseHorizontalTiles = useHorizontalTiles; } @Override @@ -271,7 +280,10 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } FrameLayout frame = (FrameLayout) inflater.inflate(R.layout.qs_customize_tile_frame, parent, false); - frame.addView(new CustomizeTileView(context, new QSIconViewImpl(context))); + View view = mUseHorizontalTiles + ? new CustomizeTileViewHorizontal(context, new QSIconViewImpl(context)) + : new CustomizeTileView(context, new QSIconViewImpl(context)); + frame.addView(view); return new Holder(frame); } @@ -354,8 +366,9 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } info.state.expandedAccessibilityClassName = ""; - holder.mTileView.handleStateChanged(info.state); - holder.mTileView.setShowAppLabel(position > mEditIndex && !info.isSystem); + // The holder has a tileView, therefore this call is not null + holder.getTileAsCustomizeView().changeState(info.state); + holder.getTileAsCustomizeView().setShowAppLabel(position > mEditIndex && !info.isSystem); holder.mTileView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); holder.mTileView.setClickable(true); holder.mTileView.setOnClickListener(null); @@ -534,25 +547,34 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } public class Holder extends ViewHolder { - private CustomizeTileView mTileView; + private QSTileView mTileView; public Holder(View itemView) { super(itemView); if (itemView instanceof FrameLayout) { - mTileView = (CustomizeTileView) ((FrameLayout) itemView).getChildAt(0); - mTileView.setBackground(null); + mTileView = (QSTileView) ((FrameLayout) itemView).getChildAt(0); + if (mTileView instanceof CustomizeTileView) { + mTileView.setBackground(null); + } mTileView.getIcon().disableAnimation(); mTileView.setTag(this); ViewCompat.setAccessibilityDelegate(mTileView, mAccessibilityDelegate); } } + @Nullable + public CustomizeView getTileAsCustomizeView() { + return (CustomizeView) mTileView; + } + public void clearDrag() { itemView.clearAnimation(); - mTileView.findViewById(R.id.tile_label).clearAnimation(); - mTileView.findViewById(R.id.tile_label).setAlpha(1); - mTileView.getAppLabel().clearAnimation(); - mTileView.getAppLabel().setAlpha(.6f); + if (mTileView instanceof CustomizeTileView) { + mTileView.findViewById(R.id.tile_label).clearAnimation(); + mTileView.findViewById(R.id.tile_label).setAlpha(1); + mTileView.getAppLabel().clearAnimation(); + mTileView.getAppLabel().setAlpha(.6f); + } } public void startDrag() { @@ -560,12 +582,14 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta .setDuration(DRAG_LENGTH) .scaleX(DRAG_SCALE) .scaleY(DRAG_SCALE); - mTileView.findViewById(R.id.tile_label).animate() - .setDuration(DRAG_LENGTH) - .alpha(0); - mTileView.getAppLabel().animate() - .setDuration(DRAG_LENGTH) - .alpha(0); + if (mTileView instanceof CustomizeTileView) { + mTileView.findViewById(R.id.tile_label).animate() + .setDuration(DRAG_LENGTH) + .alpha(0); + mTileView.getAppLabel().animate() + .setDuration(DRAG_LENGTH) + .alpha(0); + } } public void stopDrag() { @@ -573,12 +597,14 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta .setDuration(DRAG_LENGTH) .scaleX(1) .scaleY(1); - mTileView.findViewById(R.id.tile_label).animate() - .setDuration(DRAG_LENGTH) - .alpha(1); - mTileView.getAppLabel().animate() - .setDuration(DRAG_LENGTH) - .alpha(.6f); + if (mTileView instanceof CustomizeTileView) { + mTileView.findViewById(R.id.tile_label).animate() + .setDuration(DRAG_LENGTH) + .alpha(1); + mTileView.getAppLabel().animate() + .setDuration(DRAG_LENGTH) + .alpha(.6f); + } } boolean canRemove() { @@ -722,7 +748,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta int position = mCurrentDrag.getAdapterPosition(); if (position == RecyclerView.NO_POSITION) return; TileInfo info = mTiles.get(position); - mCurrentDrag.mTileView.setShowAppLabel( + ((CustomizeView) mCurrentDrag.mTileView).setShowAppLabel( position > mEditIndex && !info.isSystem); mCurrentDrag.stopDrag(); mCurrentDrag = null; @@ -782,4 +808,9 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta public void onSwiped(ViewHolder viewHolder, int direction) { } }; + + interface CustomizeView { + void setShowAppLabel(boolean showAppLabel); + void changeState(@NonNull QSTile.State state); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java index 38e2ba4df79a..33ca7d6bafd8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java @@ -259,23 +259,30 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { mIcon.setIcon(state, allowAnimations); setContentDescription(state.contentDescription); final StringBuilder stateDescription = new StringBuilder(); + String text = ""; switch (state.state) { case Tile.STATE_UNAVAILABLE: - stateDescription.append(mContext.getString(R.string.tile_unavailable)); + text = mContext.getString(R.string.tile_unavailable); break; case Tile.STATE_INACTIVE: if (state instanceof QSTile.BooleanState) { - stateDescription.append(mContext.getString(R.string.switch_bar_off)); + text = mContext.getString(R.string.switch_bar_off); } break; case Tile.STATE_ACTIVE: if (state instanceof QSTile.BooleanState) { - stateDescription.append(mContext.getString(R.string.switch_bar_on)); + text = mContext.getString(R.string.switch_bar_on); } break; default: break; } + if (!TextUtils.isEmpty(text)) { + stateDescription.append(text); + if (TextUtils.isEmpty(state.secondaryLabel)) { + state.secondaryLabel = text; + } + } if (!TextUtils.isEmpty(state.stateDescription)) { stateDescription.append(", "); stateDescription.append(state.stateDescription); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java index 207b25d001b9..b59326ae56d5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java @@ -174,4 +174,8 @@ public class QSTileView extends QSTileBaseView { mLabelContainer.setClickable(false); mLabelContainer.setLongClickable(false); } + + public TextView getAppLabel() { + return mSecondLine; + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt index 07d48f32ff20..231037fdd158 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt @@ -35,13 +35,13 @@ import com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState // Placeholder private const val CORNER_RADIUS = 40f -class QSTileViewHorizontal( +open class QSTileViewHorizontal( context: Context, icon: QSIconView ) : QSTileView(context, icon, false) { - private var paintDrawable: PaintDrawable? = null - private var paintColor = Color.TRANSPARENT + protected var paintDrawable: PaintDrawable? = null + private var paintColor = Color.WHITE private var paintAnimator: ValueAnimator? = null init { @@ -103,7 +103,7 @@ class QSTileViewHorizontal( mSecondLine.setTextColor(mLabel.textColors) mLabelContainer.background = null - val allowAnimations = animationsEnabled() && paintColor != Color.TRANSPARENT + val allowAnimations = animationsEnabled() && paintColor != Color.WHITE val newColor = getCircleColor(state.state) if (allowAnimations) { animateToNewState(newColor) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 805ac7cf1ec9..3d6dea3cd3f0 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -519,7 +519,7 @@ public class ScreenshotController { setWindowFocusable(true); if (mConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, false)) { + SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, true)) { View decorView = mWindow.getDecorView(); // Wait until this window is attached to request because it is diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java index 6cdf6ab5154e..58a54f6ce0ed 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java @@ -82,7 +82,7 @@ public class ScreenshotNotificationsController { dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE); if (intent != null) { final PendingIntent pendingIntent = PendingIntent.getActivityAsUser( - mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED, null, UserHandle.CURRENT); + mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT); b.setContentIntent(pendingIntent); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java index bf65132166b6..9da6b8f240e9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java @@ -38,13 +38,15 @@ public class ScrollCaptureController { private static final float MAX_PAGES_DEFAULT = 3f; private static final String SETTING_KEY_MAX_PAGES = "screenshot.scroll_max_pages"; + // Portion of the tiles to be acquired above the starting position in infinite scroll + // situations. 1.0 means maximize the area above, 0 means just go down. + private static final float IDEAL_PORTION_ABOVE = 0.4f; - private static final int UP = -1; - private static final int DOWN = 1; + private boolean mScrollingUp = true; + // If true, stop acquiring images when no more bitmap data is available in the current direction + // or if the desired bitmap size is reached. + private boolean mFinishOnBoundary; - private int mDirection = DOWN; - private boolean mAtBottomEdge; - private boolean mAtTopEdge; private Session mSession; public static final int MAX_HEIGHT = 12000; @@ -86,7 +88,8 @@ public class ScrollCaptureController { } private void onCaptureResult(CaptureResult result) { - Log.d(TAG, "onCaptureResult: " + result); + Log.d(TAG, "onCaptureResult: " + result + " scrolling up: " + mScrollingUp + + " finish on boundary: " + mFinishOnBoundary); boolean emptyResult = result.captured.height() == 0; boolean partialResult = !emptyResult && result.captured.height() < result.requested.height(); @@ -94,34 +97,28 @@ public class ScrollCaptureController { if (partialResult || emptyResult) { // Potentially reached a vertical boundary. Extend in the other direction. - switch (mDirection) { - case DOWN: - Log.d(TAG, "Reached bottom edge."); - mAtBottomEdge = true; - mDirection = UP; - break; - case UP: - Log.d(TAG, "Reached top edge."); - mAtTopEdge = true; - mDirection = DOWN; - break; + if (mFinishOnBoundary) { + finish = true; + } else { + // We hit a boundary, clear the tiles, capture everything in the opposite direction, + // then finish. + mImageTileSet.clear(); + mFinishOnBoundary = true; + mScrollingUp = !mScrollingUp; } - - if (mAtTopEdge && mAtBottomEdge) { - Log.d(TAG, "Reached both top and bottom edge, ending."); + } else { + // Got the full requested result, but may have got enough bitmap data now + int expectedTiles = mImageTileSet.size() + 1; + boolean hitMaxTiles = expectedTiles >= mSession.getMaxTiles(); + if (hitMaxTiles && mFinishOnBoundary) { finish = true; } else { - // only reverse if the edge was relatively close to the starting point - if (mImageTileSet.getHeight() < mSession.getPageHeight() * 3) { - Log.d(TAG, "Restarting in reverse direction."); - - // Because of temporary limitations, we cannot just jump to the opposite edge - // and continue there. Instead, clear the results and start over capturing from - // here in the other direction. - mImageTileSet.clear(); - } else { - Log.d(TAG, "Capture is tall enough, stopping here."); - finish = true; + if (mScrollingUp) { + if (expectedTiles >= mSession.getMaxTiles() * IDEAL_PORTION_ABOVE) { + // We got enough above the start point, now see how far down it can go. + mImageTileSet.clear(); + mScrollingUp = false; + } } } } @@ -136,9 +133,8 @@ public class ScrollCaptureController { // Stop when "too tall" - if (mImageTileSet.size() >= mSession.getMaxTiles() - || mImageTileSet.getHeight() > MAX_HEIGHT) { - Log.d(TAG, "Max height and/or tile count reached."); + if (mImageTileSet.getHeight() > MAX_HEIGHT) { + Log.d(TAG, "Max height reached."); finish = true; } @@ -150,8 +146,8 @@ public class ScrollCaptureController { return; } - int nextTop = (mDirection == DOWN) ? result.captured.bottom - : result.captured.top - mSession.getTileHeight(); + int nextTop = (mScrollingUp) + ? result.captured.top - mSession.getTileHeight() : result.captured.bottom; Log.d(TAG, "requestTile: " + nextTop); mSession.requestTile(nextTop, /* consumer */ this::onCaptureResult); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/GestureRecorder.java b/packages/SystemUI/src/com/android/systemui/statusbar/GestureRecorder.java index 9ed9659c7ab8..f2adaf042b2f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/GestureRecorder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/GestureRecorder.java @@ -207,7 +207,7 @@ public class GestureRecorder { sb.append(g.toJson()); count++; } - mLastSaveLen += count; + mLastSaveLen = count; sb.append("]"); return sb.toString(); } @@ -249,9 +249,7 @@ public class GestureRecorder { public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { save(); if (mLastSaveLen >= 0) { - pw.println(String.valueOf(mLastSaveLen) - + " gestures since last dump written to " + mLogfile); - mLastSaveLen = 0; + pw.println(String.valueOf(mLastSaveLen) + " gestures written to " + mLogfile); } else { pw.println("error writing gestures"); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 01d31039a749..e5a960e13e6f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -83,6 +83,9 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi public static final int STATE_DOT = 1; public static final int STATE_HIDDEN = 2; + /** Maximum allowed width or height for an icon drawable */ + private static final int MAX_IMAGE_SIZE = 500; + private static final String TAG = "StatusBarIconView"; private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT = new FloatProperty<StatusBarIconView>("iconAppearAmount") { @@ -378,6 +381,13 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi Log.w(TAG, "No icon for slot " + mSlot + "; " + mIcon.icon); return false; } + + if (drawable.getIntrinsicWidth() > MAX_IMAGE_SIZE + || drawable.getIntrinsicHeight() > MAX_IMAGE_SIZE) { + Log.w(TAG, "Drawable is too large " + mIcon); + return false; + } + if (withClear) { setImageDrawable(null); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 1251b58171da..52063285ac01 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -2052,8 +2052,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mNotificationLaunchHeight, zProgress); setTranslationZ(translationZ); - float extraWidthForClipping = params.getWidth() - getWidth() - + MathUtils.lerp(0, mOutlineRadius * 2, params.getProgress()); + float extraWidthForClipping = params.getWidth() - getWidth(); setExtraWidthForClipping(extraWidthForClipping); int top = params.getTop(); float interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(params.getProgress()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 76917612b910..b0b91bd5177c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -22,6 +22,8 @@ import android.annotation.Nullable; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; @@ -30,6 +32,7 @@ import android.util.ArrayMap; import android.util.AttributeSet; import android.util.Log; import android.util.Pair; +import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.NotificationHeaderView; @@ -1234,6 +1237,7 @@ public class NotificationContentView extends FrameLayout { mCachedHeadsUpRemoteInput = null; } + private RemoteInputView applyRemoteInput(View view, NotificationEntry entry, boolean hasRemoteInput, PendingIntent existingPendingIntent, RemoteInputView cachedView, NotificationViewWrapper wrapper) { @@ -1271,6 +1275,15 @@ public class NotificationContentView extends FrameLayout { if (color == Notification.COLOR_DEFAULT) { color = mContext.getColor(R.color.default_remote_input_background); } + if (mContext.getResources().getBoolean( + com.android.internal.R.bool.config_tintNotificationsWithTheme)) { + Resources.Theme theme = new ContextThemeWrapper(mContext, + com.android.internal.R.style.Theme_DeviceDefault_DayNight).getTheme(); + TypedArray ta = theme.obtainStyledAttributes( + new int[]{com.android.internal.R.attr.colorAccent}); + color = ta.getColor(0, color); + ta.recycle(); + } existing.setBackgroundColor(ContrastColorUtil.ensureTextBackgroundColor(color, mContext.getColor(R.color.remote_input_text_enabled), mContext.getColor(R.color.remote_input_hint))); @@ -1342,12 +1355,10 @@ public class NotificationContentView extends FrameLayout { && isPersonWithShortcut && entry.getBubbleMetadata() != null; if (showButton) { - Drawable d = mContext.getResources().getDrawable(entry.isBubble() + // explicitly resolve drawable resource using SystemUI's theme + Drawable d = mContext.getDrawable(entry.isBubble() ? R.drawable.bubble_ic_stop_bubble : R.drawable.bubble_ic_create_bubble); - mContainingNotification.updateNotificationColor(); - final int tint = mContainingNotification.getNotificationColor(); - d.setTint(tint); String contentDescription = mContext.getResources().getString(entry.isBubble() ? R.string.notification_conversation_unbubble @@ -1381,9 +1392,8 @@ public class NotificationContentView extends FrameLayout { return; } + // explicitly resolve drawable resource using SystemUI's theme Drawable snoozeDrawable = mContext.getDrawable(R.drawable.ic_snooze); - mContainingNotification.updateNotificationColor(); - snoozeDrawable.setTint(mContainingNotification.getNotificationColor()); snoozeButton.setImageDrawable(snoozeDrawable); final NotificationSnooze snoozeGuts = (NotificationSnooze) LayoutInflater.from(mContext) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 756fe6c5ba24..8446b4e6a3f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -133,7 +133,7 @@ public class AmbientState { */ public static int getNotificationLaunchHeight(Context context) { int zDistance = getZDistanceBetweenElements(context); - return getBaseHeight(zDistance) * 2; + return NOTIFICATIONS_HAVE_SHADOWS ? 2 * getBaseHeight(zDistance) : 4 * zDistance; } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 3833637e8542..3739424b4f5d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -20,10 +20,12 @@ import android.app.Notification; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.drawable.ColorDrawable; import android.service.notification.StatusBarNotification; import android.util.AttributeSet; import android.util.Pair; +import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.NotificationHeaderView; import android.view.View; @@ -33,6 +35,7 @@ import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.CachingIconView; +import com.android.internal.widget.NotificationExpandButton; import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.NotificationGroupingUtil; @@ -103,6 +106,8 @@ public class NotificationChildrenContainer extends ViewGroup { private ViewGroup mCurrentHeader; private boolean mIsConversation; + private boolean mTintWithThemeAccent; + private boolean mShowGroupCountInExpander; private boolean mShowDividersWhenExpanded; private boolean mHideDividersDuringExpand; private int mTranslationForHeader; @@ -145,6 +150,10 @@ public class NotificationChildrenContainer extends ViewGroup { com.android.internal.R.dimen.notification_content_margin); mEnableShadowOnChildNotifications = res.getBoolean(R.bool.config_enableShadowOnChildNotifications); + mTintWithThemeAccent = + res.getBoolean(com.android.internal.R.bool.config_tintNotificationsWithTheme); + mShowGroupCountInExpander = + res.getBoolean(R.bool.config_showNotificationGroupCountInExpander); mShowDividersWhenExpanded = res.getBoolean(R.bool.config_showDividersWhenGroupNotificationExpanded); mHideDividersDuringExpand = @@ -229,7 +238,6 @@ public class NotificationChildrenContainer extends ViewGroup { mNotificationHeader.measure(widthMeasureSpec, headerHeightSpec); } if (mNotificationHeaderLowPriority != null) { - headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY); mNotificationHeaderLowPriority.measure(widthMeasureSpec, headerHeightSpec); } @@ -397,7 +405,20 @@ public class NotificationChildrenContainer extends ViewGroup { mGroupingUtil.updateChildrenAppearance(); } + private void setExpandButtonNumber(NotificationViewWrapper wrapper) { + View expandButton = wrapper == null + ? null : wrapper.getExpandButton(); + if (expandButton instanceof NotificationExpandButton) { + ((NotificationExpandButton) expandButton).setNumber(mUntruncatedChildCount); + } + } + public void updateGroupOverflow() { + if (mShowGroupCountInExpander) { + setExpandButtonNumber(mNotificationHeaderWrapper); + setExpandButtonNumber(mNotificationHeaderWrapperLowPriority); + return; + } int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */); if (mUntruncatedChildCount > maxAllowedVisibleChildren) { int number = mUntruncatedChildCount - maxAllowedVisibleChildren; @@ -1201,8 +1222,21 @@ public class NotificationChildrenContainer extends ViewGroup { } public void onNotificationUpdated() { - mHybridGroupManager.setOverflowNumberColor(mOverflowNumber, - mContainingNotification.getNotificationColor()); + if (mShowGroupCountInExpander) { + // The overflow number is not used, so its color is irrelevant; skip this + return; + } + int color = mContainingNotification.getNotificationColor(); + if (mTintWithThemeAccent) { + // We're using the theme accent, color with the accent color instead of the notif color + Resources.Theme theme = new ContextThemeWrapper(mContext, + com.android.internal.R.style.Theme_DeviceDefault_DayNight).getTheme(); + TypedArray ta = theme.obtainStyledAttributes( + new int[]{com.android.internal.R.attr.colorAccent}); + color = ta.getColor(0, color); + ta.recycle(); + } + mHybridGroupManager.setOverflowNumberColor(mOverflowNumber, color); } public int getPositionInLinearLayout(View childInGroup) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 55744f94f2b0..b6ed3e50ed7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -27,7 +27,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; -import android.annotation.Nullable; import android.content.res.Configuration; import android.content.res.Resources; import android.os.SystemClock; @@ -1243,7 +1242,10 @@ public abstract class PanelViewController { mVelocityTracker.clear(); break; } - return false; + + // Finally, if none of the above cases applies, ensure that touches do not get handled + // by the contents of a panel that is not showing (a bit of a hack to avoid b/178277858) + return (mView.getVisibility() != View.VISIBLE); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index b25fced6a212..bf36435b78c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -78,7 +78,6 @@ import android.media.AudioAttributes; import android.metrics.LogMaker; import android.net.Uri; import android.os.AsyncTask; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -277,8 +276,7 @@ public class StatusBar extends SystemUI implements DemoMode, public static final boolean DEBUG = false; public static final boolean SPEW = false; public static final boolean DUMPTRUCK = true; // extra dumpsys info - public static final boolean DEBUG_GESTURES = Build.IS_DEBUGGABLE; // TODO(b/178277858) - public static final boolean DEBUG_GESTURES_VERBOSE = true; + public static final boolean DEBUG_GESTURES = false; public static final boolean DEBUG_MEDIA_FAKE_ARTWORK = false; public static final boolean DEBUG_CAMERA_LIFT = false; @@ -458,7 +456,9 @@ public class StatusBar extends SystemUI implements DemoMode, private final DisplayMetrics mDisplayMetrics; // XXX: gesture research - private GestureRecorder mGestureRec = null; + private final GestureRecorder mGestureRec = DEBUG_GESTURES + ? new GestureRecorder("/sdcard/statusbar_gestures.dat") + : null; private final ScreenPinningRequest mScreenPinningRequest; @@ -856,10 +856,6 @@ public class StatusBar extends SystemUI implements DemoMode, mActivityIntentHelper = new ActivityIntentHelper(mContext); DateTimeView.setReceiverHandler(timeTickHandler); - - if (DEBUG_GESTURES) { - mGestureRec = new GestureRecorder(mContext.getCacheDir() + "/statusbar_gestures.dat"); - } } @Override @@ -2271,7 +2267,7 @@ public class StatusBar extends SystemUI implements DemoMode, public boolean interceptTouchEvent(MotionEvent event) { if (DEBUG_GESTURES) { - if (DEBUG_GESTURES_VERBOSE || event.getActionMasked() != MotionEvent.ACTION_MOVE) { + if (event.getActionMasked() != MotionEvent.ACTION_MOVE) { EventLog.writeEvent(EventLogTags.SYSUI_STATUSBAR_TOUCH, event.getActionMasked(), (int) event.getX(), (int) event.getY(), mDisabled1, mDisabled2); @@ -2696,6 +2692,10 @@ public class StatusBar extends SystemUI implements DemoMode, return mDisplay.getRotation(); } + int getDisplayId() { + return mDisplayId; + } + public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned, boolean dismissShade, int flags) { startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, @@ -2721,7 +2721,7 @@ public class StatusBar extends SystemUI implements DemoMode, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.addFlags(flags); int result = ActivityManager.START_CANCELED; - ActivityOptions options = new ActivityOptions(getActivityOptions( + ActivityOptions options = new ActivityOptions(getActivityOptions(mDisplayId, null /* remoteAnimation */)); options.setDisallowEnterPictureInPictureWhileLaunching( disallowEnterPictureInPictureWhileLaunching); @@ -4366,6 +4366,7 @@ public class StatusBar extends SystemUI implements DemoMode, executeActionDismissingKeyguard(() -> { try { intent.send(null, 0, null, null, null, null, getActivityOptions( + mDisplayId, mActivityLaunchAnimator.getLaunchAnimation(associatedView, isOccluded()))); } catch (PendingIntent.CanceledException e) { // the stack trace isn't very helpful here. @@ -4387,15 +4388,38 @@ public class StatusBar extends SystemUI implements DemoMode, mMainThreadHandler.post(runnable); } - public static Bundle getActivityOptions(@Nullable RemoteAnimationAdapter animationAdapter) { + /** + * Returns an ActivityOptions bundle created using the given parameters. + * + * @param displayId The ID of the display to launch the activity in. Typically this would be the + * display the status bar is on. + * @param animationAdapter The animation adapter used to start this activity, or {@code null} + * for the default animation. + */ + public static Bundle getActivityOptions(int displayId, + @Nullable RemoteAnimationAdapter animationAdapter) { return getDefaultActivityOptions(animationAdapter).toBundle(); } - public static Bundle getActivityOptions(@Nullable RemoteAnimationAdapter animationAdapter, - boolean isKeyguardShowing, long eventTime) { + /** + * Returns an ActivityOptions bundle created using the given parameters. + * + * @param displayId The ID of the display to launch the activity in. Typically this would be the + * display the status bar is on. + * @param animationAdapter The animation adapter used to start this activity, or {@code null} + * for the default animation. + * @param isKeyguardShowing Whether keyguard is currently showing. + * @param eventTime The event time in milliseconds since boot, not including sleep. See + * {@link ActivityOptions#setSourceInfo}. + */ + public static Bundle getActivityOptions(int displayId, + @Nullable RemoteAnimationAdapter animationAdapter, boolean isKeyguardShowing, + long eventTime) { ActivityOptions options = getDefaultActivityOptions(animationAdapter); options.setSourceInfo(isKeyguardShowing ? ActivityOptions.SourceInfo.TYPE_LOCKSCREEN : ActivityOptions.SourceInfo.TYPE_NOTIFICATION, eventTime); + options.setLaunchDisplayId(displayId); + options.setCallerDisplayId(displayId); return options.toBundle(); } @@ -4535,4 +4559,8 @@ public class StatusBar extends SystemUI implements DemoMode, public void addExpansionChangedListener(@NonNull ExpansionChangedListener listener) { mExpansionChangedListeners.add(listener); } + + public void removeExpansionChangedListener(@NonNull ExpansionChangedListener listener) { + mExpansionChangedListeners.remove(listener); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 598addc68d2e..34673f2503ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -427,8 +427,13 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit intent.getCreatorPackage(), adapter); } long eventTime = row.getAndResetLastActionUpTime(); - Bundle options = eventTime > 0 ? getActivityOptions(adapter, - mKeyguardStateController.isShowing(), eventTime) : getActivityOptions(adapter); + Bundle options = eventTime > 0 + ? getActivityOptions( + mStatusBar.getDisplayId(), + adapter, + mKeyguardStateController.isShowing(), + eventTime) + : getActivityOptions(mStatusBar.getDisplayId(), adapter); int launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null, null, null, options); mMainThreadHandler.post(() -> { @@ -450,6 +455,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit int launchResult = TaskStackBuilder.create(mContext) .addNextIntentWithParentStack(intent) .startActivities(getActivityOptions( + mStatusBar.getDisplayId(), mActivityLaunchAnimator.getLaunchAnimation( row, mStatusBar.isOccluded())), new UserHandle(UserHandle.getUserId(appUid))); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index 38f3bc891394..59c1138431fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -94,15 +94,6 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie goingToFullShade, oldState); } - - @Override - public void onDozeAmountChanged(float linearAmount, float amount) { - if (DEBUG) { - Log.d(TAG, String.format("onDozeAmountChanged: linearAmount=%f amount=%f", - linearAmount, amount)); - } - setDarkAmount(amount); - } }; @Inject @@ -294,20 +285,6 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie } } - /** - * Set the amount (ratio) that the device has transitioned to doze. - * - * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake. - */ - private void setDarkAmount(float darkAmount) { - boolean isAwake = darkAmount != 0; - if (darkAmount == mDarkAmount) { - return; - } - mDarkAmount = darkAmount; - mView.setVisibility(isAwake ? View.VISIBLE : View.GONE); - } - private boolean isListAnimating() { return mKeyguardVisibilityHelper.isVisibilityAnimating(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index 8845a05cf6f5..5a80c05cc3cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -205,7 +205,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS protected void onViewAttached() { if (DEBUG) Log.d(TAG, "onViewAttached"); mAdapter.registerDataSetObserver(mDataSetObserver); - mDataSetObserver.onChanged(); + mAdapter.notifyDataSetChanged(); mKeyguardUpdateMonitor.registerCallback(mInfoCallback); mStatusBarStateController.addCallback(mStatusBarStateListener); mScreenLifecycle.addObserver(mScreenObserver); @@ -373,14 +373,13 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake. */ private void setDarkAmount(float darkAmount) { - boolean isAwake = darkAmount != 0; + boolean isFullyDozed = darkAmount == 1; if (darkAmount == mDarkAmount) { return; } mDarkAmount = darkAmount; mListView.setDarkAmount(darkAmount); - mView.setVisibility(isAwake ? View.VISIBLE : View.GONE); - if (!isAwake) { + if (isFullyDozed) { closeSwitcherIfOpenAndNotSimple(false); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java index 738cab15431a..5638503be198 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java @@ -439,7 +439,7 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi private final NetworkCallback mNetworkCallback = new NetworkCallback() { @Override public void onAvailable(Network network) { - if (DEBUG) Log.d(TAG, "onAvailable " + network.netId); + if (DEBUG) Log.d(TAG, "onAvailable " + network.getNetId()); updateState(); fireCallbacks(); }; @@ -448,7 +448,7 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi // how long the VPN connection is held on to. @Override public void onLost(Network network) { - if (DEBUG) Log.d(TAG, "onLost " + network.netId); + if (DEBUG) Log.d(TAG, "onLost " + network.getNetId()); updateState(); fireCallbacks(); }; diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 5d028454a417..f19228783b88 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -43,7 +43,6 @@ import android.util.Log; import androidx.annotation.NonNull; -import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dumpable; import com.android.systemui.SystemUI; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -87,12 +86,6 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { protected static final int SECONDARY = 1; protected static final int NEUTRAL = 2; - // If lock screen wallpaper colors should also be considered when selecting the theme. - // Doing this has performance impact, given that overlays would need to be swapped when - // the device unlocks. - @VisibleForTesting - static final boolean USE_LOCK_SCREEN_WALLPAPER = false; - private final ThemeOverlayApplier mThemeManager; private final UserManager mUserManager; private final BroadcastDispatcher mBroadcastDispatcher; @@ -103,7 +96,6 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { private final WallpaperManager mWallpaperManager; private final KeyguardStateController mKeyguardStateController; private final boolean mIsMonetEnabled; - private WallpaperColors mLockColors; private WallpaperColors mSystemColors; // If fabricated overlays were already created for the current theme. private boolean mNeedsOverlayCreation; @@ -117,6 +109,8 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { private FabricatedOverlay mSecondaryOverlay; // Neutral system colors overlay private FabricatedOverlay mNeutralOverlay; + // If wallpaper color event will be accepted and change the UI colors. + private boolean mAcceptColorEvents = true; @Inject public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher, @@ -146,13 +140,20 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); + filter.addAction(Intent.ACTION_WALLPAPER_CHANGED); mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added."); - reevaluateSystemTheme(true /* forceReload */); + if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction()) + || Intent.ACTION_MANAGED_PROFILE_ADDED.equals(intent.getAction())) { + if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added."); + reevaluateSystemTheme(true /* forceReload */); + } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(intent.getAction())) { + mAcceptColorEvents = true; + Log.i(TAG, "Allowing color events again"); + } } - }, filter, mBgExecutor, UserHandle.ALL); + }, filter, mMainExecutor, UserHandle.ALL); mSecureSettings.registerContentObserverForUser( Settings.Secure.getUriFor(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), false, @@ -170,38 +171,22 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { // Upon boot, make sure we have the most up to date colors mBgExecutor.execute(() -> { - WallpaperColors lockColors = mWallpaperManager.getWallpaperColors( - WallpaperManager.FLAG_LOCK); WallpaperColors systemColor = mWallpaperManager.getWallpaperColors( WallpaperManager.FLAG_SYSTEM); mMainExecutor.execute(() -> { - if (USE_LOCK_SCREEN_WALLPAPER) { - mLockColors = lockColors; - } mSystemColors = systemColor; reevaluateSystemTheme(false /* forceReload */); }); }); - if (USE_LOCK_SCREEN_WALLPAPER) { - mKeyguardStateController.addCallback(new KeyguardStateController.Callback() { - @Override - public void onKeyguardShowingChanged() { - if (mLockColors == null) { - return; - } - // It's possible that the user has a lock screen wallpaper. On this case we'll - // end up with different colors after unlocking. - reevaluateSystemTheme(false /* forceReload */); - } - }); - } mWallpaperManager.addOnColorsChangedListener((wallpaperColors, which) -> { - if (USE_LOCK_SCREEN_WALLPAPER && (which & WallpaperManager.FLAG_LOCK) != 0) { - mLockColors = wallpaperColors; - if (DEBUG) { - Log.d(TAG, "got new lock colors: " + wallpaperColors + " where: " + which); - } + if (!mAcceptColorEvents) { + Log.i(TAG, "Wallpaper color event rejected: " + wallpaperColors); + return; } + if (wallpaperColors != null && mAcceptColorEvents) { + mAcceptColorEvents = false; + } + if ((which & WallpaperManager.FLAG_SYSTEM) != 0) { mSystemColors = wallpaperColors; if (DEBUG) { @@ -213,10 +198,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { } private void reevaluateSystemTheme(boolean forceReload) { - WallpaperColors currentColors = - mKeyguardStateController.isShowing() && mLockColors != null - ? mLockColors : mSystemColors; - + final WallpaperColors currentColors = mSystemColors; final int mainColor; final int accentCandidate; if (currentColors == null) { @@ -378,8 +360,6 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { - pw.println("USE_LOCK_SCREEN_WALLPAPER=" + USE_LOCK_SCREEN_WALLPAPER); - pw.println("mLockColors=" + mLockColors); pw.println("mSystemColors=" + mSystemColors); pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor)); pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor)); @@ -388,5 +368,6 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { pw.println("mNeutralOverlay=" + mNeutralOverlay); pw.println("mIsMonetEnabled=" + mIsMonetEnabled); pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation); + pw.println("mAcceptColorEvents=" + mAcceptColorEvents); } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 25345d5c4b4c..5dc7006406ee 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -778,6 +778,12 @@ public class VolumeDialogImpl implements VolumeDialog, /** Animates away the ringer drawer. */ private void hideRingerDrawer() { + + // If the ringer drawer isn't present, don't try to hide it. + if (mRingerDrawerContainer == null) { + return; + } + // Hide the drawer icon for the selected ringer - it's visible in the ringer button and we // don't want to be able to see it while it animates away. getDrawerIconViewForMode(mState.ringerModeInternal).setVisibility(INVISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index 8d3a0402f4c8..78cd3a823aab 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -375,6 +375,9 @@ public abstract class WMShellBaseModule { return new PipUiEventLogger(uiEventLogger, packageManager); } + @BindsOptionalOf + abstract PipTouchHandler optionalPipTouchHandler(); + // // Shell transitions // @@ -498,6 +501,7 @@ public abstract class WMShellBaseModule { Optional<SplitScreenController> splitScreenOptional, Optional<AppPairsController> appPairsOptional, Optional<StartingSurface> startingSurface, + Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, Transitions transitions, @ShellMainThread ShellExecutor mainExecutor) { @@ -508,6 +512,7 @@ public abstract class WMShellBaseModule { splitScreenOptional, appPairsOptional, startingSurface, + pipTouchHandlerOptional, fullscreenTaskListener, transitions, mainExecutor); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 700f101e44b8..fb778e813adf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -242,9 +242,18 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test - public void registersViewForCallbacks() throws RemoteException { + public void registersAndUnregistersViewForCallbacks() throws RemoteException { + mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, + IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD); + mFgExecutor.runAllReady(); verify(mStatusBarStateController).addCallback(mUdfpsController.mStatusBarStateListener); verify(mStatusBar).addExpansionChangedListener( mUdfpsController.mStatusBarExpansionListener); + + mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID); + mFgExecutor.runAllReady(); + verify(mStatusBarStateController).removeCallback(mUdfpsController.mStatusBarStateListener); + verify(mStatusBar).removeExpansionChangedListener( + mUdfpsController.mStatusBarExpansionListener); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java index 3d53062d7d02..62cc9b7e3602 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java @@ -49,7 +49,7 @@ public class TileAdapterTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); TestableLooper.get(this).runWithLooper(() -> mTileAdapter = - new TileAdapter(mContext, mQSTileHost, new UiEventLoggerFake())); + new TileAdapter(mContext, mQSTileHost, new UiEventLoggerFake(), /* qsFlag */false)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileBaseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileBaseViewTest.kt new file mode 100644 index 000000000000..998e070009cc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileBaseViewTest.kt @@ -0,0 +1,146 @@ +/* + * 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.systemui.qs.tileimpl + +import android.service.quicksettings.Tile +import android.testing.AndroidTestingRunner +import android.text.TextUtils +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.qs.QSIconView +import com.android.systemui.plugins.qs.QSTile +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class QSTileBaseViewTest : SysuiTestCase() { + + @Mock + private lateinit var iconView: QSIconView + + private lateinit var tileView: QSTileBaseView + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + tileView = QSTileBaseView(context, iconView, false) + } + + @Test + fun testSecondaryLabelNotModified_unavailable() { + val state = QSTile.State() + val testString = "TEST STRING" + state.state = Tile.STATE_UNAVAILABLE + state.secondaryLabel = testString + + tileView.handleStateChanged(state) + + assertThat(state.secondaryLabel as CharSequence).isEqualTo(testString) + } + + @Test + fun testSecondaryLabelNotModified_booleanInactive() { + val state = QSTile.BooleanState() + val testString = "TEST STRING" + state.state = Tile.STATE_INACTIVE + state.secondaryLabel = testString + + tileView.handleStateChanged(state) + + assertThat(state.secondaryLabel as CharSequence).isEqualTo(testString) + } + + @Test + fun testSecondaryLabelNotModified_booleanActive() { + val state = QSTile.BooleanState() + val testString = "TEST STRING" + state.state = Tile.STATE_ACTIVE + state.secondaryLabel = testString + + tileView.handleStateChanged(state) + + assertThat(state.secondaryLabel as CharSequence).isEqualTo(testString) + } + + @Test + fun testSecondaryLabelNotModified_availableNotBoolean_inactive() { + val state = QSTile.State() + state.state = Tile.STATE_INACTIVE + state.secondaryLabel = "" + + tileView.handleStateChanged(state) + + assertThat(TextUtils.isEmpty(state.secondaryLabel)).isTrue() + } + + @Test + fun testSecondaryLabelNotModified_availableNotBoolean_active() { + val state = QSTile.State() + state.state = Tile.STATE_ACTIVE + state.secondaryLabel = "" + + tileView.handleStateChanged(state) + + assertThat(TextUtils.isEmpty(state.secondaryLabel)).isTrue() + } + + @Test + fun testSecondaryLabelDescription_unavailable() { + val state = QSTile.State() + state.state = Tile.STATE_UNAVAILABLE + state.secondaryLabel = "" + + tileView.handleStateChanged(state) + + assertThat(state.secondaryLabel as CharSequence).isEqualTo( + context.getString(R.string.tile_unavailable) + ) + } + + @Test + fun testSecondaryLabelDescription_booleanInactive() { + val state = QSTile.BooleanState() + state.state = Tile.STATE_INACTIVE + state.secondaryLabel = "" + + tileView.handleStateChanged(state) + + assertThat(state.secondaryLabel as CharSequence).isEqualTo( + context.getString(R.string.switch_bar_off) + ) + } + + @Test + fun testSecondaryLabelDescription_booleanActive() { + val state = QSTile.BooleanState() + state.state = Tile.STATE_ACTIVE + state.secondaryLabel = "" + + tileView.handleStateChanged(state) + + assertThat(state.secondaryLabel as CharSequence).isEqualTo( + context.getString(R.string.switch_bar_on) + ) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java index 71f146bf0220..f31639cef666 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java @@ -35,6 +35,7 @@ import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.Icon; import android.os.UserHandle; @@ -121,4 +122,13 @@ public class StatusBarIconViewTest extends SysuiTestCase { assertEquals("Transparent backgrounds should fallback to drawable color", color, mIconView.getStaticDrawableColor()); } + + @Test + public void testGiantImageNotAllowed() { + Bitmap largeBitmap = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888); + Icon icon = Icon.createWithBitmap(largeBitmap); + StatusBarIcon largeIcon = new StatusBarIcon(UserHandle.ALL, "mockPackage", + icon, 0, 0, ""); + assertFalse(mIconView.set(largeIcon)); + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index d80c40fcd07b..8a0ac1111b59 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -19,13 +19,13 @@ package com.android.systemui.theme; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_NEUTRAL_PALETTE; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE; -import static com.android.systemui.theme.ThemeOverlayController.USE_LOCK_SCREEN_WALLPAPER; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -33,6 +33,8 @@ import static org.mockito.Mockito.when; import android.app.WallpaperColors; import android.app.WallpaperManager; +import android.content.BroadcastReceiver; +import android.content.Intent; import android.content.om.FabricatedOverlay; import android.content.om.OverlayIdentifier; import android.graphics.Color; @@ -91,7 +93,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { @Mock private FeatureFlags mFeatureFlags; @Captor - private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallback; + private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiver; @Captor private ArgumentCaptor<WallpaperManager.OnColorsChangedListener> mColorsListener; @@ -114,12 +116,10 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { }; mThemeOverlayController.start(); - if (USE_LOCK_SCREEN_WALLPAPER) { - verify(mKeyguardStateController).addCallback( - mKeyguardStateControllerCallback.capture()); - } verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null), eq(UserHandle.USER_ALL)); + verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiver.capture(), any(), + eq(mMainExecutor), any()); verify(mDumpManager).registerDumpable(any(), any()); } @@ -129,7 +129,6 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { verify(mBgExecutor).execute(registrationRunnable.capture()); registrationRunnable.getValue().run(); - verify(mWallpaperManager).getWallpaperColors(eq(WallpaperManager.FLAG_LOCK)); verify(mWallpaperManager).getWallpaperColors(eq(WallpaperManager.FLAG_SYSTEM)); } @@ -156,6 +155,18 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { // Should not ask again if changed to same value mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM); verifyNoMoreInteractions(mThemeOverlayApplier); + + // Should not ask again even for new colors until we change wallpapers + mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK), + null, null), WallpaperManager.FLAG_SYSTEM); + verifyNoMoreInteractions(mThemeOverlayApplier); + + // But should change theme after changing wallpapers + clearInvocations(mThemeOverlayApplier); + mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED)); + mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK), + null, null), WallpaperManager.FLAG_SYSTEM); + verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); } @Test diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 065e2bbd3eef..88e6b66e23a3 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -102,6 +102,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ FingerprintGestureDispatcher.FingerprintGestureClient { private static final boolean DEBUG = false; private static final String LOG_TAG = "AbstractAccessibilityServiceConnection"; + private static final String TRACE_A11Y_SERVICE_CONNECTION = + LOG_TAG + ".IAccessibilityServiceConnection"; + private static final String TRACE_A11Y_SERVICE_CLIENT = + LOG_TAG + ".IAccessibilityServiceClient"; private static final int WAIT_WINDOWS_TIMEOUT_MILLIS = 5000; protected static final String TAKE_SCREENSHOT = "takeScreenshot"; @@ -127,6 +131,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ protected final Object mLock; protected final AccessibilitySecurityPolicy mSecurityPolicy; + protected final AccessibilityTrace mTrace; // The service that's bound to this instance. Whenever this value is non-null, this // object is registered as a death recipient @@ -247,7 +252,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName, AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler, Object lock, AccessibilitySecurityPolicy securityPolicy, SystemSupport systemSupport, - WindowManagerInternal windowManagerInternal, + AccessibilityTrace trace, WindowManagerInternal windowManagerInternal, SystemActionPerformer systemActionPerfomer, AccessibilityWindowManager a11yWindowManager) { mContext = context; @@ -259,6 +264,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ mSecurityPolicy = securityPolicy; mSystemActionPerformer = systemActionPerfomer; mSystemSupport = systemSupport; + mTrace = trace; mMainHandler = mainHandler; mInvocationHandler = new InvocationHandler(mainHandler.getLooper()); mA11yWindowManager = a11yWindowManager; @@ -291,6 +297,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return false; } try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onKeyEvent", + keyEvent + ", " + sequenceNumber); + } mServiceInterface.onKeyEvent(keyEvent, sequenceNumber); } catch (RemoteException e) { return false; @@ -354,11 +364,18 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void setOnKeyEventResult(boolean handled, int sequence) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setOnKeyEventResult", + "handled=" + handled + ";sequence=" + sequence); + } mSystemSupport.getKeyEventDispatcher().setOnKeyEventResult(this, handled, sequence); } @Override public AccessibilityServiceInfo getServiceInfo() { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getServiceInfo"); + } synchronized (mLock) { return mAccessibilityServiceInfo; } @@ -375,6 +392,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void setServiceInfo(AccessibilityServiceInfo info) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setServiceInfo", "info=" + info); + } final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { @@ -400,6 +420,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Nullable @Override public AccessibilityWindowInfo.WindowListSparseArray getWindows() { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getWindows"); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return null; @@ -434,6 +457,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public AccessibilityWindowInfo getWindow(int windowId) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getWindow", "windowId=" + windowId); + } synchronized (mLock) { int displayId = Display.INVALID_DISPLAY; if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) { @@ -469,6 +495,13 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ long accessibilityNodeId, String viewIdResName, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".findAccessibilityNodeInfosByViewId", + "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId=" + + accessibilityNodeId + ";viewIdResName=" + viewIdResName + ";interactionId=" + + interactionId + ";callback=" + callback + ";interrogatingTid=" + + interrogatingTid); + } final int resolvedWindowId; RemoteAccessibilityConnection connection; Region partialInteractiveRegion = Region.obtain(); @@ -530,6 +563,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ long accessibilityNodeId, String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".findAccessibilityNodeInfosByText", + "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId=" + + accessibilityNodeId + ";text=" + text + ";interactionId=" + interactionId + + ";callback=" + callback + ";interrogatingTid=" + interrogatingTid); + } final int resolvedWindowId; RemoteAccessibilityConnection connection; Region partialInteractiveRegion = Region.obtain(); @@ -591,6 +630,14 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ int accessibilityWindowId, long accessibilityNodeId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, long interrogatingTid, Bundle arguments) throws RemoteException { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace( + TRACE_A11Y_SERVICE_CONNECTION + ".findAccessibilityNodeInfoByAccessibilityId", + "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId=" + + accessibilityNodeId + ";interactionId=" + interactionId + ";callback=" + + callback + ";flags=" + flags + ";interrogatingTid=" + interrogatingTid + + ";arguments=" + arguments); + } final int resolvedWindowId; RemoteAccessibilityConnection connection; Region partialInteractiveRegion = Region.obtain(); @@ -652,6 +699,13 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ int focusType, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".findFocus", + "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId=" + + accessibilityNodeId + ";focusType=" + focusType + ";interactionId=" + + interactionId + ";callback=" + callback + ";interrogatingTid=" + + interrogatingTid); + } final int resolvedWindowId; RemoteAccessibilityConnection connection; Region partialInteractiveRegion = Region.obtain(); @@ -713,6 +767,13 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ int direction, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".focusSearch", + "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId=" + + accessibilityNodeId + ";direction=" + direction + ";interactionId=" + + interactionId + ";callback=" + callback + ";interrogatingTid=" + + interrogatingTid); + } final int resolvedWindowId; RemoteAccessibilityConnection connection; Region partialInteractiveRegion = Region.obtain(); @@ -770,10 +831,18 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void sendGesture(int sequence, ParceledListSlice gestureSteps) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".sendGesture", + "sequence=" + sequence + ";gestureSteps=" + gestureSteps); + } } @Override public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".dispatchGesture", "sequence=" + + sequence + ";gestureSteps=" + gestureSteps + ";displayId=" + displayId); + } } @Override @@ -781,6 +850,13 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ long accessibilityNodeId, int action, Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".performAccessibilityAction", + "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId=" + + accessibilityNodeId + ";action=" + action + ";arguments=" + arguments + + ";interactionId=" + interactionId + ";callback=" + callback + + ";interrogatingTid=" + interrogatingTid); + } final int resolvedWindowId; synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { @@ -802,6 +878,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public boolean performGlobalAction(int action) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".performGlobalAction", + "action=" + action); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return false; @@ -812,6 +892,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public @NonNull List<AccessibilityNodeInfo.AccessibilityAction> getSystemActions() { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getSystemActions"); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return Collections.emptyList(); @@ -822,6 +905,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public boolean isFingerprintGestureDetectionAvailable() { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace( + TRACE_A11Y_SERVICE_CONNECTION + ".isFingerprintGestureDetectionAvailable"); + } if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { return false; } @@ -835,6 +922,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public float getMagnificationScale(int displayId) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationScale", + "displayId=" + displayId); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return 1.0f; @@ -850,6 +941,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public Region getMagnificationRegion(int displayId) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationRegion", + "displayId=" + displayId); + } synchronized (mLock) { final Region region = Region.obtain(); if (!hasRightsToCurrentUserLocked()) { @@ -874,6 +969,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public float getMagnificationCenterX(int displayId) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationCenterX", + "displayId=" + displayId); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return 0.0f; @@ -896,6 +995,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public float getMagnificationCenterY(int displayId) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationCenterY", + "displayId=" + displayId); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return 0.0f; @@ -928,6 +1031,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public boolean resetMagnification(int displayId, boolean animate) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".resetMagnification", + "displayId=" + displayId + ";animate=" + animate); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return false; @@ -950,6 +1057,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX, float centerY, boolean animate) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setMagnificationScaleAndCenter", + "displayId=" + displayId + ";scale=" + scale + ";centerX=" + centerX + + ";centerY=" + centerY + ";animate=" + animate); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return false; @@ -974,6 +1086,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void setMagnificationCallbackEnabled(int displayId, boolean enabled) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setMagnificationCallbackEnabled", + "displayId=" + displayId + ";enabled=" + enabled); + } mInvocationHandler.setMagnificationCallbackEnabled(displayId, enabled); } @@ -983,11 +1099,19 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void setSoftKeyboardCallbackEnabled(boolean enabled) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setSoftKeyboardCallbackEnabled", + "enabled=" + enabled); + } mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled); } @Override public void takeScreenshot(int displayId, RemoteCallback callback) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".takeScreenshot", + "displayId=" + displayId + ";callback=" + callback); + } final long currentTimestamp = SystemClock.uptimeMillis(); if (mRequestTakeScreenshotTimestampMs != 0 && (currentTimestamp - mRequestTakeScreenshotTimestampMs) @@ -1157,6 +1281,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ */ @Override public IBinder getOverlayWindowToken(int displayId) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getOverlayWindowToken", + "displayId=" + displayId); + } synchronized (mLock) { return mOverlayWindowTokens.get(displayId); } @@ -1170,6 +1298,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ */ @Override public int getWindowIdForLeashToken(@NonNull IBinder token) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getWindowIdForLeashToken", + "token=" + token); + } synchronized (mLock) { return mA11yWindowManager.getWindowIdLocked(token); } @@ -1181,6 +1313,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ // Clear the proxy in the other process so this // IAccessibilityServiceConnection can be garbage collected. if (mServiceInterface != null) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".init", "null, " + mId + ", null"); + } mServiceInterface.init(null, mId, null); } } catch (RemoteException re) { @@ -1329,6 +1464,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onAccessibilityEvent", + event + ";" + serviceWantsEvent); + } listener.onAccessibilityEvent(event, serviceWantsEvent); if (DEBUG) { Slog.i(LOG_TAG, "Event " + event + " sent to " + listener); @@ -1382,6 +1521,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onMagnificationChanged", displayId + + ", " + region + ", " + scale + ", " + centerX + ", " + centerY); + } listener.onMagnificationChanged(displayId, region, scale, centerX, centerY); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re); @@ -1397,6 +1540,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onSoftKeyboardShowModeChanged", + String.valueOf(showState)); + } listener.onSoftKeyboardShowModeChanged(showState); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error sending soft keyboard show mode changes to " + mService, @@ -1409,6 +1556,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onAccessibilityButtonClicked", + String.valueOf(displayId)); + } listener.onAccessibilityButtonClicked(displayId); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error sending accessibility button click to " + mService, re); @@ -1427,6 +1578,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace( + TRACE_A11Y_SERVICE_CLIENT + ".onAccessibilityButtonAvailabilityChanged", + String.valueOf(available)); + } listener.onAccessibilityButtonAvailabilityChanged(available); } catch (RemoteException re) { Slog.e(LOG_TAG, @@ -1440,6 +1596,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onGesture", + gestureInfo.toString()); + } listener.onGesture(gestureInfo); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error during sending gesture " + gestureInfo @@ -1452,6 +1612,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onSystemActionsChanged"); + } listener.onSystemActionsChanged(); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error sending system actions change to " + mService, @@ -1464,6 +1627,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".clearAccessibilityCache"); + } listener.clearAccessibilityCache(); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error during requesting accessibility info cache" @@ -1790,14 +1956,27 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void setGestureDetectionPassthroughRegion(int displayId, Region region) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setGestureDetectionPassthroughRegion", + "displayId=" + displayId + ";region=" + region); + } mSystemSupport.setGestureDetectionPassthroughRegion(displayId, region); } @Override public void setTouchExplorationPassthroughRegion(int displayId, Region region) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setTouchExplorationPassthroughRegion", + "displayId=" + displayId + ";region=" + region); + } mSystemSupport.setTouchExplorationPassthroughRegion(displayId, region); } @Override - public void setFocusAppearance(int strokeWidth, int color) { } + public void setFocusAppearance(int strokeWidth, int color) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setFocusAppearance", + "strokeWidth=" + strokeWidth + ";color=" + color); + } + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index c63c2e1a257d..b3be0448edaf 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -149,6 +149,7 @@ import java.util.function.Predicate; */ public class AccessibilityManagerService extends IAccessibilityManager.Stub implements AbstractAccessibilityServiceConnection.SystemSupport, + AccessibilityTrace, AccessibilityUserState.ServiceInfoChangeListener, AccessibilityWindowManager.AccessibilityEventSender, AccessibilitySecurityPolicy.AccessibilityUserManager, @@ -243,6 +244,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final SparseArray<AccessibilityUserState> mUserStates = new SparseArray<>(); private final UiAutomationManager mUiAutomationManager = new UiAutomationManager(mLock); + private final WindowManagerInternal.AccessibilityControllerInternal mA11yController; private int mCurrentUserId = UserHandle.USER_SYSTEM; @@ -288,6 +290,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mContext = context; mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mWindowManagerService = LocalServices.getService(WindowManagerInternal.class); + mA11yController = mWindowManagerService.getAccessibilityController(); mMainHandler = new MainHandler(mContext.getMainLooper()); mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class); mPackageManager = packageManager; @@ -308,6 +311,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mContext = context; mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mWindowManagerService = LocalServices.getService(WindowManagerInternal.class); + mA11yController = mWindowManagerService.getAccessibilityController(); mMainHandler = new MainHandler(mContext.getMainLooper()); mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class); mPackageManager = mContext.getPackageManager(); @@ -328,16 +332,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public int getCurrentUserIdLocked() { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getCurrentUserIdLocked"); + } return mCurrentUserId; } @Override public boolean isAccessibilityButtonShown() { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".isAccessibilityButtonShown"); + } return mIsAccessibilityButtonShown; } @Override public void onServiceInfoChangedLocked(AccessibilityUserState userState) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".onServiceInfoChangedLocked", "userState=" + userState); + } scheduleNotifyClientsOfServicesStateChangeLocked(userState); } @@ -395,6 +408,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub PackageMonitor monitor = new PackageMonitor() { @Override public void onSomePackagesChanged() { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".PM.onSomePackagesChanged"); + } + synchronized (mLock) { // Only the profile parent can install accessibility services. // Therefore we ignore packages from linked profiles. @@ -419,6 +436,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // mBindingServices in binderDied() during updating. Remove services from this // package from mBindingServices, and then update the user state to re-bind new // versions of them. + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".PM.onPackageUpdateFinished", + "packageName=" + packageName + ";uid=" + uid); + } synchronized (mLock) { final int userId = getChangingUserId(); if (userId != mCurrentUserId) { @@ -448,6 +469,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void onPackageRemoved(String packageName, int uid) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".PM.onPackageRemoved", + "packageName=" + packageName + ";uid=" + uid); + } + synchronized (mLock) { final int userId = getChangingUserId(); // Only the profile parent can install accessibility services. @@ -487,6 +513,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".PM.onHandleForceStop", "intent=" + intent + ";packages=" + + packages + ";uid=" + uid + ";doit=" + doit); + } synchronized (mLock) { final int userId = getChangingUserId(); // Only the profile parent can install accessibility services. @@ -533,6 +563,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mContext.registerReceiverAsUser(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".BR.onReceive", "context=" + context + ";intent=" + intent); + } + String action = intent.getAction(); if (Intent.ACTION_USER_SWITCHED.equals(action)) { switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); @@ -616,6 +650,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public long addClient(IAccessibilityManagerClient callback, int userId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".addClient", "callback=" + callback + ";userId=" + userId); + } + synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below @@ -654,6 +692,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void sendAccessibilityEvent(AccessibilityEvent event, int userId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".sendAccessibilityEvent", "event=" + event + ";userId=" + userId); + } boolean dispatchEvent = false; synchronized (mLock) { @@ -746,6 +787,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public void registerSystemAction(RemoteAction action, int actionId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".registerSystemAction", "action=" + action + ";actionId=" + + actionId); + } mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY); getSystemActionPerformer().registerSystemAction(actionId, action); } @@ -757,6 +802,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public void unregisterSystemAction(int actionId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".unregisterSystemAction", "actionId=" + actionId); + } mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY); getSystemActionPerformer().unregisterSystemAction(actionId); } @@ -771,6 +819,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getInstalledAccessibilityServiceList", "userId=" + userId); + } + synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below @@ -788,6 +840,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, int userId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getEnabledAccessibilityServiceList", + "feedbackType=" + feedbackType + ";userId=" + userId); + } + synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below @@ -816,6 +873,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void interrupt(int userId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".interrupt", "userId=" + userId); + } + List<IAccessibilityServiceClient> interfacesToInterrupt; synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles @@ -842,6 +903,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) { try { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".IAccessibilityServiceClient.onInterrupt"); + } interfacesToInterrupt.get(i).onInterrupt(); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error sending interrupt request to " @@ -854,18 +918,31 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken, IAccessibilityInteractionConnection connection, String packageName, int userId) throws RemoteException { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".addAccessibilityInteractionConnection", + "windowToken=" + windowToken + "leashToken=" + leashToken + ";connection=" + + connection + "; packageName=" + packageName + ";userId=" + userId); + } + return mA11yWindowManager.addAccessibilityInteractionConnection( windowToken, leashToken, connection, packageName, userId); } @Override public void removeAccessibilityInteractionConnection(IWindow window) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".removeAccessibilityInteractionConnection", "window=" + window); + } mA11yWindowManager.removeAccessibilityInteractionConnection(window); } @Override public void setPictureInPictureActionReplacingConnection( IAccessibilityInteractionConnection connection) throws RemoteException { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".setPictureInPictureActionReplacingConnection", + "connection=" + connection); + } mSecurityPolicy.enforceCallingPermission(Manifest.permission.MODIFY_ACCESSIBILITY_DATA, SET_PIP_ACTION_REPLACEMENT); mA11yWindowManager.setPictureInPictureActionReplacingConnection(connection); @@ -876,13 +953,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub IAccessibilityServiceClient serviceClient, AccessibilityServiceInfo accessibilityServiceInfo, int flags) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".registerUiTestAutomationService", "owner=" + owner + + ";serviceClient=" + serviceClient + ";accessibilityServiceInfo=" + + accessibilityServiceInfo + ";flags=" + flags); + } + mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT, FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE); synchronized (mLock) { mUiAutomationManager.registerUiTestAutomationServiceLocked(owner, serviceClient, mContext, accessibilityServiceInfo, sIdCounter++, mMainHandler, - mSecurityPolicy, this, mWindowManagerService, getSystemActionPerformer(), + mSecurityPolicy, this, this, mWindowManagerService, getSystemActionPerformer(), mA11yWindowManager, flags); onUserStateChangedLocked(getCurrentUserStateLocked()); } @@ -890,6 +973,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".unregisterUiTestAutomationService", + "serviceClient=" + serviceClient); + } synchronized (mLock) { mUiAutomationManager.unregisterUiTestAutomationServiceLocked(serviceClient); } @@ -898,6 +985,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void temporaryEnableAccessibilityStateUntilKeyguardRemoved( ComponentName service, boolean touchExplorationEnabled) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".temporaryEnableAccessibilityStateUntilKeyguardRemoved", + "service=" + service + ";touchExplorationEnabled=" + touchExplorationEnabled); + } + mSecurityPolicy.enforceCallingPermission( Manifest.permission.TEMPORARY_ENABLE_ACCESSIBILITY, TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED); @@ -926,6 +1018,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public IBinder getWindowToken(int windowId, int userId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getWindowToken", "windowId=" + windowId + ";userId=" + userId); + } + mSecurityPolicy.enforceCallingPermission( Manifest.permission.RETRIEVE_WINDOW_TOKEN, GET_WINDOW_TOKEN); @@ -965,6 +1061,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public void notifyAccessibilityButtonClicked(int displayId, String targetName) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".notifyAccessibilityButtonClicked", + "displayId=" + displayId + ";targetName=" + targetName); + } + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Caller does not hold permission " @@ -990,6 +1091,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public void notifyAccessibilityButtonVisibilityChanged(boolean shown) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".notifyAccessibilityButtonVisibilityChanged", "shown=" + shown); + } + mSecurityPolicy.enforceCallingOrSelfPermission( android.Manifest.permission.STATUS_BAR_SERVICE); synchronized (mLock) { @@ -1018,6 +1123,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public void onSystemActionsChanged() { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".onSystemActionsChanged"); + } + synchronized (mLock) { AccessibilityUserState state = getCurrentUserStateLocked(); notifySystemActionsChangedLocked(state); @@ -1080,6 +1189,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public @Nullable MotionEventInjector getMotionEventInjectorForDisplayLocked(int displayId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getMotionEventInjectorForDisplayLocked", "displayId=" + displayId); + } + final long endMillis = SystemClock.uptimeMillis() + WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS; MotionEventInjector motionEventInjector = null; while ((mMotionEventInjectors == null) && (SystemClock.uptimeMillis() < endMillis)) { @@ -1646,6 +1759,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void persistComponentNamesToSettingLocked(String settingName, Set<ComponentName> componentNames, int userId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".persistComponentNamesToSettingLocked", "settingName=" + settingName + + ";componentNames=" + componentNames + ";userId=" + userId); + } + persistColonDelimitedSetToSettingLocked(settingName, userId, componentNames, componentName -> componentName.flattenToShortString()); } @@ -1730,7 +1848,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (service == null) { service = new AccessibilityServiceConnection(userState, mContext, componentName, installedService, sIdCounter++, mMainHandler, mLock, mSecurityPolicy, - this, mWindowManagerService, getSystemActionPerformer(), + this, this, mWindowManagerService, getSystemActionPerformer(), mA11yWindowManager, mActivityTaskManagerService); } else if (userState.mBoundServices.contains(service)) { continue; @@ -2607,6 +2725,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @GuardedBy("mLock") @Override public MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getCompatibleMagnificationSpecLocked", "windowId=" + windowId); + } + IBinder windowToken = mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked( mCurrentUserId, windowId); if (windowToken != null) { @@ -2618,6 +2740,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public KeyEventDispatcher getKeyEventDispatcher() { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getKeyEventDispatcher"); + } + if (mKeyEventDispatcher == null) { mKeyEventDispatcher = new KeyEventDispatcher( mMainHandler, MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER, mLock, @@ -2630,6 +2756,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @SuppressWarnings("AndroidFrameworkPendingIntentMutability") public PendingIntent getPendingIntentActivity(Context context, int requestCode, Intent intent, int flags) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getPendingIntentActivity", "context=" + context + ";requestCode=" + + requestCode + ";intent=" + intent + ";flags=" + flags); + } + + return PendingIntent.getActivity(context, requestCode, intent, flags); } @@ -2644,6 +2776,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public void performAccessibilityShortcut(String targetName) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".performAccessibilityShortcut", "targetName=" + targetName); + } + if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) && (mContext.checkCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY) != PackageManager.PERMISSION_GRANTED)) { @@ -2828,6 +2964,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getAccessibilityShortcutTargets", "shortcutType=" + shortcutType); + } + if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException( @@ -2897,6 +3037,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void sendAccessibilityEventForCurrentUserLocked(AccessibilityEvent event) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".sendAccessibilityEventForCurrentUserLocked", "event=" + event); + } + sendAccessibilityEventLocked(event, mCurrentUserId); } @@ -2918,6 +3062,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public boolean sendFingerprintGesture(int gestureKeyCode) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".sendFingerprintGesture", "gestureKeyCode=" + gestureKeyCode); + } + synchronized(mLock) { if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) { throw new SecurityException("Only SYSTEM can call sendFingerprintGesture"); @@ -2939,6 +3087,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public int getAccessibilityWindowId(@Nullable IBinder windowToken) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getAccessibilityWindowId", "windowToken=" + windowToken); + } + synchronized (mLock) { if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) { throw new SecurityException("Only SYSTEM can call getAccessibilityWindowId"); @@ -2956,6 +3108,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public long getRecommendedTimeoutMillis() { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getRecommendedTimeoutMillis"); + } + synchronized(mLock) { final AccessibilityUserState userState = getCurrentUserStateLocked(); return getRecommendedTimeoutMillisLocked(userState); @@ -2970,6 +3126,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void setWindowMagnificationConnection( IWindowMagnificationConnection connection) throws RemoteException { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".setWindowMagnificationConnection", "connection=" + connection); + } + mSecurityPolicy.enforceCallingOrSelfPermission( android.Manifest.permission.STATUS_BAR_SERVICE); @@ -3000,6 +3160,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void associateEmbeddedHierarchy(@NonNull IBinder host, @NonNull IBinder embedded) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".associateEmbeddedHierarchy", + "host=" + host + ";embedded=" + embedded); + } + synchronized (mLock) { mA11yWindowManager.associateEmbeddedHierarchyLocked(host, embedded); } @@ -3007,6 +3172,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void disassociateEmbeddedHierarchy(@NonNull IBinder token) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".disassociateEmbeddedHierarchy", "token=" + token); + } + synchronized (mLock) { mA11yWindowManager.disassociateEmbeddedHierarchyLocked(token); } @@ -3084,6 +3253,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public FullScreenMagnificationController getFullScreenMagnificationController() { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getFullScreenMagnificationController"); + } synchronized (mLock) { return mMagnificationController.getFullScreenMagnificationController(); } @@ -3091,6 +3263,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void onClientChangeLocked(boolean serviceInfoChanged) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".onClientChangeLocked", "serviceInfoChanged=" + serviceInfoChanged); + } + AccessibilityUserState userState = getUserStateLocked(mCurrentUserId); onUserStateChangedLocked(userState); if (serviceInfoChanged) { @@ -3126,8 +3302,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilityServiceConnection service = new AccessibilityServiceConnection( userState, mContext, COMPONENT_NAME, info, sIdCounter++, mMainHandler, mLock, mSecurityPolicy, - AccessibilityManagerService.this, mWindowManagerService, - getSystemActionPerformer(), mA11yWindowManager, mActivityTaskManagerService) { + AccessibilityManagerService.this, AccessibilityManagerService.this, + mWindowManagerService, getSystemActionPerformer(), mA11yWindowManager, + mActivityTaskManagerService) { @Override public boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) { return true; @@ -3614,6 +3791,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void setGestureDetectionPassthroughRegion(int displayId, Region region) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".setGestureDetectionPassthroughRegion", + "displayId=" + displayId + ";region=" + region); + } + mMainHandler.sendMessage( obtainMessage( AccessibilityManagerService::setGestureDetectionPassthroughRegionInternal, @@ -3624,6 +3806,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void setTouchExplorationPassthroughRegion(int displayId, Region region) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".setTouchExplorationPassthroughRegion", + "displayId=" + displayId + ";region=" + region); + } + mMainHandler.sendMessage( obtainMessage( AccessibilityManagerService::setTouchExplorationPassthroughRegionInternal, @@ -3661,4 +3848,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub }); } + + @Override + public boolean isA11yTracingEnabled() { + return mA11yController.isAccessibilityTracingEnabled(); + } + + @Override + public void logTrace(String where) { + logTrace(where, ""); + } + + @Override + public void logTrace(String where, String callingParams) { + mA11yController.logTrace(where, callingParams, "".getBytes(), + Binder.getCallingUid(), Thread.currentThread().getStackTrace()); + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 675626841d17..7d75b738d818 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -53,6 +53,10 @@ import java.util.Set; */ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnection { private static final String LOG_TAG = "AccessibilityServiceConnection"; + private static final String TRACE_A11Y_SERVICE_CONNECTION = + LOG_TAG + ".IAccessibilityServiceConnection"; + private static final String TRACE_A11Y_SERVICE_CLIENT = + LOG_TAG + ".IAccessibilityServiceClient"; /* Holding a weak reference so there isn't a loop of references. AccessibilityUserState keeps lists of bound and binding services. These are freed on user changes, but just in case it @@ -70,11 +74,12 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect ComponentName componentName, AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler, Object lock, AccessibilitySecurityPolicy securityPolicy, SystemSupport systemSupport, - WindowManagerInternal windowManagerInternal, + AccessibilityTrace trace, WindowManagerInternal windowManagerInternal, SystemActionPerformer systemActionPerfomer, AccessibilityWindowManager awm, ActivityTaskManagerInternal activityTaskManagerService) { super(context, componentName, accessibilityServiceInfo, id, mainHandler, lock, - securityPolicy, systemSupport, windowManagerInternal, systemActionPerfomer, awm); + securityPolicy, systemSupport, trace, windowManagerInternal, systemActionPerfomer, + awm); mUserStateWeakReference = new WeakReference<AccessibilityUserState>(userState); mIntent = new Intent().setComponent(mComponentName); mMainHandler = mainHandler; @@ -132,6 +137,9 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public void disableSelf() { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".disableSelf"); + } synchronized (mLock) { AccessibilityUserState userState = mUserStateWeakReference.get(); if (userState == null) return; @@ -210,6 +218,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect return; } try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".init", this + ", " + mId + ", " + + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY)); + } serviceInterface.init(this, mId, mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY)); } catch (RemoteException re) { Slog.w(LOG_TAG, "Error while setting connection for service: " @@ -252,6 +264,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public boolean setSoftKeyboardShowMode(int showMode) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setSoftKeyboardShowMode", + "showMode=" + showMode); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return false; @@ -264,12 +280,19 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public int getSoftKeyboardShowMode() { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getSoftKeyboardShowMode"); + } final AccessibilityUserState userState = mUserStateWeakReference.get(); return (userState != null) ? userState.getSoftKeyboardShowModeLocked() : 0; } @Override public boolean switchToInputMethod(String imeId) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".switchToInputMethod", + "imeId=" + imeId); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return false; @@ -288,6 +311,9 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public boolean isAccessibilityButtonAvailable() { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".isAccessibilityButtonAvailable"); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return false; @@ -347,6 +373,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } if (serviceInterface != null) { try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + + ".onFingerprintCapturingGesturesChanged", String.valueOf(active)); + } mServiceInterface.onFingerprintCapturingGesturesChanged(active); } catch (RemoteException e) { } @@ -364,6 +394,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } if (serviceInterface != null) { try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onFingerprintGesture", + String.valueOf(gesture)); + } mServiceInterface.onFingerprintGesture(gesture); } catch (RemoteException e) { } @@ -382,6 +416,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect gestureSteps.getList(), mServiceInterface, sequence, displayId); } else { try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onPerformGestureResult", + sequence + ", false"); + } mServiceInterface.onPerformGestureResult(sequence, false); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error sending motion event injection failure to " diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityTrace.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityTrace.java new file mode 100644 index 000000000000..0c03877d6e44 --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityTrace.java @@ -0,0 +1,41 @@ +/** + * 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.accessibility; + +/** + * Interface to log accessibility trace. + */ +public interface AccessibilityTrace { + /** + * Whether the trace is enabled. + */ + boolean isA11yTracingEnabled(); + + /** + * Log one trace entry. + * @param where A string to identify this log entry, which can be used to filter/search + * through the tracing file. + */ + void logTrace(String where); + + /** + * Log one trace entry. + * @param where A string to identify this log entry, which can be used to filter/search + * through the tracing file. + * @param callingParams The parameters for the method to be logged. + */ + void logTrace(String where, String callingParams); +} diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java index 6828dd916701..bafb641dcc9e 100644 --- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java +++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java @@ -40,34 +40,29 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection private final IAccessibilityInteractionConnectionCallback mServiceCallback; private final IAccessibilityInteractionConnection mConnectionWithReplacementActions; private final int mInteractionId; - private final int mNodeWithReplacementActionsInteractionId; private final Object mLock = new Object(); @GuardedBy("mLock") - private boolean mReplacementNodeIsReadyOrFailed; - - @GuardedBy("mLock") - AccessibilityNodeInfo mNodeWithReplacementActions; + List<AccessibilityNodeInfo> mNodesWithReplacementActions; @GuardedBy("mLock") List<AccessibilityNodeInfo> mNodesFromOriginalWindow; @GuardedBy("mLock") - boolean mSetFindNodeFromOriginalWindowCalled = false; - - @GuardedBy("mLock") AccessibilityNodeInfo mNodeFromOriginalWindow; + // Keep track of whether or not we've been called back for a single node @GuardedBy("mLock") - boolean mSetFindNodesFromOriginalWindowCalled = false; - + boolean mSingleNodeCallbackHappened; + // Keep track of whether or not we've been called back for multiple node @GuardedBy("mLock") - List<AccessibilityNodeInfo> mPrefetchedNodesFromOriginalWindow; + boolean mMultiNodeCallbackHappened; + // We shouldn't get any more callbacks after we've called back the original service, but + // keep track to make sure we catch such strange things @GuardedBy("mLock") - boolean mSetPrefetchFromOriginalWindowCalled = false; - + boolean mDone; public ActionReplacingCallback(IAccessibilityInteractionConnectionCallback serviceCallback, IAccessibilityInteractionConnection connectionWithReplacementActions, @@ -75,20 +70,19 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection mServiceCallback = serviceCallback; mConnectionWithReplacementActions = connectionWithReplacementActions; mInteractionId = interactionId; - mNodeWithReplacementActionsInteractionId = interactionId + 1; // Request the root node of the replacing window final long identityToken = Binder.clearCallingIdentity(); try { mConnectionWithReplacementActions.findAccessibilityNodeInfoByAccessibilityId( - AccessibilityNodeInfo.ROOT_NODE_ID, null, - mNodeWithReplacementActionsInteractionId, this, 0, + AccessibilityNodeInfo.ROOT_NODE_ID, null, interactionId + 1, this, 0, interrogatingPid, interrogatingTid, null, null); } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()"); } - mReplacementNodeIsReadyOrFailed = true; + // Pretend we already got a (null) list of replacement nodes + mMultiNodeCallbackHappened = true; } finally { Binder.restoreCallingIdentity(identityToken); } @@ -96,67 +90,46 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection @Override public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, int interactionId) { - synchronized (mLock) { + boolean readyForCallback; + synchronized(mLock) { if (interactionId == mInteractionId) { mNodeFromOriginalWindow = info; - mSetFindNodeFromOriginalWindowCalled = true; - } else if (interactionId == mNodeWithReplacementActionsInteractionId) { - mNodeWithReplacementActions = info; - mReplacementNodeIsReadyOrFailed = true; } else { Slog.e(LOG_TAG, "Callback with unexpected interactionId"); return; } + + mSingleNodeCallbackHappened = true; + readyForCallback = mMultiNodeCallbackHappened; + } + if (readyForCallback) { + replaceInfoActionsAndCallService(); } - replaceInfoActionsAndCallServiceIfReady(); } @Override public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos, int interactionId) { - synchronized (mLock) { + boolean callbackForSingleNode; + boolean callbackForMultipleNodes; + synchronized(mLock) { if (interactionId == mInteractionId) { mNodesFromOriginalWindow = infos; - mSetFindNodesFromOriginalWindowCalled = true; - } else if (interactionId == mNodeWithReplacementActionsInteractionId) { - setNodeWithReplacementActionsFromList(infos); - mReplacementNodeIsReadyOrFailed = true; + } else if (interactionId == mInteractionId + 1) { + mNodesWithReplacementActions = infos; } else { Slog.e(LOG_TAG, "Callback with unexpected interactionId"); return; } + callbackForSingleNode = mSingleNodeCallbackHappened; + callbackForMultipleNodes = mMultiNodeCallbackHappened; + mMultiNodeCallbackHappened = true; } - replaceInfoActionsAndCallServiceIfReady(); - } - - @Override - public void setPrefetchAccessibilityNodeInfoResult(List<AccessibilityNodeInfo> infos, - int interactionId) - throws RemoteException { - synchronized (mLock) { - if (interactionId == mInteractionId) { - mPrefetchedNodesFromOriginalWindow = infos; - mSetPrefetchFromOriginalWindowCalled = true; - } else { - Slog.e(LOG_TAG, "Callback with unexpected interactionId"); - return; - } + if (callbackForSingleNode) { + replaceInfoActionsAndCallService(); } - replaceInfoActionsAndCallServiceIfReady(); - } - - private void replaceInfoActionsAndCallServiceIfReady() { - replaceInfoActionsAndCallService(); - replaceInfosActionsAndCallService(); - replacePrefetchInfosActionsAndCallService(); - } - - private void setNodeWithReplacementActionsFromList(List<AccessibilityNodeInfo> infos) { - for (int i = 0; i < infos.size(); i++) { - AccessibilityNodeInfo info = infos.get(i); - if (info.getSourceNodeId() == AccessibilityNodeInfo.ROOT_NODE_ID) { - mNodeWithReplacementActions = info; - } + if (callbackForMultipleNodes) { + replaceInfosActionsAndCallService(); } } @@ -169,81 +142,55 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection private void replaceInfoActionsAndCallService() { final AccessibilityNodeInfo nodeToReturn; - boolean doCallback = false; synchronized (mLock) { - doCallback = mReplacementNodeIsReadyOrFailed - && mSetFindNodeFromOriginalWindowCalled; - if (doCallback && mNodeFromOriginalWindow != null) { + if (mDone) { + if (DEBUG) { + Slog.e(LOG_TAG, "Extra callback"); + } + return; + } + if (mNodeFromOriginalWindow != null) { replaceActionsOnInfoLocked(mNodeFromOriginalWindow); - mSetFindNodeFromOriginalWindowCalled = false; } + recycleReplaceActionNodesLocked(); nodeToReturn = mNodeFromOriginalWindow; + mDone = true; } - if (doCallback) { - try { - mServiceCallback.setFindAccessibilityNodeInfoResult(nodeToReturn, mInteractionId); - } catch (RemoteException re) { - if (DEBUG) { - Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfoResult"); - } + try { + mServiceCallback.setFindAccessibilityNodeInfoResult(nodeToReturn, mInteractionId); + } catch (RemoteException re) { + if (DEBUG) { + Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfoResult"); } } } private void replaceInfosActionsAndCallService() { - List<AccessibilityNodeInfo> nodesToReturn = null; - boolean doCallback = false; + final List<AccessibilityNodeInfo> nodesToReturn; synchronized (mLock) { - doCallback = mReplacementNodeIsReadyOrFailed - && mSetFindNodesFromOriginalWindowCalled; - if (doCallback) { - nodesToReturn = replaceActionsLocked(mNodesFromOriginalWindow); - mSetFindNodesFromOriginalWindowCalled = false; - } - } - if (doCallback) { - try { - mServiceCallback.setFindAccessibilityNodeInfosResult(nodesToReturn, mInteractionId); - } catch (RemoteException re) { + if (mDone) { if (DEBUG) { - Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult"); + Slog.e(LOG_TAG, "Extra callback"); } + return; } - } - } - - private void replacePrefetchInfosActionsAndCallService() { - List<AccessibilityNodeInfo> nodesToReturn = null; - boolean doCallback = false; - synchronized (mLock) { - doCallback = mReplacementNodeIsReadyOrFailed - && mSetPrefetchFromOriginalWindowCalled; - if (doCallback) { - nodesToReturn = replaceActionsLocked(mPrefetchedNodesFromOriginalWindow); - mSetPrefetchFromOriginalWindowCalled = false; - } - } - if (doCallback) { - try { - mServiceCallback.setPrefetchAccessibilityNodeInfoResult( - nodesToReturn, mInteractionId); - } catch (RemoteException re) { - if (DEBUG) { - Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult"); + if (mNodesFromOriginalWindow != null) { + for (int i = 0; i < mNodesFromOriginalWindow.size(); i++) { + replaceActionsOnInfoLocked(mNodesFromOriginalWindow.get(i)); } } + recycleReplaceActionNodesLocked(); + nodesToReturn = (mNodesFromOriginalWindow == null) + ? null : new ArrayList<>(mNodesFromOriginalWindow); + mDone = true; } - } - - @GuardedBy("mLock") - private List<AccessibilityNodeInfo> replaceActionsLocked(List<AccessibilityNodeInfo> infos) { - if (infos != null) { - for (int i = 0; i < infos.size(); i++) { - replaceActionsOnInfoLocked(infos.get(i)); + try { + mServiceCallback.setFindAccessibilityNodeInfosResult(nodesToReturn, mInteractionId); + } catch (RemoteException re) { + if (DEBUG) { + Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult"); } } - return (infos == null) - ? null : new ArrayList<>(infos); } @GuardedBy("mLock") @@ -257,22 +204,40 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection info.setDismissable(false); // We currently only replace actions for the root node if ((info.getSourceNodeId() == AccessibilityNodeInfo.ROOT_NODE_ID) - && mNodeWithReplacementActions != null) { - List<AccessibilityAction> actions = mNodeWithReplacementActions.getActionList(); - if (actions != null) { - for (int j = 0; j < actions.size(); j++) { - info.addAction(actions.get(j)); + && mNodesWithReplacementActions != null) { + // This list should always contain a single node with the root ID + for (int i = 0; i < mNodesWithReplacementActions.size(); i++) { + AccessibilityNodeInfo nodeWithReplacementActions = + mNodesWithReplacementActions.get(i); + if (nodeWithReplacementActions.getSourceNodeId() + == AccessibilityNodeInfo.ROOT_NODE_ID) { + List<AccessibilityAction> actions = nodeWithReplacementActions.getActionList(); + if (actions != null) { + for (int j = 0; j < actions.size(); j++) { + info.addAction(actions.get(j)); + } + // The PIP needs to be able to take accessibility focus + info.addAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS); + info.addAction(AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS); + } + info.setClickable(nodeWithReplacementActions.isClickable()); + info.setFocusable(nodeWithReplacementActions.isFocusable()); + info.setContextClickable(nodeWithReplacementActions.isContextClickable()); + info.setScrollable(nodeWithReplacementActions.isScrollable()); + info.setLongClickable(nodeWithReplacementActions.isLongClickable()); + info.setDismissable(nodeWithReplacementActions.isDismissable()); } - // The PIP needs to be able to take accessibility focus - info.addAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS); - info.addAction(AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS); } - info.setClickable(mNodeWithReplacementActions.isClickable()); - info.setFocusable(mNodeWithReplacementActions.isFocusable()); - info.setContextClickable(mNodeWithReplacementActions.isContextClickable()); - info.setScrollable(mNodeWithReplacementActions.isScrollable()); - info.setLongClickable(mNodeWithReplacementActions.isLongClickable()); - info.setDismissable(mNodeWithReplacementActions.isDismissable()); } } + + @GuardedBy("mLock") + private void recycleReplaceActionNodesLocked() { + if (mNodesWithReplacementActions == null) return; + for (int i = mNodesWithReplacementActions.size() - 1; i >= 0; i--) { + AccessibilityNodeInfo nodeWithReplacementAction = mNodesWithReplacementActions.get(i); + nodeWithReplacementAction.recycle(); + } + mNodesWithReplacementActions = null; + } } diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index 4473754e2b68..9547280018e4 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -53,6 +53,8 @@ class UiAutomationManager { private AbstractAccessibilityServiceConnection.SystemSupport mSystemSupport; + private AccessibilityTrace mTrace; + private int mUiAutomationFlags; UiAutomationManager(Object lock) { @@ -89,6 +91,7 @@ class UiAutomationManager { int id, Handler mainHandler, AccessibilitySecurityPolicy securityPolicy, AbstractAccessibilityServiceConnection.SystemSupport systemSupport, + AccessibilityTrace trace, WindowManagerInternal windowManagerInternal, SystemActionPerformer systemActionPerformer, AccessibilityWindowManager awm, int flags) { @@ -111,13 +114,14 @@ class UiAutomationManager { mUiAutomationFlags = flags; mSystemSupport = systemSupport; + mTrace = trace; // Ignore registering UiAutomation if it is not allowed to use the accessibility // subsystem. if (!useAccessibility()) { return; } mUiAutomationService = new UiAutomationService(context, accessibilityServiceInfo, id, - mainHandler, mLock, securityPolicy, systemSupport, windowManagerInternal, + mainHandler, mLock, securityPolicy, systemSupport, trace, windowManagerInternal, systemActionPerformer, awm); mUiAutomationServiceOwner = owner; mUiAutomationServiceInfo = accessibilityServiceInfo; @@ -239,11 +243,12 @@ class UiAutomationManager { UiAutomationService(Context context, AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler, Object lock, AccessibilitySecurityPolicy securityPolicy, - SystemSupport systemSupport, WindowManagerInternal windowManagerInternal, + SystemSupport systemSupport, AccessibilityTrace trace, + WindowManagerInternal windowManagerInternal, SystemActionPerformer systemActionPerformer, AccessibilityWindowManager awm) { super(context, COMPONENT_NAME, accessibilityServiceInfo, id, mainHandler, lock, - securityPolicy, systemSupport, windowManagerInternal, systemActionPerformer, - awm); + securityPolicy, systemSupport, trace, windowManagerInternal, + systemActionPerformer, awm); mMainHandler = mainHandler; } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 21cae453d702..a3a0cb402c76 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -1330,7 +1330,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind mPermissionControllerManager.getPrivilegesDescriptionStringForProfile( deviceProfile, FgThread.getExecutor(), desc -> { try { - result.complete(desc); + result.complete(String.valueOf(desc)); } catch (Exception e) { result.completeExceptionally(e); } diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 02930dc238ba..f4a8ccd184e5 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -69,6 +69,7 @@ import android.provider.Settings; import android.service.contentcapture.ActivityEvent.ActivityEventType; import android.service.contentcapture.IDataShareCallback; import android.service.contentcapture.IDataShareReadAdapter; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.LocalLog; import android.util.Pair; @@ -81,6 +82,7 @@ import android.view.contentcapture.ContentCaptureManager; import android.view.contentcapture.DataRemovalRequest; import android.view.contentcapture.DataShareRequest; import android.view.contentcapture.IContentCaptureManager; +import android.view.contentcapture.IContentCaptureOptionsCallback; import android.view.contentcapture.IDataShareWriteAdapter; import com.android.internal.annotations.GuardedBy; @@ -134,6 +136,9 @@ public final class ContentCaptureManagerService extends private final LocalService mLocalService = new LocalService(); + private final ContentCaptureManagerServiceStub mContentCaptureManagerServiceStub = + new ContentCaptureManagerServiceStub(); + @Nullable final LocalLog mRequestsHistory; @@ -224,8 +229,7 @@ public final class ContentCaptureManagerService extends @Override // from SystemService public void onStart() { - publishBinderService(CONTENT_CAPTURE_MANAGER_SERVICE, - new ContentCaptureManagerServiceStub()); + publishBinderService(CONTENT_CAPTURE_MANAGER_SERVICE, mContentCaptureManagerServiceStub); publishLocalService(ContentCaptureManagerInternal.class, mLocalService); } @@ -492,6 +496,19 @@ public final class ContentCaptureManagerService extends } } + void updateOptions(String packageName, ContentCaptureOptions options) { + ArraySet<CallbackRecord> records; + synchronized (mLock) { + records = mContentCaptureManagerServiceStub.mCallbacks.get(packageName); + if (records != null) { + int N = records.size(); + for (int i = 0; i < N; i++) { + records.valueAt(i).setContentCaptureOptions(options); + } + } + } + } + private ActivityManagerInternal getAmInternal() { synchronized (mLock) { if (mAm == null) { @@ -599,6 +616,8 @@ public final class ContentCaptureManagerService extends } final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub { + @GuardedBy("mLock") + private final ArrayMap<String, ArraySet<CallbackRecord>> mCallbacks = new ArrayMap<>(); @Override public void startSession(@NonNull IBinder activityToken, @@ -755,6 +774,46 @@ public final class ContentCaptureManagerService extends } @Override + public void registerContentCaptureOptionsCallback(@NonNull String packageName, + IContentCaptureOptionsCallback callback) { + assertCalledByPackageOwner(packageName); + + CallbackRecord record = new CallbackRecord(callback, packageName); + record.registerObserver(); + + synchronized (mLock) { + ArraySet<CallbackRecord> records = mCallbacks.get(packageName); + if (records == null) { + records = new ArraySet<>(); + } + records.add(record); + mCallbacks.put(packageName, records); + } + + // Set options here in case it was updated before this was registered. + final int userId = UserHandle.getCallingUserId(); + final ContentCaptureOptions options = mGlobalContentCaptureOptions.getOptions(userId, + packageName); + if (options != null) { + record.setContentCaptureOptions(options); + } + } + + private void unregisterContentCaptureOptionsCallback(CallbackRecord record) { + synchronized (mLock) { + ArraySet<CallbackRecord> records = mCallbacks.get(record.mPackageName); + if (records != null) { + records.remove(record); + } + + if (records == null || records.isEmpty()) { + mCallbacks.remove(record.mPackageName); + } + } + record.unregisterObserver(); + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return; @@ -1218,4 +1277,39 @@ public final class ContentCaptureManagerService extends mDataShareRequest.getPackageName()); } } + + private final class CallbackRecord implements IBinder.DeathRecipient { + private final String mPackageName; + private final IContentCaptureOptionsCallback mCallback; + + private CallbackRecord(IContentCaptureOptionsCallback callback, String packageName) { + mCallback = callback; + mPackageName = packageName; + } + + private void setContentCaptureOptions(ContentCaptureOptions options) { + try { + mCallback.setContentCaptureOptions(options); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to send setContentCaptureOptions(): " + e); + } + } + + private void registerObserver() { + try { + mCallback.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to register callback cleanup " + e); + } + } + + private void unregisterObserver() { + mCallback.asBinder().unlinkToDeath(this, 0); + } + + @Override + public void binderDied() { + mContentCaptureManagerServiceStub.unregisterContentCaptureOptionsCallback(this); + } + } } diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index 53cdc330cf9e..225a8d48114b 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -597,9 +597,15 @@ final class ContentCapturePerUserService ? "null_activities" : activities.size() + " activities") + ")" + " for user " + mUserId); } + + ArraySet<String> oldList = + mMaster.mGlobalContentCaptureOptions.getWhitelistedPackages(mUserId); + mMaster.mGlobalContentCaptureOptions.setWhitelist(mUserId, packages, activities); writeSetWhitelistEvent(getServiceComponentName(), packages, activities); + updateContentCaptureOptions(oldList); + // Must disable session that are not the allowlist anymore... final int numSessions = mSessions.size(); if (numSessions <= 0) return; @@ -671,5 +677,23 @@ final class ContentCapturePerUserService ContentCaptureMetricsLogger.writeSessionFlush(sessionId, getServiceComponentName(), app, flushMetrics, options, flushReason); } + + /** Updates {@link ContentCaptureOptions} for all newly added packages on allowlist. */ + private void updateContentCaptureOptions(@Nullable ArraySet<String> oldList) { + ArraySet<String> adding = mMaster.mGlobalContentCaptureOptions + .getWhitelistedPackages(mUserId); + + if (oldList != null && adding != null) { + adding.removeAll(oldList); + } + + int N = adding != null ? adding.size() : 0; + for (int i = 0; i < N; i++) { + String packageName = adding.valueAt(i); + ContentCaptureOptions options = mMaster.mGlobalContentCaptureOptions + .getOptions(mUserId, packageName); + mMaster.updateOptions(packageName, options); + } + } } } diff --git a/services/core/Android.bp b/services/core/Android.bp index b67bdc20f7fa..8ccfad6fe061 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -97,6 +97,7 @@ java_library_static { ":platform-compat-config", ":platform-compat-overrides", ":display-device-config", + ":display-layout-config", ":cec-config", ":device-state-config", "java/com/android/server/EventLogTags.logtags", @@ -222,7 +223,6 @@ filegroup { "java/com/android/server/TestNetworkService.java", "java/com/android/server/connectivity/AutodestructReference.java", "java/com/android/server/connectivity/ConnectivityConstants.java", - "java/com/android/server/connectivity/DataConnectionStats.java", "java/com/android/server/connectivity/DnsManager.java", "java/com/android/server/connectivity/KeepaliveTracker.java", "java/com/android/server/connectivity/LingerMonitor.java", diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 9e0ea9cdf79d..256e963fdb91 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -70,6 +70,7 @@ import android.annotation.Nullable; import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.app.PendingIntent; +import android.app.usage.NetworkStatsManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -95,7 +96,6 @@ import android.net.INetworkActivityListener; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; -import android.net.INetworkStatsService; import android.net.IOnSetOemNetworkPreferenceListener; import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; @@ -120,6 +120,7 @@ import android.net.NetworkSpecifier; import android.net.NetworkStack; import android.net.NetworkStackClient; import android.net.NetworkState; +import android.net.NetworkStateSnapshot; import android.net.NetworkTestResultParcelable; import android.net.NetworkUtils; import android.net.NetworkWatchlistManager; @@ -141,10 +142,13 @@ import android.net.UnderlyingNetworkInfo; import android.net.Uri; import android.net.VpnManager; import android.net.VpnTransportInfo; -import android.net.metrics.INetdEventListener; import android.net.metrics.IpConnectivityLog; import android.net.metrics.NetworkEvent; import android.net.netlink.InetDiagMessage; +import android.net.resolv.aidl.DnsHealthEventParcel; +import android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener; +import android.net.resolv.aidl.Nat64PrefixEventParcel; +import android.net.resolv.aidl.PrivateDnsValidationEventParcel; import android.net.shared.PrivateDnsConfig; import android.net.util.MultinetworkPolicyTracker; import android.net.util.NetdService; @@ -186,7 +190,6 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; -import com.android.internal.logging.MetricsLogger; import com.android.internal.util.AsyncChannel; import com.android.internal.util.BitUtils; import com.android.internal.util.IndentingPrintWriter; @@ -200,7 +203,6 @@ import com.android.net.module.util.LinkPropertiesUtils.CompareResult; import com.android.net.module.util.PermissionUtils; import com.android.server.am.BatteryStatsService; import com.android.server.connectivity.AutodestructReference; -import com.android.server.connectivity.DataConnectionStats; import com.android.server.connectivity.DnsManager; import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate; import com.android.server.connectivity.KeepaliveTracker; @@ -327,7 +329,7 @@ public class ConnectivityService extends IConnectivityManager.Stub protected IDnsResolver mDnsResolver; @VisibleForTesting protected INetd mNetd; - private INetworkStatsService mStatsService; + private NetworkStatsManager mStatsManager; private NetworkPolicyManager mPolicyManager; private NetworkPolicyManagerInternal mPolicyManagerInternal; private final NetdCallback mNetdCallback; @@ -1038,15 +1040,14 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - public ConnectivityService(Context context, INetworkStatsService statsService) { - this(context, statsService, getDnsResolver(context), new IpConnectivityLog(), + public ConnectivityService(Context context) { + this(context, getDnsResolver(context), new IpConnectivityLog(), NetdService.getInstance(), new Dependencies()); } @VisibleForTesting - protected ConnectivityService(Context context, INetworkStatsService statsService, - IDnsResolver dnsresolver, IpConnectivityLog logger, - INetd netd, Dependencies deps) { + protected ConnectivityService(Context context, IDnsResolver dnsresolver, + IpConnectivityLog logger, INetd netd, Dependencies deps) { if (DBG) log("ConnectivityService starting up"); mDeps = Objects.requireNonNull(deps, "missing Dependencies"); @@ -1092,7 +1093,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // TODO: Consider making the timer customizable. mNascentDelayMs = DEFAULT_NASCENT_DELAY_MS; - mStatsService = Objects.requireNonNull(statsService, "missing INetworkStatsService"); + mStatsManager = mContext.getSystemService(NetworkStatsManager.class); mPolicyManager = mContext.getSystemService(NetworkPolicyManager.class); mPolicyManagerInternal = Objects.requireNonNull( LocalServices.getService(NetworkPolicyManagerInternal.class), @@ -1211,9 +1212,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mSettingsObserver = new SettingsObserver(mContext, mHandler); registerSettingsCallbacks(); - final DataConnectionStats dataConnectionStats = new DataConnectionStats(mContext, mHandler); - dataConnectionStats.startMonitoring(); - mKeepaliveTracker = new KeepaliveTracker(mContext, mHandler); mNotifier = new NetworkNotificationManager(mContext, mTelephonyManager); mQosCallbackTracker = new QosCallbackTracker(mHandler, mNetworkRequestCounter); @@ -1399,7 +1397,7 @@ public class ConnectivityService extends IConnectivityManager.Stub return null; } - private NetworkState getUnfilteredActiveNetworkState(int uid) { + private NetworkAgentInfo getNetworkAgentInfoForUid(int uid) { NetworkAgentInfo nai = getDefaultNetworkForUid(uid); final Network[] networks = getVpnUnderlyingNetworks(uid); @@ -1415,12 +1413,7 @@ public class ConnectivityService extends IConnectivityManager.Stub nai = null; } } - - if (nai != null) { - return nai.getNetworkState(); - } else { - return NetworkState.EMPTY; - } + return nai; } /** @@ -1473,24 +1466,31 @@ public class ConnectivityService extends IConnectivityManager.Stub "%s %d(%d) on netId %d", action, nri.mUid, requestId, net.getNetId())); } - private void filterNetworkInfo(@NonNull NetworkInfo networkInfo, - @NonNull NetworkCapabilities nc, int uid, boolean ignoreBlocked) { - if (isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked)) { - networkInfo.setDetailedState(DetailedState.BLOCKED, null, null); - } - networkInfo.setDetailedState( - getLegacyLockdownState(networkInfo.getDetailedState()), - "" /* reason */, null /* extraInfo */); - } - /** - * Apply any relevant filters to {@link NetworkState} for the given UID. For + * Apply any relevant filters to the specified {@link NetworkInfo} for the given UID. For * example, this may mark the network as {@link DetailedState#BLOCKED} based * on {@link #isNetworkWithCapabilitiesBlocked}. */ - private void filterNetworkStateForUid(NetworkState state, int uid, boolean ignoreBlocked) { - if (state == null || state.networkInfo == null || state.linkProperties == null) return; - filterNetworkInfo(state.networkInfo, state.networkCapabilities, uid, ignoreBlocked); + @NonNull + private NetworkInfo filterNetworkInfo(@NonNull NetworkInfo networkInfo, int type, + @NonNull NetworkCapabilities nc, int uid, boolean ignoreBlocked) { + final NetworkInfo filtered = new NetworkInfo(networkInfo); + // Many legacy types (e.g,. TYPE_MOBILE_HIPRI) are not actually a property of the network + // but only exists if an app asks about them or requests them. Ensure the requesting app + // gets the type it asks for. + filtered.setType(type); + final DetailedState state = isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked) + ? DetailedState.BLOCKED + : filtered.getDetailedState(); + filtered.setDetailedState(getLegacyLockdownState(state), + "" /* reason */, null /* extraInfo */); + return filtered; + } + + private NetworkInfo getFilteredNetworkInfo(NetworkAgentInfo nai, int uid, + boolean ignoreBlocked) { + return filterNetworkInfo(nai.networkInfo, nai.networkInfo.getType(), + nai.networkCapabilities, uid, ignoreBlocked); } /** @@ -1504,10 +1504,11 @@ public class ConnectivityService extends IConnectivityManager.Stub public NetworkInfo getActiveNetworkInfo() { enforceAccessPermission(); final int uid = mDeps.getCallingUid(); - final NetworkState state = getUnfilteredActiveNetworkState(uid); - filterNetworkStateForUid(state, uid, false); - maybeLogBlockedNetworkInfo(state.networkInfo, uid); - return state.networkInfo; + final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid); + if (nai == null) return null; + final NetworkInfo networkInfo = getFilteredNetworkInfo(nai, uid, false); + maybeLogBlockedNetworkInfo(networkInfo, uid); + return networkInfo; } @Override @@ -1542,30 +1543,37 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) { PermissionUtils.enforceNetworkStackPermission(mContext); - final NetworkState state = getUnfilteredActiveNetworkState(uid); - filterNetworkStateForUid(state, uid, ignoreBlocked); - return state.networkInfo; + final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid); + if (nai == null) return null; + return getFilteredNetworkInfo(nai, uid, ignoreBlocked); + } + + /** Returns a NetworkInfo object for a network that doesn't exist. */ + private NetworkInfo makeFakeNetworkInfo(int networkType, int uid) { + final NetworkInfo info = new NetworkInfo(networkType, 0 /* subtype */, + getNetworkTypeName(networkType), "" /* subtypeName */); + info.setIsAvailable(true); + // For compatibility with legacy code, return BLOCKED instead of DISCONNECTED when + // background data is restricted. + final NetworkCapabilities nc = new NetworkCapabilities(); // Metered. + final DetailedState state = isNetworkWithCapabilitiesBlocked(nc, uid, false) + ? DetailedState.BLOCKED + : DetailedState.DISCONNECTED; + info.setDetailedState(getLegacyLockdownState(state), + "" /* reason */, null /* extraInfo */); + return info; } - private NetworkInfo getFilteredNetworkInfo(int networkType, int uid) { + private NetworkInfo getFilteredNetworkInfoForType(int networkType, int uid) { if (!mLegacyTypeTracker.isTypeSupported(networkType)) { return null; } final NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType); - final NetworkInfo info; - final NetworkCapabilities nc; - if (nai != null) { - info = new NetworkInfo(nai.networkInfo); - info.setType(networkType); - nc = nai.networkCapabilities; - } else { - info = new NetworkInfo(networkType, 0, getNetworkTypeName(networkType), ""); - info.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null); - info.setIsAvailable(true); - nc = new NetworkCapabilities(); + if (nai == null) { + return makeFakeNetworkInfo(networkType, uid); } - filterNetworkInfo(info, nc, uid, false); - return info; + return filterNetworkInfo(nai.networkInfo, networkType, nai.networkCapabilities, uid, + false); } @Override @@ -1575,27 +1583,23 @@ public class ConnectivityService extends IConnectivityManager.Stub if (getVpnUnderlyingNetworks(uid) != null) { // A VPN is active, so we may need to return one of its underlying networks. This // information is not available in LegacyTypeTracker, so we have to get it from - // getUnfilteredActiveNetworkState. - final NetworkState state = getUnfilteredActiveNetworkState(uid); - if (state.networkInfo != null && state.networkInfo.getType() == networkType) { - filterNetworkStateForUid(state, uid, false); - return state.networkInfo; + // getNetworkAgentInfoForUid. + final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid); + if (nai == null) return null; + final NetworkInfo networkInfo = getFilteredNetworkInfo(nai, uid, false); + if (networkInfo.getType() == networkType) { + return networkInfo; } } - return getFilteredNetworkInfo(networkType, uid); + return getFilteredNetworkInfoForType(networkType, uid); } @Override public NetworkInfo getNetworkInfoForUid(Network network, int uid, boolean ignoreBlocked) { enforceAccessPermission(); final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); - if (nai != null) { - final NetworkState state = nai.getNetworkState(); - filterNetworkStateForUid(state, uid, ignoreBlocked); - return state.networkInfo; - } else { - return null; - } + if (nai == null) return null; + return getFilteredNetworkInfo(nai, uid, ignoreBlocked); } @Override @@ -1623,10 +1627,10 @@ public class ConnectivityService extends IConnectivityManager.Stub return null; } final int uid = mDeps.getCallingUid(); - if (!isNetworkWithCapabilitiesBlocked(nai.networkCapabilities, uid, false)) { - return nai.network; + if (isNetworkWithCapabilitiesBlocked(nai.networkCapabilities, uid, false)) { + return null; } - return null; + return nai.network; } @Override @@ -1715,9 +1719,9 @@ public class ConnectivityService extends IConnectivityManager.Stub public LinkProperties getActiveLinkProperties() { enforceAccessPermission(); final int uid = mDeps.getCallingUid(); - NetworkState state = getUnfilteredActiveNetworkState(uid); - if (state.linkProperties == null) return null; - return linkPropertiesRestrictedForCallerPermissions(state.linkProperties, + NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid); + if (nai == null) return null; + return linkPropertiesRestrictedForCallerPermissions(nai.linkProperties, Binder.getCallingPid(), uid); } @@ -2036,25 +2040,24 @@ public class ConnectivityService extends IConnectivityManager.Stub return true; } - private class NetdEventCallback extends INetdEventListener.Stub { + class DnsResolverUnsolicitedEventCallback extends + IDnsResolverUnsolicitedEventListener.Stub { @Override - public void onPrivateDnsValidationEvent(int netId, String ipAddress, - String hostname, boolean validated) { + public void onPrivateDnsValidationEvent(final PrivateDnsValidationEventParcel event) { try { mHandler.sendMessage(mHandler.obtainMessage( EVENT_PRIVATE_DNS_VALIDATION_UPDATE, - new PrivateDnsValidationUpdate(netId, - InetAddresses.parseNumericAddress(ipAddress), - hostname, validated))); + new PrivateDnsValidationUpdate(event.netId, + InetAddresses.parseNumericAddress(event.ipAddress), + event.hostname, event.validation))); } catch (IllegalArgumentException e) { loge("Error parsing ip address in validation event"); } } @Override - public void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs, - String hostname, String[] ipAddresses, int ipAddressesCount, int uid) { - NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId); + public void onDnsHealthEvent(final DnsHealthEventParcel event) { + NetworkAgentInfo nai = getNetworkAgentInfoForNetId(event.netId); // Netd event only allow registrants from system. Each NetworkMonitor thread is under // the caller thread of registerNetworkAgent. Thus, it's not allowed to register netd // event callback for certain nai. e.g. cellular. Register here to pass to @@ -2063,34 +2066,18 @@ public class ConnectivityService extends IConnectivityManager.Stub // callback from each caller type. Need to re-factor NetdEventListenerService to allow // multiple NetworkMonitor registrants. if (nai != null && nai.satisfies(mDefaultRequest.mRequests.get(0))) { - nai.networkMonitor().notifyDnsResponse(returnCode); + nai.networkMonitor().notifyDnsResponse(event.healthResult); } } @Override - public void onNat64PrefixEvent(int netId, boolean added, - String prefixString, int prefixLength) { - mHandler.post(() -> handleNat64PrefixEvent(netId, added, prefixString, prefixLength)); - } - - @Override - public void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, - int uid) { + public void onNat64PrefixEvent(final Nat64PrefixEventParcel event) { + mHandler.post(() -> handleNat64PrefixEvent(event.netId, event.prefixOperation, + event.prefixAddress, event.prefixLength)); } @Override - public void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, - byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, - long timestampNs) { - } - - @Override - public void onTcpSocketStatsEvent(int[] networkIds, int[] sentPackets, int[] lostPackets, - int[] rttsUs, int[] sentAckDiffsMs) { - } - - @Override - public int getInterfaceVersion() throws RemoteException { + public int getInterfaceVersion() { return this.VERSION; } @@ -2098,16 +2085,17 @@ public class ConnectivityService extends IConnectivityManager.Stub public String getInterfaceHash() { return this.HASH; } - }; + } @VisibleForTesting - protected final INetdEventListener mNetdEventCallback = new NetdEventCallback(); + protected final DnsResolverUnsolicitedEventCallback mResolverUnsolEventCallback = + new DnsResolverUnsolicitedEventCallback(); - private void registerNetdEventCallback() { + private void registerDnsResolverUnsolicitedEventListener() { try { - mDnsResolver.registerEventListener(mNetdEventCallback); + mDnsResolver.registerUnsolicitedEventListener(mResolverUnsolEventCallback); } catch (Exception e) { - loge("Error registering DnsResolver callback: " + e); + loge("Error registering DnsResolver unsolicited event callback: " + e); } } @@ -2396,13 +2384,6 @@ public class ConnectivityService extends IConnectivityManager.Stub final BroadcastOptions opts = BroadcastOptions.makeBasic(); opts.setMaxManifestReceiverApiLevel(Build.VERSION_CODES.M); options = opts.toBundle(); - final IBatteryStats bs = mDeps.getBatteryStatsService(); - try { - bs.noteConnectivityChanged(intent.getIntExtra( - ConnectivityManager.EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_NONE), - ni.getState().toString()); - } catch (RemoteException e) { - } intent.addFlags(Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); } try { @@ -2438,7 +2419,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // to ensure the tracking will be initialized correctly. mPermissionMonitor.startMonitoring(); mProxyTracker.loadGlobalProxy(); - registerNetdEventCallback(); + registerDnsResolverUnsolicitedEventListener(); synchronized (this) { mSystemReady = true; @@ -3079,9 +3060,6 @@ public class ConnectivityService extends IConnectivityManager.Stub log(nai.toShortString() + " validation " + (valid ? "passed" : "failed") + logMsg); } if (valid != nai.lastValidated) { - if (wasDefault) { - mMetricsLog.logDefaultNetworkValidity(valid); - } final int oldScore = nai.getCurrentScore(); nai.lastValidated = valid; nai.everValidated |= valid; @@ -3205,16 +3183,16 @@ public class ConnectivityService extends IConnectivityManager.Stub // Invoke ConnectivityReport generation for this Network test event. final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(mNetId); if (nai == null) return; - final Message m = mConnectivityDiagnosticsHandler.obtainMessage( - ConnectivityDiagnosticsHandler.EVENT_NETWORK_TESTED, - new ConnectivityReportEvent(p.timestampMillis, nai)); final PersistableBundle extras = new PersistableBundle(); extras.putInt(KEY_NETWORK_VALIDATION_RESULT, p.result); extras.putInt(KEY_NETWORK_PROBES_SUCCEEDED_BITMASK, p.probesSucceeded); extras.putInt(KEY_NETWORK_PROBES_ATTEMPTED_BITMASK, p.probesAttempted); - m.setData(new Bundle(extras)); + ConnectivityReportEvent reportEvent = + new ConnectivityReportEvent(p.timestampMillis, nai, extras); + final Message m = mConnectivityDiagnosticsHandler.obtainMessage( + ConnectivityDiagnosticsHandler.EVENT_NETWORK_TESTED, reportEvent); mConnectivityDiagnosticsHandler.sendMessage(m); } @@ -3301,8 +3279,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final Message msg = mConnectivityDiagnosticsHandler.obtainMessage( ConnectivityDiagnosticsHandler.EVENT_DATA_STALL_SUSPECTED, detectionMethod, netId, - p.timestampMillis); - msg.setData(new Bundle(extras)); + new Pair<>(p.timestampMillis, extras)); // NetworkStateTrackerHandler currently doesn't take any actions based on data // stalls so send the message directly to ConnectivityDiagnosticsHandler and avoid @@ -3369,21 +3346,21 @@ public class ConnectivityService extends IConnectivityManager.Stub handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties)); } - private void handleNat64PrefixEvent(int netId, boolean added, String prefixString, + private void handleNat64PrefixEvent(int netId, int operation, String prefixAddress, int prefixLength) { NetworkAgentInfo nai = mNetworkForNetId.get(netId); if (nai == null) return; - log(String.format("NAT64 prefix %s on netId %d: %s/%d", - (added ? "added" : "removed"), netId, prefixString, prefixLength)); + log(String.format("NAT64 prefix changed on netId %d: operation=%d, %s/%d", + netId, operation, prefixAddress, prefixLength)); IpPrefix prefix = null; - if (added) { + if (operation == IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_ADDED) { try { - prefix = new IpPrefix(InetAddresses.parseNumericAddress(prefixString), + prefix = new IpPrefix(InetAddresses.parseNumericAddress(prefixAddress), prefixLength); } catch (IllegalArgumentException e) { - loge("Invalid NAT64 prefix " + prefixString + "/" + prefixLength); + loge("Invalid NAT64 prefix " + prefixAddress + "/" + prefixLength); return; } } @@ -3502,14 +3479,6 @@ public class ConnectivityService extends IConnectivityManager.Stub final boolean wasDefault = isDefaultNetwork(nai); if (wasDefault) { mDefaultInetConditionPublished = 0; - // Log default network disconnection before required book-keeping. - // Let rematchAllNetworksAndRequests() below record a new default network event - // if there is a fallback. Taken together, the two form a X -> 0, 0 -> Y sequence - // whose timestamps tell how long it takes to recover a default network. - long now = SystemClock.elapsedRealtime(); - mMetricsLog.logDefaultNetworkEvent(null, 0, false, - null /* lp */, null /* nc */, nai.network, nai.getCurrentScore(), - nai.linkProperties, nai.networkCapabilities); } notifyIfacesChangedForNetworkStats(); // TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied @@ -3851,7 +3820,24 @@ public class ConnectivityService extends IConnectivityManager.Stub removeListenRequestFromNetworks(req); } } - mDefaultNetworkRequests.remove(nri); + if (mDefaultNetworkRequests.remove(nri)) { + // If this request was one of the defaults, then the UID rules need to be updated + // WARNING : if the app(s) for which this network request is the default are doing + // traffic, this will kill their connected sockets, even if an equivalent request + // is going to be reinstated right away ; unconnected traffic will go on the default + // until the new default is set, which will happen very soon. + // TODO : The only way out of this is to diff old defaults and new defaults, and only + // remove ranges for those requests that won't have a replacement + final NetworkAgentInfo satisfier = nri.getSatisfier(); + if (null != satisfier) { + try { + mNetd.networkRemoveUidRanges(satisfier.network.getNetId(), + toUidRangeStableParcels(nri.getUids())); + } catch (RemoteException e) { + loge("Exception setting network preference default network", e); + } + } + } mNetworkRequestCounter.decrementCount(nri.mUid); mNetworkRequestInfoLogs.log("RELEASE " + nri); @@ -4164,13 +4150,6 @@ public class ConnectivityService extends IConnectivityManager.Stub // nai.networkMonitor() is thread-safe return nai.networkMonitor(); } - - @Override - public void logEvent(int eventId, String packageName) { - enforceSettingsPermission(); - - new MetricsLogger().action(eventId, packageName); - } } public boolean avoidBadWifi() { @@ -4500,16 +4479,13 @@ public class ConnectivityService extends IConnectivityManager.Stub case EVENT_SET_REQUIRE_VPN_FOR_UIDS: handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj); break; - case EVENT_SET_OEM_NETWORK_PREFERENCE: + case EVENT_SET_OEM_NETWORK_PREFERENCE: { final Pair<OemNetworkPreferences, IOnSetOemNetworkPreferenceListener> arg = (Pair<OemNetworkPreferences, IOnSetOemNetworkPreferenceListener>) msg.obj; - try { - handleSetOemNetworkPreference(arg.first, arg.second); - } catch (RemoteException e) { - loge("handleMessage.EVENT_SET_OEM_NETWORK_PREFERENCE failed", e); - } + handleSetOemNetworkPreference(arg.first, arg.second); break; + } case EVENT_REPORT_NETWORK_ACTIVITY: mNetworkActivityTracker.handleReportNetworkActivity(); break; @@ -5267,11 +5243,20 @@ public class ConnectivityService extends IConnectivityManager.Stub ensureAllNetworkRequestsHaveType(r); mRequests = initializeRequests(r); mNetworkRequestForCallback = nri.getNetworkRequestForCallback(); + // Note here that the satisfier may have corresponded to an old request, that + // this code doesn't try to take over. While it is a small discrepancy in the + // structure of these requests, it will be fixed by the next rematch and it's + // not as bad as having an NRI not storing its real satisfier. + // Fixing this discrepancy would require figuring out in the copying code what + // is the new request satisfied by this, which is a bit complex and not very + // useful as no code is using it until rematch fixes it. + mSatisfier = nri.mSatisfier; mMessenger = nri.mMessenger; mBinder = nri.mBinder; mPid = nri.mPid; mUid = nri.mUid; mPendingIntent = nri.mPendingIntent; + mNetworkRequestCounter.incrementCountOrThrow(mUid); mCallingAttributionTag = nri.mCallingAttributionTag; } @@ -5318,6 +5303,8 @@ public class ConnectivityService extends IConnectivityManager.Stub public String toString() { return "uid/pid:" + mUid + "/" + mPid + " active request Id: " + (mActiveRequest == null ? null : mActiveRequest.requestId) + + " callback request Id: " + + mNetworkRequestForCallback.requestId + " " + mRequests + (mPendingIntent == null ? "" : " to trigger " + mPendingIntent); } @@ -7145,27 +7132,6 @@ public class ConnectivityService extends IConnectivityManager.Stub updateTcpBufferSizes(null != newDefaultNetwork ? newDefaultNetwork.linkProperties.getTcpBufferSizes() : null); notifyIfacesChangedForNetworkStats(); - - // Log 0 -> X and Y -> X default network transitions, where X is the new default. - final Network network = (newDefaultNetwork != null) ? newDefaultNetwork.network : null; - final int score = (newDefaultNetwork != null) ? newDefaultNetwork.getCurrentScore() : 0; - final boolean validated = newDefaultNetwork != null && newDefaultNetwork.lastValidated; - final LinkProperties lp = (newDefaultNetwork != null) - ? newDefaultNetwork.linkProperties : null; - final NetworkCapabilities nc = (newDefaultNetwork != null) - ? newDefaultNetwork.networkCapabilities : null; - - final Network prevNetwork = (oldDefaultNetwork != null) - ? oldDefaultNetwork.network : null; - final int prevScore = (oldDefaultNetwork != null) - ? oldDefaultNetwork.getCurrentScore() : 0; - final LinkProperties prevLp = (oldDefaultNetwork != null) - ? oldDefaultNetwork.linkProperties : null; - final NetworkCapabilities prevNc = (oldDefaultNetwork != null) - ? oldDefaultNetwork.networkCapabilities : null; - - mMetricsLog.logDefaultNetworkEvent(network, score, validated, lp, nc, - prevNetwork, prevScore, prevLp, prevNc); } private void makeDefaultForApps(@NonNull final NetworkRequestInfo nri, @@ -7195,7 +7161,7 @@ public class ConnectivityService extends IConnectivityManager.Stub toUidRangeStableParcels(nri.getUids())); } } catch (RemoteException | ServiceSpecificException e) { - loge("Exception setting OEM network preference default network :" + e); + loge("Exception setting OEM network preference default network", e); } } @@ -7250,13 +7216,13 @@ public class ConnectivityService extends IConnectivityManager.Stub private static class NetworkReassignment { static class RequestReassignment { @NonNull public final NetworkRequestInfo mNetworkRequestInfo; - @NonNull public final NetworkRequest mOldNetworkRequest; - @NonNull public final NetworkRequest mNewNetworkRequest; + @Nullable public final NetworkRequest mOldNetworkRequest; + @Nullable public final NetworkRequest mNewNetworkRequest; @Nullable public final NetworkAgentInfo mOldNetwork; @Nullable public final NetworkAgentInfo mNewNetwork; RequestReassignment(@NonNull final NetworkRequestInfo networkRequestInfo, - @NonNull final NetworkRequest oldNetworkRequest, - @NonNull final NetworkRequest newNetworkRequest, + @Nullable final NetworkRequest oldNetworkRequest, + @Nullable final NetworkRequest newNetworkRequest, @Nullable final NetworkAgentInfo oldNetwork, @Nullable final NetworkAgentInfo newNetwork) { mNetworkRequestInfo = networkRequestInfo; @@ -7267,7 +7233,9 @@ public class ConnectivityService extends IConnectivityManager.Stub } public String toString() { - return mNetworkRequestInfo.mRequests.get(0).requestId + " : " + final NetworkRequest requestToShow = null != mNewNetworkRequest + ? mNewNetworkRequest : mNetworkRequestInfo.mRequests.get(0); + return requestToShow.requestId + " : " + (null != mOldNetwork ? mOldNetwork.network.getNetId() : "null") + " → " + (null != mNewNetwork ? mNewNetwork.network.getNetId() : "null"); } @@ -7327,14 +7295,14 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void updateSatisfiersForRematchRequest(@NonNull final NetworkRequestInfo nri, - @NonNull final NetworkRequest previousRequest, - @NonNull final NetworkRequest newRequest, + @Nullable final NetworkRequest previousRequest, + @Nullable final NetworkRequest newRequest, @Nullable final NetworkAgentInfo previousSatisfier, @Nullable final NetworkAgentInfo newSatisfier, final long now) { if (null != newSatisfier && mNoServiceNetwork != newSatisfier) { if (VDBG) log("rematch for " + newSatisfier.toShortString()); - if (null != previousSatisfier && mNoServiceNetwork != previousSatisfier) { + if (null != previousRequest && null != previousSatisfier) { if (VDBG || DDBG) { log(" accepting network in place of " + previousSatisfier.toShortString()); } @@ -7351,12 +7319,13 @@ public class ConnectivityService extends IConnectivityManager.Stub newSatisfier.unlingerRequest(NetworkRequest.REQUEST_ID_NONE); } + // if newSatisfier is not null, then newRequest may not be null. newSatisfier.unlingerRequest(newRequest.requestId); if (!newSatisfier.addRequest(newRequest)) { Log.wtf(TAG, "BUG: " + newSatisfier.toShortString() + " already has " + newRequest); } - } else if (null != previousSatisfier) { + } else if (null != previousRequest && null != previousSatisfier) { if (DBG) { log("Network " + previousSatisfier.toShortString() + " stopped satisfying" + " request " + previousRequest.requestId); @@ -7954,7 +7923,8 @@ public class ConnectivityService extends IConnectivityManager.Stub * * Must be called on the handler thread. */ - private Network[] getDefaultNetworks() { + @NonNull + private ArrayList<Network> getDefaultNetworks() { ensureRunningOnConnectivityServiceThread(); final ArrayList<Network> defaultNetworks = new ArrayList<>(); final Set<Integer> activeNetIds = new ArraySet<>(); @@ -7968,7 +7938,7 @@ public class ConnectivityService extends IConnectivityManager.Stub defaultNetworks.add(nai.network); } } - return defaultNetworks.toArray(new Network[0]); + return defaultNetworks; } /** @@ -7985,8 +7955,16 @@ public class ConnectivityService extends IConnectivityManager.Stub final UnderlyingNetworkInfo[] underlyingNetworkInfos = getAllVpnInfo(); try { - mStatsService.forceUpdateIfaces(getDefaultNetworks(), getAllNetworkState(), activeIface, - underlyingNetworkInfos); + final ArrayList<NetworkStateSnapshot> snapshots = new ArrayList<>(); + // TODO: Directly use NetworkStateSnapshot when feasible. + for (final NetworkState state : getAllNetworkState()) { + final NetworkStateSnapshot snapshot = new NetworkStateSnapshot(state.network, + state.networkCapabilities, state.linkProperties, state.subscriberId, + state.legacyNetworkType); + snapshots.add(snapshot); + } + mStatsManager.notifyNetworkStatus(getDefaultNetworks(), + snapshots, activeIface, Arrays.asList(underlyingNetworkInfos)); } catch (Exception ignored) { } } @@ -8305,24 +8283,16 @@ public class ConnectivityService extends IConnectivityManager.Stub final ConnectivityReportEvent reportEvent = (ConnectivityReportEvent) msg.obj; - // This is safe because {@link - // NetworkMonitorCallbacks#notifyNetworkTestedWithExtras} receives a - // PersistableBundle and converts it to the Bundle in the incoming Message. If - // {@link NetworkMonitorCallbacks#notifyNetworkTested} is called, msg.data will - // not be set. This is also safe, as msg.getData() will return an empty Bundle. - final PersistableBundle extras = new PersistableBundle(msg.getData()); - handleNetworkTestedWithExtras(reportEvent, extras); + handleNetworkTestedWithExtras(reportEvent, reportEvent.mExtras); break; } case EVENT_DATA_STALL_SUSPECTED: { final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2); + final Pair<Long, PersistableBundle> arg = + (Pair<Long, PersistableBundle>) msg.obj; if (nai == null) break; - // This is safe because NetworkMonitorCallbacks#notifyDataStallSuspected - // receives a PersistableBundle and converts it to the Bundle in the incoming - // Message. - final PersistableBundle extras = new PersistableBundle(msg.getData()); - handleDataStallSuspected(nai, (long) msg.obj, msg.arg1, extras); + handleDataStallSuspected(nai, arg.first, msg.arg1, arg.second); break; } case EVENT_NETWORK_CONNECTIVITY_REPORTED: { @@ -8386,10 +8356,13 @@ public class ConnectivityService extends IConnectivityManager.Stub private static class ConnectivityReportEvent { private final long mTimestampMillis; @NonNull private final NetworkAgentInfo mNai; + private final PersistableBundle mExtras; - private ConnectivityReportEvent(long timestampMillis, @NonNull NetworkAgentInfo nai) { + private ConnectivityReportEvent(long timestampMillis, @NonNull NetworkAgentInfo nai, + PersistableBundle p) { mTimestampMillis = timestampMillis; mNai = nai; + mExtras = p; } } @@ -9066,7 +9039,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleSetOemNetworkPreference( @NonNull final OemNetworkPreferences preference, - @NonNull final IOnSetOemNetworkPreferenceListener listener) throws RemoteException { + @Nullable final IOnSetOemNetworkPreferenceListener listener) { Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); if (DBG) { log("set OEM network preferences :" + preference.toString()); @@ -9078,7 +9051,11 @@ public class ConnectivityService extends IConnectivityManager.Stub // TODO http://b/176496396 persist data to shared preferences. if (null != listener) { - listener.onComplete(); + try { + listener.onComplete(); + } catch (RemoteException e) { + loge("handleMessage.EVENT_SET_OEM_NETWORK_PREFERENCE failed", e); + } } } @@ -9094,10 +9071,10 @@ public class ConnectivityService extends IConnectivityManager.Stub mDefaultNetworkRequests.addAll(nris); final ArraySet<NetworkRequestInfo> perAppCallbackRequestsToUpdate = getPerAppCallbackRequestsToUpdate(); - handleRemoveNetworkRequests(perAppCallbackRequestsToUpdate); final ArraySet<NetworkRequestInfo> nrisToRegister = new ArraySet<>(nris); nrisToRegister.addAll( createPerAppCallbackRequestsToRegister(perAppCallbackRequestsToUpdate)); + handleRemoveNetworkRequests(perAppCallbackRequestsToUpdate); handleRegisterNetworkRequests(nrisToRegister); } diff --git a/services/core/java/com/android/server/ConnectivityServiceInitializer.java b/services/core/java/com/android/server/ConnectivityServiceInitializer.java index 097441f706e6..b9922087109f 100644 --- a/services/core/java/com/android/server/ConnectivityServiceInitializer.java +++ b/services/core/java/com/android/server/ConnectivityServiceInitializer.java @@ -20,8 +20,6 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; import android.content.Context; -import android.net.INetworkStatsService; -import android.os.ServiceManager; import android.util.Log; /** @@ -37,7 +35,7 @@ public final class ConnectivityServiceInitializer extends SystemService { // Load JNI libraries used by ConnectivityService and its dependencies System.loadLibrary("service-connectivity"); // TODO: Define formal APIs to get the needed services. - mConnectivity = new ConnectivityService(context, getNetworkStatsService()); + mConnectivity = new ConnectivityService(context); } @Override @@ -46,9 +44,4 @@ public final class ConnectivityServiceInitializer extends SystemService { publishBinderService(Context.CONNECTIVITY_SERVICE, mConnectivity, /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL); } - - private INetworkStatsService getNetworkStatsService() { - return INetworkStatsService.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); - } } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 233a50d417ad..27b648e53a38 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -24,6 +24,10 @@ import static android.app.AppOpsManager.OP_LEGACY_STORAGE; import static android.app.AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE; import static android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES; import static android.app.AppOpsManager.OP_WRITE_EXTERNAL_STORAGE; +import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; +import static android.app.PendingIntent.FLAG_IMMUTABLE; +import static android.app.PendingIntent.FLAG_ONE_SHOT; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.pm.PackageManager.MATCH_ANY_USER; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; @@ -55,6 +59,7 @@ import android.app.AnrController; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.KeyguardManager; +import android.app.PendingIntent; import android.app.admin.SecurityLog; import android.app.usage.StorageStatsManager; import android.content.BroadcastReceiver; @@ -573,6 +578,12 @@ class StorageManagerService extends IStorageManager.Stub */ private static final int PBKDF2_HASH_ROUNDS = 1024; + private static final String ANR_DELAY_MILLIS_DEVICE_CONFIG_KEY = + "anr_delay_millis"; + + private static final String ANR_DELAY_NOTIFY_EXTERNAL_STORAGE_SERVICE_DEVICE_CONFIG_KEY = + "anr_delay_notify_external_storage_service"; + /** * Mounted OBB tracking information. Used to track the current state of all * OBBs. @@ -943,25 +954,51 @@ class StorageManagerService extends IStorageManager.Stub } } - // TODO(b/170486601): Check transcoding status based on events pushed from the MediaProvider private class ExternalStorageServiceAnrController implements AnrController { @Override public long getAnrDelayMillis(String packageName, int uid) { - int delay = SystemProperties.getInt("sys.fuse.transcode_anr_delay", 0); - Log.d(TAG, "getAnrDelayMillis: " + packageName + ". Delaying for " + delay + "ms"); + if (!isAppIoBlocked(uid)) { + return 0; + } + + int delay = DeviceConfig.getInt(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, + ANR_DELAY_MILLIS_DEVICE_CONFIG_KEY, 0); + Slog.v(TAG, "getAnrDelayMillis for " + packageName + ". " + delay + "ms"); return delay; } @Override public void onAnrDelayStarted(String packageName, int uid) { - Log.d(TAG, "onAnrDelayStarted: " + packageName); + if (!isAppIoBlocked(uid)) { + return; + } + + boolean notifyExternalStorageService = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, + ANR_DELAY_NOTIFY_EXTERNAL_STORAGE_SERVICE_DEVICE_CONFIG_KEY, true); + if (notifyExternalStorageService) { + Slog.d(TAG, "onAnrDelayStarted for " + packageName + + ". Notifying external storage service"); + try { + mStorageSessionController.notifyAnrDelayStarted(packageName, uid, 0 /* tid */, + StorageManager.APP_IO_BLOCKED_REASON_TRANSCODING); + } catch (ExternalStorageServiceException e) { + Slog.e(TAG, "Failed to notify ANR delay started for " + packageName, e); + } + } else { + // TODO(b/170973510): Implement framework spinning dialog for ANR delay + } } @Override public boolean onAnrDelayCompleted(String packageName, int uid) { - boolean show = SystemProperties.getBoolean("sys.fuse.transcode_anr_dialog_show", true); - Log.d(TAG, "onAnrDelayCompleted: " + packageName + ". Show: " + show); - return show; + if (isAppIoBlocked(uid)) { + Slog.d(TAG, "onAnrDelayCompleted for " + packageName + ". Showing ANR dialog..."); + return true; + } else { + Slog.d(TAG, "onAnrDelayCompleted for " + packageName + ". Skipping ANR dialog..."); + return false; + } } } @@ -3371,6 +3408,54 @@ class StorageManagerService extends IStorageManager.Stub } } + /** + * Returns PendingIntent which can be used by Apps with MANAGE_EXTERNAL_STORAGE permission + * to launch the manageSpaceActivity of the App specified by packageName. + */ + @Override + @Nullable + public PendingIntent getManageSpaceActivityIntent( + @NonNull String packageName, int requestCode) { + // Only Apps with MANAGE_EXTERNAL_STORAGE permission should be able to call this API. + enforcePermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE); + + // We want to call the manageSpaceActivity as a SystemService and clear identity + // of the calling App + int originalUid = Binder.getCallingUidOrThrow(); + long token = Binder.clearCallingIdentity(); + + try { + ApplicationInfo appInfo = mIPackageManager.getApplicationInfo(packageName, 0, + UserHandle.getUserId(originalUid)); + if (appInfo == null) { + throw new IllegalArgumentException( + "Invalid packageName"); + } + if (appInfo.manageSpaceActivityName == null) { + Log.i(TAG, packageName + " doesn't have a manageSpaceActivity"); + return null; + } + Context targetAppContext = mContext.createPackageContext(packageName, 0); + + Intent intent = new Intent(Intent.ACTION_DEFAULT); + intent.setClassName(packageName, + appInfo.manageSpaceActivityName); + intent.setFlags(FLAG_ACTIVITY_NEW_TASK); + + PendingIntent activity = PendingIntent.getActivity(targetAppContext, requestCode, + intent, + FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE); + return activity; + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalArgumentException( + "packageName not found"); + } finally { + Binder.restoreCallingIdentity(token); + } + } + /** Not thread safe */ class AppFuseMountScope extends AppFuseBridge.MountScope { private boolean mMounted = false; @@ -4637,5 +4722,19 @@ class StorageManagerService extends IStorageManager.Stub Binder.restoreCallingIdentity(token); } } + + @Override + public List<String> getPrimaryVolumeIds() { + final List<String> primaryVolumeIds = new ArrayList<>(); + synchronized (mLock) { + for (int i = 0; i < mVolumes.size(); i++) { + final VolumeInfo vol = mVolumes.valueAt(i); + if (vol.isPrimary()) { + primaryVolumeIds.add(vol.getId()); + } + } + } + return primaryVolumeIds; + } } } diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java index 3e5c89de7fab..55408ea61566 100644 --- a/services/core/java/com/android/server/TestNetworkService.java +++ b/services/core/java/com/android/server/TestNetworkService.java @@ -32,7 +32,6 @@ import android.net.NetworkAgent; import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkProvider; -import android.net.NetworkStack; import android.net.RouteInfo; import android.net.StringNetworkSpecifier; import android.net.TestNetworkInterface; @@ -50,6 +49,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.net.module.util.NetdUtils; import com.android.net.module.util.NetworkStackConstants; +import com.android.net.module.util.PermissionUtils; import java.io.UncheckedIOException; import java.net.Inet4Address; @@ -320,7 +320,7 @@ class TestNetworkService extends ITestNetworkManager.Stub { try { final long token = Binder.clearCallingIdentity(); try { - NetworkStack.checkNetworkStackPermission(mContext); + PermissionUtils.enforceNetworkStackPermission(mContext); NetdUtils.setInterfaceUp(mNetd, iface); } finally { Binder.restoreCallingIdentity(token); diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 8d5d3d939e4b..ad2f52401e93 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -35,6 +35,7 @@ import android.net.vcn.IVcnManagementService; import android.net.vcn.IVcnStatusCallback; import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; import android.net.vcn.VcnConfig; +import android.net.vcn.VcnManager; import android.net.vcn.VcnManager.VcnErrorCode; import android.net.vcn.VcnUnderlyingNetworkPolicy; import android.net.wifi.WifiInfo; @@ -724,6 +725,26 @@ public class VcnManagementService extends IVcnManagementService.Stub { } } + private boolean isCallbackPermissioned( + @NonNull VcnStatusCallbackInfo cbInfo, @NonNull ParcelUuid subgroup) { + if (!subgroup.equals(cbInfo.mSubGroup)) { + return false; + } + + if (!mLastSnapshot.packageHasPermissionsForSubscriptionGroup(subgroup, cbInfo.mPkgName)) { + return false; + } + + if (!mLocationPermissionChecker.checkLocationPermission( + cbInfo.mPkgName, + "VcnStatusCallback" /* featureId */, + cbInfo.mUid, + null /* message */)) { + return false; + } + return true; + } + /** Registers the provided callback for receiving VCN status updates. */ @Override public void registerVcnStatusCallback( @@ -758,6 +779,27 @@ public class VcnManagementService extends IVcnManagementService.Stub { } mRegisteredStatusCallbacks.put(cbBinder, cbInfo); + + // now that callback is registered, send it the VCN's current status + final VcnConfig vcnConfig = mConfigs.get(subGroup); + final Vcn vcn = mVcns.get(subGroup); + final int vcnStatus; + if (vcnConfig == null || !isCallbackPermissioned(cbInfo, subGroup)) { + vcnStatus = VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED; + } else if (vcn == null) { + vcnStatus = VcnManager.VCN_STATUS_CODE_INACTIVE; + } else if (vcn.isActive()) { + vcnStatus = VcnManager.VCN_STATUS_CODE_ACTIVE; + } else { + // TODO(b/181789060): create Vcn.getStatus() and Log.WTF() for unknown status + vcnStatus = VcnManager.VCN_STATUS_CODE_SAFE_MODE; + } + + try { + cbInfo.mCallback.onVcnStatusChanged(vcnStatus); + } catch (RemoteException e) { + Slog.d(TAG, "VcnStatusCallback threw on VCN status change", e); + } } } finally { Binder.restoreCallingIdentity(identity); @@ -806,26 +848,6 @@ public class VcnManagementService extends IVcnManagementService.Stub { mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup"); } - private boolean isCallbackPermissioned(@NonNull VcnStatusCallbackInfo cbInfo) { - if (!mSubGroup.equals(cbInfo.mSubGroup)) { - return false; - } - - if (!mLastSnapshot.packageHasPermissionsForSubscriptionGroup( - mSubGroup, cbInfo.mPkgName)) { - return false; - } - - if (!mLocationPermissionChecker.checkLocationPermission( - cbInfo.mPkgName, - "VcnStatusCallback" /* featureId */, - cbInfo.mUid, - null /* message */)) { - return false; - } - return true; - } - @Override public void onEnteredSafeMode() { synchronized (mLock) { @@ -838,7 +860,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { // Notify all registered StatusCallbacks for this subGroup for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) { - if (isCallbackPermissioned(cbInfo)) { + if (isCallbackPermissioned(cbInfo, mSubGroup)) { Binder.withCleanCallingIdentity( () -> cbInfo.mCallback.onVcnStatusChanged( @@ -862,7 +884,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { // Notify all registered StatusCallbacks for this subGroup for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) { - if (isCallbackPermissioned(cbInfo)) { + if (isCallbackPermissioned(cbInfo, mSubGroup)) { Binder.withCleanCallingIdentity( () -> cbInfo.mCallback.onGatewayConnectionError( diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java index 5d89bf1b1d82..56aabc208027 100644 --- a/services/core/java/com/android/server/VpnManagerService.java +++ b/services/core/java/com/android/server/VpnManagerService.java @@ -47,7 +47,6 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.security.Credentials; -import android.security.KeyStore; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; @@ -60,6 +59,7 @@ import com.android.internal.net.VpnProfile; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.connectivity.Vpn; +import com.android.server.connectivity.VpnProfileStore; import com.android.server.net.LockdownVpnTracker; import java.io.FileDescriptor; @@ -83,7 +83,7 @@ public class VpnManagerService extends IVpnManager.Stub { private final Dependencies mDeps; private final ConnectivityManager mCm; - private final KeyStore mKeyStore; + private final VpnProfileStore mVpnProfileStore; private final INetworkManagementService mNMS; private final INetd mNetd; private final UserManager mUserManager; @@ -114,9 +114,9 @@ public class VpnManagerService extends IVpnManager.Stub { return new HandlerThread("VpnManagerService"); } - /** Returns the KeyStore instance to be used by this class. */ - public KeyStore getKeyStore() { - return KeyStore.getInstance(); + /** Return the VpnProfileStore to be used by this class */ + public VpnProfileStore getVpnProfileStore() { + return new VpnProfileStore(); } public INetd getNetd() { @@ -135,7 +135,7 @@ public class VpnManagerService extends IVpnManager.Stub { mHandlerThread = mDeps.makeHandlerThread(); mHandlerThread.start(); mHandler = mHandlerThread.getThreadHandler(); - mKeyStore = mDeps.getKeyStore(); + mVpnProfileStore = mDeps.getVpnProfileStore(); mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); mCm = mContext.getSystemService(ConnectivityManager.class); mNMS = mDeps.getINetworkManagementService(); @@ -289,7 +289,7 @@ public class VpnManagerService extends IVpnManager.Stub { public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) { final int user = UserHandle.getUserId(mDeps.getCallingUid()); synchronized (mVpns) { - return mVpns.get(user).provisionVpnProfile(packageName, profile, mKeyStore); + return mVpns.get(user).provisionVpnProfile(packageName, profile); } } @@ -307,7 +307,7 @@ public class VpnManagerService extends IVpnManager.Stub { public void deleteVpnProfile(@NonNull String packageName) { final int user = UserHandle.getUserId(mDeps.getCallingUid()); synchronized (mVpns) { - mVpns.get(user).deleteVpnProfile(packageName, mKeyStore); + mVpns.get(user).deleteVpnProfile(packageName); } } @@ -325,7 +325,7 @@ public class VpnManagerService extends IVpnManager.Stub { final int user = UserHandle.getUserId(mDeps.getCallingUid()); synchronized (mVpns) { throwIfLockdownEnabled(); - mVpns.get(user).startVpnProfile(packageName, mKeyStore); + mVpns.get(user).startVpnProfile(packageName); } } @@ -358,7 +358,7 @@ public class VpnManagerService extends IVpnManager.Stub { } synchronized (mVpns) { throwIfLockdownEnabled(); - mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress); + mVpns.get(user).startLegacyVpn(profile, null /* underlying */, egress); } } @@ -396,7 +396,7 @@ public class VpnManagerService extends IVpnManager.Stub { } private boolean isLockdownVpnEnabled() { - return mKeyStore.contains(Credentials.LOCKDOWN_VPN); + return mVpnProfileStore.get(Credentials.LOCKDOWN_VPN) != null; } @Override @@ -417,14 +417,14 @@ public class VpnManagerService extends IVpnManager.Stub { return true; } - byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN); + byte[] profileTag = mVpnProfileStore.get(Credentials.LOCKDOWN_VPN); if (profileTag == null) { loge("Lockdown VPN configured but cannot be read from keystore"); return false; } String profileName = new String(profileTag); final VpnProfile profile = VpnProfile.decode( - profileName, mKeyStore.get(Credentials.VPN + profileName)); + profileName, mVpnProfileStore.get(Credentials.VPN + profileName)); if (profile == null) { loge("Lockdown VPN configured invalid profile " + profileName); setLockdownTracker(null); @@ -437,7 +437,7 @@ public class VpnManagerService extends IVpnManager.Stub { return false; } setLockdownTracker( - new LockdownVpnTracker(mContext, mHandler, mKeyStore, vpn, profile)); + new LockdownVpnTracker(mContext, mHandler, vpn, profile)); } return true; @@ -495,7 +495,7 @@ public class VpnManagerService extends IVpnManager.Stub { return false; } - return vpn.startAlwaysOnVpn(mKeyStore); + return vpn.startAlwaysOnVpn(); } } @@ -510,7 +510,7 @@ public class VpnManagerService extends IVpnManager.Stub { logw("User " + userId + " has no Vpn configuration"); return false; } - return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore); + return vpn.isAlwaysOnPackageSupported(packageName); } } @@ -531,11 +531,11 @@ public class VpnManagerService extends IVpnManager.Stub { logw("User " + userId + " has no Vpn configuration"); return false; } - if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownAllowlist, mKeyStore)) { + if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownAllowlist)) { return false; } if (!startAlwaysOnVpn(userId)) { - vpn.setAlwaysOnPackage(null, false, null, mKeyStore); + vpn.setAlwaysOnPackage(null, false, null); return false; } } @@ -705,7 +705,8 @@ public class VpnManagerService extends IVpnManager.Stub { loge("Starting user already has a VPN"); return; } - userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, mKeyStore); + userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, + new VpnProfileStore()); mVpns.put(userId, userVpn); if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { updateLockdownVpn(); @@ -777,7 +778,7 @@ public class VpnManagerService extends IVpnManager.Stub { if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) { log("Restarting always-on VPN package " + packageName + " for user " + userId); - vpn.startAlwaysOnVpn(mKeyStore); + vpn.startAlwaysOnVpn(); } } } @@ -798,7 +799,7 @@ public class VpnManagerService extends IVpnManager.Stub { if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) { log("Removing always-on VPN package " + packageName + " for user " + userId); - vpn.setAlwaysOnPackage(null, false, null, mKeyStore); + vpn.setAlwaysOnPackage(null, false, null); } } } @@ -843,7 +844,7 @@ public class VpnManagerService extends IVpnManager.Stub { if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) { final long ident = Binder.clearCallingIdentity(); try { - mKeyStore.delete(Credentials.LOCKDOWN_VPN); + mVpnProfileStore.remove(Credentials.LOCKDOWN_VPN); mLockdownEnabled = false; setLockdownTracker(null); } finally { diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 9930eac5cbd5..73755231c3be 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -279,7 +279,7 @@ public class AccountManagerService mAppOpsManager = mContext.getSystemService(AppOpsManager.class); mHandler = new MessageHandler(injector.getMessageHandlerLooper()); mAuthenticatorCache = mInjector.getAccountAuthenticatorCache(); - mAuthenticatorCache.setListener(this, null /* Handler */); + mAuthenticatorCache.setListener(this, mHandler); sThis.set(this); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index d998ebbf4aff..277cb8c877dd 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -86,6 +86,7 @@ import android.app.Service; import android.app.ServiceStartArgs; import android.app.admin.DevicePolicyEventLogger; import android.app.compat.CompatChanges; +import android.app.usage.UsageEvents; import android.appwidget.AppWidgetManagerInternal; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; @@ -2464,6 +2465,10 @@ public final class ActiveServices { s.setAllowedBgFgsStartsByBinding(true); } + if ((flags & Context.BIND_NOT_APP_COMPONENT_USAGE) != 0) { + s.isNotAppComponentUsage = true; + } + if (s.app != null) { updateServiceClientActivitiesLocked(s.app.mServices, c, true); } @@ -3332,6 +3337,14 @@ public final class ActiveServices { return msg; } + // Report usage if binding is from a different package except for explicitly exempted + // bindings + if (!r.appInfo.packageName.equals(r.mRecentCallingPackage) + && !r.isNotAppComponentUsage) { + mAm.mUsageStatsService.reportEvent( + r.packageName, r.userId, UsageEvents.Event.APP_COMPONENT_USED); + } + // Service is now being launched, its package can't be stopped. try { AppGlobals.getPackageManager().setPackageStoppedState( diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e8a4fa20cd30..874e5272764c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2684,6 +2684,11 @@ public class ActivityManagerService extends IActivityManager.Stub } if (mUsageStatsService != null) { mUsageStatsService.reportEvent(activity, userId, event, appToken.hashCode(), taskRoot); + if (event == Event.ACTIVITY_RESUMED) { + // Report component usage as an activity is an app component + mUsageStatsService.reportEvent( + activity.getPackageName(), userId, Event.APP_COMPONENT_USED); + } } ContentCaptureManagerInternal contentCaptureService = mContentCaptureService; if (contentCaptureService != null && (event == Event.ACTIVITY_PAUSED @@ -6099,6 +6104,10 @@ public class ActivityManagerService extends IActivityManager.Stub updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN); } + // Report usage as process is persistent and being started. + mUsageStatsService.reportEvent(info.packageName, UserHandle.getUserId(app.uid), + Event.APP_COMPONENT_USED); + // This package really, really can not be stopped. try { AppGlobals.getPackageManager().setPackageStoppedState( diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index c971bd2ab6d5..5ad77a3a412a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -168,6 +168,7 @@ final class ActivityManagerShellCommand extends ShellCommand { private int mTaskId; private boolean mIsTaskOverlay; private boolean mIsLockTask; + private boolean mAsync; private BroadcastOptions mBroadcastOptions; final boolean mDumping; @@ -342,6 +343,7 @@ final class ActivityManagerShellCommand extends ShellCommand { mTaskId = INVALID_TASK_ID; mIsTaskOverlay = false; mIsLockTask = false; + mAsync = false; mBroadcastOptions = null; return Intent.parseCommandArgs(this, new Intent.CommandOptionHandler() { @@ -406,6 +408,8 @@ final class ActivityManagerShellCommand extends ShellCommand { mBroadcastOptions = BroadcastOptions.makeBasic(); } mBroadcastOptions.setBackgroundActivityStartsAllowed(true); + } else if (opt.equals("--async")) { + mAsync = true; } else { return false; } @@ -756,7 +760,9 @@ final class ActivityManagerShellCommand extends ShellCommand { mInterface.broadcastIntentWithFeature(null, null, intent, null, receiver, 0, null, null, requiredPermissions, android.app.AppOpsManager.OP_NONE, bundle, true, false, mUserId); - receiver.waitForFinish(); + if (!mAsync) { + receiver.waitForFinish(); + } return 0; } @@ -3180,13 +3186,17 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" Stop a Service. Options are:"); pw.println(" --user <USER_ID> | current: Specify which user to run as; if not"); pw.println(" specified then run as the current user."); - pw.println(" broadcast [--user <USER_ID> | all | current] <INTENT>"); + pw.println(" broadcast [--user <USER_ID> | all | current]"); + pw.println(" [--receiver-permission <PERMISSION>]"); + pw.println(" [--allow-background-activity-starts]"); + pw.println(" [--async] <INTENT>"); pw.println(" Send a broadcast Intent. Options are:"); pw.println(" --user <USER_ID> | all | current: Specify which user to send to; if not"); pw.println(" specified then send to all users."); pw.println(" --receiver-permission <PERMISSION>: Require receiver to hold permission."); pw.println(" --allow-background-activity-starts: The receiver may start activities"); pw.println(" even if in the background."); + pw.println(" --async: Send without waiting for the completion of the receiver."); pw.println(" instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]"); pw.println(" [--user <USER_ID> | current]"); pw.println(" [--no-hidden-api-checks [--no-test-api-access]]"); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 167c2b1ad66c..ce2852ce7727 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -16,6 +16,9 @@ package com.android.server.am; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; + +import android.annotation.NonNull; import android.bluetooth.BluetoothActivityEnergyInfo; import android.content.ContentResolver; import android.content.Context; @@ -25,7 +28,9 @@ import android.hardware.power.stats.PowerEntity; import android.hardware.power.stats.State; import android.hardware.power.stats.StateResidency; import android.hardware.power.stats.StateResidencyResult; +import android.net.ConnectivityManager; import android.net.INetworkManagementEventObserver; +import android.net.Network; import android.net.NetworkCapabilities; import android.os.BatteryStats; import android.os.BatteryStatsInternal; @@ -77,8 +82,10 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.ParseUtils; import com.android.internal.util.function.pooled.PooledLambda; +import com.android.net.module.util.NetworkCapabilitiesUtils; import com.android.server.LocalServices; import com.android.server.Watchdog; +import com.android.server.connectivity.DataConnectionStats; import com.android.server.net.BaseNetworkObserver; import com.android.server.pm.UserManagerInternal; @@ -291,6 +298,23 @@ public final class BatteryStatsService extends IBatteryStats.Stub return builder.toString(); } + private ConnectivityManager.NetworkCallback mNetworkCallback = + new ConnectivityManager.NetworkCallback() { + @Override + public void onCapabilitiesChanged(@NonNull Network network, + @NonNull NetworkCapabilities networkCapabilities) { + final String state = networkCapabilities.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + ? "CONNECTED" : "SUSPENDED"; + noteConnectivityChanged(NetworkCapabilitiesUtils.getDisplayTransport( + networkCapabilities.getTransportTypes()), state); + } + + @Override + public void onLost(Network network) { + noteConnectivityChanged(-1, "DISCONNECTED"); + } + }; + BatteryStatsService(Context context, File systemDir, Handler handler) { // BatteryStatsImpl expects the ActivityManagerService handler, so pass that one through. mContext = context; @@ -330,8 +354,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub mWorker.systemServicesReady(); final INetworkManagementService nms = INetworkManagementService.Stub.asInterface( ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); + final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); try { nms.registerObserver(mActivityChangeObserver); + cm.registerDefaultNetworkCallback(mNetworkCallback); } catch (RemoteException e) { Slog.e(TAG, "Could not register INetworkManagement event observer " + e); } @@ -346,6 +372,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub } Watchdog.getInstance().addMonitor(this); + + final DataConnectionStats dataConnectionStats = new DataConnectionStats(mContext, mHandler); + dataConnectionStats.startMonitoring(); } private final class LocalService extends BatteryStatsInternal { diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 29061930cd84..06cacc70a9b8 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -29,6 +29,7 @@ import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.app.IApplicationThread; import android.app.PendingIntent; +import android.app.usage.UsageEvents.Event; import android.content.ComponentName; import android.content.ContentResolver; import android.content.IIntentReceiver; @@ -52,6 +53,7 @@ import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.permission.IPermissionManager; +import android.text.TextUtils; import android.util.EventLog; import android.util.Slog; import android.util.SparseIntArray; @@ -1634,6 +1636,13 @@ public final class BroadcastQueue { brOptions.getTemporaryAppAllowlistReason()); } + // Report that a component is used for explicit broadcasts. + if (!r.intent.isExcludingStopped() && r.curComponent != null + && !TextUtils.equals(r.curComponent.getPackageName(), r.callerPackage)) { + mService.mUsageStatsService.reportEvent( + r.curComponent.getPackageName(), r.userId, Event.APP_COMPONENT_USED); + } + // Broadcast is being executed, its package can't be stopped. try { AppGlobals.getPackageManager().setPackageStoppedState( diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index f43c7f6278c9..2c8794d75795 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -32,6 +32,7 @@ import android.app.AppOpsManager; import android.app.ApplicationExitInfo; import android.app.ContentProviderHolder; import android.app.IApplicationThread; +import android.app.usage.UsageEvents.Event; import android.content.ComponentName; import android.content.ContentProvider; import android.content.ContentResolver; @@ -57,6 +58,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; @@ -412,6 +414,12 @@ public class ContentProviderHelper { final long origId = Binder.clearCallingIdentity(); try { + if (!TextUtils.equals(cpr.appInfo.packageName, callingPackage)) { + // Report component used since a content provider is being bound. + mService.mUsageStatsService.reportEvent( + cpr.appInfo.packageName, userId, Event.APP_COMPONENT_USED); + } + // Content provider is now in use, its package can't be stopped. try { checkTime(startTime, diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java index 3258f8af0da2..d03a47afed8a 100644 --- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java @@ -329,6 +329,22 @@ class ProcessErrorStateRecord { info.append("Package is ").append((int) (loadingProgress * 100)).append("% loaded.\n"); } + // Retrieve controller with max ANR delay from AnrControllers + // Note that we retrieve the controller before dumping stacks because dumping stacks can + // take a few seconds, after which the cause of the ANR delay might have completed and + // there might no longer be a valid ANR controller to cancel the dialog in that case + AnrController anrController = mService.mActivityTaskManager.getAnrController(aInfo); + long anrDialogDelayMs = 0; + if (anrController != null) { + String packageName = aInfo.packageName; + int uid = aInfo.uid; + anrDialogDelayMs = anrController.getAnrDelayMillis(packageName, uid); + // Might execute an async binder call to a system app to show an interim + // ANR progress UI + anrController.onAnrDelayStarted(packageName, uid); + Slog.i(TAG, "ANR delay of " + anrDialogDelayMs + "ms started for " + packageName); + } + StringBuilder report = new StringBuilder(); report.append(MemoryPressureUtil.currentPsiState()); ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true); @@ -417,20 +433,6 @@ class ProcessErrorStateRecord { return; } - // Retrieve max ANR delay from AnrControllers without the mService lock since the - // controllers might in turn call into apps - AnrController anrController = mService.mActivityTaskManager.getAnrController(aInfo); - long anrDialogDelayMs = 0; - if (anrController != null) { - String packageName = aInfo.packageName; - int uid = aInfo.uid; - anrDialogDelayMs = anrController.getAnrDelayMillis(packageName, uid); - // Might execute an async binder call to a system app to show an interim - // ANR progress UI - anrController.onAnrDelayStarted(packageName, uid); - Slog.i(TAG, "ANR delay of " + anrDialogDelayMs + "ms started for " + packageName); - } - synchronized (mService) { // mBatteryStatsService can be null if the AMS is constructed with injector only. This // will only happen in tests. diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 3ab95d131fad..9cd9902f4995 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -107,6 +107,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN boolean delayed; // are we waiting to start this service in the background? boolean fgRequired; // is the service required to go foreground after starting? boolean fgWaiting; // is a timeout for going foreground already scheduled? + boolean isNotAppComponentUsage; // is service binding not considered component/package usage? boolean isForeground; // is service currently in foreground mode? int foregroundId; // Notification ID of last foreground req. Notification foregroundNoti; // Notification record of foreground state. diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 44dcc205a9d0..11125dd55665 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -843,11 +843,15 @@ public class AppOpsService extends IAppOpsService.Stub { public void accessed(int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, @OpFlags int flags) { - accessed(System.currentTimeMillis(), -1, proxyUid, proxyPackageName, + long accessTime = System.currentTimeMillis(); + accessed(accessTime, -1, proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags); mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, tag, uidState, flags); + + mHistoricalRegistry.mDiscreteRegistry.recordDiscreteAccess(parent.uid, + parent.packageName, parent.op, tag, flags, uidState, accessTime, -1); } /** @@ -1004,8 +1008,10 @@ public class AppOpsService extends IAppOpsService.Stub { OpEventProxyInfo proxyCopy = event.getProxy() != null ? new OpEventProxyInfo(event.getProxy()) : null; + long accessDurationMillis = + SystemClock.elapsedRealtime() - event.getStartElapsedTime(); NoteOpEvent finishedEvent = new NoteOpEvent(event.getStartTime(), - SystemClock.elapsedRealtime() - event.getStartElapsedTime(), proxyCopy); + accessDurationMillis, proxyCopy); mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()), finishedEvent); @@ -1013,6 +1019,10 @@ public class AppOpsService extends IAppOpsService.Stub { parent.packageName, tag, event.getUidState(), event.getFlags(), finishedEvent.getDuration()); + mHistoricalRegistry.mDiscreteRegistry.recordDiscreteAccess(parent.uid, + parent.packageName, parent.op, tag, event.getFlags(), event.getUidState(), + event.getStartTime(), accessDurationMillis); + mInProgressStartOpEventPool.release(event); if (mInProgressEvents.isEmpty()) { @@ -2087,8 +2097,8 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public void getHistoricalOps(int uid, String packageName, String attributionTag, - List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis, - int flags, RemoteCallback callback) { + List<String> opNames, int dataType, int filter, long beginTimeMillis, + long endTimeMillis, int flags, RemoteCallback callback) { PackageManager pm = mContext.getPackageManager(); ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter, @@ -2120,14 +2130,14 @@ public class AppOpsService extends IAppOpsService.Stub { // Must not hold the appops lock mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps, - mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, filter, - beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse()); + mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType, + filter, beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse()); } @Override public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag, - List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis, - int flags, RemoteCallback callback) { + List<String> opNames, int dataType, int filter, long beginTimeMillis, + long endTimeMillis, int flags, RemoteCallback callback) { ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter, beginTimeMillis, endTimeMillis, flags); Objects.requireNonNull(callback, "callback cannot be null"); @@ -2140,7 +2150,7 @@ public class AppOpsService extends IAppOpsService.Stub { // Must not hold the appops lock mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw, - mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, + mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType, filter, beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse()); } @@ -4759,6 +4769,7 @@ public class AppOpsService extends IAppOpsService.Stub { mFile.failWrite(stream); } } + mHistoricalRegistry.mDiscreteRegistry.writeAndClearAccessHistory(); } static class Shell extends ShellCommand { @@ -6115,6 +6126,7 @@ public class AppOpsService extends IAppOpsService.Stub { "clearHistory"); // Must not hold the appops lock mHistoricalRegistry.clearHistory(); + mHistoricalRegistry.mDiscreteRegistry.clearHistory(); } @Override diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java new file mode 100644 index 000000000000..76990453ee03 --- /dev/null +++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java @@ -0,0 +1,616 @@ +/* + * 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.appop; + +import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG; +import static android.app.AppOpsManager.FILTER_BY_OP_NAMES; +import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME; +import static android.app.AppOpsManager.FILTER_BY_UID; +import static android.app.AppOpsManager.OP_CAMERA; +import static android.app.AppOpsManager.OP_COARSE_LOCATION; +import static android.app.AppOpsManager.OP_FINE_LOCATION; +import static android.app.AppOpsManager.OP_FLAG_SELF; +import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; +import static android.app.AppOpsManager.OP_RECORD_AUDIO; + +import static java.lang.Math.max; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.os.Environment; +import android.os.FileUtils; +import android.os.Process; +import android.util.ArrayMap; +import android.util.Slog; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; +import android.util.Xml; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.XmlUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * This class manages information about recent accesses to ops for + * permission usage timeline. + * + * The timeline history is kept for limited time (initial default is 24 hours) and + * discarded after that. + * + * Every time state is saved (default is 30 minutes), memory state is dumped to a + * new file and memory state is cleared. Files older than time limit are deleted + * during the process. + * + * When request comes in, files are read and requested information is collected + * and delivered. + */ + +final class DiscreteRegistry { + static final String TIMELINE_FILE_SUFFIX = "tl"; + private static final String TAG = DiscreteRegistry.class.getSimpleName(); + + private static final long TIMELINE_HISTORY_CUTOFF = Duration.ofHours(24).toMillis(); + private static final String TAG_HISTORY = "h"; + private static final String ATTR_VERSION = "v"; + private static final int CURRENT_VERSION = 1; + + private static final String TAG_UID = "u"; + private static final String ATTR_UID = "ui"; + + private static final String TAG_PACKAGE = "p"; + private static final String ATTR_PACKAGE_NAME = "pn"; + + private static final String TAG_OP = "o"; + private static final String ATTR_OP_ID = "op"; + + private static final String TAG_TAG = "a"; + private static final String ATTR_TAG = "at"; + + private static final String TAG_ENTRY = "e"; + private static final String ATTR_NOTE_TIME = "nt"; + private static final String ATTR_NOTE_DURATION = "nd"; + private static final String ATTR_UID_STATE = "us"; + private static final String ATTR_FLAGS = "f"; + + // Lock for read/write access to on disk state + private final Object mOnDiskLock = new Object(); + + //Lock for read/write access to in memory state + private final @NonNull Object mInMemoryLock; + + @GuardedBy("mOnDiskLock") + private final File mDiscreteAccessDir; + + @GuardedBy("mInMemoryLock") + private DiscreteOps mDiscreteOps; + + DiscreteRegistry(Object inMemoryLock) { + mInMemoryLock = inMemoryLock; + mDiscreteAccessDir = new File(new File(Environment.getDataSystemDirectory(), "appops"), + "discrete"); + createDiscreteAccessDir(); + mDiscreteOps = new DiscreteOps(); + } + + private void createDiscreteAccessDir() { + if (!mDiscreteAccessDir.exists()) { + if (!mDiscreteAccessDir.mkdirs()) { + Slog.e(TAG, "Failed to create DiscreteRegistry directory"); + } + FileUtils.setPermissions(mDiscreteAccessDir.getPath(), + FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH, -1, -1); + } + } + + void recordDiscreteAccess(int uid, String packageName, int op, @Nullable String attributionTag, + @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, + long accessDuration) { + if (!isDiscreteOp(op, uid, flags)) { + return; + } + synchronized (mInMemoryLock) { + mDiscreteOps.addDiscreteAccess(op, uid, packageName, attributionTag, flags, uidState, + accessTime, accessDuration); + } + } + + void writeAndClearAccessHistory() { + synchronized (mOnDiskLock) { + final File[] files = mDiscreteAccessDir.listFiles(); + if (files != null && files.length > 0) { + for (File f : files) { + final String fileName = f.getName(); + if (!fileName.endsWith(TIMELINE_FILE_SUFFIX)) { + continue; + } + try { + long timestamp = Long.valueOf(fileName.substring(0, + fileName.length() - TIMELINE_FILE_SUFFIX.length())); + if (Instant.now().minus(TIMELINE_HISTORY_CUTOFF, + ChronoUnit.MILLIS).toEpochMilli() > timestamp) { + f.delete(); + Slog.e(TAG, "Deleting file " + fileName); + + } + } catch (Throwable t) { + Slog.e(TAG, "Error while cleaning timeline files: " + t.getMessage() + " " + + t.getStackTrace()); + } + } + } + } + DiscreteOps discreteOps; + synchronized (mInMemoryLock) { + discreteOps = mDiscreteOps; + mDiscreteOps = new DiscreteOps(); + } + if (discreteOps.isEmpty()) { + return; + } + long currentTimeStamp = Instant.now().toEpochMilli(); + try { + final File file = new File(mDiscreteAccessDir, currentTimeStamp + TIMELINE_FILE_SUFFIX); + discreteOps.writeToFile(file); + } catch (Throwable t) { + Slog.e(TAG, + "Error writing timeline state: " + t.getMessage() + " " + + Arrays.toString(t.getStackTrace())); + } + } + + void getHistoricalDiscreteOps(AppOpsManager.HistoricalOps result, long beginTimeMillis, + long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter, + @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, + @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) { + writeAndClearAccessHistory(); + DiscreteOps discreteOps = new DiscreteOps(); + readDiscreteOpsFromDisk(discreteOps, beginTimeMillis, endTimeMillis, filter, uidFilter, + packageNameFilter, opNamesFilter, attributionTagFilter, flagsFilter); + discreteOps.applyToHistoricalOps(result); + return; + } + + private void readDiscreteOpsFromDisk(DiscreteOps discreteOps, long beginTimeMillis, + long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter, + @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, + @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) { + synchronized (mOnDiskLock) { + long historyBeginTimeMillis = Instant.now().minus(TIMELINE_HISTORY_CUTOFF, + ChronoUnit.MILLIS).toEpochMilli(); + if (historyBeginTimeMillis > endTimeMillis) { + return; + } + beginTimeMillis = max(beginTimeMillis, historyBeginTimeMillis); + + final File[] files = mDiscreteAccessDir.listFiles(); + if (files != null && files.length > 0) { + for (File f : files) { + final String fileName = f.getName(); + if (!fileName.endsWith(TIMELINE_FILE_SUFFIX)) { + continue; + } + long timestamp = Long.valueOf(fileName.substring(0, + fileName.length() - TIMELINE_FILE_SUFFIX.length())); + if (timestamp < beginTimeMillis) { + continue; + } + discreteOps.readFromFile(f, beginTimeMillis, endTimeMillis, filter, uidFilter, + packageNameFilter, opNamesFilter, attributionTagFilter, flagsFilter); + } + } + } + } + + void clearHistory() { + synchronized (mOnDiskLock) { + synchronized (mInMemoryLock) { + mDiscreteOps = new DiscreteOps(); + } + FileUtils.deleteContentsAndDir(mDiscreteAccessDir); + createDiscreteAccessDir(); + } + } + + public static boolean isDiscreteOp(int op, int uid, @AppOpsManager.OpFlags int flags) { + if (!isDiscreteOp(op)) { + return false; + } + if (!isDiscreteUid(uid)) { + return false; + } + if ((flags & (OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED)) == 0) { + return false; + } + return true; + } + + static boolean isDiscreteOp(int op) { + if (op != OP_CAMERA && op != OP_RECORD_AUDIO && op != OP_FINE_LOCATION + && op != OP_COARSE_LOCATION) { + return false; + } + return true; + } + + static boolean isDiscreteUid(int uid) { + if (uid < Process.FIRST_APPLICATION_UID) { + return false; + } + return true; + } + + private final class DiscreteOps { + ArrayMap<Integer, DiscreteUidOps> mUids; + + DiscreteOps() { + mUids = new ArrayMap<>(); + } + + void addDiscreteAccess(int op, int uid, @NonNull String packageName, + @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, + @AppOpsManager.UidState int uidState, long accessTime, long accessDuration) { + getOrCreateDiscreteUidOps(uid).addDiscreteAccess(op, packageName, attributionTag, flags, + uidState, accessTime, accessDuration); + } + + private void applyToHistoricalOps(AppOpsManager.HistoricalOps result) { + int nUids = mUids.size(); + for (int i = 0; i < nUids; i++) { + mUids.valueAt(i).applyToHistory(result, mUids.keyAt(i)); + } + } + + private void writeToFile(File f) throws Exception { + FileOutputStream stream = new FileOutputStream(f); + TypedXmlSerializer out = Xml.resolveSerializer(stream); + + out.startDocument(null, true); + out.startTag(null, TAG_HISTORY); + out.attributeInt(null, ATTR_VERSION, CURRENT_VERSION); + + int nUids = mUids.size(); + for (int i = 0; i < nUids; i++) { + out.startTag(null, TAG_UID); + out.attributeInt(null, ATTR_UID, mUids.keyAt(i)); + mUids.valueAt(i).serialize(out); + out.endTag(null, TAG_UID); + } + out.endTag(null, TAG_HISTORY); + out.endDocument(); + stream.close(); + } + + private DiscreteUidOps getOrCreateDiscreteUidOps(int uid) { + DiscreteUidOps result = mUids.get(uid); + if (result == null) { + result = new DiscreteUidOps(); + mUids.put(uid, result); + } + return result; + } + + boolean isEmpty() { + return mUids.isEmpty(); + } + + private void readFromFile(File f, long beginTimeMillis, long endTimeMillis, + @AppOpsManager.HistoricalOpsRequestFilter int filter, + int uidFilter, @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, + @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) { + try { + FileInputStream stream = new FileInputStream(f); + TypedXmlPullParser parser = Xml.resolvePullParser(stream); + XmlUtils.beginDocument(parser, TAG_HISTORY); + + // We haven't released version 1 and have more detailed + // accounting - just nuke the current state + final int version = parser.getAttributeInt(null, ATTR_VERSION); + if (version != CURRENT_VERSION) { + throw new IllegalStateException("Dropping unsupported discrete history " + f); + } + + int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + if (TAG_UID.equals(parser.getName())) { + int uid = parser.getAttributeInt(null, ATTR_UID, -1); + if ((filter & FILTER_BY_UID) != 0 && uid != uidFilter) { + continue; + } + getOrCreateDiscreteUidOps(uid).deserialize(parser, beginTimeMillis, + endTimeMillis, filter, packageNameFilter, opNamesFilter, + attributionTagFilter, flagsFilter); + } + } + } catch (Throwable t) { + Slog.e(TAG, "Failed to read file " + f.getName() + " " + t.getMessage() + " " + + Arrays.toString(t.getStackTrace())); + } + + } + } + + private final class DiscreteUidOps { + ArrayMap<String, DiscretePackageOps> mPackages; + + DiscreteUidOps() { + mPackages = new ArrayMap<>(); + } + + void addDiscreteAccess(int op, @NonNull String packageName, @Nullable String attributionTag, + @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, + long accessTime, long accessDuration) { + getOrCreateDiscretePackageOps(packageName).addDiscreteAccess(op, attributionTag, flags, + uidState, accessTime, accessDuration); + } + + private DiscretePackageOps getOrCreateDiscretePackageOps(String packageName) { + DiscretePackageOps result = mPackages.get(packageName); + if (result == null) { + result = new DiscretePackageOps(); + mPackages.put(packageName, result); + } + return result; + } + + private void applyToHistory(AppOpsManager.HistoricalOps result, int uid) { + int nPackages = mPackages.size(); + for (int i = 0; i < nPackages; i++) { + mPackages.valueAt(i).applyToHistory(result, uid, mPackages.keyAt(i)); + } + } + + void serialize(TypedXmlSerializer out) throws Exception { + int nPackages = mPackages.size(); + for (int i = 0; i < nPackages; i++) { + out.startTag(null, TAG_PACKAGE); + out.attribute(null, ATTR_PACKAGE_NAME, mPackages.keyAt(i)); + mPackages.valueAt(i).serialize(out); + out.endTag(null, TAG_PACKAGE); + } + } + + void deserialize(TypedXmlPullParser parser, long beginTimeMillis, + long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, + @Nullable String packageNameFilter, + @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter, + @AppOpsManager.OpFlags int flagsFilter) throws Exception { + int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + if (TAG_PACKAGE.equals(parser.getName())) { + String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); + if ((filter & FILTER_BY_PACKAGE_NAME) != 0 + && !packageName.equals(packageNameFilter)) { + continue; + } + getOrCreateDiscretePackageOps(packageName).deserialize(parser, beginTimeMillis, + endTimeMillis, filter, opNamesFilter, attributionTagFilter, + flagsFilter); + } + } + } + } + + private final class DiscretePackageOps { + ArrayMap<Integer, DiscreteOp> mPackageOps; + + DiscretePackageOps() { + mPackageOps = new ArrayMap<>(); + } + + void addDiscreteAccess(int op, @Nullable String attributionTag, + @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, + long accessTime, long accessDuration) { + getOrCreateDiscreteOp(op).addDiscreteAccess(attributionTag, flags, uidState, accessTime, + accessDuration); + } + + private DiscreteOp getOrCreateDiscreteOp(int op) { + DiscreteOp result = mPackageOps.get(op); + if (result == null) { + result = new DiscreteOp(); + mPackageOps.put(op, result); + } + return result; + } + + private void applyToHistory(AppOpsManager.HistoricalOps result, int uid, + @NonNull String packageName) { + int nPackageOps = mPackageOps.size(); + for (int i = 0; i < nPackageOps; i++) { + mPackageOps.valueAt(i).applyToHistory(result, uid, packageName, + mPackageOps.keyAt(i)); + } + } + + void serialize(TypedXmlSerializer out) throws Exception { + int nOps = mPackageOps.size(); + for (int i = 0; i < nOps; i++) { + out.startTag(null, TAG_OP); + out.attributeInt(null, ATTR_OP_ID, mPackageOps.keyAt(i)); + mPackageOps.valueAt(i).serialize(out); + out.endTag(null, TAG_OP); + } + } + + void deserialize(TypedXmlPullParser parser, long beginTimeMillis, long endTimeMillis, + @AppOpsManager.HistoricalOpsRequestFilter int filter, + @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter, + @AppOpsManager.OpFlags int flagsFilter) throws Exception { + int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + if (TAG_OP.equals(parser.getName())) { + int op = parser.getAttributeInt(null, ATTR_OP_ID); + if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(opNamesFilter, + AppOpsManager.opToPublicName(op))) { + continue; + } + getOrCreateDiscreteOp(op).deserialize(parser, beginTimeMillis, endTimeMillis, + filter, attributionTagFilter, flagsFilter); + } + } + } + } + + private final class DiscreteOp { + ArrayMap<String, List<DiscreteOpEvent>> mAttributedOps; + + DiscreteOp() { + mAttributedOps = new ArrayMap<>(); + } + + void addDiscreteAccess(@Nullable String attributionTag, + @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, + long accessTime, long accessDuration) { + List<DiscreteOpEvent> attributedOps = getOrCreateDiscreteOpEventsList( + attributionTag); + accessTime = Instant.ofEpochMilli(accessTime).truncatedTo( + ChronoUnit.MINUTES).toEpochMilli(); + + int nAttributedOps = attributedOps.size(); + for (int i = nAttributedOps - 1; i >= 0; i--) { + DiscreteOpEvent previousOp = attributedOps.get(i); + if (previousOp.mNoteTime < accessTime) { + break; + } + if (previousOp.mOpFlag == flags && previousOp.mUidState == uidState) { + return; + } + } + attributedOps.add(new DiscreteOpEvent(accessTime, accessDuration, uidState, flags)); + } + + private List<DiscreteOpEvent> getOrCreateDiscreteOpEventsList(String attributionTag) { + List<DiscreteOpEvent> result = mAttributedOps.get(attributionTag); + if (result == null) { + result = new ArrayList<>(); + mAttributedOps.put(attributionTag, result); + } + return result; + } + + private void applyToHistory(AppOpsManager.HistoricalOps result, int uid, + @NonNull String packageName, int op) { + int nOps = mAttributedOps.size(); + for (int i = 0; i < nOps; i++) { + String tag = mAttributedOps.keyAt(i); + List<DiscreteOpEvent> events = mAttributedOps.valueAt(i); + int nEvents = events.size(); + for (int j = 0; j < nEvents; j++) { + DiscreteOpEvent event = events.get(j); + result.addDiscreteAccess(op, uid, packageName, tag, event.mUidState, + event.mOpFlag, event.mNoteTime, event.mNoteDuration); + } + } + } + + void serialize(TypedXmlSerializer out) throws Exception { + int nAttributions = mAttributedOps.size(); + for (int i = 0; i < nAttributions; i++) { + out.startTag(null, TAG_TAG); + String tag = mAttributedOps.keyAt(i); + if (tag != null) { + out.attribute(null, ATTR_TAG, mAttributedOps.keyAt(i)); + } + List<DiscreteOpEvent> ops = mAttributedOps.valueAt(i); + int nOps = ops.size(); + for (int j = 0; j < nOps; j++) { + out.startTag(null, TAG_ENTRY); + ops.get(j).serialize(out); + out.endTag(null, TAG_ENTRY); + } + out.endTag(null, TAG_TAG); + } + } + + void deserialize(TypedXmlPullParser parser, long beginTimeMillis, long endTimeMillis, + @AppOpsManager.HistoricalOpsRequestFilter int filter, + @Nullable String attributionTagFilter, + @AppOpsManager.OpFlags int flagsFilter) throws Exception { + int outerDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + if (TAG_TAG.equals(parser.getName())) { + String attributionTag = parser.getAttributeValue(null, ATTR_TAG); + if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !attributionTag.equals( + attributionTagFilter)) { + continue; + } + List<DiscreteOpEvent> events = getOrCreateDiscreteOpEventsList( + attributionTag); + int innerDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, innerDepth)) { + if (TAG_ENTRY.equals(parser.getName())) { + long noteTime = parser.getAttributeLong(null, ATTR_NOTE_TIME); + long noteDuration = parser.getAttributeLong(null, ATTR_NOTE_DURATION, + -1); + int uidState = parser.getAttributeInt(null, ATTR_UID_STATE); + int opFlags = parser.getAttributeInt(null, ATTR_FLAGS); + if ((flagsFilter & opFlags) == 0) { + continue; + } + if ((noteTime + noteDuration < beginTimeMillis + && noteTime > endTimeMillis)) { + continue; + } + DiscreteOpEvent event = new DiscreteOpEvent(noteTime, noteDuration, + uidState, opFlags); + events.add(event); + } + } + Collections.sort(events, (a, b) -> a.mNoteTime < b.mNoteTime ? -1 + : (a.mNoteTime == b.mNoteTime ? 0 : 1)); + } + } + } + } + + private final class DiscreteOpEvent { + final long mNoteTime; + final long mNoteDuration; + final @AppOpsManager.UidState int mUidState; + final @AppOpsManager.OpFlags int mOpFlag; + + DiscreteOpEvent(long noteTime, long noteDuration, @AppOpsManager.UidState int uidState, + @AppOpsManager.OpFlags int opFlag) { + mNoteTime = noteTime; + mNoteDuration = noteDuration; + mUidState = uidState; + mOpFlag = opFlag; + } + + private void serialize(TypedXmlSerializer out) throws Exception { + out.attributeLong(null, ATTR_NOTE_TIME, mNoteTime); + if (mNoteDuration != -1) { + out.attributeLong(null, ATTR_NOTE_DURATION, mNoteDuration); + } + out.attributeInt(null, ATTR_UID_STATE, mUidState); + out.attributeInt(null, ATTR_FLAGS, mOpFlag); + } + } +} + diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java index 17fd32c57e09..1c43fedd3112 100644 --- a/services/core/java/com/android/server/appop/HistoricalRegistry.java +++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java @@ -19,6 +19,8 @@ import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG; import static android.app.AppOpsManager.FILTER_BY_OP_NAMES; import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME; import static android.app.AppOpsManager.FILTER_BY_UID; +import static android.app.AppOpsManager.HISTORY_FLAG_AGGREGATE; +import static android.app.AppOpsManager.HISTORY_FLAG_DISCRETE; import android.annotation.NonNull; import android.annotation.Nullable; @@ -30,6 +32,7 @@ import android.app.AppOpsManager.HistoricalOpsRequestFilter; import android.app.AppOpsManager.HistoricalPackageOps; import android.app.AppOpsManager.HistoricalUidOps; import android.app.AppOpsManager.OpFlags; +import android.app.AppOpsManager.OpHistoryFlags; import android.app.AppOpsManager.UidState; import android.content.ContentResolver; import android.database.ContentObserver; @@ -61,9 +64,7 @@ import com.android.internal.util.XmlUtils; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.FgThread; -import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileInputStream; @@ -71,7 +72,6 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -85,7 +85,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; /** - * This class managers historical app op state. This includes reading, persistence, + * This class manages historical app op state. This includes reading, persistence, * accounting, querying. * <p> * The history is kept forever in multiple files. Each file time contains the @@ -138,6 +138,8 @@ final class HistoricalRegistry { private static final String PARAMETER_ASSIGNMENT = "="; private static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled"; + volatile @NonNull DiscreteRegistry mDiscreteRegistry; + @GuardedBy("mLock") private @NonNull LinkedList<HistoricalOps> mPendingWrites = new LinkedList<>(); @@ -199,6 +201,7 @@ final class HistoricalRegistry { HistoricalRegistry(@NonNull Object lock) { mInMemoryLock = lock; + mDiscreteRegistry = new DiscreteRegistry(lock); } HistoricalRegistry(@NonNull HistoricalRegistry other) { @@ -352,36 +355,49 @@ final class HistoricalRegistry { } } - void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName, + void getHistoricalOpsFromDiskRaw(int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable String[] opNames, - @HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis, - @OpFlags int flags, @NonNull RemoteCallback callback) { + @OpHistoryFlags int historyFlags, @HistoricalOpsRequestFilter int filter, + long beginTimeMillis, long endTimeMillis, @OpFlags int flags, + @NonNull RemoteCallback callback) { if (!isApiEnabled()) { callback.sendResult(new Bundle()); return; } - synchronized (mOnDiskLock) { - synchronized (mInMemoryLock) { - if (!isPersistenceInitializedMLocked()) { - Slog.e(LOG_TAG, "Interaction before persistence initialized"); - callback.sendResult(new Bundle()); - return; + final HistoricalOps result = new HistoricalOps(beginTimeMillis, endTimeMillis); + + if ((historyFlags & HISTORY_FLAG_AGGREGATE) != 0) { + synchronized (mOnDiskLock) { + synchronized (mInMemoryLock) { + if (!isPersistenceInitializedMLocked()) { + Slog.e(LOG_TAG, "Interaction before persistence initialized"); + callback.sendResult(new Bundle()); + return; + } + mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, + attributionTag, + opNames, filter, beginTimeMillis, endTimeMillis, flags); + } - final HistoricalOps result = new HistoricalOps(beginTimeMillis, endTimeMillis); - mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, attributionTag, - opNames, filter, beginTimeMillis, endTimeMillis, flags); - final Bundle payload = new Bundle(); - payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result); - callback.sendResult(payload); } } + + if ((historyFlags & HISTORY_FLAG_DISCRETE) != 0) { + mDiscreteRegistry.getHistoricalDiscreteOps(result, beginTimeMillis, endTimeMillis, + filter, uid, packageName, opNames, attributionTag, + flags); + } + + final Bundle payload = new Bundle(); + payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result); + callback.sendResult(payload); } - void getHistoricalOps(int uid, @NonNull String packageName, @Nullable String attributionTag, - @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter, - long beginTimeMillis, long endTimeMillis, @OpFlags int flags, - @NonNull RemoteCallback callback) { + void getHistoricalOps(int uid, @Nullable String packageName, @Nullable String attributionTag, + @Nullable String[] opNames, @OpHistoryFlags int historyFlags, + @HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis, + @OpFlags int flags, @NonNull RemoteCallback callback) { if (!isApiEnabled()) { callback.sendResult(new Bundle()); return; @@ -392,6 +408,8 @@ final class HistoricalRegistry { endTimeMillis = currentTimeMillis; } + final Bundle payload = new Bundle(); + // Argument times are based off epoch start while our internal store is // based off now, so take this into account. final long inMemoryAdjBeginTimeMillis = Math.max(currentTimeMillis - endTimeMillis, 0); @@ -399,55 +417,63 @@ final class HistoricalRegistry { final HistoricalOps result = new HistoricalOps(inMemoryAdjBeginTimeMillis, inMemoryAdjEndTimeMillis); - synchronized (mOnDiskLock) { - final List<HistoricalOps> pendingWrites; - final HistoricalOps currentOps; - boolean collectOpsFromDisk; - - synchronized (mInMemoryLock) { - if (!isPersistenceInitializedMLocked()) { - Slog.e(LOG_TAG, "Interaction before persistence initialized"); - callback.sendResult(new Bundle()); - return; - } - - currentOps = getUpdatedPendingHistoricalOpsMLocked(currentTimeMillis); - if (!(inMemoryAdjBeginTimeMillis >= currentOps.getEndTimeMillis() - || inMemoryAdjEndTimeMillis <= currentOps.getBeginTimeMillis())) { - // Some of the current batch falls into the query, so extract that. - final HistoricalOps currentOpsCopy = new HistoricalOps(currentOps); - currentOpsCopy.filter(uid, packageName, attributionTag, opNames, filter, - inMemoryAdjBeginTimeMillis, inMemoryAdjEndTimeMillis); - result.merge(currentOpsCopy); - } - pendingWrites = new ArrayList<>(mPendingWrites); - mPendingWrites.clear(); - collectOpsFromDisk = inMemoryAdjEndTimeMillis > currentOps.getEndTimeMillis(); - } + if ((historyFlags & HISTORY_FLAG_DISCRETE) != 0) { + mDiscreteRegistry.getHistoricalDiscreteOps(result, beginTimeMillis, endTimeMillis, + filter, uid, packageName, opNames, attributionTag, flags); + } - // If the query was only for in-memory state - done. - if (collectOpsFromDisk) { - // If there is a write in flight we need to force it now - persistPendingHistory(pendingWrites); - // Collect persisted state. - final long onDiskAndInMemoryOffsetMillis = currentTimeMillis - - mNextPersistDueTimeMillis + mBaseSnapshotInterval; - final long onDiskAdjBeginTimeMillis = Math.max(inMemoryAdjBeginTimeMillis - - onDiskAndInMemoryOffsetMillis, 0); - final long onDiskAdjEndTimeMillis = Math.max(inMemoryAdjEndTimeMillis - - onDiskAndInMemoryOffsetMillis, 0); - mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, attributionTag, - opNames, filter, onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis, flags); - } + if ((historyFlags & HISTORY_FLAG_AGGREGATE) != 0) { + synchronized (mOnDiskLock) { + final List<HistoricalOps> pendingWrites; + final HistoricalOps currentOps; + boolean collectOpsFromDisk; - // Rebase the result time to be since epoch. - result.setBeginAndEndTime(beginTimeMillis, endTimeMillis); + synchronized (mInMemoryLock) { + if (!isPersistenceInitializedMLocked()) { + Slog.e(LOG_TAG, "Interaction before persistence initialized"); + callback.sendResult(new Bundle()); + return; + } - // Send back the result. - final Bundle payload = new Bundle(); - payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result); - callback.sendResult(payload); - } + currentOps = getUpdatedPendingHistoricalOpsMLocked(currentTimeMillis); + if (!(inMemoryAdjBeginTimeMillis >= currentOps.getEndTimeMillis() + || inMemoryAdjEndTimeMillis <= currentOps.getBeginTimeMillis())) { + // Some of the current batch falls into the query, so extract that. + final HistoricalOps currentOpsCopy = new HistoricalOps(currentOps); + currentOpsCopy.filter(uid, packageName, attributionTag, opNames, + historyFlags, filter, inMemoryAdjBeginTimeMillis, + inMemoryAdjEndTimeMillis); + result.merge(currentOpsCopy); + } + pendingWrites = new ArrayList<>(mPendingWrites); + mPendingWrites.clear(); + collectOpsFromDisk = inMemoryAdjEndTimeMillis > currentOps.getEndTimeMillis(); + } + + // If the query was only for in-memory state - done. + if (collectOpsFromDisk) { + // If there is a write in flight we need to force it now + persistPendingHistory(pendingWrites); + // Collect persisted state. + final long onDiskAndInMemoryOffsetMillis = currentTimeMillis + - mNextPersistDueTimeMillis + mBaseSnapshotInterval; + final long onDiskAdjBeginTimeMillis = Math.max(inMemoryAdjBeginTimeMillis + - onDiskAndInMemoryOffsetMillis, 0); + final long onDiskAdjEndTimeMillis = Math.max(inMemoryAdjEndTimeMillis + - onDiskAndInMemoryOffsetMillis, 0); + mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, + attributionTag, + opNames, filter, onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis, + flags); + } + } + } + // Rebase the result time to be since epoch. + result.setBeginAndEndTime(beginTimeMillis, endTimeMillis); + + // Send back the result. + payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result); + callback.sendResult(payload); } void incrementOpAccessedCount(int op, int uid, @NonNull String packageName, @@ -692,6 +718,7 @@ final class HistoricalRegistry { } persistPendingHistory(pendingWrites); } + mDiscreteRegistry.writeAndClearAccessHistory(); } private void persistPendingHistory(@NonNull List<HistoricalOps> pendingWrites) { diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index e19745e5c578..050b28b363d2 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -33,6 +33,7 @@ import android.annotation.NonNull; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; +import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.IAuthService; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; @@ -337,6 +338,168 @@ public class AuthService extends SystemService { Binder.restoreCallingIdentity(identity); } } + + @Override + public CharSequence getButtonLabel( + int userId, + String opPackageName, + @Authenticators.Types int authenticators) throws RemoteException { + + // Only allow internal clients to call getButtonLabel with a different userId. + final int callingUserId = UserHandle.getCallingUserId(); + + if (userId != callingUserId) { + checkInternalPermission(); + } else { + checkPermission(); + } + + final long identity = Binder.clearCallingIdentity(); + try { + @BiometricAuthenticator.Modality final int modality = + mBiometricService.getCurrentModality( + opPackageName, userId, callingUserId, authenticators); + + final String result; + switch (getCredentialBackupModality(modality)) { + case BiometricAuthenticator.TYPE_NONE: + result = null; + break; + case BiometricAuthenticator.TYPE_CREDENTIAL: + result = getContext().getString(R.string.screen_lock_app_setting_name); + break; + case BiometricAuthenticator.TYPE_FINGERPRINT: + result = getContext().getString(R.string.fingerprint_app_setting_name); + break; + case BiometricAuthenticator.TYPE_FACE: + result = getContext().getString(R.string.face_app_setting_name); + break; + default: + result = getContext().getString(R.string.biometric_app_setting_name); + break; + } + + return result; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public CharSequence getPromptMessage( + int userId, + String opPackageName, + @Authenticators.Types int authenticators) throws RemoteException { + + // Only allow internal clients to call getButtonLabel with a different userId. + final int callingUserId = UserHandle.getCallingUserId(); + + if (userId != callingUserId) { + checkInternalPermission(); + } else { + checkPermission(); + } + + final long identity = Binder.clearCallingIdentity(); + try { + @BiometricAuthenticator.Modality final int modality = + mBiometricService.getCurrentModality( + opPackageName, userId, callingUserId, authenticators); + + final String result; + switch (getCredentialBackupModality(modality)) { + case BiometricAuthenticator.TYPE_NONE: + result = null; + break; + case BiometricAuthenticator.TYPE_CREDENTIAL: + result = getContext().getString( + R.string.screen_lock_dialog_default_subtitle); + break; + case BiometricAuthenticator.TYPE_FINGERPRINT: + result = getContext().getString( + R.string.fingerprint_dialog_default_subtitle); + break; + case BiometricAuthenticator.TYPE_FACE: + result = getContext().getString(R.string.face_dialog_default_subtitle); + break; + default: + result = getContext().getString(R.string.biometric_dialog_default_subtitle); + break; + } + return result; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public CharSequence getSettingName( + int userId, + String opPackageName, + @Authenticators.Types int authenticators) throws RemoteException { + + // Only allow internal clients to call getButtonLabel with a different userId. + final int callingUserId = UserHandle.getCallingUserId(); + + if (userId != callingUserId) { + checkInternalPermission(); + } else { + checkPermission(); + } + + final long identity = Binder.clearCallingIdentity(); + try { + @BiometricAuthenticator.Modality final int modality = + mBiometricService.getSupportedModalities(authenticators); + + final String result; + switch (modality) { + // Handle the case of a single supported modality. + case BiometricAuthenticator.TYPE_NONE: + result = null; + break; + case BiometricAuthenticator.TYPE_CREDENTIAL: + result = getContext().getString(R.string.screen_lock_app_setting_name); + break; + case BiometricAuthenticator.TYPE_IRIS: + result = getContext().getString(R.string.biometric_app_setting_name); + break; + case BiometricAuthenticator.TYPE_FINGERPRINT: + result = getContext().getString(R.string.fingerprint_app_setting_name); + break; + case BiometricAuthenticator.TYPE_FACE: + result = getContext().getString(R.string.face_app_setting_name); + break; + + // Handle other possible modality combinations. + default: + if ((modality & BiometricAuthenticator.TYPE_CREDENTIAL) == 0) { + // 2+ biometric modalities are supported (but not device credential). + result = getContext().getString(R.string.biometric_app_setting_name); + } else { + @BiometricAuthenticator.Modality final int biometricModality = + modality & ~BiometricAuthenticator.TYPE_CREDENTIAL; + if (biometricModality == BiometricAuthenticator.TYPE_FINGERPRINT) { + // Only device credential and fingerprint are supported. + result = getContext().getString( + R.string.fingerprint_or_screen_lock_app_setting_name); + } else if (biometricModality == BiometricAuthenticator.TYPE_FACE) { + // Only device credential and face are supported. + result = getContext().getString( + R.string.face_or_screen_lock_app_setting_name); + } else { + // Device credential and 1+ other biometric(s) are supported. + result = getContext().getString( + R.string.biometric_or_screen_lock_app_setting_name); + } + } + break; + } + return result; + } finally { + Binder.restoreCallingIdentity(identity); + } + } } public AuthService(Context context) { @@ -442,4 +605,10 @@ public class AuthService extends SystemService { return mInjector.getAppOps(getContext()).noteOp(AppOpsManager.OP_USE_BIOMETRIC, uid, opPackageName, null /* attributionTag */, reason) == AppOpsManager.MODE_ALLOWED; } + + @BiometricAuthenticator.Modality + private static int getCredentialBackupModality(@BiometricAuthenticator.Modality int modality) { + return modality == BiometricAuthenticator.TYPE_CREDENTIAL + ? modality : (modality & ~BiometricAuthenticator.TYPE_CREDENTIAL); + } } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 00a4e43f347d..a88820988ef7 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -666,14 +666,9 @@ public class BiometricService extends SystemService { throw new SecurityException("Invalid authenticator configuration"); } - final PromptInfo promptInfo = new PromptInfo(); - promptInfo.setAuthenticators(authenticators); - try { - PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, - mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo, - opPackageName, - false /* checkDevicePolicyManager */); + final PreAuthInfo preAuthInfo = + createPreAuthInfo(opPackageName, userId, authenticators); return preAuthInfo.getCanAuthenticateResult(); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); @@ -807,6 +802,64 @@ public class BiometricService extends SystemService { return Authenticators.EMPTY_SET; } + @Override // Binder call + public int getCurrentModality( + String opPackageName, + int userId, + int callingUserId, + @Authenticators.Types int authenticators) { + + checkInternalPermission(); + + Slog.d(TAG, "getCurrentModality: User=" + userId + + ", Caller=" + callingUserId + + ", Authenticators=" + authenticators); + + if (!Utils.isValidAuthenticatorConfig(authenticators)) { + throw new SecurityException("Invalid authenticator configuration"); + } + + try { + final PreAuthInfo preAuthInfo = + createPreAuthInfo(opPackageName, userId, authenticators); + return preAuthInfo.getPreAuthenticateStatus().first; + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + return BiometricAuthenticator.TYPE_NONE; + } + } + + @Override // Binder call + public int getSupportedModalities(@Authenticators.Types int authenticators) { + checkInternalPermission(); + + Slog.d(TAG, "getSupportedModalities: Authenticators=" + authenticators); + + if (!Utils.isValidAuthenticatorConfig(authenticators)) { + throw new SecurityException("Invalid authenticator configuration"); + } + + @BiometricAuthenticator.Modality int modality = + Utils.isCredentialRequested(authenticators) + ? BiometricAuthenticator.TYPE_CREDENTIAL + : BiometricAuthenticator.TYPE_NONE; + + if (Utils.isBiometricRequested(authenticators)) { + @Authenticators.Types final int requestedStrength = + Utils.getPublicBiometricStrength(authenticators); + + // Add modalities of all biometric sensors that meet the authenticator requirements. + for (final BiometricSensor sensor : mSensors) { + @Authenticators.Types final int sensorStrength = sensor.getCurrentStrength(); + if (Utils.isAtLeastStrength(sensorStrength, requestedStrength)) { + modality |= sensor.modality; + } + } + } + + return modality; + } + @Override protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) { @@ -845,6 +898,19 @@ public class BiometricService extends SystemService { "Must have USE_BIOMETRIC_INTERNAL permission"); } + @NonNull + private PreAuthInfo createPreAuthInfo( + @NonNull String opPackageName, + int userId, + @Authenticators.Types int authenticators) throws RemoteException { + + final PromptInfo promptInfo = new PromptInfo(); + promptInfo.setAuthenticators(authenticators); + + return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors, + userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */); + } + /** * Class for injecting dependencies into BiometricService. * TODO(b/141025588): Replace with a dependency injection framework (e.g. Guice, Dagger). diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index 5cd0bbfa4500..d9e21a83e45a 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -153,6 +153,16 @@ public class Utils { /** * Checks if any of the publicly defined strengths are set. * + * @param authenticators composed of one or more values from {@link Authenticators} + * @return true if biometric authentication is allowed. + */ + static boolean isBiometricRequested(@Authenticators.Types int authenticators) { + return getPublicBiometricStrength(authenticators) != 0; + } + + /** + * Checks if any of the publicly defined strengths are set. + * * @param promptInfo should be first processed by * {@link #combineAuthenticatorBundles(PromptInfo)} * @return true if biometric authentication is allowed. diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 42fcd4460386..088249e81171 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -52,6 +52,7 @@ import android.os.ServiceManager; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.provider.DeviceConfig; import android.provider.Settings; import android.text.TextUtils; import android.util.Slog; @@ -59,6 +60,7 @@ import android.util.SparseArray; import android.view.autofill.AutofillManagerInternal; import android.widget.Toast; +import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.UiThread; @@ -163,6 +165,10 @@ public class ClipboardService extends SystemService { private static final boolean IS_EMULATOR = SystemProperties.getBoolean("ro.kernel.qemu", false); + // DeviceConfig properties + private static final String PROPERTY_SHOW_ACCESS_NOTIFICATIONS = "show_access_notifications"; + private static final boolean DEFAULT_SHOW_ACCESS_NOTIFICATIONS = true; + private final ActivityManagerInternal mAmInternal; private final IUriGrantsManager mUgm; private final UriGrantsManagerInternal mUgmInternal; @@ -176,8 +182,14 @@ public class ClipboardService extends SystemService { private HostClipboardMonitor mHostClipboardMonitor = null; private Thread mHostMonitorThread = null; + @GuardedBy("mLock") private final SparseArray<PerUserClipboard> mClipboards = new SparseArray<>(); + @GuardedBy("mLock") + private boolean mShowAccessNotifications = DEFAULT_SHOW_ACCESS_NOTIFICATIONS; + + private final Object mLock = new Object(); + /** * Instantiates the clipboard. */ @@ -204,7 +216,7 @@ public class ClipboardService extends SystemService { new ClipData("host clipboard", new String[]{"text/plain"}, new ClipData.Item(contents)); - synchronized(mClipboards) { + synchronized (mLock) { setPrimaryClipInternal(getClipboard(0), clip, android.os.Process.SYSTEM_UID, null); } @@ -213,6 +225,10 @@ public class ClipboardService extends SystemService { mHostMonitorThread = new Thread(mHostClipboardMonitor); mHostMonitorThread.start(); } + + updateConfig(); + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CLIPBOARD, + getContext().getMainExecutor(), properties -> updateConfig()); } @Override @@ -222,11 +238,18 @@ public class ClipboardService extends SystemService { @Override public void onUserStopped(@NonNull TargetUser user) { - synchronized (mClipboards) { + synchronized (mLock) { mClipboards.remove(user.getUserIdentifier()); } } + private void updateConfig() { + synchronized (mLock) { + mShowAccessNotifications = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CLIPBOARD, + PROPERTY_SHOW_ACCESS_NOTIFICATIONS, DEFAULT_SHOW_ACCESS_NOTIFICATIONS); + } + } + private class ListenerInfo { final int mUid; final String mPackageName; @@ -472,7 +495,7 @@ public class ClipboardService extends SystemService { }; private PerUserClipboard getClipboard(@UserIdInt int userId) { - synchronized (mClipboards) { + synchronized (mLock) { PerUserClipboard puc = mClipboards.get(userId); if (puc == null) { puc = new PerUserClipboard(userId); @@ -849,9 +872,10 @@ public class ClipboardService extends SystemService { if (clipboard.primaryClip == null) { return; } - if (Settings.Global.getInt(getContext().getContentResolver(), - "clipboard_access_toast_enabled", 1) == 0) { - return; + synchronized (mLock) { + if (!mShowAccessNotifications) { + return; + } } // Don't notify if the app accessing the clipboard is the same as the current owner. if (UserHandle.isSameApp(uid, clipboard.primaryClipUid)) { diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java index 43d9ade67a11..4f6b5301e56f 100644 --- a/services/core/java/com/android/server/connectivity/DnsManager.java +++ b/services/core/java/com/android/server/connectivity/DnsManager.java @@ -19,6 +19,8 @@ package com.android.server.connectivity; import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE_FALLBACK; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; +import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE; +import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS; import static android.provider.Settings.Global.DNS_RESOLVER_MAX_SAMPLES; import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES; import static android.provider.Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS; @@ -147,17 +149,18 @@ public class DnsManager { } public static class PrivateDnsValidationUpdate { - final public int netId; - final public InetAddress ipAddress; - final public String hostname; - final public boolean validated; + public final int netId; + public final InetAddress ipAddress; + public final String hostname; + // Refer to IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_*. + public final int validationResult; public PrivateDnsValidationUpdate(int netId, InetAddress ipAddress, - String hostname, boolean validated) { + String hostname, int validationResult) { this.netId = netId; this.ipAddress = ipAddress; this.hostname = hostname; - this.validated = validated; + this.validationResult = validationResult; } } @@ -216,10 +219,13 @@ public class DnsManager { if (!mValidationMap.containsKey(p)) { return; } - if (update.validated) { + if (update.validationResult == VALIDATION_RESULT_SUCCESS) { mValidationMap.put(p, ValidationStatus.SUCCEEDED); - } else { + } else if (update.validationResult == VALIDATION_RESULT_FAILURE) { mValidationMap.put(p, ValidationStatus.FAILED); + } else { + Log.e(TAG, "Unknown private dns validation operation=" + + update.validationResult); } } diff --git a/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java b/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java index 816bf2be0d69..0f5400d0f8e6 100644 --- a/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java +++ b/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java @@ -27,7 +27,7 @@ import android.net.QosSession; import android.os.IBinder; import android.os.RemoteException; import android.telephony.data.EpsBearerQosSessionAttributes; -import android.util.Slog; +import android.util.Log; import java.util.Objects; @@ -175,18 +175,14 @@ class QosCallbackAgentConnection implements IBinder.DeathRecipient { } private static void log(@NonNull final String msg) { - Slog.d(TAG, msg); + Log.d(TAG, msg); } private static void logw(@NonNull final String msg) { - Slog.w(TAG, msg); + Log.w(TAG, msg); } private static void loge(@NonNull final String msg, final Throwable t) { - Slog.e(TAG, msg, t); - } - - private static void logwtf(@NonNull final String msg) { - Slog.wtf(TAG, msg); + Log.e(TAG, msg, t); } } diff --git a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java index 7ef315c469ae..8bda5323e4f8 100644 --- a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java +++ b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java @@ -29,7 +29,7 @@ import android.os.IBinder; import android.telephony.data.EpsBearerQosSessionAttributes; import android.util.Log; -import com.android.internal.util.CollectionUtils; +import com.android.net.module.util.CollectionUtils; import com.android.server.ConnectivityService; import java.util.ArrayList; @@ -156,12 +156,13 @@ public class QosCallbackTracker { private void handleUnregisterCallback(@NonNull final IBinder binder, final boolean sendToNetworkAgent) { - final QosCallbackAgentConnection agentConnection = - CollectionUtils.find(mConnections, c -> c.getBinder().equals(binder)); - if (agentConnection == null) { - logw("handleUnregisterCallback: agentConnection is null"); + final int connIndex = + CollectionUtils.indexOf(mConnections, c -> c.getBinder().equals(binder)); + if (connIndex < 0) { + logw("handleUnregisterCallback: no matching agentConnection"); return; } + final QosCallbackAgentConnection agentConnection = mConnections.get(connIndex); if (DBG) { log("handleUnregisterCallback: unregister " @@ -226,10 +227,10 @@ public class QosCallbackTracker { * @param network the network that was released */ public void handleNetworkReleased(@Nullable final Network network) { - final List<QosCallbackAgentConnection> connections = - CollectionUtils.filter(mConnections, ac -> ac.getNetwork().equals(network)); - - for (final QosCallbackAgentConnection agentConnection : connections) { + // Iterate in reverse order as agent connections will be removed when unregistering + for (int i = mConnections.size() - 1; i >= 0; i--) { + final QosCallbackAgentConnection agentConnection = mConnections.get(i); + if (!agentConnection.getNetwork().equals(network)) continue; agentConnection.sendEventQosCallbackError( QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED); @@ -247,15 +248,14 @@ public class QosCallbackTracker { @NonNull final String logPrefix, @NonNull final AgentConnectionAction action) { mConnectivityServiceHandler.post(() -> { - final QosCallbackAgentConnection ac = - CollectionUtils.find(mConnections, + final int acIndex = CollectionUtils.indexOf(mConnections, c -> c.getAgentCallbackId() == qosCallbackId); - if (ac == null) { + if (acIndex == -1) { loge(logPrefix + ": " + qosCallbackId + " missing callback id"); return; } - action.execute(ac); + action.execute(mConnections.get(acIndex)); }); } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 67f495a455fb..2e61ae1b3483 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -101,7 +101,12 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.security.Credentials; -import android.security.KeyStore; +import android.security.KeyStore2; +import android.security.keystore.AndroidKeyStoreProvider; +import android.security.keystore.KeyProperties; +import android.system.keystore2.Domain; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyPermission; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; @@ -132,6 +137,12 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -157,6 +168,7 @@ public class Vpn { private static final String TAG = "Vpn"; private static final String VPN_PROVIDER_NAME_BASE = "VpnNetworkProvider:"; private static final boolean LOGD = true; + private static final String ANDROID_KEYSTORE_PROVIDER = "AndroidKeyStore"; // Length of time (in milliseconds) that an app hosting an always-on VPN is placed on // the device idle allowlist during service launch and VPN bootstrap. @@ -216,6 +228,13 @@ public class Vpn { private final Ikev2SessionCreator mIkev2SessionCreator; private final UserManager mUserManager; + private final VpnProfileStore mVpnProfileStore; + + @VisibleForTesting + VpnProfileStore getVpnProfileStore() { + return mVpnProfileStore; + } + /** * Whether to keep the connection active after rebooting, or upgrading or reinstalling. This * only applies to {@link VpnService} connections. @@ -393,24 +412,25 @@ public class Vpn { } public Vpn(Looper looper, Context context, INetworkManagementService netService, INetd netd, - @UserIdInt int userId, @NonNull KeyStore keyStore) { - this(looper, context, new Dependencies(), netService, netd, userId, keyStore, + @UserIdInt int userId, VpnProfileStore vpnProfileStore) { + this(looper, context, new Dependencies(), netService, netd, userId, vpnProfileStore, new SystemServices(context), new Ikev2SessionCreator()); } @VisibleForTesting public Vpn(Looper looper, Context context, Dependencies deps, INetworkManagementService netService, INetd netd, @UserIdInt int userId, - @NonNull KeyStore keyStore) { - this(looper, context, deps, netService, netd, userId, keyStore, + VpnProfileStore vpnProfileStore) { + this(looper, context, deps, netService, netd, userId, vpnProfileStore, new SystemServices(context), new Ikev2SessionCreator()); } @VisibleForTesting protected Vpn(Looper looper, Context context, Dependencies deps, INetworkManagementService netService, INetd netd, - int userId, @NonNull KeyStore keyStore, SystemServices systemServices, + int userId, VpnProfileStore vpnProfileStore, SystemServices systemServices, Ikev2SessionCreator ikev2SessionCreator) { + mVpnProfileStore = vpnProfileStore; mContext = context; mConnectivityManager = mContext.getSystemService(ConnectivityManager.class); mUserIdContext = context.createContextAsUser(UserHandle.of(userId), 0 /* flags */); @@ -446,7 +466,7 @@ public class Vpn { mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED); mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE)); - loadAlwaysOnPackage(keyStore); + loadAlwaysOnPackage(); } /** @@ -567,11 +587,9 @@ public class Vpn { * </ul> * * @param packageName the canonical package name of the VPN app - * @param keyStore the keystore instance to use for checking if the app has a Platform VPN - * profile installed. * @return {@code true} if and only if the VPN app exists and supports always-on mode */ - public boolean isAlwaysOnPackageSupported(String packageName, @NonNull KeyStore keyStore) { + public boolean isAlwaysOnPackageSupported(String packageName) { enforceSettingsPermission(); if (packageName == null) { @@ -580,7 +598,7 @@ public class Vpn { final long oldId = Binder.clearCallingIdentity(); try { - if (getVpnProfilePrivileged(packageName, keyStore) != null) { + if (getVpnProfilePrivileged(packageName) != null) { return true; } } finally { @@ -632,17 +650,15 @@ public class Vpn { * @param packageName the package to designate as always-on VPN supplier. * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting. * @param lockdownAllowlist packages to be allowed from lockdown. - * @param keyStore the Keystore instance to use for checking of PlatformVpnProfile(s) * @return {@code true} if the package has been set as always-on, {@code false} otherwise. */ public synchronized boolean setAlwaysOnPackage( @Nullable String packageName, boolean lockdown, - @Nullable List<String> lockdownAllowlist, - @NonNull KeyStore keyStore) { + @Nullable List<String> lockdownAllowlist) { enforceControlPermissionOrInternalCaller(); - if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownAllowlist, keyStore)) { + if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownAllowlist)) { saveAlwaysOnPackage(); return true; } @@ -659,13 +675,12 @@ public class Vpn { * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting. * @param lockdownAllowlist packages to be allowed to bypass lockdown. This is only used if * {@code lockdown} is {@code true}. Packages must not contain commas. - * @param keyStore the system keystore instance to check for profiles * @return {@code true} if the package has been set as always-on, {@code false} otherwise. */ @GuardedBy("this") private boolean setAlwaysOnPackageInternal( @Nullable String packageName, boolean lockdown, - @Nullable List<String> lockdownAllowlist, @NonNull KeyStore keyStore) { + @Nullable List<String> lockdownAllowlist) { if (VpnConfig.LEGACY_VPN.equals(packageName)) { Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on."); return false; @@ -684,7 +699,7 @@ public class Vpn { final VpnProfile profile; final long oldId = Binder.clearCallingIdentity(); try { - profile = getVpnProfilePrivileged(packageName, keyStore); + profile = getVpnProfilePrivileged(packageName); } finally { Binder.restoreCallingIdentity(oldId); } @@ -759,7 +774,7 @@ public class Vpn { /** Load the always-on package and lockdown config from Settings. */ @GuardedBy("this") - private void loadAlwaysOnPackage(@NonNull KeyStore keyStore) { + private void loadAlwaysOnPackage() { final long token = Binder.clearCallingIdentity(); try { final String alwaysOnPackage = mSystemServices.settingsSecureGetStringForUser( @@ -771,7 +786,7 @@ public class Vpn { final List<String> allowedPackages = TextUtils.isEmpty(allowlistString) ? Collections.emptyList() : Arrays.asList(allowlistString.split(",")); setAlwaysOnPackageInternal( - alwaysOnPackage, alwaysOnLockdown, allowedPackages, keyStore); + alwaysOnPackage, alwaysOnLockdown, allowedPackages); } finally { Binder.restoreCallingIdentity(token); } @@ -780,11 +795,10 @@ public class Vpn { /** * Starts the currently selected always-on VPN * - * @param keyStore the keyStore instance for looking up PlatformVpnProfile(s) * @return {@code true} if the service was started, the service was already connected, or there * was no always-on VPN to start. {@code false} otherwise. */ - public boolean startAlwaysOnVpn(@NonNull KeyStore keyStore) { + public boolean startAlwaysOnVpn() { final String alwaysOnPackage; synchronized (this) { alwaysOnPackage = getAlwaysOnPackage(); @@ -793,8 +807,8 @@ public class Vpn { return true; } // Remove always-on VPN if it's not supported. - if (!isAlwaysOnPackageSupported(alwaysOnPackage, keyStore)) { - setAlwaysOnPackage(null, false, null, keyStore); + if (!isAlwaysOnPackageSupported(alwaysOnPackage)) { + setAlwaysOnPackage(null, false, null); return false; } // Skip if the service is already established. This isn't bulletproof: it's not bound @@ -808,10 +822,9 @@ public class Vpn { final long oldId = Binder.clearCallingIdentity(); try { // Prefer VPN profiles, if any exist. - VpnProfile profile = getVpnProfilePrivileged(alwaysOnPackage, keyStore); + VpnProfile profile = getVpnProfilePrivileged(alwaysOnPackage); if (profile != null) { - startVpnProfilePrivileged(profile, alwaysOnPackage, - null /* keyStore for private key retrieval - unneeded */); + startVpnProfilePrivileged(profile, alwaysOnPackage); // If the above startVpnProfilePrivileged() call returns, the Ikev2VpnProfile was // correctly parsed, and the VPN has started running in a different thread. The only @@ -2013,27 +2026,83 @@ public class Vpn { * secondary thread to perform connection work, returning quickly. * * Should only be called to respond to Binder requests as this enforces caller permission. Use - * {@link #startLegacyVpnPrivileged(VpnProfile, KeyStore, Network, LinkProperties)} to skip the + * {@link #startLegacyVpnPrivileged(VpnProfile, Network, LinkProperties)} to skip the * permission check only when the caller is trusted (or the call is initiated by the system). */ - public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, @Nullable Network underlying, + public void startLegacyVpn(VpnProfile profile, @Nullable Network underlying, LinkProperties egress) { enforceControlPermission(); final long token = Binder.clearCallingIdentity(); try { - startLegacyVpnPrivileged(profile, keyStore, underlying, egress); + startLegacyVpnPrivileged(profile, underlying, egress); } finally { Binder.restoreCallingIdentity(token); } } + private String makeKeystoreEngineGrantString(String alias) { + if (alias == null) { + return null; + } + // If Keystore 2.0 is not enabled the legacy private key prefix is used. + if (!AndroidKeyStoreProvider.isKeystore2Enabled()) { + return Credentials.USER_PRIVATE_KEY + alias; + } + final KeyStore2 keystore2 = KeyStore2.getInstance(); + + KeyDescriptor key = new KeyDescriptor(); + key.domain = Domain.APP; + key.nspace = KeyProperties.NAMESPACE_APPLICATION; + key.alias = alias; + key.blob = null; + + final int grantAccessVector = KeyPermission.USE | KeyPermission.GET_INFO; + + try { + // The native vpn daemon is running as VPN_UID. This tells Keystore 2.0 + // to allow a process running with this UID to access the key designated by + // the KeyDescriptor `key`. `grant` returns a new KeyDescriptor with a grant + // identifier. This identifier needs to be communicated to the vpn daemon. + key = keystore2.grant(key, android.os.Process.VPN_UID, grantAccessVector); + } catch (android.security.KeyStoreException e) { + Log.e(TAG, "Failed to get grant for keystore key.", e); + throw new IllegalStateException("Failed to get grant for keystore key.", e); + } + + // Turn the grant identifier into a string as understood by the keystore boringssl engine + // in system/security/keystore-engine. + return KeyStore2.makeKeystoreEngineGrantString(key.nspace); + } + + private String getCaCertificateFromKeystoreAsPem(@NonNull KeyStore keystore, + @NonNull String alias) + throws KeyStoreException, IOException, CertificateEncodingException { + if (keystore.isCertificateEntry(alias)) { + final Certificate cert = keystore.getCertificate(alias); + if (cert == null) return null; + return new String(Credentials.convertToPem(cert), StandardCharsets.UTF_8); + } else { + final Certificate[] certs = keystore.getCertificateChain(alias); + // If there is none or one entry it means there is no CA entry associated with this + // alias. + if (certs == null || certs.length <= 1) { + return null; + } + // If this is not a (pure) certificate entry, then there is a user certificate which + // will be included at the beginning of the certificate chain. But the caller of this + // function does not expect this certificate to be included, so we cut it off. + return new String(Credentials.convertToPem( + Arrays.copyOfRange(certs, 1, certs.length)), StandardCharsets.UTF_8); + } + } + /** - * Like {@link #startLegacyVpn(VpnProfile, KeyStore, Network, LinkProperties)}, but does not + * Like {@link #startLegacyVpn(VpnProfile, Network, LinkProperties)}, but does not * check permissions under the assumption that the caller is the system. * * Callers are responsible for checking permissions if needed. */ - public void startLegacyVpnPrivileged(VpnProfile profile, KeyStore keyStore, + public void startLegacyVpnPrivileged(VpnProfile profile, @Nullable Network underlying, @NonNull LinkProperties egress) { UserInfo user = mUserManager.getUserInfo(mUserId); if (user.isRestricted() || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN, @@ -2050,18 +2119,27 @@ public class Vpn { String userCert = ""; String caCert = ""; String serverCert = ""; - if (!profile.ipsecUserCert.isEmpty()) { - privateKey = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert; - byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert); - userCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8); - } - if (!profile.ipsecCaCert.isEmpty()) { - byte[] value = keyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert); - caCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8); - } - if (!profile.ipsecServerCert.isEmpty()) { - byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecServerCert); - serverCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8); + + try { + final KeyStore keystore = KeyStore.getInstance(ANDROID_KEYSTORE_PROVIDER); + keystore.load(null); + if (!profile.ipsecUserCert.isEmpty()) { + privateKey = profile.ipsecUserCert; + final Certificate cert = keystore.getCertificate(profile.ipsecUserCert); + userCert = (cert == null) ? null + : new String(Credentials.convertToPem(cert), StandardCharsets.UTF_8); + } + if (!profile.ipsecCaCert.isEmpty()) { + caCert = getCaCertificateFromKeystoreAsPem(keystore, profile.ipsecCaCert); + } + if (!profile.ipsecServerCert.isEmpty()) { + final Certificate cert = keystore.getCertificate(profile.ipsecServerCert); + serverCert = (cert == null) ? null + : new String(Credentials.convertToPem(cert), StandardCharsets.UTF_8); + } + } catch (CertificateException | KeyStoreException | IOException + | NoSuchAlgorithmException e) { + throw new IllegalStateException("Failed to load credentials from AndroidKeyStore", e); } if (userCert == null || caCert == null || serverCert == null) { throw new IllegalStateException("Cannot load credentials"); @@ -2082,7 +2160,7 @@ public class Vpn { // Start VPN profile profile.setAllowedAlgorithms(Ikev2VpnProfile.DEFAULT_ALGORITHMS); - startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore); + startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN); return; case VpnProfile.TYPE_IKEV2_IPSEC_PSK: // Ikev2VpnProfiles expect a base64-encoded preshared key. @@ -2091,7 +2169,7 @@ public class Vpn { // Start VPN profile profile.setAllowedAlgorithms(Ikev2VpnProfile.DEFAULT_ALGORITHMS); - startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore); + startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN); return; case VpnProfile.TYPE_L2TP_IPSEC_PSK: racoon = new String[] { @@ -2101,8 +2179,8 @@ public class Vpn { break; case VpnProfile.TYPE_L2TP_IPSEC_RSA: racoon = new String[] { - iface, profile.server, "udprsa", privateKey, userCert, - caCert, serverCert, "1701", + iface, profile.server, "udprsa", makeKeystoreEngineGrantString(privateKey), + userCert, caCert, serverCert, "1701", }; break; case VpnProfile.TYPE_IPSEC_XAUTH_PSK: @@ -2113,8 +2191,8 @@ public class Vpn { break; case VpnProfile.TYPE_IPSEC_XAUTH_RSA: racoon = new String[] { - iface, profile.server, "xauthrsa", privateKey, userCert, - caCert, serverCert, profile.username, profile.password, "", gateway, + iface, profile.server, "xauthrsa", makeKeystoreEngineGrantString(privateKey), + userCert, caCert, serverCert, profile.username, profile.password, "", gateway, }; break; case VpnProfile.TYPE_IPSEC_HYBRID_RSA: @@ -3049,14 +3127,12 @@ public class Vpn { * * @param packageName the package name of the app provisioning this profile * @param profile the profile to be stored and provisioned - * @param keyStore the System keystore instance to save VPN profiles * @returns whether or not the app has already been granted user consent */ public synchronized boolean provisionVpnProfile( - @NonNull String packageName, @NonNull VpnProfile profile, @NonNull KeyStore keyStore) { + @NonNull String packageName, @NonNull VpnProfile profile) { checkNotNull(packageName, "No package name provided"); checkNotNull(profile, "No profile provided"); - checkNotNull(keyStore, "KeyStore missing"); verifyCallingUidAndPackage(packageName); enforceNotRestrictedUser(); @@ -3075,11 +3151,9 @@ public class Vpn { // Permissions checked during startVpnProfile() Binder.withCleanCallingIdentity( () -> { - keyStore.put( + getVpnProfileStore().put( getProfileNameForPackage(packageName), - encodedProfile, - Process.SYSTEM_UID, - 0 /* flags */); + encodedProfile); }); // TODO: if package has CONTROL_VPN, grant the ACTIVATE_PLATFORM_VPN appop. @@ -3097,12 +3171,10 @@ public class Vpn { * Deletes an app-provisioned VPN profile. * * @param packageName the package name of the app provisioning this profile - * @param keyStore the System keystore instance to save VPN profiles */ public synchronized void deleteVpnProfile( - @NonNull String packageName, @NonNull KeyStore keyStore) { + @NonNull String packageName) { checkNotNull(packageName, "No package name provided"); - checkNotNull(keyStore, "KeyStore missing"); verifyCallingUidAndPackage(packageName); enforceNotRestrictedUser(); @@ -3114,13 +3186,13 @@ public class Vpn { if (isCurrentIkev2VpnLocked(packageName)) { if (mAlwaysOn) { // Will transitively call prepareInternal(VpnConfig.LEGACY_VPN). - setAlwaysOnPackage(null, false, null, keyStore); + setAlwaysOnPackage(null, false, null); } else { prepareInternal(VpnConfig.LEGACY_VPN); } } - keyStore.delete(getProfileNameForPackage(packageName), Process.SYSTEM_UID); + getVpnProfileStore().remove(getProfileNameForPackage(packageName)); }); } @@ -3132,13 +3204,13 @@ public class Vpn { */ @VisibleForTesting @Nullable - VpnProfile getVpnProfilePrivileged(@NonNull String packageName, @NonNull KeyStore keyStore) { + VpnProfile getVpnProfilePrivileged(@NonNull String packageName) { if (!mDeps.isCallerSystem()) { Log.wtf(TAG, "getVpnProfilePrivileged called as non-System UID "); return null; } - final byte[] encoded = keyStore.get(getProfileNameForPackage(packageName)); + final byte[] encoded = getVpnProfileStore().get(getProfileNameForPackage(packageName)); if (encoded == null) return null; return VpnProfile.decode("" /* Key unused */, encoded); @@ -3152,12 +3224,10 @@ public class Vpn { * will not match during appop checks. * * @param packageName the package name of the app provisioning this profile - * @param keyStore the System keystore instance to retrieve VPN profiles */ public synchronized void startVpnProfile( - @NonNull String packageName, @NonNull KeyStore keyStore) { + @NonNull String packageName) { checkNotNull(packageName, "No package name provided"); - checkNotNull(keyStore, "KeyStore missing"); enforceNotRestrictedUser(); @@ -3168,18 +3238,17 @@ public class Vpn { Binder.withCleanCallingIdentity( () -> { - final VpnProfile profile = getVpnProfilePrivileged(packageName, keyStore); + final VpnProfile profile = getVpnProfilePrivileged(packageName); if (profile == null) { throw new IllegalArgumentException("No profile found for " + packageName); } - startVpnProfilePrivileged(profile, packageName, - null /* keyStore for private key retrieval - unneeded */); + startVpnProfilePrivileged(profile, packageName); }); } private synchronized void startVpnProfilePrivileged( - @NonNull VpnProfile profile, @NonNull String packageName, @Nullable KeyStore keyStore) { + @NonNull VpnProfile profile, @NonNull String packageName) { // Make sure VPN is prepared. This method can be called by user apps via startVpnProfile(), // by the Setting app via startLegacyVpn(), or by ConnectivityService via // startAlwaysOnVpn(), so this is the common place to prepare the VPN. This also has the @@ -3210,7 +3279,7 @@ public class Vpn { case VpnProfile.TYPE_IKEV2_IPSEC_PSK: case VpnProfile.TYPE_IKEV2_IPSEC_RSA: mVpnRunner = - new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile, keyStore)); + new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile)); mVpnRunner.start(); break; default: @@ -3218,7 +3287,7 @@ public class Vpn { Log.d(TAG, "Unknown VPN profile type: " + profile.type); break; } - } catch (IOException | GeneralSecurityException e) { + } catch (GeneralSecurityException e) { // Reset mConfig mConfig = null; diff --git a/services/core/java/com/android/server/connectivity/VpnProfileStore.java b/services/core/java/com/android/server/connectivity/VpnProfileStore.java new file mode 100644 index 000000000000..2f8aebf07e49 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/VpnProfileStore.java @@ -0,0 +1,77 @@ +/* + * 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.connectivity; + +import android.annotation.NonNull; +import android.security.LegacyVpnProfileStore; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Mockable indirection to the actual profile store. + * @hide + */ +public class VpnProfileStore { + /** + * Stores the profile under the alias in the profile database. Existing profiles by the + * same name will be replaced. + * @param alias The name of the profile + * @param profile The profile. + * @return true if the profile was successfully added. False otherwise. + * @hide + */ + @VisibleForTesting + public boolean put(@NonNull String alias, @NonNull byte[] profile) { + return LegacyVpnProfileStore.put(alias, profile); + } + + /** + * Retrieves a profile by the name alias from the profile database. + * @param alias Name of the profile to retrieve. + * @return The unstructured blob, that is the profile that was stored using + * LegacyVpnProfileStore#put or with + * android.security.Keystore.put(Credentials.VPN + alias). + * Returns null if no profile was found. + * @hide + */ + @VisibleForTesting + public byte[] get(@NonNull String alias) { + return LegacyVpnProfileStore.get(alias); + } + + /** + * Removes a profile by the name alias from the profile database. + * @param alias Name of the profile to be removed. + * @return True if a profile was removed. False if no such profile was found. + * @hide + */ + @VisibleForTesting + public boolean remove(@NonNull String alias) { + return LegacyVpnProfileStore.remove(alias); + } + + /** + * Lists the vpn profiles stored in the database. + * @return An array of strings representing the aliases stored in the profile database. + * The return value may be empty but never null. + * @hide + */ + @VisibleForTesting + public @NonNull String[] list(@NonNull String prefix) { + return LegacyVpnProfileStore.list(prefix); + } +} diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java index d4556ed5f9fa..1acd5d097525 100644 --- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java +++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java @@ -16,17 +16,26 @@ package com.android.server.display; -import android.content.Context; import android.hardware.devicestate.DeviceStateManager; -import android.text.TextUtils; +import android.os.Environment; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.SparseArray; import android.view.DisplayAddress; +import com.android.server.display.config.layout.Layouts; +import com.android.server.display.config.layout.XmlParser; import com.android.server.display.layout.Layout; -import java.util.Arrays; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import javax.xml.datatype.DatatypeConfigurationException; /** * Mapping from device states into {@link Layout}s. This allows us to map device @@ -39,15 +48,14 @@ class DeviceStateToLayoutMap { public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE; - // TODO - b/168208162 - Remove these when we check in static definitions for layouts - public static final int STATE_FOLDED = 100; - public static final int STATE_UNFOLDED = 101; + private static final String CONFIG_FILE_PATH = + "etc/displayconfig/display_layout_configuration.xml"; private final SparseArray<Layout> mLayoutMap = new SparseArray<>(); - DeviceStateToLayoutMap(Context context) { - mLayoutMap.append(STATE_DEFAULT, new Layout()); - loadFoldedDisplayConfig(context); + DeviceStateToLayoutMap() { + loadLayoutsFromConfig(); + createLayout(STATE_DEFAULT); } public void dumpLocked(IndentingPrintWriter ipw) { @@ -68,7 +76,7 @@ class DeviceStateToLayoutMap { return layout; } - private Layout create(int state) { + private Layout createLayout(int state) { if (mLayoutMap.contains(state)) { Slog.e(TAG, "Attempted to create a second layout for state " + state); return null; @@ -79,43 +87,37 @@ class DeviceStateToLayoutMap { return layout; } - private void loadFoldedDisplayConfig(Context context) { - final String[] strDisplayIds = context.getResources().getStringArray( - com.android.internal.R.array.config_internalFoldedPhysicalDisplayIds); - - if (strDisplayIds.length != 2 || TextUtils.isEmpty(strDisplayIds[0]) - || TextUtils.isEmpty(strDisplayIds[1])) { - Slog.w(TAG, "Folded display configuration invalid: [" + Arrays.toString(strDisplayIds) - + "]"); - return; - } + /** + * Reads display-layout-configuration files to get the layouts to use for this device. + */ + private void loadLayoutsFromConfig() { + final File configFile = Environment.buildPath( + Environment.getVendorDirectory(), CONFIG_FILE_PATH); - final long[] displayIds; - try { - displayIds = new long[] { - Long.parseLong(strDisplayIds[0]), - Long.parseLong(strDisplayIds[1]) - }; - } catch (NumberFormatException nfe) { - Slog.w(TAG, "Folded display config non numerical: " + Arrays.toString(strDisplayIds)); + if (!configFile.exists()) { return; } - final int[] foldedDeviceStates = context.getResources().getIntArray( - com.android.internal.R.array.config_foldedDeviceStates); - // Only add folded states if folded state config is not empty - if (foldedDeviceStates.length == 0) { - return; + Slog.i(TAG, "Loading display layouts from " + configFile); + try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) { + final Layouts layouts = XmlParser.read(in); + if (layouts == null) { + Slog.i(TAG, "Display layout config not found: " + configFile); + return; + } + for (com.android.server.display.config.layout.Layout l : layouts.getLayout()) { + final int state = l.getState().intValue(); + final Layout layout = createLayout(state); + for (com.android.server.display.config.layout.Display d: l.getDisplay()) { + layout.createDisplayLocked( + DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()), + d.getIsDefault(), + d.getEnabled()); + } + } + } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) { + Slog.e(TAG, "Encountered an error while reading/parsing display layout config file: " + + configFile, e); } - - // Create the folded state layout - final Layout foldedLayout = create(STATE_FOLDED); - foldedLayout.createDisplayLocked( - DisplayAddress.fromPhysicalDisplayId(displayIds[0]), true /*isDefault*/); - - // Create the unfolded state layout - final Layout unfoldedLayout = create(STATE_UNFOLDED); - unfoldedLayout.createDisplayLocked( - DisplayAddress.fromPhysicalDisplayId(displayIds[1]), true /*isDefault*/); } } diff --git a/services/core/java/com/android/server/display/DisplayGroup.java b/services/core/java/com/android/server/display/DisplayGroup.java index 663883ab2825..2dcd5ccaf557 100644 --- a/services/core/java/com/android/server/display/DisplayGroup.java +++ b/services/core/java/com/android/server/display/DisplayGroup.java @@ -30,6 +30,8 @@ public class DisplayGroup { private final List<LogicalDisplay> mDisplays = new ArrayList<>(); private final int mGroupId; + private int mChangeCount; + DisplayGroup(int groupId) { mGroupId = groupId; } @@ -45,11 +47,16 @@ public class DisplayGroup { * @param display the {@link LogicalDisplay} to add to the Group */ void addDisplayLocked(LogicalDisplay display) { - if (!mDisplays.contains(display)) { + if (!containsLocked(display)) { + mChangeCount++; mDisplays.add(display); } } + boolean containsLocked(LogicalDisplay display) { + return mDisplays.contains(display); + } + /** * Removes the provided {@code display} from the Group. * @@ -57,6 +64,7 @@ public class DisplayGroup { * @return {@code true} if the {@code display} was removed; otherwise {@code false} */ boolean removeDisplayLocked(LogicalDisplay display) { + mChangeCount++; return mDisplays.remove(display); } @@ -65,6 +73,11 @@ public class DisplayGroup { return mDisplays.isEmpty(); } + /** Returns a count of the changes made to this display group. */ + int getChangeCountLocked() { + return mChangeCount; + } + /** Returns the number of {@link LogicalDisplay LogicalDisplays} in the Group. */ int getSizeLocked() { return mDisplays.size(); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 52149ee3a4dd..174d4b2fe00d 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -423,7 +423,7 @@ public final class DisplayManagerService extends SystemService { mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper()); mUiHandler = UiThread.getHandler(); mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore); - mLogicalDisplayMapper = new LogicalDisplayMapper(context, mDisplayDeviceRepo, + mLogicalDisplayMapper = new LogicalDisplayMapper(mDisplayDeviceRepo, new LogicalDisplayListener()); mDisplayModeDirector = new DisplayModeDirector(context, mHandler); mBrightnessSynchronizer = new BrightnessSynchronizer(mContext); @@ -485,13 +485,13 @@ public final class DisplayManagerService extends SystemService { synchronized (mSyncRoot) { long timeout = SystemClock.uptimeMillis() + mInjector.getDefaultDisplayDelayTimeout(); - while (mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY) == null + while (mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY) == null || mVirtualDisplayAdapter == null) { long delay = timeout - SystemClock.uptimeMillis(); if (delay <= 0) { throw new RuntimeException("Timeout waiting for default display " + "to be initialized. DefaultDisplay=" - + mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY) + + mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY) + ", mVirtualDisplayAdapter=" + mVirtualDisplayAdapter); } if (DEBUG) { @@ -549,7 +549,7 @@ public final class DisplayManagerService extends SystemService { mSystemReady = true; // Just in case the top inset changed before the system was ready. At this point, any // relevant configuration should be in place. - recordTopInsetLocked(mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY)); + recordTopInsetLocked(mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY)); updateSettingsLocked(); } @@ -617,7 +617,7 @@ public final class DisplayManagerService extends SystemService { @VisibleForTesting void setDisplayInfoOverrideFromWindowManagerInternal(int displayId, DisplayInfo info) { synchronized (mSyncRoot) { - LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display != null) { if (display.setDisplayInfoOverrideFromWindowManagerLocked(info)) { handleLogicalDisplayChangedLocked(display); @@ -632,7 +632,7 @@ public final class DisplayManagerService extends SystemService { */ private void getNonOverrideDisplayInfoInternal(int displayId, DisplayInfo outInfo) { synchronized (mSyncRoot) { - final LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display != null) { display.getNonOverrideDisplayInfoLocked(outInfo); } @@ -691,8 +691,8 @@ public final class DisplayManagerService extends SystemService { mDisplayStates.setValueAt(index, state); mDisplayBrightnesses.setValueAt(index, brightnessState); - runnable = updateDisplayStateLocked( - mLogicalDisplayMapper.getLocked(displayId).getPrimaryDisplayDeviceLocked()); + runnable = updateDisplayStateLocked(mLogicalDisplayMapper.getDisplayLocked(displayId) + .getPrimaryDisplayDeviceLocked()); } // Setting the display power state can take hundreds of milliseconds @@ -803,9 +803,9 @@ public final class DisplayManagerService extends SystemService { private DisplayInfo getDisplayInfoInternal(int displayId, int callingUid) { synchronized (mSyncRoot) { - LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display != null) { - DisplayInfo info = + final DisplayInfo info = getDisplayInfoForFrameRateOverride(display.getFrameRateOverrides(), display.getDisplayInfoLocked(), callingUid); if (info.hasAccess(callingUid) @@ -952,7 +952,7 @@ public final class DisplayManagerService extends SystemService { private void requestColorModeInternal(int displayId, int colorMode) { synchronized (mSyncRoot) { - LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display != null && display.getRequestedColorModeLocked() != colorMode) { display.setRequestedColorModeLocked(colorMode); @@ -989,7 +989,7 @@ public final class DisplayManagerService extends SystemService { mDisplayDeviceRepo.onDisplayDeviceEvent(device, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED); - LogicalDisplay display = mLogicalDisplayMapper.getLocked(device); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device); if (display != null) { return display.getDisplayIdLocked(); } @@ -1178,7 +1178,10 @@ public final class DisplayManagerService extends SystemService { private void handleLogicalDisplayRemovedLocked(@NonNull LogicalDisplay display) { final int displayId = display.getDisplayIdLocked(); - mDisplayPowerControllers.removeReturnOld(displayId).stop(); + final DisplayPowerController dpc = mDisplayPowerControllers.removeReturnOld(displayId); + if (dpc != null) { + dpc.stop(); + } mDisplayStates.delete(displayId); mDisplayBrightnesses.delete(displayId); DisplayManagerGlobal.invalidateLocalDisplayInfoCaches(); @@ -1200,10 +1203,7 @@ public final class DisplayManagerService extends SystemService { // by the display power controller (if known). DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) { - // TODO - b/170498827 The rules regarding what display state to apply to each - // display will depend on the configuration/mapping of logical displays. - // Clean up LogicalDisplay.isEnabled() mechanism once this is fixed. - final LogicalDisplay display = mLogicalDisplayMapper.getLocked(device); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device); final int state; final int displayId = display.getDisplayIdLocked(); @@ -1364,7 +1364,7 @@ public final class DisplayManagerService extends SystemService { float requestedRefreshRate, int requestedModeId, boolean preferMinimalPostProcessing, boolean inTraversal) { synchronized (mSyncRoot) { - LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display == null) { return; } @@ -1406,7 +1406,7 @@ public final class DisplayManagerService extends SystemService { private void setDisplayOffsetsInternal(int displayId, int x, int y) { synchronized (mSyncRoot) { - LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display == null) { return; } @@ -1424,7 +1424,7 @@ public final class DisplayManagerService extends SystemService { private void setDisplayScalingDisabledInternal(int displayId, boolean disable) { synchronized (mSyncRoot) { - final LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display == null) { return; } @@ -1460,7 +1460,7 @@ public final class DisplayManagerService extends SystemService { @Nullable private IBinder getDisplayToken(int displayId) { synchronized (mSyncRoot) { - final LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display != null) { final DisplayDevice device = display.getPrimaryDisplayDeviceLocked(); if (device != null) { @@ -1478,7 +1478,7 @@ public final class DisplayManagerService extends SystemService { if (token == null) { return null; } - final LogicalDisplay logicalDisplay = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId); if (logicalDisplay == null) { return null; } @@ -1611,15 +1611,16 @@ public final class DisplayManagerService extends SystemService { // Find the logical display that the display device is showing. // Certain displays only ever show their own content. - LogicalDisplay display = mLogicalDisplayMapper.getLocked(device); + LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device); if (!ownContent) { if (display != null && !display.hasContentLocked()) { // If the display does not have any content of its own, then // automatically mirror the requested logical display contents if possible. - display = mLogicalDisplayMapper.getLocked(device.getDisplayIdToMirrorLocked()); + display = mLogicalDisplayMapper.getDisplayLocked( + device.getDisplayIdToMirrorLocked()); } if (display == null) { - display = mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY); + display = mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY); } } @@ -1896,9 +1897,9 @@ public final class DisplayManagerService extends SystemService { @VisibleForTesting DisplayDeviceInfo getDisplayDeviceInfoInternal(int displayId) { synchronized (mSyncRoot) { - LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display != null) { - DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked(); + final DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked(); return displayDevice.getDisplayDeviceInfoLocked(); } return null; @@ -1908,9 +1909,9 @@ public final class DisplayManagerService extends SystemService { @VisibleForTesting int getDisplayIdToMirrorInternal(int displayId) { synchronized (mSyncRoot) { - LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display != null) { - DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked(); + final DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked(); return displayDevice.getDisplayIdToMirrorLocked(); } return Display.INVALID_DISPLAY; @@ -1992,7 +1993,8 @@ public final class DisplayManagerService extends SystemService { ArraySet<Integer> uids; synchronized (mSyncRoot) { int displayId = msg.arg1; - LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = + mLogicalDisplayMapper.getDisplayLocked(displayId); uids = display.getPendingFrameRateOverrideUids(); display.clearPendingFrameRateOverrideUids(); } @@ -2586,7 +2588,7 @@ public final class DisplayManagerService extends SystemService { @Override // Binder call public boolean isMinimalPostProcessingRequested(int displayId) { synchronized (mSyncRoot) { - return mLogicalDisplayMapper.getLocked(displayId) + return mLogicalDisplayMapper.getDisplayLocked(displayId) .getRequestedMinimalPostProcessingLocked(); } } @@ -2831,7 +2833,7 @@ public final class DisplayManagerService extends SystemService { @Override public Point getDisplayPosition(int displayId) { synchronized (mSyncRoot) { - LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display != null) { return display.getDisplayPosition(); } diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index dce6bd849953..645ca7ac33e0 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -31,7 +31,6 @@ import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.os.PowerManager; import android.os.SystemClock; import android.os.UserHandle; import android.provider.DeviceConfig; @@ -80,6 +79,8 @@ public class DisplayModeDirector { // specific display. private static final int GLOBAL_ID = -1; + private static final int INVALID_DISPLAY_MODE_ID = -1; + // The tolerance within which we consider something approximately equals. private static final float FLOAT_TOLERANCE = 0.01f; @@ -322,12 +323,30 @@ public class DisplayModeDirector { appRequestSummary.maxRefreshRate)); } - // If the application requests a given mode with preferredModeId function, it will be - // stored as baseModeId. - int baseModeId = defaultMode.getModeId(); - if (availableModes.length > 0) { + int baseModeId = INVALID_DISPLAY_MODE_ID; + + // Select the default mode if available. This is important because SurfaceFlinger + // can do only seamless switches by default. Some devices (e.g. TV) don't support + // seamless switching so the mode we select here won't be changed. + for (int availableMode : availableModes) { + if (availableMode == defaultMode.getModeId()) { + baseModeId = defaultMode.getModeId(); + break; + } + } + + // If the application requests a display mode by setting + // LayoutParams.preferredDisplayModeId, it will be the only available mode and it'll + // be stored as baseModeId. + if (baseModeId == INVALID_DISPLAY_MODE_ID && availableModes.length > 0) { baseModeId = availableModes[0]; } + + if (baseModeId == INVALID_DISPLAY_MODE_ID) { + throw new IllegalStateException("Can't select a base display mode for display " + + displayId + ". The votes are " + mVotesByDisplay.valueAt(displayId)); + } + if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE) { Display.Mode baseMode = null; for (Display.Mode mode : modes) { @@ -351,6 +370,7 @@ public class DisplayModeDirector { boolean allowGroupSwitching = mModeSwitchingType == DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS; + return new DesiredDisplayModeSpecs(baseModeId, allowGroupSwitching, new RefreshRateRange( diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 20b133ce4d0a..d9570c710b15 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -17,11 +17,11 @@ package com.android.server.display; import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.DisplayManagerInternal; import android.util.ArraySet; -import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.DisplayEventReceiver; @@ -64,12 +64,14 @@ import java.util.Objects; */ final class LogicalDisplay { private static final String TAG = "LogicalDisplay"; - private final DisplayInfo mBaseDisplayInfo = new DisplayInfo(); // The layer stack we use when the display has been blanked to prevent any // of its content from appearing. private static final int BLANK_LAYER_STACK = -1; + private static final DisplayInfo EMPTY_DISPLAY_INFO = new DisplayInfo(); + + private final DisplayInfo mBaseDisplayInfo = new DisplayInfo(); private final int mDisplayId; private final int mLayerStack; @@ -297,7 +299,7 @@ final class LogicalDisplay { // Check whether logical display has become invalid. if (!deviceRepo.containsLocked(mPrimaryDisplayDevice)) { - mPrimaryDisplayDevice = null; + setPrimaryDisplayDeviceLocked(null); return; } @@ -684,18 +686,28 @@ final class LogicalDisplay { * @param targetDisplay The display with which to swap display-devices. * @return {@code true} if the displays were swapped, {@code false} otherwise. */ - public boolean swapDisplaysLocked(@NonNull LogicalDisplay targetDisplay) { - final DisplayDevice targetDevice = targetDisplay.getPrimaryDisplayDeviceLocked(); - if (mPrimaryDisplayDevice == null || targetDevice == null) { - Slog.e(TAG, "Missing display device during swap: " + mPrimaryDisplayDevice + " , " - + targetDevice); - return false; - } + public void swapDisplaysLocked(@NonNull LogicalDisplay targetDisplay) { + final DisplayDevice oldTargetDevice = + targetDisplay.setPrimaryDisplayDeviceLocked(mPrimaryDisplayDevice); + setPrimaryDisplayDeviceLocked(oldTargetDevice); + } + + /** + * Sets the primary display device to the specified device. + * + * @param device The new device to set. + * @return The previously set display device. + */ + public DisplayDevice setPrimaryDisplayDeviceLocked(@Nullable DisplayDevice device) { + final DisplayDevice old = mPrimaryDisplayDevice; + mPrimaryDisplayDevice = device; - final DisplayDevice tmpDevice = mPrimaryDisplayDevice; - mPrimaryDisplayDevice = targetDisplay.mPrimaryDisplayDevice; - targetDisplay.mPrimaryDisplayDevice = tmpDevice; - return true; + // Reset all our display info data + mPrimaryDisplayDeviceInfo = null; + mBaseDisplayInfo.copyFrom(EMPTY_DISPLAY_INFO); + mInfo.set(null); + + return old; } /** @@ -718,8 +730,8 @@ final class LogicalDisplay { public void dumpLocked(PrintWriter pw) { pw.println("mDisplayId=" + mDisplayId); - pw.println("mLayerStack=" + mLayerStack); pw.println("mIsEnabled=" + mIsEnabled); + pw.println("mLayerStack=" + mLayerStack); pw.println("mHasContent=" + mHasContent); pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}"); pw.println("mRequestedColorMode=" + mRequestedColorMode); diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index a3ff534e336e..d6826be248df 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -16,14 +16,16 @@ package com.android.server.display; -import android.content.Context; +import android.hardware.devicestate.DeviceStateManager; import android.os.SystemProperties; import android.text.TextUtils; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; +import android.util.SparseIntArray; import android.view.Display; -import android.view.DisplayEventReceiver; +import android.view.DisplayAddress; import android.view.DisplayInfo; import com.android.server.display.layout.Layout; @@ -74,49 +76,79 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private final boolean mSingleDisplayDemoMode; /** - * List of all logical displays indexed by logical display id. + * Map of all logical displays indexed by logical display id. * Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache. * TODO: multi-display - Move the aforementioned comment? */ private final SparseArray<LogicalDisplay> mLogicalDisplays = new SparseArray<LogicalDisplay>(); - /** A mapping from logical display id to display group. */ - private final SparseArray<DisplayGroup> mDisplayIdToGroupMap = new SparseArray<>(); + /** Map of all display groups indexed by display group id. */ + private final SparseArray<DisplayGroup> mDisplayGroups = new SparseArray<>(); private final DisplayDeviceRepository mDisplayDeviceRepo; private final DeviceStateToLayoutMap mDeviceStateToLayoutMap; private final Listener mListener; - private final int[] mFoldedDeviceStates; + + /** + * Has an entry for every logical display that the rest of the system has been notified about. + * Any entry in here requires us to send a {@link LOGICAL_DISPLAY_EVENT_REMOVED} event when it + * is deleted or {@link LOGICAL_DISPLAY_EVENT_CHANGED} when it is changed. + */ + private final SparseBooleanArray mUpdatedLogicalDisplays = new SparseBooleanArray(); + + /** + * Keeps track of all the display groups that we already told other people about. IOW, if a + * display group is in this array, then we *must* send change and remove notifications for it + * because other components know about them. Also, what this array stores is a change counter + * for each group, so we know if the group itself has changes since we last sent out a + * notification. See {@link DisplayGroup#getChangeCountLocked}. + */ + private final SparseIntArray mUpdatedDisplayGroups = new SparseIntArray(); + + /** + * Array used in {@link #updateLogicalDisplaysLocked} to track events that need to be sent out. + */ + private final SparseIntArray mLogicalDisplaysToUpdate = new SparseIntArray(); + + /** + * Array used in {@link #updateLogicalDisplaysLocked} to track events that need to be sent out. + */ + private final SparseIntArray mDisplayGroupsToUpdate = new SparseIntArray(); private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; private Layout mCurrentLayout = null; - private boolean mIsFolded = false; + private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; - LogicalDisplayMapper(Context context, DisplayDeviceRepository repo, Listener listener) { + LogicalDisplayMapper(DisplayDeviceRepository repo, Listener listener) { mDisplayDeviceRepo = repo; mListener = listener; mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false); mDisplayDeviceRepo.addListener(this); - - mFoldedDeviceStates = context.getResources().getIntArray( - com.android.internal.R.array.config_foldedDeviceStates); - - mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(context); + mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(); } @Override public void onDisplayDeviceEventLocked(DisplayDevice device, int event) { switch (event) { case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_ADDED: + if (DEBUG) { + Slog.d(TAG, "Display device added: " + device.getDisplayDeviceInfoLocked()); + } handleDisplayDeviceAddedLocked(device); break; case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_CHANGED: + if (DEBUG) { + Slog.d(TAG, "Display device changed: " + device.getDisplayDeviceInfoLocked()); + } updateLogicalDisplaysLocked(); break; case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_REMOVED: + if (DEBUG) { + Slog.d(TAG, "Display device removed: " + device.getDisplayDeviceInfoLocked()); + } updateLogicalDisplaysLocked(); break; } @@ -127,11 +159,11 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { mListener.onTraversalRequested(); } - public LogicalDisplay getLocked(int displayId) { + public LogicalDisplay getDisplayLocked(int displayId) { return mLogicalDisplays.get(displayId); } - public LogicalDisplay getLocked(DisplayDevice device) { + public LogicalDisplay getDisplayLocked(DisplayDevice device) { final int count = mLogicalDisplays.size(); for (int i = 0; i < count; i++) { LogicalDisplay display = mLogicalDisplays.valueAt(i); @@ -166,16 +198,25 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } } - public DisplayGroup getDisplayGroupLocked(int groupId) { - final int size = mDisplayIdToGroupMap.size(); + public int getDisplayGroupIdFromDisplayIdLocked(int displayId) { + final LogicalDisplay display = getDisplayLocked(displayId); + if (display == null) { + return Display.INVALID_DISPLAY_GROUP; + } + + final int size = mDisplayGroups.size(); for (int i = 0; i < size; i++) { - final DisplayGroup displayGroup = mDisplayIdToGroupMap.valueAt(i); - if (displayGroup.getGroupId() == groupId) { - return displayGroup; + final DisplayGroup displayGroup = mDisplayGroups.valueAt(i); + if (displayGroup.containsLocked(display)) { + return mDisplayGroups.keyAt(i); } } - return null; + return Display.INVALID_DISPLAY_GROUP; + } + + public DisplayGroup getDisplayGroupLocked(int groupId) { + return mDisplayGroups.get(groupId); } public void dumpLocked(PrintWriter pw) { @@ -203,229 +244,334 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } void setDeviceStateLocked(int state) { - boolean folded = false; - for (int i = 0; i < mFoldedDeviceStates.length; i++) { - if (state == mFoldedDeviceStates[i]) { - folded = true; - break; - } + if (state != mDeviceState) { + resetLayoutLocked(); + mDeviceState = state; + applyLayoutLocked(); + updateLogicalDisplaysLocked(); } - setDeviceFoldedLocked(folded); } - void setDeviceFoldedLocked(boolean isFolded) { - mIsFolded = isFolded; + private void handleDisplayDeviceAddedLocked(DisplayDevice device) { + DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked(); + // Internal Displays need to have additional initialization. + // TODO: b/168208162 - This initializes a default dynamic display layout for INTERNAL + // devices, which will eventually just be a fallback in case no static layout definitions + // exist or cannot be loaded. + if (deviceInfo.type == Display.TYPE_INTERNAL) { + initializeInternalDisplayDeviceLocked(device); + } - // Until we have fully functioning state mapping, use hardcoded states based on isFolded - final int state = mIsFolded ? DeviceStateToLayoutMap.STATE_FOLDED - : DeviceStateToLayoutMap.STATE_UNFOLDED; + // Create a logical display for the new display device + LogicalDisplay display = createNewLogicalDisplayLocked( + device, Layout.assignDisplayIdLocked(false /*isDefault*/)); - if (DEBUG) { - Slog.d(TAG, "New device state: " + state); - } + applyLayoutLocked(); + updateLogicalDisplaysLocked(); + } - final Layout layout = mDeviceStateToLayoutMap.get(state); - if (layout == null) { - return; - } - final Layout.Display displayLayout = layout.getById(Display.DEFAULT_DISPLAY); - if (displayLayout == null) { - return; - } - final DisplayDevice newDefaultDevice = - mDisplayDeviceRepo.getByAddressLocked(displayLayout.getAddress()); - if (newDefaultDevice == null) { - return; - } + /** + * Updates the rest of the display system once all the changes are applied for display + * devices and logical displays. The includes releasing invalid/empty LogicalDisplays, + * creating/adjusting/removing DisplayGroups, and notifying the rest of the system of the + * relevant changes. + */ + private void updateLogicalDisplaysLocked() { + // Go through all the displays and figure out if they need to be updated. + // Loops in reverse so that displays can be removed during the loop without affecting the + // rest of the loop. + for (int i = mLogicalDisplays.size() - 1; i >= 0; i--) { + final int displayId = mLogicalDisplays.keyAt(i); + LogicalDisplay display = mLogicalDisplays.valueAt(i); - final LogicalDisplay defaultDisplay = mLogicalDisplays.get(Display.DEFAULT_DISPLAY); - mCurrentLayout = layout; + mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked()); + display.getNonOverrideDisplayInfoLocked(mTempNonOverrideDisplayInfo); - // If we're already set up accurately, return early - if (defaultDisplay.getPrimaryDisplayDeviceLocked() == newDefaultDevice) { - return; - } + display.updateLocked(mDisplayDeviceRepo); + final DisplayInfo newDisplayInfo = display.getDisplayInfoLocked(); + final boolean wasPreviouslyUpdated = mUpdatedLogicalDisplays.get(displayId); - // We need to swap the default display's display-device with the one that is supposed - // to be the default in the new layout. - final LogicalDisplay displayToSwap = getLocked(newDefaultDevice); - if (displayToSwap == null) { - Slog.w(TAG, "Canceling display swap - unexpected empty second display for: " - + newDefaultDevice); - return; - } - defaultDisplay.swapDisplaysLocked(displayToSwap); + // The display is no longer valid and needs to be removed. + if (!display.isValidLocked()) { + mUpdatedLogicalDisplays.delete(displayId); - // We ensure that the non-default Display is always forced to be off. This was likely - // already done in a previous iteration, but we do it with each swap in case something in - // the underlying LogicalDisplays changed: like LogicalDisplay recreation, for example. - defaultDisplay.setEnabled(true); - displayToSwap.setEnabled(false); + // Remove from group + final DisplayGroup displayGroup = getDisplayGroupLocked( + getDisplayGroupIdFromDisplayIdLocked(displayId)); + if (displayGroup != null) { + displayGroup.removeDisplayLocked(display); + } - // Update the world - updateLogicalDisplaysLocked(); - } + if (wasPreviouslyUpdated) { + // The display isn't actually removed from our internal data structures until + // after the notification is sent; see {@link #sendUpdatesForDisplaysLocked}. + Slog.i(TAG, "Removing display: " + displayId); + mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_REMOVED); + } else { + // This display never left this class, safe to remove without notification + mLogicalDisplays.removeAt(i); + } + continue; + + // The display is new. + } else if (!wasPreviouslyUpdated) { + Slog.i(TAG, "Adding new display: " + displayId + ": " + newDisplayInfo); + assignDisplayGroupLocked(display); + mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_ADDED); + + // Underlying displays device has changed to a different one. + } else if (!TextUtils.equals(mTempDisplayInfo.uniqueId, newDisplayInfo.uniqueId)) { + // FLAG_OWN_DISPLAY_GROUP could have changed, recalculate just in case + assignDisplayGroupLocked(display); + mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_SWAPPED); + + // Something about the display device has changed. + } else if (!mTempDisplayInfo.equals(newDisplayInfo)) { + // FLAG_OWN_DISPLAY_GROUP could have changed, recalculate just in case + assignDisplayGroupLocked(display); + mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED); + + // Display frame rate overrides changed. + } else if (!display.getPendingFrameRateOverrideUids().isEmpty()) { + mLogicalDisplaysToUpdate.put( + displayId, LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED); - private void handleDisplayDeviceAddedLocked(DisplayDevice device) { - DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked(); - boolean isDefault = (deviceInfo.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0; - if (isDefault && mLogicalDisplays.get(Display.DEFAULT_DISPLAY) != null) { - Slog.w(TAG, "Ignoring attempt to add a second default display: " + deviceInfo); - isDefault = false; + // Non-override display values changed. + } else { + // While application shouldn't know nor care about the non-overridden info, we + // still need to let WindowManager know so it can update its own internal state for + // things like display cutouts. + display.getNonOverrideDisplayInfoLocked(mTempDisplayInfo); + if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo)) { + mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED); + } + } + + mUpdatedLogicalDisplays.put(displayId, true); } - if (!isDefault && mSingleDisplayDemoMode) { - Slog.i(TAG, "Not creating a logical display for a secondary display " - + " because single display demo mode is enabled: " + deviceInfo); - return; + // Go through the groups and do the same thing. We do this after displays since group + // information can change in the previous loop. + // Loops in reverse so that groups can be removed during the loop without affecting the + // rest of the loop. + for (int i = mDisplayGroups.size() - 1; i >= 0; i--) { + final int groupId = mDisplayGroups.keyAt(i); + final DisplayGroup group = mDisplayGroups.valueAt(i); + final boolean wasPreviouslyUpdated = mUpdatedDisplayGroups.indexOfKey(groupId) < 0; + final int changeCount = group.getChangeCountLocked(); + + if (group.isEmptyLocked()) { + mUpdatedDisplayGroups.delete(groupId); + if (wasPreviouslyUpdated) { + mDisplayGroupsToUpdate.put(groupId, DISPLAY_GROUP_EVENT_REMOVED); + } + continue; + } else if (!wasPreviouslyUpdated) { + mDisplayGroupsToUpdate.put(groupId, DISPLAY_GROUP_EVENT_ADDED); + } else if (mUpdatedDisplayGroups.get(groupId) != changeCount) { + mDisplayGroupsToUpdate.put(groupId, DISPLAY_GROUP_EVENT_CHANGED); + } + mUpdatedDisplayGroups.put(groupId, changeCount); } - final int displayId = Layout.assignDisplayIdLocked(isDefault); - final int layerStack = assignLayerStackLocked(displayId); + // Send the display and display group updates in order by message type. This is important + // to ensure that addition and removal notifications happen in the right order. + sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_ADDED); + sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_REMOVED); + sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CHANGED); + sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED); + sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_ADDED); + sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_SWAPPED); + sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_CHANGED); + sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_REMOVED); + + mLogicalDisplaysToUpdate.clear(); + mDisplayGroupsToUpdate.clear(); + } - final DisplayGroup displayGroup; - final boolean addNewDisplayGroup = - isDefault || (deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0; - if (addNewDisplayGroup) { - final int groupId = assignDisplayGroupIdLocked(isDefault); - displayGroup = new DisplayGroup(groupId); - } else { - displayGroup = mDisplayIdToGroupMap.get(Display.DEFAULT_DISPLAY); - } + /** + * Send the specified message for all relevant displays in the specified display-to-message map. + */ + private void sendUpdatesForDisplaysLocked(int msg) { + for (int i = mLogicalDisplaysToUpdate.size() - 1; i >= 0; --i) { + final int currMsg = mLogicalDisplaysToUpdate.valueAt(i); + if (currMsg != msg) { + continue; + } - LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device); - display.updateDisplayGroupIdLocked(displayGroup.getGroupId()); - display.updateLocked(mDisplayDeviceRepo); - if (!display.isValidLocked()) { - // This should never happen currently. - Slog.w(TAG, "Ignoring display device because the logical display " - + "created from it was not considered valid: " + deviceInfo); - return; + final int id = mLogicalDisplaysToUpdate.keyAt(i); + mListener.onLogicalDisplayEventLocked(getDisplayLocked(id), msg); + if (msg == LOGICAL_DISPLAY_EVENT_REMOVED) { + // We wait until we sent the EVENT_REMOVED event before actually removing the + // display. + mLogicalDisplays.delete(id); + } } + } + + /** + * Send the specified message for all relevant display groups in the specified message map. + */ + private void sendUpdatesForGroupsLocked(int msg) { + for (int i = mDisplayGroupsToUpdate.size() - 1; i >= 0; --i) { + final int currMsg = mDisplayGroupsToUpdate.valueAt(i); + if (currMsg != msg) { + continue; + } - // For foldable devices, we start the internal non-default displays as disabled. - // TODO - b/168208162 - this will be removed when we recalculate the layout with each - // display-device addition. - if (mFoldedDeviceStates.length > 0 && deviceInfo.type == Display.TYPE_INTERNAL - && !isDefault) { - display.setEnabled(false); + final int id = mDisplayGroupsToUpdate.keyAt(i); + mListener.onDisplayGroupEventLocked(id, msg); + if (msg == DISPLAY_GROUP_EVENT_REMOVED) { + // We wait until we sent the EVENT_REMOVED event before actually removing the + // group. + mDisplayGroups.delete(id); + } } + } - mLogicalDisplays.put(displayId, display); - displayGroup.addDisplayLocked(display); - mDisplayIdToGroupMap.append(displayId, displayGroup); + private void assignDisplayGroupLocked(LogicalDisplay display) { + final int displayId = display.getDisplayIdLocked(); - if (addNewDisplayGroup) { - // Group added events happen before Logical Display added events. - mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(), - LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED); - } + // Get current display group data + int groupId = getDisplayGroupIdFromDisplayIdLocked(displayId); + final DisplayGroup oldGroup = getDisplayGroupLocked(groupId); - mListener.onLogicalDisplayEventLocked(display, - LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED); + // Get the new display group if a change is needed + final DisplayInfo info = display.getDisplayInfoLocked(); + final boolean needsOwnDisplayGroup = (info.flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0; + final boolean hasOwnDisplayGroup = groupId != Display.DEFAULT_DISPLAY_GROUP; + if (groupId == Display.INVALID_DISPLAY_GROUP + || hasOwnDisplayGroup != needsOwnDisplayGroup) { + groupId = assignDisplayGroupIdLocked(needsOwnDisplayGroup); + } - if (!addNewDisplayGroup) { - // Group changed events happen after Logical Display added events. - mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(), - LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED); + // Create a new group if needed + DisplayGroup newGroup = getDisplayGroupLocked(groupId); + if (newGroup == null) { + newGroup = new DisplayGroup(groupId); + mDisplayGroups.append(groupId, newGroup); + } + if (oldGroup != newGroup) { + if (oldGroup != null) { + oldGroup.removeDisplayLocked(display); + } + newGroup.addDisplayLocked(display); + display.updateDisplayGroupIdLocked(groupId); + Slog.i(TAG, "Setting new display group " + groupId + " for display " + + displayId + ", from previous group: " + + (oldGroup != null ? oldGroup.getGroupId() : "null")); } + } - if (DEBUG) { - Slog.d(TAG, "New Display added: " + display); + /** + * Resets the current layout in preparation for a new layout. Layouts can specify if some + * displays should be disabled (OFF). When switching from one layout to another, we go + * through each of the displays and make sure any displays we might have disabled are + * enabled again. + */ + private void resetLayoutLocked() { + final Layout layout = mDeviceStateToLayoutMap.get(mDeviceState); + for (int i = layout.size() - 1; i >= 0; i--) { + final Layout.Display displayLayout = layout.getAt(i); + final LogicalDisplay display = getDisplayLocked(displayLayout.getLogicalDisplayId()); + if (display != null) { + enableDisplayLocked(display, true); // Reset all displays back to enabled + } } } + /** - * Updates all existing logical displays given the current set of display devices. - * Removes invalid logical displays. Sends notifications if needed. + * Apply (or reapply) the currently selected display layout. */ - private void updateLogicalDisplaysLocked() { - for (int i = mLogicalDisplays.size() - 1; i >= 0; i--) { - final int displayId = mLogicalDisplays.keyAt(i); - LogicalDisplay display = mLogicalDisplays.valueAt(i); + private void applyLayoutLocked() { + final Layout layout = mDeviceStateToLayoutMap.get(mDeviceState); + mCurrentLayout = layout; + Slog.i(TAG, "Applying the display layout for device state(" + mDeviceState + + "): " + layout); - mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked()); - display.getNonOverrideDisplayInfoLocked(mTempNonOverrideDisplayInfo); - DisplayEventReceiver.FrameRateOverride[] frameRatesOverrides = - display.getFrameRateOverrides(); - display.updateLocked(mDisplayDeviceRepo); - final DisplayGroup changedDisplayGroup; - if (!display.isValidLocked()) { - mLogicalDisplays.removeAt(i); - final DisplayGroup displayGroup = mDisplayIdToGroupMap.removeReturnOld(displayId); - displayGroup.removeDisplayLocked(display); - - mListener.onLogicalDisplayEventLocked(display, - LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED); - - changedDisplayGroup = displayGroup; - } else if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) { - final int flags = display.getDisplayInfoLocked().flags; - final DisplayGroup defaultDisplayGroup = mDisplayIdToGroupMap.get( - Display.DEFAULT_DISPLAY); - if ((flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0) { - // The display should have its own DisplayGroup. - if (defaultDisplayGroup.removeDisplayLocked(display)) { - final int groupId = assignDisplayGroupIdLocked(false); - final DisplayGroup displayGroup = new DisplayGroup(groupId); - displayGroup.addDisplayLocked(display); - display.updateDisplayGroupIdLocked(groupId); - mDisplayIdToGroupMap.append(display.getDisplayIdLocked(), displayGroup); - mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(), - LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED); - changedDisplayGroup = defaultDisplayGroup; - } else { - changedDisplayGroup = null; - } - } else { - // The display should be a part of the default DisplayGroup. - final DisplayGroup displayGroup = mDisplayIdToGroupMap.get(displayId); - if (displayGroup != defaultDisplayGroup) { - displayGroup.removeDisplayLocked(display); - defaultDisplayGroup.addDisplayLocked(display); - display.updateDisplayGroupIdLocked(defaultDisplayGroup.getGroupId()); - mListener.onDisplayGroupEventLocked(defaultDisplayGroup.getGroupId(), - LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED); - mDisplayIdToGroupMap.put(displayId, defaultDisplayGroup); - changedDisplayGroup = displayGroup; - } else { - changedDisplayGroup = null; - } - } + // Go through each of the displays in the current layout set. + final int size = layout.size(); + for (int i = 0; i < size; i++) { + final Layout.Display displayLayout = layout.getAt(i); + + // If the underlying display-device we want to use for this display + // doesn't exist, then skip it. This can happen at startup as display-devices + // trickle in one at a time. When the new display finally shows up, the layout is + // recalculated so that the display is properly added to the current layout. + final DisplayAddress address = displayLayout.getAddress(); + final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(address); + if (device == null) { + Slog.w(TAG, "The display device (" + address + "), is not available" + + " for the display state " + mDeviceState); + continue; + } - final String oldUniqueId = mTempDisplayInfo.uniqueId; - final String newUniqueId = display.getDisplayInfoLocked().uniqueId; - final int eventMsg = TextUtils.equals(oldUniqueId, newUniqueId) - ? LOGICAL_DISPLAY_EVENT_CHANGED : LOGICAL_DISPLAY_EVENT_SWAPPED; - mListener.onLogicalDisplayEventLocked(display, eventMsg); - } else if (!display.getPendingFrameRateOverrideUids().isEmpty()) { - mListener.onLogicalDisplayEventLocked(display, - LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED); - changedDisplayGroup = null; - } else { - // While applications shouldn't know nor care about the non-overridden info, we - // still need to let WindowManager know so it can update its own internal state for - // things like display cutouts. - display.getNonOverrideDisplayInfoLocked(mTempDisplayInfo); - if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo)) { - mListener.onLogicalDisplayEventLocked(display, - LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CHANGED); - } - changedDisplayGroup = null; + // Now that we have a display-device, we need a LogicalDisplay to map it to. Find the + // right one, if it doesn't exist, create a new one. + final int logicalDisplayId = displayLayout.getLogicalDisplayId(); + LogicalDisplay newDisplay = getDisplayLocked(logicalDisplayId); + if (newDisplay == null) { + newDisplay = createNewLogicalDisplayLocked( + null /*displayDevice*/, logicalDisplayId); } - // CHANGED and REMOVED DisplayGroup events should always fire after Display events. - if (changedDisplayGroup != null) { - final int event = changedDisplayGroup.isEmptyLocked() - ? LogicalDisplayMapper.DISPLAY_GROUP_EVENT_REMOVED - : LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED; - mListener.onDisplayGroupEventLocked(changedDisplayGroup.getGroupId(), event); + // Now swap the underlying display devices between the old display and the new display + final LogicalDisplay oldDisplay = getDisplayLocked(device); + if (newDisplay != oldDisplay) { + newDisplay.swapDisplaysLocked(oldDisplay); } + enableDisplayLocked(newDisplay, displayLayout.isEnabled()); } } - private int assignDisplayGroupIdLocked(boolean isDefault) { - return isDefault ? Display.DEFAULT_DISPLAY_GROUP : mNextNonDefaultGroupId++; + + /** + * Creates a new logical display for the specified device and display Id and adds it to the list + * of logical displays. + * + * @param device The device to associate with the LogicalDisplay. + * @param displayId The display ID to give the new display. If invalid, a new ID is assigned. + * @param isDefault Indicates if we are creating the default display. + * @return The new logical display if created, null otherwise. + */ + private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) { + final int layerStack = assignLayerStackLocked(displayId); + final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device); + display.updateLocked(mDisplayDeviceRepo); + mLogicalDisplays.put(displayId, display); + enableDisplayLocked(display, device != null); + return display; + } + + private void enableDisplayLocked(LogicalDisplay display, boolean isEnabled) { + final int displayId = display.getDisplayIdLocked(); + final DisplayInfo info = display.getDisplayInfoLocked(); + + final boolean disallowSecondaryDisplay = mSingleDisplayDemoMode + && (info.type != Display.TYPE_INTERNAL); + if (isEnabled && disallowSecondaryDisplay) { + Slog.i(TAG, "Not creating a logical display for a secondary display because single" + + " display demo mode is enabled: " + display.getDisplayInfoLocked()); + isEnabled = false; + } + + display.setEnabled(isEnabled); + } + + private int assignDisplayGroupIdLocked(boolean isOwnDisplayGroup) { + return isOwnDisplayGroup ? mNextNonDefaultGroupId++ : Display.DEFAULT_DISPLAY_GROUP; + } + + private void initializeInternalDisplayDeviceLocked(DisplayDevice device) { + // We always want to make sure that our default display layout creates a logical + // display for every internal display device that is found. + // To that end, when we are notified of a new internal display, we add it to + // the default definition if it is not already there. + final Layout layoutSet = mDeviceStateToLayoutMap.get(DeviceStateToLayoutMap.STATE_DEFAULT); + final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); + final boolean isDefault = (info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0; + layoutSet.createDisplayLocked(info.address, isDefault, true /* isEnabled */); } private int assignLayerStackLocked(int displayId) { diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java index 18f39e6ade1d..ef336674df5d 100644 --- a/services/core/java/com/android/server/display/layout/Layout.java +++ b/services/core/java/com/android/server/display/layout/Layout.java @@ -57,7 +57,7 @@ public class Layout { * @return The new layout. */ public Display createDisplayLocked( - @NonNull DisplayAddress address, boolean isDefault) { + @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled) { if (contains(address)) { Slog.w(TAG, "Attempting to add second definition for display-device: " + address); return null; @@ -74,7 +74,7 @@ public class Layout { // different layouts, a logical display can be destroyed and later recreated with the // same logical display ID. final int logicalDisplayId = assignDisplayIdLocked(isDefault); - final Display layout = new Display(address, logicalDisplayId); + final Display layout = new Display(address, logicalDisplayId, isEnabled); mDisplays.add(layout); return layout; @@ -130,17 +130,25 @@ public class Layout { * Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s. */ public static class Display { + // Address of the display device to map to this display. private final DisplayAddress mAddress; + + // Logical Display ID to apply to this display. private final int mLogicalDisplayId; - Display(@NonNull DisplayAddress address, int logicalDisplayId) { + // Indicates that this display is not usable and should remain off. + private final boolean mIsEnabled; + + Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled) { mAddress = address; mLogicalDisplayId = logicalDisplayId; + mIsEnabled = isEnabled; } @Override public String toString() { - return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId + "}"; + return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId + + "(" + (mIsEnabled ? "ON" : "OFF") + ")}"; } public DisplayAddress getAddress() { @@ -150,5 +158,9 @@ public class Layout { public int getLogicalDisplayId() { return mLogicalDisplayId; } + + public boolean isEnabled() { + return mIsEnabled; + } } } diff --git a/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java b/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java deleted file mode 100644 index b082b25aea02..000000000000 --- a/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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.graphics.fonts; - -import android.annotation.NonNull; -import android.util.Slog; - -import java.io.File; -import java.io.IOException; - -/** - * A class to detect font-related native crash. - * - * <p>If a fs-verity protected file is accessed through mmap and corrupted file block is detected, - * SIGBUG signal is generated and the process will crash. To find corrupted files and remove them, - * we use a marker file to detect crash. - * <ol> - * <li>Create a marker file before reading fs-verity protected font files. - * <li>Delete the marker file after reading font files successfully. - * <li>If the marker file is found in the next process startup, it means that the process - * crashed before. We will delete font files to prevent crash loop. - * </ol> - * - * <p>Example usage: - * <pre> - * FontCrashDetector detector = new FontCrashDetector(new File("/path/to/marker_file")); - * if (detector.hasCrashed()) { - * // Do cleanup - * } - * try (FontCrashDetector.MonitoredBlock b = detector.start()) { - * // Read files - * } - * </pre> - * - * <p>This class DOES NOT detect Java exceptions. If a Java exception is thrown while monitoring - * crash, the marker file will be deleted. Creating and deleting marker files are not lightweight. - * Please use this class sparingly with caution. - */ -/* package */ final class FontCrashDetector { - - private static final String TAG = "FontCrashDetector"; - - @NonNull - private final File mMarkerFile; - - /* package */ FontCrashDetector(@NonNull File markerFile) { - mMarkerFile = markerFile; - } - - /* package */ boolean hasCrashed() { - return mMarkerFile.exists(); - } - - /* package */ void clear() { - if (!mMarkerFile.delete()) { - Slog.e(TAG, "Could not delete marker file: " + mMarkerFile); - } - } - - /** Starts crash monitoring. */ - /* package */ MonitoredBlock start() { - try { - mMarkerFile.createNewFile(); - } catch (IOException e) { - Slog.e(TAG, "Could not create marker file: " + mMarkerFile, e); - } - return new MonitoredBlock(); - } - - /** A helper class to monitor crash with try-with-resources syntax. */ - /* package */ class MonitoredBlock implements AutoCloseable { - /** Ends crash monitoring. */ - @Override - public void close() { - clear(); - } - } -} diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java index 01e839dae07a..900ec905609f 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java @@ -63,7 +63,6 @@ public final class FontManagerService extends IFontManager.Stub { private static final String TAG = "FontManagerService"; private static final String FONT_FILES_DIR = "/data/fonts/files"; - private static final String CRASH_MARKER_FILE = "/data/fonts/config/crash.txt"; @Override public FontConfig getFontConfig() { @@ -200,10 +199,6 @@ public final class FontManagerService extends IFontManager.Stub { private final Object mUpdatableFontDirLock = new Object(); @GuardedBy("mUpdatableFontDirLock") - @NonNull - private final FontCrashDetector mFontCrashDetector; - - @GuardedBy("mUpdatableFontDirLock") @Nullable private final UpdatableFontDir mUpdatableFontDir; @@ -217,7 +212,6 @@ public final class FontManagerService extends IFontManager.Stub { private FontManagerService(Context context) { mContext = context; - mFontCrashDetector = new FontCrashDetector(new File(CRASH_MARKER_FILE)); mUpdatableFontDir = createUpdatableFontDir(); initialize(); } @@ -244,19 +238,8 @@ public final class FontManagerService extends IFontManager.Stub { } return; } - if (mFontCrashDetector.hasCrashed()) { - Slog.i(TAG, "Crash detected. Clearing font updates."); - try { - mUpdatableFontDir.clearUpdates(); - } catch (SystemFontException e) { - Slog.e(TAG, "Failed to clear updates.", e); - } - mFontCrashDetector.clear(); - } - try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) { - mUpdatableFontDir.loadFontFileMap(); - updateSerializedFontMap(); - } + mUpdatableFontDir.loadFontFileMap(); + updateSerializedFontMap(); } } @@ -286,10 +269,8 @@ public final class FontManagerService extends IFontManager.Stub { FontManager.RESULT_ERROR_VERSION_MISMATCH, "The base config version is older than current."); } - try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) { - mUpdatableFontDir.update(requests); - updateSerializedFontMap(); - } + mUpdatableFontDir.update(requests); + updateSerializedFontMap(); } } @@ -300,10 +281,8 @@ public final class FontManagerService extends IFontManager.Stub { "The font updater is disabled."); } synchronized (mUpdatableFontDirLock) { - try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) { - mUpdatableFontDir.clearUpdates(); - updateSerializedFontMap(); - } + mUpdatableFontDir.clearUpdates(); + updateSerializedFontMap(); } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 8d6bcadb3e2b..18f7a068657f 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -666,8 +666,19 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mSelectRequestBuffer.process(); resetSelectRequestBuffer(); - addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this)); - addAndStartAction(new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this)); + List<HotplugDetectionAction> hotplugActions + = getActions(HotplugDetectionAction.class); + if (hotplugActions.isEmpty()) { + addAndStartAction( + new HotplugDetectionAction(HdmiCecLocalDeviceTv.this)); + } + + List<PowerStatusMonitorAction> powerStatusActions + = getActions(PowerStatusMonitorAction.class); + if (powerStatusActions.isEmpty()) { + addAndStartAction( + new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this)); + } HdmiDeviceInfo avr = getAvrDeviceInfo(); if (avr != null) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java b/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java index 8c404249cfd5..6f7473d60121 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java @@ -106,9 +106,7 @@ public final class HdmiCecStandbyModeHandler { addHandler(Constants.MESSAGE_SET_STREAM_PATH, mBystander); addHandler(Constants.MESSAGE_STANDBY, mBystander); addHandler(Constants.MESSAGE_SET_MENU_LANGUAGE, mBystander); - addHandler(Constants.MESSAGE_DEVICE_VENDOR_ID, mBystander); addHandler(Constants.MESSAGE_USER_CONTROL_RELEASED, mBystander); - addHandler(Constants.MESSAGE_REPORT_POWER_STATUS, mBystander); addHandler(Constants.MESSAGE_FEATURE_ABORT, mBystander); addHandler(Constants.MESSAGE_INACTIVE_SOURCE, mBystander); addHandler(Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS, mBystander); @@ -133,6 +131,8 @@ public final class HdmiCecStandbyModeHandler { addHandler(Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID, mBypasser); addHandler(Constants.MESSAGE_GIVE_OSD_NAME, mBypasser); addHandler(Constants.MESSAGE_SET_OSD_NAME, mBypasser); + addHandler(Constants.MESSAGE_DEVICE_VENDOR_ID, mBypasser); + addHandler(Constants.MESSAGE_REPORT_POWER_STATUS, mBypasser); addHandler(Constants.MESSAGE_USER_CONTROL_PRESSED, mUserControlProcessedHandler); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index d41f4c76861e..c0d577cd590d 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -3536,6 +3536,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub boolean didStart = false; InputBindResult res = null; + // We shows the IME when the system allows the IME focused target window to restore the + // IME visibility (e.g. switching to the app task when last time the IME is visible). + if (isTextEditor && mWindowManagerInternal.shouldRestoreImeVisibility(windowToken)) { + if (attribute != null) { + res = startInputUncheckedLocked(cs, inputContext, missingMethods, + attribute, startInputFlags, startInputReason); + showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, + SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY); + } else { + res = InputBindResult.NULL_EDITOR_INFO; + } + return res; + } + switch (softInputMode & LayoutParams.SOFT_INPUT_MASK_STATE) { case LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: if (!sameWindowFocused && (!isTextEditor || !doAutoShow)) { diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java index 3cc32bef0e67..851ea3d01085 100644 --- a/services/core/java/com/android/server/net/LockdownVpnTracker.java +++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java @@ -35,7 +35,6 @@ import android.net.Network; import android.net.NetworkInfo; import android.net.NetworkRequest; import android.os.Handler; -import android.security.KeyStore; import android.text.TextUtils; import android.util.Log; @@ -63,7 +62,6 @@ public class LockdownVpnTracker { @NonNull private final Handler mHandler; @NonNull private final Vpn mVpn; @NonNull private final VpnProfile mProfile; - @NonNull private final KeyStore mKeyStore; @NonNull private final Object mStateLock = new Object(); @@ -132,7 +130,6 @@ public class LockdownVpnTracker { public LockdownVpnTracker(@NonNull Context context, @NonNull Handler handler, - @NonNull KeyStore keyStore, @NonNull Vpn vpn, @NonNull VpnProfile profile) { mContext = Objects.requireNonNull(context); @@ -140,7 +137,6 @@ public class LockdownVpnTracker { mHandler = Objects.requireNonNull(handler); mVpn = Objects.requireNonNull(vpn); mProfile = Objects.requireNonNull(profile); - mKeyStore = Objects.requireNonNull(keyStore); mNotificationManager = mContext.getSystemService(NotificationManager.class); final Intent configIntent = new Intent(ACTION_VPN_SETTINGS); @@ -212,7 +208,7 @@ public class LockdownVpnTracker { // network is the system default. So, if the VPN is up and underlying network // (e.g., wifi) disconnects, CS will inform apps that the VPN's capabilities have // changed to match the new default network (e.g., cell). - mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, network, egressProp); + mVpn.startLegacyVpnPrivileged(mProfile, network, egressProp); } catch (IllegalStateException e) { mAcceptedEgressIface = null; Log.e(TAG, "Failed to start VPN", e); diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 5b9a11bc5a31..e0f534602dde 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -24,7 +24,6 @@ import static android.content.Intent.ACTION_UID_REMOVED; import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.EXTRA_UID; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED; import static android.net.ConnectivityManager.isNetworkTypeMobile; import static android.net.NetworkIdentity.SUBTYPE_COMBINED; import static android.net.NetworkStack.checkNetworkStackPermission; @@ -45,6 +44,7 @@ import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkStatsHistory.FIELD_ALL; import static android.net.NetworkTemplate.buildTemplateMobileWildcard; import static android.net.NetworkTemplate.buildTemplateWifiWildcard; +import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED; import static android.net.TrafficStats.KB_IN_BYTES; import static android.net.TrafficStats.MB_IN_BYTES; import static android.net.TrafficStats.UNSUPPORTED; @@ -97,7 +97,7 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkIdentity; import android.net.NetworkStack; -import android.net.NetworkState; +import android.net.NetworkStateSnapshot; import android.net.NetworkStats; import android.net.NetworkStats.NonMonotonicObserver; import android.net.NetworkStatsHistory; @@ -296,7 +296,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { /** Last states of all networks sent from ConnectivityService. */ @GuardedBy("mStatsLock") @Nullable - private NetworkState[] mLastNetworkStates = null; + private NetworkStateSnapshot[] mLastNetworkStateSnapshots = null; private final DropBoxNonMonotonicObserver mNonMonotonicObserver = new DropBoxNonMonotonicObserver(); @@ -378,8 +378,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } case MSG_UPDATE_IFACES: { // If no cached states, ignore. - if (mLastNetworkStates == null) break; - updateIfaces(mDefaultNetworks, mLastNetworkStates, mActiveIface); + if (mLastNetworkStateSnapshots == null) break; + // TODO (b/181642673): Protect mDefaultNetworks from concurrent accessing. + updateIfaces(mDefaultNetworks, mLastNetworkStateSnapshots, mActiveIface); break; } case MSG_PERFORM_POLL_REGISTER_ALERT: { @@ -967,10 +968,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } - @Override public void forceUpdateIfaces( Network[] defaultNetworks, - NetworkState[] networkStates, + NetworkStateSnapshot[] networkStates, String activeIface, UnderlyingNetworkInfo[] underlyingNetworkInfos) { checkNetworkStackPermission(mContext); @@ -1248,13 +1248,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private void updateIfaces( Network[] defaultNetworks, - NetworkState[] networkStates, + NetworkStateSnapshot[] snapshots, String activeIface) { synchronized (mStatsLock) { mWakeLock.acquire(); try { mActiveIface = activeIface; - updateIfacesLocked(defaultNetworks, networkStates); + updateIfacesLocked(defaultNetworks, snapshots); } finally { mWakeLock.release(); } @@ -1262,13 +1262,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } /** - * Inspect all current {@link NetworkState} to derive mapping from {@code iface} to {@link - * NetworkStatsHistory}. When multiple networks are active on a single {@code iface}, + * Inspect all current {@link NetworkStateSnapshot}s to derive mapping from {@code iface} to + * {@link NetworkStatsHistory}. When multiple networks are active on a single {@code iface}, * they are combined under a single {@link NetworkIdentitySet}. */ @GuardedBy("mStatsLock") - private void updateIfacesLocked(@Nullable Network[] defaultNetworks, - @NonNull NetworkState[] states) { + private void updateIfacesLocked(@NonNull Network[] defaultNetworks, + @NonNull NetworkStateSnapshot[] snapshots) { if (!mSystemReady) return; if (LOGV) Slog.v(TAG, "updateIfacesLocked()"); @@ -1283,26 +1283,24 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // Rebuild active interfaces based on connected networks mActiveIfaces.clear(); mActiveUidIfaces.clear(); - if (defaultNetworks != null) { - // Caller is ConnectivityService. Update the list of default networks. - mDefaultNetworks = defaultNetworks; - } + // Update the list of default networks. + mDefaultNetworks = defaultNetworks; - mLastNetworkStates = states; + mLastNetworkStateSnapshots = snapshots; final boolean combineSubtypeEnabled = mSettings.getCombineSubtypeEnabled(); final ArraySet<String> mobileIfaces = new ArraySet<>(); - for (NetworkState state : states) { - final boolean isMobile = isNetworkTypeMobile(state.legacyNetworkType); - final boolean isDefault = ArrayUtils.contains(mDefaultNetworks, state.network); + for (NetworkStateSnapshot snapshot : snapshots) { + final boolean isMobile = isNetworkTypeMobile(snapshot.legacyType); + final boolean isDefault = ArrayUtils.contains(mDefaultNetworks, snapshot.network); final int subType = combineSubtypeEnabled ? SUBTYPE_COMBINED - : getSubTypeForState(state); - final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state, + : getSubTypeForStateSnapshot(snapshot); + final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, snapshot, isDefault, subType); // Traffic occurring on the base interface is always counted for // both total usage and UID details. - final String baseIface = state.linkProperties.getInterfaceName(); + final String baseIface = snapshot.linkProperties.getInterfaceName(); if (baseIface != null) { findOrCreateNetworkIdentitySet(mActiveIfaces, baseIface).add(ident); findOrCreateNetworkIdentitySet(mActiveUidIfaces, baseIface).add(ident); @@ -1312,7 +1310,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // If IMS is metered, then the IMS network usage has already included VT usage. // VT is considered always metered in framework's layer. If VT is not metered // per carrier's policy, modem will report 0 usage for VT calls. - if (state.networkCapabilities.hasCapability( + if (snapshot.networkCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_IMS) && !ident.getMetered()) { // Copy the identify from IMS one but mark it as metered. @@ -1358,7 +1356,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // (or non eBPF offloaded) TX they would appear on both, however egress interface // accounting is explicitly bypassed for traffic from the clat uid. // - final List<LinkProperties> stackedLinks = state.linkProperties.getStackedLinks(); + final List<LinkProperties> stackedLinks = snapshot.linkProperties.getStackedLinks(); for (LinkProperties stackedLink : stackedLinks) { final String stackedIface = stackedLink.getInterfaceName(); if (stackedIface != null) { @@ -1381,7 +1379,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * {@link PhoneStateListener}. Otherwise, return 0 given that other networks with different * transport types do not actually fill this value. */ - private int getSubTypeForState(@NonNull NetworkState state) { + private int getSubTypeForStateSnapshot(@NonNull NetworkStateSnapshot state) { if (!state.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { return 0; } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 460b2f2bf5c6..903652ab76a5 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -2086,15 +2086,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { try { sealLocked(); - // Session that are staged, ready and not multi package will be installed during - // this boot. As such, we need populate all the fields for successful installation. - if (isMultiPackage()) { + // Session that are staged, committed and not multi package will be installed or + // restart verification during this boot. As such, we need populate all the fields + // for successful installation. + if (isMultiPackage() || !isStaged() || !isCommitted()) { return; } final PackageInstallerSession root = hasParentSessionId() ? allSessions.get(getParentSessionId()) : this; - if (root != null && root.isStagedSessionReady()) { + if (root != null) { if (isApexSession()) { validateApexInstallLocked(); } else { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index e8eec6714873..7da53b50d927 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -17993,7 +17993,9 @@ public class PackageManagerService extends IPackageManager.Stub try { makeDirRecursive(afterCodeFile.getParentFile(), 0775); if (onIncremental) { - mIncrementalManager.renameCodePath(beforeCodeFile, afterCodeFile); + // Just link files here. The stage dir will be removed when the installation + // session is completed. + mIncrementalManager.linkCodePath(beforeCodeFile, afterCodeFile); } else { Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath()); } @@ -18002,7 +18004,6 @@ public class PackageManagerService extends IPackageManager.Stub return false; } - //TODO(b/136132412): enable selinux restorecon for incremental directories if (!onIncremental && !SELinux.restoreconRecursive(afterCodeFile)) { Slog.w(TAG, "Failed to restorecon"); return false; @@ -20100,7 +20101,7 @@ public class PackageManagerService extends IPackageManager.Stub } catch (PackageManagerException pme) { Slog.e(TAG, "Error deriving application ABI", pme); throw new PrepareFailure(INSTALL_FAILED_INTERNAL_ERROR, - "Error deriving application ABI"); + "Error deriving application ABI: " + pme.getMessage()); } } diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index f84eb4437acf..a377f1c72375 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -362,6 +362,7 @@ public class ShortcutService extends IShortcutService.Stub { private List<Integer> mDirtyUserIds = new ArrayList<>(); private final AtomicBoolean mBootCompleted = new AtomicBoolean(); + private final AtomicBoolean mShutdown = new AtomicBoolean(); /** * Note we use a fine-grained lock for {@link #mUnlockedUsers} due to b/64303666. @@ -498,6 +499,12 @@ public class ShortcutService extends IShortcutService.Stub { mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, localeFilter, null, mHandler); + IntentFilter shutdownFilter = new IntentFilter(); + shutdownFilter.addAction(Intent.ACTION_SHUTDOWN); + shutdownFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); + mContext.registerReceiverAsUser(mShutdownReceiver, UserHandle.SYSTEM, + shutdownFilter, null, mHandler); + injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE); @@ -1162,6 +1169,9 @@ public class ShortcutService extends IShortcutService.Stub { if (DEBUG) { Slog.d(TAG, "saveDirtyInfo"); } + if (mShutdown.get()) { + return; + } try { synchronized (mLock) { for (int i = mDirtyUserIds.size() - 1; i >= 0; i--) { @@ -3494,6 +3504,22 @@ public class ShortcutService extends IShortcutService.Stub { } }; + private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // Since it cleans up the shortcut directory and rewrite the ShortcutPackageItems + // in odrder during saveToXml(), it could lead to shortcuts missing when shutdown. + // We need it so that it can finish up saving before shutdown. + synchronized (mLock) { + if (mHandler.hasCallbacks(mSaveDirtyInfoRunner)) { + mHandler.removeCallbacks(mSaveDirtyInfoRunner); + saveDirtyInfo(); + } + mShutdown.set(true); + } + } + }; + /** * Called when a user is unlocked. * - Check all known packages still exist, and otherwise perform cleanup. diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS index e05ef482ec08..8c1a90c13513 100644 --- a/services/core/java/com/android/server/pm/permission/OWNERS +++ b/services/core/java/com/android/server/pm/permission/OWNERS @@ -1,9 +1,7 @@ -zhanghai@google.com +include platform/frameworks/base:/core/java/android/permission/OWNERS + per-file DefaultPermissionGrantPolicy.java = hackbod@android.com per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com -per-file DefaultPermissionGrantPolicy.java = svetoslavganov@google.com per-file DefaultPermissionGrantPolicy.java = toddke@google.com per-file DefaultPermissionGrantPolicy.java = yamasani@google.com per-file DefaultPermissionGrantPolicy.java = patb@google.com -per-file DefaultPermissionGrantPolicy.java = eugenesusla@google.com -per-file DefaultPermissionGrantPolicy.java = zhanghai@google.com diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java index c9067a3a9392..b61fd8d633f6 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java @@ -33,9 +33,9 @@ import android.util.SparseArray; import com.android.internal.util.CollectionUtils; import com.android.server.pm.PackageSetting; import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState; import com.android.server.pm.verify.domain.models.DomainVerificationPkgState; import com.android.server.pm.verify.domain.models.DomainVerificationStateMap; -import com.android.server.pm.verify.domain.models.DomainVerificationUserState; import java.util.Arrays; import java.util.function.Function; @@ -169,8 +169,8 @@ public class DomainVerificationDebug { } ArraySet<String> allWebDomains = mCollector.collectAllWebDomains(pkg); - SparseArray<DomainVerificationUserState> userStates = - pkgState.getUserSelectionStates(); + SparseArray<DomainVerificationInternalUserState> userStates = + pkgState.getUserStates(); if (userId == UserHandle.USER_ALL) { int size = userStates.size(); if (size == 0) { @@ -178,13 +178,13 @@ public class DomainVerificationDebug { wasHeaderPrinted); } else { for (int index = 0; index < size; index++) { - DomainVerificationUserState userState = userStates.valueAt(index); + DomainVerificationInternalUserState userState = userStates.valueAt(index); printState(writer, pkgState, userState.getUserId(), userState, reusedSet, allWebDomains, wasHeaderPrinted); } } } else { - DomainVerificationUserState userState = userStates.get(userId); + DomainVerificationInternalUserState userState = userStates.get(userId); printState(writer, pkgState, userId, userState, reusedSet, allWebDomains, wasHeaderPrinted); } @@ -192,8 +192,9 @@ public class DomainVerificationDebug { boolean printState(@NonNull IndentingPrintWriter writer, @NonNull DomainVerificationPkgState pkgState, @UserIdInt int userId, - @Nullable DomainVerificationUserState userState, @NonNull ArraySet<String> reusedSet, - @NonNull ArraySet<String> allWebDomains, boolean wasHeaderPrinted) { + @Nullable DomainVerificationInternalUserState userState, + @NonNull ArraySet<String> reusedSet, @NonNull ArraySet<String> allWebDomains, + boolean wasHeaderPrinted) { reusedSet.clear(); reusedSet.addAll(allWebDomains); if (userState != null) { diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java index ed37fa0da01f..1721a18f4f60 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java @@ -132,6 +132,21 @@ public class DomainVerificationEnforcer { /** * Enforced when mutating user selection state inside an exposed API method. */ + public boolean assertApprovedUserStateQuerent(int callingUid, @UserIdInt int callingUserId, + @NonNull String packageName, @UserIdInt int targetUserId) throws SecurityException { + if (callingUserId != targetUserId) { + mContext.enforcePermission( + Manifest.permission.INTERACT_ACROSS_USERS, + Binder.getCallingPid(), callingUid, + "Caller is not allowed to edit other users"); + } + + return !mCallback.filterAppAccess(packageName, callingUid, targetUserId); + } + + /** + * Enforced when mutating user selection state inside an exposed API method. + */ public boolean assertApprovedUserSelector(int callingUid, @UserIdInt int callingUserId, @Nullable String packageName, @UserIdInt int targetUserId) throws SecurityException { if (callingUserId != targetUserId) { diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java index a68b3da0f0b6..0c2b4c547dae 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java @@ -48,7 +48,7 @@ import java.util.Set; import java.util.UUID; import java.util.function.Function; -public interface DomainVerificationManagerInternal extends DomainVerificationManager { +public interface DomainVerificationManagerInternal { UUID DISABLED_ID = new UUID(0, 0); @@ -69,8 +69,8 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan * during the legacy transition period. * * TODO(b/177923646): The legacy values can be removed once the Settings API changes are - * shipped. These values are not stable, so just deleting the constant and shifting others is - * fine. + * shipped. These values are not stable, so just deleting the constant and shifting others is + * fine. */ int APPROVAL_LEVEL_LEGACY_ASK = 1; @@ -84,14 +84,15 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan /** * The app has been chosen by the user through - * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)}, indictag an explicit - * choice to use this app to open an unverified domain. + * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)}, + * indicating an explicit choice to use this app to open an unverified domain. */ int APPROVAL_LEVEL_SELECTION = 2; /** * The app is approved through the digital asset link statement being hosted at the domain - * it is capturing. This is set through {@link #setDomainVerificationStatus(UUID, Set, int)} by + * it is capturing. This is set through + * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)} by * the domain verification agent on device. */ int APPROVAL_LEVEL_VERIFIED = 3; @@ -102,7 +103,7 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan * declares against the digital asset link statements before allowing it to be installed. * * The user is still able to disable instant app link handling through - * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}. + * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String, boolean)}. */ int APPROVAL_LEVEL_INSTANT_APP = 4; @@ -122,7 +123,17 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan APPROVAL_LEVEL_VERIFIED, APPROVAL_LEVEL_INSTANT_APP }) - @interface ApprovalLevel{} + @interface ApprovalLevel { + } + + /** @see DomainVerificationManager#getDomainVerificationInfo(String) */ + @Nullable + @RequiresPermission(anyOf = { + android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, + android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION + }) + DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName) + throws NameNotFoundException; /** * Generate a new domain set ID to be used for attaching new packages. @@ -173,9 +184,9 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan /** * Migrates verification state from a previous install to a new one. It is expected that the * {@link PackageSetting#getDomainSetId()} already be set to the correct value, usually from - * {@link #generateNewId()}. This will preserve {@link #STATE_SUCCESS} domains under the - * assumption that the new package will pass the same server side config as the previous - * package, as they have matching signatures. + * {@link #generateNewId()}. This will preserve {@link DomainVerificationManager#STATE_SUCCESS} + * domains under the assumption that the new package will pass the same server side config as + * the previous package, as they have matching signatures. * <p> * This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal * lock. This should never be called from within the domain verification classes themselves. @@ -229,8 +240,10 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan * tag has already been entered. * <p> * This is <b>only</b> for restore, and will override package states, ignoring if their {@link - * DomainVerificationInfo#getIdentifier()}s match. It's expected that any restored domains marked - * as success verify against the server correctly, although the verification agent may decide to + * DomainVerificationInfo#getIdentifier()}s match. It's expected that any restored domains + * marked + * as success verify against the server correctly, although the verification agent may decide + * to * re-verify them when it gets the chance. */ /* diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java index 6f2810785c60..a7a52e0cd10c 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java @@ -23,29 +23,29 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.verify.domain.DomainOwner; import android.content.pm.verify.domain.DomainSet; import android.content.pm.verify.domain.DomainVerificationInfo; +import android.content.pm.verify.domain.DomainVerificationManager; import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException; -import android.content.pm.verify.domain.DomainVerificationManagerImpl; -import android.content.pm.verify.domain.DomainVerificationUserSelection; +import android.content.pm.verify.domain.DomainVerificationUserState; import android.content.pm.verify.domain.IDomainVerificationManager; import android.os.ServiceSpecificException; import java.util.List; import java.util.UUID; -class DomainVerificationManagerStub extends IDomainVerificationManager.Stub { +public class DomainVerificationManagerStub extends IDomainVerificationManager.Stub { @NonNull - private DomainVerificationService mService; + private final DomainVerificationService mService; - DomainVerificationManagerStub(DomainVerificationService service) { + public DomainVerificationManagerStub(DomainVerificationService service) { mService = service; } @NonNull @Override - public List<String> getValidVerificationPackageNames() { + public List<String> queryValidVerificationPackageNames() { try { - return mService.getValidVerificationPackageNames(); + return mService.queryValidVerificationPackageNames(); } catch (Exception e) { throw rethrow(e); } @@ -95,10 +95,10 @@ class DomainVerificationManagerStub extends IDomainVerificationManager.Stub { @Nullable @Override - public DomainVerificationUserSelection getDomainVerificationUserSelection( + public DomainVerificationUserState getDomainVerificationUserState( String packageName, @UserIdInt int userId) { try { - return mService.getDomainVerificationUserSelection(packageName, userId); + return mService.getDomainVerificationUserState(packageName, userId); } catch (Exception e) { throw rethrow(e); } @@ -117,13 +117,13 @@ class DomainVerificationManagerStub extends IDomainVerificationManager.Stub { private RuntimeException rethrow(Exception exception) throws RuntimeException { if (exception instanceof InvalidDomainSetException) { - int packedErrorCode = DomainVerificationManagerImpl.ERROR_INVALID_DOMAIN_SET; + int packedErrorCode = DomainVerificationManager.ERROR_INVALID_DOMAIN_SET; packedErrorCode |= ((InvalidDomainSetException) exception).getReason() << 16; return new ServiceSpecificException(packedErrorCode, ((InvalidDomainSetException) exception).getPackageName()); } else if (exception instanceof NameNotFoundException) { return new ServiceSpecificException( - DomainVerificationManagerImpl.ERROR_NAME_NOT_FOUND); + DomainVerificationManager.ERROR_NAME_NOT_FOUND); } else if (exception instanceof RuntimeException) { return (RuntimeException) exception; } else { diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java index c864b2937f6b..abb8d2fb6e1e 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java @@ -27,9 +27,9 @@ import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import com.android.server.pm.SettingsXml; +import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState; import com.android.server.pm.verify.domain.models.DomainVerificationPkgState; import com.android.server.pm.verify.domain.models.DomainVerificationStateMap; -import com.android.server.pm.verify.domain.models.DomainVerificationUserState; import org.xmlpull.v1.XmlPullParserException; @@ -157,7 +157,7 @@ public class DomainVerificationPersistence { UUID id = UUID.fromString(idString); final ArrayMap<String, Integer> stateMap = new ArrayMap<>(); - final SparseArray<DomainVerificationUserState> userStates = new SparseArray<>(); + final SparseArray<DomainVerificationInternalUserState> userStates = new SparseArray<>(); SettingsXml.ChildSection child = section.children(); while (child.moveToNext()) { @@ -176,10 +176,10 @@ public class DomainVerificationPersistence { } private static void readUserStates(@NonNull SettingsXml.ReadSection section, - @NonNull SparseArray<DomainVerificationUserState> userStates) { + @NonNull SparseArray<DomainVerificationInternalUserState> userStates) { SettingsXml.ChildSection child = section.children(); while (child.moveToNext(TAG_USER_STATE)) { - DomainVerificationUserState userState = createUserStateFromXml(child); + DomainVerificationInternalUserState userState = createUserStateFromXml(child); if (userState != null) { userStates.put(userState.getUserId(), userState); } @@ -205,12 +205,12 @@ public class DomainVerificationPersistence { .attribute(ATTR_HAS_AUTO_VERIFY_DOMAINS, pkgState.isHasAutoVerifyDomains())) { writeStateMap(parentSection, pkgState.getStateMap()); - writeUserStates(parentSection, pkgState.getUserSelectionStates()); + writeUserStates(parentSection, pkgState.getUserStates()); } } private static void writeUserStates(@NonNull SettingsXml.WriteSection parentSection, - @NonNull SparseArray<DomainVerificationUserState> states) throws IOException { + @NonNull SparseArray<DomainVerificationInternalUserState> states) throws IOException { int size = states.size(); if (size == 0) { return; @@ -245,7 +245,7 @@ public class DomainVerificationPersistence { * entered. */ @Nullable - public static DomainVerificationUserState createUserStateFromXml( + public static DomainVerificationInternalUserState createUserStateFromXml( @NonNull SettingsXml.ReadSection section) { int userId = section.getInt(ATTR_USER_ID); if (userId == -1) { @@ -260,7 +260,7 @@ public class DomainVerificationPersistence { readEnabledHosts(child, enabledHosts); } - return new DomainVerificationUserState(userId, enabledHosts, allowLinkHandling); + return new DomainVerificationInternalUserState(userId, enabledHosts, allowLinkHandling); } private static void readEnabledHosts(@NonNull SettingsXml.ReadSection section, @@ -275,7 +275,7 @@ public class DomainVerificationPersistence { } public static void writeUserStateToXml(@NonNull SettingsXml.WriteSection parentSection, - @NonNull DomainVerificationUserState userState) throws IOException { + @NonNull DomainVerificationInternalUserState userState) throws IOException { try (SettingsXml.WriteSection section = parentSection.startSection(TAG_USER_STATE) .attribute(ATTR_USER_ID, userState.getUserId()) diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index dbd7f96f2cbc..e85bbe41f747 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -34,8 +34,9 @@ import android.content.pm.parsing.component.ParsedActivity; import android.content.pm.verify.domain.DomainOwner; import android.content.pm.verify.domain.DomainVerificationInfo; import android.content.pm.verify.domain.DomainVerificationManager; +import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException; import android.content.pm.verify.domain.DomainVerificationState; -import android.content.pm.verify.domain.DomainVerificationUserSelection; +import android.content.pm.verify.domain.DomainVerificationUserState; import android.content.pm.verify.domain.IDomainVerificationManager; import android.os.UserHandle; import android.util.ArrayMap; @@ -55,9 +56,9 @@ import com.android.server.SystemService; import com.android.server.compat.PlatformCompat; import com.android.server.pm.PackageSetting; import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState; import com.android.server.pm.verify.domain.models.DomainVerificationPkgState; import com.android.server.pm.verify.domain.models.DomainVerificationStateMap; -import com.android.server.pm.verify.domain.models.DomainVerificationUserState; import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy; import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyUnavailable; @@ -208,8 +209,7 @@ public class DomainVerificationService extends SystemService } @NonNull - @Override - public List<String> getValidVerificationPackageNames() { + public List<String> queryValidVerificationPackageNames() { mEnforcer.assertApprovedVerifier(mConnection.getCallingUid(), mProxy); List<String> packageNames = new ArrayList<>(); synchronized (mLock) { @@ -272,7 +272,6 @@ public class DomainVerificationService extends SystemService } } - @Override public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains, int state) throws InvalidDomainSetException, NameNotFoundException { if (state < DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED) { @@ -314,7 +313,7 @@ public class DomainVerificationService extends SystemService int size = verifiedDomains.size(); for (int index = 0; index < size; index++) { - removeUserSelectionsForDomain(verifiedDomains.get(index)); + removeUserStatesForDomain(verifiedDomains.get(index)); } } @@ -401,12 +400,12 @@ public class DomainVerificationService extends SystemService } } - private void removeUserSelectionsForDomain(@NonNull String domain) { + private void removeUserStatesForDomain(@NonNull String domain) { synchronized (mLock) { final int size = mAttachedPkgStates.size(); for (int index = 0; index < size; index++) { DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index); - SparseArray<DomainVerificationUserState> array = pkgState.getUserSelectionStates(); + SparseArray<DomainVerificationInternalUserState> array = pkgState.getUserStates(); int arraySize = array.size(); for (int arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) { array.valueAt(arrayIndex).removeHost(domain); @@ -415,13 +414,6 @@ public class DomainVerificationService extends SystemService } } - @Override - public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, - boolean allowed) throws NameNotFoundException { - setDomainVerificationLinkHandlingAllowed(packageName, allowed, - mConnection.getCallingUserId()); - } - public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, boolean allowed, @UserIdInt int userId) throws NameNotFoundException { if (!mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(), @@ -434,7 +426,7 @@ public class DomainVerificationService extends SystemService throw DomainVerificationUtils.throwPackageUnavailable(packageName); } - pkgState.getOrCreateUserSelectionState(userId) + pkgState.getOrCreateUserState(userId) .setLinkHandlingAllowed(allowed); } @@ -452,11 +444,11 @@ public class DomainVerificationService extends SystemService DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(pkgStateIndex); if (userId == UserHandle.USER_ALL) { for (int aUserId : mConnection.getAllUserIds()) { - pkgState.getOrCreateUserSelectionState(aUserId) + pkgState.getOrCreateUserState(aUserId) .setLinkHandlingAllowed(allowed); } } else { - pkgState.getOrCreateUserSelectionState(userId) + pkgState.getOrCreateUserState(userId) .setLinkHandlingAllowed(allowed); } } @@ -468,7 +460,7 @@ public class DomainVerificationService extends SystemService throw DomainVerificationUtils.throwPackageUnavailable(packageName); } - pkgState.getOrCreateUserSelectionState(userId) + pkgState.getOrCreateUserState(userId) .setLinkHandlingAllowed(allowed); } } @@ -476,14 +468,6 @@ public class DomainVerificationService extends SystemService mConnection.scheduleWriteSettings(); } - @Override - public void setDomainVerificationUserSelection(@NonNull UUID domainSetId, - @NonNull Set<String> domains, boolean enabled) - throws InvalidDomainSetException, NameNotFoundException { - setDomainVerificationUserSelection(domainSetId, domains, enabled, - mConnection.getCallingUserId()); - } - public void setDomainVerificationUserSelection(@NonNull UUID domainSetId, @NonNull Set<String> domains, boolean enabled, @UserIdInt int userId) throws InvalidDomainSetException, NameNotFoundException { @@ -500,7 +484,8 @@ public class DomainVerificationService extends SystemService DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains, false /* forAutoVerify */, callingUid, userId); - DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId); + DomainVerificationInternalUserState userState = + pkgState.getOrCreateUserState(userId); // Disable other packages if approving this one. Note that this check is only done for // enabling. This allows an escape hatch in case multiple packages somehow get selected. @@ -540,8 +525,8 @@ public class DomainVerificationService extends SystemService continue; } - DomainVerificationUserState approvedUserState = - approvedPkgState.getUserSelectionState(userId); + DomainVerificationInternalUserState approvedUserState = + approvedPkgState.getUserState(userId); if (approvedUserState == null) { continue; } @@ -623,8 +608,8 @@ public class DomainVerificationService extends SystemService if (userId == UserHandle.USER_ALL) { for (int aUserId : mConnection.getAllUserIds()) { - DomainVerificationUserState userState = - pkgState.getOrCreateUserSelectionState(aUserId); + DomainVerificationInternalUserState userState = + pkgState.getOrCreateUserState(aUserId); if (enabled) { userState.addHosts(domains); } else { @@ -632,7 +617,8 @@ public class DomainVerificationService extends SystemService } } } else { - DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId); + DomainVerificationInternalUserState userState = + pkgState.getOrCreateUserState(userId); if (enabled) { userState.addHosts(domains); } else { @@ -643,17 +629,9 @@ public class DomainVerificationService extends SystemService @Nullable @Override - public DomainVerificationUserSelection getDomainVerificationUserSelection( - @NonNull String packageName) throws NameNotFoundException { - return getDomainVerificationUserSelection(packageName, - mConnection.getCallingUserId()); - } - - @Nullable - @Override - public DomainVerificationUserSelection getDomainVerificationUserSelection( + public DomainVerificationUserState getDomainVerificationUserState( @NonNull String packageName, @UserIdInt int userId) throws NameNotFoundException { - if (!mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(), + if (!mEnforcer.assertApprovedUserStateQuerent(mConnection.getCallingUid(), mConnection.getCallingUserId(), packageName, userId)) { throw DomainVerificationUtils.throwPackageUnavailable(packageName); } @@ -673,7 +651,7 @@ public class DomainVerificationService extends SystemService Map<String, Integer> domains = new ArrayMap<>(webDomainsSize); ArrayMap<String, Integer> stateMap = pkgState.getStateMap(); - DomainVerificationUserState userState = pkgState.getUserSelectionState(userId); + DomainVerificationInternalUserState userState = pkgState.getUserState(userId); Set<String> enabledHosts = userState == null ? emptySet() : userState.getEnabledHosts(); for (int index = 0; index < webDomainsSize; index++) { @@ -682,11 +660,11 @@ public class DomainVerificationService extends SystemService int domainState; if (state != null && DomainVerificationManager.isStateVerified(state)) { - domainState = DomainVerificationUserSelection.DOMAIN_STATE_VERIFIED; + domainState = DomainVerificationUserState.DOMAIN_STATE_VERIFIED; } else if (enabledHosts.contains(host)) { - domainState = DomainVerificationUserSelection.DOMAIN_STATE_SELECTED; + domainState = DomainVerificationUserState.DOMAIN_STATE_SELECTED; } else { - domainState = DomainVerificationUserSelection.DOMAIN_STATE_NONE; + domainState = DomainVerificationUserState.DOMAIN_STATE_NONE; } domains.put(host, domainState); @@ -694,17 +672,11 @@ public class DomainVerificationService extends SystemService boolean linkHandlingAllowed = userState == null || userState.isLinkHandlingAllowed(); - return new DomainVerificationUserSelection(pkgState.getId(), packageName, + return new DomainVerificationUserState(pkgState.getId(), packageName, UserHandle.of(userId), linkHandlingAllowed, domains); } } - @NonNull - @Override - public List<DomainOwner> getOwnersForDomain(@NonNull String domain) { - return getOwnersForDomain(domain, mConnection.getCallingUserId()); - } - public List<DomainOwner> getOwnersForDomain(@NonNull String domain, @UserIdInt int userId) { mEnforcer.assertOwnerQuerent(mConnection.getCallingUid(), mConnection.getCallingUserId(), userId); @@ -795,7 +767,7 @@ public class DomainVerificationService extends SystemService AndroidPackage newPkg = newPkgSetting.getPkg(); ArrayMap<String, Integer> newStateMap = new ArrayMap<>(); - SparseArray<DomainVerificationUserState> newUserStates = new SparseArray<>(); + SparseArray<DomainVerificationInternalUserState> newUserStates = new SparseArray<>(); if (oldPkgState == null || oldPkg == null || newPkg == null) { // Should be impossible, but to be safe, continue with a new blank state instead @@ -838,21 +810,22 @@ public class DomainVerificationService extends SystemService } } - SparseArray<DomainVerificationUserState> oldUserStates = - oldPkgState.getUserSelectionStates(); + SparseArray<DomainVerificationInternalUserState> oldUserStates = + oldPkgState.getUserStates(); int oldUserStatesSize = oldUserStates.size(); if (oldUserStatesSize > 0) { ArraySet<String> newWebDomains = mCollector.collectValidAutoVerifyDomains(newPkg); for (int oldUserStatesIndex = 0; oldUserStatesIndex < oldUserStatesSize; oldUserStatesIndex++) { int userId = oldUserStates.keyAt(oldUserStatesIndex); - DomainVerificationUserState oldUserState = oldUserStates.valueAt( + DomainVerificationInternalUserState oldUserState = oldUserStates.valueAt( oldUserStatesIndex); ArraySet<String> oldEnabledHosts = oldUserState.getEnabledHosts(); ArraySet<String> newEnabledHosts = new ArraySet<>(oldEnabledHosts); newEnabledHosts.retainAll(newWebDomains); - DomainVerificationUserState newUserState = new DomainVerificationUserState( - userId, newEnabledHosts, oldUserState.isLinkHandlingAllowed()); + DomainVerificationInternalUserState newUserState = + new DomainVerificationInternalUserState(userId, newEnabledHosts, + oldUserState.isLinkHandlingAllowed()); newUserStates.put(userId, newUserState); } } @@ -926,7 +899,7 @@ public class DomainVerificationService extends SystemService webDomains = mCollector.collectAllWebDomains(pkg); } - pkgState.getOrCreateUserSelectionState(userId).addHosts(webDomains); + pkgState.getOrCreateUserState(userId).addHosts(webDomains); } } @@ -1295,7 +1268,7 @@ public class DomainVerificationService extends SystemService } @Override - public void clearUserSelections(@Nullable List<String> packageNames, @UserIdInt int userId) { + public void clearUserStates(@Nullable List<String> packageNames, @UserIdInt int userId) { mEnforcer.assertInternal(mConnection.getCallingUid()); synchronized (mLock) { if (packageNames == null) { @@ -1545,7 +1518,7 @@ public class DomainVerificationService extends SystemService return APPROVAL_LEVEL_NONE; } - DomainVerificationUserState userState = pkgState.getUserSelectionState(userId); + DomainVerificationInternalUserState userState = pkgState.getUserState(userId); if (userState != null && !userState.isLinkHandlingAllowed()) { if (DEBUG_APPROVAL) { diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java index a8e937cf2b90..f3d1dbb1f6ad 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java @@ -29,9 +29,9 @@ import android.util.TypedXmlSerializer; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState; import com.android.server.pm.verify.domain.models.DomainVerificationPkgState; import com.android.server.pm.verify.domain.models.DomainVerificationStateMap; -import com.android.server.pm.verify.domain.models.DomainVerificationUserState; import org.xmlpull.v1.XmlPullParserException; @@ -216,21 +216,22 @@ class DomainVerificationSettings { } } - SparseArray<DomainVerificationUserState> oldSelectionStates = - oldState.getUserSelectionStates(); + SparseArray<DomainVerificationInternalUserState> oldSelectionStates = + oldState.getUserStates(); - SparseArray<DomainVerificationUserState> newSelectionStates = - newState.getUserSelectionStates(); + SparseArray<DomainVerificationInternalUserState> newSelectionStates = + newState.getUserStates(); - DomainVerificationUserState newUserState = newSelectionStates.get(UserHandle.USER_SYSTEM); + DomainVerificationInternalUserState newUserState = + newSelectionStates.get(UserHandle.USER_SYSTEM); if (newUserState != null) { ArraySet<String> newEnabledHosts = newUserState.getEnabledHosts(); - DomainVerificationUserState oldUserState = + DomainVerificationInternalUserState oldUserState = oldSelectionStates.get(UserHandle.USER_SYSTEM); boolean linkHandlingAllowed = newUserState.isLinkHandlingAllowed(); if (oldUserState == null) { - oldUserState = new DomainVerificationUserState(UserHandle.USER_SYSTEM, + oldUserState = new DomainVerificationInternalUserState(UserHandle.USER_SYSTEM, newEnabledHosts, linkHandlingAllowed); oldSelectionStates.put(UserHandle.USER_SYSTEM, oldUserState); } else { diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java index d083d11cb2e2..94767f555574 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java @@ -24,7 +24,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.verify.domain.DomainVerificationManager; import android.content.pm.verify.domain.DomainVerificationState; -import android.content.pm.verify.domain.DomainVerificationUserSelection; +import android.content.pm.verify.domain.DomainVerificationUserState; import android.os.Binder; import android.os.UserHandle; import android.text.TextUtils; @@ -118,7 +118,7 @@ public class DomainVerificationShell { case "set-app-links": return runSetAppLinks(commandHandler); case "set-app-links-user-selection": - return runSetAppLinksUserSelection(commandHandler); + return runSetAppLinksUserState(commandHandler); case "set-app-links-allowed": return runSetAppLinksAllowed(commandHandler); } @@ -193,7 +193,7 @@ public class DomainVerificationShell { } // pm set-app-links-user-selection --user <USER_ID> [--package <PACKAGE>] <ENABLED> <DOMAINS>... - private boolean runSetAppLinksUserSelection(@NonNull BasicShellCommandHandler commandHandler) { + private boolean runSetAppLinksUserState(@NonNull BasicShellCommandHandler commandHandler) { Integer userId = null; String packageName = null; @@ -224,7 +224,7 @@ public class DomainVerificationShell { return false; } - userId = translateUserId(userId, "runSetAppLinksUserSelection"); + userId = translateUserId(userId, "runSetAppLinksUserState"); String enabledString = commandHandler.getNextArgRequired(); @@ -326,7 +326,7 @@ public class DomainVerificationShell { } if (userId != null) { - mCallback.clearUserSelections(packageNames, userId); + mCallback.clearUserStates(packageNames, userId); } else { mCallback.clearDomainVerificationState(packageNames); } @@ -457,10 +457,10 @@ public class DomainVerificationShell { throws PackageManager.NameNotFoundException; /** - * @see DomainVerificationManager#getDomainVerificationUserSelection(String) + * @see DomainVerificationManager#getDomainVerificationUserState(String) */ @Nullable - DomainVerificationUserSelection getDomainVerificationUserSelection( + DomainVerificationUserState getDomainVerificationUserState( @NonNull String packageName, @UserIdInt int userId) throws PackageManager.NameNotFoundException; @@ -486,7 +486,7 @@ public class DomainVerificationShell { * Reset all the user selections for the given package names, or all package names if null * is provided. */ - void clearUserSelections(@Nullable List<String> packageNames, @UserIdInt int userId); + void clearUserStates(@Nullable List<String> packageNames, @UserIdInt int userId); /** * Broadcast a verification request for the given package names, or all package names if diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationInternalUserState.java index 8fbb33afb6ca..aa7407ce3fe8 100644 --- a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java +++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationInternalUserState.java @@ -29,7 +29,7 @@ import java.util.Set; * when a web URL Intent is sent and the application is the highest priority for that domain. */ @DataClass(genSetters = true, genEqualsHashCode = true, genToString = true, genBuilder = false) -public class DomainVerificationUserState { +public class DomainVerificationInternalUserState { @UserIdInt private final int mUserId; @@ -43,32 +43,32 @@ public class DomainVerificationUserState { */ private boolean mLinkHandlingAllowed = true; - public DomainVerificationUserState(@UserIdInt int userId) { + public DomainVerificationInternalUserState(@UserIdInt int userId) { mUserId = userId; mEnabledHosts = new ArraySet<>(); } - public DomainVerificationUserState addHosts(@NonNull ArraySet<String> newHosts) { + public DomainVerificationInternalUserState addHosts(@NonNull ArraySet<String> newHosts) { mEnabledHosts.addAll(newHosts); return this; } - public DomainVerificationUserState addHosts(@NonNull Set<String> newHosts) { + public DomainVerificationInternalUserState addHosts(@NonNull Set<String> newHosts) { mEnabledHosts.addAll(newHosts); return this; } - public DomainVerificationUserState removeHost(String host) { + public DomainVerificationInternalUserState removeHost(String host) { mEnabledHosts.remove(host); return this; } - public DomainVerificationUserState removeHosts(@NonNull ArraySet<String> newHosts) { + public DomainVerificationInternalUserState removeHosts(@NonNull ArraySet<String> newHosts) { mEnabledHosts.removeAll(newHosts); return this; } - public DomainVerificationUserState removeHosts(@NonNull Set<String> newHosts) { + public DomainVerificationInternalUserState removeHosts(@NonNull Set<String> newHosts) { mEnabledHosts.removeAll(newHosts); return this; } @@ -81,8 +81,7 @@ public class DomainVerificationUserState { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm - // /verify/domain/models/DomainVerificationUserState.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationInternalUserState.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -90,7 +89,7 @@ public class DomainVerificationUserState { /** - * Creates a new DomainVerificationUserState. + * Creates a new DomainVerificationInternalUserState. * * @param enabledHosts * List of domains which have been enabled by the user. * @@ -98,7 +97,7 @@ public class DomainVerificationUserState { * Whether to allow this package to automatically open links by auto verification. */ @DataClass.Generated.Member - public DomainVerificationUserState( + public DomainVerificationInternalUserState( @UserIdInt int userId, @NonNull ArraySet<String> enabledHosts, boolean linkHandlingAllowed) { @@ -138,7 +137,7 @@ public class DomainVerificationUserState { * Whether to allow this package to automatically open links by auto verification. */ @DataClass.Generated.Member - public @NonNull DomainVerificationUserState setLinkHandlingAllowed( boolean value) { + public @NonNull DomainVerificationInternalUserState setLinkHandlingAllowed( boolean value) { mLinkHandlingAllowed = value; return this; } @@ -149,7 +148,7 @@ public class DomainVerificationUserState { // You can override field toString logic by defining methods like: // String fieldNameToString() { ... } - return "DomainVerificationUserState { " + + return "DomainVerificationInternalUserState { " + "userId = " + mUserId + ", " + "enabledHosts = " + mEnabledHosts + ", " + "linkHandlingAllowed = " + mLinkHandlingAllowed + @@ -160,13 +159,13 @@ public class DomainVerificationUserState { @DataClass.Generated.Member public boolean equals(@android.annotation.Nullable Object o) { // You can override field equality logic by defining either of the methods like: - // boolean fieldNameEquals(DomainVerificationUserState other) { ... } + // boolean fieldNameEquals(DomainVerificationInternalUserState other) { ... } // boolean fieldNameEquals(FieldType otherValue) { ... } if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @SuppressWarnings("unchecked") - DomainVerificationUserState that = (DomainVerificationUserState) o; + DomainVerificationInternalUserState that = (DomainVerificationInternalUserState) o; //noinspection PointlessBooleanExpression return true && mUserId == that.mUserId @@ -188,10 +187,10 @@ public class DomainVerificationUserState { } @DataClass.Generated( - time = 1612894390039L, + time = 1614714563905L, codegenVersion = "1.0.22", - sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java", - inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate boolean mLinkHandlingAllowed\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(java.util.Set<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true, genBuilder=false)") + sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationInternalUserState.java", + inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate boolean mLinkHandlingAllowed\npublic com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState addHosts(java.util.Set<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState removeHost(java.lang.String)\npublic com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationInternalUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true, genBuilder=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java index 48099aa5382b..a089a6022735 100644 --- a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java +++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java @@ -19,7 +19,6 @@ package com.android.server.pm.verify.domain.models; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.content.pm.verify.domain.DomainVerificationManager; import android.content.pm.verify.domain.DomainVerificationState; import android.util.ArrayMap; import android.util.SparseArray; @@ -44,9 +43,9 @@ public class DomainVerificationPkgState { /** * Whether or not the package declares any autoVerify domains. This is separate from an empty - * check on the map itself, because an empty map means no response recorded, not necessarily no - * domains declared. When this is false, {@link #mStateMap} will be empty, but - * {@link #mUserSelectionStates} may contain any domains the user has explicitly chosen to + * check on the map itself, because an empty map means no response recorded, not necessarily + * no domains declared. When this is false, {@link #mStateMap} will be empty, but + * {@link #mUserStates} may contain any domains the user has explicitly chosen to * allow this package to open, which may or may not be marked autoVerify. */ private final boolean mHasAutoVerifyDomains; @@ -62,7 +61,7 @@ public class DomainVerificationPkgState { private final ArrayMap<String, Integer> mStateMap; @NonNull - private final SparseArray<DomainVerificationUserState> mUserSelectionStates; + private final SparseArray<DomainVerificationInternalUserState> mUserStates; public DomainVerificationPkgState(@NonNull String packageName, @NonNull UUID id, boolean hasAutoVerifyDomains) { @@ -70,16 +69,17 @@ public class DomainVerificationPkgState { } @Nullable - public DomainVerificationUserState getUserSelectionState(@UserIdInt int userId) { - return mUserSelectionStates.get(userId); + public DomainVerificationInternalUserState getUserState(@UserIdInt int userId) { + return mUserStates.get(userId); } @Nullable - public DomainVerificationUserState getOrCreateUserSelectionState(@UserIdInt int userId) { - DomainVerificationUserState userState = mUserSelectionStates.get(userId); + public DomainVerificationInternalUserState getOrCreateUserState( + @UserIdInt int userId) { + DomainVerificationInternalUserState userState = mUserStates.get(userId); if (userState == null) { - userState = new DomainVerificationUserState(userId); - mUserSelectionStates.put(userId, userState); + userState = new DomainVerificationInternalUserState(userId); + mUserStates.put(userId, userState); } return userState; } @@ -89,20 +89,20 @@ public class DomainVerificationPkgState { } public void removeUser(@UserIdInt int userId) { - mUserSelectionStates.remove(userId); + mUserStates.remove(userId); } public void removeAllUsers() { - mUserSelectionStates.clear(); + mUserStates.clear(); } - private int userSelectionStatesHashCode() { - return mUserSelectionStates.contentHashCode(); + private int userStatesHashCode() { + return mUserStates.contentHashCode(); } - private boolean userSelectionStatesEquals( - @NonNull SparseArray<DomainVerificationUserState> other) { - return mUserSelectionStates.contentEquals(other); + private boolean userStatesEquals( + @NonNull SparseArray<DomainVerificationInternalUserState> other) { + return mUserStates.contentEquals(other); } @@ -113,7 +113,7 @@ public class DomainVerificationPkgState { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationPkgState.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -123,9 +123,15 @@ public class DomainVerificationPkgState { /** * Creates a new DomainVerificationPkgState. * + * @param hasAutoVerifyDomains + * Whether or not the package declares any autoVerify domains. This is separate from an empty + * check on the map itself, because an empty map means no response recorded, not necessarily + * no domains declared. When this is false, {@link #mStateMap} will be empty, but + * {@link #mUserStates} may contain any domains the user has explicitly chosen to + * allow this package to open, which may or may not be marked autoVerify. * @param stateMap * Map of domains to state integers. Only domains that are not set to the default value of - * {@link DomainVerificationManager#STATE_NO_RESPONSE} are included. + * {@link DomainVerificationState#STATE_NO_RESPONSE} are included. * * TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations, * such as storing no state when the package is marked as a linked app in SystemConfig. @@ -136,7 +142,7 @@ public class DomainVerificationPkgState { @NonNull UUID id, boolean hasAutoVerifyDomains, @NonNull ArrayMap<String,Integer> stateMap, - @NonNull SparseArray<DomainVerificationUserState> userSelectionStates) { + @NonNull SparseArray<DomainVerificationInternalUserState> userStates) { this.mPackageName = packageName; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mPackageName); @@ -147,9 +153,9 @@ public class DomainVerificationPkgState { this.mStateMap = stateMap; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mStateMap); - this.mUserSelectionStates = userSelectionStates; + this.mUserStates = userStates; com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mUserSelectionStates); + NonNull.class, null, mUserStates); // onConstructed(); // You can define this method to get a callback } @@ -164,6 +170,13 @@ public class DomainVerificationPkgState { return mId; } + /** + * Whether or not the package declares any autoVerify domains. This is separate from an empty + * check on the map itself, because an empty map means no response recorded, not necessarily + * no domains declared. When this is false, {@link #mStateMap} will be empty, but + * {@link #mUserStates} may contain any domains the user has explicitly chosen to + * allow this package to open, which may or may not be marked autoVerify. + */ @DataClass.Generated.Member public boolean isHasAutoVerifyDomains() { return mHasAutoVerifyDomains; @@ -171,7 +184,7 @@ public class DomainVerificationPkgState { /** * Map of domains to state integers. Only domains that are not set to the default value of - * {@link DomainVerificationManager#STATE_NO_RESPONSE} are included. + * {@link DomainVerificationState#STATE_NO_RESPONSE} are included. * * TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations, * such as storing no state when the package is marked as a linked app in SystemConfig. @@ -182,8 +195,8 @@ public class DomainVerificationPkgState { } @DataClass.Generated.Member - public @NonNull SparseArray<DomainVerificationUserState> getUserSelectionStates() { - return mUserSelectionStates; + public @NonNull SparseArray<DomainVerificationInternalUserState> getUserStates() { + return mUserStates; } @Override @@ -197,7 +210,7 @@ public class DomainVerificationPkgState { "id = " + mId + ", " + "hasAutoVerifyDomains = " + mHasAutoVerifyDomains + ", " + "stateMap = " + mStateMap + ", " + - "userSelectionStates = " + mUserSelectionStates + + "userStates = " + mUserStates + " }"; } @@ -218,7 +231,7 @@ public class DomainVerificationPkgState { && Objects.equals(mId, that.mId) && mHasAutoVerifyDomains == that.mHasAutoVerifyDomains && Objects.equals(mStateMap, that.mStateMap) - && userSelectionStatesEquals(that.mUserSelectionStates); + && userStatesEquals(that.mUserStates); } @Override @@ -232,15 +245,15 @@ public class DomainVerificationPkgState { _hash = 31 * _hash + Objects.hashCode(mId); _hash = 31 * _hash + Boolean.hashCode(mHasAutoVerifyDomains); _hash = 31 * _hash + Objects.hashCode(mStateMap); - _hash = 31 * _hash + userSelectionStatesHashCode(); + _hash = 31 * _hash + userStatesHashCode(); return _hash; } @DataClass.Generated( - time = 1608234185474L, + time = 1614818362549L, codegenVersion = "1.0.22", - sourceFile = "frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationPkgState.java", - inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull java.util.UUID mId\nprivate final boolean mHasAutoVerifyDomains\nprivate final @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.Integer> mStateMap\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationUserState> mUserSelectionStates\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationUserState getUserSelectionState(int)\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationUserState getOrCreateUserSelectionState(int)\npublic void setId(java.util.UUID)\npublic void removeUser(int)\npublic void removeAllUsers()\nprivate int userSelectionStatesHashCode()\nprivate boolean userSelectionStatesEquals(android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationUserState>)\nclass DomainVerificationPkgState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)") + sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java", + inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull java.util.UUID mId\nprivate final boolean mHasAutoVerifyDomains\nprivate final @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.Integer> mStateMap\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState> mUserStates\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getUserState(int)\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getOrCreateUserState(int)\npublic void setId(java.util.UUID)\npublic void removeUser(int)\npublic void removeAllUsers()\nprivate int userStatesHashCode()\nprivate boolean userStatesEquals(android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState>)\nclass DomainVerificationPkgState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/policy/ShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java index ab404dbad910..a0771c0cc8d5 100644 --- a/services/core/java/com/android/server/policy/ShortcutManager.java +++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 The Android Open Source Project + * 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. @@ -16,18 +16,24 @@ package com.android.server.policy; +import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.XmlResourceParser; +import android.os.RemoteException; +import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import android.util.LongSparseArray; +import android.util.Slog; import android.util.SparseArray; import android.view.KeyCharacterMap; import android.view.KeyEvent; +import com.android.internal.policy.IShortcutService; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; @@ -39,8 +45,9 @@ import java.io.IOException; * Manages quick launch shortcuts by: * <li> Keeping the local copy in sync with the database (this is an observer) * <li> Returning a shortcut-matching intent to clients + * <li> Returning particular kind of application intent by special key. */ -class ShortcutManager { +class ModifierShortcutManager { private static final String TAG = "ShortcutManager"; private static final String TAG_BOOKMARKS = "bookmarks"; @@ -52,12 +59,39 @@ class ShortcutManager { private static final String ATTRIBUTE_CATEGORY = "category"; private static final String ATTRIBUTE_SHIFT = "shift"; - private final SparseArray<ShortcutInfo> mShortcuts = new SparseArray<>(); + private final SparseArray<ShortcutInfo> mIntentShortcuts = new SparseArray<>(); private final SparseArray<ShortcutInfo> mShiftShortcuts = new SparseArray<>(); + private LongSparseArray<IShortcutService> mShortcutKeyServices = new LongSparseArray<>(); + + /* Table of Application Launch keys. Maps from key codes to intent categories. + * + * These are special keys that are used to launch particular kinds of applications, + * such as a web browser. HID defines nearly a hundred of them in the Consumer (0x0C) + * usage page. We don't support quite that many yet... + */ + static SparseArray<String> sApplicationLaunchKeyCategories; + static { + sApplicationLaunchKeyCategories = new SparseArray<String>(); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_EXPLORER, Intent.CATEGORY_APP_BROWSER); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR); + } + private final Context mContext; - - public ShortcutManager(Context context) { + private boolean mSearchKeyShortcutPending = false; + private boolean mConsumeSearchKeyUp = true; + + ModifierShortcutManager(Context context) { mContext = context; loadShortcuts(); } @@ -70,19 +104,19 @@ class ShortcutManager { * <p> * This will first try an exact match (with modifiers), and then try a * match without modifiers (primary character on a key). - * + * * @param kcm The key character map of the device on which the key was pressed. * @param keyCode The key code. * @param metaState The meta state, omitting any modifiers that were used * to invoke the shortcut. * @return The intent that matches the shortcut, or null if not found. */ - public Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) { + private Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) { ShortcutInfo shortcut = null; // If the Shift key is pressed, then search for the shift shortcuts. boolean isShiftOn = (metaState & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON; - SparseArray<ShortcutInfo> shortcutMap = isShiftOn ? mShiftShortcuts : mShortcuts; + SparseArray<ShortcutInfo> shortcutMap = isShiftOn ? mShiftShortcuts : mIntentShortcuts; // First try the exact keycode (with modifiers). int shortcutChar = kcm.get(keyCode, metaState); @@ -176,7 +210,7 @@ class ShortcutManager { if (isShiftShortcut) { mShiftShortcuts.put(shortcutChar, shortcut); } else { - mShortcuts.put(shortcutChar, shortcut); + mIntentShortcuts.put(shortcutChar, shortcut); } } } catch (XmlPullParserException e) { @@ -186,11 +220,159 @@ class ShortcutManager { } } + void registerShortcutKey(long shortcutCode, IShortcutService shortcutService) + throws RemoteException { + IShortcutService service = mShortcutKeyServices.get(shortcutCode); + if (service != null && service.asBinder().pingBinder()) { + throw new RemoteException("Key already exists."); + } + + mShortcutKeyServices.put(shortcutCode, shortcutService); + } + + /** + * Handle the shortcut to {@link IShortcutService} + * @param keyCode The key code of the event. + * @param metaState The meta key modifier state. + * @return True if invoked the shortcut, otherwise false. + */ + private boolean handleShortcutService(int keyCode, int metaState) { + long shortcutCode = keyCode; + if ((metaState & KeyEvent.META_CTRL_ON) != 0) { + shortcutCode |= ((long) KeyEvent.META_CTRL_ON) << Integer.SIZE; + } + + if ((metaState & KeyEvent.META_ALT_ON) != 0) { + shortcutCode |= ((long) KeyEvent.META_ALT_ON) << Integer.SIZE; + } + + if ((metaState & KeyEvent.META_SHIFT_ON) != 0) { + shortcutCode |= ((long) KeyEvent.META_SHIFT_ON) << Integer.SIZE; + } + + if ((metaState & KeyEvent.META_META_ON) != 0) { + shortcutCode |= ((long) KeyEvent.META_META_ON) << Integer.SIZE; + } + + IShortcutService shortcutService = mShortcutKeyServices.get(shortcutCode); + if (shortcutService != null) { + try { + shortcutService.notifyShortcutKeyPressed(shortcutCode); + } catch (RemoteException e) { + mShortcutKeyServices.delete(shortcutCode); + } + return true; + } + return false; + } + + /** + * Handle the shortcut to {@link Intent} + * + * @param kcm the {@link KeyCharacterMap} associated with the keyboard device. + * @param keyCode The key code of the event. + * @param metaState The meta key modifier state. + * @return True if invoked the shortcut, otherwise false. + */ + private boolean handleIntentShortcut(KeyCharacterMap kcm, int keyCode, int metaState) { + // Shortcuts are invoked through Search+key, so intercept those here + // Any printing key that is chorded with Search should be consumed + // even if no shortcut was invoked. This prevents text from being + // inadvertently inserted when using a keyboard that has built-in macro + // shortcut keys (that emit Search+x) and some of them are not registered. + if (mSearchKeyShortcutPending) { + if (kcm.isPrintingKey(keyCode)) { + mConsumeSearchKeyUp = true; + mSearchKeyShortcutPending = false; + } else { + return false; + } + } else if ((metaState & KeyEvent.META_META_MASK) != 0) { + // Invoke shortcuts using Meta. + metaState &= ~KeyEvent.META_META_MASK; + } else { + // Handle application launch keys. + String category = sApplicationLaunchKeyCategories.get(keyCode); + if (category != null) { + Intent intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + mContext.startActivityAsUser(intent, UserHandle.CURRENT); + } catch (ActivityNotFoundException ex) { + Slog.w(TAG, "Dropping application launch key because " + + "the activity to which it is registered was not found: " + + "keyCode=" + KeyEvent.keyCodeToString(keyCode) + "," + + " category=" + category, ex); + } + return true; + } else { + return false; + } + } + + final Intent shortcutIntent = getIntent(kcm, keyCode, metaState); + if (shortcutIntent != null) { + shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + mContext.startActivityAsUser(shortcutIntent, UserHandle.CURRENT); + } catch (ActivityNotFoundException ex) { + Slog.w(TAG, "Dropping shortcut key combination because " + + "the activity to which it is registered was not found: " + + "META+ or SEARCH" + KeyEvent.keyCodeToString(keyCode), ex); + } + return true; + } + return false; + } + + /** + * Handle the shortcut from {@link KeyEvent} + * + * @param event Description of the key event. + * @return True if invoked the shortcut, otherwise false. + */ + boolean interceptKey(KeyEvent event) { + if (event.getRepeatCount() != 0) { + return false; + } + + final int metaState = event.getModifiers(); + final int keyCode = event.getKeyCode(); + if (keyCode == KeyEvent.KEYCODE_SEARCH) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + mSearchKeyShortcutPending = true; + mConsumeSearchKeyUp = false; + } else { + mSearchKeyShortcutPending = false; + if (mConsumeSearchKeyUp) { + mConsumeSearchKeyUp = false; + return true; + } + } + return false; + } + + if (event.getAction() != KeyEvent.ACTION_DOWN) { + return false; + } + + final KeyCharacterMap kcm = event.getKeyCharacterMap(); + if (handleIntentShortcut(kcm, keyCode, metaState)) { + return true; + } + + if (handleShortcutService(keyCode, metaState)) { + return true; + } + + return false; + } + private static final class ShortcutInfo { public final String title; public final Intent intent; - public ShortcutInfo(String title, Intent intent) { + ShortcutInfo(String title, Intent intent) { this.title = title; this.intent = intent; } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index c50efc70a6bc..bce218f8a74b 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -163,7 +163,6 @@ import android.service.vr.IPersistentVrStateCallbacks; import android.speech.RecognizerIntent; import android.telecom.TelecomManager; import android.util.Log; -import android.util.LongSparseArray; import android.util.MutableBoolean; import android.util.PrintWriterPrinter; import android.util.Slog; @@ -318,29 +317,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { */ private boolean mKeyguardDrawnOnce; - /* Table of Application Launch keys. Maps from key codes to intent categories. - * - * These are special keys that are used to launch particular kinds of applications, - * such as a web browser. HID defines nearly a hundred of them in the Consumer (0x0C) - * usage page. We don't support quite that many yet... - */ - static SparseArray<String> sApplicationLaunchKeyCategories; - static { - sApplicationLaunchKeyCategories = new SparseArray<String>(); - sApplicationLaunchKeyCategories.append( - KeyEvent.KEYCODE_EXPLORER, Intent.CATEGORY_APP_BROWSER); - sApplicationLaunchKeyCategories.append( - KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL); - sApplicationLaunchKeyCategories.append( - KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS); - sApplicationLaunchKeyCategories.append( - KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR); - sApplicationLaunchKeyCategories.append( - KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC); - sApplicationLaunchKeyCategories.append( - KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR); - } - /** Amount of time (in milliseconds) to wait for windows drawn before powering on. */ static final int WAITING_FOR_DRAWN_TIMEOUT = 1000; @@ -419,8 +395,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mSafeMode; private WindowState mKeyguardCandidate = null; - private LongSparseArray<IShortcutService> mShortcutKeyServices = new LongSparseArray<>(); - // Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key. // This is for car dock and this is updated from resource. private boolean mEnableCarDockHomeCapture = true; @@ -516,8 +490,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { Intent mCarDockIntent; Intent mDeskDockIntent; Intent mVrHeadsetHomeIntent; - boolean mSearchKeyShortcutPending; - boolean mConsumeSearchKeyUp; boolean mPendingMetaAction; boolean mPendingCapsLockToggle; @@ -578,7 +550,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int BRIGHTNESS_STEPS = 10; SettingsObserver mSettingsObserver; - ShortcutManager mShortcutManager; + ModifierShortcutManager mModifierShortcutManager; PowerManager.WakeLock mBroadcastWakeLock; PowerManager.WakeLock mPowerKeyWakeLock; boolean mHavePendingMediaKeyRepeatWithWakeLock; @@ -1772,7 +1744,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler); mSettingsObserver = new SettingsObserver(mHandler); mSettingsObserver.observe(); - mShortcutManager = new ShortcutManager(context); + mModifierShortcutManager = new ModifierShortcutManager(context); mUiMode = context.getResources().getInteger( com.android.internal.R.integer.config_defaultUiModeType); mHomeIntent = new Intent(Intent.ACTION_MAIN, null); @@ -2574,6 +2546,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPendingCapsLockToggle = false; } + if (isUserSetupComplete() && !keyguardOn) { + if (mModifierShortcutManager.interceptKey(event)) { + dismissKeyboardShortcutsMenu(); + mPendingMetaAction = false; + mPendingCapsLockToggle = false; + return key_consumed; + } + } + switch(keyCode) { case KeyEvent.KEYCODE_HOME: // First we always handle the home key here, so applications @@ -2599,20 +2580,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } break; - case KeyEvent.KEYCODE_SEARCH: - if (down) { - if (repeatCount == 0) { - mSearchKeyShortcutPending = true; - mConsumeSearchKeyUp = false; - } - } else { - mSearchKeyShortcutPending = false; - if (mConsumeSearchKeyUp) { - mConsumeSearchKeyUp = false; - return key_consumed; - } - } - return 0; case KeyEvent.KEYCODE_APP_SWITCH: if (!keyguardOn) { if (down && repeatCount == 0) { @@ -2820,114 +2787,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { break; } - // Shortcuts are invoked through Search+key, so intercept those here - // Any printing key that is chorded with Search should be consumed - // even if no shortcut was invoked. This prevents text from being - // inadvertently inserted when using a keyboard that has built-in macro - // shortcut keys (that emit Search+x) and some of them are not registered. - if (mSearchKeyShortcutPending) { - final KeyCharacterMap kcm = event.getKeyCharacterMap(); - if (kcm.isPrintingKey(keyCode)) { - mConsumeSearchKeyUp = true; - mSearchKeyShortcutPending = false; - if (down && repeatCount == 0 && !keyguardOn) { - Intent shortcutIntent = mShortcutManager.getIntent(kcm, keyCode, metaState); - if (shortcutIntent != null) { - shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - try { - startActivityAsUser(shortcutIntent, UserHandle.CURRENT); - dismissKeyboardShortcutsMenu(); - } catch (ActivityNotFoundException ex) { - Slog.w(TAG, "Dropping shortcut key combination because " - + "the activity to which it is registered was not found: " - + "SEARCH+" + KeyEvent.keyCodeToString(keyCode), ex); - } - } else { - Slog.i(TAG, "Dropping unregistered shortcut key combination: " - + "SEARCH+" + KeyEvent.keyCodeToString(keyCode)); - } - } - return key_consumed; - } - } - - // Invoke shortcuts using Meta. - if (down && repeatCount == 0 && !keyguardOn - && (metaState & KeyEvent.META_META_ON) != 0) { - final KeyCharacterMap kcm = event.getKeyCharacterMap(); - if (kcm.isPrintingKey(keyCode)) { - Intent shortcutIntent = mShortcutManager.getIntent(kcm, keyCode, - metaState & ~(KeyEvent.META_META_ON - | KeyEvent.META_META_LEFT_ON | KeyEvent.META_META_RIGHT_ON)); - if (shortcutIntent != null) { - shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - try { - startActivityAsUser(shortcutIntent, UserHandle.CURRENT); - dismissKeyboardShortcutsMenu(); - } catch (ActivityNotFoundException ex) { - Slog.w(TAG, "Dropping shortcut key combination because " - + "the activity to which it is registered was not found: " - + "META+" + KeyEvent.keyCodeToString(keyCode), ex); - } - return key_consumed; - } - } - } - - // Handle application launch keys. - if (down && repeatCount == 0 && !keyguardOn) { - String category = sApplicationLaunchKeyCategories.get(keyCode); - if (category != null) { - Intent intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - try { - startActivityAsUser(intent, UserHandle.CURRENT); - dismissKeyboardShortcutsMenu(); - } catch (ActivityNotFoundException ex) { - Slog.w(TAG, "Dropping application launch key because " - + "the activity to which it is registered was not found: " - + "keyCode=" + keyCode + ", category=" + category, ex); - } - return key_consumed; - } - } - if (isValidGlobalKey(keyCode) && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) { return key_consumed; } - if (down) { - long shortcutCode = keyCode; - if (event.isCtrlPressed()) { - shortcutCode |= ((long) KeyEvent.META_CTRL_ON) << Integer.SIZE; - } - - if (event.isAltPressed()) { - shortcutCode |= ((long) KeyEvent.META_ALT_ON) << Integer.SIZE; - } - - if (event.isShiftPressed()) { - shortcutCode |= ((long) KeyEvent.META_SHIFT_ON) << Integer.SIZE; - } - - if (event.isMetaPressed()) { - shortcutCode |= ((long) KeyEvent.META_META_ON) << Integer.SIZE; - } - - IShortcutService shortcutService = mShortcutKeyServices.get(shortcutCode); - if (shortcutService != null) { - try { - if (isUserSetupComplete()) { - shortcutService.notifyShortcutKeyPressed(shortcutCode); - } - } catch (RemoteException e) { - mShortcutKeyServices.delete(shortcutCode); - } - return key_consumed; - } - } - // Reserve all the META modifier combos for system behavior if ((metaState & KeyEvent.META_META_ON) != 0) { return key_consumed; @@ -3113,12 +2977,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService) throws RemoteException { synchronized (mLock) { - IShortcutService service = mShortcutKeyServices.get(shortcutCode); - if (service != null && service.asBinder().pingBinder()) { - throw new RemoteException("Key already exists."); - } - - mShortcutKeyServices.put(shortcutCode, shortcutService); + mModifierShortcutManager.registerShortcutKey(shortcutCode, shortcutService); } } diff --git a/services/core/java/com/android/server/role/OWNERS b/services/core/java/com/android/server/role/OWNERS index 31e3549d9111..dafdf0f8075c 100644 --- a/services/core/java/com/android/server/role/OWNERS +++ b/services/core/java/com/android/server/role/OWNERS @@ -1,5 +1 @@ -svetoslavganov@google.com -zhanghai@google.com -evanseverson@google.com -eugenesusla@google.com -ntmyren@google.com +include platform/frameworks/base:/core/java/android/permission/OWNERS diff --git a/services/core/java/com/android/server/servicewatcher/OWNERS b/services/core/java/com/android/server/servicewatcher/OWNERS new file mode 100644 index 000000000000..ced619f05f1d --- /dev/null +++ b/services/core/java/com/android/server/servicewatcher/OWNERS @@ -0,0 +1,5 @@ +# Bug component: 25692 + +sooniln@google.com +wyattriley@google.com + diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java index de8823c4b7f3..6366280e1762 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java @@ -39,6 +39,7 @@ import android.media.soundtrigger_middleware.RecognitionStatus; import android.media.soundtrigger_middleware.SoundModel; import android.media.soundtrigger_middleware.SoundModelType; import android.media.soundtrigger_middleware.SoundTriggerModuleProperties; +import android.os.HidlMemory; import android.os.HidlMemoryUtil; import android.os.ParcelFileDescriptor; @@ -199,18 +200,7 @@ class ConversionUtil { hidlModel.header.type = aidl2hidlSoundModelType(aidlModel.type); hidlModel.header.uuid = aidl2hidlUuid(aidlModel.uuid); hidlModel.header.vendorUuid = aidl2hidlUuid(aidlModel.vendorUuid); - - // Extract a dup of the underlying FileDescriptor out of aidlModel.data without changing - // the original. - FileDescriptor fd = new FileDescriptor(); - try { - ParcelFileDescriptor dup = aidlModel.data.dup(); - fd.setInt$(dup.detachFd()); - hidlModel.data = HidlMemoryUtil.fileDescriptorToHidlMemory(fd, aidlModel.dataSize); - } catch (IOException e) { - throw new RuntimeException(e); - } - + hidlModel.data = parcelFileDescriptorToHidlMemory(aidlModel.data, aidlModel.dataSize); return hidlModel; } @@ -425,4 +415,31 @@ class ConversionUtil { } return aidlCapabilities; } + + /** + * Convert an AIDL representation of a shared memory block (ParcelFileDescriptor + size) to the + * HIDL representation (HidlMemory). Will not change the incoming data or any ownership + * semantics, but rather duplicate the underlying FD. + * + * @param data The incoming memory block. May be null if dataSize is 0. + * @param dataSize The number of bytes in the block. + * @return A HidlMemory representation of the memory block. Will be non-null even for an empty + * block. + */ + private static @NonNull + HidlMemory parcelFileDescriptorToHidlMemory(@Nullable ParcelFileDescriptor data, int dataSize) { + if (dataSize > 0) { + // Extract a dup of the underlying FileDescriptor out of data. + FileDescriptor fd = new FileDescriptor(); + try { + ParcelFileDescriptor dup = data.dup(); + fd.setInt$(dup.detachFd()); + return HidlMemoryUtil.fileDescriptorToHidlMemory(fd, dataSize); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + return HidlMemoryUtil.fileDescriptorToHidlMemory(null, 0); + } + } } diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java index ebe9733e5d55..212f81f72b24 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java @@ -35,8 +35,7 @@ import java.util.TimerTask; * HAL whenever they expire. */ public class SoundTriggerHw2Watchdog implements ISoundTriggerHw2 { - // TODO(b/166328980): Reduce this to 1000 as soon as HAL is fixed. - private static final long TIMEOUT_MS = 10000; + private static final long TIMEOUT_MS = 3000; private static final String TAG = "SoundTriggerHw2Watchdog"; private final @NonNull diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java index 43047d1ebaaa..e05c468186ed 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java @@ -62,7 +62,9 @@ public class ValidationUtil { } validateUuid(model.uuid); validateUuid(model.vendorUuid); - Objects.requireNonNull(model.data); + if (model.dataSize > 0) { + Objects.requireNonNull(model.data); + } } static void validatePhraseModel(@Nullable PhraseSoundModel model) { diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 6aa7c8a290c1..7ed7a592a972 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -3759,13 +3759,15 @@ public class StatsPullAtomService extends SystemService { ConnectivityManager.NetworkCallback { @Override public void onAvailable(Network network) { - FrameworkStatsLog.write(FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED, network.netId, + FrameworkStatsLog.write(FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED, + network.getNetId(), FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED__STATE__CONNECTED); } @Override public void onLost(Network network) { - FrameworkStatsLog.write(FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED, network.netId, + FrameworkStatsLog.write(FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED, + network.getNetId(), FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED__STATE__DISCONNECTED); } } diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java index d2b05c0914d7..9409eb5d1ad9 100644 --- a/services/core/java/com/android/server/storage/StorageUserConnection.java +++ b/services/core/java/com/android/server/storage/StorageUserConnection.java @@ -53,11 +53,13 @@ import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; /** * Controls the lifecycle of the {@link ActiveConnection} to an {@link ExternalStorageService} @@ -72,6 +74,7 @@ public final class StorageUserConnection { private final Context mContext; private final int mUserId; private final StorageSessionController mSessionController; + private final StorageManagerInternal mSmInternal; private final ActiveConnection mActiveConnection = new ActiveConnection(); @GuardedBy("mLock") private final Map<String, Session> mSessions = new HashMap<>(); @GuardedBy("mLock") private final Set<Integer> mUidsBlockedOnIo = new ArraySet<>(); @@ -81,6 +84,7 @@ public final class StorageUserConnection { mContext = Objects.requireNonNull(context); mUserId = Preconditions.checkArgumentNonnegative(userId); mSessionController = controller; + mSmInternal = LocalServices.getService(StorageManagerInternal.class); mHandlerThread = new HandlerThread("StorageUserConnectionThread-" + mUserId); mHandlerThread.start(); } @@ -152,9 +156,13 @@ public final class StorageUserConnection { */ public void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason) throws ExternalStorageServiceException { + List<String> primarySessionIds = mSmInternal.getPrimaryVolumeIds(); synchronized (mSessionsLock) { for (String sessionId : mSessions.keySet()) { - mActiveConnection.notifyAnrDelayStarted(packageName, uid, tid, reason); + if (primarySessionIds.contains(sessionId)) { + mActiveConnection.notifyAnrDelayStarted(packageName, uid, tid, reason); + return; + } } } } @@ -201,8 +209,7 @@ public final class StorageUserConnection { return; } } - StorageManagerInternal sm = LocalServices.getService(StorageManagerInternal.class); - sm.resetUser(mUserId); + mSmInternal.resetUser(mUserId); } /** @@ -317,6 +324,23 @@ public final class StorageUserConnection { } } + private void asyncBestEffort(Consumer<IExternalStorageService> consumer) { + synchronized (mLock) { + if (mRemoteFuture == null) { + Slog.w(TAG, "Dropping async request service is not bound"); + return; + } + + IExternalStorageService service = mRemoteFuture.getNow(null); + if (service == null) { + Slog.w(TAG, "Dropping async request service is not connected"); + return; + } + + consumer.accept(service); + } + } + private void waitForAsyncVoid(AsyncStorageServiceCall asyncCall) throws Exception { CompletableFuture<Void> opFuture = new CompletableFuture<>(); RemoteCallback callback = new RemoteCallback(result -> setResult(result, opFuture)); @@ -401,13 +425,13 @@ public final class StorageUserConnection { public void notifyAnrDelayStarted(String packgeName, int uid, int tid, int reason) throws ExternalStorageServiceException { - try { - waitForAsyncVoid((service, callback) -> - service.notifyAnrDelayStarted(packgeName, uid, tid, reason, callback)); - } catch (Exception e) { - throw new ExternalStorageServiceException("Failed to notify ANR delay started: " - + packgeName, e); - } + asyncBestEffort(service -> { + try { + service.notifyAnrDelayStarted(packgeName, uid, tid, reason); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify ANR delay started", e); + } + }); } private void setResult(Bundle result, CompletableFuture<Void> future) { diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java index b6ddd93af3b8..b2db9f5af07e 100644 --- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java +++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java @@ -65,7 +65,7 @@ public class UnderlyingNetworkTracker { @NonNull private final NetworkCallback mRouteSelectionCallback = new RouteSelectionCallback(); @NonNull private TelephonySubscriptionSnapshot mLastSnapshot; - private boolean mIsRunning = true; + private boolean mIsQuitting = false; @Nullable private UnderlyingNetworkRecord mCurrentRecord; @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress; @@ -151,7 +151,7 @@ public class UnderlyingNetworkTracker { mVcnContext.ensureRunningOnLooperThread(); // Don't bother re-filing NetworkRequests if this Tracker has been torn down. - if (!mIsRunning) { + if (mIsQuitting) { return; } @@ -205,7 +205,7 @@ public class UnderlyingNetworkTracker { } mCellBringupCallbacks.clear(); - mIsRunning = false; + mIsQuitting = true; } /** Returns whether the currently selected Network matches the given network. */ diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java index 6ad30b544257..9d39c67d27fb 100644 --- a/services/core/java/com/android/server/vcn/Vcn.java +++ b/services/core/java/com/android/server/vcn/Vcn.java @@ -16,6 +16,8 @@ package com.android.server.vcn; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; + import static com.android.server.VcnManagementService.VDBG; import android.annotation.NonNull; @@ -84,6 +86,13 @@ public class Vcn extends Handler { */ private static final int MSG_EVENT_SUBSCRIPTIONS_CHANGED = MSG_EVENT_BASE + 2; + /** + * A GatewayConnection owned by this VCN quit. + * + * @param obj VcnGatewayConnectionConfig + */ + private static final int MSG_EVENT_GATEWAY_CONNECTION_QUIT = MSG_EVENT_BASE + 3; + /** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */ private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE; @@ -208,6 +217,9 @@ public class Vcn extends Handler { case MSG_EVENT_SUBSCRIPTIONS_CHANGED: handleSubscriptionsChanged((TelephonySubscriptionSnapshot) msg.obj); break; + case MSG_EVENT_GATEWAY_CONNECTION_QUIT: + handleGatewayConnectionQuit((VcnGatewayConnectionConfig) msg.obj); + break; case MSG_CMD_TEARDOWN: handleTeardown(); break; @@ -263,7 +275,7 @@ public class Vcn extends Handler { // If preexisting VcnGatewayConnection(s) satisfy request, return for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) { - if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) { + if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) { if (VDBG) { Slog.v( getLogTag(), @@ -278,7 +290,7 @@ public class Vcn extends Handler { // up for (VcnGatewayConnectionConfig gatewayConnectionConfig : mConfig.getGatewayConnectionConfigs()) { - if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) { + if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) { Slog.v( getLogTag(), "Bringing up new VcnGatewayConnection for request " + request.requestId); @@ -289,12 +301,21 @@ public class Vcn extends Handler { mSubscriptionGroup, mLastSnapshot, gatewayConnectionConfig, - new VcnGatewayStatusCallbackImpl()); + new VcnGatewayStatusCallbackImpl(gatewayConnectionConfig)); mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection); } } } + private void handleGatewayConnectionQuit(VcnGatewayConnectionConfig config) { + Slog.v(getLogTag(), "VcnGatewayConnection quit: " + config); + mVcnGatewayConnections.remove(config); + + // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied + // start a new GatewayConnection) + mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener); + } + private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) { mLastSnapshot = snapshot; @@ -305,9 +326,10 @@ public class Vcn extends Handler { } } - private boolean requestSatisfiedByGatewayConnectionConfig( + private boolean isRequestSatisfiedByGatewayConnectionConfig( @NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) { final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(); + builder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); for (int cap : config.getAllExposedCapabilities()) { builder.addCapability(cap); } @@ -339,9 +361,23 @@ public class Vcn extends Handler { @VcnErrorCode int errorCode, @Nullable String exceptionClass, @Nullable String exceptionMessage); + + /** Called by a VcnGatewayConnection to indicate that it has fully torn down. */ + void onQuit(); } private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback { + public final VcnGatewayConnectionConfig mGatewayConnectionConfig; + + VcnGatewayStatusCallbackImpl(VcnGatewayConnectionConfig gatewayConnectionConfig) { + mGatewayConnectionConfig = gatewayConnectionConfig; + } + + @Override + public void onQuit() { + sendMessage(obtainMessage(MSG_EVENT_GATEWAY_CONNECTION_QUIT, mGatewayConnectionConfig)); + } + @Override public void onEnteredSafeMode() { sendMessage(obtainMessage(MSG_CMD_ENTER_SAFE_MODE)); diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 06748a3aa2d1..6bc9978a0731 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -168,6 +168,8 @@ public class VcnGatewayConnection extends StateMachine { private static final String DISCONNECT_REASON_INTERNAL_ERROR = "Uncaught exception: "; private static final String DISCONNECT_REASON_UNDERLYING_NETWORK_LOST = "Underlying Network lost"; + private static final String DISCONNECT_REASON_NETWORK_AGENT_UNWANTED = + "NetworkAgent was unwanted"; private static final String DISCONNECT_REASON_TEARDOWN = "teardown() called on VcnTunnel"; private static final int TOKEN_ALL = Integer.MIN_VALUE; @@ -379,13 +381,16 @@ public class VcnGatewayConnection extends StateMachine { /** The reason why the disconnect was requested. */ @NonNull public final String reason; - EventDisconnectRequestedInfo(@NonNull String reason) { + public final boolean shouldQuit; + + EventDisconnectRequestedInfo(@NonNull String reason, boolean shouldQuit) { this.reason = Objects.requireNonNull(reason); + this.shouldQuit = shouldQuit; } @Override public int hashCode() { - return Objects.hash(reason); + return Objects.hash(reason, shouldQuit); } @Override @@ -395,7 +400,7 @@ public class VcnGatewayConnection extends StateMachine { } final EventDisconnectRequestedInfo rhs = (EventDisconnectRequestedInfo) other; - return reason.equals(rhs.reason); + return reason.equals(rhs.reason) && shouldQuit == rhs.shouldQuit; } } @@ -488,8 +493,14 @@ public class VcnGatewayConnection extends StateMachine { */ @NonNull private final VcnWakeLock mWakeLock; - /** Running state of this VcnGatewayConnection. */ - private boolean mIsRunning = true; + /** + * Whether the VcnGatewayConnection is in the process of irreversibly quitting. + * + * <p>This variable is false for the lifecycle of the VcnGatewayConnection, until a command to + * teardown has been received. This may be flipped due to events such as the Network becoming + * unwanted, the owning VCN entering safe mode, or an irrecoverable internal failure. + */ + private boolean mIsQuitting = false; /** * The token used by the primary/current/active session. @@ -622,10 +633,8 @@ public class VcnGatewayConnection extends StateMachine { * <p>Once torn down, this VcnTunnel CANNOT be started again. */ public void teardownAsynchronously() { - sendMessageAndAcquireWakeLock( - EVENT_DISCONNECT_REQUESTED, - TOKEN_ALL, - new EventDisconnectRequestedInfo(DISCONNECT_REASON_TEARDOWN)); + sendDisconnectRequestedAndAcquireWakelock( + DISCONNECT_REASON_TEARDOWN, true /* shouldQuit */); // TODO: Notify VcnInstance (via callbacks) of permanent teardown of this tunnel, since this // is also called asynchronously when a NetworkAgent becomes unwanted @@ -646,6 +655,8 @@ public class VcnGatewayConnection extends StateMachine { cancelSafeModeAlarm(); mUnderlyingNetworkTracker.teardown(); + + mGatewayStatusCallback.onQuit(); } /** @@ -693,7 +704,7 @@ public class VcnGatewayConnection extends StateMachine { private void acquireWakeLock() { mVcnContext.ensureRunningOnLooperThread(); - if (mIsRunning) { + if (!mIsQuitting) { mWakeLock.acquire(); } } @@ -892,7 +903,7 @@ public class VcnGatewayConnection extends StateMachine { TOKEN_ALL, 0 /* arg2 */, new EventDisconnectRequestedInfo( - DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)); + DISCONNECT_REASON_UNDERLYING_NETWORK_LOST, false /* shouldQuit */)); mDisconnectRequestAlarm = createScheduledAlarm( DISCONNECT_REQUEST_ALARM, @@ -909,7 +920,8 @@ public class VcnGatewayConnection extends StateMachine { // Cancel any existing disconnect due to previous loss of underlying network removeEqualMessages( EVENT_DISCONNECT_REQUESTED, - new EventDisconnectRequestedInfo(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)); + new EventDisconnectRequestedInfo( + DISCONNECT_REASON_UNDERLYING_NETWORK_LOST, false /* shouldQuit */)); } private void setRetryTimeoutAlarm(long delay) { @@ -1041,11 +1053,8 @@ public class VcnGatewayConnection extends StateMachine { enterState(); } catch (Exception e) { Slog.wtf(TAG, "Uncaught exception", e); - sendMessageAndAcquireWakeLock( - EVENT_DISCONNECT_REQUESTED, - TOKEN_ALL, - new EventDisconnectRequestedInfo( - DISCONNECT_REASON_INTERNAL_ERROR + e.toString())); + sendDisconnectRequestedAndAcquireWakelock( + DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */); } } @@ -1083,11 +1092,8 @@ public class VcnGatewayConnection extends StateMachine { processStateMsg(msg); } catch (Exception e) { Slog.wtf(TAG, "Uncaught exception", e); - sendMessageAndAcquireWakeLock( - EVENT_DISCONNECT_REQUESTED, - TOKEN_ALL, - new EventDisconnectRequestedInfo( - DISCONNECT_REASON_INTERNAL_ERROR + e.toString())); + sendDisconnectRequestedAndAcquireWakelock( + DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */); } // Attempt to release the WakeLock - only possible if the Handler queue is empty @@ -1104,11 +1110,8 @@ public class VcnGatewayConnection extends StateMachine { exitState(); } catch (Exception e) { Slog.wtf(TAG, "Uncaught exception", e); - sendMessageAndAcquireWakeLock( - EVENT_DISCONNECT_REQUESTED, - TOKEN_ALL, - new EventDisconnectRequestedInfo( - DISCONNECT_REASON_INTERNAL_ERROR + e.toString())); + sendDisconnectRequestedAndAcquireWakelock( + DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */); } } @@ -1141,11 +1144,11 @@ public class VcnGatewayConnection extends StateMachine { } } - protected void handleDisconnectRequested(String msg) { + protected void handleDisconnectRequested(EventDisconnectRequestedInfo info) { // TODO(b/180526152): notify VcnStatusCallback for Network loss - Slog.v(TAG, "Tearing down. Cause: " + msg); - mIsRunning = false; + Slog.v(TAG, "Tearing down. Cause: " + info.reason); + mIsQuitting = info.shouldQuit; teardownNetwork(); @@ -1177,7 +1180,7 @@ public class VcnGatewayConnection extends StateMachine { private class DisconnectedState extends BaseState { @Override protected void enterState() { - if (!mIsRunning) { + if (mIsQuitting) { quitNow(); // Ignore all queued events; cleanup is complete. } @@ -1200,9 +1203,11 @@ public class VcnGatewayConnection extends StateMachine { } break; case EVENT_DISCONNECT_REQUESTED: - mIsRunning = false; + if (((EventDisconnectRequestedInfo) msg.obj).shouldQuit) { + mIsQuitting = true; - quitNow(); + quitNow(); + } break; default: logUnhandledMessage(msg); @@ -1284,10 +1289,11 @@ public class VcnGatewayConnection extends StateMachine { break; case EVENT_DISCONNECT_REQUESTED: + EventDisconnectRequestedInfo info = ((EventDisconnectRequestedInfo) msg.obj); + mIsQuitting = info.shouldQuit; teardownNetwork(); - String reason = ((EventDisconnectRequestedInfo) msg.obj).reason; - if (reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) { + if (info.reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) { // TODO(b/180526152): notify VcnStatusCallback for Network loss // Will trigger EVENT_SESSION_CLOSED immediately. @@ -1300,7 +1306,7 @@ public class VcnGatewayConnection extends StateMachine { case EVENT_SESSION_CLOSED: mIkeSession = null; - if (mIsRunning && mUnderlying != null) { + if (!mIsQuitting && mUnderlying != null) { transitionTo(mSkipRetryTimeout ? mConnectingState : mRetryTimeoutState); } else { teardownNetwork(); @@ -1391,7 +1397,7 @@ public class VcnGatewayConnection extends StateMachine { transitionTo(mConnectedState); break; case EVENT_DISCONNECT_REQUESTED: - handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); + handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj); break; case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: mGatewayStatusCallback.onEnteredSafeMode(); @@ -1438,6 +1444,7 @@ public class VcnGatewayConnection extends StateMachine { mVcnContext.getVcnNetworkProvider()) { @Override public void unwanted() { + Slog.d(TAG, "NetworkAgent was unwanted"); teardownAsynchronously(); } @@ -1471,7 +1478,7 @@ public class VcnGatewayConnection extends StateMachine { @NonNull IpSecTransform transform, int direction) { try { - // TODO(b/180163196): Set underlying network of tunnel interface + tunnelIface.setUnderlyingNetwork(underlyingNetwork); // Transforms do not need to be persisted; the IkeSession will keep them alive mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform); @@ -1577,7 +1584,7 @@ public class VcnGatewayConnection extends StateMachine { setupInterfaceAndNetworkAgent(mCurrentToken, mTunnelIface, mChildConfig); break; case EVENT_DISCONNECT_REQUESTED: - handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); + handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj); break; case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: mGatewayStatusCallback.onEnteredSafeMode(); @@ -1682,7 +1689,7 @@ public class VcnGatewayConnection extends StateMachine { transitionTo(mConnectingState); break; case EVENT_DISCONNECT_REQUESTED: - handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); + handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj); break; case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: mGatewayStatusCallback.onEnteredSafeMode(); @@ -1905,13 +1912,13 @@ public class VcnGatewayConnection extends StateMachine { } @VisibleForTesting(visibility = Visibility.PRIVATE) - boolean isRunning() { - return mIsRunning; + boolean isQuitting() { + return mIsQuitting; } @VisibleForTesting(visibility = Visibility.PRIVATE) - void setIsRunning(boolean isRunning) { - mIsRunning = isRunning; + void setIsQuitting(boolean isQuitting) { + mIsQuitting = isQuitting; } @VisibleForTesting(visibility = Visibility.PRIVATE) @@ -1924,6 +1931,14 @@ public class VcnGatewayConnection extends StateMachine { mIkeSession = session; } + @VisibleForTesting(visibility = Visibility.PRIVATE) + void sendDisconnectRequestedAndAcquireWakelock(String reason, boolean shouldQuit) { + sendMessageAndAcquireWakeLock( + EVENT_DISCONNECT_REQUESTED, + TOKEN_ALL, + new EventDisconnectRequestedInfo(reason, shouldQuit)); + } + private IkeSessionParams buildIkeParams() { // TODO: Implement this once IkeSessionParams is persisted return null; diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java index bfeec011a2c9..a90969599159 100644 --- a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java +++ b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java @@ -67,9 +67,7 @@ public class VcnNetworkProvider extends NetworkProvider { mListeners.add(listener); // Send listener all cached requests - for (NetworkRequestEntry entry : mRequests.values()) { - notifyListenerForEvent(listener, entry); - } + resendAllRequests(listener); } /** Unregisters the specified listener from receiving future NetworkRequests. */ @@ -78,6 +76,14 @@ public class VcnNetworkProvider extends NetworkProvider { mListeners.remove(listener); } + /** Sends all cached NetworkRequest(s) to the specified listener. */ + @VisibleForTesting(visibility = Visibility.PACKAGE) + public void resendAllRequests(@NonNull NetworkRequestListener listener) { + for (NetworkRequestEntry entry : mRequests.values()) { + notifyListenerForEvent(listener, entry); + } + } + private void notifyListenerForEvent( @NonNull NetworkRequestListener listener, @NonNull NetworkRequestEntry entry) { listener.onNetworkRequested(entry.mRequest, entry.mScore, entry.mProviderId); diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index 607da3ce6fe2..e84ee672bf0f 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -120,7 +120,9 @@ final class Vibration { if (newEffect.equals(mEffect)) { return; } - mOriginalEffect = mEffect; + if (mOriginalEffect == null) { + mOriginalEffect = mEffect; + } mEffect = newEffect; } diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 90a763c260f6..c9751bb7abe4 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -344,10 +344,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (!isEffectValid(effect)) { return; } - effect = fixupVibrationEffect(effect); attrs = fixupVibrationAttributes(attrs); Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs, uid, opPkg, reason); + // Update with fixed up effect to keep the original effect in Vibration for debugging. + vib.updateEffect(fixupVibrationEffect(effect)); synchronized (mLock) { Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib); @@ -1138,6 +1139,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { void dumpText(PrintWriter pw) { pw.println("Vibrator Manager Service:"); synchronized (mLock) { + pw.println(" mVibrationSettings:"); + pw.println(" " + mVibrationSettings); + pw.println(); pw.println(" mVibratorControllers:"); for (int i = 0; i < mVibrators.size(); i++) { pw.println(" " + mVibrators.valueAt(i)); @@ -1146,14 +1150,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { pw.println(" mCurrentVibration:"); pw.println(" " + (mCurrentVibration == null ? null : mCurrentVibration.getVibration().getDebugInfo())); + pw.println(); pw.println(" mNextVibration:"); pw.println(" " + (mNextVibration == null ? null : mNextVibration.getVibration().getDebugInfo())); + pw.println(); pw.println(" mCurrentExternalVibration:"); pw.println(" " + (mCurrentExternalVibration == null ? null : mCurrentExternalVibration.getDebugInfo())); pw.println(); - pw.println(" mVibrationSettings=" + mVibrationSettings); for (int i = 0; i < mPreviousVibrations.size(); i++) { pw.println(); pw.print(" Previous vibrations for usage "); diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 3bbc81a696e6..e6d37b60882e 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -1861,7 +1861,7 @@ final class AccessibilityController { } @Override - public boolean isEnabled() { + public boolean isAccessibilityTracingEnabled() { return mTracing.isEnabled(); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 33abcb478c77..55625809b632 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -132,7 +132,6 @@ import static com.android.server.wm.ActivityRecordProto.CLIENT_VISIBLE; import static com.android.server.wm.ActivityRecordProto.DEFER_HIDING_CLIENT; import static com.android.server.wm.ActivityRecordProto.FILLS_PARENT; import static com.android.server.wm.ActivityRecordProto.FRONT_OF_TASK; -import static com.android.server.wm.ActivityRecordProto.FROZEN_BOUNDS; import static com.android.server.wm.ActivityRecordProto.IS_ANIMATING; import static com.android.server.wm.ActivityRecordProto.IS_WAITING_FOR_TRANSITION_START; import static com.android.server.wm.ActivityRecordProto.LAST_ALL_DRAWN; @@ -215,6 +214,7 @@ import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_SO import static com.android.server.wm.WindowManagerService.MIN_TASK_LETTERBOX_ASPECT_RATIO; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES; +import static com.android.server.wm.WindowManagerService.letterboxBackgroundTypeToString; import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY; import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN; import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM; @@ -344,7 +344,6 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.lang.ref.WeakReference; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -582,9 +581,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private Task mLastParent; - // Have we told the window clients to show themselves? - private boolean mClientVisible; - boolean firstWindowDrawn; /** Whether the visible window(s) of this activity is drawn. */ private boolean mReportedDrawn; @@ -732,9 +728,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // windows, where the app hasn't had time to set a value on the window. int mRotationAnimationHint = -1; - ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>(); - ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>(); - private AppSaturationInfo mLastAppSaturationInfo; private final ColorDisplayService.ColorTransformController mColorTransformController = @@ -1003,7 +996,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.print(prefix); pw.print("mOrientation="); pw.println(ActivityInfo.screenOrientationToString(mOrientation)); pw.println(prefix + "mVisibleRequested=" + mVisibleRequested - + " mVisible=" + mVisible + " mClientVisible=" + mClientVisible + + " mVisible=" + mVisible + " mClientVisible=" + isClientVisible() + ((mDeferHidingClient) ? " mDeferHidingClient=" + mDeferHidingClient : "") + " reportedDrawn=" + mReportedDrawn + " reportedVisible=" + reportedVisible); if (paused) { @@ -1035,10 +1028,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.println(" mVisibleSetFromTransferredStartingWindow=" + mVisibleSetFromTransferredStartingWindow); } - if (!mFrozenBounds.isEmpty()) { - pw.print(prefix); pw.print("mFrozenBounds="); pw.println(mFrozenBounds); - pw.print(prefix); pw.print("mFrozenMergedConfig="); pw.println(mFrozenMergedConfig); - } if (mPendingRelaunchCount != 0) { pw.print(prefix); pw.print("mPendingRelaunchCount="); pw.println(mPendingRelaunchCount); } @@ -1089,6 +1078,46 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.println(prefix + "configChanges=0x" + Integer.toHexString(info.configChanges)); } } + + dumpLetterboxInfo(pw, prefix); + } + + private void dumpLetterboxInfo(PrintWriter pw, String prefix) { + final WindowState mainWin = findMainWindow(); + if (mainWin == null) { + return; + } + + boolean isLetterboxed = isLetterboxed(mainWin); + pw.println(prefix + "isLetterboxed=" + isLetterboxed); + if (!isLetterboxed) { + return; + } + + pw.println(prefix + " letterboxReason=" + getLetterboxReasonString(mainWin)); + pw.println(prefix + " letterboxBackgroundColor=" + Integer.toHexString( + getLetterboxBackgroundColor().toArgb())); + pw.println(prefix + " letterboxBackgroundType=" + + letterboxBackgroundTypeToString(mWmService.getLetterboxBackgroundType())); + pw.println(prefix + " letterboxAspectRatio=" + + computeAspectRatio(getBounds())); + } + + /** + * Returns a string representing the reason for letterboxing. This method assumes the activity + * is letterboxed. + */ + private String getLetterboxReasonString(WindowState mainWin) { + if (inSizeCompatMode()) { + return "SIZE_COMPAT_MODE"; + } + if (isLetterboxedForFixedOrientationAndAspectRatio()) { + return "FIXED_ORIENTATION"; + } + if (mainWin.isLetterboxedForDisplayCutout()) { + return "DISPLAY_CUTOUT"; + } + return "UNKNOWN_REASON"; } void setAppTimeTracker(AppTimeTracker att) { @@ -1704,7 +1733,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A keysPaused = false; inHistory = false; nowVisible = false; - mClientVisible = true; + super.setClientVisible(true); idle = false; hasBeenLaunched = false; mTaskSupervisor = supervisor; @@ -3359,56 +3388,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return mPendingRelaunchCount > 0; } - boolean shouldFreezeBounds() { - // For freeform windows, we can't freeze the bounds at the moment because this would make - // the resizing unresponsive. - if (task == null || task.inFreeformWindowingMode()) { - return false; - } - - // We freeze the bounds while drag resizing to deal with the time between - // the divider/drag handle being released, and the handling it's new - // configuration. If we are relaunched outside of the drag resizing state, - // we need to be careful not to do this. - return task.isDragResizing(); - } - @VisibleForTesting void startRelaunching() { if (mPendingRelaunchCount == 0) { mRelaunchStartTime = SystemClock.elapsedRealtime(); } - if (shouldFreezeBounds()) { - freezeBounds(); - } - clearAllDrawn(); mPendingRelaunchCount++; } - /** - * Freezes the task bounds. The size of this task reported the app will be fixed to the bounds - * freezed by {@link Task#prepareFreezingBounds} until {@link #unfreezeBounds} gets called, even - * if they change in the meantime. If the bounds are already frozen, the bounds will be frozen - * with a queue. - */ - private void freezeBounds() { - mFrozenBounds.offer(new Rect(task.mPreparedFrozenBounds)); - - if (task.mPreparedFrozenMergedConfig.equals(Configuration.EMPTY)) { - // We didn't call prepareFreezingBounds on the task, so use the current value. - mFrozenMergedConfig.offer(new Configuration(task.getConfiguration())); - } else { - mFrozenMergedConfig.offer(new Configuration(task.mPreparedFrozenMergedConfig)); - } - // Calling unset() to make it equal to Configuration.EMPTY. - task.mPreparedFrozenMergedConfig.unset(); - } - void finishRelaunching() { mTaskSupervisor.getActivityMetricsLogger().notifyActivityRelaunched(this); - unfreezeBounds(); if (mPendingRelaunchCount > 0) { mPendingRelaunchCount--; @@ -3430,30 +3421,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mPendingRelaunchCount == 0) { return; } - unfreezeBounds(); mPendingRelaunchCount = 0; mRelaunchStartTime = 0; } /** - * Unfreezes the previously frozen bounds. See {@link #freezeBounds}. - */ - private void unfreezeBounds() { - if (mFrozenBounds.isEmpty()) { - return; - } - mFrozenBounds.remove(); - if (!mFrozenMergedConfig.isEmpty()) { - mFrozenMergedConfig.remove(); - } - for (int i = mChildren.size() - 1; i >= 0; i--) { - final WindowState win = mChildren.get(i); - win.onUnfreezeBounds(); - } - mWmService.mWindowPlacerLocked.performSurfacePlacement(); - } - - /** * Perform clean-up of service connections in an activity record. */ private void cleanUpActivityServices() { @@ -3801,7 +3773,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A setVisibleRequested(true); mVisibleSetFromTransferredStartingWindow = true; } - setClientVisible(fromActivity.mClientVisible); + setClientVisible(fromActivity.isClientVisible()); if (fromActivity.isAnimating()) { transferAnimation(fromActivity); @@ -4449,6 +4421,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } mVisibleRequested = visible; + setInsetsFrozen(!visible); if (app != null) { mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */); } @@ -5901,18 +5874,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return mReportedDrawn; } - boolean isClientVisible() { - return mClientVisible; - } - + @Override void setClientVisible(boolean clientVisible) { - if (mClientVisible == clientVisible || (!clientVisible && mDeferHidingClient)) { - return; - } + // TODO(shell-transitions): Remove mDeferHidingClient once everything is shell-transitions. + // pip activities should just remain in clientVisible. + if (!clientVisible && mDeferHidingClient) return; ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "setClientVisible: %s clientVisible=%b Callers=%s", this, clientVisible, Debug.getCallers(5)); - mClientVisible = clientVisible; + super.setClientVisible(clientVisible); sendAppVisibilityToClients(); } @@ -7456,8 +7426,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final int containingAppWidth = containingAppBounds.width(); final int containingAppHeight = containingAppBounds.height(); - final float containingRatio = Math.max(containingAppWidth, containingAppHeight) - / (float) Math.min(containingAppWidth, containingAppHeight); + final float containingRatio = computeAspectRatio(containingAppBounds); int activityWidth = containingAppWidth; int activityHeight = containingAppHeight; @@ -7521,6 +7490,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } /** + * Returns the aspect ratio of the given {@code rect}. + */ + private static float computeAspectRatio(Rect rect) { + final int width = rect.width(); + final int height = rect.height(); + if (width == 0 || height == 0) { + return 0; + } + return Math.max(width, height) / (float) Math.min(width, height); + } + + /** * @return {@code true} if this activity was reparented to another display but * {@link #ensureActivityConfiguration} is not called. */ @@ -8199,7 +8180,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A proto.write(TRANSLUCENT, !occludesParent()); proto.write(VISIBLE, mVisible); proto.write(VISIBLE_REQUESTED, mVisibleRequested); - proto.write(CLIENT_VISIBLE, mClientVisible); + proto.write(CLIENT_VISIBLE, isClientVisible()); proto.write(DEFER_HIDING_CLIENT, mDeferHidingClient); proto.write(REPORTED_DRAWN, mReportedDrawn); proto.write(REPORTED_VISIBLE, reportedVisible); @@ -8214,9 +8195,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A proto.write(STARTING_MOVED, startingMoved); proto.write(VISIBLE_SET_FROM_TRANSFERRED_STARTING_WINDOW, mVisibleSetFromTransferredStartingWindow); - for (Rect bounds : mFrozenBounds) { - bounds.dumpDebug(proto, FROZEN_BOUNDS); - } proto.write(STATE, mState.toString()); proto.write(FRONT_OF_TASK, isRootOfTask()); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index a874dee2c768..0c77d9f1f724 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -4042,17 +4042,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale, boolean persistent, int userId) { - final DisplayContent defaultDisplay = - mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY); - mTempConfig.setTo(getGlobalConfiguration()); final int changes = mTempConfig.updateFrom(values); if (changes == 0) { - // Since calling to Activity.setRequestedOrientation leads to freezing the window with - // setting WindowManagerService.mWaitingForConfig to true, it is important that we call - // performDisplayOverrideConfigUpdate in order to send the new display configuration - // (even if there are no actual changes) to unfreeze the window. - defaultDisplay.performDisplayOverrideConfigUpdate(values); return 0; } diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index 309b5ec25f0f..62a00802896f 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -141,17 +141,20 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { mChangeListeners.get(i).onMergedOverrideConfigurationChanged( mMergedOverrideConfiguration); } - dispatchConfigurationToChildren(); - } - - void dispatchConfigurationToChildren() { for (int i = getChildCount() - 1; i >= 0; --i) { - final ConfigurationContainer child = getChildAt(i); - child.onConfigurationChanged(mFullConfiguration); + dispatchConfigurationToChild(getChildAt(i), mFullConfiguration); } } /** + * Dispatches the configuration to child when {@link #onConfigurationChanged(Configuration)} is + * called. This allows the derived classes to override how to dispatch the configuration. + */ + void dispatchConfigurationToChild(E child, Configuration config) { + child.onConfigurationChanged(config); + } + + /** * Resolves the current requested override configuration into * {@link #mResolvedOverrideConfiguration} * diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index 759b7fe054bc..5ccf576e1099 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -495,6 +495,21 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { return info; } + /** + * Gets the stable bounds of the DisplayArea, which is the bounds excluding insets for + * navigation bar, cutout, and status bar. + */ + void getStableRect(Rect out) { + if (mDisplayContent == null) { + getBounds(out); + return; + } + + // Intersect with the display stable bounds to get the DisplayArea stable bounds. + mDisplayContent.getStableRect(out); + out.intersect(getBounds()); + } + @Override public boolean providesMaxBounds() { return true; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index eff4ea6536bd..168ab43f4a9a 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -2677,6 +2677,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWmService.mDisplayWindowSettings.setForcedSize(this, width, height); } + @Override void getStableRect(Rect out) { final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState(); out.set(state.getDisplayFrame()); @@ -2943,10 +2944,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return dockFrame.bottom - imeFrame.top; } - void prepareFreezingTaskBounds() { - forAllRootTasks(Task::prepareFreezingTaskBounds); - } - void rotateBounds(@Rotation int oldRotation, @Rotation int newRotation, Rect inOutBounds) { // Get display bounds on oldRotation as parent bounds for the rotation. getBounds(mTmpRect, oldRotation); @@ -3703,8 +3700,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // app. assignWindowLayers(true /* setLayoutNeeded */); // 3. The z-order of IME might have been changed. Update the above insets state. - mInsetsStateController.updateAboveInsetsState( - mInputMethodWindow, true /* notifyInsetsChange */); + mInsetsStateController.updateAboveInsetsState(mInputMethodWindow, + mInsetsStateController.getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME)); // 4. Update the IME control target to apply any inset change and animation. // 5. Reparent the IME container surface to either the input target app, or the IME window // parent. @@ -4105,13 +4102,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ void onWindowAnimationFinished(@NonNull WindowContainer wc, int type) { if (type == ANIMATION_TYPE_APP_TRANSITION || type == ANIMATION_TYPE_RECENTS) { - // Unfreeze the insets state of the frozen target when the animation finished if exists. - final Task task = wc.asTask(); - if (task != null) { - task.forAllWindows(w -> { - w.clearFrozenInsetsState(); - }, true /* traverseTopToBottom */); - } removeImeSurfaceImmediately(); } } @@ -4833,7 +4823,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } mNoAnimationNotifyOnTransitionFinished.clear(); - mWallpaperController.hideDeferredWallpapersIfNeeded(); + mWallpaperController.hideDeferredWallpapersIfNeededLegacy(); onAppTransitionDone(); diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java index 1f7e1524b702..316c20ba5c47 100644 --- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java +++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java @@ -99,6 +99,9 @@ class EnsureActivitiesVisibleHelper { mTask.forAllActivities(a -> { setActivityVisibilityState(a, starting, resumeTopActivity); }); + if (mTask.mAtmService.getTransitionController().getTransitionPlayer() != null) { + mTask.getDisplayContent().mWallpaperController.adjustWallpaperWindows(); + } } private void setActivityVisibilityState(ActivityRecord r, ActivityRecord starting, diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 27ef147e2781..28c5a6d9323d 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -410,15 +410,19 @@ final class InputMonitor { return; } + requestFocus(focusToken, focus.getName()); + } + + private void requestFocus(IBinder focusToken, String windowName) { if (focusToken == mInputFocus) { return; } mInputFocus = focusToken; - mInputTransaction.setFocusedWindow(mInputFocus, focus.getName(), mDisplayId); - EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Focus request " + focus, + mInputTransaction.setFocusedWindow(mInputFocus, windowName, mDisplayId); + EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Focus request " + windowName, "reason=UpdateInputWindows"); - ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Focus requested for window=%s", focus); + ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Focus requested for window=%s", windowName); } void setFocusedAppLw(ActivityRecord newApp) { @@ -470,6 +474,8 @@ final class InputMonitor { boolean mInDrag; + private boolean mRecentsAnimationFocusOverride; + private void updateInputWindows(boolean inDrag) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateInputWindows"); @@ -485,10 +491,16 @@ final class InputMonitor { mInDrag = inDrag; resetInputConsumers(mInputTransaction); - + mRecentsAnimationFocusOverride = false; mDisplayContent.forAllWindows(this, true /* traverseTopToBottom */); - updateInputFocusRequest(); + if (mRecentsAnimationFocusOverride) { + requestFocus(mRecentsAnimationInputConsumer.mWindowHandle.token, + mRecentsAnimationInputConsumer.mName); + } else { + updateInputFocusRequest(); + } + if (!mUpdateInputWindowsImmediately) { mDisplayContent.getPendingTransaction().merge(mInputTransaction); @@ -526,6 +538,7 @@ final class InputMonitor { mRecentsAnimationInputConsumer.mWindowHandle)) { mRecentsAnimationInputConsumer.show(mInputTransaction, w.mActivityRecord); mAddRecentsAnimationInputConsumerHandle = false; + mRecentsAnimationFocusOverride = true; } } diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java index e18516d7bc3a..62c155a3c198 100644 --- a/services/core/java/com/android/server/wm/LockTaskController.java +++ b/services/core/java/com/android/server/wm/LockTaskController.java @@ -540,7 +540,7 @@ public class LockTaskController { setStatusBarState(mLockTaskModeState, userId); setKeyguardState(mLockTaskModeState, userId); if (oldLockTaskModeState == LOCK_TASK_MODE_PINNED) { - lockKeyguardIfNeeded(); + lockKeyguardIfNeeded(userId); } if (getDevicePolicyManager() != null) { getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId); @@ -886,15 +886,15 @@ public class LockTaskController { * Helper method for locking the device immediately. This may be necessary when the device * leaves the pinned mode. */ - private void lockKeyguardIfNeeded() { - if (shouldLockKeyguard()) { + private void lockKeyguardIfNeeded(int userId) { + if (shouldLockKeyguard(userId)) { mWindowManager.lockNow(null); mWindowManager.dismissKeyguard(null /* callback */, null /* message */); getLockPatternUtils().requireCredentialEntry(USER_ALL); } } - private boolean shouldLockKeyguard() { + private boolean shouldLockKeyguard(int userId) { // This functionality should be kept consistent with // com.android.settings.security.ScreenPinningSettings (see b/127605586) try { @@ -904,7 +904,7 @@ public class LockTaskController { } catch (Settings.SettingNotFoundException e) { // Log to SafetyNet for b/127605586 android.util.EventLog.writeEvent(0x534e4554, "127605586", -1, ""); - return getLockPatternUtils().isSecure(USER_CURRENT); + return getLockPatternUtils().isSecure(userId); } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 3f9ea1fd2afd..0e8cadbcbcdd 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -650,28 +650,12 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } @Override - void dispatchConfigurationToChildren() { - final Configuration configuration = getConfiguration(); - for (int i = getChildCount() - 1; i >= 0; i--) { - final DisplayContent displayContent = getChildAt(i); - if (displayContent.isDefaultDisplay) { - // The global configuration is also the override configuration of default display. - displayContent.performDisplayOverrideConfigUpdate(configuration); - } else { - displayContent.onConfigurationChanged(configuration); - } - } - } - - @Override - public void onConfigurationChanged(Configuration newParentConfig) { - prepareFreezingTaskBounds(); - super.onConfigurationChanged(newParentConfig); - } - - private void prepareFreezingTaskBounds() { - for (int i = mChildren.size() - 1; i >= 0; i--) { - mChildren.get(i).prepareFreezingTaskBounds(); + void dispatchConfigurationToChild(DisplayContent child, Configuration config) { + if (child.isDefaultDisplay) { + // The global configuration is also the override configuration of default display. + child.performDisplayOverrideConfigUpdate(config); + } else { + child.onConfigurationChanged(config); } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 80173b5942e6..5efbb0956823 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -498,9 +498,6 @@ class Task extends WindowContainer<WindowContainer> { // TODO: Make final int mUserId; - final Rect mPreparedFrozenBounds = new Rect(); - final Configuration mPreparedFrozenMergedConfig = new Configuration(); - // Id of the previous display the root task was on. int mPrevDisplayId = INVALID_DISPLAY; @@ -1182,10 +1179,6 @@ class Task extends WindowContainer<WindowContainer> { mTaskSupervisor.mNoAnimActivities.add(topActivity); } - // We might trigger a configuration change. Save the current task bounds for freezing. - // TODO: Should this call be moved inside the resize method in WM? - toRootTask.prepareFreezingTaskBounds(); - if (toRootTaskWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && moveRootTaskMode == REPARENT_KEEP_ROOT_TASK_AT_FRONT) { // Move recents to front so it is not behind root home task when going into docked @@ -3363,15 +3356,6 @@ class Task extends WindowContainer<WindowContainer> { return isResizeable(); } - /** - * Prepares the task bounds to be frozen with the current size. See - * {@link ActivityRecord#freezeBounds}. - */ - void prepareFreezingBounds() { - mPreparedFrozenBounds.set(getBounds()); - mPreparedFrozenMergedConfig.setTo(getConfiguration()); - } - @Override void getAnimationFrames(Rect outFrame, Rect outInsets, Rect outStableInsets, Rect outSurfaceInsets) { @@ -5058,6 +5042,10 @@ class Task extends WindowContainer<WindowContainer> { } } else { // No longer managed by any organizer. + final TaskDisplayArea taskDisplayArea = getDisplayArea(); + if (taskDisplayArea != null) { + taskDisplayArea.removeLaunchRootTask(this); + } setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, false /* set */); if (mCreatedByOrganizer) { removeImmediately("setTaskOrganizer"); @@ -7499,10 +7487,6 @@ class Task extends WindowContainer<WindowContainer> { }); } - void prepareFreezingTaskBounds() { - forAllLeafTasks(Task::prepareFreezingBounds, true /* traverseTopToBottom */); - } - private int setBounds(Rect existing, Rect bounds) { if (equivalentBounds(existing, bounds)) { return BOUNDS_CHANGE_NONE; diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index badd7fda2897..76869e548fce 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -1135,12 +1135,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { "Can't set not mCreatedByOrganizer as launch root tr=" + rootTask); } - LaunchRootTaskDef def = null; - for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) { - if (mLaunchRootTasks.get(i).task.mTaskId != rootTask.mTaskId) continue; - def = mLaunchRootTasks.get(i); - } - + LaunchRootTaskDef def = getLaunchRootTaskDef(rootTask); if (def != null) { // Remove so we add to the end of the list. mLaunchRootTasks.remove(def); @@ -1156,6 +1151,23 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } } + void removeLaunchRootTask(Task rootTask) { + LaunchRootTaskDef def = getLaunchRootTaskDef(rootTask); + if (def != null) { + mLaunchRootTasks.remove(def); + } + } + + private @Nullable LaunchRootTaskDef getLaunchRootTaskDef(Task rootTask) { + LaunchRootTaskDef def = null; + for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) { + if (mLaunchRootTasks.get(i).task.mTaskId != rootTask.mTaskId) continue; + def = mLaunchRootTasks.get(i); + break; + } + return def; + } + Task getLaunchRootTask(int windowingMode, int activityType, ActivityOptions options) { // Try to use the launch root task in options if available. if (options != null) { diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java index f3b69e30b40a..ee5c1f014895 100644 --- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java @@ -165,6 +165,10 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { // hasInitialBounds is set if either activity options or layout has specified bounds. If // that's set we'll skip some adjustments later to avoid overriding the initial bounds. boolean hasInitialBounds = false; + // hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow is set if the outParams.mBounds + // is set with the suggestedDisplayArea. If it is set, but the eventual TaskDisplayArea is + // different, we should recalculating the bounds. + boolean hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow = false; final boolean canApplyFreeformPolicy = canApplyFreeformWindowPolicy(display, launchMode); if (mSupervisor.canUseActivityOptionsLaunchBounds(options) && (canApplyFreeformPolicy || canApplyPipWindowPolicy(launchMode))) { @@ -180,11 +184,13 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { } else if (launchMode == WINDOWING_MODE_FULLSCREEN) { if (DEBUG) appendLog("activity-options-fullscreen=" + outParams.mBounds); } else if (layout != null && canApplyFreeformPolicy) { - getLayoutBounds(display, root, layout, mTmpBounds); + mTmpBounds.set(currentParams.mBounds); + getLayoutBounds(suggestedDisplayArea, root, layout, mTmpBounds); if (!mTmpBounds.isEmpty()) { launchMode = WINDOWING_MODE_FREEFORM; outParams.mBounds.set(mTmpBounds); hasInitialBounds = true; + hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow = true; if (DEBUG) appendLog("bounds-from-layout=" + outParams.mBounds); } else { if (DEBUG) appendLog("empty-window-layout"); @@ -240,6 +246,11 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { // such as orientation. Otherwise, the app is forcefully launched in maximized. The rest of // this step is to define the default policy when there is no initial bounds or a fully // resolved current params from callers. + + // hasInitialBoundsForSuggestedDisplayAreaInFreeformDisplay is set if the outParams.mBounds + // is set with the suggestedDisplayArea. If it is set, but the eventual TaskDisplayArea is + // different, we should recalcuating the bounds. + boolean hasInitialBoundsForSuggestedDisplayAreaInFreeformDisplay = false; if (display.inFreeformWindowingMode()) { if (launchMode == WINDOWING_MODE_PINNED) { if (DEBUG) appendLog("picture-in-picture"); @@ -247,8 +258,9 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { if (shouldLaunchUnresizableAppInFreeform(root, suggestedDisplayArea)) { launchMode = WINDOWING_MODE_FREEFORM; if (outParams.mBounds.isEmpty()) { - getTaskBounds(root, display, layout, launchMode, hasInitialBounds, - outParams.mBounds); + getTaskBounds(root, suggestedDisplayArea, layout, launchMode, + hasInitialBounds, outParams.mBounds); + hasInitialBoundsForSuggestedDisplayAreaInFreeformDisplay = true; } if (DEBUG) appendLog("unresizable-freeform"); } else { @@ -287,6 +299,20 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { mTmpDisplayArea = displayArea; return true; }); + // We may need to recalculate the bounds if the new TaskDisplayArea is different from + // the suggested one we used to calculate the bounds. + if (mTmpDisplayArea != null && mTmpDisplayArea != suggestedDisplayArea) { + if (hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow) { + outParams.mBounds.setEmpty(); + getLayoutBounds(mTmpDisplayArea, root, layout, outParams.mBounds); + hasInitialBounds = !outParams.mBounds.isEmpty(); + } else if (hasInitialBoundsForSuggestedDisplayAreaInFreeformDisplay) { + outParams.mBounds.setEmpty(); + getTaskBounds(root, mTmpDisplayArea, layout, launchMode, + hasInitialBounds, outParams.mBounds); + } + } + if (mTmpDisplayArea != null) { taskDisplayArea = mTmpDisplayArea; mTmpDisplayArea = null; @@ -302,7 +328,6 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { if (phase == PHASE_DISPLAY_AREA) { return RESULT_CONTINUE; } - // TODO(b/152116619): Update the usages of display to use taskDisplayArea below. // STEP 4: Determine final launch bounds based on resolved windowing mode and activity // requested orientation. We set bounds to empty for fullscreen mode and keep bounds as is @@ -312,13 +337,13 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { // We skip making adjustments if the params are fully resolved from previous results. if (fullyResolvedCurrentParam) { if (resolvedMode == WINDOWING_MODE_FREEFORM) { - // Make sure bounds are in the display if it's possibly in a different display/area. + // Make sure bounds are in the displayArea. if (currentParams.mPreferredTaskDisplayArea != taskDisplayArea) { - adjustBoundsToFitInDisplay(display, outParams.mBounds); + adjustBoundsToFitInDisplayArea(taskDisplayArea, outParams.mBounds); } // Even though we want to keep original bounds, we still don't want it to stomp on // an existing task. - adjustBoundsToAvoidConflictInDisplay(display, outParams.mBounds); + adjustBoundsToAvoidConflictInDisplayArea(taskDisplayArea, outParams.mBounds); } } else if (taskDisplayArea.inFreeformWindowingMode()) { if (source != null && source.inFreeformWindowingMode() @@ -327,9 +352,10 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { && source.getDisplayArea() == taskDisplayArea) { // Set bounds to be not very far from source activity. cascadeBounds(source.getConfiguration().windowConfiguration.getBounds(), - display, outParams.mBounds); + taskDisplayArea, outParams.mBounds); } - getTaskBounds(root, display, layout, resolvedMode, hasInitialBounds, outParams.mBounds); + getTaskBounds(root, taskDisplayArea, layout, resolvedMode, hasInitialBounds, + outParams.mBounds); } return RESULT_CONTINUE; } @@ -499,30 +525,36 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { && launchMode == WINDOWING_MODE_PINNED; } - private void getLayoutBounds(@NonNull DisplayContent display, @NonNull ActivityRecord root, - @NonNull ActivityInfo.WindowLayout windowLayout, @NonNull Rect outBounds) { + private void getLayoutBounds(@NonNull TaskDisplayArea displayArea, @NonNull ActivityRecord root, + @NonNull ActivityInfo.WindowLayout windowLayout, @NonNull Rect inOutBounds) { final int verticalGravity = windowLayout.gravity & Gravity.VERTICAL_GRAVITY_MASK; final int horizontalGravity = windowLayout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; if (!windowLayout.hasSpecifiedSize() && verticalGravity == 0 && horizontalGravity == 0) { - outBounds.setEmpty(); + inOutBounds.setEmpty(); return; } // Use stable frame instead of raw frame to avoid launching freeform windows on top of // stable insets, which usually are system widgets such as sysbar & navbar. - final Rect displayStableBounds = mTmpStableBounds; - display.getStableRect(displayStableBounds); - final int defaultWidth = displayStableBounds.width(); - final int defaultHeight = displayStableBounds.height(); + final Rect stableBounds = mTmpStableBounds; + displayArea.getStableRect(stableBounds); + final int defaultWidth = stableBounds.width(); + final int defaultHeight = stableBounds.height(); int width; int height; if (!windowLayout.hasSpecifiedSize()) { - outBounds.setEmpty(); - getTaskBounds(root, display, windowLayout, WINDOWING_MODE_FREEFORM, - /* hasInitialBounds */ false, outBounds); - width = outBounds.width(); - height = outBounds.height(); + if (!inOutBounds.isEmpty()) { + // If the bounds is resolved already and WindowLayout doesn't have any opinion on + // its size, use the already resolved size and apply the gravity to it. + width = inOutBounds.width(); + height = inOutBounds.height(); + } else { + getTaskBounds(root, displayArea, windowLayout, WINDOWING_MODE_FREEFORM, + /* hasInitialBounds */ false, inOutBounds); + width = inOutBounds.width(); + height = inOutBounds.height(); + } } else { width = defaultWidth; if (windowLayout.width > 0 && windowLayout.width < defaultWidth) { @@ -563,11 +595,11 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { fractionOfVerticalOffset = 0.5f; } - outBounds.set(0, 0, width, height); - outBounds.offset(displayStableBounds.left, displayStableBounds.top); + inOutBounds.set(0, 0, width, height); + inOutBounds.offset(stableBounds.left, stableBounds.top); final int xOffset = (int) (fractionOfHorizontalOffset * (defaultWidth - width)); final int yOffset = (int) (fractionOfVerticalOffset * (defaultHeight - height)); - outBounds.offset(xOffset, yOffset); + inOutBounds.offset(xOffset, yOffset); } private boolean shouldLaunchUnresizableAppInFreeform(ActivityRecord activity, @@ -575,13 +607,9 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { if (!mSupervisor.mService.mSupportsNonResizableMultiWindow || activity.isResizeable()) { return false; } - final DisplayContent display = displayArea.getDisplayContent(); - if (display == null) { - return false; - } final int displayOrientation = orientationFromBounds(displayArea.getBounds()); - final int activityOrientation = resolveOrientation(activity, display, + final int activityOrientation = resolveOrientation(activity, displayArea, displayArea.getBounds()); if (displayArea.getWindowingMode() == WINDOWING_MODE_FREEFORM && displayOrientation != activityOrientation) { @@ -631,19 +659,19 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { return orientation; } - private void cascadeBounds(@NonNull Rect srcBounds, @NonNull DisplayContent display, + private void cascadeBounds(@NonNull Rect srcBounds, @NonNull TaskDisplayArea displayArea, @NonNull Rect outBounds) { outBounds.set(srcBounds); - float density = (float) display.getConfiguration().densityDpi / DENSITY_DEFAULT; + float density = (float) displayArea.getConfiguration().densityDpi / DENSITY_DEFAULT; final int defaultOffset = (int) (CASCADING_OFFSET_DP * density + 0.5f); - display.getBounds(mTmpBounds); + displayArea.getBounds(mTmpBounds); final int dx = Math.min(defaultOffset, Math.max(0, mTmpBounds.right - srcBounds.right)); final int dy = Math.min(defaultOffset, Math.max(0, mTmpBounds.bottom - srcBounds.bottom)); outBounds.offset(dx, dy); } - private void getTaskBounds(@NonNull ActivityRecord root, @NonNull DisplayContent display, + private void getTaskBounds(@NonNull ActivityRecord root, @NonNull TaskDisplayArea displayArea, @NonNull ActivityInfo.WindowLayout layout, int resolvedMode, boolean hasInitialBounds, @NonNull Rect inOutBounds) { if (resolvedMode == WINDOWING_MODE_FULLSCREEN) { @@ -662,7 +690,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { return; } - final int orientation = resolveOrientation(root, display, inOutBounds); + final int orientation = resolveOrientation(root, displayArea, inOutBounds); if (orientation != SCREEN_ORIENTATION_PORTRAIT && orientation != SCREEN_ORIENTATION_LANDSCAPE) { throw new IllegalStateException( @@ -671,7 +699,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { } // First we get the default size we want. - getDefaultFreeformSize(display, layout, orientation, mTmpBounds); + getDefaultFreeformSize(displayArea, layout, orientation, mTmpBounds); if (hasInitialBounds || sizeMatches(inOutBounds, mTmpBounds)) { // We're here because either input parameters specified initial bounds, or the suggested // bounds have the same size of the default freeform size. We should use the suggested @@ -681,22 +709,24 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { if (DEBUG) appendLog("freeform-size-orientation-match=" + inOutBounds); } else { // Meh, orientation doesn't match. Let's rotate inOutBounds in-place. - centerBounds(display, inOutBounds.height(), inOutBounds.width(), inOutBounds); + centerBounds(displayArea, inOutBounds.height(), inOutBounds.width(), + inOutBounds); if (DEBUG) appendLog("freeform-orientation-mismatch=" + inOutBounds); } } else { // We are here either because there is no suggested bounds, or the suggested bounds is // a cascade from source activity. We should use the default freeform size and center it - // to the center of suggested bounds (or the display if no suggested bounds). The - // default size might be too big to center to source activity bounds in display, so we - // may need to move it back to the display. - centerBounds(display, mTmpBounds.width(), mTmpBounds.height(), inOutBounds); - adjustBoundsToFitInDisplay(display, inOutBounds); + // to the center of suggested bounds (or the displayArea if no suggested bounds). The + // default size might be too big to center to source activity bounds in displayArea, so + // we may need to move it back to the displayArea. + centerBounds(displayArea, mTmpBounds.width(), mTmpBounds.height(), + inOutBounds); + adjustBoundsToFitInDisplayArea(displayArea, inOutBounds); if (DEBUG) appendLog("freeform-size-mismatch=" + inOutBounds); } // Lastly we adjust bounds to avoid conflicts with other tasks as much as possible. - adjustBoundsToAvoidConflictInDisplay(display, inOutBounds); + adjustBoundsToAvoidConflictInDisplayArea(displayArea, inOutBounds); } private int convertOrientationToScreenOrientation(int orientation) { @@ -710,13 +740,14 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { } } - private int resolveOrientation(@NonNull ActivityRecord root, @NonNull DisplayContent display, - @NonNull Rect bounds) { + private int resolveOrientation(@NonNull ActivityRecord root, + @NonNull TaskDisplayArea displayArea, @NonNull Rect bounds) { int orientation = resolveOrientation(root); if (orientation == SCREEN_ORIENTATION_LOCKED) { orientation = bounds.isEmpty() - ? convertOrientationToScreenOrientation(display.getConfiguration().orientation) + ? convertOrientationToScreenOrientation( + displayArea.getConfiguration().orientation) : orientationFromBounds(bounds); if (DEBUG) { appendLog(bounds.isEmpty() ? "locked-orientation-from-display=" + orientation @@ -736,19 +767,17 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { return orientation; } - private void getDefaultFreeformSize(@NonNull DisplayContent display, + private void getDefaultFreeformSize(@NonNull TaskDisplayArea displayArea, @NonNull ActivityInfo.WindowLayout layout, int orientation, @NonNull Rect bounds) { - // Default size, which is letterboxing/pillarboxing in display. That's to say the large - // dimension of default size is the small dimension of display size, and the small dimension - // of default size is calculated to keep the same aspect ratio as the display's. Here we use - // stable bounds of displays because that indicates the area that isn't occupied by system - // widgets (e.g. sysbar and navbar). - final Rect displayStableBounds = mTmpStableBounds; - display.getStableRect(displayStableBounds); - final int portraitHeight = - Math.min(displayStableBounds.width(), displayStableBounds.height()); - final int otherDimension = - Math.max(displayStableBounds.width(), displayStableBounds.height()); + // Default size, which is letterboxing/pillarboxing in displayArea. That's to say the large + // dimension of default size is the small dimension of displayArea size, and the small + // dimension of default size is calculated to keep the same aspect ratio as the + // displayArea's. Here we use stable bounds of displayArea because that indicates the area + // that isn't occupied by system widgets (e.g. sysbar and navbar). + final Rect stableBounds = mTmpStableBounds; + displayArea.getStableRect(stableBounds); + final int portraitHeight = Math.min(stableBounds.width(), stableBounds.height()); + final int otherDimension = Math.max(stableBounds.width(), stableBounds.height()); final int portraitWidth = (portraitHeight * portraitHeight) / otherDimension; final int defaultWidth = (orientation == SCREEN_ORIENTATION_LANDSCAPE) ? portraitHeight : portraitWidth; @@ -757,7 +786,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { // Get window size based on Nexus 5x screen, we assume that this is enough to show content // of activities. - final float density = (float) display.getConfiguration().densityDpi / DENSITY_DEFAULT; + final float density = (float) displayArea.getConfiguration().densityDpi / DENSITY_DEFAULT; final int phonePortraitWidth = (int) (DEFAULT_PORTRAIT_PHONE_WIDTH_DP * density + 0.5f); final int phonePortraitHeight = (int) (DEFAULT_PORTRAIT_PHONE_HEIGHT_DP * density + 0.5f); final int phoneWidth = (orientation == SCREEN_ORIENTATION_LANDSCAPE) ? phonePortraitHeight @@ -774,83 +803,83 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { final int height = Math.min(defaultHeight, Math.max(phoneHeight, layoutMinHeight)); bounds.set(0, 0, width, height); - bounds.offset(displayStableBounds.left, displayStableBounds.top); + bounds.offset(stableBounds.left, stableBounds.top); } /** * Gets centered bounds of width x height. If inOutBounds is not empty, the result bounds - * centers at its center or display's app bounds center if inOutBounds is empty. + * centers at its center or displayArea's app bounds center if inOutBounds is empty. */ - private void centerBounds(@NonNull DisplayContent display, int width, int height, + private void centerBounds(@NonNull TaskDisplayArea displayArea, int width, int height, @NonNull Rect inOutBounds) { if (inOutBounds.isEmpty()) { - display.getStableRect(inOutBounds); + displayArea.getStableRect(inOutBounds); } final int left = inOutBounds.centerX() - width / 2; final int top = inOutBounds.centerY() - height / 2; inOutBounds.set(left, top, left + width, top + height); } - private void adjustBoundsToFitInDisplay(@NonNull DisplayContent display, + private void adjustBoundsToFitInDisplayArea(@NonNull TaskDisplayArea displayArea, @NonNull Rect inOutBounds) { - final Rect displayStableBounds = mTmpStableBounds; - display.getStableRect(displayStableBounds); + final Rect stableBounds = mTmpStableBounds; + displayArea.getStableRect(stableBounds); - if (displayStableBounds.width() < inOutBounds.width() - || displayStableBounds.height() < inOutBounds.height()) { - // There is no way for us to fit the bounds in the display without changing width - // or height. Just move the start to align with the display. + if (stableBounds.width() < inOutBounds.width() + || stableBounds.height() < inOutBounds.height()) { + // There is no way for us to fit the bounds in the displayArea without changing width + // or height. Just move the start to align with the displayArea. final int layoutDirection = mSupervisor.mRootWindowContainer.getConfiguration().getLayoutDirection(); final int left = layoutDirection == View.LAYOUT_DIRECTION_RTL - ? displayStableBounds.right - inOutBounds.right + inOutBounds.left - : displayStableBounds.left; - inOutBounds.offsetTo(left, displayStableBounds.top); + ? stableBounds.right - inOutBounds.right + inOutBounds.left + : stableBounds.left; + inOutBounds.offsetTo(left, stableBounds.top); return; } final int dx; - if (inOutBounds.right > displayStableBounds.right) { - // Right edge is out of display. - dx = displayStableBounds.right - inOutBounds.right; - } else if (inOutBounds.left < displayStableBounds.left) { - // Left edge is out of display. - dx = displayStableBounds.left - inOutBounds.left; + if (inOutBounds.right > stableBounds.right) { + // Right edge is out of displayArea. + dx = stableBounds.right - inOutBounds.right; + } else if (inOutBounds.left < stableBounds.left) { + // Left edge is out of displayArea. + dx = stableBounds.left - inOutBounds.left; } else { - // Vertical edges are all in display. + // Vertical edges are all in displayArea. dx = 0; } final int dy; - if (inOutBounds.top < displayStableBounds.top) { - // Top edge is out of display. - dy = displayStableBounds.top - inOutBounds.top; - } else if (inOutBounds.bottom > displayStableBounds.bottom) { - // Bottom edge is out of display. - dy = displayStableBounds.bottom - inOutBounds.bottom; + if (inOutBounds.top < stableBounds.top) { + // Top edge is out of displayArea. + dy = stableBounds.top - inOutBounds.top; + } else if (inOutBounds.bottom > stableBounds.bottom) { + // Bottom edge is out of displayArea. + dy = stableBounds.bottom - inOutBounds.bottom; } else { - // Horizontal edges are all in display. + // Horizontal edges are all in displayArea. dy = 0; } inOutBounds.offset(dx, dy); } /** - * Adjusts input bounds to avoid conflict with existing tasks in the display. + * Adjusts input bounds to avoid conflict with existing tasks in the displayArea. * * If the input bounds conflict with existing tasks, this method scans the bounds in a series of - * directions to find a location where the we can put the bounds in display without conflict + * directions to find a location where the we can put the bounds in displayArea without conflict * with any other tasks. * - * It doesn't try to adjust bounds that's not fully in the given display. + * It doesn't try to adjust bounds that's not fully in the given displayArea. * - * @param display the display which tasks are to check + * @param displayArea the displayArea which tasks are to check * @param inOutBounds the bounds used to input initial bounds and output result bounds */ - private void adjustBoundsToAvoidConflictInDisplay(@NonNull DisplayContent display, + private void adjustBoundsToAvoidConflictInDisplayArea(@NonNull TaskDisplayArea displayArea, @NonNull Rect inOutBounds) { final List<Rect> taskBoundsToCheck = new ArrayList<>(); - display.forAllRootTasks(task -> { + displayArea.forAllRootTasks(task -> { if (!task.inFreeformWindowingMode()) { return; } @@ -859,28 +888,28 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { taskBoundsToCheck.add(task.getChildAt(j).getBounds()); } }, false /* traverseTopToBottom */); - adjustBoundsToAvoidConflict(display.getBounds(), taskBoundsToCheck, inOutBounds); + adjustBoundsToAvoidConflict(displayArea.getBounds(), taskBoundsToCheck, inOutBounds); } /** - * Adjusts input bounds to avoid conflict with provided display bounds and list of tasks bounds - * for the display. + * Adjusts input bounds to avoid conflict with provided displayArea bounds and list of tasks + * bounds for the displayArea. * * Scans the bounds in directions to find a candidate location that does not conflict with the - * provided list of task bounds. If starting bounds are outside the display bounds or if no + * provided list of task bounds. If starting bounds are outside the displayArea bounds or if no * suitable candidate bounds are found, the method returns the input bounds. * - * @param displayBounds display bounds used to restrict the candidate bounds + * @param displayAreaBounds displayArea bounds used to restrict the candidate bounds * @param taskBoundsToCheck list of task bounds to check for conflict * @param inOutBounds the bounds used to input initial bounds and output result bounds */ @VisibleForTesting - void adjustBoundsToAvoidConflict(@NonNull Rect displayBounds, + void adjustBoundsToAvoidConflict(@NonNull Rect displayAreaBounds, @NonNull List<Rect> taskBoundsToCheck, @NonNull Rect inOutBounds) { - if (!displayBounds.contains(inOutBounds)) { - // The initial bounds are already out of display. The scanning algorithm below doesn't - // work so well with them. + if (!displayAreaBounds.contains(inOutBounds)) { + // The initial bounds are already out of displayArea. The scanning algorithm below + // doesn't work so well with them. return; } @@ -890,7 +919,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { return; } - calculateCandidateShiftDirections(displayBounds, inOutBounds); + calculateCandidateShiftDirections(displayAreaBounds, inOutBounds); for (int direction : mTmpDirections) { if (direction == Gravity.NO_GRAVITY) { // We exhausted candidate directions, give up. @@ -899,12 +928,12 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { mTmpBounds.set(inOutBounds); while (boundsConflict(taskBoundsToCheck, mTmpBounds) - && displayBounds.contains(mTmpBounds)) { - shiftBounds(direction, displayBounds, mTmpBounds); + && displayAreaBounds.contains(mTmpBounds)) { + shiftBounds(direction, displayAreaBounds, mTmpBounds); } if (!boundsConflict(taskBoundsToCheck, mTmpBounds) - && displayBounds.contains(mTmpBounds)) { + && displayAreaBounds.contains(mTmpBounds)) { // Found a candidate. Just use this. inOutBounds.set(mTmpBounds); if (DEBUG) appendLog("avoid-bounds-conflict=" + inOutBounds); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 98eb11f8a970..ee4c66d05eb4 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -135,6 +135,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mSyncId = mSyncEngine.startSyncSet(this); } + @VisibleForTesting + int getSyncId() { + return mSyncId; + } + /** * Formally starts the transition. Participants can be collected before this is started, * but this won't consider itself ready until started -- even if all the participants have @@ -271,12 +276,17 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // Commit all going-invisible containers for (int i = 0; i < mParticipants.size(); ++i) { final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); - if (ar == null || ar.mVisibleRequested) { - continue; + if (ar != null && !ar.isVisibleRequested()) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " Commit activity becoming invisible: %s", ar); + ar.commitVisibility(false /* visible */, false /* performLayout */); + } + final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken(); + if (wt != null && !wt.isVisibleRequested()) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " Commit wallpaper becoming invisible: %s", ar); + wt.commitVisibility(false /* visible */); } - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, - " Commit activity becoming invisible: %s", ar); - ar.commitVisibility(false /* visible */, false /* performLayout */); } } diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 1a3138d492c8..7c5afa8282ee 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -126,12 +126,18 @@ class WallpaperController { } mFindResults.resetTopWallpaper = true; - if (w.mActivityRecord != null && !w.mActivityRecord.isVisible() - && !w.mActivityRecord.isAnimating(TRANSITION | PARENTS)) { - - // If this window's app token is hidden and not animating, it is of no interest to us. - if (DEBUG_WALLPAPER) Slog.v(TAG, "Skipping hidden and not animating token: " + w); - return false; + if (mService.mAtmService.getTransitionController().getTransitionPlayer() == null) { + if (w.mActivityRecord != null && !w.mActivityRecord.isVisible() + && !w.mActivityRecord.isAnimating(TRANSITION | PARENTS)) { + // If this window's app token is hidden and not animating, it is of no interest. + if (DEBUG_WALLPAPER) Slog.v(TAG, "Skipping hidden and not animating token: " + w); + return false; + } + } else { + if (w.mActivityRecord != null && !w.mActivityRecord.isVisibleRequested()) { + // An activity that is not going to remain visible shouldn't be the target. + return false; + } } if (DEBUG_WALLPAPER) Slog.v(TAG, "Win " + w + ": isOnScreen=" + w.isOnScreen() + " mDrawState=" + w.mWinAnimator.mDrawState); @@ -227,7 +233,10 @@ class WallpaperController { } boolean isWallpaperVisible() { - return isWallpaperVisible(mWallpaperTarget); + for (int i = mWallpaperTokens.size() - 1; i >= 0; --i) { + if (mWallpaperTokens.get(i).isVisible()) return true; + } + return false; } /** @@ -240,7 +249,7 @@ class WallpaperController { } } - private boolean isWallpaperVisible(WindowState wallpaperTarget) { + private boolean shouldWallpaperBeVisible(WindowState wallpaperTarget) { if (DEBUG_WALLPAPER) { Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + " prev=" + mPrevWallpaperTarget); @@ -255,18 +264,18 @@ class WallpaperController { } void updateWallpaperVisibility() { - final boolean visible = isWallpaperVisible(mWallpaperTarget); + final boolean visible = shouldWallpaperBeVisible(mWallpaperTarget); for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx); - token.updateWallpaperVisibility(visible); + token.setVisibility(visible); } } - void hideDeferredWallpapersIfNeeded() { - if (mDeferredHideWallpaper != null) { - hideWallpapers(mDeferredHideWallpaper); - mDeferredHideWallpaper = null; + void hideDeferredWallpapersIfNeededLegacy() { + for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) { + final WallpaperWindowToken token = mWallpaperTokens.get(i); + token.commitVisibility(false); } } @@ -275,18 +284,9 @@ class WallpaperController { && (mWallpaperTarget != winGoingAway || mPrevWallpaperTarget != null)) { return; } - if (mWallpaperTarget != null - && mWallpaperTarget.getDisplayContent().mAppTransition.isRunning()) { - // Defer hiding the wallpaper when app transition is running until the animations - // are done. - mDeferredHideWallpaper = winGoingAway; - return; - } - - final boolean wasDeferred = (mDeferredHideWallpaper == winGoingAway); for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) { final WallpaperWindowToken token = mWallpaperTokens.get(i); - token.hideWallpaperToken(wasDeferred, "hideWallpapers"); + token.setVisibility(false); if (DEBUG_WALLPAPER_LIGHT && token.isVisible()) { Slog.d(TAG, "Hiding wallpaper " + token + " from " + winGoingAway + " target=" + mWallpaperTarget + " prev=" @@ -616,7 +616,7 @@ class WallpaperController { // The window is visible to the compositor...but is it visible to the user? // That is what the wallpaper cares about. - final boolean visible = mWallpaperTarget != null && isWallpaperVisible(mWallpaperTarget); + final boolean visible = mWallpaperTarget != null; if (DEBUG_WALLPAPER) { Slog.v(TAG, "Wallpaper visibility: " + visible + " at display " + mDisplayContent.getDisplayId()); diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index 43303d4a5d7e..717775605c94 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -19,7 +19,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -34,6 +34,8 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.view.animation.Animation; +import com.android.internal.protolog.common.ProtoLog; + import java.util.function.Consumer; /** @@ -43,6 +45,8 @@ class WallpaperWindowToken extends WindowToken { private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperWindowToken" : TAG_WM; + private boolean mVisibleRequested = false; + WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit, DisplayContent dc, boolean ownerCanManageAppTokens) { this(service, token, explicit, dc, ownerCanManageAppTokens, null /* options */); @@ -57,18 +61,16 @@ class WallpaperWindowToken extends WindowToken { } @Override + WallpaperWindowToken asWallpaperToken() { + return this; + } + + @Override void setExiting() { super.setExiting(); mDisplayContent.mWallpaperController.removeWallpaperToken(this); } - void hideWallpaperToken(boolean wasDeferred, String reason) { - for (int j = mChildren.size() - 1; j >= 0; j--) { - final WindowState wallpaper = mChildren.get(j); - wallpaper.hideWallpaperWindow(wasDeferred, reason); - } - } - void sendWindowWallpaperCommand( String action, int x, int y, int z, Bundle extras, boolean sync) { for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) { @@ -93,24 +95,6 @@ class WallpaperWindowToken extends WindowToken { } } - void updateWallpaperVisibility(boolean visible) { - if (isVisible() != visible) { - mWmService.mAtmService.getTransitionController().collect(this); - // Need to do a layout to ensure the wallpaper now has the correct size. - mDisplayContent.setLayoutNeeded(); - } - - final WallpaperController wallpaperController = mDisplayContent.mWallpaperController; - for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) { - final WindowState wallpaper = mChildren.get(wallpaperNdx); - if (visible) { - wallpaperController.updateWallpaperOffset(wallpaper, false /* sync */); - } - - wallpaper.dispatchWallpaperVisibility(visible); - } - } - /** * Starts {@param anim} on all children. */ @@ -122,16 +106,16 @@ class WallpaperWindowToken extends WindowToken { } void updateWallpaperWindows(boolean visible) { - if (isVisible() != visible) { if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG, "Wallpaper token " + token + " visible=" + visible); - mWmService.mAtmService.getTransitionController().collect(this); - // Need to do a layout to ensure the wallpaper now has the correct size. - mDisplayContent.setLayoutNeeded(); + setVisibility(visible); } - final WallpaperController wallpaperController = mDisplayContent.mWallpaperController; + if (mWmService.mAtmService.getTransitionController().getTransitionPlayer() != null) { + return; + } + final WindowState wallpaperTarget = wallpaperController.getWallpaperTarget(); if (visible && wallpaperTarget != null) { @@ -153,19 +137,52 @@ class WallpaperWindowToken extends WindowToken { } } - for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) { - final WindowState wallpaper = mChildren.get(wallpaperNdx); + setVisible(visible); + } - if (visible) { - wallpaperController.updateWallpaperOffset(wallpaper, false /* sync */); + private void setVisible(boolean visible) { + final boolean wasClientVisible = isClientVisible(); + setClientVisible(visible); + if (visible && !wasClientVisible) { + for (int i = mChildren.size() - 1; i >= 0; i--) { + final WindowState wallpaper = mChildren.get(i); + wallpaper.requestUpdateWallpaperIfNeeded(); } + } + } - // First, make sure the client has the current visibility state. - wallpaper.dispatchWallpaperVisibility(visible); + /** + * Sets the requested visibility of this token. The visibility may not be if this is part of a + * transition. In that situation, make sure to call {@link #commitVisibility} when done. + */ + void setVisibility(boolean visible) { + // Before setting mVisibleRequested so we can track changes. + mWmService.mAtmService.getTransitionController().collect(this); + + setVisibleRequested(visible); - if (DEBUG_LAYERS || DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "adjustWallpaper win " - + wallpaper); + // If in a transition, defer commits for activities that are going invisible + if (!visible && (mWmService.mAtmService.getTransitionController().inTransition() + || getDisplayContent().mAppTransition.isRunning())) { + return; } + + commitVisibility(visible); + } + + /** + * Commits the visibility of this token. This will directly update the visibility without + * regard for other state (like being in a transition). + */ + void commitVisibility(boolean visible) { + if (visible == isVisible()) return; + + ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS, + "commitVisibility: %s: visible=%b mVisibleRequested=%b", this, + isVisible(), mVisibleRequested); + + setVisibleRequested(visible); + setVisible(visible); } @Override @@ -186,9 +203,10 @@ class WallpaperWindowToken extends WindowToken { } boolean hasVisibleNotDrawnWallpaper() { + if (!isVisible()) return false; for (int j = mChildren.size() - 1; j >= 0; --j) { final WindowState wallpaper = mChildren.get(j); - if (wallpaper.hasVisibleNotDrawnWallpaper()) { + if (!wallpaper.isDrawn() && wallpaper.isVisible()) { return true; } } @@ -210,6 +228,21 @@ class WallpaperWindowToken extends WindowToken { return false; } + void setVisibleRequested(boolean visible) { + if (mVisibleRequested == visible) return; + mVisibleRequested = visible; + setInsetsFrozen(!visible); + } + + @Override + boolean isVisibleRequested() { + return mVisibleRequested; + } + + @Override + boolean isVisible() { + return isClientVisible(); + } @Override public String toString() { diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index dd4ee877c05b..0c4ff2fe6365 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2684,14 +2684,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< @Nullable ArrayList<WindowContainer> sources) { final Task task = asTask(); if (task != null && !enter && !task.isHomeOrRecentsRootTask()) { - if (AppTransition.isClosingTransitOld(transit)) { - // Freezes the insets state when the window is in app exiting transition, to - // ensure the exiting window won't receive unexpected insets changes from the - // next window. - task.forAllWindows(w -> { - w.freezeInsetsState(); - }, true /* traverseTopToBottom */); - } mDisplayContent.showImeScreenshot(); } final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp, @@ -3068,6 +3060,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } /** Cheap way of doing cast and instanceof. */ + WallpaperWindowToken asWallpaperToken() { + return null; + } + + /** Cheap way of doing cast and instanceof. */ DisplayArea asDisplayArea() { return null; } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index e183ea0d81ab..7450782364f4 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -66,7 +66,7 @@ public abstract class WindowManagerInternal { /** * Is trace enabled or not. */ - boolean isEnabled(); + boolean isAccessibilityTracingEnabled(); /** * Add an accessibility trace entry. @@ -667,4 +667,13 @@ public abstract class WindowManagerInternal { * Moves the {@link WindowToken} {@code binder} to the display specified by {@code displayId}. */ public abstract void moveWindowTokenToDisplay(IBinder binder, int displayId); + + /** + * Checks whether the given window should restore the last IME visibility. + * + * @param imeTargetWindowToken The token of the (IME target) window + * @return {@code true} when the system allows to restore the IME visibility, + * {@code false} otherwise. + */ + public abstract boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index cec03210efeb..c9e1605f7f0d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -3971,6 +3971,21 @@ public class WindowManagerService extends IWindowManager.Stub ? backgroundType : LETTERBOX_BACKGROUND_SOLID_COLOR; } + /** Returns a string representing the given {@link LetterboxBackgroundType}. */ + static String letterboxBackgroundTypeToString( + @LetterboxBackgroundType int backgroundType) { + switch (backgroundType) { + case LETTERBOX_BACKGROUND_SOLID_COLOR: + return "LETTERBOX_BACKGROUND_SOLID_COLOR"; + case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND: + return "LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND"; + case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING: + return "LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING"; + default: + return "unknown=" + backgroundType; + } + } + @Override public void setIgnoreOrientationRequest(int displayId, boolean ignoreOrientationRequest) { if (!checkCallingPermission( @@ -8024,6 +8039,11 @@ public class WindowManagerService extends IWindowManager.Stub return dc.getImeTarget(IME_TARGET_LAYERING).getWindow().getName(); } } + + @Override + public boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) { + return WindowManagerService.this.shouldRestoreImeVisibility(imeTargetWindowToken); + } } void registerAppFreezeListener(AppFreezeListener listener) { @@ -8681,6 +8701,22 @@ public class WindowManagerService extends IWindowManager.Stub boundsInWindow, hashAlgorithm, callback); } + boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) { + synchronized (mGlobalLock) { + final WindowState imeTargetWindow = mWindowMap.get(imeTargetWindowToken); + if (imeTargetWindow == null) { + return false; + } + final Task imeTargetWindowTask = imeTargetWindow.getTask(); + if (imeTargetWindowTask == null) { + return false; + } + final TaskSnapshot snapshot = mAtmService.getTaskSnapshot(imeTargetWindowTask.mTaskId, + false /* isLowResolution */); + return snapshot != null && snapshot.hasImeSurface(); + } + } + private void sendDisplayHashError(RemoteCallback callback, int errorCode) { Bundle bundle = new Bundle(); bundle.putInt(EXTRA_DISPLAY_HASH_ERROR_CODE, errorCode); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 2a9e08e90e98..deb3913a32ed 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -141,11 +141,9 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_POWER; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.H.WINDOW_STATE_BLAST_SYNC_TIMEOUT; @@ -358,7 +356,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private boolean mForceHideNonSystemOverlayWindow; boolean mAppFreezing; boolean mHidden = true; // Used to determine if to show child windows. - boolean mWallpaperVisible; // for wallpaper, what was last vis report? private boolean mDragResizing; private boolean mDragResizingChangeReported = true; private int mResizeMode; @@ -797,7 +794,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * {@link InsetsStateController#notifyInsetsChanged}. */ boolean isReadyToDispatchInsetsState() { - return isVisible() && mFrozenInsetsState == null; + return isVisibleRequested() && mFrozenInsetsState == null; } void seamlesslyRotateIfAllowed(Transaction transaction, @Rotation int oldRotation, @@ -1190,16 +1187,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP layoutYDiff = 0; } else { windowFrames.mContainingFrame.set(getBounds()); - if (mActivityRecord != null && !mActivityRecord.mFrozenBounds.isEmpty()) { - - // If the bounds are frozen, we still want to translate the window freely and only - // freeze the size. - Rect frozen = mActivityRecord.mFrozenBounds.peek(); - windowFrames.mContainingFrame.right = - windowFrames.mContainingFrame.left + frozen.width(); - windowFrames.mContainingFrame.bottom = - windowFrames.mContainingFrame.top + frozen.height(); - } // IME is up and obscuring this window. Adjust the window position so it is visible. if (isImeTarget) { if (inFreeformWindowingMode()) { @@ -1725,7 +1712,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override boolean isVisibleRequested() { - return isVisible() && (mActivityRecord == null || mActivityRecord.isVisibleRequested()); + if (mToken != null && (mActivityRecord != null || mToken.asWallpaperToken() != null)) { + // Currently only ActivityRecord and WallpaperToken support visibleRequested. + return isVisible() && mToken.isVisibleRequested(); + } + return isVisible(); } /** @@ -1755,8 +1746,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * {@code false} otherwise. */ boolean wouldBeVisibleIfPolicyIgnored() { - return mHasSurface && !isParentWindowHidden() - && !mAnimatingExit && !mDestroying && (!mIsWallpaper || mWallpaperVisible); + if (!mHasSurface || isParentWindowHidden() || mAnimatingExit || mDestroying) { + return false; + } + final boolean isWallpaper = mToken != null && mToken.asWallpaperToken() != null; + return !isWallpaper || mToken.isVisible(); } /** @@ -1814,6 +1808,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return ((!isParentWindowHidden() && atoken.isVisible()) || isAnimating(TRANSITION | PARENTS)); } + final WallpaperWindowToken wtoken = mToken.asWallpaperToken(); + if (wtoken != null) { + return !isParentWindowHidden() && wtoken.isVisible(); + } return !isParentWindowHidden() || isAnimating(TRANSITION | PARENTS); } @@ -1953,8 +1951,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // When there is keyguard, wallpaper could be placed over the secure app // window but invisible. We need to check wallpaper visibility explicitly // to determine if it's occluding apps. - return ((!mIsWallpaper && mAttrs.format == PixelFormat.OPAQUE) - || (mIsWallpaper && mWallpaperVisible)) + final boolean isWallpaper = mToken != null && mToken.asWallpaperToken() != null; + return ((!isWallpaper && mAttrs.format == PixelFormat.OPAQUE) + || (isWallpaper && mToken.isVisible())) && isDrawn() && !isAnimating(TRANSITION | PARENTS); } @@ -2068,23 +2067,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP super.onResize(); } - void onUnfreezeBounds() { - for (int i = mChildren.size() - 1; i >= 0; --i) { - final WindowState c = mChildren.get(i); - c.onUnfreezeBounds(); - } - - if (!mHasSurface) { - return; - } - - mLayoutNeeded = true; - setDisplayLayoutNeeded(); - if (!mWmService.mResizingWindows.contains(this)) { - mWmService.mResizingWindows.add(this); - } - } - /** * If the window has moved due to its containing content frame changing, then notify the * listeners and optionally animate it. Simply checking a change of position is not enough, @@ -3251,7 +3233,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP void sendAppVisibilityToClients() { super.sendAppVisibilityToClients(); - final boolean clientVisible = mActivityRecord.isClientVisible(); + if (mToken == null) return; + + final boolean clientVisible = mToken.isClientVisible(); + // TODO(shell-transitions): This is currently only applicable to app windows, BUT we + // want to extend the "starting" concept to other windows. if (mAttrs.type == TYPE_APPLICATION_STARTING && !clientVisible) { // Don't hide the starting window. return; @@ -3579,10 +3565,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override public Configuration getConfiguration() { - if (mActivityRecord != null && mActivityRecord.mFrozenMergedConfig.size() > 0) { - return mActivityRecord.mFrozenMergedConfig.peek(); - } - // If the process has not registered to any display area to listen to the configuration // change, we can simply return the mFullConfiguration as default. if (!registeredForDisplayAreaConfigChanges()) { @@ -3639,9 +3621,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (mActivityRecord != null && mActivityRecord.isRelaunching()) { return; } - // If the activity is invisible or going invisible, don't report either since it is going - // away. This is likely during a transition so we want to preserve the original state. - if (mActivityRecord != null && !mActivityRecord.isVisibleRequested()) { + // If this is an activity or wallpaper and is invisible or going invisible, don't report + // either since it is going away. This is likely during a transition so we want to preserve + // the original state. + if ((mActivityRecord != null || mToken.asWallpaperToken() != null) + && !mToken.isVisibleRequested()) { return; } @@ -3944,14 +3928,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return true; } - // If the bounds are currently frozen, it means that the layout size that the app sees - // and the bounds we clip this window to might be different. In order to avoid holes, we - // simulate that we are still resizing so the app fills the hole with the resizing - // background. - return (getDisplayContent().mDividerControllerLocked.isResizing() - || mActivityRecord != null && !mActivityRecord.mFrozenBounds.isEmpty()) && - !task.inFreeformWindowingMode() && !isGoneForLayout(); - + return getDisplayContent().mDividerControllerLocked.isResizing() + && !task.inFreeformWindowingMode() && !isGoneForLayout(); } void setDragResizing() { @@ -4061,8 +4039,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (mIsImWindow || mIsWallpaper || mIsFloatingLayer) { pw.println(prefix + "mIsImWindow=" + mIsImWindow + " mIsWallpaper=" + mIsWallpaper - + " mIsFloatingLayer=" + mIsFloatingLayer - + " mWallpaperVisible=" + mWallpaperVisible); + + " mIsFloatingLayer=" + mIsFloatingLayer); } if (dumpAll) { pw.print(prefix); pw.print("mBaseLayer="); pw.print(mBaseLayer); @@ -4876,61 +4853,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; } - void hideWallpaperWindow(boolean wasDeferred, String reason) { - for (int j = mChildren.size() - 1; j >= 0; --j) { - final WindowState c = mChildren.get(j); - c.hideWallpaperWindow(wasDeferred, reason); - } - if (!mWinAnimator.mLastHidden || wasDeferred) { - mWinAnimator.hide(getGlobalTransaction(), reason); - getDisplayContent().mWallpaperController.mDeferredHideWallpaper = null; - dispatchWallpaperVisibility(false); - final DisplayContent displayContent = getDisplayContent(); - if (displayContent != null) { - displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; - if (DEBUG_LAYOUT_REPEATS) { - mWmService.mWindowPlacerLocked.debugLayoutRepeats("hideWallpaperWindow " + this, - displayContent.pendingLayoutChanges); - } - } - } - } - - /** - * Check wallpaper window for visibility change and notify window if so. - * @param visible Current visibility. - */ - void dispatchWallpaperVisibility(final boolean visible) { - final boolean hideAllowed = - getDisplayContent().mWallpaperController.mDeferredHideWallpaper == null; - - // Only send notification if the visibility actually changed and we are not trying to hide - // the wallpaper when we are deferring hiding of the wallpaper. - if (mWallpaperVisible != visible && (hideAllowed || visible)) { - mWallpaperVisible = visible; - try { - if (DEBUG_VISIBILITY || DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, - "Updating vis of wallpaper " + this - + ": " + visible + " from:\n" + Debug.getCallers(4, " ")); - mClient.dispatchAppVisibility(visible); - } catch (RemoteException e) { - } - } - } - - boolean hasVisibleNotDrawnWallpaper() { - if (mWallpaperVisible && !isDrawn()) { - return true; - } - for (int j = mChildren.size() - 1; j >= 0; --j) { - final WindowState c = mChildren.get(j); - if (c.hasVisibleNotDrawnWallpaper()) { - return true; - } - } - return false; - } - void updateReportedVisibility(UpdateReportedVisibilityResults results) { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowState c = mChildren.get(i); diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index ece256e8c591..ebbebbb702d8 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -57,7 +57,6 @@ import android.content.Context; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Rect; -import android.graphics.Region; import android.os.Debug; import android.os.Trace; import android.util.Slog; @@ -575,10 +574,7 @@ class WindowStateAnimator { setSurfaceBoundariesLocked(t); - if (mIsWallpaper && !w.mWallpaperVisible) { - // Wallpaper is no longer visible and there is no wp target => hide it. - hide(t, "prepareSurfaceLocked"); - } else if (w.isParentWindowHidden() || !w.isOnScreen()) { + if (w.isParentWindowHidden() || !w.isOnScreen()) { hide(t, "prepareSurfaceLocked"); mWallpaperControllerLocked.hideWallpapers(w); @@ -631,9 +627,6 @@ class WindowStateAnimator { if (showSurfaceRobustlyLocked(t)) { mAnimator.requestRemovalOfReplacedWindows(w); mLastHidden = false; - if (mIsWallpaper) { - w.dispatchWallpaperVisibility(true); - } final DisplayContent displayContent = w.getDisplayContent(); if (!displayContent.getLastHasContent()) { // This draw means the difference between unique content and mirroring. diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index e87ee918e0f0..8867aa747379 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -22,6 +22,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; @@ -112,6 +113,9 @@ class WindowToken extends WindowContainer<WindowState> { */ private final boolean mFromClientToken; + /** Have we told the window clients to show themselves? */ + private boolean mClientVisible; + /** * Used to fix the transform of the token to be rotated to a rotation different than it's * display. The window frames and surfaces corresponding to this token will be layouted and @@ -397,6 +401,21 @@ class WindowToken extends WindowContainer<WindowState> { return builder; } + boolean isClientVisible() { + return mClientVisible; + } + + void setClientVisible(boolean clientVisible) { + if (mClientVisible == clientVisible) { + return; + } + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, + "setClientVisible: %s clientVisible=%b Callers=%s", this, clientVisible, + Debug.getCallers(5)); + mClientVisible = clientVisible; + sendAppVisibilityToClients(); + } + boolean hasFixedRotationTransform() { return mFixedRotationTransformState != null; } @@ -736,4 +755,13 @@ class WindowToken extends WindowContainer<WindowState> { boolean isFromClient() { return mFromClientToken; } + + /** @see WindowState#freezeInsetsState() */ + void setInsetsFrozen(boolean freeze) { + if (freeze) { + forAllWindows(WindowState::freezeInsetsState, true /* traverseTopToBottom */); + } else { + forAllWindows(WindowState::clearFrozenInsetsState, true /* traverseTopToBottom */); + } + } } diff --git a/services/core/xsd/Android.bp b/services/core/xsd/Android.bp index c285ef519e44..6a8f6d419786 100644 --- a/services/core/xsd/Android.bp +++ b/services/core/xsd/Android.bp @@ -30,7 +30,6 @@ xsd_config { gen_writer: true, } - xsd_config { name: "display-device-config", srcs: ["display-device-config/display-device-config.xsd"], @@ -38,6 +37,12 @@ xsd_config { package_name: "com.android.server.display.config", } +xsd_config { + name: "display-layout-config", + srcs: ["display-layout-config/display-layout-config.xsd"], + api_dir: "display-layout-config/schema", + package_name: "com.android.server.display.config.layout", +} xsd_config { name: "cec-config", 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 7d705c1fac69..e4b961299f12 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -1,23 +1,24 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - ~ Copyright (C) 2020 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> + Copyright (C) 2020 The Android Open Source Project -<!-- This defines the format of the XML file generated by - ~ com.android.compat.annotation.ChangeIdProcessor annotation processor (from - ~ tools/platform-compat), and is parsed in com/android/server/compat/CompatConfig.java. + 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. +--> + +<!-- + This defines the format of the XML file used to provide static configuration values + for the displays on a device. + It is parsed in com/android/server/display/DisplayDeviceConfig.java --> <xs:schema version="2.0" elementFormDefault="qualified" diff --git a/services/core/xsd/display-layout-config/OWNERS b/services/core/xsd/display-layout-config/OWNERS new file mode 100644 index 000000000000..20b75be9f11f --- /dev/null +++ b/services/core/xsd/display-layout-config/OWNERS @@ -0,0 +1,3 @@ +include /services/core/java/com/android/server/display/OWNERS + +flc@google.com diff --git a/services/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd new file mode 100644 index 000000000000..c542c0d0c382 --- /dev/null +++ b/services/core/xsd/display-layout-config/display-layout-config.xsd @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> + +<!-- + This defines the format of the XML file used to defines how displays are laid out + for a given device-state. + It is parsed in com/android/server/display/layout/DeviceStateToLayoutMap.java + More information on device-state can be found in DeviceStateManager.java +--> +<xs:schema version="2.0" + elementFormDefault="qualified" + xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <xs:element name="layouts"> + <xs:complexType> + <xs:sequence> + <xs:element type="layout" name="layout" minOccurs="1" maxOccurs="unbounded" /> + </xs:sequence> + </xs:complexType> + + <!-- Ensures only one layout is allowed per device state. --> + <xs:unique name="UniqueState"> + <xs:selector xpath="layout" /> + <xs:field xpath="@state" /> + </xs:unique> + </xs:element> + + <!-- Type definitions --> + + <xs:complexType name="layout"> + <xs:sequence> + <xs:element name="state" type="xs:nonNegativeInteger" /> + <xs:element name="display" type="display" maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="display"> + <xs:sequence> + <xs:element name="address" type="xs:nonNegativeInteger"/> + </xs:sequence> + <xs:attribute name="enabled" type="xs:boolean" use="optional" /> + <xs:attribute name="isDefault" type="xs:boolean" use="optional" /> + </xs:complexType> +</xs:schema> diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt new file mode 100644 index 000000000000..817188509f81 --- /dev/null +++ b/services/core/xsd/display-layout-config/schema/current.txt @@ -0,0 +1,34 @@ +// Signature format: 2.0 +package com.android.server.display.config.layout { + + public class Display { + ctor public Display(); + method public java.math.BigInteger getAddress(); + method public boolean getEnabled(); + method public boolean getIsDefault(); + method public void setAddress(java.math.BigInteger); + method public void setEnabled(boolean); + method public void setIsDefault(boolean); + } + + public class Layout { + ctor public Layout(); + method public java.util.List<com.android.server.display.config.layout.Display> getDisplay(); + method public java.math.BigInteger getState(); + method public void setState(java.math.BigInteger); + } + + public class Layouts { + ctor public Layouts(); + method public java.util.List<com.android.server.display.config.layout.Layout> getLayout(); + } + + public class XmlParser { + ctor public XmlParser(); + method public static com.android.server.display.config.layout.Layouts read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + } + +} + diff --git a/services/core/xsd/display-layout-config/schema/last_current.txt b/services/core/xsd/display-layout-config/schema/last_current.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/services/core/xsd/display-layout-config/schema/last_current.txt diff --git a/services/core/xsd/display-layout-config/schema/last_removed.txt b/services/core/xsd/display-layout-config/schema/last_removed.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/services/core/xsd/display-layout-config/schema/last_removed.txt diff --git a/services/core/xsd/display-layout-config/schema/removed.txt b/services/core/xsd/display-layout-config/schema/removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/services/core/xsd/display-layout-config/schema/removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 28e9acf8d883..04af5c93160d 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -1583,8 +1583,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } public String[] getPersonalAppsForSuspension(int userId) { - return new PersonalAppsSuspensionHelper( - mContext.createContextAsUser(UserHandle.of(userId), 0 /* flags */)) + return PersonalAppsSuspensionHelper.forUser(mContext, userId) .getPersonalAppsForSuspension(); } @@ -1599,6 +1598,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) { mSafetyChecker = safetyChecker; } + + void dumpPerUserData(IndentingPrintWriter pw, @UserIdInt int userId) { + PersonalAppsSuspensionHelper.forUser(mContext, userId).dump(pw); + } } /** @@ -9161,11 +9164,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private void dumpDevicePolicyData(IndentingPrintWriter pw) { + private void dumpPerUserData(IndentingPrintWriter pw) { int userCount = mUserData.size(); - for (int u = 0; u < userCount; u++) { - DevicePolicyData policy = getUserData(mUserData.keyAt(u)); + for (int userId = 0; userId < userCount; userId++) { + DevicePolicyData policy = getUserData(mUserData.keyAt(userId)); policy.dump(pw); + pw.println(); + + pw.increaseIndent(); + mInjector.dumpPerUserData(pw, userId); + pw.decreaseIndent(); + pw.println(); } } @@ -9183,7 +9192,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { pw.println(); mDeviceAdminServiceController.dump(pw); pw.println(); - dumpDevicePolicyData(pw); + dumpPerUserData(pw); pw.println(); mConstants.dump(pw); pw.println(); @@ -9229,20 +9238,30 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { pw.increaseIndent(); dumpResources(pw, mContext, "cross_profile_apps", R.array.cross_profile_apps); dumpResources(pw, mContext, "vendor_cross_profile_apps", R.array.vendor_cross_profile_apps); + dumpResources(pw, mContext, "config_packagesExemptFromSuspension", + R.array.config_packagesExemptFromSuspension); pw.decreaseIndent(); pw.println(); } static void dumpResources(IndentingPrintWriter pw, Context context, String resName, int resId) { - String[] apps = context.getResources().getStringArray(resId); - if (apps == null || apps.length == 0) { - pw.printf("%s: empty\n", resName); + dumpApps(pw, resName, context.getResources().getStringArray(resId)); + } + + static void dumpApps(IndentingPrintWriter pw, String name, String[] apps) { + dumpApps(pw, name, Arrays.asList(apps)); + } + + static void dumpApps(IndentingPrintWriter pw, String name, List apps) { + if (apps == null || apps.isEmpty()) { + pw.printf("%s: empty\n", name); return; } - pw.printf("%s: %d app%s\n", resName, apps.length, apps.length == 1 ? "" : "s"); + int size = apps.size(); + pw.printf("%s: %d app%s\n", name, size, size == 1 ? "" : "s"); pw.increaseIndent(); - for (int i = 0; i < apps.length; i++) { - pw.printf("%d: %s\n", i, apps[i]); + for (int i = 0; i < size; i++) { + pw.printf("%d: %s\n", i, apps.get(i)); } pw.decreaseIndent(); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java index c687184265c1..37dbfc170aff 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java @@ -20,6 +20,7 @@ import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -29,10 +30,12 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.IBinder; import android.os.ServiceManager; +import android.os.UserHandle; import android.provider.Settings; import android.provider.Telephony; import android.text.TextUtils; import android.util.ArraySet; +import android.util.IndentingPrintWriter; import android.util.Slog; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManager; @@ -49,7 +52,7 @@ import java.util.Set; /** * Utility class to find what personal apps should be suspended to limit personal device use. */ -public class PersonalAppsSuspensionHelper { +public final class PersonalAppsSuspensionHelper { private static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG; // Flags to get all packages even if the user is still locked. @@ -60,9 +63,17 @@ public class PersonalAppsSuspensionHelper { private final PackageManager mPackageManager; /** + * Factory method + */ + public static PersonalAppsSuspensionHelper forUser(Context context, @UserIdInt int userId) { + return new PersonalAppsSuspensionHelper(context.createContextAsUser(UserHandle.of(userId), + /* flags= */ 0)); + } + + /** * @param context Context for the user whose apps should to be suspended. */ - public PersonalAppsSuspensionHelper(Context context) { + private PersonalAppsSuspensionHelper(Context context) { mContext = context; mPackageManager = context.getPackageManager(); } @@ -181,4 +192,21 @@ public class PersonalAppsSuspensionHelper { iBinder == null ? null : IAccessibilityManager.Stub.asInterface(iBinder); return new AccessibilityManager(mContext, service, userId); } + + void dump(IndentingPrintWriter pw) { + pw.println("PersonalAppsSuspensionHelper"); + pw.increaseIndent(); + + DevicePolicyManagerService.dumpApps(pw, "critical packages", getCriticalPackages()); + DevicePolicyManagerService.dumpApps(pw, "launcher packages", getSystemLauncherPackages()); + DevicePolicyManagerService.dumpApps(pw, "accessibility services", + getAccessibilityServices()); + DevicePolicyManagerService.dumpApps(pw, "input method packages", getInputMethodPackages()); + pw.printf("SMS package: %s\n", Telephony.Sms.getDefaultSmsPackage(mContext)); + pw.printf("Settings package: %s\n", getSettingsPackageName()); + DevicePolicyManagerService.dumpApps(pw, "Packages subject to suspension", + getPersonalAppsForSuspension()); + + pw.decreaseIndent(); + } } diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index 0d878b401bee..a262939c0ef9 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -102,7 +102,7 @@ public final class ProfcollectForwardingService extends SystemService { return false; } try { - return !mIProfcollect.GetSupportedProvider().isEmpty(); + return !mIProfcollect.get_supported_provider().isEmpty(); } catch (RemoteException e) { Log.e(LOG_TAG, e.getMessage()); return false; @@ -191,7 +191,7 @@ public final class ProfcollectForwardingService extends SystemService { } try { - sSelfService.mIProfcollect.ProcessProfile(); + sSelfService.mIProfcollect.process(false); } catch (RemoteException e) { Log.e(LOG_TAG, e.getMessage()); } @@ -234,7 +234,7 @@ public final class ProfcollectForwardingService extends SystemService { if (DEBUG) { Log.d(LOG_TAG, "Tracing on app launch event: " + packageName); } - mIProfcollect.TraceOnce("applaunch"); + mIProfcollect.trace_once("applaunch"); } catch (RemoteException e) { Log.e(LOG_TAG, e.getMessage()); } @@ -296,7 +296,7 @@ public final class ProfcollectForwardingService extends SystemService { } try { - mIProfcollect.CreateProfileReport(); + mIProfcollect.report(); } catch (RemoteException e) { Log.e(LOG_TAG, e.getMessage()); } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt index 9447f390ada0..8ef92393242a 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt @@ -19,7 +19,7 @@ package com.android.server.pm.test.verify.domain import android.content.pm.verify.domain.DomainSet import android.content.pm.verify.domain.DomainVerificationInfo import android.content.pm.verify.domain.DomainVerificationRequest -import android.content.pm.verify.domain.DomainVerificationUserSelection +import android.content.pm.verify.domain.DomainVerificationUserState import android.os.Parcel import android.os.Parcelable import android.os.UserHandle @@ -28,7 +28,6 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized import java.util.UUID -import kotlin.random.Random @RunWith(Parameterized::class) class DomainVerificationCoreApiTest { @@ -92,9 +91,9 @@ class DomainVerificationCoreApiTest { } ), Parameter( - testName = "DomainVerificationUserSelection", + testName = "DomainVerificationUserState", initial = { - DomainVerificationUserSelection( + DomainVerificationUserState( UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"), "com.test.pkg", UserHandle.of(10), @@ -103,22 +102,22 @@ class DomainVerificationCoreApiTest { .associate { it.value to (it.index % 3) } ) }, - unparcel = { DomainVerificationUserSelection.CREATOR.createFromParcel(it) }, + unparcel = { DomainVerificationUserState.CREATOR.createFromParcel(it) }, assertion = { first, second -> - assertAll<DomainVerificationUserSelection, UUID>(first, second, + assertAll<DomainVerificationUserState, UUID>(first, second, { it.identifier }, { it.component1() }, IS_EQUAL_TO ) - assertAll<DomainVerificationUserSelection, String>(first, second, + assertAll<DomainVerificationUserState, String>(first, second, { it.packageName }, { it.component2() }, IS_EQUAL_TO ) - assertAll<DomainVerificationUserSelection, UserHandle>(first, second, + assertAll<DomainVerificationUserState, UserHandle>(first, second, { it.user }, { it.component3() }, IS_EQUAL_TO ) - assertAll<DomainVerificationUserSelection, Boolean>( + assertAll<DomainVerificationUserState, Boolean>( first, second, { it.isLinkHandlingAllowed }, { it.component4() }, IS_EQUAL_TO ) - assertAll<DomainVerificationUserSelection, Map<String, Int>>( + assertAll<DomainVerificationUserState, Map<String, Int>>( first, second, { it.hostToStateMap }, { it.component5() }, IS_MAP_EQUAL_TO ) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt index 89394837655a..53f0ca20e787 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt @@ -155,6 +155,15 @@ class DomainVerificationEnforcerTest { assertApprovedVerifier(it.callingUid, it.proxy) }, enforcer( + Type.SELECTION_QUERENT, + "approvedUserStateQuerent" + ) { + assertApprovedUserStateQuerent( + it.callingUid, it.callingUserId, + it.targetPackageName, it.userId + ) + }, + enforcer( Type.SELECTOR, "approvedUserSelector" ) { @@ -170,7 +179,7 @@ class DomainVerificationEnforcerTest { ArraySet(setOf("example.com")) ) }, - service(Type.INTERNAL, "setUserSelectionInternal") { + service(Type.INTERNAL, "setUserStateInternal") { setDomainVerificationUserSelectionInternal( it.userId, it.targetPackageName, @@ -184,11 +193,11 @@ class DomainVerificationEnforcerTest { service(Type.INTERNAL, "clearState") { clearDomainVerificationState(listOf(it.targetPackageName)) }, - service(Type.INTERNAL, "clearUserSelections") { - clearUserSelections(listOf(it.targetPackageName), it.userId) + service(Type.INTERNAL, "clearUserStates") { + clearUserStates(listOf(it.targetPackageName), it.userId) }, - service(Type.VERIFIER, "getPackageNames") { - validVerificationPackageNames + service(Type.VERIFIER, "queryValidPackageNames") { + queryValidVerificationPackageNames() }, service(Type.QUERENT, "getInfo") { getDomainVerificationInfo(it.targetPackageName) @@ -208,26 +217,13 @@ class DomainVerificationEnforcerTest { DomainVerificationManager.STATE_SUCCESS ) }, - service(Type.SELECTOR, "setLinkHandlingAllowed") { - setDomainVerificationLinkHandlingAllowed(it.targetPackageName, true) - }, service(Type.SELECTOR_USER, "setLinkHandlingAllowedUserId") { setDomainVerificationLinkHandlingAllowed(it.targetPackageName, true, it.userId) }, - service(Type.SELECTOR, "getUserSelection") { - getDomainVerificationUserSelection(it.targetPackageName) - }, - service(Type.SELECTOR_USER, "getUserSelectionUserId") { - getDomainVerificationUserSelection(it.targetPackageName, it.userId) + service(Type.SELECTION_QUERENT, "getUserStateUserId") { + getDomainVerificationUserState(it.targetPackageName, it.userId) }, - service(Type.SELECTOR, "setUserSelection") { - setDomainVerificationUserSelection( - it.targetDomainSetId, - setOf("example.com"), - true - ) - }, - service(Type.SELECTOR_USER, "setUserSelectionUserId") { + service(Type.SELECTOR_USER, "setUserStateUserId") { setDomainVerificationUserSelection( it.targetDomainSetId, setOf("example.com"), @@ -244,10 +240,6 @@ class DomainVerificationEnforcerTest { service(Type.LEGACY_QUERENT, "getLegacyUserState") { getLegacyState(it.targetPackageName, it.userId) }, - service(Type.OWNER_QUERENT, "getOwnersForDomain") { - // Re-use package name, since the result itself isn't relevant - getOwnersForDomain(it.targetPackageName) - }, service(Type.OWNER_QUERENT_USER, "getOwnersForDomainUserId") { // Re-use package name, since the result itself isn't relevant getOwnersForDomain(it.targetPackageName, it.userId) @@ -362,6 +354,7 @@ class DomainVerificationEnforcerTest { Type.INTERNAL -> internal() Type.QUERENT -> approvedQuerent() Type.VERIFIER -> approvedVerifier() + Type.SELECTION_QUERENT -> approvedUserStateQuerent(verifyCrossUser = true) Type.SELECTOR -> approvedUserSelector(verifyCrossUser = false) Type.SELECTOR_USER -> approvedUserSelector(verifyCrossUser = true) Type.LEGACY_QUERENT -> legacyQuerent() @@ -371,7 +364,7 @@ class DomainVerificationEnforcerTest { }.run { /*exhaust*/ } } - fun internal() { + private fun internal() { val context: Context = mockThrowOnUnmocked() val target = params.construct(context) @@ -385,13 +378,13 @@ class DomainVerificationEnforcerTest { } } - fun approvedQuerent() { - val allowUserSelection = AtomicBoolean(false) + private fun approvedQuerent() { + val allowUserState = AtomicBoolean(false) val allowPreferredApps = AtomicBoolean(false) val allowQueryAll = AtomicBoolean(false) val context: Context = mockThrowOnUnmocked { initPermission( - allowUserSelection, + allowUserState, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION ) initPermission( @@ -418,7 +411,7 @@ class DomainVerificationEnforcerTest { assertFails { runMethod(target, NON_VERIFIER_UID) } - allowUserSelection.set(true) + allowUserState.set(true) assertFails { runMethod(target, NON_VERIFIER_UID) } @@ -427,7 +420,7 @@ class DomainVerificationEnforcerTest { runMethod(target, NON_VERIFIER_UID) } - fun approvedVerifier() { + private fun approvedVerifier() { val allowDomainVerificationAgent = AtomicBoolean(false) val allowIntentVerificationAgent = AtomicBoolean(false) val allowQueryAll = AtomicBoolean(false) @@ -469,12 +462,61 @@ class DomainVerificationEnforcerTest { assertFails { runMethod(target, NON_VERIFIER_UID) } } - fun approvedUserSelector(verifyCrossUser: Boolean) { - val allowUserSelection = AtomicBoolean(false) + private fun approvedUserStateQuerent(verifyCrossUser: Boolean) { + val allowInteractAcrossUsers = AtomicBoolean(false) + val context: Context = mockThrowOnUnmocked { + initPermission( + allowInteractAcrossUsers, + android.Manifest.permission.INTERACT_ACROSS_USERS + ) + } + val target = params.construct(context) + + fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) { + // User selector makes no distinction by UID + val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID + if (throws) { + allUids.forEach { + assertFails { + runMethod(target, it, visible = true, callingUserId, targetUserId) + } + } + } else { + allUids.forEach { + runMethod(target, it, visible = true, callingUserId, targetUserId) + } + } + + // User selector doesn't use QUERY_ALL, so the invisible package should always fail + allUids.forEach { + assertFails { + runMethod(target, it, visible = false, callingUserId, targetUserId) + } + } + } + + val callingUserId = 0 + val notCallingUserId = 1 + + runTestCases(callingUserId, callingUserId, throws = false) + if (verifyCrossUser) { + runTestCases(callingUserId, notCallingUserId, throws = true) + } + + allowInteractAcrossUsers.set(true) + + runTestCases(callingUserId, callingUserId, throws = false) + if (verifyCrossUser) { + runTestCases(callingUserId, notCallingUserId, throws = false) + } + } + + private fun approvedUserSelector(verifyCrossUser: Boolean) { + val allowUserState = AtomicBoolean(false) val allowInteractAcrossUsers = AtomicBoolean(false) val context: Context = mockThrowOnUnmocked { initPermission( - allowUserSelection, + allowUserState, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION ) initPermission( @@ -515,7 +557,7 @@ class DomainVerificationEnforcerTest { runTestCases(callingUserId, notCallingUserId, throws = true) } - allowUserSelection.set(true) + allowUserState.set(true) runTestCases(callingUserId, callingUserId, throws = false) if (verifyCrossUser) { @@ -641,7 +683,7 @@ class DomainVerificationEnforcerTest { private fun ownerQuerent(verifyCrossUser: Boolean) { val allowQueryAll = AtomicBoolean(false) - val allowUserSelection = AtomicBoolean(false) + val allowUserState = AtomicBoolean(false) val allowInteractAcrossUsers = AtomicBoolean(false) val context: Context = mockThrowOnUnmocked { initPermission( @@ -649,7 +691,7 @@ class DomainVerificationEnforcerTest { android.Manifest.permission.QUERY_ALL_PACKAGES ) initPermission( - allowUserSelection, + allowUserState, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION ) initPermission( @@ -690,7 +732,7 @@ class DomainVerificationEnforcerTest { runTestCases(callingUserId, notCallingUserId, throws = true) } - allowUserSelection.set(true) + allowUserState.set(true) runTestCases(callingUserId, callingUserId, throws = false) if (verifyCrossUser) { @@ -769,6 +811,9 @@ class DomainVerificationEnforcerTest { // INTERNAL || domain verification agent VERIFIER, + // No permissions, allows all apps to view domain state for visible packages + SELECTION_QUERENT, + // Holding the user setting permission SELECTOR, diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt index 439048ce51bb..8c31c65e1b0a 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt @@ -18,7 +18,7 @@ package com.android.server.pm.test.verify.domain import android.content.pm.verify.domain.DomainVerificationRequest import android.content.pm.verify.domain.DomainVerificationInfo -import android.content.pm.verify.domain.DomainVerificationUserSelection +import android.content.pm.verify.domain.DomainVerificationUserState import com.android.server.pm.verify.domain.DomainVerificationPersistence operator fun <F> android.util.Pair<F, *>.component1() = first @@ -30,11 +30,11 @@ operator fun DomainVerificationInfo.component1() = identifier operator fun DomainVerificationInfo.component2() = packageName operator fun DomainVerificationInfo.component3() = hostToStateMap -operator fun DomainVerificationUserSelection.component1() = identifier -operator fun DomainVerificationUserSelection.component2() = packageName -operator fun DomainVerificationUserSelection.component3() = user -operator fun DomainVerificationUserSelection.component4() = isLinkHandlingAllowed -operator fun DomainVerificationUserSelection.component5() = hostToStateMap +operator fun DomainVerificationUserState.component1() = identifier +operator fun DomainVerificationUserState.component2() = packageName +operator fun DomainVerificationUserState.component3() = user +operator fun DomainVerificationUserState.component4() = isLinkHandlingAllowed +operator fun DomainVerificationUserState.component5() = hostToStateMap operator fun DomainVerificationPersistence.ReadResult.component1() = active operator fun DomainVerificationPersistence.ReadResult.component2() = restored diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt index f8a3fb94bd3e..6597577cf14f 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt @@ -22,9 +22,9 @@ import android.util.TypedXmlPullParser import android.util.TypedXmlSerializer import android.util.Xml import com.android.server.pm.verify.domain.DomainVerificationPersistence +import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState import com.android.server.pm.verify.domain.models.DomainVerificationPkgState import com.android.server.pm.verify.domain.models.DomainVerificationStateMap -import com.android.server.pm.verify.domain.models.DomainVerificationUserState import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import org.junit.Rule @@ -123,14 +123,14 @@ class DomainVerificationPersistenceTest { // A domain without a written state falls back to default stateMap["missing-state.com"] = DomainVerificationManager.STATE_NO_RESPONSE - userSelectionStates[1] = DomainVerificationUserState(1).apply { + userStates[1] = DomainVerificationInternalUserState(1).apply { addHosts(setOf("example-user1.com", "example-user1.org")) isLinkHandlingAllowed = true } } val stateOne = mockEmptyPkgState(1).apply { // It's valid to have a user selection without any autoVerify domains - userSelectionStates[1] = DomainVerificationUserState(1).apply { + userStates[1] = DomainVerificationInternalUserState(1).apply { addHosts(setOf("example-user1.com", "example-user1.org")) isLinkHandlingAllowed = false } @@ -235,7 +235,7 @@ class DomainVerificationPersistenceTest { private fun mockPkgState(id: Int) = mockEmptyPkgState(id).apply { stateMap["$packageName.com"] = id - userSelectionStates[id] = DomainVerificationUserState(id).apply { + userStates[id] = DomainVerificationInternalUserState(id).apply { addHosts(setOf("$packageName-user.com")) isLinkHandlingAllowed = true } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt index 010eacf3f51f..0d8f275be09c 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt @@ -122,8 +122,8 @@ class DomainVerificationSettingsMutationTest { service("clearState") { clearDomainVerificationState(listOf(TEST_PKG)) }, - service("clearUserSelections") { - clearUserSelections(listOf(TEST_PKG), TEST_USER_ID) + service("clearUserStates") { + clearUserStates(listOf(TEST_PKG), TEST_USER_ID) }, service("setStatus") { setDomainVerificationStatus( @@ -147,19 +147,13 @@ class DomainVerificationSettingsMutationTest { DomainVerificationManager.STATE_SUCCESS ) }, - service("setLinkHandlingAllowed") { - setDomainVerificationLinkHandlingAllowed(TEST_PKG, true) - }, service("setLinkHandlingAllowedUserId") { setDomainVerificationLinkHandlingAllowed(TEST_PKG, true, TEST_USER_ID) }, service("setLinkHandlingAllowedInternal") { setDomainVerificationLinkHandlingAllowedInternal(TEST_PKG, true, TEST_USER_ID) }, - service("setUserSelection") { - setDomainVerificationUserSelection(TEST_UUID, setOf("example.com"), true) - }, - service("setUserSelectionUserId") { + service("setUserStateUserId") { setDomainVerificationUserSelection( TEST_UUID, setOf("example.com"), @@ -167,7 +161,7 @@ class DomainVerificationSettingsMutationTest { TEST_USER_ID ) }, - service("setUserSelectionInternal") { + service("setUserStateInternal") { setDomainVerificationUserSelectionInternal( TEST_USER_ID, TEST_PKG, diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt index 48056a2b54d1..0576125748fb 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt @@ -16,18 +16,16 @@ package com.android.server.pm.test.verify.domain -import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.pm.parsing.component.ParsedActivity import android.content.pm.parsing.component.ParsedIntentInfo import android.content.pm.verify.domain.DomainVerificationManager -import android.content.pm.verify.domain.DomainVerificationUserSelection +import android.content.pm.verify.domain.DomainVerificationUserState import android.os.Build import android.os.PatternMatcher import android.os.Process import android.util.ArraySet -import androidx.test.InstrumentationRegistry import com.android.server.pm.PackageSetting import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.verify.domain.DomainVerificationService @@ -41,7 +39,7 @@ import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.anyString import java.util.UUID -class DomainVerificationManagerUserSelectionOverrideTest { +class DomainVerificationUserStateOverrideTest { companion object { private const val PKG_ONE = "com.test.one" @@ -50,17 +48,19 @@ class DomainVerificationManagerUserSelectionOverrideTest { private val UUID_TWO = UUID.fromString("a3389c16-7f9f-4e86-85e3-500d1249c74c") private val DOMAIN_ONE = - DomainVerificationManagerUserSelectionOverrideTest::class.java.packageName + DomainVerificationUserStateOverrideTest::class.java.packageName - private const val STATE_NONE = DomainVerificationUserSelection.DOMAIN_STATE_NONE - private const val STATE_SELECTED = DomainVerificationUserSelection.DOMAIN_STATE_SELECTED - private const val STATE_VERIFIED = DomainVerificationUserSelection.DOMAIN_STATE_VERIFIED + private const val STATE_NONE = DomainVerificationUserState.DOMAIN_STATE_NONE + private const val STATE_SELECTED = DomainVerificationUserState.DOMAIN_STATE_SELECTED + private const val STATE_VERIFIED = DomainVerificationUserState.DOMAIN_STATE_VERIFIED + + private const val USER_ID = 0 } private val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE) private val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO) - fun makeManager(): DomainVerificationManager = + fun makeService() = DomainVerificationService(mockThrowOnUnmocked { // Assume the test has every permission necessary whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString())) @@ -88,7 +88,7 @@ class DomainVerificationManagerUserSelectionOverrideTest { addPackage(pkg2) // Starting state for all tests is to have domain 1 enabled for the first package - setDomainVerificationUserSelection(UUID_ONE, setOf(DOMAIN_ONE), true) + setDomainVerificationUserSelection(UUID_ONE, setOf(DOMAIN_ONE), true, USER_ID) assertThat(stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_SELECTED) } @@ -138,37 +138,37 @@ class DomainVerificationManagerUserSelectionOverrideTest { @Test fun anotherPackageTakeoverSuccess() { - val manager = makeManager() + val service = makeService() // Attempt override by package 2 - manager.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true) + service.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true, USER_ID) // 1 loses approval - assertThat(manager.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_NONE) + assertThat(service.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_NONE) // 2 gains approval - assertThat(manager.stateFor(PKG_TWO, DOMAIN_ONE)).isEqualTo(STATE_SELECTED) + assertThat(service.stateFor(PKG_TWO, DOMAIN_ONE)).isEqualTo(STATE_SELECTED) // 2 is the only owner - assertThat(manager.getOwnersForDomain(DOMAIN_ONE).map { it.packageName }) + assertThat(service.getOwnersForDomain(DOMAIN_ONE, USER_ID).map { it.packageName }) .containsExactly(PKG_TWO) } @Test(expected = IllegalArgumentException::class) fun anotherPackageTakeoverFailure() { - val manager = makeManager() + val service = makeService() // Verify 1 to give it a higher approval level - manager.setDomainVerificationStatus(UUID_ONE, setOf(DOMAIN_ONE), + service.setDomainVerificationStatus(UUID_ONE, setOf(DOMAIN_ONE), DomainVerificationManager.STATE_SUCCESS) - assertThat(manager.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_VERIFIED) - assertThat(manager.getOwnersForDomain(DOMAIN_ONE).map { it.packageName }) + assertThat(service.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_VERIFIED) + assertThat(service.getOwnersForDomain(DOMAIN_ONE, USER_ID).map { it.packageName }) .containsExactly(PKG_ONE) // Attempt override by package 2 - manager.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true) + service.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true, USER_ID) } - private fun DomainVerificationManager.stateFor(pkgName: String, host: String) = - getDomainVerificationUserSelection(pkgName)!!.hostToStateMap[host] + private fun DomainVerificationService.stateFor(pkgName: String, host: String) = + getDomainVerificationUserState(pkgName, USER_ID)!!.hostToStateMap[host] } diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 91098813380e..51c9b0ddb0d6 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -85,6 +85,7 @@ import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AlarmManager; import android.app.AppOpsManager; +import android.app.BroadcastOptions; import android.app.IActivityManager; import android.app.IAlarmCompleteListener; import android.app.IAlarmListener; @@ -1649,8 +1650,8 @@ public class AlarmManagerServiceTest { eq(FLAG_ALLOW_WHILE_IDLE_COMPAT | FLAG_STANDALONE), isNull(), isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); - final Bundle idleOptions = bundleCaptor.getValue(); - final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); + final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @@ -1669,8 +1670,8 @@ public class AlarmManagerServiceTest { eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(), isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); - final Bundle idleOptions = bundleCaptor.getValue(); - final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); + final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @@ -1716,8 +1717,8 @@ public class AlarmManagerServiceTest { isNull(), eq(alarmClock), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); - final Bundle idleOptions = bundleCaptor.getValue(); - final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); + final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @@ -1742,8 +1743,8 @@ public class AlarmManagerServiceTest { eq(FLAG_ALLOW_WHILE_IDLE | FLAG_STANDALONE), isNull(), isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); - final Bundle idleOptions = bundleCaptor.getValue(); - final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); + final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @@ -1772,8 +1773,8 @@ public class AlarmManagerServiceTest { eq(FLAG_ALLOW_WHILE_IDLE_COMPAT | FLAG_STANDALONE), isNull(), isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); - final Bundle idleOptions = bundleCaptor.getValue(); - final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); + final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @@ -1797,8 +1798,8 @@ public class AlarmManagerServiceTest { eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(), isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); - final Bundle idleOptions = bundleCaptor.getValue(); - final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); + final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @@ -1822,8 +1823,8 @@ public class AlarmManagerServiceTest { eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(), isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); - final Bundle idleOptions = bundleCaptor.getValue(); - final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); + final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED, type); } diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java index 18184b0a82af..f1d8e6c167d7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -592,6 +592,7 @@ public class LocalDisplayAdapterTest { new DisplayModeDirector.RefreshRateRange(60f, 60f), new DisplayModeDirector.RefreshRateRange(60f, 60f) )); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); verify(mSurfaceControlProxy).setDesiredDisplayModeSpecs(display.token, new SurfaceControl.DesiredDisplayModeSpecs( /* baseModeId */ 0, diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java index f7f592886473..3870b02ba37c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java @@ -641,6 +641,6 @@ public class ConnectivityControllerTest { private static JobStatus createJobStatus(JobInfo.Builder job, int uid, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) { return new JobStatus(job.build(), uid, null, -1, 0, null, - earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, null, 0); + earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, null, 0, 0); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java index 91b3cb7dbdd9..7925b69852ba 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java @@ -685,7 +685,7 @@ public class JobStatusTest { final JobInfo job = new JobInfo.Builder(101, new ComponentName("foo", "bar")) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY).build(); return new JobStatus(job, 0, null, -1, 0, null, earliestRunTimeElapsedMillis, - latestRunTimeElapsedMillis, 0, 0, null, 0); + latestRunTimeElapsedMillis, 0, 0, null, 0, 0); } private static JobStatus createJobStatus(JobInfo job) { diff --git a/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java new file mode 100644 index 000000000000..d786a5dac83a --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java @@ -0,0 +1,128 @@ +/* + * 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.usage; + +import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED; +import static android.app.usage.UsageEvents.Event.APP_COMPONENT_USED; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mockitoSession; + +import android.app.usage.UsageEvents; +import android.app.usage.UsageEvents.Event; +import android.content.Context; +import android.os.SystemClock; +import android.text.format.DateUtils; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.usage.UserUsageStatsService.StatsUpdatedListener; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + +import java.io.File; +import java.util.HashMap; + +@RunWith(AndroidJUnit4.class) +public class UserUsageStatsServiceTest { + private static final int TEST_USER_ID = 0; + private static final String TEST_PACKAGE_NAME = "test.package"; + private static final long TIME_INTERVAL_MILLIS = DateUtils.DAY_IN_MILLIS; + + private UserUsageStatsService mService; + private MockitoSession mMockitoSession; + + @Mock + private Context mContext; + @Mock + private StatsUpdatedListener mStatsUpdatedListener; + + @Before + public void setUp() { + mMockitoSession = mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .startMocking(); + + File dir = new File(InstrumentationRegistry.getContext().getCacheDir(), "test"); + mService = new UserUsageStatsService(mContext, TEST_USER_ID, dir, mStatsUpdatedListener); + + HashMap<String, Long> installedPkgs = new HashMap<>(); + installedPkgs.put(TEST_PACKAGE_NAME, System.currentTimeMillis()); + + mService.init(System.currentTimeMillis(), installedPkgs); + } + + @After + public void tearDown() { + if (mMockitoSession != null) { + mMockitoSession.finishMocking(); + } + } + + @Test + public void testReportEvent_eventAppearsInQueries() { + Event event = new Event(ACTIVITY_RESUMED, SystemClock.elapsedRealtime()); + event.mPackage = TEST_PACKAGE_NAME; + mService.reportEvent(event); + + long now = System.currentTimeMillis(); + long startTime = now - TIME_INTERVAL_MILLIS; + UsageEvents events = mService.queryEventsForPackage( + startTime, now, TEST_PACKAGE_NAME, false /* includeTaskRoot */); + + boolean hasTestEvent = false; + while (events != null && events.hasNextEvent()) { + Event outEvent = new Event(); + events.getNextEvent(outEvent); + if (outEvent.mEventType == ACTIVITY_RESUMED) { + hasTestEvent = true; + } + } + assertTrue(hasTestEvent); + } + + @Test + public void testReportEvent_packageUsedEventNotTracked() { + Event event = new Event(APP_COMPONENT_USED, SystemClock.elapsedRealtime()); + event.mPackage = TEST_PACKAGE_NAME; + mService.reportEvent(event); + + long now = System.currentTimeMillis(); + long startTime = now - TIME_INTERVAL_MILLIS; + UsageEvents events = mService.queryEventsForPackage( + startTime, now, TEST_PACKAGE_NAME, false /* includeTaskRoot */); + + boolean hasTestEvent = false; + while (events != null && events.hasNextEvent()) { + Event outEvent = new Event(); + events.getNextEvent(outEvent); + if (outEvent.mEventType == APP_COMPONENT_USED) { + hasTestEvent = true; + } + } + assertFalse(hasTestEvent); + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index cfa2086793a4..f897d5ca3cc8 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -160,6 +160,7 @@ public class AbstractAccessibilityServiceConnectionTest { @Mock private AccessibilitySecurityPolicy mMockSecurityPolicy; @Mock private AccessibilityWindowManager mMockA11yWindowManager; @Mock private AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport; + @Mock private AccessibilityTrace mMockA11yTrace; @Mock private WindowManagerInternal mMockWindowManagerInternal; @Mock private SystemActionPerformer mMockSystemActionPerformer; @Mock private IBinder mMockService; @@ -188,6 +189,7 @@ public class AbstractAccessibilityServiceConnectionTest { when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); when(mMockPackageManager.hasSystemFeature(FEATURE_FINGERPRINT)).thenReturn(true); + when(mMockA11yTrace.isA11yTracingEnabled()).thenReturn(false); // Fake a11yWindowInfo and remote a11y connection for tests. addA11yWindowInfo(mA11yWindowInfos, WINDOWID, false, Display.DEFAULT_DISPLAY); addA11yWindowInfo(mA11yWindowInfos, PIP_WINDOWID, true, Display.DEFAULT_DISPLAY); @@ -227,8 +229,8 @@ public class AbstractAccessibilityServiceConnectionTest { mServiceConnection = new TestAccessibilityServiceConnection(mMockContext, COMPONENT_NAME, mSpyServiceInfo, SERVICE_ID, mHandler, new Object(), mMockSecurityPolicy, - mMockSystemSupport, mMockWindowManagerInternal, mMockSystemActionPerformer, - mMockA11yWindowManager); + mMockSystemSupport, mMockA11yTrace, mMockWindowManagerInternal, + mMockSystemActionPerformer, mMockA11yWindowManager); // Assume that the service is connected mServiceConnection.mService = mMockService; mServiceConnection.mServiceInterface = mMockServiceInterface; @@ -849,12 +851,13 @@ public class AbstractAccessibilityServiceConnectionTest { TestAccessibilityServiceConnection(Context context, ComponentName componentName, AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler, Object lock, AccessibilitySecurityPolicy securityPolicy, - SystemSupport systemSupport, WindowManagerInternal windowManagerInternal, + SystemSupport systemSupport, AccessibilityTrace trace, + WindowManagerInternal windowManagerInternal, SystemActionPerformer systemActionPerfomer, AccessibilityWindowManager a11yWindowManager) { super(context, componentName, accessibilityServiceInfo, id, mainHandler, lock, - securityPolicy, systemSupport, windowManagerInternal, systemActionPerfomer, - a11yWindowManager); + securityPolicy, systemSupport, trace, windowManagerInternal, + systemActionPerfomer, a11yWindowManager); mResolvedUserId = USER_ID; } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java index 4b2a9fcd10d2..80e81d6e7cb9 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java @@ -30,7 +30,6 @@ import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEA import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -51,16 +50,19 @@ import android.view.MotionEvent; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import com.android.server.LocalServices; import com.android.server.accessibility.gestures.TouchExplorer; import com.android.server.accessibility.magnification.FullScreenMagnificationController; import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler; import com.android.server.accessibility.magnification.MagnificationGestureHandler; import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler; +import com.android.server.wm.WindowManagerInternal; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; @@ -91,7 +93,9 @@ public class AccessibilityInputFilterTest { FullScreenMagnificationGestureHandler.class, TouchExplorer.class, AutoclickController.class, AccessibilityInputFilter.class}; - private FullScreenMagnificationController mMockFullScreenMagnificationController; + @Mock private WindowManagerInternal.AccessibilityControllerInternal mMockA11yController; + @Mock private WindowManagerInternal mMockWindowManagerService; + @Mock private FullScreenMagnificationController mMockFullScreenMagnificationController; private AccessibilityManagerService mAms; private AccessibilityInputFilter mA11yInputFilter; private EventCaptor mCaptor1; @@ -134,16 +138,21 @@ public class AccessibilityInputFilterTest { public void setUp() { MockitoAnnotations.initMocks(this); Context context = InstrumentationRegistry.getContext(); + LocalServices.removeServiceForTest(WindowManagerInternal.class); + LocalServices.addService( + WindowManagerInternal.class, mMockWindowManagerService); + when(mMockWindowManagerService.getAccessibilityController()).thenReturn( + mMockA11yController); + when(mMockA11yController.isAccessibilityTracingEnabled()).thenReturn(false); setDisplayCount(1); mAms = spy(new AccessibilityManagerService(context)); - mMockFullScreenMagnificationController = mock(FullScreenMagnificationController.class); mA11yInputFilter = new AccessibilityInputFilter(context, mAms, mEventHandler); mA11yInputFilter.onInstalled(); - when(mAms.getValidDisplayList()).thenReturn(mDisplayList); - when(mAms.getFullScreenMagnificationController()).thenReturn( - mMockFullScreenMagnificationController); + doReturn(mDisplayList).when(mAms).getValidDisplayList(); + doReturn(mMockFullScreenMagnificationController).when(mAms) + .getFullScreenMagnificationController(); } @After diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java deleted file mode 100644 index 170f561aa2da..000000000000 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java +++ /dev/null @@ -1,581 +0,0 @@ -/* - * 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.accessibility; - - -import static android.view.accessibility.AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; -import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS; -import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS; -import static android.view.accessibility.AccessibilityNodeInfo.ROOT_NODE_ID; - -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import android.app.Instrumentation; -import android.content.Context; -import android.os.RemoteException; -import android.view.AccessibilityInteractionController; -import android.view.View; -import android.view.ViewRootImpl; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityNodeIdManager; -import android.view.accessibility.AccessibilityNodeInfo; -import android.view.accessibility.AccessibilityNodeProvider; -import android.view.accessibility.IAccessibilityInteractionConnectionCallback; -import android.widget.FrameLayout; -import android.widget.TextView; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; -import java.util.List; - -/** - * Tests that verify expected node and prefetched node results when finding a view by node id. We - * send some requests to the controller via View methods to control message timing. - */ -@RunWith(AndroidJUnit4.class) -public class AccessibilityInteractionControllerNodeRequestsTest { - private AccessibilityInteractionController mAccessibilityInteractionController; - @Mock - private IAccessibilityInteractionConnectionCallback mMockClientCallback1; - @Mock - private IAccessibilityInteractionConnectionCallback mMockClientCallback2; - - @Captor - private ArgumentCaptor<AccessibilityNodeInfo> mFindInfoCaptor; - @Captor private ArgumentCaptor<List<AccessibilityNodeInfo>> mPrefetchInfoListCaptor; - - private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation(); - private static final int MOCK_CLIENT_1_THREAD_AND_PROCESS_ID = 1; - private static final int MOCK_CLIENT_2_THREAD_AND_PROCESS_ID = 2; - - private static final String FRAME_LAYOUT_DESCRIPTION = "frameLayout"; - private static final String TEXT_VIEW_1_DESCRIPTION = "textView1"; - private static final String TEXT_VIEW_2_DESCRIPTION = "textView2"; - - private TestFrameLayout mFrameLayout; - private TestTextView mTextView1; - private TestTextView2 mTextView2; - - private boolean mSendClient1RequestForTextAfterTextPrefetched; - private boolean mSendClient2RequestForTextAfterTextPrefetched; - private boolean mSendRequestForTextAndIncludeUnImportantViews; - private int mMockClient1InteractionId; - private int mMockClient2InteractionId; - - @Before - public void setUp() throws Throwable { - MockitoAnnotations.initMocks(this); - - mInstrumentation.runOnMainSync(() -> { - final Context context = mInstrumentation.getTargetContext(); - final ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay()); - - mFrameLayout = new TestFrameLayout(context); - mTextView1 = new TestTextView(context); - mTextView2 = new TestTextView2(context); - - mFrameLayout.addView(mTextView1); - mFrameLayout.addView(mTextView2); - - // The controller retrieves views through this manager, and registration happens on - // when attached to a window, which we don't have. We can simply reference FrameLayout - // with ROOT_NODE_ID - AccessibilityNodeIdManager.getInstance().registerViewWithId( - mTextView1, mTextView1.getAccessibilityViewId()); - AccessibilityNodeIdManager.getInstance().registerViewWithId( - mTextView2, mTextView2.getAccessibilityViewId()); - - try { - viewRootImpl.setView(mFrameLayout, new WindowManager.LayoutParams(), null); - - } catch (WindowManager.BadTokenException e) { - // activity isn't running, we will ignore BadTokenException. - } - - mAccessibilityInteractionController = - new AccessibilityInteractionController(viewRootImpl); - }); - - } - - @After - public void tearDown() throws Throwable { - AccessibilityNodeIdManager.getInstance().unregisterViewWithId( - mTextView1.getAccessibilityViewId()); - AccessibilityNodeIdManager.getInstance().unregisterViewWithId( - mTextView2.getAccessibilityViewId()); - } - - /** - * Tests a basic request for the root node with prefetch flag - * {@link AccessibilityNodeInfo#FLAG_PREFETCH_DESCENDANTS} - * - * @throws RemoteException - */ - @Test - public void testFindRootView_withOneClient_shouldReturnRootNodeAndPrefetchDescendants() - throws RemoteException { - // Request for our FrameLayout - sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, - mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); - mInstrumentation.waitForIdleSync(); - - // Verify we get FrameLayout - verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( - mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); - AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); - assertEquals(FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription()); - - verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult( - mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId)); - // The descendants are our two TextViews - List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); - assertEquals(2, prefetchedNodes.size()); - assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); - assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription()); - - } - - /** - * Tests a basic request for TestTextView1's node with prefetch flag - * {@link AccessibilityNodeInfo#FLAG_PREFETCH_SIBLINGS} - * - * @throws RemoteException - */ - @Test - public void testFindTextView_withOneClient_shouldReturnNodeAndPrefetchedSiblings() - throws RemoteException { - // Request for TextView1 - sendNodeRequestToController(AccessibilityNodeInfo.makeNodeId( - mTextView1.getAccessibilityViewId(), AccessibilityNodeProvider.HOST_VIEW_ID), - mMockClientCallback1, mMockClient1InteractionId, FLAG_PREFETCH_SIBLINGS); - mInstrumentation.waitForIdleSync(); - - // Verify we get TextView1 - verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( - mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); - AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); - assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription()); - - // Verify the prefetched sibling of TextView1 is TextView2 - verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult( - mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId)); - // TextView2 is the prefetched sibling - List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); - assertEquals(1, prefetchedNodes.size()); - assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); - } - - /** - * Tests a series of controller requests to prevent prefetching. - * Request 1: Client 1 requests the root node - * Request 2: When the root node is initialized in - * {@link TestFrameLayout#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)}, - * Client 2 requests TestTextView1's node - * - * Request 2 on the queue prevents prefetching for Request 1. - * - * @throws RemoteException - */ - @Test - public void testFindRootAndTextNodes_withTwoClients_shouldPreventClient1Prefetch() - throws RemoteException { - mFrameLayout.setAccessibilityDelegate(new View.AccessibilityDelegate() { - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(host, info); - final long nodeId = AccessibilityNodeInfo.makeNodeId( - mTextView1.getAccessibilityViewId(), - AccessibilityNodeProvider.HOST_VIEW_ID); - - // Enqueue a request when this node is found from a different service for - // TextView1 - sendNodeRequestToController(nodeId, mMockClientCallback2, - mMockClient2InteractionId, FLAG_PREFETCH_SIBLINGS); - } - }); - // Client 1 request for FrameLayout - sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, - mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); - - mInstrumentation.waitForIdleSync(); - - // Verify client 1 gets FrameLayout - verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( - mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); - AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); - assertEquals(FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription()); - - // The second request is put in the queue in the FrameLayout's onInitializeA11yNodeInfo, - // meaning prefetching is interrupted and does not even begin for the first request - verify(mMockClientCallback1, never()) - .setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt()); - - // Verify client 2 gets TextView1 - verify(mMockClientCallback2).setFindAccessibilityNodeInfoResult( - mFindInfoCaptor.capture(), eq(mMockClient2InteractionId)); - infoSentToService = mFindInfoCaptor.getValue(); - assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription()); - - // Verify the prefetched sibling of TextView1 is TextView2 (FLAG_PREFETCH_SIBLINGS) - verify(mMockClientCallback2).setPrefetchAccessibilityNodeInfoResult( - mPrefetchInfoListCaptor.capture(), eq(mMockClient2InteractionId)); - List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); - assertEquals(1, prefetchedNodes.size()); - assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); - } - - /** - * Tests a series of controller same-service requests to interrupt prefetching and satisfy a - * pending node request. - * Request 1: Request the root node - * Request 2: When TextTextView1's node is initialized as part of Request 1's prefetching, - * request TestTextView1's node - * - * Request 1 prefetches TestTextView1's node, is interrupted by a pending request, and checks - * if its prefetched nodes satisfy any pending requests. It satisfies Request 2's request for - * TestTextView1's node. Request 2 is fulfilled, so it is removed from queue and does not - * prefetch. - * - * @throws RemoteException - */ - @Test - public void testFindRootAndTextNode_withOneClient_shouldInterruptPrefetchAndSatisfyPendingMsg() - throws RemoteException { - mSendClient1RequestForTextAfterTextPrefetched = true; - - mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){ - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(host, info); - info.setContentDescription(TEXT_VIEW_1_DESCRIPTION); - final long nodeId = AccessibilityNodeInfo.makeNodeId( - mTextView1.getAccessibilityViewId(), - AccessibilityNodeProvider.HOST_VIEW_ID); - - if (mSendClient1RequestForTextAfterTextPrefetched) { - // Prevent a loop when processing second request - mSendClient1RequestForTextAfterTextPrefetched = false; - // TextView1 is prefetched here after the FrameLayout is found. Now enqueue a - // same-client request for TextView1 - sendNodeRequestToController(nodeId, mMockClientCallback1, - ++mMockClient1InteractionId, FLAG_PREFETCH_SIBLINGS); - - } - } - }); - // Client 1 requests FrameLayout - sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, - mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); - - // Flush out all messages - mInstrumentation.waitForIdleSync(); - - // When TextView1 is prefetched for FrameLayout, we put a message on the queue in - // TextView1's onInitializeA11yNodeInfo that requests for TextView1. The service thus get - // two node results for FrameLayout and TextView1. - verify(mMockClientCallback1, times(2)) - .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt()); - - List<AccessibilityNodeInfo> foundNodes = mFindInfoCaptor.getAllValues(); - assertEquals(FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription()); - assertEquals(TEXT_VIEW_1_DESCRIPTION, foundNodes.get(1).getContentDescription()); - - // The controller will look at FrameLayout's prefetched nodes and find matching nodes in - // pending requests. The prefetched TextView1 matches the second request. The second - // request was removed from queue and prefetching for this request never occurred. - verify(mMockClientCallback1, times(1)) - .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), - eq(mMockClient1InteractionId - 1)); - List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); - assertEquals(1, prefetchedNodes.size()); - assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); - } - - /** - * Like above, but tests a series of controller requests from different services to interrupt - * prefetching and satisfy a pending node request. - * - * @throws RemoteException - */ - @Test - public void testFindRootAndTextNode_withTwoClients_shouldInterruptPrefetchAndSatisfyPendingMsg() - throws RemoteException { - mSendClient2RequestForTextAfterTextPrefetched = true; - mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){ - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(host, info); - info.setContentDescription(TEXT_VIEW_1_DESCRIPTION); - final long nodeId = AccessibilityNodeInfo.makeNodeId( - mTextView1.getAccessibilityViewId(), - AccessibilityNodeProvider.HOST_VIEW_ID); - - if (mSendClient2RequestForTextAfterTextPrefetched) { - mSendClient2RequestForTextAfterTextPrefetched = false; - // TextView1 is prefetched here. Now enqueue client 2's request for - // TextView1 - sendNodeRequestToController(nodeId, mMockClientCallback2, - mMockClient2InteractionId, FLAG_PREFETCH_SIBLINGS); - } - } - }); - // Client 1 requests FrameLayout - sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, - mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); - - mInstrumentation.waitForIdleSync(); - - // Verify client 1 gets FrameLayout - verify(mMockClientCallback1, times(1)) - .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt()); - assertEquals(FRAME_LAYOUT_DESCRIPTION, - mFindInfoCaptor.getValue().getContentDescription()); - - // Verify client 1 has prefetched nodes - verify(mMockClientCallback1, times(1)) - .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), - eq(mMockClient1InteractionId)); - - // Verify client 1's only prefetched node is TextView1 - List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); - assertEquals(1, prefetchedNodes.size()); - assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); - - // Verify client 2 gets TextView1 - verify(mMockClientCallback2, times(1)) - .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt()); - - assertEquals(TEXT_VIEW_1_DESCRIPTION, mFindInfoCaptor.getValue().getContentDescription()); - - // The second request was removed from queue and prefetching for this client request never - // occurred as it was satisfied. - verify(mMockClientCallback2, never()) - .setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt()); - - } - - @Test - public void testFindNodeById_withTwoDifferentPrefetchFlags_shouldNotSatisfyPendingRequest() - throws RemoteException { - mSendRequestForTextAndIncludeUnImportantViews = true; - mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){ - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(host, info); - info.setContentDescription(TEXT_VIEW_1_DESCRIPTION); - final long nodeId = AccessibilityNodeInfo.makeNodeId( - mTextView1.getAccessibilityViewId(), - AccessibilityNodeProvider.HOST_VIEW_ID); - - if (mSendRequestForTextAndIncludeUnImportantViews) { - mSendRequestForTextAndIncludeUnImportantViews = false; - // TextView1 is prefetched here for client 1. Now enqueue a request from a - // different client that holds different fetch flags for TextView1 - sendNodeRequestToController(nodeId, mMockClientCallback2, - mMockClient2InteractionId, - FLAG_PREFETCH_SIBLINGS | FLAG_INCLUDE_NOT_IMPORTANT_VIEWS); - } - } - }); - - // Mockito does not make copies of objects when called. It holds references, so - // the captor would point to client 2's results after all requests are processed. Verify - // prefetched node immediately - doAnswer(invocation -> { - List<AccessibilityNodeInfo> prefetched = invocation.getArgument(0); - assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetched.get(0).getContentDescription()); - return null; - }).when(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(anyList(), - eq(mMockClient1InteractionId)); - - // Client 1 requests FrameLayout - sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, - mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); - - mInstrumentation.waitForIdleSync(); - - // Verify client 1 gets FrameLayout - verify(mMockClientCallback1, times(1)) - .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), - eq(mMockClient1InteractionId)); - - assertEquals(FRAME_LAYOUT_DESCRIPTION, - mFindInfoCaptor.getValue().getContentDescription()); - - // Verify client 1 has prefetched results. The only prefetched node is TextView1 - // (from above doAnswer) - verify(mMockClientCallback1, times(1)) - .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), - eq(mMockClient1InteractionId)); - - // Verify client 2 gets TextView1 - verify(mMockClientCallback2, times(1)) - .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), - eq(mMockClient2InteractionId)); - assertEquals(TEXT_VIEW_1_DESCRIPTION, - mFindInfoCaptor.getValue().getContentDescription()); - // Verify client 2 has TextView2 as a prefetched node - verify(mMockClientCallback2, times(1)) - .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), - eq(mMockClient2InteractionId)); - List<AccessibilityNodeInfo> prefetchedNode = mPrefetchInfoListCaptor.getValue(); - assertEquals(1, prefetchedNode.size()); - assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNode.get(0).getContentDescription()); - } - - private void sendNodeRequestToController(long requestedNodeId, - IAccessibilityInteractionConnectionCallback callback, int interactionId, - int prefetchFlags) { - final int processAndThreadId = callback == mMockClientCallback1 - ? MOCK_CLIENT_1_THREAD_AND_PROCESS_ID - : MOCK_CLIENT_2_THREAD_AND_PROCESS_ID; - - mAccessibilityInteractionController.findAccessibilityNodeInfoByAccessibilityIdClientThread( - requestedNodeId, - null, interactionId, - callback, prefetchFlags, - processAndThreadId, - processAndThreadId, null, null); - - } - - private class TestFrameLayout extends FrameLayout { - - TestFrameLayout(Context context) { - super(context); - } - - @Override - public int getWindowVisibility() { - // We aren't attached to a window so let's pretend - return VISIBLE; - } - - @Override - public boolean isShown() { - // Controller check - return true; - } - - @Override - public int getAccessibilityViewId() { - // static id doesn't reset after tests so return the same one - return 0; - } - - @Override - public void addChildrenForAccessibility(ArrayList<View> outChildren) { - // ViewGroup#addChildrenForAccessbility sorting logic will switch these two - outChildren.add(mTextView1); - outChildren.add(mTextView2); - } - - @Override - public boolean includeForAccessibility() { - return true; - } - - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - info.setContentDescription(FRAME_LAYOUT_DESCRIPTION); - } - } - - private class TestTextView extends TextView { - TestTextView(Context context) { - super(context); - } - - @Override - public int getWindowVisibility() { - return VISIBLE; - } - - @Override - public boolean isShown() { - return true; - } - - @Override - public int getAccessibilityViewId() { - return 1; - } - - @Override - public boolean includeForAccessibility() { - return true; - } - - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - info.setContentDescription(TEXT_VIEW_1_DESCRIPTION); - } - } - - private class TestTextView2 extends TextView { - TestTextView2(Context context) { - super(context); - } - - @Override - public int getWindowVisibility() { - return VISIBLE; - } - - @Override - public boolean isShown() { - return true; - } - - @Override - public int getAccessibilityViewId() { - return 2; - } - - @Override - public boolean includeForAccessibility() { - return true; - } - - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - info.setContentDescription(TEXT_VIEW_2_DESCRIPTION); - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 110bb21b5851..bcc756a0f8e8 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -84,6 +84,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { @Mock private AccessibilityServiceInfo mMockServiceInfo; @Mock private ResolveInfo mMockResolveInfo; @Mock private AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport; + @Mock private WindowManagerInternal.AccessibilityControllerInternal mMockA11yController; @Mock private PackageManager mMockPackageManager; @Mock private WindowManagerInternal mMockWindowManagerService; @Mock private AccessibilitySecurityPolicy mMockSecurityPolicy; @@ -115,6 +116,9 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { when(mMockMagnificationController.getWindowMagnificationMgr()).thenReturn( mMockWindowMagnificationMgr); + when(mMockWindowManagerService.getAccessibilityController()).thenReturn( + mMockA11yController); + when(mMockA11yController.isAccessibilityTracingEnabled()).thenReturn(false); mA11yms = new AccessibilityManagerService( InstrumentationRegistry.getContext(), mMockPackageManager, @@ -153,6 +157,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { new Object(), mMockSecurityPolicy, mMockSystemSupport, + mA11yms, mMockWindowManagerService, mMockSystemActionPerformer, mMockA11yWindowManager, diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java index 6963a1ab1538..00daa5c89fba 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java @@ -85,6 +85,7 @@ public class AccessibilityServiceConnectionTest { @Mock AccessibilityWindowManager mMockA11yWindowManager; @Mock ActivityTaskManagerInternal mMockActivityTaskManagerInternal; @Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport; + @Mock AccessibilityTrace mMockA11yTrace; @Mock WindowManagerInternal mMockWindowManagerInternal; @Mock SystemActionPerformer mMockSystemActionPerformer; @Mock KeyEventDispatcher mMockKeyEventDispatcher; @@ -110,12 +111,13 @@ public class AccessibilityServiceConnectionTest { mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class); when(mMockIBinder.queryLocalInterface(any())).thenReturn(mMockServiceClient); + when(mMockA11yTrace.isA11yTracingEnabled()).thenReturn(false); mConnection = new AccessibilityServiceConnection(mMockUserState, mMockContext, COMPONENT_NAME, mMockServiceInfo, SERVICE_ID, mHandler, new Object(), - mMockSecurityPolicy, mMockSystemSupport, mMockWindowManagerInternal, - mMockSystemActionPerformer, mMockA11yWindowManager, - mMockActivityTaskManagerInternal); + mMockSecurityPolicy, mMockSystemSupport, mMockA11yTrace, + mMockWindowManagerInternal, mMockSystemActionPerformer, + mMockA11yWindowManager, mMockActivityTaskManagerInternal); when(mMockSecurityPolicy.canPerformGestures(mConnection)).thenReturn(true); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java index 8062bfec3703..160308762a58 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java @@ -63,6 +63,7 @@ public class UiAutomationManagerTest { @Mock AccessibilitySecurityPolicy mMockSecurityPolicy; @Mock AccessibilityWindowManager mMockA11yWindowManager; @Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport; + @Mock AccessibilityTrace mMockA11yTrace; @Mock WindowManagerInternal mMockWindowManagerInternal; @Mock SystemActionPerformer mMockSystemActionPerformer; @Mock IBinder mMockOwner; @@ -80,6 +81,7 @@ public class UiAutomationManagerTest { mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class); when(mMockAccessibilityServiceClient.asBinder()).thenReturn(mMockServiceAsBinder); + when(mMockA11yTrace.isA11yTracingEnabled()).thenReturn(false); final Context context = getInstrumentation().getTargetContext(); when(mMockContext.getSystemService(Context.DISPLAY_SERVICE)).thenReturn( @@ -197,7 +199,7 @@ public class UiAutomationManagerTest { private void register(int flags) { mUiAutomationManager.registerUiTestAutomationServiceLocked(mMockOwner, mMockAccessibilityServiceClient, mMockContext, mMockServiceInfo, SERVICE_ID, - mMessageCapturingHandler, mMockSecurityPolicy, mMockSystemSupport, + mMessageCapturingHandler, mMockSecurityPolicy, mMockSystemSupport, mMockA11yTrace, mMockWindowManagerInternal, mMockSystemActionPerformer, mMockA11yWindowManager, flags); } diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java index 81b2381cd629..15ada896512b 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -88,6 +88,7 @@ public class DisplayModeDirectorTest { private static final String TAG = "DisplayModeDirectorTest"; private static final boolean DEBUG = false; private static final float FLOAT_TOLERANCE = 0.01f; + private static final int DISPLAY_ID = 0; private Context mContext; private FakesInjector mInjector; @@ -107,19 +108,29 @@ public class DisplayModeDirectorTest { private DisplayModeDirector createDirectorFromRefreshRateArray( float[] refreshRates, int baseModeId) { + return createDirectorFromRefreshRateArray(refreshRates, baseModeId, refreshRates[0]); + } + + private DisplayModeDirector createDirectorFromRefreshRateArray( + float[] refreshRates, int baseModeId, float defaultRefreshRate) { DisplayModeDirector director = new DisplayModeDirector(mContext, mHandler, mInjector); - int displayId = 0; 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]; + } } + assertThat(defaultMode).isNotNull(); + SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>(); - supportedModesByDisplay.put(displayId, modes); + supportedModesByDisplay.put(DISPLAY_ID, modes); director.injectSupportedModesByDisplay(supportedModesByDisplay); SparseArray<Display.Mode> defaultModesByDisplay = new SparseArray<>(); - defaultModesByDisplay.put(displayId, modes[0]); + defaultModesByDisplay.put(DISPLAY_ID, defaultMode); director.injectDefaultModeByDisplay(defaultModesByDisplay); return director; } @@ -130,16 +141,15 @@ public class DisplayModeDirectorTest { for (int i = 0; i < numRefreshRates; i++) { refreshRates[i] = minFps + i; } - return createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/minFps); + return createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/minFps, + /*defaultRefreshRate=*/minFps); } @Test public void testDisplayModeVoting() { - int displayId = 0; - // With no votes present, DisplayModeDirector should allow any refresh rate. DesiredDisplayModeSpecs modeSpecs = - createDirectorFromFpsRange(60, 90).getDesiredDisplayModeSpecs(displayId); + createDirectorFromFpsRange(60, 90).getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(modeSpecs.baseModeId).isEqualTo(60); assertThat(modeSpecs.primaryRefreshRateRange.min).isEqualTo(0f); assertThat(modeSpecs.primaryRefreshRateRange.max).isEqualTo(Float.POSITIVE_INFINITY); @@ -156,12 +166,12 @@ public class DisplayModeDirectorTest { assertTrue(2 * numPriorities < maxFps - minFps + 1); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); - votesByDisplay.put(displayId, votes); + votesByDisplay.put(DISPLAY_ID, votes); for (int i = 0; i < numPriorities; i++) { int priority = Vote.MIN_PRIORITY + i; votes.put(priority, Vote.forRefreshRates(minFps + i, maxFps - i)); director.injectVotesByDisplay(votesByDisplay); - modeSpecs = director.getDesiredDisplayModeSpecs(displayId); + modeSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(modeSpecs.baseModeId).isEqualTo(minFps + i); assertThat(modeSpecs.primaryRefreshRateRange.min) .isEqualTo((float) (minFps + i)); @@ -177,11 +187,11 @@ public class DisplayModeDirectorTest { DisplayModeDirector director = createDirectorFromFpsRange(60, 90); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); - votesByDisplay.put(displayId, votes); + votesByDisplay.put(DISPLAY_ID, votes); votes.put(Vote.MAX_PRIORITY, Vote.forRefreshRates(65, 85)); votes.put(Vote.MIN_PRIORITY, Vote.forRefreshRates(70, 80)); director.injectVotesByDisplay(votesByDisplay); - modeSpecs = director.getDesiredDisplayModeSpecs(displayId); + modeSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(modeSpecs.baseModeId).isEqualTo(70); assertThat(modeSpecs.primaryRefreshRateRange.min).isEqualTo(70f); assertThat(modeSpecs.primaryRefreshRateRange.max).isEqualTo(80f); @@ -190,18 +200,17 @@ public class DisplayModeDirectorTest { @Test public void testVotingWithFloatingPointErrors() { - int displayId = 0; DisplayModeDirector director = createDirectorFromFpsRange(50, 90); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); - votesByDisplay.put(displayId, votes); + votesByDisplay.put(DISPLAY_ID, votes); float error = FLOAT_TOLERANCE / 4; votes.put(Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE, Vote.forRefreshRates(0, 60)); votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forRefreshRates(60 + error, 60 + error)); votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60 - error, 60 - error)); director.injectVotesByDisplay(votesByDisplay); - DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); @@ -213,15 +222,14 @@ public class DisplayModeDirectorTest { assertTrue(PRIORITY_FLICKER < Vote.PRIORITY_APP_REQUEST_REFRESH_RATE); assertTrue(PRIORITY_FLICKER < Vote.PRIORITY_APP_REQUEST_SIZE); - int displayId = 0; DisplayModeDirector director = createDirectorFromFpsRange(60, 90); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); - votesByDisplay.put(displayId, votes); + votesByDisplay.put(DISPLAY_ID, votes); votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 90)); votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(60, 60)); director.injectVotesByDisplay(votesByDisplay); - DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); @@ -229,7 +237,7 @@ public class DisplayModeDirectorTest { votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 90)); votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(90, 90)); director.injectVotesByDisplay(votesByDisplay); - desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90); @@ -237,7 +245,7 @@ public class DisplayModeDirectorTest { votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(90, 90)); votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(60, 60)); director.injectVotesByDisplay(votesByDisplay); - desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90); @@ -245,7 +253,7 @@ public class DisplayModeDirectorTest { votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 60)); votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(90, 90)); director.injectVotesByDisplay(votesByDisplay); - desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); } @@ -261,14 +269,13 @@ public class DisplayModeDirectorTest { assertTrue(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF); - int displayId = 0; DisplayModeDirector director = createDirectorFromFpsRange(60, 90); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); - votesByDisplay.put(displayId, votes); + votesByDisplay.put(DISPLAY_ID, votes); votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(60, 60)); director.injectVotesByDisplay(votesByDisplay); - DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.appRequestRefreshRateRange.min).isAtMost(60f); @@ -277,7 +284,7 @@ public class DisplayModeDirectorTest { votes.put(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE, Vote.forRefreshRates(90, Float.POSITIVE_INFINITY)); director.injectVotesByDisplay(votesByDisplay); - desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); assertThat(desiredSpecs.primaryRefreshRateRange.max).isAtLeast(90f); assertThat(desiredSpecs.appRequestRefreshRateRange.min).isAtMost(60f); @@ -285,7 +292,7 @@ public class DisplayModeDirectorTest { votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(75, 75)); director.injectVotesByDisplay(votesByDisplay); - desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(75); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(75); assertThat(desiredSpecs.appRequestRefreshRateRange.min) @@ -355,11 +362,10 @@ public class DisplayModeDirectorTest { @Test public void testVotingWithAlwaysRespectAppRequest() { - final int displayId = 0; DisplayModeDirector director = createDirectorFromFpsRange(50, 90); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); - votesByDisplay.put(displayId, votes); + votesByDisplay.put(DISPLAY_ID, votes); votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(0, 60)); votes.put(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE, Vote.forRefreshRates(60, 90)); votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(90, 90)); @@ -369,7 +375,7 @@ public class DisplayModeDirectorTest { director.injectVotesByDisplay(votesByDisplay); assertThat(director.shouldAlwaysRespectAppRequestedMode()).isFalse(); - DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); @@ -377,7 +383,7 @@ public class DisplayModeDirectorTest { director.setShouldAlwaysRespectAppRequestedMode(true); assertThat(director.shouldAlwaysRespectAppRequestedMode()).isTrue(); - desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90); assertThat(desiredSpecs.baseModeId).isEqualTo(90); @@ -385,7 +391,7 @@ public class DisplayModeDirectorTest { director.setShouldAlwaysRespectAppRequestedMode(false); assertThat(director.shouldAlwaysRespectAppRequestedMode()).isFalse(); - desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.baseModeId).isEqualTo(60); @@ -393,11 +399,10 @@ public class DisplayModeDirectorTest { @Test public void testVotingWithSwitchingTypeNone() { - final int displayId = 0; DisplayModeDirector director = createDirectorFromFpsRange(0, 90); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); - votesByDisplay.put(displayId, votes); + votesByDisplay.put(DISPLAY_ID, votes); votes.put(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE, Vote.forRefreshRates(30, 90)); votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRefreshRates(0, 60)); @@ -405,7 +410,7 @@ public class DisplayModeDirectorTest { director.injectVotesByDisplay(votesByDisplay); assertThat(director.getModeSwitchingType()) .isNotEqualTo(DisplayManager.SWITCHING_TYPE_NONE); - DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(30); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); @@ -417,7 +422,7 @@ public class DisplayModeDirectorTest { assertThat(director.getModeSwitchingType()) .isEqualTo(DisplayManager.SWITCHING_TYPE_NONE); - desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(30); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(30); assertThat(desiredSpecs.appRequestRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(30); @@ -427,29 +432,38 @@ public class DisplayModeDirectorTest { @Test public void testVotingWithSwitchingTypeWithinGroups() { - final int displayId = 0; DisplayModeDirector director = createDirectorFromFpsRange(0, 90); director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS); assertThat(director.getModeSwitchingType()) .isEqualTo(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS); - DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.allowGroupSwitching).isFalse(); } @Test public void testVotingWithSwitchingTypeWithinAndAcrossGroups() { - final int displayId = 0; DisplayModeDirector director = createDirectorFromFpsRange(0, 90); director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS); assertThat(director.getModeSwitchingType()) .isEqualTo(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS); - DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.allowGroupSwitching).isTrue(); } @Test + public void testDefaultDisplayModeIsSelectedIfAvailable() { + final float[] refreshRates = new float[]{24f, 25f, 30f, 60f, 90f}; + final int defaultModeId = 3; + DisplayModeDirector director = createDirectorFromRefreshRateArray( + refreshRates, /*baseModeId=*/0, refreshRates[defaultModeId]); + + DesiredDisplayModeSpecs specs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(specs.baseModeId).isEqualTo(defaultModeId); + } + + @Test public void testBrightnessObserverGetsUpdatedRefreshRatesForZone() { DisplayModeDirector director = createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0); diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java new file mode 100644 index 000000000000..bcd853c76a79 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -0,0 +1,327 @@ +/* + * 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.display; + +import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED; +import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED; +import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED; +import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED; +import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.verify; + +import android.app.PropertyInvalidatedCache; +import android.content.Context; +import android.os.Parcel; +import android.os.Process; +import android.platform.test.annotations.Presubmit; +import android.view.Display; +import android.view.DisplayAddress; +import android.view.DisplayInfo; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; + +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class LogicalDisplayMapperTest { + private static int sUniqueTestDisplayId = 0; + + private DisplayDeviceRepository mDisplayDeviceRepo; + private LogicalDisplayMapper mLogicalDisplayMapper; + private Context mContext; + + @Mock LogicalDisplayMapper.Listener mListenerMock; + + @Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor; + + @Before + public void setUp() { + // Share classloader to allow package private access. + System.setProperty("dexmaker.share_classloader", "true"); + MockitoAnnotations.initMocks(this); + + mContext = InstrumentationRegistry.getContext(); + mDisplayDeviceRepo = new DisplayDeviceRepository( + new DisplayManagerService.SyncRoot(), + new PersistentDataStore(new PersistentDataStore.Injector() { + @Override + public InputStream openRead() { + return null; + } + + @Override + public OutputStream startWrite() { + return null; + } + + @Override + public void finishWrite(OutputStream os, boolean success) {} + })); + + // Disable binder caches in this process. + PropertyInvalidatedCache.disableForTestMode(); + + mLogicalDisplayMapper = new LogicalDisplayMapper(mDisplayDeviceRepo, mListenerMock); + } + + + ///////////////// + // Test Methods + ///////////////// + + @Test + public void testDisplayDeviceAddAndRemove_Internal() { + DisplayDevice device = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY); + + // add + LogicalDisplay displayAdded = add(device); + assertEquals(info(displayAdded).address, info(device).address); + assertEquals(Display.DEFAULT_DISPLAY, id(displayAdded)); + + // remove + mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED); + verify(mListenerMock).onLogicalDisplayEventLocked( + mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED)); + LogicalDisplay displayRemoved = mDisplayCaptor.getValue(); + assertEquals(Display.DEFAULT_DISPLAY, id(displayRemoved)); + assertEquals(displayAdded, displayRemoved); + } + + @Test + public void testDisplayDeviceAddAndRemove_NonInternalTypes() { + testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_EXTERNAL); + testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_WIFI); + testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_OVERLAY); + testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_VIRTUAL); + testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_UNKNOWN); + + // Call the internal test again, just to verify that adding non-internal displays + // doesn't affect the ability for an internal display to become the default display. + testDisplayDeviceAddAndRemove_Internal(); + } + + @Test + public void testDisplayDeviceAdd_TwoInternalOneDefault() { + DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0); + DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY); + + LogicalDisplay display1 = add(device1); + assertEquals(info(display1).address, info(device1).address); + assertNotEquals(Display.DEFAULT_DISPLAY, id(display1)); + + LogicalDisplay display2 = add(device2); + assertEquals(info(display2).address, info(device2).address); + assertEquals(Display.DEFAULT_DISPLAY, id(display2)); + } + + @Test + public void testDisplayDeviceAdd_TwoInternalBothDefault() { + DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY); + DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY); + + LogicalDisplay display1 = add(device1); + assertEquals(info(display1).address, info(device1).address); + assertEquals(Display.DEFAULT_DISPLAY, id(display1)); + + LogicalDisplay display2 = add(device2); + assertEquals(info(display2).address, info(device2).address); + // Despite the flags, we can only have one default display + assertNotEquals(Display.DEFAULT_DISPLAY, id(display2)); + } + + @Test + public void testGetDisplayIdsLocked() { + add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + add(createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800, 0)); + add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0)); + + int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID); + assertEquals(3, ids.length); + Arrays.sort(ids); + assertEquals(Display.DEFAULT_DISPLAY, ids[0]); + } + + @Test + public void testSingleDisplayGroup() { + LogicalDisplay display1 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0)); + LogicalDisplay display3 = add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0)); + + assertEquals(Display.DEFAULT_DISPLAY_GROUP, + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1))); + assertEquals(Display.DEFAULT_DISPLAY_GROUP, + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2))); + assertEquals(Display.DEFAULT_DISPLAY_GROUP, + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3))); + } + + @Test + public void testMultipleDisplayGroups() { + LogicalDisplay display1 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0)); + + + TestDisplayDevice device3 = createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, + DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP); + LogicalDisplay display3 = add(device3); + + assertEquals(Display.DEFAULT_DISPLAY_GROUP, + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1))); + assertEquals(Display.DEFAULT_DISPLAY_GROUP, + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2))); + assertNotEquals(Display.DEFAULT_DISPLAY_GROUP, + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3))); + + // Now switch it back to the default group by removing the flag and issuing an update + DisplayDeviceInfo info = device3.getSourceInfo(); + info.flags = info.flags & ~DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP; + mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED); + + // Verify the new group is correct. + assertEquals(Display.DEFAULT_DISPLAY_GROUP, + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3))); + } + + + ///////////////// + // Helper Methods + ///////////////// + + private TestDisplayDevice createDisplayDevice(int type, int width, int height, int flags) { + return createDisplayDevice(new DisplayAddressImpl(), type, width, height, flags); + } + + private TestDisplayDevice createDisplayDevice( + DisplayAddress address, int type, int width, int height, int flags) { + TestDisplayDevice device = new TestDisplayDevice(); + DisplayDeviceInfo displayDeviceInfo = device.getSourceInfo(); + displayDeviceInfo.type = type; + displayDeviceInfo.width = width; + displayDeviceInfo.height = height; + displayDeviceInfo.flags = flags; + displayDeviceInfo.address = new DisplayAddressImpl(); + return device; + } + + private DisplayDeviceInfo info(DisplayDevice device) { + return device.getDisplayDeviceInfoLocked(); + } + + private DisplayInfo info(LogicalDisplay display) { + return display.getDisplayInfoLocked(); + } + + private int id(LogicalDisplay display) { + return display.getDisplayIdLocked(); + } + + private LogicalDisplay add(DisplayDevice device) { + mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_ADDED); + ArgumentCaptor<LogicalDisplay> displayCaptor = + ArgumentCaptor.forClass(LogicalDisplay.class); + verify(mListenerMock).onLogicalDisplayEventLocked( + displayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_ADDED)); + clearInvocations(mListenerMock); + return displayCaptor.getValue(); + } + + private void testDisplayDeviceAddAndRemove_NonInternal(int type) { + DisplayDevice device = createDisplayDevice(type, 600, 800, 0); + + // add + LogicalDisplay displayAdded = add(device); + assertEquals(info(displayAdded).address, info(device).address); + assertNotEquals(Display.DEFAULT_DISPLAY, id(displayAdded)); + + // remove + mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED); + verify(mListenerMock).onLogicalDisplayEventLocked( + mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED)); + LogicalDisplay displayRemoved = mDisplayCaptor.getValue(); + assertNotEquals(Display.DEFAULT_DISPLAY, id(displayRemoved)); + } + + /** + * Create a custom {@link DisplayAddress} to ensure we're not relying on any specific + * display-address implementation in our code. Intentionally uses default object (reference) + * equality rules. + */ + class DisplayAddressImpl extends DisplayAddress { + @Override + public void writeToParcel(Parcel out, int flags) { } + } + + class TestDisplayDevice extends DisplayDevice { + private DisplayDeviceInfo mInfo = new DisplayDeviceInfo(); + private DisplayDeviceInfo mSentInfo; + + TestDisplayDevice() { + super(null, null, "test_display_" + sUniqueTestDisplayId++, mContext); + mInfo = new DisplayDeviceInfo(); + } + + @Override + public DisplayDeviceInfo getDisplayDeviceInfoLocked() { + if (mSentInfo == null) { + mSentInfo = new DisplayDeviceInfo(); + mSentInfo.copyFrom(mInfo); + } + return mSentInfo; + } + + @Override + public void applyPendingDisplayDeviceInfoChangesLocked() { + mSentInfo = null; + } + + @Override + public boolean hasStableUniqueId() { + return true; + } + + public DisplayDeviceInfo getSourceInfo() { + return mInfo; + } + } +} + diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.java deleted file mode 100644 index 275e7c7fec04..000000000000 --- a/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.graphics.fonts; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.Context; -import android.os.FileUtils; -import android.platform.test.annotations.Presubmit; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; - -@Presubmit -@SmallTest -@RunWith(AndroidJUnit4.class) -public final class FontCrashDetectorTest { - - private File mCacheDir; - - @SuppressWarnings("ResultOfMethodCallIgnored") - @Before - public void setUp() { - Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); - mCacheDir = new File(context.getCacheDir(), "UpdatableFontDirTest"); - FileUtils.deleteContentsAndDir(mCacheDir); - mCacheDir.mkdirs(); - } - - @Test - public void detectCrash() throws Exception { - // Prepare a marker file. - File file = new File(mCacheDir, "detectCrash"); - assertThat(file.createNewFile()).isTrue(); - - FontCrashDetector detector = new FontCrashDetector(file); - assertThat(detector.hasCrashed()).isTrue(); - - detector.clear(); - assertThat(detector.hasCrashed()).isFalse(); - assertThat(file.exists()).isFalse(); - } - - @Test - public void monitorCrash() { - File file = new File(mCacheDir, "monitorCrash"); - FontCrashDetector detector = new FontCrashDetector(file); - assertThat(detector.hasCrashed()).isFalse(); - - FontCrashDetector.MonitoredBlock block = detector.start(); - assertThat(file.exists()).isTrue(); - - block.close(); - assertThat(file.exists()).isFalse(); - } -} diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java index deaeb46c4074..8b35af80e47f 100644 --- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java @@ -276,7 +276,7 @@ public class JobStoreTest { 0 /* sourceUserId */, 0, "someTag", invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis, 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */, - persistedExecutionTimesUTC, 0 /* innerFlagg */); + persistedExecutionTimesUTC, 0 /* innerFlag */, 0 /* dynamicConstraints */); mTaskStoreUnderTest.add(js); waitForPendingIo(); diff --git a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java index f87d5993c1b5..7d9ab3772733 100644 --- a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java @@ -20,8 +20,10 @@ import static com.android.server.job.JobConcurrencyManager.NUM_WORK_TYPES; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ; +import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP; +import static com.android.server.job.JobConcurrencyManager.workTypeToString; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -54,7 +56,7 @@ public class WorkCountTrackerTest { private static final double[] EQUAL_PROBABILITY_CDF = buildWorkTypeCdf(1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, - 1.0 / NUM_WORK_TYPES); + 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES); private Random mRandom; private WorkCountTracker mWorkCountTracker; @@ -66,8 +68,9 @@ public class WorkCountTrackerTest { } @NonNull - private static double[] buildWorkTypeCdf(double pTop, double pEj, double pBg, double pBgUser) { - return buildCdf(pTop, pEj, pBg, pBgUser); + private static double[] buildWorkTypeCdf( + double pTop, double pFgs, double pEj, double pBg, double pBgUser) { + return buildCdf(pTop, pFgs, pEj, pBg, pBgUser); } @NonNull @@ -102,10 +105,12 @@ public class WorkCountTrackerTest { case 0: return WORK_TYPE_TOP; case 1: - return WORK_TYPE_EJ; + return WORK_TYPE_FGS; case 2: - return WORK_TYPE_BG; + return WORK_TYPE_EJ; case 3: + return WORK_TYPE_BG; + case 4: return WORK_TYPE_BGUSER; default: throw new IllegalStateException("Unknown work type"); @@ -224,12 +229,15 @@ public class WorkCountTrackerTest { private void startPendingJobs(Jobs jobs) { while (hasStartablePendingJob(jobs)) { - final int startingWorkType = - getRandomWorkType(EQUAL_PROBABILITY_CDF, mRandom.nextDouble()); + final int workType = getRandomWorkType(EQUAL_PROBABILITY_CDF, mRandom.nextDouble()); + + if (jobs.pending.get(workType) > 0) { + final int pendingMultiType = getPendingMultiType(jobs, workType); + final int startingWorkType = mWorkCountTracker.canJobStart(pendingMultiType); + if (startingWorkType == WORK_TYPE_NONE) { + continue; + } - if (jobs.pending.get(startingWorkType) > 0 - && mWorkCountTracker.canJobStart(startingWorkType) != WORK_TYPE_NONE) { - final int pendingMultiType = getPendingMultiType(jobs, startingWorkType); jobs.removePending(pendingMultiType); jobs.running.put(startingWorkType, jobs.running.get(startingWorkType) + 1); mWorkCountTracker.stageJob(startingWorkType, pendingMultiType); @@ -304,7 +312,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final List<Pair<Integer, Integer>> minLimits = List.of(); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(0.5, 0, 0.5, 0); + final double[] cdf = buildWorkTypeCdf(0.5, 0, 0, 0.5, 0); final double[] numTypesCdf = buildCdf(.5, .3, .15, .05); final double probStart = 0.5; @@ -322,7 +330,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3); + final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 0, 1.0 / 3, 1.0 / 3); final double[] numTypesCdf = buildCdf(.75, .2, .05); final double probStart = 0.5; @@ -340,7 +348,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final List<Pair<Integer, Integer>> minLimits = List.of(); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3); + final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 0, 1.0 / 3, 1.0 / 3); final double[] numTypesCdf = buildCdf(.05, .95); final double probStart = 0.5; @@ -358,7 +366,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(0.1, 0, 0.8, .1); + final double[] cdf = buildWorkTypeCdf(0.1, 0, 0, 0.8, .1); final double[] numTypesCdf = buildCdf(.5, .3, .15, .05); final double probStart = 0.5; @@ -376,7 +384,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(0.9, 0, 0.1, 0); + final double[] cdf = buildWorkTypeCdf(0.85, 0.05, 0, 0.1, 0); final double[] numTypesCdf = buildCdf(1); final double probStart = 0.5; @@ -394,7 +402,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.4; - final double[] cdf = buildWorkTypeCdf(0.1, 0, 0.1, .8); + final double[] cdf = buildWorkTypeCdf(0.1, 0, 0, 0.1, .8); final double[] numTypesCdf = buildCdf(0.5, 0.5); final double probStart = 0.5; @@ -413,7 +421,7 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final double probStop = 0.4; - final double[] cdf = buildWorkTypeCdf(0.9, 0, 0.05, 0.05); + final double[] cdf = buildWorkTypeCdf(0.8, 0.1, 0, 0.05, 0.05); final double[] numTypesCdf = buildCdf(1); final double probStart = 0.5; @@ -432,7 +440,7 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(0, 0, 0.5, 0.5); + final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.5, 0.5); final double[] numTypesCdf = buildCdf(1); final double probStart = 0.5; @@ -451,7 +459,7 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(0, 0, 0.1, 0.9); + final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.1, 0.9); final double[] numTypesCdf = buildCdf(0.9, 0.1); final double probStart = 0.5; @@ -470,7 +478,7 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(0, 0, 0.9, 0.1); + final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.9, 0.1); final double[] numTypesCdf = buildCdf(1); final double probStart = 0.5; @@ -488,7 +496,7 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.4; - final double[] cdf = buildWorkTypeCdf(0.5, 0.5, 0, 0); + final double[] cdf = buildWorkTypeCdf(0.5, 0, 0.5, 0, 0); final double[] numTypesCdf = buildCdf(0.1, 0.7, 0.2); final double probStart = 0.5; @@ -511,7 +519,7 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1)); final double probStop = 0.13; - final double[] numTypesCdf = buildCdf(0, 0.05, 0.1, 0.85); + final double[] numTypesCdf = buildCdf(0, 0.05, 0.1, 0.8, 0.05); final double probStart = 0.87; checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, @@ -528,7 +536,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_EJ, 5), Pair.create(WORK_TYPE_BG, 4)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.4; - final double[] cdf = buildWorkTypeCdf(.1, 0.5, 0.35, 0.05); + final double[] cdf = buildWorkTypeCdf(.1, 0, 0.5, 0.35, 0.05); final double[] numTypesCdf = buildCdf(1); final double probStart = 0.5; @@ -548,7 +556,7 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.4; - final double[] cdf = buildWorkTypeCdf(0.01, 0.49, 0.1, 0.4); + final double[] cdf = buildWorkTypeCdf(0.01, 0.09, 0.4, 0.1, 0.4); final double[] numTypesCdf = buildCdf(0.7, 0.3); final double probStart = 0.5; @@ -576,11 +584,13 @@ public class WorkCountTrackerTest { startPendingJobs(jobs); for (Pair<Integer, Integer> run : resultRunning) { - assertWithMessage("Incorrect running result for work type " + run.first) + assertWithMessage( + "Incorrect running result for work type " + workTypeToString(run.first)) .that(jobs.running.get(run.first)).isEqualTo(run.second); } for (Pair<Integer, Integer> pend : resultPending) { - assertWithMessage("Incorrect pending result for work type " + pend.first) + assertWithMessage( + "Incorrect pending result for work type " + workTypeToString(pend.first)) .that(jobs.pending.get(pend.first)).isEqualTo(pend.second); } } @@ -938,10 +948,15 @@ public class WorkCountTrackerTest { assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(6); assertThat(jobs.running.get(WORK_TYPE_EJ)).isEqualTo(1); assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1); - assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(4); + // If run the TOP jobs as TOP first, and a TOP|EJ job as EJ, then we'll have 4 TOP jobs + // remaining. + assertThat(jobs.pending.get(WORK_TYPE_TOP)).isAtLeast(4); + // If we end up running the TOP|EJ jobs as TOP first, then we'll have 5 TOP jobs remaining. + assertThat(jobs.pending.get(WORK_TYPE_TOP)).isAtMost(5); // Can't equate pending EJ since some could be running as TOP and BG assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2); - assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(9); + assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtLeast(8); + assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtMost(9); // Stop all jobs jobs.maybeFinishJobs(1); @@ -975,7 +990,7 @@ public class WorkCountTrackerTest { assertThat(jobs.running.get(WORK_TYPE_EJ)).isAtLeast(1); assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1); assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0); - assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2); + assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(1); assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(4); } } diff --git a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java index 2288a8925561..cc18317d0529 100644 --- a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java @@ -18,7 +18,9 @@ package com.android.server.job; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ; +import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP; +import static com.android.server.job.JobConcurrencyManager.workTypeToString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -44,10 +46,12 @@ import java.util.List; public class WorkTypeConfigTest { private static final String KEY_MAX_TOTAL = "concurrency_max_total_test"; private static final String KEY_MAX_TOP = "concurrency_max_top_test"; + private static final String KEY_MAX_FGS = "concurrency_max_fgs_test"; private static final String KEY_MAX_EJ = "concurrency_max_ej_test"; private static final String KEY_MAX_BG = "concurrency_max_bg_test"; private static final String KEY_MAX_BGUSER = "concurrency_max_bguser_test"; private static final String KEY_MIN_TOP = "concurrency_min_top_test"; + private static final String KEY_MIN_FGS = "concurrency_min_fgs_test"; private static final String KEY_MIN_EJ = "concurrency_min_ej_test"; private static final String KEY_MIN_BG = "concurrency_min_bg_test"; private static final String KEY_MIN_BGUSER = "concurrency_min_bguser_test"; @@ -59,15 +63,17 @@ public class WorkTypeConfigTest { private void resetConfig() { // DeviceConfig.resetToDefaults() doesn't work here. Need to reset constants manually. - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOTAL, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOP, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_EJ, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BG, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BGUSER, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_TOP, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_EJ, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BG, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BGUSER, "", false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOTAL, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOP, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_FGS, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_EJ, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BG, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BGUSER, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_TOP, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_FGS, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_EJ, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BG, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BGUSER, null, false); } private void check(@Nullable DeviceConfig.Properties config, @@ -103,10 +109,12 @@ public class WorkTypeConfigTest { assertEquals(expectedTotal, counts.getMaxTotal()); for (Pair<Integer, Integer> min : expectedMinLimits) { - assertEquals((int) min.second, counts.getMinReserved(min.first)); + assertEquals("Incorrect min value for " + workTypeToString(min.first), + (int) min.second, counts.getMinReserved(min.first)); } for (Pair<Integer, Integer> max : expectedMaxLimits) { - assertEquals((int) max.second, counts.getMax(max.first)); + assertEquals("Incorrect max value for " + workTypeToString(max.first), + (int) max.second, counts.getMax(max.first)); } } @@ -193,6 +201,14 @@ public class WorkTypeConfigTest { /* min */ List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 1)), /* max */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 1))); + check(null, /*default*/ 10, + /* min */ List.of(Pair.create(WORK_TYPE_TOP, 3), Pair.create(WORK_TYPE_FGS, 2), + Pair.create(WORK_TYPE_EJ, 1), Pair.create(WORK_TYPE_BG, 1)), + /* max */ List.of(Pair.create(WORK_TYPE_FGS, 3)), + /*expected*/ true, 10, + /* min */ List.of(Pair.create(WORK_TYPE_TOP, 3), Pair.create(WORK_TYPE_FGS, 2), + Pair.create(WORK_TYPE_EJ, 1), Pair.create(WORK_TYPE_BG, 1)), + /* max */ List.of(Pair.create(WORK_TYPE_FGS, 3))); check(null, /*default*/ 15, /* min */ List.of(Pair.create(WORK_TYPE_BG, 15)), /* max */ List.of(Pair.create(WORK_TYPE_BG, 15)), @@ -289,5 +305,30 @@ public class WorkTypeConfigTest { /*expected*/ true, 16, /* min */ List.of(Pair.create(WORK_TYPE_TOP, 1), Pair.create(WORK_TYPE_BG, 8)), /* max */ List.of(Pair.create(WORK_TYPE_TOP, 16), Pair.create(WORK_TYPE_BG, 16))); + + check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER) + .setInt(KEY_MAX_TOTAL, 16) + .setInt(KEY_MAX_TOP, 16) + .setInt(KEY_MIN_TOP, 1) + .setInt(KEY_MAX_FGS, 15) + .setInt(KEY_MIN_FGS, 2) + .setInt(KEY_MAX_EJ, 14) + .setInt(KEY_MIN_EJ, 3) + .setInt(KEY_MAX_BG, 13) + .setInt(KEY_MIN_BG, 4) + .setInt(KEY_MAX_BGUSER, 12) + .setInt(KEY_MIN_BGUSER, 5) + .build(), + /*default*/ 9, + /* min */ List.of(Pair.create(WORK_TYPE_BG, 9)), + /* max */ List.of(Pair.create(WORK_TYPE_BG, 9)), + /*expected*/ true, 16, + /* min */ List.of(Pair.create(WORK_TYPE_TOP, 1), Pair.create(WORK_TYPE_FGS, 2), + Pair.create(WORK_TYPE_EJ, 3), + Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 5)), + /* max */ + List.of(Pair.create(WORK_TYPE_TOP, 16), Pair.create(WORK_TYPE_FGS, 15), + Pair.create(WORK_TYPE_EJ, 14), + Pair.create(WORK_TYPE_BG, 13), Pair.create(WORK_TYPE_BGUSER, 12))); } } diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index 74bf4f5da70d..13c3919cefc5 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -2041,7 +2041,8 @@ public class NetworkPolicyManagerServiceTest { final NetworkCapabilities networkCapabilities = new NetworkCapabilities(); networkCapabilities.addTransportType(TRANSPORT_WIFI); networkCapabilities.setSSID(TEST_SSID); - return new NetworkState(TYPE_WIFI, prop, networkCapabilities, null, null); + return new NetworkState(TYPE_WIFI, prop, networkCapabilities, new Network(TEST_NET_ID), + null); } private void expectHasInternetPermission(int uid, boolean hasIt) throws Exception { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 137cf6523caf..09a436c59e7b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -518,6 +518,7 @@ public class DisplayContentTests extends WindowTestsBase { TYPE_WALLPAPER, TYPE_APPLICATION); final WindowState wallpaper = windows[0]; assertTrue(wallpaper.mIsWallpaper); + wallpaper.mToken.asWallpaperToken().setVisibility(false); // By default WindowState#mWallpaperVisible is false. assertFalse(wallpaper.isVisible()); diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java index d663b649fbba..cc1869e72b34 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java @@ -454,7 +454,7 @@ public class LockTaskControllerTest { Settings.Secure.clearProviderForTest(); // AND a password is set - when(mLockPatternUtils.isSecure(anyInt())) + when(mLockPatternUtils.isSecure(TEST_USER_ID)) .thenReturn(true); // AND there is a task record diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java index a1e5afb8b758..5239462a1ec0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java @@ -473,6 +473,68 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { mDefaultDisplay.getDefaultTaskDisplayArea(), mResult.mPreferredTaskDisplayArea); } + @Test + public void testRecalculateFreeformInitialBoundsWithOverrideDisplayArea() { + final TestDisplayContent freeformDisplay = createNewDisplayContent( + WINDOWING_MODE_FREEFORM); + final TaskDisplayArea secondaryDisplayArea = createTaskDisplayArea(freeformDisplay, + mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST); + secondaryDisplayArea.setBounds(DISPLAY_BOUNDS.width() / 2, 0, + DISPLAY_BOUNDS.width(), DISPLAY_BOUNDS.height()); + final Task launchRoot = createTaskStackOnTaskDisplayArea(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, secondaryDisplayArea); + launchRoot.mCreatedByOrganizer = true; + secondaryDisplayArea.setLaunchRootTask(launchRoot, new int[] { WINDOWING_MODE_FREEFORM }, + new int[] { ACTIVITY_TYPE_STANDARD }); + final Rect secondaryDAStableBounds = new Rect(); + secondaryDisplayArea.getStableRect(secondaryDAStableBounds); + + // Specify the display and provide a layout so that it will be set to freeform bounds. + final ActivityOptions options = ActivityOptions.makeBasic() + .setLaunchDisplayId(freeformDisplay.getDisplayId()); + final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder() + .setGravity(Gravity.LEFT).build(); + + assertEquals(RESULT_CONTINUE, + new CalculateRequestBuilder().setOptions(options).setLayout(layout).calculate()); + + assertEquals(secondaryDisplayArea, mResult.mPreferredTaskDisplayArea); + assertTrue(secondaryDAStableBounds.contains(mResult.mBounds)); + } + + @Test + public void testRecalculateFreeformInitialBoundsWithOverrideDisplayArea_unresizableApp() { + mAtm.mSupportsNonResizableMultiWindow = true; + + final TestDisplayContent freeformDisplay = createNewDisplayContent( + WINDOWING_MODE_FREEFORM); + final TaskDisplayArea secondaryDisplayArea = createTaskDisplayArea(freeformDisplay, + mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST); + secondaryDisplayArea.setBounds(DISPLAY_BOUNDS.width() / 2, 0, + DISPLAY_BOUNDS.width(), DISPLAY_BOUNDS.height()); + final Task launchRoot = createTaskStackOnTaskDisplayArea(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, secondaryDisplayArea); + launchRoot.mCreatedByOrganizer = true; + secondaryDisplayArea.setLaunchRootTask(launchRoot, new int[] { WINDOWING_MODE_FREEFORM }, + new int[] { ACTIVITY_TYPE_STANDARD }); + final Rect secondaryDAStableBounds = new Rect(); + secondaryDisplayArea.getStableRect(secondaryDAStableBounds); + + // The bounds will get updated for unresizable with opposite orientation on freeform display + final Rect displayBounds = new Rect(freeformDisplay.getBounds()); + mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; + mActivity.info.screenOrientation = displayBounds.width() > displayBounds.height() + ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE; + final ActivityOptions options = ActivityOptions.makeBasic() + .setLaunchDisplayId(freeformDisplay.getDisplayId()); + + assertEquals(RESULT_CONTINUE, + new CalculateRequestBuilder().setOptions(options).calculate()); + + assertEquals(secondaryDisplayArea, mResult.mPreferredTaskDisplayArea); + assertTrue(secondaryDAStableBounds.contains(mResult.mBounds)); + } + // ===================================== // Launch Windowing Mode Related Tests // ===================================== @@ -521,6 +583,7 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { WINDOWING_MODE_FULLSCREEN); } + @Test public void testKeepsPictureInPictureLaunchModeInOptions() { final TestDisplayContent freeformDisplay = createNewDisplayContent( @@ -588,11 +651,14 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { } @Test - public void testNonEmptyLayoutInfersFreeformWithEmptySize() { + public void testLayoutWithGravityAndEmptySizeInfersFreeformAndRespectsCurrentSize() { final TestDisplayContent freeformDisplay = createNewDisplayContent( WINDOWING_MODE_FREEFORM); + final Rect expectedLaunchBounds = new Rect(0, 0, 200, 100); + mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea(); + mCurrent.mBounds.set(expectedLaunchBounds); final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder() .setGravity(Gravity.LEFT).build(); @@ -600,6 +666,9 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setLayout(layout).calculate()); + assertEquals(expectedLaunchBounds.width(), mResult.mBounds.width()); + assertEquals(expectedLaunchBounds.height(), mResult.mBounds.height()); + assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode, WINDOWING_MODE_FREEFORM); } @@ -1358,8 +1427,8 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { // This test case requires a relatively big app bounds to ensure the default size calculated // by letterbox won't be too small to hold the minimum width/height. configInsetsState( - freeformDisplay.getInsetsStateController().getRawInsetsState(), - DISPLAY_BOUNDS, new Rect(10, 10, 1910, 1070)); + freeformDisplay.getInsetsStateController().getRawInsetsState(), freeformDisplay, + new Rect(10, 10, 1910, 1070)); final ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchDisplayId(freeformDisplay.mDisplayId); @@ -1580,15 +1649,17 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { display.setBounds(DISPLAY_BOUNDS); display.getConfiguration().densityDpi = DENSITY_DEFAULT; display.getConfiguration().orientation = ORIENTATION_LANDSCAPE; - configInsetsState(display.getInsetsStateController().getRawInsetsState(), - DISPLAY_BOUNDS, DISPLAY_STABLE_BOUNDS); + configInsetsState(display.getInsetsStateController().getRawInsetsState(), display, + DISPLAY_STABLE_BOUNDS); return display; } /** * Creates insets sources so that we can get the expected stable frame. */ - private static void configInsetsState(InsetsState state, Rect displayFrame, Rect stableFrame) { + private static void configInsetsState(InsetsState state, DisplayContent display, + Rect stableFrame) { + final Rect displayFrame = display.getBounds(); final int dl = displayFrame.left; final int dt = displayFrame.top; final int dr = displayFrame.right; @@ -1611,6 +1682,8 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { if (sb < db) { state.getSource(ITYPE_NAVIGATION_BAR).setFrame(dl, sb, dr, db); } + // Recompute config and push to children. + display.onRequestedOverrideConfigurationChanged(display.getConfiguration()); } private ActivityRecord createSourceActivity(TestDisplayContent display) { diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 401ace03c554..154a899fb5ff 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -316,9 +316,9 @@ public class TransitionTests extends WindowTestsBase { mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */)); final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken, "wallpaperWindow"); - wallpaperWindow.mWallpaperVisible = false; + wallpaperWindowToken.setVisibleRequested(false); transition.collect(wallpaperWindowToken); - wallpaperWindow.mWallpaperVisible = true; + wallpaperWindowToken.setVisibleRequested(true); wallpaperWindow.mHasSurface = true; // doesn't matter which order collected since participants is a set diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java index d1d0ac68017a..8b4e94724e6c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -24,6 +24,8 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_OPEN; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; @@ -49,7 +51,9 @@ import android.view.Gravity; import android.view.InsetsState; import android.view.RoundedCorners; import android.view.Surface; +import android.view.SurfaceControl; import android.view.WindowManager; +import android.window.ITransitionPlayer; import androidx.test.filters.SmallTest; @@ -135,7 +139,8 @@ public class WallpaperControllerTests extends WindowTestsBase { int expectedWidth = (int) (wallpaperWidth * (displayHeight / (double) wallpaperHeight)); // Check that the wallpaper is correctly scaled - assertEquals(new Rect(0, 0, expectedWidth, displayHeight), wallpaperWindow.getFrame()); + assertEquals(expectedWidth, wallpaperWindow.getFrame().width()); + assertEquals(displayHeight, wallpaperWindow.getFrame().height()); Rect portraitFrame = wallpaperWindow.getFrame(); // Rotate the display @@ -297,6 +302,46 @@ public class WallpaperControllerTests extends WindowTestsBase { assertFalse(mAppWindow.mActivityRecord.hasFixedRotationTransform()); } + @Test + public void testWallpaperTokenVisibility() { + final DisplayContent dc = mWm.mRoot.getDefaultDisplay(); + final WallpaperWindowToken token = new WallpaperWindowToken(mWm, mock(IBinder.class), + true, dc, true /* ownerCanManageAppTokens */); + final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, token, + "wallpaperWindow"); + wallpaperWindow.setHasSurface(true); + + // Set-up mock shell transitions + final IBinder mockBinder = mock(IBinder.class); + final ITransitionPlayer mockPlayer = mock(ITransitionPlayer.class); + doReturn(mockBinder).when(mockPlayer).asBinder(); + mWm.mAtmService.getTransitionController().registerTransitionPlayer(mockPlayer); + + Transition transit = + mWm.mAtmService.getTransitionController().createTransition(TRANSIT_OPEN); + + // wallpaper windows are immediately visible when set to visible even during a transition + token.setVisibility(true); + assertTrue(wallpaperWindow.isVisible()); + assertTrue(token.isVisibleRequested()); + assertTrue(token.isVisible()); + mWm.mAtmService.getTransitionController().abort(transit); + + // In a transition, setting invisible should ONLY set requestedVisible false; otherwise + // wallpaper should remain "visible" until transition is over. + transit = mWm.mAtmService.getTransitionController().createTransition(TRANSIT_CLOSE); + transit.start(); + token.setVisibility(false); + assertTrue(wallpaperWindow.isVisible()); + assertFalse(token.isVisibleRequested()); + assertTrue(token.isVisible()); + + transit.onTransactionReady(transit.getSyncId(), mock(SurfaceControl.Transaction.class)); + transit.finishTransition(); + assertFalse(wallpaperWindow.isVisible()); + assertFalse(token.isVisible()); + } + private WindowState createWallpaperTargetWindow(DisplayContent dc) { final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService) .setTask(dc.getDefaultTaskDisplayArea().getRootHomeTask()) diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index 99c96bd0de1b..bbb885eb0dd0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -984,6 +984,22 @@ public class WindowContainerTests extends WindowTestsBase { } @Test + public void testFreezeInsets() { + final Task stack = createTaskStackOnDisplay(mDisplayContent); + final ActivityRecord activity = createActivityRecord(mDisplayContent, stack); + final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win"); + + // Set visibility to false, verify the main window of the task will be set the frozen + // insets state immediately. + activity.setVisibility(false); + assertNotNull(win.getFrozenInsetsState()); + + // Now make it visible again, verify that the insets are immediately unfrozen. + activity.setVisibility(true); + assertNull(win.getFrozenInsetsState()); + } + + @Test public void testFreezeInsetsStateWhenAppTransition() { final Task stack = createTaskStackOnDisplay(mDisplayContent); final Task task = createTaskInStack(stack, 0 /* userId */); @@ -996,15 +1012,20 @@ public class WindowContainerTests extends WindowTestsBase { sources.add(activity); // Simulate the task applying the exit transition, verify the main window of the task - // will be set the frozen insets state. + // will be set the frozen insets state before the animation starts + activity.setVisibility(false); task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */, false /* isVoiceInteraction */, sources); verify(win).freezeInsetsState(); - // Simulate the task transition finished, verify the frozen insets state of the window - // will be reset. + // Simulate the task transition finished. + activity.commitVisibility(false, false); task.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, task.mSurfaceAnimator.getAnimation()); + + // Now make it visible again, verify that the insets are immediately unfrozen even before + // transition starts. + activity.setVisibility(true); verify(win).clearFrozenInsetsState(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index ebc5c4ff280a..1f38f463a7d3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -245,6 +245,9 @@ class WindowTestsBase extends SystemServiceTestsBase { private WindowToken createWindowToken( DisplayContent dc, int windowingMode, int activityType, int type) { + if (type == TYPE_WALLPAPER) { + return createWallpaperToken(dc); + } if (type < FIRST_APPLICATION_WINDOW || type > LAST_APPLICATION_WINDOW) { return createTestWindowToken(type, dc); } @@ -252,6 +255,11 @@ class WindowTestsBase extends SystemServiceTestsBase { return createActivityRecord(dc, windowingMode, activityType); } + private WindowToken createWallpaperToken(DisplayContent dc) { + return new WallpaperWindowToken(mWm, mock(IBinder.class), true /* explicit */, dc, + true /* ownerCanManageAppTokens */); + } + WindowState createAppWindow(Task task, int type, String name) { final ActivityRecord activity = createNonAttachedActivityRecord(task.getDisplayContent()); task.addChild(activity, 0); diff --git a/services/translation/java/com/android/server/translation/TranslationManagerService.java b/services/translation/java/com/android/server/translation/TranslationManagerService.java index b6244b8fb93b..8874e0afd716 100644 --- a/services/translation/java/com/android/server/translation/TranslationManagerService.java +++ b/services/translation/java/com/android/server/translation/TranslationManagerService.java @@ -26,6 +26,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; +import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; @@ -171,20 +172,37 @@ public final class TranslationManagerService } @Override - public void updateUiTranslationState(@UiTranslationState int state, + public void updateUiTranslationStateByTaskId(@UiTranslationState int state, TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds, int taskId, int userId) { + // deprecated enforceCallerHasPermission(MANAGE_UI_TRANSLATION); synchronized (mLock) { final TranslationManagerServiceImpl service = getServiceForUserLocked(userId); if (service != null && (isDefaultServiceLocked(userId) - || isCalledByServiceAppLocked(userId, "updateUiTranslationState"))) { - service.updateUiTranslationState(state, sourceSpec, destSpec, viewIds, + || isCalledByServiceAppLocked(userId, + "updateUiTranslationStateByTaskId"))) { + service.updateUiTranslationStateLocked(state, sourceSpec, destSpec, viewIds, taskId); } } } + @Override + public void updateUiTranslationState(@UiTranslationState int state, + TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds, + IBinder token, int taskId, int userId) { + enforceCallerHasPermission(MANAGE_UI_TRANSLATION); + synchronized (mLock) { + final TranslationManagerServiceImpl service = getServiceForUserLocked(userId); + if (service != null && (isDefaultServiceLocked(userId) + || isCalledByServiceAppLocked(userId, "updateUiTranslationState"))) { + service.updateUiTranslationStateLocked(state, sourceSpec, destSpec, viewIds, + token, taskId); + } + } + } + /** * Dump the service state into the given stream. You run "adb shell dumpsys translation". */ diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java index 38be85c92197..ab6ac12c90fa 100644 --- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java +++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; +import android.os.IBinder; import android.os.RemoteException; import android.service.translation.TranslationServiceInfo; import android.util.Slog; @@ -133,18 +134,40 @@ final class TranslationManagerServiceImpl extends } @GuardedBy("mLock") - public void updateUiTranslationState(@UiTranslationState int state, + public void updateUiTranslationStateLocked(@UiTranslationState int state, TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds, int taskId) { - // TODO(b/177394471): use taskId as a temporary solution. The solution may use a token to - // content capture manager service find the activitytoken. Then we can use this - // activitytoken to find the activity to callback. But we need to change cc API so use - // temporary solution. - final ActivityTokens tokens = mActivityTaskManagerInternal.getTopActivityForTask(taskId); - if (tokens == null) { + // deprecated + final ActivityTokens taskTopActivityTokens = + mActivityTaskManagerInternal.getTopActivityForTask(taskId); + if (taskTopActivityTokens == null) { Slog.w(TAG, "Unknown activity to query for update translation state."); return; } + updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec, destSpec, + viewIds); + } + + @GuardedBy("mLock") + public void updateUiTranslationStateLocked(@UiTranslationState int state, + TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds, + IBinder token, int taskId) { + // Get top activity for a given task id + final ActivityTokens taskTopActivityTokens = + mActivityTaskManagerInternal.getTopActivityForTask(taskId); + if (taskTopActivityTokens == null + || taskTopActivityTokens.getShareableActivityToken() != token) { + Slog.w(TAG, "Unknown activity or it was finished to query for update " + + "translation state for token=" + token + " taskId=" + taskId); + return; + } + updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec, destSpec, + viewIds); + } + + private void updateUiTranslationStateByActivityTokens(ActivityTokens tokens, + @UiTranslationState int state, TranslationSpec sourceSpec, TranslationSpec destSpec, + List<AutofillId> viewIds) { try { tokens.getApplicationThread().updateUiTranslationState(tokens.getActivityToken(), state, sourceSpec, destSpec, viewIds); diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index b1e6683f0486..f35b9e2ce0ed 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -303,7 +303,9 @@ class UserUsageStatsService { // FLUSH_TO_DISK is a private event. && event.mEventType != Event.FLUSH_TO_DISK // DEVICE_SHUTDOWN is added to event list after reboot. - && event.mEventType != Event.DEVICE_SHUTDOWN) { + && event.mEventType != Event.DEVICE_SHUTDOWN + // We aren't interested in every instance of the APP_COMPONENT_USED event. + && event.mEventType != Event.APP_COMPONENT_USED) { currentDailyStats.addEvent(event); } @@ -1176,6 +1178,8 @@ class UserUsageStatsService { return "USER_STOPPED"; case Event.LOCUS_ID_SET: return "LOCUS_ID_SET"; + case Event.APP_COMPONENT_USED: + return "APP_COMPONENT_USED"; default: return "UNKNOWN_TYPE_" + eventType; } diff --git a/telecomm/java/android/telecom/BluetoothCallQualityReport.aidl b/telecomm/java/android/telecom/BluetoothCallQualityReport.aidl new file mode 100644 index 000000000000..685fe9c927b1 --- /dev/null +++ b/telecomm/java/android/telecom/BluetoothCallQualityReport.aidl @@ -0,0 +1,22 @@ +/* + * Copyright 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 android.telecom; + +/** + * {@hide} + */ +parcelable BluetoothCallQualityReport; diff --git a/telecomm/java/android/telecom/BluetoothCallQualityReport.java b/telecomm/java/android/telecom/BluetoothCallQualityReport.java index 10339a818205..8703d84831ff 100644 --- a/telecomm/java/android/telecom/BluetoothCallQualityReport.java +++ b/telecomm/java/android/telecom/BluetoothCallQualityReport.java @@ -24,6 +24,8 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import java.util.Objects; + /** * This class represents the quality report that bluetooth framework sends * whenever there's a bad voice quality is detected from their side. @@ -145,6 +147,26 @@ public final class BluetoothCallQualityReport implements Parcelable { } }; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BluetoothCallQualityReport that = (BluetoothCallQualityReport) o; + return mSentTimestampMillis == that.mSentTimestampMillis + && mChoppyVoice == that.mChoppyVoice && mRssiDbm == that.mRssiDbm + && mSnrDb == that.mSnrDb + && mRetransmittedPacketsCount == that.mRetransmittedPacketsCount + && mPacketsNotReceivedCount == that.mPacketsNotReceivedCount + && mNegativeAcknowledgementCount == that.mNegativeAcknowledgementCount; + } + + @Override + public int hashCode() { + return Objects.hash(mSentTimestampMillis, mChoppyVoice, mRssiDbm, mSnrDb, + mRetransmittedPacketsCount, mPacketsNotReceivedCount, + mNegativeAcknowledgementCount); + } + /** * Builder class for {@link ConnectionRequest} */ diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 044ea80cba4b..2a5ddfd891b1 100755 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -267,6 +267,64 @@ public final class Call { public static final String EVENT_HANDOVER_FAILED = "android.telecom.event.HANDOVER_FAILED"; + /** + * Event reported from the Telecom stack to report an in-call diagnostic message which the + * dialer app may opt to display to the user. A diagnostic message is used to communicate + * scenarios the device has detected which may impact the quality of the ongoing call. + * <p> + * For example a problem with a bluetooth headset may generate a recommendation for the user to + * try using the speakerphone instead, or if the device detects it has entered a poor service + * area, the user might be warned so that they can finish their call prior to it dropping. + * <p> + * A diagnostic message is considered persistent in nature. When the user enters a poor service + * area, for example, the accompanying diagnostic message persists until they leave the area + * of poor service. Each message is accompanied with a {@link #EXTRA_DIAGNOSTIC_MESSAGE_ID} + * which uniquely identifies the diagnostic condition being reported. The framework raises a + * call event of type {@link #EVENT_CLEAR_DIAGNOSTIC_MESSAGE} when the condition reported has + * been cleared. The dialer app should display the diagnostic message until it is cleared. + * If multiple diagnostic messages are sent with different IDs (which have not yet been cleared) + * the dialer app should prioritize the most recently received message, but still provide the + * user with a means to review past messages. + * <p> + * The text of the message is found in {@link #EXTRA_DIAGNOSTIC_MESSAGE} in the form of a human + * readable {@link CharSequence} which is intended for display in the call UX. + * <p> + * The telecom framework audibly notifies the user of the presence of a diagnostic message, so + * the dialer app needs only to concern itself with visually displaying the message. + * <p> + * The dialer app receives this event via + * {@link Call.Callback#onConnectionEvent(Call, String, Bundle)}. + */ + public static final String EVENT_DISPLAY_DIAGNOSTIC_MESSAGE = + "android.telecom.event.DISPLAY_DIAGNOSTIC_MESSAGE"; + + /** + * Event reported from the telecom framework when a diagnostic message previously raised with + * {@link #EVENT_DISPLAY_DIAGNOSTIC_MESSAGE} has cleared and is no longer pertinent. + * <p> + * The {@link #EXTRA_DIAGNOSTIC_MESSAGE_ID} indicates the diagnostic message which has been + * cleared. + * <p> + * The dialer app receives this event via + * {@link Call.Callback#onConnectionEvent(Call, String, Bundle)}. + */ + public static final String EVENT_CLEAR_DIAGNOSTIC_MESSAGE = + "android.telecom.event.CLEAR_DIAGNOSTIC_MESSAGE"; + + /** + * Integer extra representing a message ID for a message posted via + * {@link #EVENT_DISPLAY_DIAGNOSTIC_MESSAGE}. Used to ensure that the dialer app knows when + * the message in question has cleared via {@link #EVENT_CLEAR_DIAGNOSTIC_MESSAGE}. + */ + public static final String EXTRA_DIAGNOSTIC_MESSAGE_ID = + "android.telecom.extra.DIAGNOSTIC_MESSAGE_ID"; + + /** + * {@link CharSequence} extra used with {@link #EVENT_DISPLAY_DIAGNOSTIC_MESSAGE}. This is the + * diagnostic message the dialer app should display. + */ + public static final String EXTRA_DIAGNOSTIC_MESSAGE = + "android.telecom.extra.DIAGNOSTIC_MESSAGE"; /** * Reject reason used with {@link #reject(int)} to indicate that the user is rejecting this diff --git a/telecomm/java/android/telecom/CallDiagnosticService.java b/telecomm/java/android/telecom/CallDiagnosticService.java new file mode 100644 index 000000000000..201c5db74e16 --- /dev/null +++ b/telecomm/java/android/telecom/CallDiagnosticService.java @@ -0,0 +1,328 @@ +/* + * 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 android.telecom; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.annotation.SystemApi; +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; + +import com.android.internal.telecom.ICallDiagnosticService; +import com.android.internal.telecom.ICallDiagnosticServiceAdapter; + +import java.util.Map; + +/** + * The platform supports a single OEM provided {@link CallDiagnosticService}, as defined by the + * {@code call_diagnostic_service_package_name} key in the + * {@code packages/services/Telecomm/res/values/config.xml} file. An OEM can use this API to help + * provide more actionable information about calling issues the user encounters during and after + * a call. + * + * <h1>Manifest Declaration</h1> + * The following is an example of how to declare the service entry in the + * {@link CallDiagnosticService} manifest file: + * <pre> + * {@code + * <service android:name="your.package.YourCallDiagnosticServiceImplementation" + * android:permission="android.permission.BIND_CALL_DIAGNOSTIC_SERVICE"> + * <intent-filter> + * <action android:name="android.telecom.CallDiagnosticService"/> + * </intent-filter> + * </service> + * } + * </pre> + * @hide + */ +@SystemApi +public abstract class CallDiagnosticService extends Service { + + /** + * Binder stub implementation which handles incoming requests from Telecom. + */ + private final class CallDiagnosticServiceBinder extends ICallDiagnosticService.Stub { + + @Override + public void setAdapter(ICallDiagnosticServiceAdapter adapter) throws RemoteException { + handleSetAdapter(adapter); + } + + @Override + public void initializeDiagnosticCall(ParcelableCall call) throws RemoteException { + handleCallAdded(call); + } + + @Override + public void updateCall(ParcelableCall call) throws RemoteException { + handleCallUpdated(call); + } + + @Override + public void removeDiagnosticCall(String callId) throws RemoteException { + handleCallRemoved(callId); + } + + @Override + public void updateCallAudioState(CallAudioState callAudioState) throws RemoteException { + onCallAudioStateChanged(callAudioState); + } + + @Override + public void receiveDeviceToDeviceMessage(String callId, int message, int value) { + handleReceivedD2DMessage(callId, message, value); + } + + @Override + public void receiveBluetoothCallQualityReport(BluetoothCallQualityReport qualityReport) + throws RemoteException { + handleBluetoothCallQualityReport(qualityReport); + } + } + + /** + * Listens to events raised by a {@link DiagnosticCall}. + */ + private android.telecom.DiagnosticCall.Listener mDiagnosticCallListener = + new android.telecom.DiagnosticCall.Listener() { + + @Override + public void onSendDeviceToDeviceMessage(DiagnosticCall diagnosticCall, + @DiagnosticCall.MessageType int message, int value) { + handleSendDeviceToDeviceMessage(diagnosticCall, message, value); + } + + @Override + public void onDisplayDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId, + CharSequence message) { + handleDisplayDiagnosticMessage(diagnosticCall, messageId, message); + } + + @Override + public void onClearDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId) { + handleClearDiagnosticMessage(diagnosticCall, messageId); + } + }; + + /** + * The {@link Intent} that must be declared as handled by the service. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE = "android.telecom.CallDiagnosticService"; + + /** + * Map which tracks the Telecom calls received from the Telecom stack. + */ + private final Map<String, Call.Details> mCallByTelecomCallId = new ArrayMap<>(); + private final Map<String, DiagnosticCall> mDiagnosticCallByTelecomCallId = new ArrayMap<>(); + private ICallDiagnosticServiceAdapter mAdapter; + + @Nullable + @Override + public IBinder onBind(@NonNull Intent intent) { + Log.i(this, "onBind!"); + return new CallDiagnosticServiceBinder(); + } + + /** + * Telecom calls this method on the {@link CallDiagnosticService} with details about a new call + * which was added to Telecom. + * <p> + * The {@link CallDiagnosticService} returns an implementation of {@link DiagnosticCall} to be + * used for the lifespan of this call. + * + * @param call The details of the new call. + * @return An instance of {@link DiagnosticCall} which the {@link CallDiagnosticService} + * provides to be used for the lifespan of the call. + * @throws IllegalArgumentException if a {@code null} {@link DiagnosticCall} is returned. + */ + public abstract @NonNull DiagnosticCall onInitializeDiagnosticCall(@NonNull + android.telecom.Call.Details call); + + /** + * Telecom calls this method when a previous created {@link DiagnosticCall} is no longer needed. + * This happens when Telecom is no longer tracking the call in question. + * @param call The diagnostic call which is no longer tracked by Telecom. + */ + public abstract void onRemoveDiagnosticCall(@NonNull DiagnosticCall call); + + /** + * Telecom calls this method when the audio routing or available audio route information + * changes. + * <p> + * Audio state is common to all calls. + * + * @param audioState The new audio state. + */ + public abstract void onCallAudioStateChanged( + @NonNull CallAudioState audioState); + + /** + * Telecom calls this method when a {@link BluetoothCallQualityReport} is received from the + * bluetooth stack. + * @param qualityReport the {@link BluetoothCallQualityReport}. + */ + public abstract void onBluetoothCallQualityReportReceived( + @NonNull BluetoothCallQualityReport qualityReport); + + /** + * Handles a request from Telecom to set the adapater used to communicate back to Telecom. + * @param adapter + */ + private void handleSetAdapter(@NonNull ICallDiagnosticServiceAdapter adapter) { + mAdapter = adapter; + } + + /** + * Handles a request from Telecom to add a new call. + * @param parcelableCall + */ + private void handleCallAdded(@NonNull ParcelableCall parcelableCall) { + String telecomCallId = parcelableCall.getId(); + Log.i(this, "handleCallAdded: callId=%s - added", telecomCallId); + Call.Details newCallDetails = Call.Details.createFromParcelableCall(parcelableCall); + mCallByTelecomCallId.put(telecomCallId, newCallDetails); + + DiagnosticCall diagnosticCall = onInitializeDiagnosticCall(newCallDetails); + if (diagnosticCall == null) { + throw new IllegalArgumentException("A valid DiagnosticCall instance was not provided."); + } + diagnosticCall.setListener(mDiagnosticCallListener); + diagnosticCall.setCallId(telecomCallId); + mDiagnosticCallByTelecomCallId.put(telecomCallId, diagnosticCall); + } + + /** + * Handles an update to {@link Call.Details} notified by Telecom. + * Caches the call details and notifies the {@link DiagnosticCall} of the change via + * {@link DiagnosticCall#onCallDetailsChanged(Call.Details)}. + * @param parcelableCall the new parceled call details from Telecom. + */ + private void handleCallUpdated(@NonNull ParcelableCall parcelableCall) { + String telecomCallId = parcelableCall.getId(); + Log.i(this, "handleCallUpdated: callId=%s - updated", telecomCallId); + Call.Details newCallDetails = Call.Details.createFromParcelableCall(parcelableCall); + + DiagnosticCall diagnosticCall = mDiagnosticCallByTelecomCallId.get(telecomCallId); + mCallByTelecomCallId.put(telecomCallId, newCallDetails); + diagnosticCall.handleCallUpdated(newCallDetails); + } + + /** + * Handles a request from Telecom to remove an existing call. + * @param telecomCallId + */ + private void handleCallRemoved(@NonNull String telecomCallId) { + Log.i(this, "handleCallRemoved: callId=%s - removed", telecomCallId); + + if (mCallByTelecomCallId.containsKey(telecomCallId)) { + mCallByTelecomCallId.remove(telecomCallId); + } + if (mDiagnosticCallByTelecomCallId.containsKey(telecomCallId)) { + DiagnosticCall call = mDiagnosticCallByTelecomCallId.remove(telecomCallId); + // Inform the service of the removed call. + onRemoveDiagnosticCall(call); + } + } + + /** + * Handles an incoming device to device message received from Telecom. Notifies the + * {@link DiagnosticCall} via {@link DiagnosticCall#onReceiveDeviceToDeviceMessage(int, int)}. + * @param callId + * @param message + * @param value + */ + private void handleReceivedD2DMessage(@NonNull String callId, int message, int value) { + Log.i(this, "handleReceivedD2DMessage: callId=%s, msg=%d/%d", callId, message, value); + DiagnosticCall diagnosticCall = mDiagnosticCallByTelecomCallId.get(callId); + diagnosticCall.onReceiveDeviceToDeviceMessage(message, value); + } + + /** + * Handles an incoming bluetooth call quality report from Telecom. Notifies via + * {@link CallDiagnosticService#onBluetoothCallQualityReportReceived( + * BluetoothCallQualityReport)}. + * @param qualityReport The bluetooth call quality remote. + */ + private void handleBluetoothCallQualityReport(@NonNull BluetoothCallQualityReport + qualityReport) { + Log.i(this, "handleBluetoothCallQualityReport; report=%s", qualityReport); + onBluetoothCallQualityReportReceived(qualityReport); + } + + /** + * Handles a request from a {@link DiagnosticCall} to send a device to device message (received + * via {@link DiagnosticCall#sendDeviceToDeviceMessage(int, int)}. + * @param diagnosticCall + * @param message + * @param value + */ + private void handleSendDeviceToDeviceMessage(@NonNull DiagnosticCall diagnosticCall, + int message, int value) { + String callId = diagnosticCall.getCallId(); + try { + mAdapter.sendDeviceToDeviceMessage(callId, message, value); + Log.i(this, "handleSendDeviceToDeviceMessage: call=%s; msg=%d/%d", callId, message, + value); + } catch (RemoteException e) { + Log.w(this, "handleSendDeviceToDeviceMessage: call=%s; msg=%d/%d failed %s", + callId, message, value, e); + } + } + + /** + * Handles a request from a {@link DiagnosticCall} to display an in-call diagnostic message. + * Originates from {@link DiagnosticCall#displayDiagnosticMessage(int, CharSequence)}. + * @param diagnosticCall + * @param messageId + * @param message + */ + private void handleDisplayDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId, + CharSequence message) { + String callId = diagnosticCall.getCallId(); + try { + mAdapter.displayDiagnosticMessage(callId, messageId, message); + Log.i(this, "handleDisplayDiagnosticMessage: call=%s; msg=%d/%s", callId, messageId, + message); + } catch (RemoteException e) { + Log.w(this, "handleDisplayDiagnosticMessage: call=%s; msg=%d/%s failed %s", + callId, messageId, message, e); + } + } + + /** + * Handles a request from a {@link DiagnosticCall} to clear a previously shown diagnostic + * message. + * Originates from {@link DiagnosticCall#clearDiagnosticMessage(int)}. + * @param diagnosticCall + * @param messageId + */ + private void handleClearDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId) { + String callId = diagnosticCall.getCallId(); + try { + mAdapter.clearDiagnosticMessage(callId, messageId); + Log.i(this, "handleClearDiagnosticMessage: call=%s; msg=%d", callId, messageId); + } catch (RemoteException e) { + Log.w(this, "handleClearDiagnosticMessage: call=%s; msg=%d failed %s", + callId, messageId, e); + } + } +} diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 7c6253ce933a..335857af8883 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -938,6 +938,46 @@ public abstract class Connection extends Conferenceable { public static final String EVENT_RTT_AUDIO_INDICATION_CHANGED = "android.telecom.event.RTT_AUDIO_INDICATION_CHANGED"; + /** + * Connection event used to signal between the telephony {@link ConnectionService} and Telecom + * when device to device messages are sent/received. + * <p> + * Device to device messages originating from the network are sent by telephony using + * {@link Connection#sendConnectionEvent(String, Bundle)} and are routed up to any active + * {@link CallDiagnosticService} implementation which is active. + * <p> + * Likewise, if a {@link CallDiagnosticService} sends a message using + * {@link DiagnosticCall#sendDeviceToDeviceMessage(int, int)}, it will be routed to telephony + * via {@link Connection#onCallEvent(String, Bundle)}. The telephony stack will relay the + * message to the other device. + * @hide + */ + @SystemApi + public static final String EVENT_DEVICE_TO_DEVICE_MESSAGE = + "android.telecom.event.DEVICE_TO_DEVICE_MESSAGE"; + + /** + * Sent along with {@link #EVENT_DEVICE_TO_DEVICE_MESSAGE} to indicate the device to device + * message type. + * + * See {@link DiagnosticCall} for more information. + * @hide + */ + @SystemApi + public static final String EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE = + "android.telecom.extra.DEVICE_TO_DEVICE_MESSAGE_TYPE"; + + /** + * Sent along with {@link #EVENT_DEVICE_TO_DEVICE_MESSAGE} to indicate the device to device + * message value. + * <p> + * See {@link DiagnosticCall} for more information. + * @hide + */ + @SystemApi + public static final String EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE = + "android.telecom.extra.DEVICE_TO_DEVICE_MESSAGE_VALUE"; + // Flag controlling whether PII is emitted into the logs private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG); diff --git a/telecomm/java/android/telecom/DiagnosticCall.java b/telecomm/java/android/telecom/DiagnosticCall.java new file mode 100644 index 000000000000..a4952899eb46 --- /dev/null +++ b/telecomm/java/android/telecom/DiagnosticCall.java @@ -0,0 +1,381 @@ +/* + * 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 android.telecom; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.telephony.Annotation; +import android.telephony.CallQuality; +import android.telephony.ims.ImsReasonInfo; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A {@link DiagnosticCall} provides a way for a {@link CallDiagnosticService} to receive diagnostic + * information about a mobile call on the device. The {@link CallDiagnosticService} can generate + * mid-call diagnostic messages using the {@link #displayDiagnosticMessage(int, CharSequence)} API + * which provides the user with valuable information about conditions impacting their call and + * corrective actions. For example, if the {@link CallDiagnosticService} determines that conditions + * on the call are degrading, it can inform the user that the call may soon drop and that they + * can try using a different calling method (e.g. VOIP or WIFI). + * @hide + */ +@SystemApi +public abstract class DiagnosticCall { + + /** + * @hide + */ + public interface Listener { + void onSendDeviceToDeviceMessage(DiagnosticCall diagnosticCall, int message, int value); + void onDisplayDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId, + CharSequence message); + void onClearDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId); + } + + /** + * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via + * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the radio access type + * used for the current call. Based loosely on the + * {@link android.telephony.TelephonyManager#getNetworkType(int)} for the call, provides a + * high level summary of the call radio access type. + * <p> + * Valid values: + * <UL> + * <LI>{@link #NETWORK_TYPE_LTE}</LI> + * <LI>{@link #NETWORK_TYPE_IWLAN}</LI> + * <LI>{@link #NETWORK_TYPE_NR}</LI> + * </UL> + */ + public static final int MESSAGE_CALL_NETWORK_TYPE = 1; + + /** + * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via + * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the call audio codec + * used for the current call. Based loosely on the {@link Connection#EXTRA_AUDIO_CODEC} for a + * call. + * <p> + * Valid values: + * <UL> + * <LI>{@link #AUDIO_CODEC_EVS}</LI> + * <LI>{@link #AUDIO_CODEC_AMR_WB}</LI> + * <LI>{@link #AUDIO_CODEC_AMR_NB}</LI> + * </UL> + */ + public static final int MESSAGE_CALL_AUDIO_CODEC = 2; + + /** + * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via + * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the battery state of + * the device. Will typically mirror battery state reported via intents such as + * {@link android.content.Intent#ACTION_BATTERY_LOW}. + * <p> + * Valid values: + * <UL> + * <LI>{@link #BATTERY_STATE_LOW}</LI> + * <LI>{@link #BATTERY_STATE_GOOD}</LI> + * <LI>{@link #BATTERY_STATE_CHARGING}</LI> + * </UL> + */ + public static final int MESSAGE_DEVICE_BATTERY_STATE = 3; + + /** + * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via + * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the overall network + * coverage as it pertains to the current call. A {@link CallDiagnosticService} should signal + * poor coverage if the network coverage reaches a level where there is a high probability of + * the call dropping as a result. + * <p> + * Valid values: + * <UL> + * <LI>{@link #COVERAGE_POOR}</LI> + * <LI>{@link #COVERAGE_GOOD}</LI> + * </UL> + */ + public static final int MESSAGE_DEVICE_NETWORK_COVERAGE = 4; + + /**@hide*/ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "MESSAGE_", value = { + MESSAGE_CALL_NETWORK_TYPE, + MESSAGE_CALL_AUDIO_CODEC, + MESSAGE_DEVICE_BATTERY_STATE, + MESSAGE_DEVICE_NETWORK_COVERAGE + }) + public @interface MessageType {} + + /** + * Used with {@link #MESSAGE_CALL_NETWORK_TYPE} to indicate an LTE network is being used for the + * call. + */ + public static final int NETWORK_TYPE_LTE = 1; + + /** + * Used with {@link #MESSAGE_CALL_NETWORK_TYPE} to indicate WIFI calling is in use for the call. + */ + public static final int NETWORK_TYPE_IWLAN = 2; + + /** + * Used with {@link #MESSAGE_CALL_NETWORK_TYPE} to indicate a 5G NR (new radio) network is in + * used for the call. + */ + public static final int NETWORK_TYPE_NR = 3; + + /** + * Used with {@link #MESSAGE_CALL_AUDIO_CODEC} to indicate call audio is using the + * Enhanced Voice Services (EVS) codec for the call. + */ + public static final int AUDIO_CODEC_EVS = 1; + + /** + * Used with {@link #MESSAGE_CALL_AUDIO_CODEC} to indicate call audio is using the AMR + * (adaptive multi-rate) WB (wide band) audio codec. + */ + public static final int AUDIO_CODEC_AMR_WB = 2; + + /** + * Used with {@link #MESSAGE_CALL_AUDIO_CODEC} to indicate call audio is using the AMR + * (adaptive multi-rate) NB (narrow band) audio codec. + */ + public static final int AUDIO_CODEC_AMR_NB = 3; + + /** + * Used with {@link #MESSAGE_DEVICE_BATTERY_STATE} to indicate that the battery is low. + */ + public static final int BATTERY_STATE_LOW = 1; + + /** + * Used with {@link #MESSAGE_DEVICE_BATTERY_STATE} to indicate that the battery is not low. + */ + public static final int BATTERY_STATE_GOOD = 2; + + /** + * Used with {@link #MESSAGE_DEVICE_BATTERY_STATE} to indicate that the battery is charging. + */ + public static final int BATTERY_STATE_CHARGING = 3; + + /** + * Used with {@link #MESSAGE_DEVICE_NETWORK_COVERAGE} to indicate that the coverage is poor. + */ + public static final int COVERAGE_POOR = 1; + + /** + * Used with {@link #MESSAGE_DEVICE_NETWORK_COVERAGE} to indicate that the coverage is good. + */ + public static final int COVERAGE_GOOD = 2; + + private Listener mListener; + private String mCallId; + private Call.Details mCallDetails; + + /** + * @hide + */ + public void setListener(@NonNull Listener listener) { + mListener = listener; + } + + /** + * Sets the call ID for this {@link DiagnosticCall}. + * @param callId + * @hide + */ + public void setCallId(@NonNull String callId) { + mCallId = callId; + } + + /** + * @return the Telecom call ID for this {@link DiagnosticCall}. + * @hide + */ + public @NonNull String getCallId() { + return mCallId; + } + + /** + * Returns the latest {@link Call.Details} associated with this {@link DiagnosticCall} as + * reported by {@link #onCallDetailsChanged(Call.Details)}. + * @return The latest {@link Call.Details}. + */ + public @NonNull Call.Details getCallDetails() { + return mCallDetails; + } + + /** + * Telecom calls this method when the details of a call changes. + */ + public abstract void onCallDetailsChanged(@NonNull android.telecom.Call.Details details); + + /** + * The {@link CallDiagnosticService} implements this method to handle messages received via + * device to device communication. + * <p> + * See {@link #sendDeviceToDeviceMessage(int, int)} for background on device to device + * communication. + * <p> + * The underlying device to device communication protocol assumes that where there the two + * devices communicating are using a different version of the protocol, messages the recipient + * are not aware of are silently discarded. This means an older client talking to a new client + * will not receive newer messages and values sent by the new client. + */ + public abstract void onReceiveDeviceToDeviceMessage( + @MessageType int message, + int value); + + /** + * Sends a device to device message to the device on the other end of this call. + * <p> + * Device to device communication is an Android platform feature which supports low bandwidth + * communication between Android devices while they are in a call. The device to device + * communication leverages DTMF tones or RTP header extensions to pass messages. The + * messages are unacknowledged and sent in a best-effort manner. The protocols assume that the + * nature of the message are informational only and are used only to convey basic state + * information between devices. + * <p> + * Device to device messages are intentional simplifications of more rich indicators in the + * platform due to the extreme bandwidth constraints inherent with underlying device to device + * communication transports used by the telephony framework. Device to device communication is + * either accomplished by adding RFC8285 compliant RTP header extensions to the audio packets + * for a call, or using the DTMF digits A-D as a communication pathway. Signalling requirements + * for DTMF digits place a significant limitation on the amount of information which can be + * communicated during a call. + * <p> + * Allowed message types and values are: + * <ul> + * <li>{@link #MESSAGE_CALL_NETWORK_TYPE} + * <ul> + * <li>{@link #NETWORK_TYPE_LTE}</li> + * <li>{@link #NETWORK_TYPE_IWLAN}</li> + * <li>{@link #NETWORK_TYPE_NR}</li> + * </ul> + * </li> + * <li>{@link #MESSAGE_CALL_AUDIO_CODEC} + * <ul> + * <li>{@link #AUDIO_CODEC_EVS}</li> + * <li>{@link #AUDIO_CODEC_AMR_WB}</li> + * <li>{@link #AUDIO_CODEC_AMR_NB}</li> + * </ul> + * </li> + * <li>{@link #MESSAGE_DEVICE_BATTERY_STATE} + * <ul> + * <li>{@link #BATTERY_STATE_LOW}</li> + * <li>{@link #BATTERY_STATE_GOOD}</li> + * <li>{@link #BATTERY_STATE_CHARGING}</li> + * </ul> + * </li> + * <li>{@link #MESSAGE_DEVICE_NETWORK_COVERAGE} + * <ul> + * <li>{@link #COVERAGE_POOR}</li> + * <li>{@link #COVERAGE_GOOD}</li> + * </ul> + * </li> + * </ul> + * @param message The message type to send. + * @param value The message value corresponding to the type. + */ + public final void sendDeviceToDeviceMessage(int message, int value) { + if (mListener != null) { + mListener.onSendDeviceToDeviceMessage(this, message, value); + } + } + + /** + * Telecom calls this method when a GSM or CDMA call disconnects. + * The CallDiagnosticService can return a human readable disconnect message which will be passed + * to the Dialer app as the {@link DisconnectCause#getDescription()}. A dialer app typically + * shows this message at the termination of the call. If {@code null} is returned, the + * disconnect message generated by the telephony stack will be shown instead. + * <p> + * @param disconnectCause the disconnect cause for the call. + * @param preciseDisconnectCause the precise disconnect cause for the call. + * @return the disconnect message to use in place of the default Telephony message, or + * {@code null} if the default message will not be overridden. + */ + // TODO: Wire in Telephony support for this. + public abstract @Nullable CharSequence onCallDisconnected( + @Annotation.DisconnectCauses int disconnectCause, + @Annotation.PreciseDisconnectCauses int preciseDisconnectCause); + + /** + * Telecom calls this method when an IMS call disconnects and Telephony has already + * provided the disconnect reason info and disconnect message for the call. The + * {@link CallDiagnosticService} can intercept the raw IMS disconnect reason at this point and + * combine it with other call diagnostic information it is aware of to override the disconnect + * call message if desired. + * + * @param disconnectReason The {@link ImsReasonInfo} associated with the call disconnection. + * @return A user-readable call disconnect message to use in place of the platform-generated + * disconnect message, or {@code null} if the disconnect message should not be overridden. + */ + // TODO: Wire in Telephony support for this. + public abstract @Nullable CharSequence onCallDisconnected( + @NonNull ImsReasonInfo disconnectReason); + + /** + * Telecom calls this method when a {@link CallQuality} report is received from the telephony + * stack for a call. + * @param callQuality The call quality report for this call. + */ + public abstract void onCallQualityReceived(@NonNull CallQuality callQuality); + + /** + * Signals the active default dialer app to display a call diagnostic message. This can be + * used to report problems encountered during the span of a call. + * <p> + * The {@link CallDiagnosticService} provides a unique client-specific identifier used to + * identify the specific diagnostic message type. + * <p> + * The {@link CallDiagnosticService} should call {@link #clearDiagnosticMessage(int)} when the + * diagnostic condition has cleared. + * @param messageId the unique message identifier. + * @param message a human-readable, localized message to be shown to the user indicating a + * call issue which has occurred, along with potential mitigating actions. + */ + public final void displayDiagnosticMessage(int messageId, @NonNull + CharSequence message) { + if (mListener != null) { + mListener.onDisplayDiagnosticMessage(this, messageId, message); + } + } + + /** + * Signals to the active default dialer that the diagnostic message previously signalled using + * {@link #displayDiagnosticMessage(int, CharSequence)} with the specified messageId is no + * longer applicable (e.g. service has improved, for example. + * @param messageId the message identifier for a message previously shown via + * {@link #displayDiagnosticMessage(int, CharSequence)}. + */ + public final void clearDiagnosticMessage(int messageId) { + if (mListener != null) { + mListener.onClearDiagnosticMessage(this, messageId); + } + } + + /** + * Called by the {@link CallDiagnosticService} to update the call details for this + * {@link DiagnosticCall} based on an update received from Telecom. + * @param newDetails the new call details. + * @hide + */ + public void handleCallUpdated(@NonNull Call.Details newDetails) { + mCallDetails = newDetails; + onCallDetailsChanged(newDetails); + } +} diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java index 2a4fdcb1475d..922eddb6ac3e 100644 --- a/telecomm/java/android/telecom/Log.java +++ b/telecomm/java/android/telecom/Log.java @@ -522,7 +522,7 @@ public class Log { return ""; } return Arrays.stream(packageName.split("\\.")) - .map(s -> s.substring(0,1)) + .map(s -> s.length() == 0 ? "" : s.substring(0, 1)) .collect(Collectors.joining("")); } } diff --git a/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl b/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl new file mode 100644 index 000000000000..65b4d19b3d9b --- /dev/null +++ b/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl @@ -0,0 +1,37 @@ +/* + * 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.internal.telecom; + +import android.telecom.BluetoothCallQualityReport; +import android.telecom.CallAudioState; +import android.telecom.ParcelableCall; +import com.android.internal.telecom.ICallDiagnosticServiceAdapter; + +/** + * Internal remote interface for a call diagnostic service. + * @see android.telecom.CallDiagnosticService + * @hide + */ +oneway interface ICallDiagnosticService { + void setAdapter(in ICallDiagnosticServiceAdapter adapter); + void initializeDiagnosticCall(in ParcelableCall call); + void updateCall(in ParcelableCall call); + void updateCallAudioState(in CallAudioState callAudioState); + void removeDiagnosticCall(in String callId); + void receiveDeviceToDeviceMessage(in String callId, int message, int value); + void receiveBluetoothCallQualityReport(in BluetoothCallQualityReport qualityReport); +} diff --git a/telecomm/java/com/android/internal/telecom/ICallDiagnosticServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/ICallDiagnosticServiceAdapter.aidl new file mode 100644 index 000000000000..92eec2a95430 --- /dev/null +++ b/telecomm/java/com/android/internal/telecom/ICallDiagnosticServiceAdapter.aidl @@ -0,0 +1,32 @@ +/* + * 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.internal.telecom; + +import android.telecom.CallAudioState; +import android.telecom.ParcelableCall; + +/** + * Remote interface for messages from the CallDiagnosticService to the platform. + * @see android.telecom.CallDiagnosticService + * @hide + */ +oneway interface ICallDiagnosticServiceAdapter { + void displayDiagnosticMessage(in String callId, int messageId, in CharSequence message); + void clearDiagnosticMessage(in String callId, int messageId); + void sendDeviceToDeviceMessage(in String callId, int message, int value); + void overrideDisconnectMessage(in String callId, in CharSequence message); +} diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index eb106b50f69d..78283fa73514 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -353,4 +353,8 @@ interface ITelecomService { */ void setTestDefaultDialer(in String packageName); + /** + * @see TelecomServiceImpl#setTestCallDiagnosticService + */ + void setTestCallDiagnosticService(in String packageName); } diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 1473b7a8873d..cf4e6779b363 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -891,6 +891,14 @@ public class SubscriptionManager { public static final String PROFILE_CLASS = SimInfo.COLUMN_PROFILE_CLASS; /** + * TelephonyProvider column name for VoIMS opt-in status. + * + * <P>Type: INTEGER (int)</P> + * @hide + */ + public static final String VOIMS_OPT_IN_STATUS = SimInfo.COLUMN_VOIMS_OPT_IN_STATUS; + + /** * Profile class of the subscription * @hide */ diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 61e809b55031..b46440d7d557 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -9129,18 +9129,11 @@ public class TelephonyManager { */ public static final int CALL_COMPOSER_STATUS_ON = 1; - /** - * Call composer status indicating that sending/receiving pictures is disabled. - * All other attachments are still enabled in this state. - */ - public static final int CALL_COMPOSER_STATUS_ON_NO_PICTURES = 2; - /** @hide */ @IntDef(prefix = {"CALL_COMPOSER_STATUS_"}, value = { CALL_COMPOSER_STATUS_ON, CALL_COMPOSER_STATUS_OFF, - CALL_COMPOSER_STATUS_ON_NO_PICTURES, }) public @interface CallComposerStatus {} @@ -9148,9 +9141,8 @@ public class TelephonyManager { * Set the user-set status for enriched calling with call composer. * * @param status user-set status for enriched calling with call composer; - * it must be any of {@link #CALL_COMPOSER_STATUS_ON} - * {@link #CALL_COMPOSER_STATUS_OFF}, - * or {@link #CALL_COMPOSER_STATUS_ON_NO_PICTURES} + * it must be either {@link #CALL_COMPOSER_STATUS_ON} or + * {@link #CALL_COMPOSER_STATUS_OFF}. * * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()} @@ -9160,7 +9152,7 @@ public class TelephonyManager { */ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallComposerStatus(@CallComposerStatus int status) { - if (status > CALL_COMPOSER_STATUS_ON_NO_PICTURES + if (status > CALL_COMPOSER_STATUS_ON || status < CALL_COMPOSER_STATUS_OFF) { throw new IllegalArgumentException("requested status is invalid"); } @@ -9183,9 +9175,8 @@ public class TelephonyManager { * * @throws SecurityException if the caller does not have the permission. * - * @return the user-set status for enriched calling with call composer, any of - * {@link #CALL_COMPOSER_STATUS_ON}, {@link #CALL_COMPOSER_STATUS_OFF}, or - * {@link #CALL_COMPOSER_STATUS_ON_NO_PICTURES}. + * @return the user-set status for enriched calling with call composer, either of + * {@link #CALL_COMPOSER_STATUS_ON} or {@link #CALL_COMPOSER_STATUS_OFF}. */ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public @CallComposerStatus int getCallComposerStatus() { diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index 6fda2e12fffd..0ab679f79b29 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -866,6 +866,19 @@ public class ProvisioningManager { public static final int KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID = 67; /** + * An integer key representing the voice over IMS opt-in provisioning status for the + * associated subscription. Determines whether the user can see for voice services over + * IMS. + * <p> + * Use {@link #PROVISIONING_VALUE_ENABLED} to enable VoIMS provisioning and + * {@link #PROVISIONING_VALUE_DISABLED} to disable VoIMS provisioning. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + * @hide + */ + public static final int KEY_VOIMS_OPT_IN_STATUS = 68; + + /** * Callback for IMS provisioning changes. */ public static class Callback { diff --git a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java index bdf628b4d339..cedf48b0b8e1 100644 --- a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java +++ b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java @@ -24,9 +24,14 @@ import android.net.Uri; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; +import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -39,6 +44,8 @@ import java.util.List; @SystemApi public final class RcsContactPresenceTuple implements Parcelable { + private static final String LOG_TAG = "RcsContactPresenceTuple"; + /** * The service ID used to indicate that service discovery via presence is available. * <p> @@ -370,7 +377,8 @@ public final class RcsContactPresenceTuple implements Parcelable { } /** - * The optional SIP Contact URI associated with the PIDF tuple element. + * The optional SIP Contact URI associated with the PIDF tuple element if the network + * expects the user to use the URI instead of the contact URI to contact it. */ public @NonNull Builder setContactUri(@NonNull Uri contactUri) { mPresenceTuple.mContactUri = contactUri; @@ -381,8 +389,24 @@ public final class RcsContactPresenceTuple implements Parcelable { * The optional timestamp indicating the data and time of the status change of this tuple. * Per RFC3863 section 4.1.7, the timestamp is formatted as an IMPP datetime format * string per RFC3339. + * @hide */ public @NonNull Builder setTimestamp(@NonNull String timestamp) { + try { + mPresenceTuple.mTimestamp = + DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(timestamp, Instant::from); + } catch (DateTimeParseException e) { + Log.d(LOG_TAG, "Parse timestamp failed " + e); + } + return this; + } + + /** + * The optional timestamp indicating the data and time of the status change of this tuple. + * Per RFC3863 section 4.1.7, the timestamp is formatted as an IMPP datetime format + * string per RFC3339. + */ + public @NonNull Builder setTime(@NonNull Instant timestamp) { mPresenceTuple.mTimestamp = timestamp; return this; } @@ -414,7 +438,7 @@ public final class RcsContactPresenceTuple implements Parcelable { } private Uri mContactUri; - private String mTimestamp; + private Instant mTimestamp; private @BasicStatus String mStatus; // The service information in the service-description element. @@ -433,7 +457,7 @@ public final class RcsContactPresenceTuple implements Parcelable { private RcsContactPresenceTuple(Parcel in) { mContactUri = in.readParcelable(Uri.class.getClassLoader()); - mTimestamp = in.readString(); + mTimestamp = convertStringFormatTimeToInstant(in.readString()); mStatus = in.readString(); mServiceId = in.readString(); mServiceVersion = in.readString(); @@ -444,7 +468,7 @@ public final class RcsContactPresenceTuple implements Parcelable { @Override public void writeToParcel(@NonNull Parcel out, int flags) { out.writeParcelable(mContactUri, flags); - out.writeString(mTimestamp); + out.writeString(convertInstantToStringFormat(mTimestamp)); out.writeString(mStatus); out.writeString(mServiceId); out.writeString(mServiceVersion); @@ -470,6 +494,26 @@ public final class RcsContactPresenceTuple implements Parcelable { } }; + // Convert the Instant to the string format + private String convertInstantToStringFormat(Instant instant) { + if (instant == null) { + return ""; + } + return instant.toString(); + } + + // Convert the time string format to Instant + private @Nullable Instant convertStringFormatTimeToInstant(String timestamp) { + if (TextUtils.isEmpty(timestamp)) { + return null; + } + try { + return DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(timestamp, Instant::from); + } catch (DateTimeParseException e) { + return null; + } + } + /** @return the status of the tuple element. */ public @NonNull @BasicStatus String getStatus() { return mStatus; @@ -490,8 +534,16 @@ public final class RcsContactPresenceTuple implements Parcelable { return mContactUri; } - /** @return the timestamp element contained in the tuple if it exists */ + /** + * @return the timestamp element contained in the tuple if it exists + * @hide + */ public @Nullable String getTimestamp() { + return (mTimestamp == null) ? null : mTimestamp.toString(); + } + + /** @return the timestamp element contained in the tuple if it exists */ + public @Nullable Instant getTime() { return mTimestamp; } diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java index 9299fed1e27d..52d0f036788c 100644 --- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java +++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java @@ -340,6 +340,7 @@ public final class RcsContactUceCapability implements Parcelable { } /** + * Retrieve the contact URI requested by the applications. * @return the URI representing the contact associated with the capabilities. */ public @NonNull Uri getContactUri() { diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java index 09c07d3f203c..815c08d120c2 100644 --- a/telephony/java/android/telephony/ims/RcsUceAdapter.java +++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java @@ -32,11 +32,12 @@ import android.telephony.TelephonyFrameworkInitializer; import android.telephony.ims.aidl.IImsRcsController; import android.telephony.ims.aidl.IRcsUceControllerCallback; import android.telephony.ims.aidl.IRcsUcePublishStateCallback; -import android.telephony.ims.feature.RcsFeature; import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -431,13 +432,15 @@ public class RcsUceAdapter { /** * The pending request has completed successfully due to all requested contacts information - * being delivered. + * being delivered. The callback {@link #onCapabilitiesReceived(List)} + * for each contacts is required to be called before {@link #onComplete} is called. */ void onComplete(); /** * The pending request has resulted in an error and may need to be retried, depending on the - * error code. + * error code. The callback {@link #onCapabilitiesReceived(List)} + * for each contacts is required to be called before {@link #onError} is called. * @param errorCode The reason for the framework being unable to process the request. * @param retryIntervalMillis The time in milliseconds the requesting application should * wait before retrying, if non-zero. @@ -484,7 +487,6 @@ public class RcsUceAdapter { * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes. * @hide */ - @SystemApi @RequiresPermission(allOf = {Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, Manifest.permission.READ_CONTACTS}) public void requestCapabilities(@NonNull List<Uri> contactNumbers, @@ -550,6 +552,94 @@ public class RcsUceAdapter { } /** + * Request the User Capability Exchange capabilities for one or more contacts. + * <p> + * This will return the cached capabilities of the contact and will not perform a capability + * poll on the network unless there are contacts being queried with stale information. + * <p> + * Be sure to check the availability of this feature using + * {@link ImsRcsManager#isAvailable(int, int)} and ensuring + * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or + * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is enabled or else + * this operation will fail with {@link #ERROR_NOT_AVAILABLE} or {@link #ERROR_NOT_ENABLED}. + * + * @param contactNumbers A list of numbers that the capabilities are being requested for. + * @param executor The executor that will be used when the request is completed and the + * {@link CapabilitiesCallback} is called. + * @param c A one-time callback for when the request for capabilities completes or there is an + * error processing the request. + * @throws ImsException if the subscription associated with this instance of + * {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not + * available. This can happen if the ImsService has crashed, for example, or if the subscription + * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes. + * @hide + */ + @SystemApi + @RequiresPermission(allOf = {Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, + Manifest.permission.READ_CONTACTS}) + public void requestCapabilities(@NonNull Collection<Uri> contactNumbers, + @NonNull @CallbackExecutor Executor executor, + @NonNull CapabilitiesCallback c) throws ImsException { + if (c == null) { + throw new IllegalArgumentException("Must include a non-null CapabilitiesCallback."); + } + if (executor == null) { + throw new IllegalArgumentException("Must include a non-null Executor."); + } + if (contactNumbers == null) { + throw new IllegalArgumentException("Must include non-null contact number list."); + } + + IImsRcsController imsRcsController = getIImsRcsController(); + if (imsRcsController == null) { + Log.e(TAG, "requestCapabilities: IImsRcsController is null"); + throw new ImsException("Can not find remote IMS service", + ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + } + + IRcsUceControllerCallback internalCallback = new IRcsUceControllerCallback.Stub() { + @Override + public void onCapabilitiesReceived(List<RcsContactUceCapability> contactCapabilities) { + final long callingIdentity = Binder.clearCallingIdentity(); + try { + executor.execute(() -> c.onCapabilitiesReceived(contactCapabilities)); + } finally { + restoreCallingIdentity(callingIdentity); + } + } + @Override + public void onComplete() { + final long callingIdentity = Binder.clearCallingIdentity(); + try { + executor.execute(() -> c.onComplete()); + } finally { + restoreCallingIdentity(callingIdentity); + } + } + @Override + public void onError(int errorCode, long retryAfterMilliseconds) { + final long callingIdentity = Binder.clearCallingIdentity(); + try { + executor.execute(() -> c.onError(errorCode, retryAfterMilliseconds)); + } finally { + restoreCallingIdentity(callingIdentity); + } + } + }; + + try { + imsRcsController.requestCapabilities(mSubId, mContext.getOpPackageName(), + mContext.getAttributionTag(), new ArrayList(contactNumbers), internalCallback); + } catch (ServiceSpecificException e) { + throw new ImsException(e.toString(), e.errorCode); + } catch (RemoteException e) { + Log.e(TAG, "Error calling IImsRcsController#requestCapabilities", e); + throw new ImsException("Remote IMS Service is not available", + ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + } + } + + /** * Ignore the device cache and perform a capability discovery for one contact, also called * "availability fetch." * <p> @@ -570,6 +660,10 @@ public class RcsUceAdapter { * {@link CapabilitiesCallback} is called. * @param c A one-time callback for when the request for capabilities completes or there is * an error processing the request. + * @throws ImsException if the subscription associated with this instance of + * {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not + * available. This can happen if the ImsService has crashed, for example, or if the subscription + * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes. * @hide */ @SystemApi diff --git a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl index 5eee3890f1dc..1b5e5603ec66 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl @@ -47,4 +47,6 @@ interface IImsConfig { void removeRcsConfigCallback(IRcsConfigCallback c); void triggerRcsReconfiguration(); void setRcsClientConfiguration(in RcsClientConfiguration rcc); + void notifyIntImsConfigChanged(int item, int value); + void notifyStringImsConfigChanged(int item, String value); } diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java index 34984e05e181..21aeb64bb417 100644 --- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java @@ -258,6 +258,16 @@ public class ImsConfigImplBase { public void setRcsClientConfiguration(RcsClientConfiguration rcc) throws RemoteException { getImsConfigImpl().setRcsClientConfiguration(rcc); } + + @Override + public void notifyIntImsConfigChanged(int item, int value) throws RemoteException { + notifyImsConfigChanged(item, value); + } + + @Override + public void notifyStringImsConfigChanged(int item, String value) throws RemoteException { + notifyImsConfigChanged(item, value); + } } /** diff --git a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java index 908869beb607..00c91681d9ea 100644 --- a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java +++ b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java @@ -31,6 +31,7 @@ import android.util.Pair; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; @@ -241,7 +242,7 @@ public class RcsCapabilityExchangeImplBase { /** * Notify the framework of the response to the SUBSCRIBE request from - * {@link #subscribeForCapabilities(List, SubscribeResponseCallback)}. + * {@link #subscribeForCapabilities(Collection, SubscribeResponseCallback)}. * <p> * If the carrier network responds to the SUBSCRIBE request with a 2XX response, then the * framework will expect the IMS stack to call {@link #onNotifyCapabilitiesUpdate}, @@ -266,7 +267,7 @@ public class RcsCapabilityExchangeImplBase { /** * Notify the framework of the response to the SUBSCRIBE request from - * {@link #subscribeForCapabilities(List, SubscribeResponseCallback)} that also + * {@link #subscribeForCapabilities(Collection, SubscribeResponseCallback)} that also * includes a reason provided in the “reason” header. See RFC3326 for more * information. * @@ -388,6 +389,7 @@ public class RcsCapabilityExchangeImplBase { * @param uris A {@link List} of the {@link Uri}s that the framework is requesting the UCE * capabilities for. * @param cb The callback of the subscribe request. + * @hide */ // executor used is defined in the constructor. @SuppressLint("ExecutorRegistration") @@ -403,6 +405,40 @@ public class RcsCapabilityExchangeImplBase { } /** + * The user capabilities of one or multiple contacts have been requested by the framework. + * <p> + * The implementer must follow up this call with an + * {@link SubscribeResponseCallback#onCommandError} call to indicate this operation has failed. + * The response from the network to the SUBSCRIBE request must be sent back to the framework + * using {@link SubscribeResponseCallback#onNetworkResponse(int, String)}. + * As NOTIFY requests come in from the network, the requested contact’s capabilities should be + * sent back to the framework using + * {@link SubscribeResponseCallback#onNotifyCapabilitiesUpdate(List<String>}) and + * {@link SubscribeResponseCallback#onResourceTerminated(List<Pair<Uri, String>>)} + * should be called with the presence information for the contacts specified. + * <p> + * Once the subscription is terminated, + * {@link SubscribeResponseCallback#onTerminated(String, long)} must be called for the + * framework to finish listening for NOTIFY responses. + * + * @param uris A {@link Collection} of the {@link Uri}s that the framework is requesting the + * UCE capabilities for. + * @param cb The callback of the subscribe request. + */ + // executor used is defined in the constructor. + @SuppressLint("ExecutorRegistration") + public void subscribeForCapabilities(@NonNull Collection<Uri> uris, + @NonNull SubscribeResponseCallback cb) { + // Stub - to be implemented by service + Log.w(LOG_TAG, "subscribeForCapabilities called with no implementation."); + try { + cb.onCommandError(COMMAND_CODE_NOT_SUPPORTED); + } catch (ImsException e) { + // Do not do anything, this is a stub implementation. + } + } + + /** * The capabilities of this device have been updated and should be published to the network. * <p> * If this operation succeeds, network response updates should be sent to the framework using diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java index 15d19a49ee56..541292a1e230 100644 --- a/telephony/java/com/android/internal/telephony/DctConstants.java +++ b/telephony/java/com/android/internal/telephony/DctConstants.java @@ -114,6 +114,7 @@ public class DctConstants { public static final int EVENT_CARRIER_CONFIG_CHANGED = BASE + 54; public static final int EVENT_SIM_STATE_UPDATED = BASE + 55; public static final int EVENT_APN_UNTHROTTLED = BASE + 56; + public static final int EVENT_AIRPLANE_MODE_CHANGED = BASE + 57; /***** Constants *****/ diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt index 386dafc590af..b61310aa4bd8 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt @@ -102,7 +102,7 @@ class OpenAppWarmTest(private val testSpec: FlickerTestParameter) { @Test fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - @Presubmit + @FlakyTest @Test fun visibleWindowsShownMoreThanOneConsecutiveEntry() = testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry() diff --git a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt new file mode 100644 index 000000000000..2e985fbba269 --- /dev/null +++ b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt @@ -0,0 +1,130 @@ +/* + * 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.test.input + +import android.view.InputDevice.SOURCE_MOUSE +import android.view.InputDevice.SOURCE_TOUCHSCREEN +import android.view.InputEventAssigner +import android.view.KeyEvent +import android.view.MotionEvent +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * Create a MotionEvent with the provided action, eventTime, and source + */ +fun createMotionEvent(action: Int, eventTime: Long, source: Int): MotionEvent { + val downTime: Long = 10 + val x = 1f + val y = 2f + val pressure = 3f + val size = 1f + val metaState = 0 + val xPrecision = 0f + val yPrecision = 0f + val deviceId = 1 + val edgeFlags = 0 + val displayId = 0 + return MotionEvent.obtain(downTime, eventTime, action, x, y, pressure, size, metaState, + xPrecision, yPrecision, deviceId, edgeFlags, source, displayId) +} + +fun createKeyEvent(action: Int, eventTime: Long): KeyEvent { + val code = KeyEvent.KEYCODE_A + val repeat = 0 + return KeyEvent(eventTime, eventTime, action, code, repeat) +} + +class InputEventAssignerTest { + companion object { + private const val TAG = "InputEventAssignerTest" + } + + /** + * A single MOVE event should be assigned to the next available frame. + */ + @Test + fun testTouchGesture() { + val assigner = InputEventAssigner() + val event = createMotionEvent(MotionEvent.ACTION_MOVE, 10, SOURCE_TOUCHSCREEN) + val eventId = assigner.processEvent(event) + assertEquals(event.id, eventId) + } + + /** + * DOWN event should be used until a vsync comes in. After vsync, the latest event should be + * produced. + */ + @Test + fun testTouchDownWithMove() { + val assigner = InputEventAssigner() + val down = createMotionEvent(MotionEvent.ACTION_DOWN, 10, SOURCE_TOUCHSCREEN) + val move1 = createMotionEvent(MotionEvent.ACTION_MOVE, 12, SOURCE_TOUCHSCREEN) + val move2 = createMotionEvent(MotionEvent.ACTION_MOVE, 13, SOURCE_TOUCHSCREEN) + val move3 = createMotionEvent(MotionEvent.ACTION_MOVE, 14, SOURCE_TOUCHSCREEN) + val move4 = createMotionEvent(MotionEvent.ACTION_MOVE, 15, SOURCE_TOUCHSCREEN) + var eventId = assigner.processEvent(down) + assertEquals(down.id, eventId) + eventId = assigner.processEvent(move1) + assertEquals(down.id, eventId) + eventId = assigner.processEvent(move2) + // Even though we already had 2 move events, there was no choreographer callback yet. + // Therefore, we should still get the id of the down event + assertEquals(down.id, eventId) + + // Now send CALLBACK_INPUT to the assigner. It should provide the latest motion event + assigner.onChoreographerCallback() + eventId = assigner.processEvent(move3) + assertEquals(move3.id, eventId) + eventId = assigner.processEvent(move4) + assertEquals(move4.id, eventId) + } + + /** + * Similar to the above test, but with SOURCE_MOUSE. Since we don't have down latency + * concept for non-touchscreens, the latest input event will be used. + */ + @Test + fun testMouseDownWithMove() { + val assigner = InputEventAssigner() + val down = createMotionEvent(MotionEvent.ACTION_DOWN, 10, SOURCE_MOUSE) + val move1 = createMotionEvent(MotionEvent.ACTION_MOVE, 12, SOURCE_MOUSE) + var eventId = assigner.processEvent(down) + assertEquals(down.id, eventId) + eventId = assigner.processEvent(move1) + assertEquals(move1.id, eventId) + } + + /** + * KeyEvents are processed immediately, so the latest event should be returned. + */ + @Test + fun testKeyEvent() { + val assigner = InputEventAssigner() + val down = createKeyEvent(KeyEvent.ACTION_DOWN, 20) + var eventId = assigner.processEvent(down) + assertEquals(down.id, eventId) + val up = createKeyEvent(KeyEvent.ACTION_UP, 21) + eventId = assigner.processEvent(up) + // DOWN is only sticky for Motions, not for keys + assertEquals(up.id, eventId) + assigner.onChoreographerCallback() + val down2 = createKeyEvent(KeyEvent.ACTION_DOWN, 22) + eventId = assigner.processEvent(down2) + assertEquals(down2.id, eventId) + } +} diff --git a/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt b/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt index c19e5cc34611..c01d32bf4cd2 100644 --- a/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt +++ b/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt @@ -17,6 +17,7 @@ package com.android.test.input import android.graphics.FrameInfo +import android.os.IInputConstants.INVALID_INPUT_EVENT_ID import android.os.SystemClock import android.view.ViewFrameInfo import com.google.common.truth.Truth.assertThat @@ -33,8 +34,7 @@ class ViewFrameInfoTest { @Before fun setUp() { mViewFrameInfo.reset() - mViewFrameInfo.updateOldestInputEvent(10) - mViewFrameInfo.updateNewestInputEvent(20) + mViewFrameInfo.setInputEvent(139) mViewFrameInfo.flags = mViewFrameInfo.flags or FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED mTimeStarted = SystemClock.uptimeNanos() mViewFrameInfo.markDrawStart() @@ -43,8 +43,6 @@ class ViewFrameInfoTest { @Test fun testPopulateFields() { assertThat(mViewFrameInfo.drawStart).isGreaterThan(mTimeStarted) - assertThat(mViewFrameInfo.oldestInputEventTime).isEqualTo(10) - assertThat(mViewFrameInfo.newestInputEventTime).isEqualTo(20) assertThat(mViewFrameInfo.flags).isEqualTo(FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED) } @@ -53,8 +51,6 @@ class ViewFrameInfoTest { mViewFrameInfo.reset() // Ensure that the original object is reset correctly assertThat(mViewFrameInfo.drawStart).isEqualTo(0) - assertThat(mViewFrameInfo.oldestInputEventTime).isEqualTo(0) - assertThat(mViewFrameInfo.newestInputEventTime).isEqualTo(0) assertThat(mViewFrameInfo.flags).isEqualTo(0) } @@ -62,12 +58,13 @@ class ViewFrameInfoTest { fun testUpdateFrameInfoFromViewFrameInfo() { val frameInfo = FrameInfo() // By default, all values should be zero - // TODO(b/169866723): Use InputEventAssigner and assert INPUT_EVENT_ID + assertThat(frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID]).isEqualTo(INVALID_INPUT_EVENT_ID) assertThat(frameInfo.frameInfo[FrameInfo.FLAGS]).isEqualTo(0) assertThat(frameInfo.frameInfo[FrameInfo.DRAW_START]).isEqualTo(0) // The values inside FrameInfo should match those from ViewFrameInfo after we update them mViewFrameInfo.populateFrameInfo(frameInfo) + assertThat(frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID]).isEqualTo(139) assertThat(frameInfo.frameInfo[FrameInfo.FLAGS]).isEqualTo( FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED) assertThat(frameInfo.frameInfo[FrameInfo.DRAW_START]).isGreaterThan(mTimeStarted) diff --git a/tests/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp index ee24d48f0ed5..d4f1ad317d31 100644 --- a/tests/UpdatableSystemFontTest/Android.bp +++ b/tests/UpdatableSystemFontTest/Android.bp @@ -26,13 +26,9 @@ java_test_host { srcs: ["src/**/*.java"], libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"], static_libs: [ - "block_device_writer_jar", "frameworks-base-hostutils", ], test_suites: ["general-tests", "vts"], - target_required: [ - "block_device_writer_module", - ], data: [ ":NotoColorEmojiTtf", ":UpdatableSystemFontTestCertDer", diff --git a/tests/UpdatableSystemFontTest/AndroidTest.xml b/tests/UpdatableSystemFontTest/AndroidTest.xml index 7b919bd4b114..efe5d703880c 100644 --- a/tests/UpdatableSystemFontTest/AndroidTest.xml +++ b/tests/UpdatableSystemFontTest/AndroidTest.xml @@ -21,7 +21,6 @@ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> <option name="cleanup" value="true" /> - <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" /> <option name="push" value="UpdatableSystemFontTestCert.der->/data/local/tmp/UpdatableSystemFontTestCert.der" /> <option name="push" value="NotoColorEmoji.ttf->/data/local/tmp/NotoColorEmoji.ttf" /> <option name="push" value="UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig" /> diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java index e249f8a99b0c..92fa498f8326 100644 --- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java +++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java @@ -21,7 +21,6 @@ import static com.google.common.truth.Truth.assertWithMessage; import android.platform.test.annotations.RootPermissionTest; -import com.android.blockdevicewriter.BlockDeviceWriter; import com.android.fsverity.AddFsVerityCertRule; import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.log.LogUtil.CLog; @@ -144,30 +143,6 @@ public class UpdatableSystemFontTest extends BaseHostJUnit4Test { assertThat(fontPathAfterReboot).isEqualTo(fontPath); } - @Test - public void reboot_clearDamagedFiles() throws Exception { - expectRemoteCommandToSucceed(String.format("cmd font update %s %s", - TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG)); - String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF); - assertThat(fontPath).startsWith("/data/fonts/files/"); - assertThat(BlockDeviceWriter.canReadByte(getDevice(), fontPath, 0)).isTrue(); - - BlockDeviceWriter.damageFileAgainstBlockDevice(getDevice(), fontPath, 0); - expectRemoteCommandToSucceed("stop"); - // We have to make sure system_server is gone before dropping caches, because system_server - // process holds font memory maps and prevents cache eviction. - waitUntilSystemServerIsGone(); - BlockDeviceWriter.assertFileNotOpen(getDevice(), fontPath); - BlockDeviceWriter.dropCaches(getDevice()); - assertThat(BlockDeviceWriter.canReadByte(getDevice(), fontPath, 0)).isFalse(); - - expectRemoteCommandToSucceed("start"); - waitUntilFontCommandIsReady(); - String fontPathAfterReboot = getFontPath(NOTO_COLOR_EMOJI_TTF); - assertWithMessage("Damaged file should be deleted") - .that(fontPathAfterReboot).startsWith("/system"); - } - private String getFontPath(String fontFileName) throws Exception { // TODO: add a dedicated command for testing. String lines = expectRemoteCommandToSucceed("cmd font dump"); diff --git a/tests/net/common/java/android/net/CaptivePortalTest.java b/tests/net/common/java/android/net/CaptivePortalTest.java index 7a60cc105a26..4cdf6a2a4b36 100644 --- a/tests/net/common/java/android/net/CaptivePortalTest.java +++ b/tests/net/common/java/android/net/CaptivePortalTest.java @@ -24,7 +24,6 @@ import android.os.RemoteException; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; @@ -54,12 +53,6 @@ public class CaptivePortalTest { public void appRequest(final int request) throws RemoteException { mCode = request; } - - @Override - public void logEvent(int eventId, String packageName) throws RemoteException { - mCode = eventId; - mPackageName = packageName; - } } private interface TestFunctor { @@ -98,12 +91,14 @@ public class CaptivePortalTest { assertEquals(result.mCode, CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED); } + /** + * Test testLogEvent is expected to do nothing but shouldn't crash, because the API logEvent + * has been deprecated. + */ @Test public void testLogEvent() { final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.logEvent( - MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY, + 0, TEST_PACKAGE_NAME)); - assertEquals(result.mCode, MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY); - assertEquals(result.mPackageName, TEST_PACKAGE_NAME); } } diff --git a/tests/net/common/java/android/net/NetworkStateSnapshotTest.kt b/tests/net/common/java/android/net/NetworkStateSnapshotTest.kt index 56b56efd501b..0ca4d9551f39 100644 --- a/tests/net/common/java/android/net/NetworkStateSnapshotTest.kt +++ b/tests/net/common/java/android/net/NetworkStateSnapshotTest.kt @@ -63,10 +63,10 @@ class NetworkStateSnapshotTest { @Test fun testParcelUnparcel() { - val emptySnapshot = NetworkStateSnapshot(LinkProperties(), NetworkCapabilities(), - Network(TEST_NETID), null, TYPE_NONE) + val emptySnapshot = NetworkStateSnapshot(Network(TEST_NETID), NetworkCapabilities(), + LinkProperties(), null, TYPE_NONE) val snapshot = NetworkStateSnapshot( - TEST_LINK_PROPERTIES, TEST_CAPABILITIES, Network(TEST_NETID), TEST_IMSI, TYPE_WIFI) + Network(TEST_NETID), TEST_CAPABILITIES, TEST_LINK_PROPERTIES, TEST_IMSI, TYPE_WIFI) assertParcelSane(emptySnapshot, 5) assertParcelSane(snapshot, 5) } diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt index c10c573aa024..2a2dc5628ecd 100644 --- a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt +++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt @@ -16,6 +16,7 @@ package com.android.server.net.integrationtests +import android.app.usage.NetworkStatsManager import android.content.ComponentName import android.content.Context import android.content.Context.BIND_AUTO_CREATE @@ -25,7 +26,6 @@ import android.content.ServiceConnection import android.net.ConnectivityManager import android.net.IDnsResolver import android.net.INetd -import android.net.INetworkStatsService import android.net.LinkProperties import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET @@ -37,7 +37,6 @@ import android.net.Uri import android.net.metrics.IpConnectivityLog import android.os.ConditionVariable import android.os.IBinder -import android.os.INetworkManagementService import android.os.SystemConfigManager import android.os.UserHandle import android.testing.TestableContext @@ -87,9 +86,7 @@ class ConnectivityServiceIntegrationTest { // lateinit used here for mocks as they need to be reinitialized between each test and the test // should crash if they are used before being initialized. @Mock - private lateinit var netManager: INetworkManagementService - @Mock - private lateinit var statsService: INetworkStatsService + private lateinit var statsManager: NetworkStatsManager @Mock private lateinit var log: IpConnectivityLog @Mock @@ -172,12 +169,13 @@ class ConnectivityServiceIntegrationTest { service = TestConnectivityService(makeDependencies()) cm = ConnectivityManager(context, service) context.addMockSystemService(Context.CONNECTIVITY_SERVICE, cm) + context.addMockSystemService(Context.NETWORK_STATS_SERVICE, statsManager) service.systemReadyInternal() } private inner class TestConnectivityService(deps: Dependencies) : ConnectivityService( - context, statsService, dnsResolver, log, netd, deps) + context, dnsResolver, log, netd, deps) private fun makeDependencies(): ConnectivityService.Dependencies { val deps = spy(ConnectivityService.Dependencies()) diff --git a/tests/net/java/android/net/IpSecAlgorithmTest.java b/tests/net/java/android/net/IpSecAlgorithmTest.java index 2e1c29a2e405..3a8d6004f66f 100644 --- a/tests/net/java/android/net/IpSecAlgorithmTest.java +++ b/tests/net/java/android/net/IpSecAlgorithmTest.java @@ -129,6 +129,7 @@ public class IpSecAlgorithmTest { checkCryptKeyLenValidation(IpSecAlgorithm.CRYPT_AES_CTR, len); } checkAuthKeyAndTruncLenValidation(IpSecAlgorithm.AUTH_AES_XCBC, 128, 96); + checkAuthKeyAndTruncLenValidation(IpSecAlgorithm.AUTH_AES_CMAC, 128, 96); checkAuthKeyAndTruncLenValidation(IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305, 288, 128); } diff --git a/tests/net/java/android/net/NetworkTemplateTest.kt b/tests/net/java/android/net/NetworkTemplateTest.kt index 27224c216db3..64b774cc4340 100644 --- a/tests/net/java/android/net/NetworkTemplateTest.kt +++ b/tests/net/java/android/net/NetworkTemplateTest.kt @@ -20,14 +20,13 @@ import android.content.Context import android.net.ConnectivityManager.TYPE_MOBILE import android.net.ConnectivityManager.TYPE_WIFI import android.net.NetworkIdentity.SUBTYPE_COMBINED -import android.net.NetworkIdentity.OEM_NONE; -import android.net.NetworkIdentity.OEM_PAID; -import android.net.NetworkIdentity.OEM_PRIVATE; +import android.net.NetworkIdentity.OEM_NONE +import android.net.NetworkIdentity.OEM_PAID +import android.net.NetworkIdentity.OEM_PRIVATE import android.net.NetworkIdentity.buildNetworkIdentity import android.net.NetworkStats.DEFAULT_NETWORK_ALL import android.net.NetworkStats.METERED_ALL import android.net.NetworkStats.ROAMING_ALL -import android.net.NetworkTemplate.MATCH_ETHERNET import android.net.NetworkTemplate.MATCH_MOBILE import android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD import android.net.NetworkTemplate.MATCH_WIFI @@ -50,7 +49,6 @@ import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotEquals import kotlin.test.assertTrue -import kotlin.test.fail private const val TEST_IMSI1 = "imsi1" private const val TEST_IMSI2 = "imsi2" @@ -60,17 +58,17 @@ private const val TEST_SSID1 = "ssid1" class NetworkTemplateTest { private val mockContext = mock(Context::class.java) - private fun buildMobileNetworkState(subscriberId: String): NetworkState = + private fun buildMobileNetworkState(subscriberId: String): NetworkStateSnapshot = buildNetworkState(TYPE_MOBILE, subscriberId = subscriberId) - private fun buildWifiNetworkState(ssid: String): NetworkState = + private fun buildWifiNetworkState(ssid: String): NetworkStateSnapshot = buildNetworkState(TYPE_WIFI, ssid = ssid) private fun buildNetworkState( type: Int, subscriberId: String? = null, ssid: String? = null, - oemManaged: Int = OEM_NONE, - ): NetworkState { + oemManaged: Int = OEM_NONE + ): NetworkStateSnapshot { val lp = LinkProperties() val caps = NetworkCapabilities().apply { setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false) @@ -81,7 +79,7 @@ class NetworkTemplateTest { setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE, (oemManaged and OEM_PRIVATE) == OEM_PRIVATE) } - return NetworkState(type, lp, caps, mock(Network::class.java), subscriberId) + return NetworkStateSnapshot(mock(Network::class.java), caps, lp, subscriberId, type) } private fun NetworkTemplate.assertMatches(ident: NetworkIdentity) = @@ -179,7 +177,7 @@ class NetworkTemplateTest { OEM_PAID, OEM_PRIVATE, OEM_PAID or OEM_PRIVATE) // Verify that "not OEM managed network" constants are equal. - assertEquals(OEM_MANAGED_NO, OEM_NONE); + assertEquals(OEM_MANAGED_NO, OEM_NONE) // Verify the constants don't conflict. assertEquals(constantValues.size, constantValues.distinct().count()) @@ -201,8 +199,13 @@ class NetworkTemplateTest { * @param identSsid If networkType is {@code TYPE_WIFI}, this value must *NOT* be null. Provide * one of {@code TEST_SSID*}. */ - private fun matchOemManagedIdent(networkType: Int, matchType:Int, subscriberId: String? = null, - templateSsid: String? = null, identSsid: String? = null) { + private fun matchOemManagedIdent( + networkType: Int, + matchType: Int, + subscriberId: String? = null, + templateSsid: String? = null, + identSsid: String? = null + ) { val oemManagedStates = arrayOf(OEM_NONE, OEM_PAID, OEM_PRIVATE, OEM_PAID or OEM_PRIVATE) // A null subscriberId needs a null matchSubscriberIds argument as well. val matchSubscriberIds = if (subscriberId == null) null else arrayOf(subscriberId) diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 10ec981e42a2..3bc15a386183 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -91,6 +91,10 @@ import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED; import static android.net.RouteInfo.RTN_UNREACHABLE; +import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_ADDED; +import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_REMOVED; +import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE; +import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS; import static android.os.Process.INVALID_UID; import static android.system.OsConstants.IPPROTO_TCP; @@ -145,6 +149,7 @@ import android.app.AlarmManager; import android.app.AppOpsManager; import android.app.NotificationManager; import android.app.PendingIntent; +import android.app.usage.NetworkStatsManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentProvider; @@ -176,7 +181,6 @@ import android.net.INetd; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; -import android.net.INetworkStatsService; import android.net.IOnSetOemNetworkPreferenceListener; import android.net.IQosCallback; import android.net.InetAddresses; @@ -199,7 +203,6 @@ import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.NetworkStack; import android.net.NetworkStackClient; -import android.net.NetworkState; import android.net.NetworkTestResultParcelable; import android.net.OemNetworkPreferences; import android.net.ProxyInfo; @@ -218,6 +221,8 @@ import android.net.Uri; import android.net.VpnManager; import android.net.VpnTransportInfo; import android.net.metrics.IpConnectivityLog; +import android.net.resolv.aidl.Nat64PrefixEventParcel; +import android.net.resolv.aidl.PrivateDnsValidationEventParcel; import android.net.shared.NetworkMonitorUtils; import android.net.shared.PrivateDnsConfig; import android.net.util.MultinetworkPolicyTracker; @@ -244,7 +249,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.security.Credentials; -import android.security.KeyStore; import android.system.Os; import android.telephony.TelephonyManager; import android.telephony.data.EpsBearerQosSessionAttributes; @@ -276,6 +280,7 @@ import com.android.server.connectivity.NetworkNotificationManager.NotificationTy import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.QosCallbackTracker; import com.android.server.connectivity.Vpn; +import com.android.server.connectivity.VpnProfileStore; import com.android.server.net.NetworkPinner; import com.android.server.net.NetworkPolicyManagerInternal; import com.android.testutils.ExceptionUtils; @@ -345,6 +350,9 @@ public class ConnectivityServiceTest { private static final String TAG = "ConnectivityServiceTest"; private static final int TIMEOUT_MS = 500; + // Broadcasts can take a long time to be delivered. The test will not wait for that long unless + // there is a failure, so use a long timeout. + private static final int BROADCAST_TIMEOUT_MS = 30_000; private static final int TEST_LINGER_DELAY_MS = 400; private static final int TEST_NASCENT_DELAY_MS = 300; // Chosen to be less than the linger and nascent timeout. This ensures that we can distinguish @@ -414,7 +422,7 @@ public class ConnectivityServiceTest { @Mock DeviceIdleInternal mDeviceIdleInternal; @Mock INetworkManagementService mNetworkManagementService; - @Mock INetworkStatsService mStatsService; + @Mock NetworkStatsManager mStatsManager; @Mock IBatteryStats mBatteryStatsService; @Mock IDnsResolver mMockDnsResolver; @Mock INetd mMockNetd; @@ -431,7 +439,7 @@ public class ConnectivityServiceTest { @Mock MockableSystemProperties mSystemProperties; @Mock EthernetManager mEthernetManager; @Mock NetworkPolicyManager mNetworkPolicyManager; - @Mock KeyStore mKeyStore; + @Mock VpnProfileStore mVpnProfileStore; @Mock SystemConfigManager mSystemConfigManager; private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor = @@ -530,6 +538,7 @@ public class ConnectivityServiceTest { if (Context.ETHERNET_SERVICE.equals(name)) return mEthernetManager; if (Context.NETWORK_POLICY_SERVICE.equals(name)) return mNetworkPolicyManager; if (Context.SYSTEM_CONFIG_SERVICE.equals(name)) return mSystemConfigManager; + if (Context.NETWORK_STATS_SERVICE.equals(name)) return mStatsManager; return super.getSystemService(name); } @@ -1115,7 +1124,7 @@ public class ConnectivityServiceTest { return mDeviceIdleInternal; } }, - mNetworkManagementService, mMockNetd, userId, mKeyStore); + mNetworkManagementService, mMockNetd, userId, mVpnProfileStore); } public void setUids(Set<UidRange> uids) { @@ -1294,8 +1303,9 @@ public class ConnectivityServiceTest { return mVMSHandlerThread; } - public KeyStore getKeyStore() { - return mKeyStore; + @Override + public VpnProfileStore getVpnProfileStore() { + return mVpnProfileStore; } public INetd getNetd() { @@ -1462,7 +1472,6 @@ public class ConnectivityServiceTest { mDeps = makeDependencies(); returnRealCallingUid(); mService = new ConnectivityService(mServiceContext, - mStatsService, mMockDnsResolver, mock(IpConnectivityLog.class), mMockNetd, @@ -1679,7 +1688,7 @@ public class ConnectivityServiceTest { } public Intent expectBroadcast() throws Exception { - return expectBroadcast(TIMEOUT_MS); + return expectBroadcast(BROADCAST_TIMEOUT_MS); } public void expectNoBroadcast(int timeoutMs) throws Exception { @@ -5477,18 +5486,19 @@ public class ConnectivityServiceTest { assertEquals(expectedSet, actualSet); } - private void expectForceUpdateIfaces(Network[] networks, String defaultIface, + private void expectNetworkStatus(Network[] networks, String defaultIface, Integer vpnUid, String vpnIfname, String[] underlyingIfaces) throws Exception { - ArgumentCaptor<Network[]> networksCaptor = ArgumentCaptor.forClass(Network[].class); - ArgumentCaptor<UnderlyingNetworkInfo[]> vpnInfosCaptor = ArgumentCaptor.forClass( - UnderlyingNetworkInfo[].class); + ArgumentCaptor<List<Network>> networksCaptor = ArgumentCaptor.forClass(List.class); + ArgumentCaptor<List<UnderlyingNetworkInfo>> vpnInfosCaptor = + ArgumentCaptor.forClass(List.class); - verify(mStatsService, atLeastOnce()).forceUpdateIfaces(networksCaptor.capture(), - any(NetworkState[].class), eq(defaultIface), vpnInfosCaptor.capture()); + verify(mStatsManager, atLeastOnce()).notifyNetworkStatus(networksCaptor.capture(), + any(List.class), eq(defaultIface), vpnInfosCaptor.capture()); - assertSameElementsNoDuplicates(networksCaptor.getValue(), networks); + assertSameElementsNoDuplicates(networksCaptor.getValue().toArray(), networks); - UnderlyingNetworkInfo[] infos = vpnInfosCaptor.getValue(); + UnderlyingNetworkInfo[] infos = + vpnInfosCaptor.getValue().toArray(new UnderlyingNetworkInfo[0]); if (vpnUid != null) { assertEquals("Should have exactly one VPN:", 1, infos.length); UnderlyingNetworkInfo info = infos[0]; @@ -5502,8 +5512,9 @@ public class ConnectivityServiceTest { } } - private void expectForceUpdateIfaces(Network[] networks, String defaultIface) throws Exception { - expectForceUpdateIfaces(networks, defaultIface, null, null, new String[0]); + private void expectNetworkStatus( + Network[] networks, String defaultIface) throws Exception { + expectNetworkStatus(networks, defaultIface, null, null, new String[0]); } @Test @@ -5523,47 +5534,46 @@ public class ConnectivityServiceTest { mCellNetworkAgent.connect(false); mCellNetworkAgent.sendLinkProperties(cellLp); waitForIdle(); - expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME); - reset(mStatsService); + expectNetworkStatus(onlyCell, MOBILE_IFNAME); + reset(mStatsManager); // Default network switch should update ifaces. mWiFiNetworkAgent.connect(false); mWiFiNetworkAgent.sendLinkProperties(wifiLp); waitForIdle(); assertEquals(wifiLp, mService.getActiveLinkProperties()); - expectForceUpdateIfaces(onlyWifi, WIFI_IFNAME); - reset(mStatsService); + expectNetworkStatus(onlyWifi, WIFI_IFNAME); + reset(mStatsManager); // Disconnect should update ifaces. mWiFiNetworkAgent.disconnect(); waitForIdle(); - expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME); - reset(mStatsService); + expectNetworkStatus(onlyCell, MOBILE_IFNAME); + reset(mStatsManager); // Metered change should update ifaces mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); waitForIdle(); - expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME); - reset(mStatsService); + expectNetworkStatus(onlyCell, MOBILE_IFNAME); + reset(mStatsManager); mCellNetworkAgent.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); waitForIdle(); - expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME); - reset(mStatsService); + expectNetworkStatus(onlyCell, MOBILE_IFNAME); + reset(mStatsManager); // Temp metered change shouldn't update ifaces mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED); waitForIdle(); - verify(mStatsService, never()) - .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME), - eq(new UnderlyingNetworkInfo[0])); - reset(mStatsService); + verify(mStatsManager, never()).notifyNetworkStatus(eq(Arrays.asList(onlyCell)), + any(List.class), eq(MOBILE_IFNAME), any(List.class)); + reset(mStatsManager); // Roaming change should update ifaces mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); waitForIdle(); - expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME); - reset(mStatsService); + expectNetworkStatus(onlyCell, MOBILE_IFNAME); + reset(mStatsManager); // Test VPNs. final LinkProperties lp = new LinkProperties(); @@ -5576,7 +5586,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent.getNetwork(), mMockVpn.getNetwork()}; // A VPN with default (null) underlying networks sets the underlying network's interfaces... - expectForceUpdateIfaces(cellAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + expectNetworkStatus(cellAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, new String[]{MOBILE_IFNAME}); // ...and updates them as the default network switches. @@ -5593,9 +5603,9 @@ public class ConnectivityServiceTest { waitForIdle(); assertEquals(wifiLp, mService.getActiveLinkProperties()); - expectForceUpdateIfaces(wifiAndVpn, WIFI_IFNAME, Process.myUid(), VPN_IFNAME, + expectNetworkStatus(wifiAndVpn, WIFI_IFNAME, Process.myUid(), VPN_IFNAME, new String[]{WIFI_IFNAME}); - reset(mStatsService); + reset(mStatsManager); // A VPN that sets its underlying networks passes the underlying interfaces, and influences // the default interface sent to NetworkStatsService by virtue of applying to the system @@ -5605,22 +5615,22 @@ public class ConnectivityServiceTest { // applies to the system server UID should not have any bearing on network stats. mMockVpn.setUnderlyingNetworks(onlyCell); waitForIdle(); - expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + expectNetworkStatus(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, new String[]{MOBILE_IFNAME}); - reset(mStatsService); + reset(mStatsManager); mMockVpn.setUnderlyingNetworks(cellAndWifi); waitForIdle(); - expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + expectNetworkStatus(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, new String[]{MOBILE_IFNAME, WIFI_IFNAME}); - reset(mStatsService); + reset(mStatsManager); // Null underlying networks are ignored. mMockVpn.setUnderlyingNetworks(cellNullAndWifi); waitForIdle(); - expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + expectNetworkStatus(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, new String[]{MOBILE_IFNAME, WIFI_IFNAME}); - reset(mStatsService); + reset(mStatsManager); // If an underlying network disconnects, that interface should no longer be underlying. // This doesn't actually work because disconnectAndDestroyNetwork only notifies @@ -5632,17 +5642,17 @@ public class ConnectivityServiceTest { mCellNetworkAgent.disconnect(); waitForIdle(); assertNull(mService.getLinkProperties(mCellNetworkAgent.getNetwork())); - expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + expectNetworkStatus(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, new String[]{MOBILE_IFNAME, WIFI_IFNAME}); // Confirm that we never tell NetworkStatsService that cell is no longer the underlying // network for the VPN... - verify(mStatsService, never()).forceUpdateIfaces(any(Network[].class), - any(NetworkState[].class), any() /* anyString() doesn't match null */, - argThat(infos -> infos[0].underlyingIfaces.size() == 1 - && WIFI_IFNAME.equals(infos[0].underlyingIfaces.get(0)))); - verifyNoMoreInteractions(mStatsService); - reset(mStatsService); + verify(mStatsManager, never()).notifyNetworkStatus(any(List.class), + any(List.class), any() /* anyString() doesn't match null */, + argThat(infos -> infos.get(0).underlyingIfaces.size() == 1 + && WIFI_IFNAME.equals(infos.get(0).underlyingIfaces.get(0)))); + verifyNoMoreInteractions(mStatsManager); + reset(mStatsManager); // ... but if something else happens that causes notifyIfacesChangedForNetworkStats to be // called again, it does. For example, connect Ethernet, but with a low score, such that it @@ -5651,13 +5661,13 @@ public class ConnectivityServiceTest { mEthernetNetworkAgent.adjustScore(-40); mEthernetNetworkAgent.connect(false); waitForIdle(); - verify(mStatsService).forceUpdateIfaces(any(Network[].class), - any(NetworkState[].class), any() /* anyString() doesn't match null */, - argThat(vpnInfos -> vpnInfos[0].underlyingIfaces.size() == 1 - && WIFI_IFNAME.equals(vpnInfos[0].underlyingIfaces.get(0)))); + verify(mStatsManager).notifyNetworkStatus(any(List.class), + any(List.class), any() /* anyString() doesn't match null */, + argThat(vpnInfos -> vpnInfos.get(0).underlyingIfaces.size() == 1 + && WIFI_IFNAME.equals(vpnInfos.get(0).underlyingIfaces.get(0)))); mEthernetNetworkAgent.disconnect(); waitForIdle(); - reset(mStatsService); + reset(mStatsManager); // When a VPN declares no underlying networks (i.e., no connectivity), getAllVpnInfo // does not return the VPN, so CS does not pass it to NetworkStatsService. This causes @@ -5667,27 +5677,27 @@ public class ConnectivityServiceTest { // Also, for the same reason as above, the active interface passed in is null. mMockVpn.setUnderlyingNetworks(new Network[0]); waitForIdle(); - expectForceUpdateIfaces(wifiAndVpn, null); - reset(mStatsService); + expectNetworkStatus(wifiAndVpn, null); + reset(mStatsManager); // Specifying only a null underlying network is the same as no networks. mMockVpn.setUnderlyingNetworks(onlyNull); waitForIdle(); - expectForceUpdateIfaces(wifiAndVpn, null); - reset(mStatsService); + expectNetworkStatus(wifiAndVpn, null); + reset(mStatsManager); // Specifying networks that are all disconnected is the same as specifying no networks. mMockVpn.setUnderlyingNetworks(onlyCell); waitForIdle(); - expectForceUpdateIfaces(wifiAndVpn, null); - reset(mStatsService); + expectNetworkStatus(wifiAndVpn, null); + reset(mStatsManager); // Passing in null again means follow the default network again. mMockVpn.setUnderlyingNetworks(null); waitForIdle(); - expectForceUpdateIfaces(wifiAndVpn, WIFI_IFNAME, Process.myUid(), VPN_IFNAME, + expectNetworkStatus(wifiAndVpn, WIFI_IFNAME, Process.myUid(), VPN_IFNAME, new String[]{WIFI_IFNAME}); - reset(mStatsService); + reset(mStatsManager); } @Test @@ -5920,6 +5930,16 @@ public class ConnectivityServiceTest { assertEquals("strict.example.com", cbi.getLp().getPrivateDnsServerName()); } + private PrivateDnsValidationEventParcel makePrivateDnsValidationEvent( + final int netId, final String ipAddress, final String hostname, final int validation) { + final PrivateDnsValidationEventParcel event = new PrivateDnsValidationEventParcel(); + event.netId = netId; + event.ipAddress = ipAddress; + event.hostname = hostname; + event.validation = validation; + return event; + } + @Test public void testLinkPropertiesWithPrivateDnsValidationEvents() throws Exception { // The default on Android is opportunistic mode ("Automatic"). @@ -5950,8 +5970,9 @@ public class ConnectivityServiceTest { // Send a validation event for a server that is not part of the current // resolver config. The validation event should be ignored. - mService.mNetdEventCallback.onPrivateDnsValidationEvent( - mCellNetworkAgent.getNetwork().netId, "", "145.100.185.18", true); + mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent( + makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId, "", + "145.100.185.18", VALIDATION_RESULT_SUCCESS)); cellNetworkCallback.assertNoCallback(); // Add a dns server to the LinkProperties. @@ -5968,20 +5989,23 @@ public class ConnectivityServiceTest { // Send a validation event containing a hostname that is not part of // the current resolver config. The validation event should be ignored. - mService.mNetdEventCallback.onPrivateDnsValidationEvent( - mCellNetworkAgent.getNetwork().netId, "145.100.185.16", "hostname", true); + mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent( + makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId, + "145.100.185.16", "hostname", VALIDATION_RESULT_SUCCESS)); cellNetworkCallback.assertNoCallback(); // Send a validation event where validation failed. - mService.mNetdEventCallback.onPrivateDnsValidationEvent( - mCellNetworkAgent.getNetwork().netId, "145.100.185.16", "", false); + mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent( + makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId, + "145.100.185.16", "", VALIDATION_RESULT_FAILURE)); cellNetworkCallback.assertNoCallback(); // Send a validation event where validation succeeded for a server in // the current resolver config. A LinkProperties callback with updated // private dns fields should be sent. - mService.mNetdEventCallback.onPrivateDnsValidationEvent( - mCellNetworkAgent.getNetwork().netId, "145.100.185.16", "", true); + mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent( + makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId, + "145.100.185.16", "", VALIDATION_RESULT_SUCCESS)); cbi = cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); @@ -7487,8 +7511,7 @@ public class ConnectivityServiceTest { private void setupLegacyLockdownVpn() { final String profileName = "testVpnProfile"; final byte[] profileTag = profileName.getBytes(StandardCharsets.UTF_8); - when(mKeyStore.contains(Credentials.LOCKDOWN_VPN)).thenReturn(true); - when(mKeyStore.get(Credentials.LOCKDOWN_VPN)).thenReturn(profileTag); + when(mVpnProfileStore.get(Credentials.LOCKDOWN_VPN)).thenReturn(profileTag); final VpnProfile profile = new VpnProfile(profileName); profile.name = "My VPN"; @@ -7496,7 +7519,7 @@ public class ConnectivityServiceTest { profile.dnsServers = "8.8.8.8"; profile.type = VpnProfile.TYPE_IPSEC_XAUTH_PSK; final byte[] encodedProfile = profile.encode(); - when(mKeyStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile); + when(mVpnProfileStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile); } private void establishLegacyLockdownVpn(Network underlying) throws Exception { @@ -7825,6 +7848,16 @@ public class ConnectivityServiceTest { return stacked; } + private Nat64PrefixEventParcel makeNat64PrefixEvent(final int netId, final int prefixOperation, + final String prefixAddress, final int prefixLength) { + final Nat64PrefixEventParcel event = new Nat64PrefixEventParcel(); + event.netId = netId; + event.prefixOperation = prefixOperation; + event.prefixAddress = prefixAddress; + event.prefixLength = prefixLength; + return event; + } + @Test public void testStackedLinkProperties() throws Exception { final LinkAddress myIpv4 = new LinkAddress("1.2.3.4/24"); @@ -7909,8 +7942,8 @@ public class ConnectivityServiceTest { // When NAT64 prefix discovery succeeds, LinkProperties are updated and clatd is started. Nat464Xlat clat = getNat464Xlat(mCellNetworkAgent); assertNull(mCm.getLinkProperties(mCellNetworkAgent.getNetwork()).getNat64Prefix()); - mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */, - kNat64PrefixString, 96); + mService.mResolverUnsolEventCallback.onNat64PrefixEvent( + makeNat64PrefixEvent(cellNetId, PREFIX_OPERATION_ADDED, kNat64PrefixString, 96)); LinkProperties lpBeforeClat = networkCallback.expectCallback( CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent).getLp(); assertEquals(0, lpBeforeClat.getStackedLinks().size()); @@ -7950,8 +7983,8 @@ public class ConnectivityServiceTest { .thenReturn(getClatInterfaceConfigParcel(myIpv4)); // Change the NAT64 prefix without first removing it. // Expect clatd to be stopped and started with the new prefix. - mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */, - kOtherNat64PrefixString, 96); + mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent( + cellNetId, PREFIX_OPERATION_ADDED, kOtherNat64PrefixString, 96)); networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, (lp) -> lp.getStackedLinks().size() == 0); verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME); @@ -7999,8 +8032,8 @@ public class ConnectivityServiceTest { .thenReturn(getClatInterfaceConfigParcel(myIpv4)); // Stopping prefix discovery causes netd to tell us that the NAT64 prefix is gone. - mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, false /* added */, - kOtherNat64PrefixString, 96); + mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent( + cellNetId, PREFIX_OPERATION_REMOVED, kOtherNat64PrefixString, 96)); networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, (lp) -> lp.getNat64Prefix() == null); @@ -8012,8 +8045,8 @@ public class ConnectivityServiceTest { networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); assertRoutesRemoved(cellNetId, ipv4Subnet); // Directly-connected routes auto-added. verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId); - mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */, - kNat64PrefixString, 96); + mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent( + cellNetId, PREFIX_OPERATION_ADDED, kNat64PrefixString, 96)); networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString()); @@ -8025,8 +8058,8 @@ public class ConnectivityServiceTest { verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_PREFIX + MOBILE_IFNAME); // NAT64 prefix is removed. Expect that clat is stopped. - mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, false /* added */, - kNat64PrefixString, 96); + mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent( + cellNetId, PREFIX_OPERATION_REMOVED, kNat64PrefixString, 96)); networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, (lp) -> lp.getStackedLinks().size() == 0 && lp.getNat64Prefix() == null); assertRoutesRemoved(cellNetId, ipv4Subnet, stackedDefault); @@ -8114,8 +8147,8 @@ public class ConnectivityServiceTest { inOrder.verify(mMockDnsResolver).setPrefix64(netId, ""); inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId); - mService.mNetdEventCallback.onNat64PrefixEvent(netId, true /* added */, - pref64FromDnsStr, 96); + mService.mResolverUnsolEventCallback.onNat64PrefixEvent( + makeNat64PrefixEvent(netId, PREFIX_OPERATION_ADDED, pref64FromDnsStr, 96)); expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromDns); inOrder.verify(mMockNetd).clatdStart(iface, pref64FromDns.toString()); @@ -8148,8 +8181,8 @@ public class ConnectivityServiceTest { inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId); // Stopping prefix discovery results in a prefix removed notification. - mService.mNetdEventCallback.onNat64PrefixEvent(netId, false /* added */, - pref64FromDnsStr, 96); + mService.mResolverUnsolEventCallback.onNat64PrefixEvent( + makeNat64PrefixEvent(netId, PREFIX_OPERATION_REMOVED, pref64FromDnsStr, 96)); inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString()); inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString()); @@ -8187,8 +8220,8 @@ public class ConnectivityServiceTest { inOrder.verify(mMockNetd).clatdStop(iface); inOrder.verify(mMockDnsResolver).setPrefix64(netId, ""); inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId); - mService.mNetdEventCallback.onNat64PrefixEvent(netId, true /* added */, - pref64FromDnsStr, 96); + mService.mResolverUnsolEventCallback.onNat64PrefixEvent( + makeNat64PrefixEvent(netId, PREFIX_OPERATION_ADDED, pref64FromDnsStr, 96)); expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromDns); inOrder.verify(mMockNetd).clatdStart(iface, pref64FromDns.toString()); inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), any()); @@ -9844,12 +9877,11 @@ public class ConnectivityServiceTest { .build(); // Act on ConnectivityService.setOemNetworkPreference() - final TestOemListenerCallback mOnSetOemNetworkPreferenceTestListener = - new TestOemListenerCallback(); - mService.setOemNetworkPreference(pref, mOnSetOemNetworkPreferenceTestListener); + final TestOemListenerCallback oemPrefListener = new TestOemListenerCallback(); + mService.setOemNetworkPreference(pref, oemPrefListener); // Verify call returned successfully - mOnSetOemNetworkPreferenceTestListener.expectOnComplete(); + oemPrefListener.expectOnComplete(); } private static class TestOemListenerCallback implements IOnSetOemNetworkPreferenceListener { diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java index f5b85ca06f92..5760211d9a27 100644 --- a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java @@ -22,6 +22,8 @@ import static android.net.NetworkCapabilities.MAX_TRANSPORT; import static android.net.NetworkCapabilities.MIN_TRANSPORT; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE; +import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS; import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE; import static android.provider.Settings.Global.PRIVATE_DNS_MODE; import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; @@ -164,7 +166,8 @@ public class DnsManagerTest { mDnsManager.flushVmDnsCache(); mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID_ALTERNATE, - InetAddress.parseNumericAddress("4.4.4.4"), "", true)); + InetAddress.parseNumericAddress("4.4.4.4"), "", + VALIDATION_RESULT_SUCCESS)); LinkProperties fixedLp = new LinkProperties(lp); mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp); assertFalse(fixedLp.isPrivateDnsActive()); @@ -204,7 +207,8 @@ public class DnsManagerTest { // Validate one. mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, - InetAddress.parseNumericAddress("6.6.6.6"), "strictmode.com", true)); + InetAddress.parseNumericAddress("6.6.6.6"), "strictmode.com", + VALIDATION_RESULT_SUCCESS)); fixedLp = new LinkProperties(lp); mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp); assertEquals(Arrays.asList(InetAddress.parseNumericAddress("6.6.6.6")), @@ -212,7 +216,8 @@ public class DnsManagerTest { // Validate the 2nd one. mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, - InetAddress.parseNumericAddress("2001:db8:66:66::1"), "strictmode.com", true)); + InetAddress.parseNumericAddress("2001:db8:66:66::1"), "strictmode.com", + VALIDATION_RESULT_SUCCESS)); fixedLp = new LinkProperties(lp); mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp); assertEquals(Arrays.asList( @@ -232,7 +237,8 @@ public class DnsManagerTest { mDnsManager.flushVmDnsCache(); mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, - InetAddress.parseNumericAddress("3.3.3.3"), "", true)); + InetAddress.parseNumericAddress("3.3.3.3"), "", + VALIDATION_RESULT_SUCCESS)); mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); assertFalse(lp.isPrivateDnsActive()); assertNull(lp.getPrivateDnsServerName()); @@ -245,7 +251,8 @@ public class DnsManagerTest { mDnsManager.flushVmDnsCache(); mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID_UNTRACKED, - InetAddress.parseNumericAddress("3.3.3.3"), "", true)); + InetAddress.parseNumericAddress("3.3.3.3"), "", + VALIDATION_RESULT_SUCCESS)); mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); assertFalse(lp.isPrivateDnsActive()); assertNull(lp.getPrivateDnsServerName()); @@ -253,7 +260,8 @@ public class DnsManagerTest { // Validation event has untracked ipAddress mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, - InetAddress.parseNumericAddress("4.4.4.4"), "", true)); + InetAddress.parseNumericAddress("4.4.4.4"), "", + VALIDATION_RESULT_SUCCESS)); mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); assertFalse(lp.isPrivateDnsActive()); assertNull(lp.getPrivateDnsServerName()); @@ -261,8 +269,8 @@ public class DnsManagerTest { // Validation event has untracked hostname mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, - InetAddress.parseNumericAddress("3.3.3.3"), "hostname", - true)); + InetAddress.parseNumericAddress("3.3.3.3"), "hostname", + VALIDATION_RESULT_SUCCESS)); mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); assertFalse(lp.isPrivateDnsActive()); assertNull(lp.getPrivateDnsServerName()); @@ -270,7 +278,8 @@ public class DnsManagerTest { // Validation event failed mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, - InetAddress.parseNumericAddress("3.3.3.3"), "", false)); + InetAddress.parseNumericAddress("3.3.3.3"), "", + VALIDATION_RESULT_FAILURE)); mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); assertFalse(lp.isPrivateDnsActive()); assertNull(lp.getPrivateDnsServerName()); @@ -279,7 +288,7 @@ public class DnsManagerTest { mDnsManager.removeNetwork(new Network(TEST_NETID)); mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, - InetAddress.parseNumericAddress("3.3.3.3"), "", true)); + InetAddress.parseNumericAddress("3.3.3.3"), "", VALIDATION_RESULT_SUCCESS)); mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); assertFalse(lp.isPrivateDnsActive()); assertNull(lp.getPrivateDnsServerName()); @@ -293,7 +302,8 @@ public class DnsManagerTest { mDnsManager.flushVmDnsCache(); mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, - InetAddress.parseNumericAddress("3.3.3.3"), "", true)); + InetAddress.parseNumericAddress("3.3.3.3"), "", + VALIDATION_RESULT_SUCCESS)); mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); assertFalse(lp.isPrivateDnsActive()); assertNull(lp.getPrivateDnsServerName()); @@ -398,7 +408,8 @@ public class DnsManagerTest { mDnsManager.updatePrivateDns(network, mDnsManager.getPrivateDnsConfig()); mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp); mDnsManager.updatePrivateDnsValidation( - new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, dnsAddr, "", true)); + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, dnsAddr, "", + VALIDATION_RESULT_SUCCESS)); mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); privateDnsCfg = mDnsManager.getPrivateDnsConfig(network); assertTrue(privateDnsCfg.useTls); diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index 7489a0f889dc..b8f7fbca3983 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -91,7 +91,6 @@ import android.os.UserManager; import android.os.test.TestLooper; import android.provider.Settings; import android.security.Credentials; -import android.security.KeyStore; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Range; @@ -196,7 +195,7 @@ public class VpnTest { @Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator; @Mock private ConnectivityManager mConnectivityManager; @Mock private IpSecService mIpSecService; - @Mock private KeyStore mKeyStore; + @Mock private VpnProfileStore mVpnProfileStore; private final VpnProfile mVpnProfile; private IpSecManager mIpSecManager; @@ -333,17 +332,17 @@ public class VpnTest { assertFalse(vpn.getLockdown()); // Set always-on without lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList(), mKeyStore)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList())); assertTrue(vpn.getAlwaysOn()); assertFalse(vpn.getLockdown()); // Set always-on with lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList(), mKeyStore)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList())); assertTrue(vpn.getAlwaysOn()); assertTrue(vpn.getLockdown()); // Remove always-on configuration. - assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList(), mKeyStore)); + assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList())); assertFalse(vpn.getAlwaysOn()); assertFalse(vpn.getLockdown()); } @@ -354,17 +353,17 @@ public class VpnTest { final UidRange user = PRI_USER_RANGE; // Set always-on without lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null, mKeyStore)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null)); // Set always-on with lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null, mKeyStore)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null)); verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1), new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop) })); // Switch to another app. - assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null)); verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1), new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop) @@ -382,14 +381,14 @@ public class VpnTest { // Set always-on with lockdown and allow app PKGS[2] from lockdown. assertTrue(vpn.setAlwaysOnPackage( - PKGS[1], true, Collections.singletonList(PKGS[2]), mKeyStore)); + PKGS[1], true, Collections.singletonList(PKGS[2]))); verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1), new UidRangeParcel(user.start + PKG_UIDS[2] + 1, user.stop) })); // Change allowed app list to PKGS[3]. assertTrue(vpn.setAlwaysOnPackage( - PKGS[1], true, Collections.singletonList(PKGS[3]), mKeyStore)); + PKGS[1], true, Collections.singletonList(PKGS[3]))); verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start + PKG_UIDS[2] + 1, user.stop) })); @@ -400,7 +399,7 @@ public class VpnTest { // Change the VPN app. assertTrue(vpn.setAlwaysOnPackage( - PKGS[0], true, Collections.singletonList(PKGS[3]), mKeyStore)); + PKGS[0], true, Collections.singletonList(PKGS[3]))); verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1), new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1) @@ -411,7 +410,7 @@ public class VpnTest { })); // Remove the list of allowed packages. - assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null, mKeyStore)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null)); verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1), new UidRangeParcel(user.start + PKG_UIDS[3] + 1, user.stop) @@ -422,7 +421,7 @@ public class VpnTest { // Add the list of allowed packages. assertTrue(vpn.setAlwaysOnPackage( - PKGS[0], true, Collections.singletonList(PKGS[1]), mKeyStore)); + PKGS[0], true, Collections.singletonList(PKGS[1]))); verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.stop) })); @@ -433,12 +432,12 @@ public class VpnTest { // Try allowing a package with a comma, should be rejected. assertFalse(vpn.setAlwaysOnPackage( - PKGS[0], true, Collections.singletonList("a.b,c.d"), mKeyStore)); + PKGS[0], true, Collections.singletonList("a.b,c.d"))); // Pass a non-existent packages in the allowlist, they (and only they) should be ignored. // allowed package should change from PGKS[1] to PKGS[2]. assertTrue(vpn.setAlwaysOnPackage( - PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"), mKeyStore)); + PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"))); verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1), new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop) @@ -525,22 +524,22 @@ public class VpnTest { .thenReturn(Collections.singletonList(resInfo)); // null package name should return false - assertFalse(vpn.isAlwaysOnPackageSupported(null, mKeyStore)); + assertFalse(vpn.isAlwaysOnPackageSupported(null)); // Pre-N apps are not supported appInfo.targetSdkVersion = VERSION_CODES.M; - assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore)); + assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0])); // N+ apps are supported by default appInfo.targetSdkVersion = VERSION_CODES.N; - assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore)); + assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0])); // Apps that opt out explicitly are not supported appInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT; Bundle metaData = new Bundle(); metaData.putBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, false); svcInfo.metaData = metaData; - assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore)); + assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0])); } @Test @@ -556,7 +555,7 @@ public class VpnTest { order.verify(mNotificationManager, atLeastOnce()).cancel(anyString(), anyInt()); // Start showing a notification for disconnected once always-on. - vpn.setAlwaysOnPackage(PKGS[0], false, null, mKeyStore); + vpn.setAlwaysOnPackage(PKGS[0], false, null); order.verify(mNotificationManager).notify(anyString(), anyInt(), any()); // Stop showing the notification once connected. @@ -568,7 +567,7 @@ public class VpnTest { order.verify(mNotificationManager).notify(anyString(), anyInt(), any()); // Notification should be cleared after unsetting always-on package. - vpn.setAlwaysOnPackage(null, false, null, mKeyStore); + vpn.setAlwaysOnPackage(null, false, null); order.verify(mNotificationManager).cancel(anyString(), anyInt()); } @@ -608,15 +607,13 @@ public class VpnTest { } private void checkProvisionVpnProfile(Vpn vpn, boolean expectedResult, String... checkedOps) { - assertEquals(expectedResult, vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile, mKeyStore)); + assertEquals(expectedResult, vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile)); // The profile should always be stored, whether or not consent has been previously granted. - verify(mKeyStore) + verify(mVpnProfileStore) .put( eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)), - eq(mVpnProfile.encode()), - eq(Process.SYSTEM_UID), - eq(0)); + eq(mVpnProfile.encode())); for (final String checkedOpStr : checkedOps) { verify(mAppOps).noteOpNoThrow(checkedOpStr, Process.myUid(), TEST_VPN_PKG, @@ -671,7 +668,7 @@ public class VpnTest { bigProfile.name = new String(new byte[Vpn.MAX_VPN_PROFILE_SIZE_BYTES + 1]); try { - vpn.provisionVpnProfile(TEST_VPN_PKG, bigProfile, mKeyStore); + vpn.provisionVpnProfile(TEST_VPN_PKG, bigProfile); fail("Expected IAE due to profile size"); } catch (IllegalArgumentException expected) { } @@ -684,7 +681,7 @@ public class VpnTest { restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); try { - vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile, mKeyStore); + vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile); fail("Expected SecurityException due to restricted user"); } catch (SecurityException expected) { } @@ -694,10 +691,10 @@ public class VpnTest { public void testDeleteVpnProfile() throws Exception { final Vpn vpn = createVpnAndSetupUidChecks(); - vpn.deleteVpnProfile(TEST_VPN_PKG, mKeyStore); + vpn.deleteVpnProfile(TEST_VPN_PKG); - verify(mKeyStore) - .delete(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)), eq(Process.SYSTEM_UID)); + verify(mVpnProfileStore) + .remove(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); } @Test @@ -707,7 +704,7 @@ public class VpnTest { restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); try { - vpn.deleteVpnProfile(TEST_VPN_PKG, mKeyStore); + vpn.deleteVpnProfile(TEST_VPN_PKG); fail("Expected SecurityException due to restricted user"); } catch (SecurityException expected) { } @@ -717,24 +714,24 @@ public class VpnTest { public void testGetVpnProfilePrivileged() throws Exception { final Vpn vpn = createVpnAndSetupUidChecks(); - when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) .thenReturn(new VpnProfile("").encode()); - vpn.getVpnProfilePrivileged(TEST_VPN_PKG, mKeyStore); + vpn.getVpnProfilePrivileged(TEST_VPN_PKG); - verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); + verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); } @Test public void testStartVpnProfile() throws Exception { final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) .thenReturn(mVpnProfile.encode()); - vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + vpn.startVpnProfile(TEST_VPN_PKG); - verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); + verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); verify(mAppOps) .noteOpNoThrow( eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), @@ -748,10 +745,10 @@ public class VpnTest { public void testStartVpnProfileVpnServicePreconsented() throws Exception { final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_VPN); - when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) .thenReturn(mVpnProfile.encode()); - vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + vpn.startVpnProfile(TEST_VPN_PKG); // Verify that the the ACTIVATE_VPN appop was checked, but no error was thrown. verify(mAppOps).noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_VPN, Process.myUid(), @@ -763,7 +760,7 @@ public class VpnTest { final Vpn vpn = createVpnAndSetupUidChecks(); try { - vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + vpn.startVpnProfile(TEST_VPN_PKG); fail("Expected failure due to no user consent"); } catch (SecurityException expected) { } @@ -780,22 +777,22 @@ public class VpnTest { TEST_VPN_PKG, null /* attributionTag */, null /* message */); // Keystore should never have been accessed. - verify(mKeyStore, never()).get(any()); + verify(mVpnProfileStore, never()).get(any()); } @Test public void testStartVpnProfileMissingProfile() throws Exception { final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))).thenReturn(null); + when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))).thenReturn(null); try { - vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + vpn.startVpnProfile(TEST_VPN_PKG); fail("Expected failure due to missing profile"); } catch (IllegalArgumentException expected) { } - verify(mKeyStore).get(vpn.getProfileNameForPackage(TEST_VPN_PKG)); + verify(mVpnProfileStore).get(vpn.getProfileNameForPackage(TEST_VPN_PKG)); verify(mAppOps) .noteOpNoThrow( eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), @@ -812,7 +809,7 @@ public class VpnTest { restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); try { - vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + vpn.startVpnProfile(TEST_VPN_PKG); fail("Expected SecurityException due to restricted user"); } catch (SecurityException expected) { } @@ -938,9 +935,9 @@ public class VpnTest { } private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) { - assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null, mKeyStore)); + assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null)); - verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); + verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); verify(mAppOps).setMode( eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), eq(uid), eq(TEST_VPN_PKG), eq(AppOpsManager.MODE_ALLOWED)); @@ -963,11 +960,11 @@ public class VpnTest { final int uid = Process.myUid() + 1; when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt())) .thenReturn(uid); - when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) .thenReturn(mVpnProfile.encode()); setAndVerifyAlwaysOnPackage(vpn, uid, false); - assertTrue(vpn.startAlwaysOnVpn(mKeyStore)); + assertTrue(vpn.startAlwaysOnVpn()); // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in // a subsequent CL. @@ -984,7 +981,7 @@ public class VpnTest { InetAddresses.parseNumericAddress("192.0.2.0"), EGRESS_IFACE); lp.addRoute(defaultRoute); - vpn.startLegacyVpn(vpnProfile, mKeyStore, EGRESS_NETWORK, lp); + vpn.startLegacyVpn(vpnProfile, EGRESS_NETWORK, lp); return vpn; } @@ -1186,7 +1183,7 @@ public class VpnTest { .thenReturn(asUserContext); final TestLooper testLooper = new TestLooper(); final Vpn vpn = new Vpn(testLooper.getLooper(), mContext, new TestDeps(), mNetService, - mNetd, userId, mKeyStore, mSystemServices, mIkev2SessionCreator); + mNetd, userId, mVpnProfileStore, mSystemServices, mIkev2SessionCreator); verify(mConnectivityManager, times(1)).registerNetworkProvider(argThat( provider -> provider.getName().contains("VpnNetworkProvider") )); diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java index 54d6fb9f2c12..9334e2c4ad77 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java @@ -19,9 +19,7 @@ package com.android.server.net; import static android.content.Intent.ACTION_UID_REMOVED; import static android.content.Intent.EXTRA_UID; import static android.net.ConnectivityManager.TYPE_MOBILE; -import static android.net.ConnectivityManager.TYPE_VPN; import static android.net.ConnectivityManager.TYPE_WIFI; -import static android.net.NetworkIdentity.OEM_NONE; import static android.net.NetworkIdentity.OEM_PAID; import static android.net.NetworkIdentity.OEM_PRIVATE; import static android.net.NetworkStats.DEFAULT_NETWORK_ALL; @@ -86,7 +84,7 @@ import android.net.INetworkStatsSession; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; -import android.net.NetworkState; +import android.net.NetworkStateSnapshot; import android.net.NetworkStats; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; @@ -286,7 +284,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // pretend that wifi network comes online; service should ask about full // network state, and poll any existing interfaces before updating. expectDefaultSettings(); - NetworkState[] states = new NetworkState[] {buildWifiState()}; + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -329,7 +327,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // pretend that wifi network comes online; service should ask about full // network state, and poll any existing interfaces before updating. expectDefaultSettings(); - NetworkState[] states = new NetworkState[] {buildWifiState()}; + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -403,7 +401,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // pretend that wifi network comes online; service should ask about full // network state, and poll any existing interfaces before updating. expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS); - NetworkState[] states = new NetworkState[] {buildWifiState()}; + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -444,7 +442,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testUidStatsAcrossNetworks() throws Exception { // pretend first mobile network comes online expectDefaultSettings(); - NetworkState[] states = new NetworkState[] {buildMobile3gState(IMSI_1)}; + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildMobile3gState(IMSI_1)}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -475,7 +473,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // disappearing, to verify we don't count backwards. incrementCurrentTime(HOUR_IN_MILLIS); expectDefaultSettings(); - states = new NetworkState[] {buildMobile3gState(IMSI_2)}; + states = new NetworkStateSnapshot[] {buildMobile3gState(IMSI_2)}; expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) .insertEntry(TEST_IFACE, 2048L, 16L, 512L, 4L)); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3) @@ -519,7 +517,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testUidRemovedIsMoved() throws Exception { // pretend that network comes online expectDefaultSettings(); - NetworkState[] states = new NetworkState[] {buildWifiState()}; + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -583,7 +581,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_LTE); final NetworkTemplate template5g = buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_NR); - final NetworkState[] states = new NetworkState[]{buildMobile3gState(IMSI_1)}; + final NetworkStateSnapshot[] states = + new NetworkStateSnapshot[]{buildMobile3gState(IMSI_1)}; // 3G network comes online. expectNetworkStatsSummary(buildEmptyStats()); @@ -673,7 +672,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_NO); // OEM_PAID network comes online. - NetworkState[] states = new NetworkState[]{buildOemManagedMobileState(IMSI_1, false, + NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{ + buildOemManagedMobileState(IMSI_1, false, new int[]{NetworkCapabilities.NET_CAPABILITY_OEM_PAID})}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -688,7 +688,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { forcePollAndWaitForIdle(); // OEM_PRIVATE network comes online. - states = new NetworkState[]{buildOemManagedMobileState(IMSI_1, false, + states = new NetworkStateSnapshot[]{buildOemManagedMobileState(IMSI_1, false, new int[]{NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE})}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -703,7 +703,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { forcePollAndWaitForIdle(); // OEM_PAID + OEM_PRIVATE network comes online. - states = new NetworkState[]{buildOemManagedMobileState(IMSI_1, false, + states = new NetworkStateSnapshot[]{buildOemManagedMobileState(IMSI_1, false, new int[]{NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE, NetworkCapabilities.NET_CAPABILITY_OEM_PAID})}; expectNetworkStatsSummary(buildEmptyStats()); @@ -719,7 +719,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { forcePollAndWaitForIdle(); // OEM_NONE network comes online. - states = new NetworkState[]{buildOemManagedMobileState(IMSI_1, false, new int[]{})}; + states = new NetworkStateSnapshot[]{buildOemManagedMobileState(IMSI_1, false, new int[]{})}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), @@ -771,7 +771,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testSummaryForAllUid() throws Exception { // pretend that network comes online expectDefaultSettings(); - NetworkState[] states = new NetworkState[] {buildWifiState()}; + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -830,7 +830,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testDetailedUidStats() throws Exception { // pretend that network comes online expectDefaultSettings(); - NetworkState[] states = new NetworkState[] {buildWifiState()}; + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -871,9 +871,9 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { final String stackedIface = "stacked-test0"; final LinkProperties stackedProp = new LinkProperties(); stackedProp.setInterfaceName(stackedIface); - final NetworkState wifiState = buildWifiState(); + final NetworkStateSnapshot wifiState = buildWifiState(); wifiState.linkProperties.addStackedLink(stackedProp); - NetworkState[] states = new NetworkState[] {wifiState}; + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {wifiState}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -929,7 +929,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testForegroundBackground() throws Exception { // pretend that network comes online expectDefaultSettings(); - NetworkState[] states = new NetworkState[] {buildWifiState()}; + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -986,8 +986,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testMetered() throws Exception { // pretend that network comes online expectDefaultSettings(); - NetworkState[] states = - new NetworkState[] {buildWifiState(true /* isMetered */, TEST_IFACE)}; + NetworkStateSnapshot[] states = + new NetworkStateSnapshot[] {buildWifiState(true /* isMetered */, TEST_IFACE)}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -1026,8 +1026,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testRoaming() throws Exception { // pretend that network comes online expectDefaultSettings(); - NetworkState[] states = - new NetworkState[] {buildMobile3gState(IMSI_1, true /* isRoaming */)}; + NetworkStateSnapshot[] states = + new NetworkStateSnapshot[] {buildMobile3gState(IMSI_1, true /* isRoaming */)}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -1065,7 +1065,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testTethering() throws Exception { // pretend first mobile network comes online expectDefaultSettings(); - final NetworkState[] states = new NetworkState[]{buildMobile3gState(IMSI_1)}; + final NetworkStateSnapshot[] states = + new NetworkStateSnapshot[]{buildMobile3gState(IMSI_1)}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -1122,7 +1123,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // pretend that wifi network comes online; service should ask about full // network state, and poll any existing interfaces before updating. expectDefaultSettings(); - NetworkState[] states = new NetworkState[] {buildWifiState()}; + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -1220,8 +1221,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testStatsProviderUpdateStats() throws Exception { // Pretend that network comes online. expectDefaultSettings(); - final NetworkState[] states = - new NetworkState[]{buildWifiState(true /* isMetered */, TEST_IFACE)}; + final NetworkStateSnapshot[] states = + new NetworkStateSnapshot[]{buildWifiState(true /* isMetered */, TEST_IFACE)}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -1282,8 +1283,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testStatsProviderSetAlert() throws Exception { // Pretend that network comes online. expectDefaultSettings(); - NetworkState[] states = - new NetworkState[]{buildWifiState(true /* isMetered */, TEST_IFACE)}; + NetworkStateSnapshot[] states = + new NetworkStateSnapshot[]{buildWifiState(true /* isMetered */, TEST_IFACE)}; mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new UnderlyingNetworkInfo[0]); @@ -1326,7 +1327,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_UNKNOWN); final NetworkTemplate templateAll = buildTemplateMobileWithRatType(null, NETWORK_TYPE_ALL); - final NetworkState[] states = new NetworkState[]{buildMobile3gState(IMSI_1)}; + final NetworkStateSnapshot[] states = + new NetworkStateSnapshot[]{buildMobile3gState(IMSI_1)}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -1401,7 +1403,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testOperationCount_nonDefault_traffic() throws Exception { // Pretend mobile network comes online, but wifi is the default network. expectDefaultSettings(); - NetworkState[] states = new NetworkState[]{ + NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{ buildWifiState(true /*isMetered*/, TEST_IFACE2), buildMobile3gState(IMSI_1)}; expectNetworkStatsUidDetail(buildEmptyStats()); mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), @@ -1489,7 +1491,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectNetworkStatsSummary(buildEmptyStats()); } - private String getActiveIface(NetworkState... states) throws Exception { + private String getActiveIface(NetworkStateSnapshot... states) throws Exception { if (states == null || states.length == 0 || states[0].linkProperties == null) { return null; } @@ -1565,11 +1567,11 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { assertEquals("unexpected operations", operations, entry.operations); } - private static NetworkState buildWifiState() { + private static NetworkStateSnapshot buildWifiState() { return buildWifiState(false, TEST_IFACE); } - private static NetworkState buildWifiState(boolean isMetered, @NonNull String iface) { + private static NetworkStateSnapshot buildWifiState(boolean isMetered, @NonNull String iface) { final LinkProperties prop = new LinkProperties(); prop.setInterfaceName(iface); final NetworkCapabilities capabilities = new NetworkCapabilities(); @@ -1577,35 +1579,30 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true); capabilities.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); capabilities.setSSID(TEST_SSID); - return new NetworkState(TYPE_WIFI, prop, capabilities, WIFI_NETWORK, null); + return new NetworkStateSnapshot(WIFI_NETWORK, capabilities, prop, null, TYPE_WIFI); } - private static NetworkState buildMobile3gState(String subscriberId) { + private static NetworkStateSnapshot buildMobile3gState(String subscriberId) { return buildMobile3gState(subscriberId, false /* isRoaming */); } - private static NetworkState buildMobile3gState(String subscriberId, boolean isRoaming) { + private static NetworkStateSnapshot buildMobile3gState(String subscriberId, boolean isRoaming) { final LinkProperties prop = new LinkProperties(); prop.setInterfaceName(TEST_IFACE); final NetworkCapabilities capabilities = new NetworkCapabilities(); capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false); capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, !isRoaming); capabilities.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); - return new NetworkState(TYPE_MOBILE, prop, capabilities, MOBILE_NETWORK, subscriberId); + return new NetworkStateSnapshot( + MOBILE_NETWORK, capabilities, prop, subscriberId, TYPE_MOBILE); } private NetworkStats buildEmptyStats() { return new NetworkStats(getElapsedRealtime(), 0); } - private static NetworkState buildVpnState() { - final LinkProperties prop = new LinkProperties(); - prop.setInterfaceName(TUN_IFACE); - return new NetworkState(TYPE_VPN, prop, new NetworkCapabilities(), VPN_NETWORK, null); - } - - private static NetworkState buildOemManagedMobileState(String subscriberId, boolean isRoaming, - int[] oemNetCapabilities) { + private static NetworkStateSnapshot buildOemManagedMobileState( + String subscriberId, boolean isRoaming, int[] oemNetCapabilities) { final LinkProperties prop = new LinkProperties(); prop.setInterfaceName(TEST_IFACE); final NetworkCapabilities capabilities = new NetworkCapabilities(); @@ -1615,7 +1612,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { capabilities.setCapability(nc, true); } capabilities.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); - return new NetworkState(TYPE_MOBILE, prop, capabilities, MOBILE_NETWORK, subscriberId); + return new NetworkStateSnapshot(MOBILE_NETWORK, capabilities, prop, subscriberId, + TYPE_MOBILE); } private long getElapsedRealtime() { diff --git a/tests/vcn/assets/self-signed-ca.pem b/tests/vcn/assets/self-signed-ca.pem new file mode 100644 index 000000000000..5135ea7077a8 --- /dev/null +++ b/tests/vcn/assets/self-signed-ca.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDPjCCAiagAwIBAgIICrKLpR7LxlowDQYJKoZIhvcNAQELBQAwPTELMAkGA1UE +BhMCVVMxEDAOBgNVBAoTB0FuZHJvaWQxHDAaBgNVBAMTE2NhLnRlc3QuYW5kcm9p +ZC5uZXQwHhcNMTkwNzE2MTcxNTUyWhcNMjkwNzEzMTcxNTUyWjA9MQswCQYDVQQG +EwJVUzEQMA4GA1UEChMHQW5kcm9pZDEcMBoGA1UEAxMTY2EudGVzdC5hbmRyb2lk +Lm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANsvTwad2Nie0VOy +Xb1VtHL0R760Jm4vr14JWMcX4oiE6jUdTNdXQ0CGb65wvulP2aEeukFH0D/cvBMR +Bv9+haEwo9/grIXg9ALNKp+GfuZYw/dfnUMHFn3g2+SUgP6BoMZc4lkHktjkDKxp +99Q6h4NP/ip1labkhBeB9+Z6l78LTixKRKspNITWASJed9bjzshYxKHi6dJy3maQ +1LwYKmK7PEGRpoDoT8yZhFbxsVDUojGnJKH1RLXVOn/psG6dI/+IsbTipAttj5zc +g2VAD56PZG2Jd+vsup+g4Dy72hyy242x5c/H2LKZn4X0B0B+IXyii/ZVc+DJldQ5 +JqplOL8CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFGYUzuvZUaVJl8mcxejuFiUNGcTfMA0GCSqGSIb3DQEBCwUAA4IB +AQDQYeqjvHsK2ZqSqxakDp0nu36Plbj48Wvx1ru7GW2faz7i0w/Zkxh06zniILCb +QJRjDebSTHc5SSbCFrRTvqagaLDhbH42/hQncWqIoJqW+pmznJET4JiBO0sqzm05 +yQWsLI/h9Ir28Y2g5N+XPBU0VVVejQqH4iI0iwQx7y7ABssQ0Xa/K73VPbeGaKd6 +Prt4wjJvTlIL2yE2+0MggJ3F2rNptL5SDpg3g+4/YQ6wVRBFil95kUqplEsCtU4P +t+8RghiEmsRx/8CywKfZ5Hex87ODhsSDmDApcefbd5gxoWVkqxZUkPcKwYv1ucm8 +u4r44fj4/9W0Zeooav5Yoh1q +-----END CERTIFICATE-----
\ No newline at end of file diff --git a/tests/vcn/java/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java index 66590c92579b..7515971b8307 100644 --- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java +++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java @@ -203,9 +203,6 @@ public class VcnManagerTest { IVcnStatusCallback cbBinder = new VcnStatusCallbackBinder(INLINE_EXECUTOR, mMockStatusCallback); - cbBinder.onEnteredSafeMode(); - verify(mMockStatusCallback).onEnteredSafeMode(); - cbBinder.onVcnStatusChanged(VCN_STATUS_CODE_ACTIVE); verify(mMockStatusCallback).onVcnStatusChanged(VCN_STATUS_CODE_ACTIVE); diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtilsTest.java new file mode 100644 index 000000000000..bc8e9d3200b6 --- /dev/null +++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtilsTest.java @@ -0,0 +1,113 @@ +/* + * 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 android.net.vcn.persistablebundleutils; + +import static android.telephony.TelephonyManager.APPTYPE_USIM; + +import static org.junit.Assert.assertEquals; + +import android.net.eap.EapSessionConfig; +import android.os.PersistableBundle; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class EapSessionConfigUtilsTest { + private static final byte[] EAP_ID = "test@android.net".getBytes(StandardCharsets.US_ASCII); + private static final String USERNAME = "username"; + private static final String PASSWORD = "password"; + private static final int SUB_ID = 1; + private static final String NETWORK_NAME = "android.net"; + private static final boolean ALLOW_MISMATCHED_NETWORK_NAMES = true; + + private EapSessionConfig.Builder createBuilderWithId() { + return new EapSessionConfig.Builder().setEapIdentity(EAP_ID); + } + + private static void verifyPersistableBundleEncodeDecodeIsLossless(EapSessionConfig config) { + final PersistableBundle bundle = EapSessionConfigUtils.toPersistableBundle(config); + final EapSessionConfig resultConfig = EapSessionConfigUtils.fromPersistableBundle(bundle); + + assertEquals(config, resultConfig); + } + + @Test + public void testSetEapMsChapV2EncodeDecodeIsLossless() throws Exception { + final EapSessionConfig config = + createBuilderWithId().setEapMsChapV2Config(USERNAME, PASSWORD).build(); + + verifyPersistableBundleEncodeDecodeIsLossless(config); + } + + @Test + public void testSetEapSimEncodeDecodeIsLossless() throws Exception { + final EapSessionConfig config = + createBuilderWithId().setEapSimConfig(SUB_ID, APPTYPE_USIM).build(); + + verifyPersistableBundleEncodeDecodeIsLossless(config); + } + + @Test + public void testSetEapAkaEncodeDecodeIsLossless() throws Exception { + final EapSessionConfig config = + createBuilderWithId().setEapAkaConfig(SUB_ID, APPTYPE_USIM).build(); + + verifyPersistableBundleEncodeDecodeIsLossless(config); + } + + @Test + public void testSetEapAkaPrimeEncodeDecodeIsLossless() throws Exception { + final EapSessionConfig config = + createBuilderWithId() + .setEapAkaPrimeConfig( + SUB_ID, APPTYPE_USIM, NETWORK_NAME, ALLOW_MISMATCHED_NETWORK_NAMES) + .build(); + + verifyPersistableBundleEncodeDecodeIsLossless(config); + } + + @Test + public void testSetEapTtlsEncodeDecodeIsLossless() throws Exception { + final InputStream inputStream = + InstrumentationRegistry.getContext() + .getResources() + .getAssets() + .open("self-signed-ca.pem"); + final CertificateFactory factory = CertificateFactory.getInstance("X.509"); + final X509Certificate trustedCa = + (X509Certificate) factory.generateCertificate(inputStream); + + final EapSessionConfig innerConfig = + new EapSessionConfig.Builder().setEapMsChapV2Config(USERNAME, PASSWORD).build(); + + final EapSessionConfig config = + new EapSessionConfig.Builder().setEapTtlsConfig(trustedCa, innerConfig).build(); + + verifyPersistableBundleEncodeDecodeIsLossless(config); + } +} diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtilsTest.java new file mode 100644 index 000000000000..4f3930f9b5af --- /dev/null +++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtilsTest.java @@ -0,0 +1,87 @@ +/* + * 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 android.net.vcn.persistablebundleutils; + +import static org.junit.Assert.assertEquals; + +import android.net.ipsec.ike.IkeDerAsn1DnIdentification; +import android.net.ipsec.ike.IkeFqdnIdentification; +import android.net.ipsec.ike.IkeIdentification; +import android.net.ipsec.ike.IkeIpv4AddrIdentification; +import android.net.ipsec.ike.IkeIpv6AddrIdentification; +import android.net.ipsec.ike.IkeKeyIdIdentification; +import android.net.ipsec.ike.IkeRfc822AddrIdentification; +import android.os.PersistableBundle; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; + +import javax.security.auth.x500.X500Principal; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class IkeIdentificationUtilsTest { + private static void verifyPersistableBundleEncodeDecodeIsLossless(IkeIdentification id) { + final PersistableBundle bundle = IkeIdentificationUtils.toPersistableBundle(id); + final IkeIdentification result = IkeIdentificationUtils.fromPersistableBundle(bundle); + + assertEquals(result, id); + } + + @Test + public void testPersistableBundleEncodeDecodeIpv4AddressId() throws Exception { + final Inet4Address ipv4Address = (Inet4Address) InetAddress.getByName("192.0.2.100"); + verifyPersistableBundleEncodeDecodeIsLossless(new IkeIpv4AddrIdentification(ipv4Address)); + } + + @Test + public void testPersistableBundleEncodeDecodeIpv6AddressId() throws Exception { + final Inet6Address ipv6Address = (Inet6Address) InetAddress.getByName("2001:db8:2::100"); + verifyPersistableBundleEncodeDecodeIsLossless(new IkeIpv6AddrIdentification(ipv6Address)); + } + + @Test + public void testPersistableBundleEncodeDecodeRfc822AddrId() throws Exception { + verifyPersistableBundleEncodeDecodeIsLossless(new IkeFqdnIdentification("ike.android.net")); + } + + @Test + public void testPersistableBundleEncodeDecodeFqdnId() throws Exception { + verifyPersistableBundleEncodeDecodeIsLossless( + new IkeRfc822AddrIdentification("androidike@example.com")); + } + + @Test + public void testPersistableBundleEncodeDecodeKeyId() throws Exception { + verifyPersistableBundleEncodeDecodeIsLossless( + new IkeKeyIdIdentification("androidIkeKeyId".getBytes())); + } + + @Test + public void testPersistableBundleEncodeDecodeDerAsn1DnId() throws Exception { + verifyPersistableBundleEncodeDecodeIsLossless( + new IkeDerAsn1DnIdentification( + new X500Principal("CN=small.server.test.android.net, O=Android, C=US"))); + } +} diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.java new file mode 100644 index 000000000000..8ae8692b4f75 --- /dev/null +++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.java @@ -0,0 +1,78 @@ +/* + * 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 android.net.vcn.persistablebundleutils; + +import static org.junit.Assert.assertEquals; + +import android.net.ipsec.ike.ChildSaProposal; +import android.net.ipsec.ike.IkeSaProposal; +import android.net.ipsec.ike.SaProposal; +import android.os.PersistableBundle; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class SaProposalUtilsTest { + @Test + public void testPersistableBundleEncodeDecodeIsLosslessIkeProposal() throws Exception { + final IkeSaProposal proposal = + new IkeSaProposal.Builder() + .addEncryptionAlgorithm( + SaProposal.ENCRYPTION_ALGORITHM_3DES, SaProposal.KEY_LEN_UNUSED) + .addEncryptionAlgorithm( + SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128) + .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96) + .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128) + .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC) + .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_SHA2_256) + .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP) + .addDhGroup(SaProposal.DH_GROUP_3072_BIT_MODP) + .build(); + + final PersistableBundle bundle = IkeSaProposalUtils.toPersistableBundle(proposal); + final SaProposal resultProposal = IkeSaProposalUtils.fromPersistableBundle(bundle); + + assertEquals(proposal, resultProposal); + } + + /** Package private so that TunnelModeChildSessionParamsUtilsTest can use it */ + static ChildSaProposal buildTestChildSaProposal() { + return new ChildSaProposal.Builder() + .addEncryptionAlgorithm( + SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.KEY_LEN_AES_128) + .addEncryptionAlgorithm( + SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.KEY_LEN_AES_192) + .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP) + .addDhGroup(SaProposal.DH_GROUP_4096_BIT_MODP) + .build(); + } + + @Test + public void testPersistableBundleEncodeDecodeIsLosslessChildProposal() throws Exception { + final ChildSaProposal proposal = buildTestChildSaProposal(); + + final PersistableBundle bundle = ChildSaProposalUtils.toPersistableBundle(proposal); + final SaProposal resultProposal = ChildSaProposalUtils.fromPersistableBundle(bundle); + + assertEquals(proposal, resultProposal); + } +} diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtilsTest.java new file mode 100644 index 000000000000..b3cd0ab80599 --- /dev/null +++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtilsTest.java @@ -0,0 +1,117 @@ +/* + * 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 android.net.vcn.persistablebundleutils; + +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; + +import static org.junit.Assert.assertEquals; + +import android.net.InetAddresses; +import android.net.ipsec.ike.ChildSaProposal; +import android.net.ipsec.ike.IkeTrafficSelector; +import android.net.ipsec.ike.TunnelModeChildSessionParams; +import android.os.PersistableBundle; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class TunnelModeChildSessionParamsUtilsTest { + private TunnelModeChildSessionParams.Builder createBuilderMinimum() { + final ChildSaProposal saProposal = SaProposalUtilsTest.buildTestChildSaProposal(); + return new TunnelModeChildSessionParams.Builder().addSaProposal(saProposal); + } + + private static void verifyPersistableBundleEncodeDecodeIsLossless( + TunnelModeChildSessionParams params) { + final PersistableBundle bundle = + TunnelModeChildSessionParamsUtils.toPersistableBundle(params); + final TunnelModeChildSessionParams result = + TunnelModeChildSessionParamsUtils.fromPersistableBundle(bundle); + + assertEquals(params, result); + } + + @Test + public void testMinimumParamsEncodeDecodeIsLossless() throws Exception { + final TunnelModeChildSessionParams sessionParams = createBuilderMinimum().build(); + verifyPersistableBundleEncodeDecodeIsLossless(sessionParams); + } + + @Test + public void testSetTsEncodeDecodeIsLossless() throws Exception { + final IkeTrafficSelector tsInbound = + new IkeTrafficSelector( + 16, + 65520, + InetAddresses.parseNumericAddress("192.0.2.100"), + InetAddresses.parseNumericAddress("192.0.2.101")); + final IkeTrafficSelector tsOutbound = + new IkeTrafficSelector( + 32, + 256, + InetAddresses.parseNumericAddress("192.0.2.200"), + InetAddresses.parseNumericAddress("192.0.2.255")); + + final TunnelModeChildSessionParams sessionParams = + createBuilderMinimum() + .addInboundTrafficSelectors(tsInbound) + .addOutboundTrafficSelectors(tsOutbound) + .build(); + verifyPersistableBundleEncodeDecodeIsLossless(sessionParams); + } + + @Test + public void testSetLifetimesEncodeDecodeIsLossless() throws Exception { + final int hardLifetime = (int) TimeUnit.HOURS.toSeconds(3L); + final int softLifetime = (int) TimeUnit.HOURS.toSeconds(1L); + + final TunnelModeChildSessionParams sessionParams = + createBuilderMinimum().setLifetimeSeconds(hardLifetime, softLifetime).build(); + verifyPersistableBundleEncodeDecodeIsLossless(sessionParams); + } + + @Test + public void testSetConfigRequestsEncodeDecodeIsLossless() throws Exception { + final int ipv6PrefixLen = 64; + final Inet4Address ipv4Address = + (Inet4Address) InetAddresses.parseNumericAddress("192.0.2.100"); + final Inet6Address ipv6Address = + (Inet6Address) InetAddresses.parseNumericAddress("2001:db8::1"); + + final TunnelModeChildSessionParams sessionParams = + createBuilderMinimum() + .addInternalAddressRequest(AF_INET) + .addInternalAddressRequest(AF_INET6) + .addInternalAddressRequest(ipv4Address) + .addInternalAddressRequest(ipv6Address, ipv6PrefixLen) + .addInternalDnsServerRequest(AF_INET) + .addInternalDnsServerRequest(AF_INET6) + .addInternalDhcpServerRequest(AF_INET) + .build(); + verifyPersistableBundleEncodeDecodeIsLossless(sessionParams); + } +} diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 9b500a7271d7..73a6b88e29ed 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -100,6 +100,8 @@ import java.util.UUID; public class VcnManagementServiceTest { private static final String TEST_PACKAGE_NAME = VcnManagementServiceTest.class.getPackage().getName(); + private static final String TEST_CB_PACKAGE_NAME = + VcnManagementServiceTest.class.getPackage().getName() + ".callback"; private static final ParcelUuid TEST_UUID_1 = new ParcelUuid(new UUID(0, 0)); private static final ParcelUuid TEST_UUID_2 = new ParcelUuid(new UUID(1, 1)); private static final VcnConfig TEST_VCN_CONFIG; @@ -288,6 +290,14 @@ public class VcnManagementServiceTest { private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot( Set<ParcelUuid> activeSubscriptionGroups, Map<Integer, ParcelUuid> subIdToGroupMap) { + return triggerSubscriptionTrackerCbAndGetSnapshot( + activeSubscriptionGroups, subIdToGroupMap, true /* hasCarrierPrivileges */); + } + + private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot( + Set<ParcelUuid> activeSubscriptionGroups, + Map<Integer, ParcelUuid> subIdToGroupMap, + boolean hasCarrierPrivileges) { final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class); doReturn(activeSubscriptionGroups).when(snapshot).getActiveSubscriptionGroups(); @@ -295,7 +305,7 @@ public class VcnManagementServiceTest { (activeSubscriptionGroups == null || activeSubscriptionGroups.isEmpty()) ? Collections.emptySet() : Collections.singleton(TEST_PACKAGE_NAME); - doReturn(true) + doReturn(hasCarrierPrivileges) .when(snapshot) .packageHasPermissionsForSubscriptionGroup( argThat(val -> activeSubscriptionGroups.contains(val)), @@ -549,13 +559,6 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); } - private void setUpVcnSubscription(int subId, ParcelUuid subGroup) { - mVcnMgmtSvc.setVcnConfig(subGroup, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); - - triggerSubscriptionTrackerCbAndGetSnapshot( - Collections.singleton(subGroup), Collections.singletonMap(subId, subGroup)); - } - private void verifyMergedNetworkCapabilities( NetworkCapabilities mergedCapabilities, @Transport int transportType, @@ -573,9 +576,23 @@ public class VcnManagementServiceTest { } private void setupSubscriptionAndStartVcn(int subId, ParcelUuid subGrp, boolean isVcnActive) { - setUpVcnSubscription(subId, subGrp); + setupSubscriptionAndStartVcn(subId, subGrp, isVcnActive, true /* hasCarrierPrivileges */); + } + + private void setupSubscriptionAndStartVcn( + int subId, ParcelUuid subGrp, boolean isVcnActive, boolean hasCarrierPrivileges) { + mVcnMgmtSvc.systemReady(); + triggerSubscriptionTrackerCbAndGetSnapshot( + Collections.singleton(subGrp), + Collections.singletonMap(subId, subGrp), + hasCarrierPrivileges); + final Vcn vcn = startAndGetVcnInstance(subGrp); doReturn(isVcnActive).when(vcn).isActive(); + + doReturn(true) + .when(mLocationPermissionChecker) + .checkLocationPermission(eq(TEST_PACKAGE_NAME), any(), eq(TEST_UID), any()); } private VcnUnderlyingNetworkPolicy startVcnAndGetPolicyForTransport( @@ -721,7 +738,7 @@ public class VcnManagementServiceTest { verify(mMockPolicyListener).onPolicyChanged(); } - private void verifyVcnCallback( + private void triggerVcnSafeMode( @NonNull ParcelUuid subGroup, @NonNull TelephonySubscriptionSnapshot snapshot) throws Exception { verify(mMockDeps) @@ -732,20 +749,20 @@ public class VcnManagementServiceTest { eq(snapshot), mVcnCallbackCaptor.capture()); - mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); - VcnCallback vcnCallback = mVcnCallbackCaptor.getValue(); vcnCallback.onEnteredSafeMode(); - - verify(mMockPolicyListener).onPolicyChanged(); } @Test - public void testVcnCallbackOnEnteredSafeMode() throws Exception { + public void testVcnEnteringSafeModeNotifiesPolicyListeners() throws Exception { TelephonySubscriptionSnapshot snapshot = triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1)); - verifyVcnCallback(TEST_UUID_1, snapshot); + mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); + + triggerVcnSafeMode(TEST_UUID_1, snapshot); + + verify(mMockPolicyListener).onPolicyChanged(); } private void triggerVcnStatusCallbackOnEnteredSafeMode( @@ -758,6 +775,9 @@ public class VcnManagementServiceTest { TelephonySubscriptionSnapshot snapshot = triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(subGroup)); + setupSubscriptionAndStartVcn( + TEST_SUBSCRIPTION_ID, subGroup, true /* isActive */, hasPermissionsforSubGroup); + doReturn(hasPermissionsforSubGroup) .when(snapshot) .packageHasPermissionsForSubscriptionGroup(eq(subGroup), eq(pkgName)); @@ -768,10 +788,7 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.registerVcnStatusCallback(subGroup, mMockStatusCallback, pkgName); - // Trigger systemReady() to set up LocationPermissionChecker - mVcnMgmtSvc.systemReady(); - - verifyVcnCallback(subGroup, snapshot); + triggerVcnSafeMode(subGroup, snapshot); } @Test @@ -825,6 +842,83 @@ public class VcnManagementServiceTest { assertEquals(TEST_PACKAGE_NAME, cbInfo.mPkgName); assertEquals(TEST_UID, cbInfo.mUid); verify(mMockIBinder).linkToDeath(eq(cbInfo), anyInt()); + + verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED); + } + + @Test + public void testRegisterVcnStatusCallback_MissingPermission() throws Exception { + setupSubscriptionAndStartVcn( + TEST_SUBSCRIPTION_ID, + TEST_UUID_1, + true /* isActive */, + false /* hasCarrierPrivileges */); + + mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); + + verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED); + } + + @Test + public void testRegisterVcnStatusCallback_VcnInactive() throws Exception { + setupSubscriptionAndStartVcn( + TEST_SUBSCRIPTION_ID, + TEST_UUID_1, + true /* isActive */, + true /* hasCarrierPrivileges */); + + // VCN is currently active. Lose carrier privileges for TEST_PACKAGE and hit teardown + // timeout so the VCN goes inactive. + final TelephonySubscriptionSnapshot snapshot = + triggerSubscriptionTrackerCbAndGetSnapshot( + Collections.singleton(TEST_UUID_1), + Collections.singletonMap(TEST_SUBSCRIPTION_ID, TEST_UUID_1), + false /* hasCarrierPrivileges */); + mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + mTestLooper.dispatchAll(); + + // Giving TEST_PACKAGE privileges again will restart the VCN (which will indicate ACTIVE + // when the status callback is registered). Instead, setup permissions for TEST_CB_PACKAGE + // so that it's permissioned to receive INACTIVE (instead of NOT_CONFIGURED) without + // reactivating the VCN. + doReturn(true) + .when(snapshot) + .packageHasPermissionsForSubscriptionGroup( + eq(TEST_UUID_1), eq(TEST_CB_PACKAGE_NAME)); + doReturn(true) + .when(mLocationPermissionChecker) + .checkLocationPermission(eq(TEST_CB_PACKAGE_NAME), any(), eq(TEST_UID), any()); + + mVcnMgmtSvc.registerVcnStatusCallback( + TEST_UUID_1, mMockStatusCallback, TEST_CB_PACKAGE_NAME); + + verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_INACTIVE); + } + + @Test + public void testRegisterVcnStatusCallback_VcnActive() throws Exception { + setupSubscriptionAndStartVcn( + TEST_SUBSCRIPTION_ID, + TEST_UUID_1, + true /* isActive */, + true /* hasCarrierPrivileges */); + + mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); + + verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_ACTIVE); + } + + @Test + public void testRegisterVcnStatusCallback_VcnSafeMode() throws Exception { + setupSubscriptionAndStartVcn( + TEST_SUBSCRIPTION_ID, + TEST_UUID_1, + false /* isActive */, + true /* hasCarrierPrivileges */); + + mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); + + verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_SAFE_MODE); } @Test(expected = IllegalStateException.class) diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index 69c21b967917..69b2fb135a8d 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -36,6 +36,7 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -143,11 +144,18 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection .onIpSecTransformsMigrated(makeDummyIpSecTransform(), makeDummyIpSecTransform()); mTestLooper.dispatchAll(); + verify(mIpSecSvc, times(2)) + .setNetworkForTunnelInterface( + eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), + eq(TEST_UNDERLYING_NETWORK_RECORD_1.network), + any()); + for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT}) { verify(mIpSecSvc) .applyTunnelModeTransform( eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any()); } + assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); } @@ -290,4 +298,22 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback( new TemporaryFailureException("vcn test"), VCN_ERROR_CODE_INTERNAL_ERROR); } + + @Test + public void testTeardown() throws Exception { + mGatewayConnection.teardownAsynchronously(); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); + assertTrue(mGatewayConnection.isQuitting()); + } + + @Test + public void testNonTeardownDisconnectRequest() throws Exception { + mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); + assertFalse(mGatewayConnection.isQuitting()); + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java index 17ae19e086cf..d07d2cf4f1bb 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java @@ -19,6 +19,8 @@ package com.android.server.vcn; import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -111,4 +113,22 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio public void testSafeModeTimeoutNotifiesCallback() { verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState); } + + @Test + public void testTeardown() throws Exception { + mGatewayConnection.teardownAsynchronously(); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); + assertTrue(mGatewayConnection.isQuitting()); + } + + @Test + public void testNonTeardownDisconnectRequest() throws Exception { + mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); + assertFalse(mGatewayConnection.isQuitting()); + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java index 9ea641f52e48..5f27fabb62b0 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java @@ -21,9 +21,12 @@ import static android.net.IpSecManager.IpSecTunnelInterface; import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.net.IpSecManager; @@ -54,7 +57,7 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect } @Test - public void testEnterWhileNotRunningTriggersQuit() throws Exception { + public void testEnterWhileQuittingTriggersQuit() throws Exception { final VcnGatewayConnection vgc = new VcnGatewayConnection( mVcnContext, @@ -64,7 +67,7 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect mGatewayStatusCallback, mDeps); - vgc.setIsRunning(false); + vgc.setIsQuitting(true); vgc.transitionTo(vgc.mDisconnectedState); mTestLooper.dispatchAll(); @@ -101,5 +104,18 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect assertNull(mGatewayConnection.getCurrentState()); verify(mIpSecSvc).deleteTunnelInterface(eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), any()); verifySafeModeTimeoutAlarmAndGetCallback(true /* expectCanceled */); + assertTrue(mGatewayConnection.isQuitting()); + verify(mGatewayStatusCallback).onQuit(); + } + + @Test + public void testNonTeardownDisconnectRequest() throws Exception { + mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState()); + assertFalse(mGatewayConnection.isQuitting()); + verify(mGatewayStatusCallback, never()).onQuit(); + // No safe mode timer changes expected. } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java index 7385204993c0..661e03af4f84 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java @@ -18,6 +18,8 @@ package com.android.server.vcn; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -79,10 +81,20 @@ public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnec // Should do nothing; already tearing down. assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */); + assertTrue(mGatewayConnection.isQuitting()); } @Test public void testSafeModeTimeoutNotifiesCallback() { verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState); } + + @Test + public void testNonTeardownDisconnectRequest() throws Exception { + mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); + assertFalse(mGatewayConnection.isQuitting()); + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java index 5b0850b03f1a..85a0277f8b48 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java @@ -17,6 +17,9 @@ package com.android.server.vcn; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -96,4 +99,22 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect public void testSafeModeTimeoutNotifiesCallback() { verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState); } + + @Test + public void testTeardownDisconnectRequest() throws Exception { + mGatewayConnection.teardownAsynchronously(); + mTestLooper.dispatchAll(); + + assertNull(mGatewayConnection.getCurrentState()); + assertTrue(mGatewayConnection.isQuitting()); + } + + @Test + public void testNonTeardownDisconnectRequest() throws Exception { + mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState()); + assertFalse(mGatewayConnection.isQuitting()); + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java index 9d3368271243..3dd710afed7b 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java @@ -16,6 +16,10 @@ package com.android.server.vcn; +import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.mockito.Matchers.any; @@ -33,6 +37,7 @@ import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnGatewayConnectionConfigTest; import android.os.ParcelUuid; import android.os.test.TestLooper; +import android.util.ArraySet; import com.android.server.VcnManagementService.VcnCallback; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; @@ -51,6 +56,11 @@ public class VcnTest { private static final ParcelUuid TEST_SUB_GROUP = new ParcelUuid(new UUID(0, 0)); private static final int NETWORK_SCORE = 0; private static final int PROVIDER_ID = 5; + private static final int[][] TEST_CAPS = + new int[][] { + new int[] {NET_CAPABILITY_INTERNET, NET_CAPABILITY_MMS}, + new int[] {NET_CAPABILITY_DUN} + }; private Context mContext; private VcnContext mVcnContext; @@ -91,13 +101,12 @@ public class VcnTest { mGatewayStatusCallbackCaptor = ArgumentCaptor.forClass(VcnGatewayStatusCallback.class); final VcnConfig.Builder configBuilder = new VcnConfig.Builder(mContext); - for (final int capability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) { + for (final int[] caps : TEST_CAPS) { configBuilder.addGatewayConnectionConfig( - VcnGatewayConnectionConfigTest.buildTestConfigWithExposedCaps(capability)); + VcnGatewayConnectionConfigTest.buildTestConfigWithExposedCaps(caps)); } - configBuilder.addGatewayConnectionConfig(VcnGatewayConnectionConfigTest.buildTestConfig()); - mConfig = configBuilder.build(); + mConfig = configBuilder.build(); mVcn = new Vcn( mVcnContext, @@ -130,8 +139,7 @@ public class VcnTest { @Test public void testSubscriptionSnapshotUpdatesVcnGatewayConnections() { final NetworkRequestListener requestListener = verifyAndGetRequestListener(); - startVcnGatewayWithCapabilities( - requestListener, VcnGatewayConnectionConfigTest.EXPOSED_CAPS); + startVcnGatewayWithCapabilities(requestListener, TEST_CAPS[0]); final Set<VcnGatewayConnection> gatewayConnections = mVcn.getVcnGatewayConnections(); assertFalse(gatewayConnections.isEmpty()); @@ -153,10 +161,19 @@ public class VcnTest { for (final int capability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) { startVcnGatewayWithCapabilities(requestListener, capability); } + } + + private void triggerVcnRequestListeners(NetworkRequestListener requestListener) { + for (final int[] caps : TEST_CAPS) { + startVcnGatewayWithCapabilities(requestListener, caps); + } + } - // Each Capability in EXPOSED_CAPS was split into a separate VcnGatewayConnection in #setUp. - // Expect one VcnGatewayConnection per capability. - final int numExpectedGateways = VcnGatewayConnectionConfigTest.EXPOSED_CAPS.length; + public Set<VcnGatewayConnection> startGatewaysAndGetGatewayConnections( + NetworkRequestListener requestListener) { + triggerVcnRequestListeners(requestListener); + + final int numExpectedGateways = TEST_CAPS.length; final Set<VcnGatewayConnection> gatewayConnections = mVcn.getVcnGatewayConnections(); assertEquals(numExpectedGateways, gatewayConnections.size()); @@ -168,7 +185,16 @@ public class VcnTest { any(), mGatewayStatusCallbackCaptor.capture()); - // Doesn't matter which callback this gets - any Gateway entering safe mode should shut down + return gatewayConnections; + } + + @Test + public void testGatewayEnteringSafemodeNotifiesVcn() { + final NetworkRequestListener requestListener = verifyAndGetRequestListener(); + final Set<VcnGatewayConnection> gatewayConnections = + startGatewaysAndGetGatewayConnections(requestListener); + + // Doesn't matter which callback this gets - any Gateway entering Safemode should shut down // all Gateways final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue(); statusCallback.onEnteredSafeMode(); @@ -181,4 +207,31 @@ public class VcnTest { verify(mVcnNetworkProvider).unregisterListener(requestListener); verify(mVcnCallback).onEnteredSafeMode(); } + + @Test + public void testGatewayQuit() { + final NetworkRequestListener requestListener = verifyAndGetRequestListener(); + final Set<VcnGatewayConnection> gatewayConnections = + new ArraySet<>(startGatewaysAndGetGatewayConnections(requestListener)); + + final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue(); + statusCallback.onQuit(); + mTestLooper.dispatchAll(); + + // Verify that the VCN requests the networkRequests be resent + assertEquals(1, mVcn.getVcnGatewayConnections().size()); + verify(mVcnNetworkProvider).resendAllRequests(requestListener); + + // Verify that the VcnGatewayConnection is restarted + triggerVcnRequestListeners(requestListener); + mTestLooper.dispatchAll(); + assertEquals(2, mVcn.getVcnGatewayConnections().size()); + verify(mDeps, times(gatewayConnections.size() + 1)) + .newVcnGatewayConnection( + eq(mVcnContext), + eq(TEST_SUB_GROUP), + eq(mSubscriptionSnapshot), + any(), + mGatewayStatusCallbackCaptor.capture()); + } } diff --git a/tools/bit/make.cpp b/tools/bit/make.cpp index df64a801e213..2a88732b50b1 100644 --- a/tools/bit/make.cpp +++ b/tools/bit/make.cpp @@ -89,8 +89,8 @@ BuildVars::BuildVars(const string& outDir, const string& buildProduct, } Json::Value json; - Json::Reader reader; - if (!reader.parse(stream, json)) { + Json::CharReaderBuilder builder; + if (!Json::parseFromStream(builder, stream, &json, /* errorMessage = */ nullptr)) { return; } @@ -132,8 +132,9 @@ BuildVars::save() return; } - Json::StyledStreamWriter writer(" "); - + Json::StreamWriterBuilder factory; + factory["indentation"] = " "; + std::unique_ptr<Json::StreamWriter> const writer(factory.newStreamWriter()); Json::Value json(Json::objectValue); for (map<string,string>::const_iterator it = m_cache.begin(); it != m_cache.end(); it++) { @@ -141,7 +142,7 @@ BuildVars::save() } std::ofstream stream(m_filename, std::ofstream::binary); - writer.write(stream, json); + writer->write(json, &stream); } string @@ -212,8 +213,8 @@ read_modules(const string& buildOut, const string& device, map<string,Module>* r } Json::Value json; - Json::Reader reader; - if (!reader.parse(stream, json)) { + Json::CharReaderBuilder builder; + if (!Json::parseFromStream(builder, stream, &json, /* errorMessage = */ nullptr)) { json_error(filename, "can't parse json format", quiet); return; } |