diff options
299 files changed, 9083 insertions, 2743 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/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/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/core/api/current.txt b/core/api/current.txt index 8710e613602f..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 @@ -26723,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); } } @@ -30273,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; @@ -30431,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(); @@ -50983,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 91ccefee5d54..d6786f8a3f8e 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -241,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 019e7ffe11ef..f01724316c5f 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -154,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"; @@ -423,6 +424,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"; @@ -536,9 +540,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); @@ -565,6 +574,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); @@ -2779,13 +2789,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; @@ -2802,18 +2811,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 } } @@ -8835,6 +8834,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"; @@ -13049,7 +13050,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"; @@ -13076,7 +13077,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 { @@ -13132,7 +13133,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 @@ -13608,7 +13609,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 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/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/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/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 7b62f3b2f1a2..d79b66c1cf56 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -7017,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/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/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 8f1e2defd215..268002f1dd52 100644 --- a/core/java/android/net/IpSecAlgorithm.java +++ b/core/java/android/net/IpSecAlgorithm.java @@ -232,11 +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_AES_CMAC, 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 = 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/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/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/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/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 71a849963818..c967deb5e810 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -2766,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(); } @@ -2792,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/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/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/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/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/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/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/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/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/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index 342456a58091..33ee8f04d83e 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 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/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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ecb7c1d30f83..50d1e6bb0b18 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1886,6 +1886,11 @@ <permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS" android:protectionLevel="signature|privileged" /> + <!-- @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. <p>Not for use by third-party or privileged applications. --> 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 b0a4c6e027cd..d61d19a41266 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -673,15 +673,6 @@ --> </integer-array> - <!-- The device states (supplied by DeviceStateManager) that should be treated as unfolded by - the display fold controller. Default is empty. --> - <integer-array name="config_unfoldedDeviceStates"> - <!-- Example: - <item>3</item> - <item>4</item> - --> - </integer-array> - <!-- Indicate the display area rect for foldable devices in folded state. --> <string name="config_foldedArea"></string> @@ -3957,6 +3948,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> @@ -4671,15 +4666,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 @@ -4710,6 +4696,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/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 aecb01551a26..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" /> @@ -3765,7 +3784,6 @@ <!-- For Foldables --> <java-symbol type="array" name="config_foldedDeviceStates" /> - <java-symbol type="array" name="config_unfoldedDeviceStates" /> <java-symbol type="string" name="config_foldedArea" /> <java-symbol type="array" name="config_disableApksUnlessMatchedSku_apk_list" /> @@ -4033,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" /> @@ -4054,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" /> @@ -4161,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/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/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/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/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/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/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/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/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/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 4bdc16b050a8..b75a0bc1525a 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -412,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/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 ed4d47cfa592..a02900328ae7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java @@ -31,11 +31,13 @@ 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; @@ -61,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/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/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/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/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/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/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/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..99ce2db006ce 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", diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 986e2acf6029..4ca6f73755f0 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; @@ -190,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; @@ -331,7 +330,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; @@ -1042,15 +1041,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"); @@ -1096,7 +1094,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), @@ -1480,7 +1478,10 @@ public class ConnectivityService extends IConnectivityManager.Stub @NonNull private NetworkInfo filterNetworkInfo(@NonNull NetworkInfo networkInfo, int type, @NonNull NetworkCapabilities nc, int uid, boolean ignoreBlocked) { - NetworkInfo filtered = new NetworkInfo(networkInfo); + 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 @@ -2387,13 +2388,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 { @@ -3193,16 +3187,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); } @@ -3289,8 +3283,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 @@ -3831,7 +3824,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); @@ -4144,13 +4154,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() { @@ -4480,16 +4483,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; @@ -5247,11 +5247,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; } @@ -5298,6 +5307,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); } @@ -7154,7 +7165,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); } } @@ -7209,13 +7220,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; @@ -7226,7 +7237,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"); } @@ -7286,14 +7299,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()); } @@ -7310,12 +7323,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); @@ -7913,7 +7927,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<>(); @@ -7927,7 +7942,7 @@ public class ConnectivityService extends IConnectivityManager.Stub defaultNetworks.add(nai.network); } } - return defaultNetworks.toArray(new Network[0]); + return defaultNetworks; } /** @@ -7952,8 +7967,8 @@ public class ConnectivityService extends IConnectivityManager.Stub state.legacyNetworkType); snapshots.add(snapshot); } - mStatsService.forceUpdateIfaces(getDefaultNetworks(), snapshots.toArray( - new NetworkStateSnapshot[0]), activeIface, underlyingNetworkInfos); + mStatsManager.notifyNetworkStatus(getDefaultNetworks(), + snapshots, activeIface, Arrays.asList(underlyingNetworkInfos)); } catch (Exception ignored) { } } @@ -8272,24 +8287,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: { @@ -8353,10 +8360,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; } } @@ -9033,7 +9043,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()); @@ -9045,7 +9055,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); + } } } @@ -9061,10 +9075,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 c5233f43dcb9..27b648e53a38 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -578,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. @@ -948,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; + } } } @@ -4690,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/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/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..82f72e8cc1ac 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,6 +82,7 @@ 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.net.BaseNetworkObserver; @@ -291,6 +297,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 +353,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); } 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/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 91674cd8afa6..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,11 +48,14 @@ class DeviceStateToLayoutMap { public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE; + 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) { @@ -76,48 +88,36 @@ class DeviceStateToLayoutMap { } /** - * Loads config.xml-specified folded configurations for foldable devices. + * Reads display-layout-configuration files to get the layouts to use for this device. */ - 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; - } + 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); - final int[] unfoldedDeviceStates = context.getResources().getIntArray( - com.android.internal.R.array.config_unfoldedDeviceStates); - // Only add folded states if folded state config is not empty - if (foldedDeviceStates.length == 0 || unfoldedDeviceStates.length == 0) { - return; - } - - for (int state : foldedDeviceStates) { - // Create the folded state layout - createLayout(state).createDisplayLocked( - DisplayAddress.fromPhysicalDisplayId(displayIds[0]), true /*isDefault*/); - } - - for (int state : unfoldedDeviceStates) { - // Create the unfolded state layout - createLayout(state).createDisplayLocked( - DisplayAddress.fromPhysicalDisplayId(displayIds[1]), true /*isDefault*/); + 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); } } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index ce0ba9f3231c..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); @@ -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,9 +1203,6 @@ 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.getDisplayLocked(device); final int state; final int displayId = display.getDisplayIdLocked(); diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 2bcc35cf176f..d6826be248df 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -16,7 +16,6 @@ package com.android.server.display; -import android.content.Context; import android.hardware.devicestate.DeviceStateManager; import android.os.SystemProperties; import android.text.TextUtils; @@ -90,7 +89,6 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { 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. @@ -122,16 +120,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private Layout mCurrentLayout = null; 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 @@ -470,10 +464,10 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } /** - * Resets the current layout in preparation for a new layout; essentially just marks - * all the currently layed out displays as disabled. This ensures the display devices - * are turned off. If they are meant to be used in the new layout, - * {@link #applyLayoutLocked()} will reenabled them. + * 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); @@ -481,7 +475,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { final Layout.Display displayLayout = layout.getAt(i); final LogicalDisplay display = getDisplayLocked(displayLayout.getLogicalDisplayId()); if (display != null) { - enableDisplayLocked(display, false); + enableDisplayLocked(display, true); // Reset all displays back to enabled } } } @@ -503,7 +497,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // 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, or if the layout has an error. + // 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) { @@ -526,7 +521,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { if (newDisplay != oldDisplay) { newDisplay.swapDisplaysLocked(oldDisplay); } - enableDisplayLocked(newDisplay, true); + enableDisplayLocked(newDisplay, displayLayout.isEnabled()); } } @@ -545,13 +540,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device); display.updateLocked(mDisplayDeviceRepo); mLogicalDisplays.put(displayId, display); - - // Internal displays start off disabled. The display is enabled later if it is part of the - // currently selected display layout. - final boolean isEnabled = device != null - && device.getDisplayDeviceInfoLocked().type != Display.TYPE_INTERNAL; - enableDisplayLocked(display, isEnabled); - + enableDisplayLocked(display, device != null); return display; } @@ -582,7 +571,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { 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); + 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/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/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 ebf1fe9d7cd0..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; 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/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/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/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/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 dd5a19669f74..55625809b632 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -214,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; @@ -580,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; @@ -998,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) { @@ -1080,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) { @@ -1695,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; @@ -3735,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); @@ -4383,6 +4421,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } mVisibleRequested = visible; + setInsetsFrozen(!visible); if (app != null) { mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */); } @@ -5835,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(); } @@ -7390,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; @@ -7455,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. */ @@ -8133,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); 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 9d5c5bed0419..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()); @@ -3699,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. @@ -4101,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(); } } @@ -4829,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/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java index 0be43ab8d26c..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))) { @@ -181,11 +185,12 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { if (DEBUG) appendLog("activity-options-fullscreen=" + outParams.mBounds); } else if (layout != null && canApplyFreeformPolicy) { mTmpBounds.set(currentParams.mBounds); - getLayoutBounds(display, root, layout, mTmpBounds); + 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"); @@ -241,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"); @@ -248,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 { @@ -288,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; @@ -303,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 @@ -313,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() @@ -328,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; } @@ -500,7 +525,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { && launchMode == WINDOWING_MODE_PINNED; } - private void getLayoutBounds(@NonNull DisplayContent display, @NonNull ActivityRecord root, + 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; @@ -511,10 +536,10 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { // 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; @@ -525,7 +550,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { width = inOutBounds.width(); height = inOutBounds.height(); } else { - getTaskBounds(root, display, windowLayout, WINDOWING_MODE_FREEFORM, + getTaskBounds(root, displayArea, windowLayout, WINDOWING_MODE_FREEFORM, /* hasInitialBounds */ false, inOutBounds); width = inOutBounds.width(); height = inOutBounds.height(); @@ -571,7 +596,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { } inOutBounds.set(0, 0, width, height); - inOutBounds.offset(displayStableBounds.left, displayStableBounds.top); + inOutBounds.offset(stableBounds.left, stableBounds.top); final int xOffset = (int) (fractionOfHorizontalOffset * (defaultWidth - width)); final int yOffset = (int) (fractionOfVerticalOffset * (defaultHeight - height)); inOutBounds.offset(xOffset, yOffset); @@ -582,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) { @@ -638,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) { @@ -669,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( @@ -678,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 @@ -688,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) { @@ -717,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 @@ -743,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; @@ -764,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 @@ -781,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; } @@ -866,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; } @@ -897,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. @@ -906,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 e03198d63b4c..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. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 70b0f5888766..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( diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index a35f4dea643a..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, @@ -1715,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(); } /** @@ -1745,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(); } /** @@ -1804,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); } @@ -1943,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); } @@ -3224,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; @@ -3608,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; } @@ -4024,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); @@ -4839,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/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 a92ab9e35ddc..ad9aa7b6e3ae 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 @@ -102,14 +102,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 } @@ -214,7 +214,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/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/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/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java index 92221c9713d3..bcd853c76a79 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -94,8 +94,7 @@ public class LogicalDisplayMapperTest { // Disable binder caches in this process. PropertyInvalidatedCache.disableForTestMode(); - mLogicalDisplayMapper = new LogicalDisplayMapper( - mContext, mDisplayDeviceRepo, mListenerMock); + mLogicalDisplayMapper = new LogicalDisplayMapper(mDisplayDeviceRepo, mListenerMock); } 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/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 fb2272ed9fd3..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 // ===================================== @@ -1365,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); @@ -1587,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; @@ -1618,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/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/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/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/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/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/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/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 4d5cd9af17ef..3bc15a386183 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -149,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; @@ -180,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; @@ -203,7 +203,6 @@ import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.NetworkStack; import android.net.NetworkStackClient; -import android.net.NetworkStateSnapshot; import android.net.NetworkTestResultParcelable; import android.net.OemNetworkPreferences; import android.net.ProxyInfo; @@ -250,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; @@ -282,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; @@ -423,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; @@ -440,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 = @@ -539,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); } @@ -1124,7 +1124,7 @@ public class ConnectivityServiceTest { return mDeviceIdleInternal; } }, - mNetworkManagementService, mMockNetd, userId, mKeyStore); + mNetworkManagementService, mMockNetd, userId, mVpnProfileStore); } public void setUids(Set<UidRange> uids) { @@ -1303,8 +1303,9 @@ public class ConnectivityServiceTest { return mVMSHandlerThread; } - public KeyStore getKeyStore() { - return mKeyStore; + @Override + public VpnProfileStore getVpnProfileStore() { + return mVpnProfileStore; } public INetd getNetd() { @@ -1471,7 +1472,6 @@ public class ConnectivityServiceTest { mDeps = makeDependencies(); returnRealCallingUid(); mService = new ConnectivityService(mServiceContext, - mStatsService, mMockDnsResolver, mock(IpConnectivityLog.class), mMockNetd, @@ -5486,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(NetworkStateSnapshot[].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]; @@ -5511,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 @@ -5532,46 +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( - NetworkStateSnapshot[].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(); @@ -5584,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. @@ -5601,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 @@ -5613,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 @@ -5640,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(NetworkStateSnapshot[].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 @@ -5659,13 +5661,13 @@ public class ConnectivityServiceTest { mEthernetNetworkAgent.adjustScore(-40); mEthernetNetworkAgent.connect(false); waitForIdle(); - verify(mStatsService).forceUpdateIfaces(any(Network[].class), - any(NetworkStateSnapshot[].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 @@ -5675,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 @@ -7509,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"; @@ -7518,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 { @@ -9876,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/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/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/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; } |