diff options
206 files changed, 5912 insertions, 1633 deletions
diff --git a/Android.bp b/Android.bp index 20ca1b7c1020..709929139fad 100644 --- a/Android.bp +++ b/Android.bp @@ -584,6 +584,7 @@ java_library { "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/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/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index 2a23d60d8af6..c9a184358925 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())); 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 00dec11c1f56..8ef2230b32e6 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 @@ -17535,6 +17536,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 @@ -30273,6 +30277,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 +30436,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(); @@ -31868,6 +31864,7 @@ package android.os.storage { method @WorkerThread public long getAllocatableBytes(@NonNull java.util.UUID) throws java.io.IOException; method @WorkerThread public long getCacheQuotaBytes(@NonNull java.util.UUID) throws java.io.IOException; method @WorkerThread public long getCacheSizeBytes(@NonNull java.util.UUID) throws java.io.IOException; + method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) public android.app.PendingIntent getManageSpaceActivityIntent(@NonNull String, int); method public String getMountedObbPath(String); method @NonNull public android.os.storage.StorageVolume getPrimaryStorageVolume(); method @NonNull public java.util.List<android.os.storage.StorageVolume> getRecentStorageVolumes(); 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..8f067c237bbe 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); @@ -8835,6 +8845,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 +13061,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 +13088,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 +13144,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 +13620,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/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/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/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/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/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl index 0041699df9ef..98b4e0b4f402 100644 --- a/core/java/android/os/storage/IStorageManager.aidl +++ b/core/java/android/os/storage/IStorageManager.aidl @@ -28,6 +28,8 @@ import android.os.storage.StorageVolume; import android.os.storage.VolumeInfo; import android.os.storage.VolumeRecord; import com.android.internal.os.AppFuseMount; +import android.app.PendingIntent; + /** * WARNING! Update IMountService.h and IMountService.cpp if you change this @@ -198,4 +200,5 @@ interface IStorageManager { void disableAppDataIsolation(in String pkgName, int pid, int userId) = 90; void notifyAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 91; void notifyAppIoResumed(in String volumeUuid, int uid, int tid, int reason) = 92; + PendingIntent getManageSpaceActivityIntent(in String packageName, int requestCode) = 93; } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 7c8874cc1ea7..c967deb5e810 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -49,6 +49,7 @@ import android.app.Activity; import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.content.Context; @@ -701,6 +702,33 @@ public class StorageManager { } } + /** + * Returns a {@link PendingIntent} that can be used by Apps with + * {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission + * to launch the manageSpaceActivity for any App that implements it, irrespective of its + * exported status. + * <p> + * Caller has the responsibility of supplying a valid packageName which has + * manageSpaceActivity implemented. + * + * @param packageName package name for the App for which manageSpaceActivity is to be launched + * @param requestCode for launching the activity + * @return PendingIntent to launch the manageSpaceActivity if successful, null if the + * packageName doesn't have a manageSpaceActivity. + * @throws IllegalArgumentException an invalid packageName is supplied. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) + @Nullable + public PendingIntent getManageSpaceActivityIntent( + @NonNull String packageName, int requestCode) { + try { + return mStorageManager.getManageSpaceActivityIntent(packageName, + requestCode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private ObbInfo getObbInfo(String canonicalPath) { try { final ObbInfo obbInfo = ObbScanner.getObbInfo(canonicalPath); @@ -2738,10 +2766,11 @@ public class StorageManager { * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public void notifyAppIoBlocked(@NonNull String volumeUuid, int uid, int tid, + public void notifyAppIoBlocked(@NonNull UUID volumeUuid, int uid, int tid, @AppIoBlockedReason int reason) { + Objects.requireNonNull(volumeUuid); try { - mStorageManager.notifyAppIoBlocked(volumeUuid, uid, tid, reason); + mStorageManager.notifyAppIoBlocked(convert(volumeUuid), uid, tid, reason); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2764,10 +2793,11 @@ public class StorageManager { * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public void notifyAppIoResumed(@NonNull String volumeUuid, int uid, int tid, + public void notifyAppIoResumed(@NonNull UUID volumeUuid, int uid, int tid, @AppIoBlockedReason int reason) { + Objects.requireNonNull(volumeUuid); try { - mStorageManager.notifyAppIoResumed(volumeUuid, uid, tid, reason); + mStorageManager.notifyAppIoResumed(convert(volumeUuid), uid, tid, reason); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/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/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java index 4a5c95f43d46..d23a1e5992cc 100644 --- a/core/java/android/view/ImeFocusController.java +++ b/core/java/android/view/ImeFocusController.java @@ -116,8 +116,9 @@ public final class ImeFocusController { if (!hasWindowFocus || !mHasImeFocus || isInLocalFocusMode(windowAttribute)) { return; } + View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView; if (DEBUG) { - Log.v(TAG, "onWindowFocus: " + focusedView + Log.v(TAG, "onWindowFocus: " + viewForWindowFocus + " softInputMode=" + InputMethodDebug.softInputModeToString( windowAttribute.softInputMode)); } @@ -128,8 +129,8 @@ public final class ImeFocusController { if (DEBUG) Log.v(TAG, "Restarting due to isRestartOnNextWindowFocus as true"); forceFocus = true; } + // Update mNextServedView when focusedView changed. - final View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView; onViewFocusChanged(viewForWindowFocus, true); // Starting new input when the next focused view is same as served view but the currently diff --git a/core/java/android/view/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/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/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/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java index c353de88dc3b..93374ba0cf46 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java +++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java @@ -228,6 +228,8 @@ public final class InputMethodDebug { return "HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR"; case SoftInputShowHideReason.HIDE_REMOVE_CLIENT: return "HIDE_REMOVE_CLIENT"; + case SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY: + return "SHOW_RESTORE_IME_VISIBILITY"; default: return "Unknown=" + reason; } diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java index 1553e2eb0793..f1cdf2b38c4c 100644 --- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java +++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java @@ -49,7 +49,8 @@ import java.lang.annotation.Retention; SoftInputShowHideReason.HIDE_RECENTS_ANIMATION, SoftInputShowHideReason.HIDE_BUBBLES, SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR, - SoftInputShowHideReason.HIDE_REMOVE_CLIENT}) + SoftInputShowHideReason.HIDE_REMOVE_CLIENT, + SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY}) public @interface SoftInputShowHideReason { /** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */ int SHOW_SOFT_INPUT = 0; @@ -167,4 +168,10 @@ public @interface SoftInputShowHideReason { * Hide soft input when a {@link com.android.internal.view.IInputMethodClient} is removed. */ int HIDE_REMOVE_CLIENT = 21; + + /** + * Show soft input when the system invoking + * {@link com.android.server.wm.WindowManagerInternal#shouldRestoreImeVisibility}. + */ + int SHOW_RESTORE_IME_VISIBILITY = 22; } diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index 342456a58091..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/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/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/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/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/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 79fbcc376b3c..757dc0c65815 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> 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 761512ce3d14..b75a0bc1525a 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -91,9 +91,6 @@ <!-- The number of columns in the QuickSettings --> <integer name="quick_settings_num_columns">3</integer> - <!-- The number of columns in the Quick Settings customizer --> - <integer name="quick_settings_edit_num_columns">@integer/quick_settings_num_columns</integer> - <!-- The number of rows in the QuickSettings --> <integer name="quick_settings_max_rows">3</integer> @@ -415,6 +412,9 @@ <!-- Whether or not child notifications that are part of a group will have shadows. --> <bool name="config_enableShadowOnChildNotifications">true</bool> + <!-- If true, group numbers are shown in the expander instead of via "+N" overflow number --> + <bool name="config_showNotificationGroupCountInExpander">true</bool> + <!-- Whether or not a view containing child notifications will have a custom background when it has been expanded to reveal its children. --> <bool name="config_showGroupNotificationBgWhenExpanded">false</bool> diff --git a/packages/SystemUI/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/qs/SideLabelTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt index c3cc3af10e83..52f111e7ab48 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt @@ -18,7 +18,6 @@ package com.android.systemui.qs import android.content.Context import android.util.AttributeSet -import com.android.systemui.R open class SideLabelTileLayout( context: Context, @@ -28,9 +27,6 @@ open class SideLabelTileLayout( override fun updateResources(): Boolean { return super.updateResources().also { mMaxAllowedRows = 4 - mCellMarginHorizontal = (mCellMarginHorizontal * 1.2).toInt() - mCellMarginVertical = mCellMarginHorizontal - mMaxCellHeight = context.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java index 47cb45b14b9b..ce8f6c1737d7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java @@ -16,19 +16,19 @@ package com.android.systemui.qs.customize; import android.content.Context; import android.view.View; -import android.widget.TextView; import com.android.systemui.plugins.qs.QSIconView; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.tileimpl.QSTileView; -public class CustomizeTileView extends QSTileView { +public class CustomizeTileView extends QSTileView implements TileAdapter.CustomizeView { private boolean mShowAppLabel; public CustomizeTileView(Context context, QSIconView icon) { super(context, icon); } + @Override public void setShowAppLabel(boolean showAppLabel) { mShowAppLabel = showAppLabel; mSecondLine.setVisibility(showAppLabel ? View.VISIBLE : View.GONE); @@ -41,10 +41,6 @@ public class CustomizeTileView extends QSTileView { mSecondLine.setVisibility(mShowAppLabel ? View.VISIBLE : View.GONE); } - public TextView getAppLabel() { - return mSecondLine; - } - @Override protected boolean animationsEnabled() { return false; @@ -54,4 +50,9 @@ public class CustomizeTileView extends QSTileView { public boolean isLongClickable() { return false; } + + @Override + public void changeState(QSTile.State state) { + handleStateChanged(state); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt new file mode 100644 index 000000000000..4ffcd8cdd9ce --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt @@ -0,0 +1,50 @@ +package com.android.systemui.qs.customize + +import android.content.Context +import android.graphics.drawable.Drawable +import android.view.View +import com.android.systemui.plugins.qs.QSIconView +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.tileimpl.QSTileViewHorizontal + +/** + * Class for displaying tiles in [QSCustomizer] with the new design (labels on the side). + * + * This is a class parallel to [CustomizeTileView], but inheriting from [QSTileViewHorizontal]. + */ +class CustomizeTileViewHorizontal( + context: Context, + icon: QSIconView +) : QSTileViewHorizontal(context, icon), + TileAdapter.CustomizeView { + + private var showAppLabel = false + + override fun setShowAppLabel(showAppLabel: Boolean) { + this.showAppLabel = showAppLabel + mSecondLine.visibility = if (showAppLabel) View.VISIBLE else View.GONE + mLabel.isSingleLine = showAppLabel + } + + override fun handleStateChanged(state: QSTile.State) { + super.handleStateChanged(state) + mSecondLine.visibility = if (showAppLabel) View.VISIBLE else View.GONE + } + + override fun animationsEnabled(): Boolean { + return false + } + + override fun isLongClickable(): Boolean { + return false + } + + override fun changeState(state: QSTile.State) { + handleStateChanged(state) + } + + override fun newTileBackground(): Drawable? { + super.newTileBackground() + return paintDrawable + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index 7a91421b00a1..0adc8448b89f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -14,6 +14,8 @@ package com.android.systemui.qs.customize; +import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG; + import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; @@ -30,6 +32,7 @@ import android.widget.FrameLayout; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.core.view.AccessibilityDelegateCompat; import androidx.core.view.ViewCompat; import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup; @@ -41,6 +44,7 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; +import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSEditEvent; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.customize.TileAdapter.Holder; @@ -49,11 +53,13 @@ import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.tileimpl.QSIconViewImpl; +import com.android.systemui.qs.tileimpl.QSTileView; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; +import javax.inject.Named; /** */ @QSScope @@ -75,7 +81,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta private static final int ACTION_ADD = 1; private static final int ACTION_MOVE = 2; - private static final int NUM_COLUMNS_ID = R.integer.quick_settings_edit_num_columns; + private static final int NUM_COLUMNS_ID = R.integer.quick_settings_num_columns; private final Context mContext; @@ -102,9 +108,11 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta private final AccessibilityDelegateCompat mAccessibilityDelegate; private RecyclerView mRecyclerView; private int mNumColumns; + private final boolean mUseHorizontalTiles; @Inject - public TileAdapter(Context context, QSTileHost qsHost, UiEventLogger uiEventLogger) { + public TileAdapter(Context context, QSTileHost qsHost, UiEventLogger uiEventLogger, + @Named(QS_LABELS_FLAG) boolean useHorizontalTiles) { mContext = context; mHost = qsHost; mUiEventLogger = uiEventLogger; @@ -114,6 +122,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta mMinNumTiles = context.getResources().getInteger(R.integer.quick_settings_min_num_tiles); mNumColumns = context.getResources().getInteger(NUM_COLUMNS_ID); mAccessibilityDelegate = new TileAdapterDelegate(); + mUseHorizontalTiles = useHorizontalTiles; } @Override @@ -271,7 +280,10 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } FrameLayout frame = (FrameLayout) inflater.inflate(R.layout.qs_customize_tile_frame, parent, false); - frame.addView(new CustomizeTileView(context, new QSIconViewImpl(context))); + View view = mUseHorizontalTiles + ? new CustomizeTileViewHorizontal(context, new QSIconViewImpl(context)) + : new CustomizeTileView(context, new QSIconViewImpl(context)); + frame.addView(view); return new Holder(frame); } @@ -354,8 +366,9 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } info.state.expandedAccessibilityClassName = ""; - holder.mTileView.handleStateChanged(info.state); - holder.mTileView.setShowAppLabel(position > mEditIndex && !info.isSystem); + // The holder has a tileView, therefore this call is not null + holder.getTileAsCustomizeView().changeState(info.state); + holder.getTileAsCustomizeView().setShowAppLabel(position > mEditIndex && !info.isSystem); holder.mTileView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); holder.mTileView.setClickable(true); holder.mTileView.setOnClickListener(null); @@ -534,25 +547,34 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } public class Holder extends ViewHolder { - private CustomizeTileView mTileView; + private QSTileView mTileView; public Holder(View itemView) { super(itemView); if (itemView instanceof FrameLayout) { - mTileView = (CustomizeTileView) ((FrameLayout) itemView).getChildAt(0); - mTileView.setBackground(null); + mTileView = (QSTileView) ((FrameLayout) itemView).getChildAt(0); + if (mTileView instanceof CustomizeTileView) { + mTileView.setBackground(null); + } mTileView.getIcon().disableAnimation(); mTileView.setTag(this); ViewCompat.setAccessibilityDelegate(mTileView, mAccessibilityDelegate); } } + @Nullable + public CustomizeView getTileAsCustomizeView() { + return (CustomizeView) mTileView; + } + public void clearDrag() { itemView.clearAnimation(); - mTileView.findViewById(R.id.tile_label).clearAnimation(); - mTileView.findViewById(R.id.tile_label).setAlpha(1); - mTileView.getAppLabel().clearAnimation(); - mTileView.getAppLabel().setAlpha(.6f); + if (mTileView instanceof CustomizeTileView) { + mTileView.findViewById(R.id.tile_label).clearAnimation(); + mTileView.findViewById(R.id.tile_label).setAlpha(1); + mTileView.getAppLabel().clearAnimation(); + mTileView.getAppLabel().setAlpha(.6f); + } } public void startDrag() { @@ -560,12 +582,14 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta .setDuration(DRAG_LENGTH) .scaleX(DRAG_SCALE) .scaleY(DRAG_SCALE); - mTileView.findViewById(R.id.tile_label).animate() - .setDuration(DRAG_LENGTH) - .alpha(0); - mTileView.getAppLabel().animate() - .setDuration(DRAG_LENGTH) - .alpha(0); + if (mTileView instanceof CustomizeTileView) { + mTileView.findViewById(R.id.tile_label).animate() + .setDuration(DRAG_LENGTH) + .alpha(0); + mTileView.getAppLabel().animate() + .setDuration(DRAG_LENGTH) + .alpha(0); + } } public void stopDrag() { @@ -573,12 +597,14 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta .setDuration(DRAG_LENGTH) .scaleX(1) .scaleY(1); - mTileView.findViewById(R.id.tile_label).animate() - .setDuration(DRAG_LENGTH) - .alpha(1); - mTileView.getAppLabel().animate() - .setDuration(DRAG_LENGTH) - .alpha(.6f); + if (mTileView instanceof CustomizeTileView) { + mTileView.findViewById(R.id.tile_label).animate() + .setDuration(DRAG_LENGTH) + .alpha(1); + mTileView.getAppLabel().animate() + .setDuration(DRAG_LENGTH) + .alpha(.6f); + } } boolean canRemove() { @@ -722,7 +748,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta int position = mCurrentDrag.getAdapterPosition(); if (position == RecyclerView.NO_POSITION) return; TileInfo info = mTiles.get(position); - mCurrentDrag.mTileView.setShowAppLabel( + ((CustomizeView) mCurrentDrag.mTileView).setShowAppLabel( position > mEditIndex && !info.isSystem); mCurrentDrag.stopDrag(); mCurrentDrag = null; @@ -782,4 +808,9 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta public void onSwiped(ViewHolder viewHolder, int direction) { } }; + + interface CustomizeView { + void setShowAppLabel(boolean showAppLabel); + void changeState(@NonNull QSTile.State state); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java index 207b25d001b9..b59326ae56d5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java @@ -174,4 +174,8 @@ public class QSTileView extends QSTileBaseView { mLabelContainer.setClickable(false); mLabelContainer.setLongClickable(false); } + + public TextView getAppLabel() { + return mSecondLine; + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt index 07d48f32ff20..231037fdd158 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt @@ -35,13 +35,13 @@ import com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState // Placeholder private const val CORNER_RADIUS = 40f -class QSTileViewHorizontal( +open class QSTileViewHorizontal( context: Context, icon: QSIconView ) : QSTileView(context, icon, false) { - private var paintDrawable: PaintDrawable? = null - private var paintColor = Color.TRANSPARENT + protected var paintDrawable: PaintDrawable? = null + private var paintColor = Color.WHITE private var paintAnimator: ValueAnimator? = null init { @@ -103,7 +103,7 @@ class QSTileViewHorizontal( mSecondLine.setTextColor(mLabel.textColors) mLabelContainer.background = null - val allowAnimations = animationsEnabled() && paintColor != Color.TRANSPARENT + val allowAnimations = animationsEnabled() && paintColor != Color.WHITE val newColor = getCircleColor(state.state) if (allowAnimations) { animateToNewState(newColor) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 805ac7cf1ec9..3d6dea3cd3f0 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -519,7 +519,7 @@ public class ScreenshotController { setWindowFocusable(true); if (mConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, false)) { + SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, true)) { View decorView = mWindow.getDecorView(); // Wait until this window is attached to request because it is diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/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/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/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/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/qs/customize/TileAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java index 3d53062d7d02..62cc9b7e3602 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java @@ -49,7 +49,7 @@ public class TileAdapterTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); TestableLooper.get(this).runWithLooper(() -> mTileAdapter = - new TileAdapter(mContext, mQSTileHost, new UiEventLoggerFake())); + new TileAdapter(mContext, mQSTileHost, new UiEventLoggerFake(), /* qsFlag */false)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/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/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..e05a202ae657 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 @@ -4144,13 +4137,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() { @@ -7913,7 +7899,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 +7914,7 @@ public class ConnectivityService extends IConnectivityManager.Stub defaultNetworks.add(nai.network); } } - return defaultNetworks.toArray(new Network[0]); + return defaultNetworks; } /** @@ -7952,8 +7939,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 +8259,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 +8332,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; } } diff --git a/services/core/java/com/android/server/ConnectivityServiceInitializer.java b/services/core/java/com/android/server/ConnectivityServiceInitializer.java index 097441f706e6..b9922087109f 100644 --- a/services/core/java/com/android/server/ConnectivityServiceInitializer.java +++ b/services/core/java/com/android/server/ConnectivityServiceInitializer.java @@ -20,8 +20,6 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; import android.content.Context; -import android.net.INetworkStatsService; -import android.os.ServiceManager; import android.util.Log; /** @@ -37,7 +35,7 @@ public final class ConnectivityServiceInitializer extends SystemService { // Load JNI libraries used by ConnectivityService and its dependencies System.loadLibrary("service-connectivity"); // TODO: Define formal APIs to get the needed services. - mConnectivity = new ConnectivityService(context, getNetworkStatsService()); + mConnectivity = new ConnectivityService(context); } @Override @@ -46,9 +44,4 @@ public final class ConnectivityServiceInitializer extends SystemService { publishBinderService(Context.CONNECTIVITY_SERVICE, mConnectivity, /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL); } - - private INetworkStatsService getNetworkStatsService() { - return INetworkStatsService.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); - } } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 233a50d417ad..c5233f43dcb9 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -24,6 +24,10 @@ import static android.app.AppOpsManager.OP_LEGACY_STORAGE; import static android.app.AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE; import static android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES; import static android.app.AppOpsManager.OP_WRITE_EXTERNAL_STORAGE; +import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; +import static android.app.PendingIntent.FLAG_IMMUTABLE; +import static android.app.PendingIntent.FLAG_ONE_SHOT; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.pm.PackageManager.MATCH_ANY_USER; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; @@ -55,6 +59,7 @@ import android.app.AnrController; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.KeyguardManager; +import android.app.PendingIntent; import android.app.admin.SecurityLog; import android.app.usage.StorageStatsManager; import android.content.BroadcastReceiver; @@ -3371,6 +3376,54 @@ class StorageManagerService extends IStorageManager.Stub } } + /** + * Returns PendingIntent which can be used by Apps with MANAGE_EXTERNAL_STORAGE permission + * to launch the manageSpaceActivity of the App specified by packageName. + */ + @Override + @Nullable + public PendingIntent getManageSpaceActivityIntent( + @NonNull String packageName, int requestCode) { + // Only Apps with MANAGE_EXTERNAL_STORAGE permission should be able to call this API. + enforcePermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE); + + // We want to call the manageSpaceActivity as a SystemService and clear identity + // of the calling App + int originalUid = Binder.getCallingUidOrThrow(); + long token = Binder.clearCallingIdentity(); + + try { + ApplicationInfo appInfo = mIPackageManager.getApplicationInfo(packageName, 0, + UserHandle.getUserId(originalUid)); + if (appInfo == null) { + throw new IllegalArgumentException( + "Invalid packageName"); + } + if (appInfo.manageSpaceActivityName == null) { + Log.i(TAG, packageName + " doesn't have a manageSpaceActivity"); + return null; + } + Context targetAppContext = mContext.createPackageContext(packageName, 0); + + Intent intent = new Intent(Intent.ACTION_DEFAULT); + intent.setClassName(packageName, + appInfo.manageSpaceActivityName); + intent.setFlags(FLAG_ACTIVITY_NEW_TASK); + + PendingIntent activity = PendingIntent.getActivity(targetAppContext, requestCode, + intent, + FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE); + return activity; + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalArgumentException( + "packageName not found"); + } finally { + Binder.restoreCallingIdentity(token); + } + } + /** Not thread safe */ class AppFuseMountScope extends AppFuseBridge.MountScope { private boolean mMounted = false; 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/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/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index d41f4c76861e..c0d577cd590d 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -3536,6 +3536,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub boolean didStart = false; InputBindResult res = null; + // We shows the IME when the system allows the IME focused target window to restore the + // IME visibility (e.g. switching to the app task when last time the IME is visible). + if (isTextEditor && mWindowManagerInternal.shouldRestoreImeVisibility(windowToken)) { + if (attribute != null) { + res = startInputUncheckedLocked(cs, inputContext, missingMethods, + attribute, startInputFlags, startInputReason); + showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, + SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY); + } else { + res = InputBindResult.NULL_EDITOR_INFO; + } + return res; + } + switch (softInputMode & LayoutParams.SOFT_INPUT_MASK_STATE) { case LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: if (!sameWindowFocused && (!isTextEditor || !doAutoShow)) { diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java index 3cc32bef0e67..851ea3d01085 100644 --- a/services/core/java/com/android/server/net/LockdownVpnTracker.java +++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java @@ -35,7 +35,6 @@ import android.net.Network; import android.net.NetworkInfo; import android.net.NetworkRequest; import android.os.Handler; -import android.security.KeyStore; import android.text.TextUtils; import android.util.Log; @@ -63,7 +62,6 @@ public class LockdownVpnTracker { @NonNull private final Handler mHandler; @NonNull private final Vpn mVpn; @NonNull private final VpnProfile mProfile; - @NonNull private final KeyStore mKeyStore; @NonNull private final Object mStateLock = new Object(); @@ -132,7 +130,6 @@ public class LockdownVpnTracker { public LockdownVpnTracker(@NonNull Context context, @NonNull Handler handler, - @NonNull KeyStore keyStore, @NonNull Vpn vpn, @NonNull VpnProfile profile) { mContext = Objects.requireNonNull(context); @@ -140,7 +137,6 @@ public class LockdownVpnTracker { mHandler = Objects.requireNonNull(handler); mVpn = Objects.requireNonNull(vpn); mProfile = Objects.requireNonNull(profile); - mKeyStore = Objects.requireNonNull(keyStore); mNotificationManager = mContext.getSystemService(NotificationManager.class); final Intent configIntent = new Intent(ACTION_VPN_SETTINGS); @@ -212,7 +208,7 @@ public class LockdownVpnTracker { // network is the system default. So, if the VPN is up and underlying network // (e.g., wifi) disconnects, CS will inform apps that the VPN's capabilities have // changed to match the new default network (e.g., cell). - mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, network, egressProp); + mVpn.startLegacyVpnPrivileged(mProfile, network, egressProp); } catch (IllegalStateException e) { mAcceptedEgressIface = null; Log.e(TAG, "Failed to start VPN", e); diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 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/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/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 f00da5cc7a56..ee980317c04e 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -4424,6 +4424,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } mVisibleRequested = visible; + setInsetsFrozen(!visible); if (app != null) { mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */); } 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..84bc8538f163 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(); } } 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/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index dd4ee877c05b..000889a16d4c 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, diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index e183ea0d81ab..7450782364f4 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -66,7 +66,7 @@ public abstract class WindowManagerInternal { /** * Is trace enabled or not. */ - boolean isEnabled(); + boolean isAccessibilityTracingEnabled(); /** * Add an accessibility trace entry. @@ -667,4 +667,13 @@ public abstract class WindowManagerInternal { * Moves the {@link WindowToken} {@code binder} to the display specified by {@code displayId}. */ public abstract void moveWindowTokenToDisplay(IBinder binder, int displayId); + + /** + * Checks whether the given window should restore the last IME visibility. + * + * @param imeTargetWindowToken The token of the (IME target) window + * @return {@code true} when the system allows to restore the IME visibility, + * {@code false} otherwise. + */ + public abstract boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a4c752029da0..c9e1605f7f0d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -8039,6 +8039,11 @@ public class WindowManagerService extends IWindowManager.Stub return dc.getImeTarget(IME_TARGET_LAYERING).getWindow().getName(); } } + + @Override + public boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) { + return WindowManagerService.this.shouldRestoreImeVisibility(imeTargetWindowToken); + } } void registerAppFreezeListener(AppFreezeListener listener) { @@ -8696,6 +8701,22 @@ public class WindowManagerService extends IWindowManager.Stub boundsInWindow, hashAlgorithm, callback); } + boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) { + synchronized (mGlobalLock) { + final WindowState imeTargetWindow = mWindowMap.get(imeTargetWindowToken); + if (imeTargetWindow == null) { + return false; + } + final Task imeTargetWindowTask = imeTargetWindow.getTask(); + if (imeTargetWindowTask == null) { + return false; + } + final TaskSnapshot snapshot = mAtmService.getTaskSnapshot(imeTargetWindowTask.mTaskId, + false /* isLowResolution */); + return snapshot != null && snapshot.hasImeSurface(); + } + } + private void sendDisplayHashError(RemoteCallback callback, int errorCode) { Bundle bundle = new Bundle(); bundle.putInt(EXTRA_DISPLAY_HASH_ERROR_CODE, errorCode); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index a35f4dea643a..fec715ec7f79 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -797,7 +797,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, diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index e87ee918e0f0..066cc1e105ab 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -736,4 +736,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/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/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/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/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/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/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/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..f0d10d20f3bd 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 { 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/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; } |