Merge "AppUriAuthenticationPolicy and UrisToAliases equals and hashcode" into sc-dev
diff --git a/Android.bp b/Android.bp
index 20ca1b7..9374c01 100644
--- a/Android.bp
+++ b/Android.bp
@@ -581,9 +581,11 @@
"android.hardware.vibrator-V1.1-java",
"android.hardware.vibrator-V1.2-java",
"android.hardware.vibrator-V1.3-java",
+ "android.hardware.vibrator-V2-java",
"android.security.apc-java",
"android.security.authorization-java",
"android.security.usermanager-java",
+ "android.security.vpnprofilestore-java",
"android.system.keystore2-V1-java",
"android.system.suspend.control.internal-java",
"cameraprotosnano",
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java
index e83c64c..5a45961 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 @@
@RunWith(AndroidJUnit4.class)
public class TypefaceCreatePerfTest {
// A font file name in asset directory.
- private static final String TEST_FONT_NAME = "DancingScript-Regular.ttf";
+ private static final String TEST_FONT_NAME = "DancingScript.ttf";
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java b/apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java
index 34ba753b3..862d8b7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java
@@ -25,7 +25,9 @@
public interface JobCompletedListener {
/**
* Callback for when a job is completed.
+ *
+ * @param stopReason The stop reason provided to JobParameters.
* @param needsReschedule Whether the implementing class should reschedule this job.
*/
- void onJobCompletedLocked(JobStatus jobStatus, boolean needsReschedule);
+ void onJobCompletedLocked(JobStatus jobStatus, int stopReason, boolean needsReschedule);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index e8e2c27..0308d68 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
.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 @@
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 @@
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 @@
// 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 @@
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 @@
mNumUnspecializedRemaining = mConfigMaxTotal;
// Step 1
- int runTop = mNumRunningJobs.get(WORK_TYPE_TOP);
- int resTop = runTop;
- mNumUnspecializedRemaining -= resTop;
- int runEj = mNumRunningJobs.get(WORK_TYPE_EJ);
- int resEj = runEj;
- mNumUnspecializedRemaining -= resEj;
- int runBg = mNumRunningJobs.get(WORK_TYPE_BG);
- int resBg = runBg;
- mNumUnspecializedRemaining -= resBg;
- int runBgUser = mNumRunningJobs.get(WORK_TYPE_BGUSER);
- int resBgUser = runBgUser;
- mNumUnspecializedRemaining -= resBgUser;
+ for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
+ int run = mNumRunningJobs.get(workType);
+ mRecycledReserved.put(workType, run);
+ mNumUnspecializedRemaining -= run;
+ }
// Step 2
- final int numTop = runTop + mNumPendingJobs.get(WORK_TYPE_TOP);
- int fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining,
- Math.min(numTop, mConfigNumReservedSlots.get(WORK_TYPE_TOP) - resTop)));
- resTop += fillUp;
- mNumUnspecializedRemaining -= fillUp;
- final int numEj = runEj + mNumPendingJobs.get(WORK_TYPE_EJ);
- fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining,
- Math.min(numEj, mConfigNumReservedSlots.get(WORK_TYPE_EJ) - resEj)));
- resEj += fillUp;
- mNumUnspecializedRemaining -= fillUp;
- final int numBg = runBg + mNumPendingJobs.get(WORK_TYPE_BG);
- fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining,
- Math.min(numBg, mConfigNumReservedSlots.get(WORK_TYPE_BG) - resBg)));
- resBg += fillUp;
- mNumUnspecializedRemaining -= fillUp;
- final int numBgUser = runBgUser + mNumPendingJobs.get(WORK_TYPE_BGUSER);
- fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining,
- Math.min(numBgUser,
- mConfigNumReservedSlots.get(WORK_TYPE_BGUSER) - resBgUser)));
- resBgUser += fillUp;
- mNumUnspecializedRemaining -= fillUp;
+ for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
+ int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType);
+ int res = mRecycledReserved.get(workType);
+ int fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining,
+ Math.min(num, mConfigNumReservedSlots.get(workType) - res)));
+ res += fillUp;
+ mRecycledReserved.put(workType, res);
+ mNumUnspecializedRemaining -= fillUp;
+ }
// Step 3
- int unspecializedAssigned = Math.max(0,
- Math.min(mNumUnspecializedRemaining,
- Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP), numTop) - resTop));
- mNumActuallyReservedSlots.put(WORK_TYPE_TOP, resTop + unspecializedAssigned);
- mNumUnspecializedRemaining -= unspecializedAssigned;
-
- unspecializedAssigned = Math.max(0,
- Math.min(mNumUnspecializedRemaining,
- Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_EJ), numEj) - resEj));
- mNumActuallyReservedSlots.put(WORK_TYPE_EJ, resEj + unspecializedAssigned);
- mNumUnspecializedRemaining -= unspecializedAssigned;
-
- unspecializedAssigned = Math.max(0,
- Math.min(mNumUnspecializedRemaining,
- Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG), numBg) - resBg));
- mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg + unspecializedAssigned);
- mNumUnspecializedRemaining -= unspecializedAssigned;
-
- unspecializedAssigned = Math.max(0,
- Math.min(mNumUnspecializedRemaining,
- Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BGUSER), numBgUser)
- - resBgUser));
- mNumActuallyReservedSlots.put(WORK_TYPE_BGUSER, resBgUser + unspecializedAssigned);
- mNumUnspecializedRemaining -= unspecializedAssigned;
+ for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
+ int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType);
+ int res = mRecycledReserved.get(workType);
+ int unspecializedAssigned = Math.max(0,
+ Math.min(mNumUnspecializedRemaining,
+ Math.min(mConfigAbsoluteMaxSlots.get(workType), num) - res));
+ mNumActuallyReservedSlots.put(workType, res + unspecializedAssigned);
+ mNumUnspecializedRemaining -= unspecializedAssigned;
+ }
}
int canJobStart(int workTypes) {
- if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) {
- final int maxAllowed = Math.min(
- mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP),
- mNumActuallyReservedSlots.get(WORK_TYPE_TOP) + mNumUnspecializedRemaining);
- if (mNumRunningJobs.get(WORK_TYPE_TOP) + mNumStartingJobs.get(WORK_TYPE_TOP)
- < maxAllowed) {
- return WORK_TYPE_TOP;
- }
- }
- if ((workTypes & WORK_TYPE_EJ) == WORK_TYPE_EJ) {
- final int maxAllowed = Math.min(
- mConfigAbsoluteMaxSlots.get(WORK_TYPE_EJ),
- mNumActuallyReservedSlots.get(WORK_TYPE_EJ) + mNumUnspecializedRemaining);
- if (mNumRunningJobs.get(WORK_TYPE_EJ) + mNumStartingJobs.get(WORK_TYPE_EJ)
- < maxAllowed) {
- return WORK_TYPE_EJ;
- }
- }
- if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) {
- final int maxAllowed = Math.min(
- mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG),
- mNumActuallyReservedSlots.get(WORK_TYPE_BG) + mNumUnspecializedRemaining);
- if (mNumRunningJobs.get(WORK_TYPE_BG) + mNumStartingJobs.get(WORK_TYPE_BG)
- < maxAllowed) {
- return WORK_TYPE_BG;
- }
- }
- if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) {
- final int maxAllowed = Math.min(
- mConfigAbsoluteMaxSlots.get(WORK_TYPE_BGUSER),
- mNumActuallyReservedSlots.get(WORK_TYPE_BGUSER)
- + mNumUnspecializedRemaining);
- if (mNumRunningJobs.get(WORK_TYPE_BGUSER) + mNumStartingJobs.get(WORK_TYPE_BGUSER)
- < maxAllowed) {
- return WORK_TYPE_BGUSER;
+ for (int workType = 1; workType <= workTypes; workType <<= 1) {
+ if ((workTypes & workType) == workType) {
+ final int maxAllowed = Math.min(
+ mConfigAbsoluteMaxSlots.get(workType),
+ mNumActuallyReservedSlots.get(workType) + mNumUnspecializedRemaining);
+ if (mNumRunningJobs.get(workType) + mNumStartingJobs.get(workType)
+ < maxAllowed) {
+ return workType;
+ }
}
}
return WORK_TYPE_NONE;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 8bb03e9..515cb74 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1745,11 +1745,12 @@
* A job just finished executing. We fetch the
* {@link com.android.server.job.controllers.JobStatus} from the store and depending on
* whether we want to reschedule we re-add it to the controllers.
- * @param jobStatus Completed job.
+ *
+ * @param jobStatus Completed job.
* @param needsReschedule Whether the implementing class should reschedule this job.
*/
@Override
- public void onJobCompletedLocked(JobStatus jobStatus, boolean needsReschedule) {
+ public void onJobCompletedLocked(JobStatus jobStatus, int stopReason, boolean needsReschedule) {
if (DEBUG) {
Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
}
@@ -1767,6 +1768,11 @@
// we stop it.
final JobStatus rescheduledJob = needsReschedule
? getRescheduleJobForFailureLocked(jobStatus) : null;
+ if (rescheduledJob != null
+ && (stopReason == JobParameters.REASON_TIMEOUT
+ || stopReason == JobParameters.REASON_PREEMPT)) {
+ rescheduledJob.disallowRunInBatterySaverAndDoze();
+ }
// Do not write back immediately if this is a periodic job. The job may get lost if system
// shuts down before it is added back.
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 2a23d60..9673449 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 @@
// another binding flag for that.
bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
| Context.BIND_ALMOST_PERCEPTIBLE
- | Context.BIND_ALLOW_NETWORK_ACCESS;
+ | Context.BIND_ALLOW_NETWORK_ACCESS
+ | Context.BIND_NOT_APP_COMPONENT_USAGE;
} else {
bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
- | Context.BIND_NOT_PERCEPTIBLE;
+ | Context.BIND_NOT_PERCEPTIBLE
+ | Context.BIND_NOT_APP_COMPONENT_USAGE;
}
binding = mContext.bindServiceAsUser(intent, this, bindFlags,
UserHandle.of(job.getUserId()));
@@ -379,8 +381,8 @@
}
boolean isWithinExecutionGuaranteeTime() {
- return mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis
- < sElapsedRealtimeClock.millis();
+ return sElapsedRealtimeClock.millis()
+ < mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis;
}
@GuardedBy("mLock")
@@ -848,11 +850,12 @@
}
applyStoppedReasonLocked(reason);
completedJob = mRunningJob;
- mJobPackageTracker.noteInactive(completedJob, mParams.getStopReason(), reason);
+ final int stopReason = mParams.getStopReason();
+ mJobPackageTracker.noteInactive(completedJob, stopReason, reason);
FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
completedJob.getSourceUid(), null, completedJob.getBatteryName(),
FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__FINISHED,
- mParams.getStopReason(), completedJob.getStandbyBucket(), completedJob.getJobId(),
+ stopReason, completedJob.getStandbyBucket(), completedJob.getJobId(),
completedJob.hasChargingConstraint(),
completedJob.hasBatteryNotLowConstraint(),
completedJob.hasStorageNotLowConstraint(),
@@ -863,7 +866,7 @@
completedJob.hasContentTriggerConstraint());
try {
mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(),
- mParams.getStopReason());
+ stopReason);
} catch (RemoteException e) {
// Whatever.
}
@@ -882,7 +885,7 @@
service = null;
mAvailable = true;
removeOpTimeOutLocked();
- mCompletedListener.onJobCompletedLocked(completedJob, reschedule);
+ mCompletedListener.onJobCompletedLocked(completedJob, stopReason, reschedule);
mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index eaf8f4d..aa8d98c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -954,7 +954,7 @@
appBucket, sourceTag,
elapsedRuntimes.first, elapsedRuntimes.second,
lastSuccessfulRunTime, lastFailedRunTime,
- (rtcIsGood) ? null : rtcRuntimes, internalFlags);
+ (rtcIsGood) ? null : rtcRuntimes, internalFlags, /* dynamicConstraints */ 0);
return js;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index 192f5e6..79ef321 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -64,7 +64,7 @@
* when the app is temp whitelisted or in the foreground.
*/
private final ArraySet<JobStatus> mAllowInIdleJobs;
- private final SparseBooleanArray mForegroundUids;
+ private final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
private final DeviceIdleUpdateFunctor mDeviceIdleUpdateFunctor;
private final DeviceIdleJobsDelayHandler mHandler;
private final PowerManager mPowerManager;
@@ -77,7 +77,6 @@
private int[] mDeviceIdleWhitelistAppIds;
private int[] mPowerSaveTempWhitelistAppIds;
- // onReceive
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -120,6 +119,10 @@
}
};
+ /** Criteria for whether or not we should a job's rush evaluation when the device exits Doze. */
+ private final Predicate<JobStatus> mShouldRushEvaluation = (jobStatus) ->
+ jobStatus.isRequestedExpeditedJob() || mForegroundUids.get(jobStatus.getSourceUid());
+
public DeviceIdleJobsController(JobSchedulerService service) {
super(service);
@@ -133,7 +136,6 @@
mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
mDeviceIdleUpdateFunctor = new DeviceIdleUpdateFunctor();
mAllowInIdleJobs = new ArraySet<>();
- mForegroundUids = new SparseBooleanArray();
final IntentFilter filter = new IntentFilter();
filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
@@ -156,14 +158,9 @@
mHandler.removeMessages(PROCESS_BACKGROUND_JOBS);
mService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
} else {
- // When coming out of doze, process all foreground uids immediately, while others
- // will be processed after a delay of 3 seconds.
- for (int i = 0; i < mForegroundUids.size(); i++) {
- if (mForegroundUids.valueAt(i)) {
- mService.getJobStore().forEachJobForSourceUid(
- mForegroundUids.keyAt(i), mDeviceIdleUpdateFunctor);
- }
- }
+ // When coming out of doze, process all foreground uids and EJs immediately,
+ // while others will be processed after a delay of 3 seconds.
+ mService.getJobStore().forEachJob(mShouldRushEvaluation, mDeviceIdleUpdateFunctor);
mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY);
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 5bdeb38..bad8dc1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -91,6 +91,12 @@
static final int CONSTRAINT_WITHIN_EXPEDITED_QUOTA = 1 << 23; // Implicit constraint
static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
+ // The following set of dynamic constraints are for specific use cases (as explained in their
+ // relative naming and comments). Right now, they apply different constraints, which is fine,
+ // but if in the future, we have overlapping dynamic constraint sets, removing one constraint
+ // set may accidentally remove a constraint applied by another dynamic set.
+ // TODO: properly handle overlapping dynamic constraint sets
+
/**
* The additional set of dynamic constraints that must be met if the job's effective bucket is
* {@link JobSchedulerService#RESTRICTED_INDEX}. Connectivity can be ignored if the job doesn't
@@ -103,6 +109,13 @@
| CONSTRAINT_IDLE;
/**
+ * The additional set of dynamic constraints that must be met if this is an expedited job that
+ * had a long enough run while the device was Dozing or in battery saver.
+ */
+ private static final int DYNAMIC_EXPEDITED_DEFERRAL_CONSTRAINTS =
+ CONSTRAINT_DEVICE_NOT_DOZING | CONSTRAINT_BACKGROUND_NOT_RESTRICTED;
+
+ /**
* Standard media URIs that contain the media files that might be important to the user.
* @see #mHasMediaBackupExemption
*/
@@ -426,7 +439,8 @@
private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
int sourceUserId, int standbyBucket, String tag, int numFailures,
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
- long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags) {
+ long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags,
+ int dynamicConstraints) {
this.job = job;
this.callingUid = callingUid;
this.standbyBucket = standbyBucket;
@@ -487,6 +501,7 @@
}
this.requiredConstraints = requiredConstraints;
mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST;
+ addDynamicConstraints(dynamicConstraints);
mReadyNotDozing = canRunInDoze();
if (standbyBucket == RESTRICTED_INDEX) {
addDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS);
@@ -521,7 +536,7 @@
jobStatus.getSourceTag(), jobStatus.getNumFailures(),
jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(),
- jobStatus.getInternalFlags());
+ jobStatus.getInternalFlags(), jobStatus.mDynamicConstraints);
mPersistedUtcTimes = jobStatus.mPersistedUtcTimes;
if (jobStatus.mPersistedUtcTimes != null) {
if (DEBUG) {
@@ -543,12 +558,12 @@
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
long lastSuccessfulRunTime, long lastFailedRunTime,
Pair<Long, Long> persistedExecutionTimesUTC,
- int innerFlags) {
+ int innerFlags, int dynamicConstraints) {
this(job, callingUid, sourcePkgName, sourceUserId,
standbyBucket,
sourceTag, 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
- lastSuccessfulRunTime, lastFailedRunTime, innerFlags);
+ lastSuccessfulRunTime, lastFailedRunTime, innerFlags, dynamicConstraints);
// Only during initial inflation do we record the UTC-timebase execution bounds
// read from the persistent store. If we ever have to recreate the JobStatus on
@@ -572,7 +587,8 @@
rescheduling.getStandbyBucket(),
rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis,
newLatestRuntimeElapsedMillis,
- lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags());
+ lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags(),
+ rescheduling.mDynamicConstraints);
}
/**
@@ -609,7 +625,7 @@
standbyBucket, tag, 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
- /*innerFlags=*/ 0);
+ /*innerFlags=*/ 0, /* dynamicConstraints */ 0);
}
public void enqueueWorkLocked(JobWorkItem work) {
@@ -1083,12 +1099,15 @@
* in Doze.
*/
public boolean canRunInDoze() {
- return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0 || shouldTreatAsExpeditedJob();
+ return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0
+ || (shouldTreatAsExpeditedJob()
+ && (mDynamicConstraints & CONSTRAINT_DEVICE_NOT_DOZING) == 0);
}
boolean canRunInBatterySaver() {
return (getInternalFlags() & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0
- || shouldTreatAsExpeditedJob();
+ || (shouldTreatAsExpeditedJob()
+ && (mDynamicConstraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) == 0);
}
boolean shouldIgnoreNetworkBlocking() {
@@ -1245,6 +1264,14 @@
}
/**
+ * Add additional constraints to prevent this job from running when doze or battery saver are
+ * active.
+ */
+ public void disallowRunInBatterySaverAndDoze() {
+ addDynamicConstraints(DYNAMIC_EXPEDITED_DEFERRAL_CONSTRAINTS);
+ }
+
+ /**
* Indicates that this job cannot run without the specified constraints. This is evaluated
* separately from the job's explicitly requested constraints and MUST be satisfied before
* the job can run if the app doesn't have quota.
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index b18a22b..1d6f20d 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -47,6 +47,7 @@
static_libs: [
"exoplayer2-extractor",
"mediatranscoding_aidl_interface-java",
+ "modules-annotation-minsdk",
"modules-utils-build",
],
jarjar_rules: "jarjar_rules.txt",
@@ -108,7 +109,7 @@
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 eb71fdd..91489dc 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 9ec25fe..f39bcfb 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.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 @@
* 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 6397ba3..7697359 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.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.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 @@
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 @@
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 @@
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 @@
final ControllerInfo controllerInfo = new ControllerInfo(
remoteUserInfo,
- mCommunicationManager.isTrustedForMediaControl(remoteUserInfo),
+ isTrustedForMediaControl(remoteUserInfo),
controller,
connectionHints);
mCallbackExecutor.execute(() -> {
@@ -606,9 +624,15 @@
// 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 2c388ad..a06f994 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -11768,6 +11768,7 @@
method public boolean isResourceOverlay();
method public boolean isVirtualPreload();
method public CharSequence loadDescription(android.content.pm.PackageManager);
+ field public static final int CATEGORY_ACCESSIBILITY = 8; // 0x8
field public static final int CATEGORY_AUDIO = 1; // 0x1
field public static final int CATEGORY_GAME = 0; // 0x0
field public static final int CATEGORY_IMAGE = 3; // 0x3
@@ -12947,6 +12948,27 @@
}
+package android.content.pm.verify.domain {
+
+ public final class DomainVerificationManager {
+ method @Nullable public android.content.pm.verify.domain.DomainVerificationUserState getDomainVerificationUserState(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ }
+
+ public final class DomainVerificationUserState implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getHostToStateMap();
+ method @NonNull public String getPackageName();
+ method @NonNull public android.os.UserHandle getUser();
+ method @NonNull public boolean isLinkHandlingAllowed();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationUserState> CREATOR;
+ field public static final int DOMAIN_STATE_NONE = 0; // 0x0
+ field public static final int DOMAIN_STATE_SELECTED = 1; // 0x1
+ field public static final int DOMAIN_STATE_VERIFIED = 2; // 0x2
+ }
+
+}
+
package android.content.res {
public class AssetFileDescriptor implements java.io.Closeable android.os.Parcelable {
@@ -17535,6 +17557,9 @@
public class BiometricManager {
method @Deprecated @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate();
method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate(int);
+ method @Nullable @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public CharSequence getButtonLabel(int);
+ method @Nullable @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public CharSequence getPromptMessage(int);
+ method @Nullable @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public CharSequence getSettingName(int);
field public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; // 0x1
field public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11; // 0xb
field public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12; // 0xc
@@ -20300,6 +20325,10 @@
field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_BIT_WIDTH;
field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_CHANNEL_MASK;
field @NonNull public static final android.media.AudioMetadata.Key<java.lang.String> KEY_MIME;
+ field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_PRESENTATION_CONTENT_CLASSIFIER;
+ field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_PRESENTATION_ID;
+ field @NonNull public static final android.media.AudioMetadata.Key<java.lang.String> KEY_PRESENTATION_LANGUAGE;
+ field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_PROGRAM_ID;
field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_SAMPLE_RATE;
}
@@ -20354,6 +20383,15 @@
method public boolean hasAudioDescription();
method public boolean hasDialogueEnhancement();
method public boolean hasSpokenSubtitles();
+ field public static final int CONTENT_COMMENTARY = 5; // 0x5
+ field public static final int CONTENT_DIALOG = 4; // 0x4
+ field public static final int CONTENT_EMERGENCY = 6; // 0x6
+ field public static final int CONTENT_HEARING_IMPAIRED = 3; // 0x3
+ field public static final int CONTENT_MAIN = 0; // 0x0
+ field public static final int CONTENT_MUSIC_AND_EFFECTS = 1; // 0x1
+ field public static final int CONTENT_UNKNOWN = -1; // 0xffffffff
+ field public static final int CONTENT_VISUALLY_IMPAIRED = 2; // 0x2
+ field public static final int CONTENT_VOICEOVER = 7; // 0x7
field public static final int MASTERED_FOR_3D = 3; // 0x3
field public static final int MASTERED_FOR_HEADPHONE = 4; // 0x4
field public static final int MASTERED_FOR_STEREO = 1; // 0x1
@@ -26710,7 +26748,22 @@
public class VcnManager {
method @RequiresPermission("carrier privileges") public void clearVcnConfig(@NonNull android.os.ParcelUuid) throws java.io.IOException;
+ method public void registerVcnStatusCallback(@NonNull android.os.ParcelUuid, @NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnStatusCallback);
method @RequiresPermission("carrier privileges") public void setVcnConfig(@NonNull android.os.ParcelUuid, @NonNull android.net.vcn.VcnConfig) throws java.io.IOException;
+ method public void unregisterVcnStatusCallback(@NonNull android.net.vcn.VcnManager.VcnStatusCallback);
+ field public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1; // 0x1
+ field public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0; // 0x0
+ field public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2; // 0x2
+ field public static final int VCN_STATUS_CODE_ACTIVE = 2; // 0x2
+ field public static final int VCN_STATUS_CODE_INACTIVE = 1; // 0x1
+ field public static final int VCN_STATUS_CODE_NOT_CONFIGURED = 0; // 0x0
+ field public static final int VCN_STATUS_CODE_SAFE_MODE = 3; // 0x3
+ }
+
+ public abstract static class VcnManager.VcnStatusCallback {
+ ctor public VcnManager.VcnStatusCallback();
+ method public abstract void onGatewayConnectionError(@NonNull int[], int, @Nullable Throwable);
+ method public abstract void onVcnStatusChanged(int);
}
}
@@ -30260,6 +30313,7 @@
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;
@@ -30418,19 +30472,10 @@
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();
@@ -31855,6 +31900,7 @@
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();
@@ -50969,6 +51015,7 @@
method public void onDisplayHashError(int);
method public void onDisplayHashResult(@NonNull android.view.displayhash.DisplayHash);
field public static final int DISPLAY_HASH_ERROR_INVALID_BOUNDS = -2; // 0xfffffffe
+ field public static final int DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM = -5; // 0xfffffffb
field public static final int DISPLAY_HASH_ERROR_MISSING_WINDOW = -3; // 0xfffffffd
field public static final int DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN = -4; // 0xfffffffc
field public static final int DISPLAY_HASH_ERROR_UNKNOWN = -1; // 0xffffffff
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 91ccefe..ad2942a 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -35,7 +35,7 @@
}
public final class PendingIntent implements android.os.Parcelable {
- method @Nullable @RequiresPermission(android.Manifest.permission.GET_INTENT_SENDER_INTENT) public java.util.List<android.content.pm.ResolveInfo> queryIntentComponents(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.GET_INTENT_SENDER_INTENT) public java.util.List<android.content.pm.ResolveInfo> queryIntentComponents(int);
}
public class StatusBarManager {
@@ -241,8 +241,8 @@
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 59bc9bd..bc490af 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -154,6 +154,7 @@
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";
@@ -357,6 +358,7 @@
field public static final int config_systemGallery = 17039399; // 0x1040027
field public static final int config_systemShell = 17039402; // 0x104002a
field public static final int config_systemSpeechRecognizer = 17039406; // 0x104002e
+ field public static final int config_systemWifiCoexManager = 17039407; // 0x104002f
}
public static final class R.style {
@@ -423,6 +425,9 @@
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 +541,14 @@
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 +575,7 @@
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);
@@ -1888,11 +1899,18 @@
field public static final int ACCESS_REJECTED = 2; // 0x2
field public static final int ACCESS_UNKNOWN = 0; // 0x0
field public static final String ACTION_SILENCE_MODE_CHANGED = "android.bluetooth.device.action.SILENCE_MODE_CHANGED";
+ field public static final String DEVICE_TYPE_DEFAULT = "Default";
+ field public static final String DEVICE_TYPE_UNTETHERED_HEADSET = "Untethered Headset";
+ field public static final String DEVICE_TYPE_WATCH = "Watch";
field public static final int METADATA_COMPANION_APP = 4; // 0x4
+ field public static final int METADATA_DEVICE_TYPE = 17; // 0x11
field public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; // 0x10
field public static final int METADATA_HARDWARE_VERSION = 3; // 0x3
field public static final int METADATA_IS_UNTETHERED_HEADSET = 6; // 0x6
+ field public static final int METADATA_MAIN_BATTERY = 18; // 0x12
+ field public static final int METADATA_MAIN_CHARGING = 19; // 0x13
field public static final int METADATA_MAIN_ICON = 5; // 0x5
+ field public static final int METADATA_MAIN_LOW_BATTERY_THRESHOLD = 20; // 0x14
field public static final int METADATA_MANUFACTURER_NAME = 0; // 0x0
field public static final int METADATA_MAX_LENGTH = 2048; // 0x800
field public static final int METADATA_MODEL_NAME = 1; // 0x1
@@ -1900,12 +1918,15 @@
field public static final int METADATA_UNTETHERED_CASE_BATTERY = 12; // 0xc
field public static final int METADATA_UNTETHERED_CASE_CHARGING = 15; // 0xf
field public static final int METADATA_UNTETHERED_CASE_ICON = 9; // 0x9
+ field public static final int METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD = 23; // 0x17
field public static final int METADATA_UNTETHERED_LEFT_BATTERY = 10; // 0xa
field public static final int METADATA_UNTETHERED_LEFT_CHARGING = 13; // 0xd
field public static final int METADATA_UNTETHERED_LEFT_ICON = 7; // 0x7
+ field public static final int METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD = 21; // 0x15
field public static final int METADATA_UNTETHERED_RIGHT_BATTERY = 11; // 0xb
field public static final int METADATA_UNTETHERED_RIGHT_CHARGING = 14; // 0xe
field public static final int METADATA_UNTETHERED_RIGHT_ICON = 8; // 0x8
+ field public static final int METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD = 22; // 0x16
}
public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
@@ -2769,13 +2790,12 @@
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationInfo> CREATOR;
}
- public interface DomainVerificationManager {
+ public final class DomainVerificationManager {
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) public android.content.pm.verify.domain.DomainVerificationInfo getDomainVerificationInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
- method @Nullable @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public android.content.pm.verify.domain.DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public java.util.List<android.content.pm.verify.domain.DomainOwner> getOwnersForDomain(@NonNull String);
- method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> getValidVerificationPackageNames();
method public static boolean isStateModifiable(int);
method public static boolean isStateVerified(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> queryValidVerificationPackageNames();
method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationLinkHandlingAllowed(@NonNull String, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public void setDomainVerificationStatus(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationUserSelection(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -2792,18 +2812,8 @@
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationRequest> CREATOR;
}
- public final class DomainVerificationUserSelection implements android.os.Parcelable {
- method public int describeContents();
- method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getHostToStateMap();
+ public final class DomainVerificationUserState implements android.os.Parcelable {
method @NonNull public java.util.UUID getIdentifier();
- method @NonNull public String getPackageName();
- method @NonNull public android.os.UserHandle getUser();
- method @NonNull public boolean isLinkHandlingAllowed();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationUserSelection> CREATOR;
- field public static final int DOMAIN_STATE_NONE = 0; // 0x0
- field public static final int DOMAIN_STATE_SELECTED = 1; // 0x1
- field public static final int DOMAIN_STATE_VERIFIED = 2; // 0x2
}
}
@@ -8825,6 +8835,8 @@
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";
@@ -13039,7 +13051,7 @@
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";
@@ -13066,7 +13078,7 @@
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 {
@@ -13122,7 +13134,7 @@
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
@@ -13285,10 +13297,12 @@
public final class SipMessage implements android.os.Parcelable {
ctor public SipMessage(@NonNull String, @NonNull String, @NonNull byte[]);
method public int describeContents();
+ method @Nullable public String getCallIdParameter();
method @NonNull public byte[] getContent();
method @NonNull public byte[] getEncodedMessage();
method @NonNull public String getHeaderSection();
method @NonNull public String getStartLine();
+ method @Nullable public String getViaBranchParameter();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.SipMessage> CREATOR;
}
@@ -13598,7 +13612,7 @@
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 1e5a6f1..b41f970 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -224,6 +224,9 @@
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 @@
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);
@@ -294,7 +298,7 @@
}
public final class PendingIntent implements android.os.Parcelable {
- method @Nullable @RequiresPermission("android.permission.GET_INTENT_SENDER_INTENT") public java.util.List<android.content.pm.ResolveInfo> queryIntentComponents(int);
+ method @NonNull @RequiresPermission("android.permission.GET_INTENT_SENDER_INTENT") public java.util.List<android.content.pm.ResolveInfo> queryIntentComponents(int);
field @Deprecated public static final int FLAG_MUTABLE_UNAUDITED = 33554432; // 0x2000000
}
@@ -1485,6 +1489,7 @@
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 @@
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 730fce9..e7751b8 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -176,6 +176,8 @@
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 @@
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 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 @@
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 @@
(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 @@
// Propagate Content Capture options
app.setContentCaptureOptions(data.contentCaptureOptions);
+ sendMessage(H.SET_CONTENT_CAPTURE_OPTIONS_CALLBACK, data.appInfo.packageName);
mInitialApplication = app;
@@ -6856,6 +6867,36 @@
}
}
+ 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 160844a..dd1bc7c 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 @@
@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 @@
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 @@
}
/**
+ * 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 @@
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 @@
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 @@
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 @@
"beginTimeMillis must be non negative and lesser than endTimeMillis");
mBeginTimeMillis = beginTimeMillis;
mEndTimeMillis = endTimeMillis;
+ mHistoryFlags = HISTORY_FLAG_AGGREGATE;
}
/**
@@ -4772,11 +4838,25 @@
}
/**
+ * 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 @@
* @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 @@
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 @@
/** @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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
}
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 @@
op.getOpName())) {
mHistoricalOps.removeAt(i);
} else {
- op.filter(scaleFactor);
+ op.filter(historyFilter, scaleFactor, beginTimeMillis, endTimeMillis);
}
}
}
@@ -5909,6 +6020,12 @@
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 @@
return op;
}
-
-
// Code below generated by codegen v1.0.14.
//
// DO NOT MODIFY!
@@ -6121,6 +6236,9 @@
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 @@
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 @@
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 @@
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 @@
}
}
+ 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
writeLongSparseLongArrayToParcel(mAccessCount, parcel);
writeLongSparseLongArrayToParcel(mRejectCount, parcel);
writeLongSparseLongArrayToParcel(mAccessDuration, parcel);
+ writeDiscreteAccessArrayToParcel(mDiscreteAccesses, parcel);
}
@Override
@@ -6447,7 +6723,11 @@
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 @@
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 @@
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 @@
}
/**
+ * 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 @@
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 @@
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 @@
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/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 3a8172e..ef0dcab 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -706,7 +706,7 @@
boolean stopProfile(int userId);
/** Called by PendingIntent.queryIntentComponents() */
- List<ResolveInfo> queryIntentComponentsForIntentSender(in IIntentSender sender, int matchFlags);
+ ParceledListSlice queryIntentComponentsForIntentSender(in IIntentSender sender, int matchFlags);
int getUidProcessCapabilities(int uid, in String callingPackage);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8167622..bc24e97 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -24,6 +24,7 @@
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 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 @@
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 @@
private boolean mRebuildStyledRemoteViews;
private boolean mTintActionButtons;
+ private boolean mTintWithThemeAccent;
private boolean mInNightMode;
/**
@@ -3701,6 +3699,7 @@
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 @@
}
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 @@
contentView.setDrawableTint(
R.id.alerted_icon,
false /* targetBackground */,
- getNeutralColor(p),
+ getHeaderIconColor(p),
PorterDuff.Mode.SRC_ATOP);
}
@@ -5057,10 +5054,9 @@
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 @@
}
/**
- * 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 @@
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 @@
}
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 @@
}
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 @@
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 @@
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 @@
// 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 @@
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 @@
} 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 @@
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 @@
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 @@
}
}
- 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 @@
/**
* 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 @@
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 @@
}
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,7 +6582,11 @@
return R.layout.notification_material_action_tombstone;
}
- private int getBackgroundColor(StandardTemplateParams p) {
+ /**
+ * Gets the background color, with {@link #COLOR_DEFAULT} being a valid return value,
+ * which must be resolved by the caller before being used.
+ */
+ private @ColorInt int getUnresolvedBackgroundColor(StandardTemplateParams p) {
if (isColorized(p)) {
return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : getRawColor(p);
} else {
@@ -6526,33 +6595,17 @@
}
/**
- * 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
+ * Same as {@link #getUnresolvedBackgroundColor(StandardTemplateParams)} except that it
+ * also resolves the default color to the background.
*/
- private int getNeutralColor(StandardTemplateParams p) {
- if (isColorized(p)) {
- return getSecondaryTextColor(p);
- } else {
- return resolveNeutralColor();
- }
- }
-
- /**
- * Same as getBackgroundColor but also resolved the default color to the background.
- * @param p the template params to inflate this with
- */
- 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 @@
*
* @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 @@
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 @@
// 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 @@
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 @@
.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 @@
// Bind some custom CallLayout properties
contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
- mBuilder.isColorized(p)
- ? mBuilder.getPrimaryTextColor(p)
- : mBuilder.resolveContrastColor(p));
+ mBuilder.getSmallIconColor(p));
contentView.setInt(R.id.status_bar_latest_event_content,
- "setNotificationBackgroundColor", mBuilder.resolveBackgroundColor(p));
+ "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p));
contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
mBuilder.mN.mLargeIcon);
contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 1ff64db..e0e9b62 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -73,6 +73,7 @@
per-file Fragment.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file *Task* = file:/services/core/java/com/android/server/wm/OWNERS
per-file Window* = file:/services/core/java/com/android/server/wm/OWNERS
+per-file ConfigurationController.java = file:/services/core/java/com/android/server/wm/OWNERS
# TODO(b/174932174): determine the ownership of KeyguardManager.java
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 549bd4b..009c936 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -26,7 +26,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
-import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemApi.Client;
import android.annotation.TestApi;
@@ -41,6 +40,7 @@
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager.ResolveInfoFlags;
+import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.os.Build;
import android.os.Bundle;
@@ -60,6 +60,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -1239,14 +1240,17 @@
* @param flags MATCH_* flags from {@link android.content.pm.PackageManager}.
* @hide
*/
- @SuppressLint("NullableCollection")
@RequiresPermission(permission.GET_INTENT_SENDER_INTENT)
@SystemApi(client = Client.MODULE_LIBRARIES)
@TestApi
- public @Nullable List<ResolveInfo> queryIntentComponents(@ResolveInfoFlags int flags) {
+ public @NonNull List<ResolveInfo> queryIntentComponents(@ResolveInfoFlags int flags) {
try {
- return ActivityManager.getService()
+ ParceledListSlice<ResolveInfo> parceledList = ActivityManager.getService()
.queryIntentComponentsForIntentSender(mTarget, flags);
+ if (parceledList == null) {
+ return Collections.emptyList();
+ }
+ return parceledList.getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index e16e40b..43c14a9 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -71,7 +71,6 @@
import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
import android.content.pm.verify.domain.DomainVerificationManager;
-import android.content.pm.verify.domain.DomainVerificationManagerImpl;
import android.content.pm.verify.domain.IDomainVerificationManager;
import android.content.res.Resources;
import android.content.rollback.RollbackManagerFrameworkInitializer;
@@ -1422,7 +1421,6 @@
}
});
- // TODO(b/159952358): Only register this service for the domain verification agent?
registerService(Context.DOMAIN_VERIFICATION_SERVICE, DomainVerificationManager.class,
new CachedServiceFetcher<DomainVerificationManager>() {
@Override
@@ -1432,7 +1430,7 @@
Context.DOMAIN_VERIFICATION_SERVICE);
IDomainVerificationManager service =
IDomainVerificationManager.Stub.asInterface(binder);
- return new DomainVerificationManagerImpl(context, service);
+ return new DomainVerificationManager(context, service);
}
});
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index b919bfc..0635bd0 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2997,6 +2997,7 @@
*/
// TODO(b/173541467): should it throw SecurityException if caller is not admin?
public boolean isSafeOperation(@OperationSafetyReason int reason) {
+ throwIfParentInstance("isSafeOperation");
if (mService == null) return false;
try {
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 22492cc..94a4fde0 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -403,7 +403,7 @@
public void onFullBackup(FullBackupDataOutput data) throws IOException {
FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this,
mOperationType);
- if (!isDeviceToDeviceMigration() && !backupScheme.isFullBackupContentEnabled()) {
+ if (!backupScheme.isFullBackupEnabled(data.getTransportFlags())) {
return;
}
@@ -911,7 +911,7 @@
}
FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this, mOperationType);
- if (!bs.isFullBackupContentEnabled()) {
+ if (!bs.isFullRestoreEnabled()) {
if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(FullBackup.TAG_XML_PARSER,
"onRestoreFile \"" + destination.getCanonicalPath()
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 829b6cd..9b543b5 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -99,6 +99,8 @@
public static final String FLAG_REQUIRED_DEVICE_TO_DEVICE_TRANSFER = "deviceToDeviceTransfer";
public static final String FLAG_REQUIRED_FAKE_CLIENT_SIDE_ENCRYPTION =
"fakeClientSideEncryption";
+ private static final String FLAG_DISABLE_IF_NO_ENCRYPTION_CAPABILITIES
+ = "disableIfNoEncryptionCapabilities";
/**
* When this change is enabled, include / exclude rules specified via
@@ -307,6 +309,10 @@
// lazy initialized, only when needed
private StorageVolume[] mVolumes = null;
+ // Properties the transport must have (e.g. encryption) for the operation to go ahead.
+ @Nullable private Integer mRequiredTransportFlags;
+ @Nullable private Boolean mIsUsingNewScheme;
+
/**
* Parse out the semantic domains into the correct physical location.
*/
@@ -453,6 +459,35 @@
}
}
+ boolean isFullBackupEnabled(int transportFlags) {
+ try {
+ if (isUsingNewScheme()) {
+ int requiredTransportFlags = getRequiredTransportFlags();
+ // All bits that are set in requiredTransportFlags must be set in
+ // transportFlags.
+ return (transportFlags & requiredTransportFlags) == requiredTransportFlags;
+ }
+ } catch (IOException | XmlPullParserException e) {
+ Slog.w(TAG, "Failed to interpret the backup scheme: " + e);
+ return false;
+ }
+
+ return isFullBackupContentEnabled();
+ }
+
+ boolean isFullRestoreEnabled() {
+ try {
+ if (isUsingNewScheme()) {
+ return true;
+ }
+ } catch (IOException | XmlPullParserException e) {
+ Slog.w(TAG, "Failed to interpret the backup scheme: " + e);
+ return false;
+ }
+
+ return isFullBackupContentEnabled();
+ }
+
boolean isFullBackupContentEnabled() {
if (mFullBackupContent < 0) {
// android:fullBackupContent="false", bail.
@@ -491,10 +526,30 @@
return mExcludes;
}
+ private synchronized int getRequiredTransportFlags()
+ throws IOException, XmlPullParserException {
+ if (mRequiredTransportFlags == null) {
+ maybeParseBackupSchemeLocked();
+ }
+
+ return mRequiredTransportFlags;
+ }
+
+ private synchronized boolean isUsingNewScheme()
+ throws IOException, XmlPullParserException {
+ if (mIsUsingNewScheme == null) {
+ maybeParseBackupSchemeLocked();
+ }
+
+ return mIsUsingNewScheme;
+ }
+
private void maybeParseBackupSchemeLocked() throws IOException, XmlPullParserException {
// This not being null is how we know that we've tried to parse the xml already.
mIncludes = new ArrayMap<String, Set<PathWithRequiredFlags>>();
mExcludes = new ArraySet<PathWithRequiredFlags>();
+ mRequiredTransportFlags = 0;
+ mIsUsingNewScheme = false;
if (mFullBackupContent == 0 && mDataExtractionRules == 0) {
// No scheme specified via either new or legacy config, will copy everything.
@@ -535,12 +590,14 @@
}
if (!mExcludes.isEmpty() || !mIncludes.isEmpty()) {
// Found configuration in the new config, we will use it.
+ mIsUsingNewScheme = true;
return;
}
}
if (operationType == OperationType.MIGRATION
&& CompatChanges.isChangeEnabled(IGNORE_FULL_BACKUP_CONTENT_IN_D2D)) {
+ mIsUsingNewScheme = true;
return;
}
@@ -584,13 +641,24 @@
continue;
}
- // TODO(b/180523028): Parse required attributes for rules (e.g. encryption).
+ parseRequiredTransportFlags(parser, configSection);
parseRules(parser, excludes, includes, Optional.of(0), configSection);
}
logParsingResults(excludes, includes);
}
+ private void parseRequiredTransportFlags(XmlPullParser parser,
+ @ConfigSection String configSection) {
+ if (ConfigSection.CLOUD_BACKUP.equals(configSection)) {
+ String encryptionAttribute = parser.getAttributeValue(/* namespace */ null,
+ FLAG_DISABLE_IF_NO_ENCRYPTION_CAPABILITIES);
+ if ("true".equals(encryptionAttribute)) {
+ mRequiredTransportFlags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
+ }
+ }
+ }
+
@VisibleForTesting
public void parseBackupSchemeFromXmlLocked(XmlPullParser parser,
Set<PathWithRequiredFlags> excludes,
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 081f4fd..e6a4656 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -334,10 +334,19 @@
public static final int LOCUS_ID_SET = 30;
/**
+ * An event type denoting that a component in the package has been used (e.g. broadcast
+ * receiver, service, content provider). This generally matches up with usage that would
+ * cause an app to leave force stop. The component itself is not provided as we are only
+ * interested in whether the package is used, not the component itself.
+ * @hide
+ */
+ public static final int APP_COMPONENT_USED = 31;
+
+ /**
* Keep in sync with the greatest event type value.
* @hide
*/
- public static final int MAX_EVENT_TYPE = 30;
+ public static final int MAX_EVENT_TYPE = 31;
/** @hide */
public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0;
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index e7661db..ec94faa 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -374,6 +374,35 @@
public static final String ACTION_SDP_RECORD =
"android.bluetooth.device.action.SDP_RECORD";
+ /** @hide */
+ @IntDef(prefix = "METADATA_", value = {
+ METADATA_MANUFACTURER_NAME,
+ METADATA_MODEL_NAME,
+ METADATA_SOFTWARE_VERSION,
+ METADATA_HARDWARE_VERSION,
+ METADATA_COMPANION_APP,
+ METADATA_MAIN_ICON,
+ METADATA_IS_UNTETHERED_HEADSET,
+ METADATA_UNTETHERED_LEFT_ICON,
+ METADATA_UNTETHERED_RIGHT_ICON,
+ METADATA_UNTETHERED_CASE_ICON,
+ METADATA_UNTETHERED_LEFT_BATTERY,
+ METADATA_UNTETHERED_RIGHT_BATTERY,
+ METADATA_UNTETHERED_CASE_BATTERY,
+ METADATA_UNTETHERED_LEFT_CHARGING,
+ METADATA_UNTETHERED_RIGHT_CHARGING,
+ METADATA_UNTETHERED_CASE_CHARGING,
+ METADATA_ENHANCED_SETTINGS_UI_URI,
+ METADATA_DEVICE_TYPE,
+ METADATA_MAIN_BATTERY,
+ METADATA_MAIN_CHARGING,
+ METADATA_MAIN_LOW_BATTERY_THRESHOLD,
+ METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD,
+ METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD,
+ METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MetadataKey{}
+
/**
* Maximum length of a metadata entry, this is to avoid exploding Bluetooth
* disk usage
@@ -523,6 +552,89 @@
public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16;
/**
+ * Type of the Bluetooth device, must be within the list of
+ * BluetoothDevice.DEVICE_TYPE_*
+ * Data type should be {@String} as {@link Byte} array.
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_DEVICE_TYPE = 17;
+
+ /**
+ * Battery level of the Bluetooth device, use when the Bluetooth device
+ * does not support HFP battery indicator.
+ * Data type should be {@String} as {@link Byte} array.
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_MAIN_BATTERY = 18;
+
+ /**
+ * Whether the device is charging.
+ * Data type should be {@String} as {@link Byte} array.
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_MAIN_CHARGING = 19;
+
+ /**
+ * The battery threshold of the Bluetooth device to show low battery icon.
+ * Data type should be {@String} as {@link Byte} array.
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_MAIN_LOW_BATTERY_THRESHOLD = 20;
+
+ /**
+ * The battery threshold of the left headset to show low battery icon.
+ * Data type should be {@String} as {@link Byte} array.
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD = 21;
+
+ /**
+ * The battery threshold of the right headset to show low battery icon.
+ * Data type should be {@String} as {@link Byte} array.
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD = 22;
+
+ /**
+ * The battery threshold of the case to show low battery icon.
+ * Data type should be {@String} as {@link Byte} array.
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD = 23;
+
+ /**
+ * Device type which is used in METADATA_DEVICE_TYPE
+ * Indicates this Bluetooth device is a standard Bluetooth accessory or
+ * not listed in METADATA_DEVICE_TYPE_*.
+ * @hide
+ */
+ @SystemApi
+ public static final String DEVICE_TYPE_DEFAULT = "Default";
+
+ /**
+ * Device type which is used in METADATA_DEVICE_TYPE
+ * Indicates this Bluetooth device is a watch.
+ * @hide
+ */
+ @SystemApi
+ public static final String DEVICE_TYPE_WATCH = "Watch";
+
+ /**
+ * Device type which is used in METADATA_DEVICE_TYPE
+ * Indicates this Bluetooth device is an untethered headset.
+ * @hide
+ */
+ @SystemApi
+ public static final String DEVICE_TYPE_UNTETHERED_HEADSET = "Untethered Headset";
+
+ /**
* Broadcast Action: This intent is used to broadcast the {@link UUID}
* wrapped as a {@link android.os.ParcelUuid} of the remote device after it
* has been fetched. This intent is sent only when the UUIDs of the remote
@@ -2316,7 +2428,7 @@
*/
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
- public boolean setMetadata(int key, @NonNull byte[] value) {
+ public boolean setMetadata(@MetadataKey int key, @NonNull byte[] value) {
final IBluetooth service = sService;
if (service == null) {
Log.e(TAG, "Bluetooth is not enabled. Cannot set metadata");
@@ -2344,7 +2456,7 @@
@SystemApi
@Nullable
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
- public byte[] getMetadata(int key) {
+ public byte[] getMetadata(@MetadataKey int key) {
final IBluetooth service = sService;
if (service == null) {
Log.e(TAG, "Bluetooth is not enabled. Cannot get metadata");
@@ -2357,4 +2469,14 @@
return null;
}
}
+
+ /**
+ * Get the maxinum metadata key ID.
+ *
+ * @return the last supported metadata key
+ * @hide
+ */
+ public static @MetadataKey int getMaxMetadataKey() {
+ return METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD;
+ }
}
diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
index e3a130c..4e64dbe 100644
--- a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
+++ b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
@@ -22,7 +22,7 @@
/**
* The {@link PeriodicAdvertisingParameters} provide a way to adjust periodic
* advertising preferences for each Bluetooth LE advertising set. Use {@link
- * AdvertisingSetParameters.Builder} to create an instance of this class.
+ * PeriodicAdvertisingParameters.Builder} to create an instance of this class.
*/
public final class PeriodicAdvertisingParameters implements Parcelable {
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 102c98f..17bdd42 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -60,6 +60,10 @@
/**
* Device profile: watch.
*
+ * If specified, the current request may have a modified UI to highlight that the device being
+ * set up is a specific kind of device, and some extra permissions may be granted to the app
+ * as a result.
+ *
* @see AssociationRequest.Builder#setDeviceProfile
*/
public static final String DEVICE_PROFILE_WATCH =
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index f3a4e1f..02e86cd 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -370,6 +370,15 @@
/*********** 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 0aa1be9..1a5dad5 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1205,7 +1205,8 @@
CATEGORY_SOCIAL,
CATEGORY_NEWS,
CATEGORY_MAPS,
- CATEGORY_PRODUCTIVITY
+ CATEGORY_PRODUCTIVITY,
+ CATEGORY_ACCESSIBILITY
})
@Retention(RetentionPolicy.SOURCE)
public @interface Category {
@@ -1281,6 +1282,13 @@
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 @@
return context.getText(com.android.internal.R.string.app_category_maps);
case ApplicationInfo.CATEGORY_PRODUCTIVITY:
return context.getText(com.android.internal.R.string.app_category_productivity);
+ case ApplicationInfo.CATEGORY_ACCESSIBILITY:
+ return context.getText(com.android.internal.R.string.app_category_accessibility);
default:
return null;
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 7b62f3b..d79b66c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -7017,7 +7017,7 @@
* domain to an application, use
* {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)},
* passing in all of the domains returned inside
- * {@link DomainVerificationManager#getDomainVerificationUserSelection(String)}.
+ * {@link DomainVerificationManager#getDomainVerificationUserState(String)}.
*
* @hide
*/
diff --git a/core/java/android/content/pm/parsing/component/ParsedActivity.java b/core/java/android/content/pm/parsing/component/ParsedActivity.java
index 2ea24f7..6f478ac 100644
--- a/core/java/android/content/pm/parsing/component/ParsedActivity.java
+++ b/core/java/android/content/pm/parsing/component/ParsedActivity.java
@@ -155,6 +155,7 @@
alias.nonLocalizedLabel = target.nonLocalizedLabel;
alias.launchMode = target.launchMode;
alias.lockTaskLaunchMode = target.lockTaskLaunchMode;
+ alias.documentLaunchMode = target.documentLaunchMode;
alias.descriptionRes = target.descriptionRes;
alias.screenOrientation = target.screenOrientation;
alias.taskAffinity = target.taskAffinity;
@@ -179,7 +180,6 @@
// alias.exported = target.exported;
// alias.permission = target.permission;
// alias.splitName = target.splitName;
-// alias.documentLaunchMode = target.documentLaunchMode;
// alias.persistableMode = target.persistableMode;
// alias.rotationAnimation = target.rotationAnimation;
// alias.colorMode = target.colorMode;
diff --git a/core/java/android/content/pm/verify/domain/DomainOwner.java b/core/java/android/content/pm/verify/domain/DomainOwner.java
index b050f5d..5bf2c09 100644
--- a/core/java/android/content/pm/verify/domain/DomainOwner.java
+++ b/core/java/android/content/pm/verify/domain/DomainOwner.java
@@ -66,16 +66,7 @@
* @param packageName
* Package name of that owns the domain.
* @param overrideable
- * Whether or not this owner can be automatically overridden. If all owners for a domain are
- * overrideable, then calling
- * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID,
- * Set, boolean)} to enable the domain will disable all other owners. On the other hand, if any
- * of the owners are non-overrideable, then
- * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String,
- * boolean)} must be called with false to disable all of the other owners before this domain can
- * be taken by a new owner through
- * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID,
- * Set, boolean)}.
+ * Whether or not this owner can be automatically overridden.
*/
@DataClass.Generated.Member
public DomainOwner(
@@ -98,16 +89,9 @@
}
/**
- * Whether or not this owner can be automatically overridden. If all owners for a domain are
- * overrideable, then calling
- * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID,
- * Set, boolean)} to enable the domain will disable all other owners. On the other hand, if any
- * of the owners are non-overrideable, then
- * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String,
- * boolean)} must be called with false to disable all of the other owners before this domain can
- * be taken by a new owner through
- * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID,
- * Set, boolean)}.
+ * Whether or not this owner can be automatically overridden.
+ *
+ * @see DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)
*/
@DataClass.Generated.Member
public boolean isOverrideable() {
@@ -205,7 +189,7 @@
};
@DataClass.Generated(
- time = 1614119379978L,
+ time = 1614721802044L,
codegenVersion = "1.0.22",
sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainOwner.java",
inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final boolean mOverrideable\nclass DomainOwner extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genEqualsHashCode=true, genAidl=true, genToString=true)")
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
index 8095875..7c335b1 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
@@ -94,7 +94,7 @@
private Map<String, Integer> unparcelHostToStateMap(Parcel in) {
return DomainVerificationUtils.readHostMap(in, new ArrayMap<>(),
- DomainVerificationUserSelection.class.getClassLoader());
+ DomainVerificationUserState.class.getClassLoader());
}
@@ -105,8 +105,7 @@
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain
- // /DomainVerificationInfo.java
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -321,7 +320,7 @@
};
@DataClass.Generated(
- time = 1613002530369L,
+ time = 1614721812023L,
codegenVersion = "1.0.22",
sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java",
inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)")
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
index 11402af..f7c81bcf 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -25,6 +25,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.os.UserHandle;
import java.util.List;
@@ -32,55 +34,63 @@
import java.util.UUID;
/**
- * System service to access the domain verification APIs.
+ * System service to access domain verification APIs.
*
- * Allows the approved domain verification
- * agent on the device (the sole holder of
- * {@link android.Manifest.permission#DOMAIN_VERIFICATION_AGENT}) to update the approval status
- * of domains declared by applications in their AndroidManifest.xml, to allow them to open those
- * links inside the app when selected by the user. This is done through querying
- * {@link #getDomainVerificationInfo(String)} and calling
- * {@link #setDomainVerificationStatus(UUID, Set, int)}.
- *
- * Also allows the domain preference settings (holder of
- * {@link android.Manifest.permission#UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) to update the
- * preferences of the user, when they have chosen to explicitly allow an application to open links.
- * This is done through querying {@link #getDomainVerificationUserSelection(String)} and calling
- * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)} and
- * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}.
- *
- * @hide
+ * Applications should use {@link #getDomainVerificationUserState(String)} if necessary to
+ * check if/how they are verified for a domain, which is required starting from platform
+ * {@link android.os.Build.VERSION_CODES#S} in order to open {@link Intent}s which declare
+ * {@link Intent#CATEGORY_BROWSABLE} or no category and also match against
+ * {@link Intent#CATEGORY_DEFAULT} {@link android.content.IntentFilter}s, either through an
+ * explicit declaration of {@link Intent#CATEGORY_DEFAULT} or through the use of
+ * {@link android.content.pm.PackageManager#MATCH_DEFAULT_ONLY}, which is usually added for the
+ * caller when using {@link Context#startActivity(Intent)} and similar.
*/
-@SystemApi
@SystemService(Context.DOMAIN_VERIFICATION_SERVICE)
-public interface DomainVerificationManager {
+public final class DomainVerificationManager {
/**
- * Extra field name for a {@link DomainVerificationRequest} for the requested packages.
- * Passed to an the domain verification agent that handles
+ * Extra field name for a {@link DomainVerificationRequest} for the requested packages. Passed
+ * to an the domain verification agent that handles
* {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION}.
+ *
+ * @hide
*/
- String EXTRA_VERIFICATION_REQUEST =
+ @SystemApi
+ public static final String EXTRA_VERIFICATION_REQUEST =
"android.content.pm.verify.domain.extra.VERIFICATION_REQUEST";
/**
* No response has been recorded by either the system or any verification agent.
+ *
+ * @hide
*/
- int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE;
-
- /** The verification agent has explicitly verified the domain at some point. */
- int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS;
+ @SystemApi
+ public static final int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE;
/**
- * The first available custom response code. This and any greater integer, along with
- * {@link #STATE_SUCCESS} are the only values settable by the verification agent. All values
- * will be treated as if the domain is unverified.
+ * The verification agent has explicitly verified the domain at some point.
+ *
+ * @hide
*/
- int STATE_FIRST_VERIFIER_DEFINED = DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
+ @SystemApi
+ public static final int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS;
- /** @hide */
+ /**
+ * The first available custom response code. This and any greater integer, along with {@link
+ * #STATE_SUCCESS} are the only values settable by the verification agent. All values will be
+ * treated as if the domain is unverified.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int STATE_FIRST_VERIFIER_DEFINED =
+ DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
+
+ /**
+ * @hide
+ */
@NonNull
- static String stateToDebugString(@DomainVerificationState.State int state) {
+ public static String stateToDebugString(@DomainVerificationState.State int state) {
switch (state) {
case DomainVerificationState.STATE_NO_RESPONSE:
return "none";
@@ -104,10 +114,13 @@
}
/**
- * Checks if a state considers the corresponding domain to be successfully verified. The
- * domain verification agent may use this to determine whether or not to re-verify a domain.
+ * Checks if a state considers the corresponding domain to be successfully verified. The domain
+ * verification agent may use this to determine whether or not to re-verify a domain.
+ *
+ * @hide
*/
- static boolean isStateVerified(@DomainVerificationState.State int state) {
+ @SystemApi
+ public static boolean isStateVerified(@DomainVerificationState.State int state) {
switch (state) {
case DomainVerificationState.STATE_SUCCESS:
case DomainVerificationState.STATE_APPROVED:
@@ -126,10 +139,13 @@
/**
* Checks if a state is modifiable by the domain verification agent. This is useful as the
* platform may add new state codes in newer versions, and older verification agents can use
- * this method to determine if a state can be changed without having to be aware of what the
- * new state means.
+ * this method to determine if a state can be changed without having to be aware of what the new
+ * state means.
+ *
+ * @hide
*/
- static boolean isStateModifiable(@DomainVerificationState.State int state) {
+ @SystemApi
+ public static boolean isStateModifiable(@DomainVerificationState.State int state) {
switch (state) {
case DomainVerificationState.STATE_NO_RESPONSE:
case DomainVerificationState.STATE_SUCCESS:
@@ -147,11 +163,12 @@
}
/**
- * For determine re-verify policy. This is hidden from the domain verification agent so that
- * no behavior is made based on the result.
+ * For determine re-verify policy. This is hidden from the domain verification agent so that no
+ * behavior is made based on the result.
+ *
* @hide
*/
- static boolean isStateDefault(@DomainVerificationState.State int state) {
+ public static boolean isStateDefault(@DomainVerificationState.State int state) {
switch (state) {
case DomainVerificationState.STATE_NO_RESPONSE:
case DomainVerificationState.STATE_MIGRATED:
@@ -168,14 +185,72 @@
}
/**
+ * @hide
+ */
+ public static final int ERROR_INVALID_DOMAIN_SET = 1;
+ /**
+ * @hide
+ */
+ public static final int ERROR_NAME_NOT_FOUND = 2;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"ERROR_"}, value = {
+ ERROR_INVALID_DOMAIN_SET,
+ ERROR_NAME_NOT_FOUND,
+ })
+ private @interface Error {
+ }
+
+ private final Context mContext;
+
+ private final IDomainVerificationManager mDomainVerificationManager;
+
+
+ /**
+ * System service to access the domain verification APIs.
+ * <p>
+ * Allows the approved domain verification agent on the device (the sole holder of {@link
+ * android.Manifest.permission#DOMAIN_VERIFICATION_AGENT}) to update the approval status of
+ * domains declared by applications in their AndroidManifest.xml, to allow them to open those
+ * links inside the app when selected by the user. This is done through querying {@link
+ * #getDomainVerificationInfo(String)} and calling {@link #setDomainVerificationStatus(UUID,
+ * Set, int)}.
+ * <p>
+ * Also allows the domain preference settings (holder of
+ * {@link android.Manifest.permission#UPDATE_DOMAIN_VERIFICATION_USER_SELECTION})
+ * to update the preferences of the user, when they have chosen to explicitly allow an
+ * application to open links. This is done through querying
+ * {@link #getDomainVerificationUserState(String)} and calling
+ * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)} and
+ * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}.
+ *
+ * @hide
+ */
+ public DomainVerificationManager(Context context,
+ IDomainVerificationManager domainVerificationManager) {
+ mContext = context;
+ mDomainVerificationManager = domainVerificationManager;
+ }
+
+ /**
* Used to iterate all {@link DomainVerificationInfo} values to do cleanup or retries. This is
* usually a heavy workload and should be done infrequently.
*
* @return the current snapshot of package names with valid autoVerify URLs.
+ * @hide
*/
+ @SystemApi
@NonNull
@RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
- List<String> getValidVerificationPackageNames();
+ public List<String> queryValidVerificationPackageNames() {
+ try {
+ return mDomainVerificationManager.queryValidVerificationPackageNames();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
/**
* Retrieves the domain verification state for a given package.
@@ -183,61 +258,106 @@
* @return the data for the package, or null if it does not declare any autoVerify domains
* @throws NameNotFoundException If the package is unavailable. This is an unrecoverable error
* and should not be re-tried except on a time scheduled basis.
+ * @hide
*/
+ @SystemApi
@Nullable
@RequiresPermission(anyOf = {
android.Manifest.permission.DOMAIN_VERIFICATION_AGENT,
android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
})
- DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
- throws NameNotFoundException;
+ public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
+ throws NameNotFoundException {
+ try {
+ return mDomainVerificationManager.getDomainVerificationInfo(packageName);
+ } catch (Exception e) {
+ Exception converted = rethrow(e, packageName);
+ if (converted instanceof NameNotFoundException) {
+ throw (NameNotFoundException) converted;
+ } else if (converted instanceof RuntimeException) {
+ throw (RuntimeException) converted;
+ } else {
+ throw new RuntimeException(converted);
+ }
+ }
+ }
/**
- * Change the verification status of the {@param domains} of the package associated with
- * {@param domainSetId}.
+ * Change the verification status of the {@param domains} of the package associated with {@param
+ * domainSetId}.
*
* @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
* @param domains List of host names to change the state of.
* @param state See {@link DomainVerificationInfo#getHostToStateMap()}.
* @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are
* invalid. This usually means the work being processed by the
- * verification agent is outdated and a new request should
- * be scheduled, if one has not already been done as part of
- * the {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION}
- * broadcast.
+ * verification agent is outdated and a new request should be
+ * scheduled, if one has not already been done as part of the
+ * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast.
* @throws NameNotFoundException If the ID is known to be good, but the package is
- * unavailable. This may be because the package is
- * installed on a volume that is no longer mounted. This
- * error is unrecoverable until the package is available
- * again, and should not be re-tried except on a time
- * scheduled basis.
+ * unavailable. This may be because the package is installed on
+ * a volume that is no longer mounted. This error is
+ * unrecoverable until the package is available again, and
+ * should not be re-tried except on a time scheduled basis.
+ * @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
- void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
- @DomainVerificationState.State int state) throws NameNotFoundException;
+ public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
+ @DomainVerificationState.State int state) throws NameNotFoundException {
+ try {
+ mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(),
+ new DomainSet(domains), state);
+ } catch (Exception e) {
+ Exception converted = rethrow(e, domainSetId);
+ if (converted instanceof NameNotFoundException) {
+ throw (NameNotFoundException) converted;
+ } else if (converted instanceof RuntimeException) {
+ throw (RuntimeException) converted;
+ } else {
+ throw new RuntimeException(converted);
+ }
+ }
+ }
/**
- * TODO(b/178525735): This documentation is incorrect in the context of UX changes.
- * Change whether the given {@param packageName} is allowed to automatically open verified
- * HTTP/HTTPS domains. The final state is determined along with the verification status for the
- * specific domain being opened and other system state. An app with this enabled is not
- * guaranteed to be the sole link handler for its domains.
+ * Change whether the given packageName is allowed to handle BROWSABLE and DEFAULT category web
+ * (HTTP/HTTPS) {@link Intent} Activity open requests. The final state is determined along with
+ * the verification status for the specific domain being opened and other system state. An app
+ * with this enabled is not guaranteed to be the sole link handler for its domains.
+ * <p>
+ * By default, all apps are allowed to open links. Users must disable them explicitly.
*
- * By default, all apps are allowed to open verified links. Users must disable them explicitly.
+ * @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
- void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, boolean allowed)
- throws NameNotFoundException;
+ public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
+ boolean allowed) throws NameNotFoundException {
+ try {
+ mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(packageName,
+ allowed, mContext.getUserId());
+ } catch (Exception e) {
+ Exception converted = rethrow(e, packageName);
+ if (converted instanceof NameNotFoundException) {
+ throw (NameNotFoundException) converted;
+ } else if (converted instanceof RuntimeException) {
+ throw (RuntimeException) converted;
+ } else {
+ throw new RuntimeException(converted);
+ }
+ }
+ }
/**
* Update the recorded user selection for the given {@param domains} for the given {@param
* domainSetId}. This state is recorded for the lifetime of a domain for a package on device,
* and will never be reset by the system short of an app data clear.
- *
+ * <p>
* This state is stored per device user. If another user needs to be changed, the appropriate
- * permissions must be acquired and
- * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used.
- *
+ * permissions must be acquired and {@link Context#createContextAsUser(UserHandle, int)} should
+ * be used.
+ * <p>
* Enabling an unverified domain will allow an application to open it, but this can only occur
* if no other app on the device is approved for a higher approval level. This can queried
* using {@link #getOwnersForDomain(String)}.
@@ -255,33 +375,55 @@
* @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are
* invalid.
* @throws NameNotFoundException If the ID is known to be good, but the package is
- * unavailable. This may be because the package is
- * installed on a volume that is no longer mounted. This
- * error is unrecoverable until the package is available
- * again, and should not be re-tried except on a time
- * scheduled basis.
+ * unavailable. This may be because the package is installed on
+ * a volume that is no longer mounted. This error is
+ * unrecoverable until the package is available again, and
+ * should not be re-tried except on a time scheduled basis.
+ * @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
- void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
- @NonNull Set<String> domains, boolean enabled) throws NameNotFoundException;
+ public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
+ @NonNull Set<String> domains, boolean enabled) throws NameNotFoundException {
+ try {
+ mDomainVerificationManager.setDomainVerificationUserSelection(domainSetId.toString(),
+ new DomainSet(domains), enabled, mContext.getUserId());
+ } catch (Exception e) {
+ Exception converted = rethrow(e, domainSetId);
+ if (converted instanceof NameNotFoundException) {
+ throw (NameNotFoundException) converted;
+ } else if (converted instanceof RuntimeException) {
+ throw (RuntimeException) converted;
+ } else {
+ throw new RuntimeException(converted);
+ }
+ }
+ }
/**
* Retrieve the user selection data for the given {@param packageName} and the current user.
- * It is the responsibility of the caller to ensure that the
- * {@link DomainVerificationUserSelection#getIdentifier()} matches any prior API calls.
- *
- * This state is stored per device user. If another user needs to be accessed, the appropriate
- * permissions must be acquired and
- * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used.
*
* @param packageName The app to query state for.
- * @return the user selection verification data for the given package for the current user,
- * or null if the package does not declare any HTTP/HTTPS domains.
+ * @return the user selection verification data for the given package for the current user, or
+ * null if the package does not declare any HTTP/HTTPS domains.
*/
@Nullable
- @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
- DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String packageName)
- throws NameNotFoundException;
+ public DomainVerificationUserState getDomainVerificationUserState(
+ @NonNull String packageName) throws NameNotFoundException {
+ try {
+ return mDomainVerificationManager.getDomainVerificationUserState(packageName,
+ mContext.getUserId());
+ } catch (Exception e) {
+ Exception converted = rethrow(e, packageName);
+ if (converted instanceof NameNotFoundException) {
+ throw (NameNotFoundException) converted;
+ } else if (converted instanceof RuntimeException) {
+ throw (RuntimeException) converted;
+ } else {
+ throw new RuntimeException(converted);
+ }
+ }
+ }
/**
* For the given domain, return all apps which are approved to open it in a
@@ -291,21 +433,65 @@
*
* By default the list will be returned ordered from lowest to highest
* priority.
+ *
+ * @hide
*/
+ @SystemApi
@NonNull
@RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
- List<DomainOwner> getOwnersForDomain(@NonNull String domain);
+ public List<DomainOwner> getOwnersForDomain(@NonNull String domain) {
+ try {
+ return mDomainVerificationManager.getOwnersForDomain(domain, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private Exception rethrow(Exception exception, @Nullable UUID domainSetId) {
+ return rethrow(exception, domainSetId, null);
+ }
+
+ private Exception rethrow(Exception exception, @Nullable String packageName) {
+ return rethrow(exception, null, packageName);
+ }
+
+ private Exception rethrow(Exception exception, @Nullable UUID domainSetId,
+ @Nullable String packageName) {
+ if (exception instanceof ServiceSpecificException) {
+ int packedErrorCode = ((ServiceSpecificException) exception).errorCode;
+ if (packageName == null) {
+ packageName = exception.getMessage();
+ }
+
+ @Error int managerErrorCode = packedErrorCode & 0xFFFF;
+ switch (managerErrorCode) {
+ case ERROR_INVALID_DOMAIN_SET:
+ int errorSpecificCode = packedErrorCode >> 16;
+ return new IllegalArgumentException(InvalidDomainSetException.buildMessage(
+ domainSetId, packageName, errorSpecificCode));
+ case ERROR_NAME_NOT_FOUND:
+ return new NameNotFoundException(packageName);
+ default:
+ return exception;
+ }
+ } else if (exception instanceof RemoteException) {
+ return ((RemoteException) exception).rethrowFromSystemServer();
+ } else {
+ return exception;
+ }
+ }
/**
* Thrown if a {@link DomainVerificationInfo#getIdentifier()}} or an associated set of domains
* provided by the caller is no longer valid. This may be recoverable, and the caller should
* re-query the package name associated with the ID using
- * {@link #getDomainVerificationInfo(String)} in order to check. If that also fails, then the
- * package is no longer known to the device and thus all pending work for it should be dropped.
+ * {@link #getDomainVerificationInfo(String)}
+ * in order to check. If that also fails, then the package is no longer known to the device and
+ * thus all pending work for it should be dropped.
*
* @hide
*/
- class InvalidDomainSetException extends IllegalArgumentException {
+ public static class InvalidDomainSetException extends IllegalArgumentException {
public static final int REASON_ID_NULL = 1;
public static final int REASON_ID_INVALID = 2;
@@ -313,7 +499,9 @@
public static final int REASON_UNKNOWN_DOMAIN = 4;
public static final int REASON_UNABLE_TO_APPROVE = 5;
- /** @hide */
+ /**
+ * @hide
+ */
@IntDef({
REASON_ID_NULL,
REASON_ID_INVALID,
@@ -352,7 +540,9 @@
@Nullable
private final String mPackageName;
- /** @hide */
+ /**
+ * @hide
+ */
public InvalidDomainSetException(@Nullable UUID domainSetId, @Nullable String packageName,
@Reason int reason) {
super(buildMessage(domainSetId, packageName, reason));
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java b/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java
deleted file mode 100644
index 8b9865c..0000000
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content.pm.verify.domain;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.RemoteException;
-import android.os.ServiceSpecificException;
-
-import java.util.List;
-import java.util.Set;
-import java.util.UUID;
-
-/**
- * @hide
- */
-@SuppressWarnings("RedundantThrows")
-public class DomainVerificationManagerImpl implements DomainVerificationManager {
-
- public static final int ERROR_INVALID_DOMAIN_SET = 1;
- public static final int ERROR_NAME_NOT_FOUND = 2;
-
- @IntDef(prefix = { "ERROR_" }, value = {
- ERROR_INVALID_DOMAIN_SET,
- ERROR_NAME_NOT_FOUND,
- })
- private @interface Error {
- }
-
- private final Context mContext;
-
- private final IDomainVerificationManager mDomainVerificationManager;
-
- public DomainVerificationManagerImpl(Context context,
- IDomainVerificationManager domainVerificationManager) {
- mContext = context;
- mDomainVerificationManager = domainVerificationManager;
- }
-
- @NonNull
- @Override
- public List<String> getValidVerificationPackageNames() {
- try {
- return mDomainVerificationManager.getValidVerificationPackageNames();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- @Nullable
- @Override
- public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
- throws NameNotFoundException {
- try {
- return mDomainVerificationManager.getDomainVerificationInfo(packageName);
- } catch (Exception e) {
- Exception converted = rethrow(e, packageName);
- if (converted instanceof NameNotFoundException) {
- throw (NameNotFoundException) converted;
- } else if (converted instanceof RuntimeException) {
- throw (RuntimeException) converted;
- } else {
- throw new RuntimeException(converted);
- }
- }
- }
-
- @Override
- public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
- int state) throws IllegalArgumentException, NameNotFoundException {
- try {
- mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(),
- new DomainSet(domains), state);
- } catch (Exception e) {
- Exception converted = rethrow(e, domainSetId);
- if (converted instanceof NameNotFoundException) {
- throw (NameNotFoundException) converted;
- } else if (converted instanceof RuntimeException) {
- throw (RuntimeException) converted;
- } else {
- throw new RuntimeException(converted);
- }
- }
- }
-
- @Override
- public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
- boolean allowed) throws NameNotFoundException {
- try {
- mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(packageName,
- allowed, mContext.getUserId());
- } catch (Exception e) {
- Exception converted = rethrow(e, packageName);
- if (converted instanceof NameNotFoundException) {
- throw (NameNotFoundException) converted;
- } else if (converted instanceof RuntimeException) {
- throw (RuntimeException) converted;
- } else {
- throw new RuntimeException(converted);
- }
- }
- }
-
- @Override
- public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
- @NonNull Set<String> domains, boolean enabled)
- throws IllegalArgumentException, NameNotFoundException {
- try {
- mDomainVerificationManager.setDomainVerificationUserSelection(domainSetId.toString(),
- new DomainSet(domains), enabled, mContext.getUserId());
- } catch (Exception e) {
- Exception converted = rethrow(e, domainSetId);
- if (converted instanceof NameNotFoundException) {
- throw (NameNotFoundException) converted;
- } else if (converted instanceof RuntimeException) {
- throw (RuntimeException) converted;
- } else {
- throw new RuntimeException(converted);
- }
- }
- }
-
- @Nullable
- @Override
- public DomainVerificationUserSelection getDomainVerificationUserSelection(
- @NonNull String packageName) throws NameNotFoundException {
- try {
- return mDomainVerificationManager.getDomainVerificationUserSelection(packageName,
- mContext.getUserId());
- } catch (Exception e) {
- Exception converted = rethrow(e, packageName);
- if (converted instanceof NameNotFoundException) {
- throw (NameNotFoundException) converted;
- } else if (converted instanceof RuntimeException) {
- throw (RuntimeException) converted;
- } else {
- throw new RuntimeException(converted);
- }
- }
- }
-
- @NonNull
- @Override
- public List<DomainOwner> getOwnersForDomain(@NonNull String domain) {
- try {
- return mDomainVerificationManager.getOwnersForDomain(domain, mContext.getUserId());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- private Exception rethrow(Exception exception, @Nullable UUID domainSetId) {
- return rethrow(exception, domainSetId, null);
- }
-
- private Exception rethrow(Exception exception, @Nullable String packageName) {
- return rethrow(exception, null, packageName);
- }
-
- private Exception rethrow(Exception exception, @Nullable UUID domainSetId,
- @Nullable String packageName) {
- if (exception instanceof ServiceSpecificException) {
- int packedErrorCode = ((ServiceSpecificException) exception).errorCode;
- if (packageName == null) {
- packageName = exception.getMessage();
- }
-
- @Error int managerErrorCode = packedErrorCode & 0xFFFF;
- switch (managerErrorCode) {
- case ERROR_INVALID_DOMAIN_SET:
- int errorSpecificCode = packedErrorCode >> 16;
- return new IllegalArgumentException(InvalidDomainSetException.buildMessage(
- domainSetId, packageName, errorSpecificCode));
- case ERROR_NAME_NOT_FOUND:
- return new NameNotFoundException(packageName);
- default:
- return exception;
- }
- } else if (exception instanceof RemoteException) {
- return ((RemoteException) exception).rethrowFromSystemServer();
- } else {
- return exception;
- }
- }
-}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.aidl
similarity index 93%
rename from core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl
rename to core/java/android/content/pm/verify/domain/DomainVerificationUserState.aidl
index ddb5ef8..94690c1 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.aidl
@@ -16,4 +16,4 @@
package android.content.pm.verify.domain;
-parcelable DomainVerificationUserSelection;
+parcelable DomainVerificationUserState;
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java
similarity index 82%
rename from core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
rename to core/java/android/content/pm/verify/domain/DomainVerificationUserState.java
index d23f5f1..1e60abb 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Context;
+import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
@@ -29,39 +30,36 @@
import com.android.internal.util.Parcelling;
import java.util.Map;
-import java.util.Set;
import java.util.UUID;
/**
* Contains the user selection state for a package. This means all web HTTP(S) domains declared by a
* package in its manifest, whether or not they were marked for auto verification.
* <p>
- * By default, all apps are allowed to automatically open links with domains that they've
- * successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}. The user
- * can decide to disable this, disallowing the application from opening all links. Note that the
- * toggle affects <b>all</b> links and is not based on the verification state of the domains.
+ * Applications should use {@link #getHostToStateMap()} if necessary to
+ * check if/how they are verified for a domain, which is required starting from platform
+ * {@link android.os.Build.VERSION_CODES#S} in order to open {@link Intent}s which declare
+ * {@link Intent#CATEGORY_BROWSABLE} or no category and also match against
+ * {@link Intent#CATEGORY_DEFAULT} {@link android.content.IntentFilter}s, either through an
+ * explicit declaration of {@link Intent#CATEGORY_DEFAULT} or through the use of
+ * {@link android.content.pm.PackageManager#MATCH_DEFAULT_ONLY}, which is usually added for the
+ * caller when using {@link Context#startActivity(Intent)} and similar.
+ * <p>
+ * By default, all apps are allowed to automatically open links for the above case for domains that
+ * they've successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}.
+ * The user can decide to disable this, disallowing the application from opening all links. Note
+ * that the toggle affects <b>all</b> links and is not based on the verification state of the
+ * domains.
* <p>
* Assuming the toggle is enabled, the user can also select additional unverified domains to grant
* to the application to open, which is reflected in {@link #getHostToStateMap()}. But only a single
* application can be approved for a domain unless the applications are both approved. If another
* application is approved, the user will not be allowed to enable the domain.
- * <p>
- * These values can be changed through the
- * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String,
- * boolean)} and {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set,
- * boolean)} APIs.
- * <p>
- * Note that because state is per user, if a different user needs to be changed, one will need to
- * use {@link Context#createContextAsUser(UserHandle, int)} and hold the {@link
- * android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
- *
- * @hide
*/
-@SystemApi
@SuppressWarnings("DefaultAnnotationParam")
@DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true,
genEqualsHashCode = true, genHiddenConstDefs = true)
-public final class DomainVerificationUserSelection implements Parcelable {
+public final class DomainVerificationUserState implements Parcelable {
/**
* The domain is unverified and unselected, and the application is unable to open web links
@@ -70,9 +68,8 @@
public static final int DOMAIN_STATE_NONE = 0;
/**
- * The domain has been selected through the
- * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)}
- * API, under the assumption it has not been reset by the system.
+ * The domain has been selected by the user. This may be reset to {@link #DOMAIN_STATE_NONE} if
+ * another application is selected or verified for the same domain.
*/
public static final int DOMAIN_STATE_SELECTED = 1;
@@ -119,7 +116,16 @@
@NonNull
private Map<String, Integer> unparcelHostToStateMap(Parcel in) {
return DomainVerificationUtils.readHostMap(in, new ArrayMap<>(),
- DomainVerificationUserSelection.class.getClassLoader());
+ DomainVerificationUserState.class.getClassLoader());
+ }
+
+ /**
+ * @see DomainVerificationInfo#getIdentifier
+ * @hide
+ */
+ @SystemApi
+ public @NonNull UUID getIdentifier() {
+ return mIdentifier;
}
@@ -130,7 +136,7 @@
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -162,7 +168,7 @@
}
/**
- * Creates a new DomainVerificationUserSelection.
+ * Creates a new DomainVerificationUserState.
*
* @param packageName
* The package name that this data corresponds to.
@@ -175,7 +181,7 @@
* @hide
*/
@DataClass.Generated.Member
- public DomainVerificationUserSelection(
+ public DomainVerificationUserState(
@NonNull UUID identifier,
@NonNull String packageName,
@NonNull UserHandle user,
@@ -201,14 +207,6 @@
}
/**
- * @see DomainVerificationInfo#getIdentifier
- */
- @DataClass.Generated.Member
- public @NonNull UUID getIdentifier() {
- return mIdentifier;
- }
-
- /**
* The package name that this data corresponds to.
*/
@DataClass.Generated.Member
@@ -246,7 +244,7 @@
// You can override field toString logic by defining methods like:
// String fieldNameToString() { ... }
- return "DomainVerificationUserSelection { " +
+ return "DomainVerificationUserState { " +
"identifier = " + mIdentifier + ", " +
"packageName = " + mPackageName + ", " +
"user = " + mUser + ", " +
@@ -259,13 +257,13 @@
@DataClass.Generated.Member
public boolean equals(@Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
- // boolean fieldNameEquals(DomainVerificationUserSelection other) { ... }
+ // boolean fieldNameEquals(DomainVerificationUserState other) { ... }
// boolean fieldNameEquals(FieldType otherValue) { ... }
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@SuppressWarnings("unchecked")
- DomainVerificationUserSelection that = (DomainVerificationUserSelection) o;
+ DomainVerificationUserState that = (DomainVerificationUserState) o;
//noinspection PointlessBooleanExpression
return true
&& java.util.Objects.equals(mIdentifier, that.mIdentifier)
@@ -323,7 +321,7 @@
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- /* package-private */ DomainVerificationUserSelection(@NonNull Parcel in) {
+ /* package-private */ DomainVerificationUserState(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
@@ -354,24 +352,24 @@
}
@DataClass.Generated.Member
- public static final @NonNull Parcelable.Creator<DomainVerificationUserSelection> CREATOR
- = new Parcelable.Creator<DomainVerificationUserSelection>() {
+ public static final @NonNull Parcelable.Creator<DomainVerificationUserState> CREATOR
+ = new Parcelable.Creator<DomainVerificationUserState>() {
@Override
- public DomainVerificationUserSelection[] newArray(int size) {
- return new DomainVerificationUserSelection[size];
+ public DomainVerificationUserState[] newArray(int size) {
+ return new DomainVerificationUserState[size];
}
@Override
- public DomainVerificationUserSelection createFromParcel(@NonNull Parcel in) {
- return new DomainVerificationUserSelection(in);
+ public DomainVerificationUserState createFromParcel(@NonNull Parcel in) {
+ return new DomainVerificationUserState(in);
}
};
@DataClass.Generated(
- time = 1613683603297L,
+ time = 1614721840152L,
codegenVersion = "1.0.22",
- sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java",
- inputSignatures = "public static final int DOMAIN_STATE_NONE\npublic static final int DOMAIN_STATE_SELECTED\npublic static final int DOMAIN_STATE_VERIFIED\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationUserSelection extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)")
+ sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java",
+ inputSignatures = "public static final int DOMAIN_STATE_NONE\npublic static final int DOMAIN_STATE_SELECTED\npublic static final int DOMAIN_STATE_VERIFIED\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\npublic @android.annotation.SystemApi @android.annotation.NonNull java.util.UUID getIdentifier()\nclass DomainVerificationUserState extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
index 701af32..332b925 100644
--- a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
+++ b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
@@ -19,7 +19,7 @@
import android.content.pm.verify.domain.DomainOwner;
import android.content.pm.verify.domain.DomainSet;
import android.content.pm.verify.domain.DomainVerificationInfo;
-import android.content.pm.verify.domain.DomainVerificationUserSelection;
+import android.content.pm.verify.domain.DomainVerificationUserState;
import java.util.List;
/**
@@ -28,13 +28,13 @@
*/
interface IDomainVerificationManager {
- List<String> getValidVerificationPackageNames();
+ List<String> queryValidVerificationPackageNames();
@nullable
DomainVerificationInfo getDomainVerificationInfo(String packageName);
@nullable
- DomainVerificationUserSelection getDomainVerificationUserSelection(String packageName,
+ DomainVerificationUserState getDomainVerificationUserState(String packageName,
int userId);
@nullable
diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java
index fd98d37..31d1b69 100644
--- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java
+++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java
@@ -62,10 +62,13 @@
* @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 5b28e00..1fdce5e 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -23,6 +23,7 @@
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 @@
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 @@
*/
@Deprecated
@RequiresPermission(USE_BIOMETRIC)
- public @BiometricError int canAuthenticate() {
+ @BiometricError
+ public int canAuthenticate() {
return canAuthenticate(Authenticators.BIOMETRIC_WEAK);
}
@@ -304,7 +306,8 @@
* 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 @@
* @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 @@
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 @@
}
}
+ /**
+ * 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 d8c9dbc..1472bb9 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -68,4 +68,16 @@
// 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 2433186..6d8bf0f 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -75,4 +75,10 @@
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 06b5b67..a5c9a7f 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.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 @@
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 @@
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 @@
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 183f500..cc1312b 100644
--- a/core/java/android/net/Ikev2VpnProfile.java
+++ b/core/java/android/net/Ikev2VpnProfile.java
@@ -24,10 +24,7 @@
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 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 @@
/** 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 @@
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 @@
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/NetworkStateSnapshot.java b/core/java/android/net/NetworkStateSnapshot.java
index b3d8d4e..0d26c2d 100644
--- a/core/java/android/net/NetworkStateSnapshot.java
+++ b/core/java/android/net/NetworkStateSnapshot.java
@@ -24,6 +24,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.net.module.util.NetworkIdentityUtils;
+
import java.util.Objects;
/**
@@ -124,4 +126,15 @@
public int hashCode() {
return Objects.hash(network, networkCapabilities, linkProperties, subscriberId, legacyType);
}
+
+ @Override
+ public String toString() {
+ return "NetworkStateSnapshot{"
+ + "network=" + network
+ + ", networkCapabilities=" + networkCapabilities
+ + ", linkProperties=" + linkProperties
+ + ", subscriberId='" + NetworkIdentityUtils.scrubSubscriberId(subscriberId) + '\''
+ + ", legacyType=" + legacyType
+ + '}';
+ }
}
diff --git a/core/java/android/net/OemNetworkPreferences.java b/core/java/android/net/OemNetworkPreferences.java
index b403455..48bd297 100644
--- a/core/java/android/net/OemNetworkPreferences.java
+++ b/core/java/android/net/OemNetworkPreferences.java
@@ -29,7 +29,15 @@
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 @@
@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 @@
mNetworkMappings = new Bundle();
}
+ /**
+ * Constructor to populate the builder's values with an already built
+ * {@link OemNetworkPreferences}.
+ * @param preferences the {@link OemNetworkPreferences} to populate with.
+ */
public Builder(@NonNull final OemNetworkPreferences preferences) {
Objects.requireNonNull(preferences);
mNetworkMappings = (Bundle) preferences.mNetworkMappings.clone();
diff --git a/core/java/android/net/vcn/IVcnStatusCallback.aidl b/core/java/android/net/vcn/IVcnStatusCallback.aidl
index d91cef5..236ae8b 100644
--- a/core/java/android/net/vcn/IVcnStatusCallback.aidl
+++ b/core/java/android/net/vcn/IVcnStatusCallback.aidl
@@ -18,7 +18,6 @@
/** @hide */
oneway interface IVcnStatusCallback {
- void onEnteredSafeMode();
void onVcnStatusChanged(int statusCode);
void onGatewayConnectionError(
in int[] gatewayNetworkCapabilities,
diff --git a/core/java/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl b/core/java/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl
index f8ae492..62de821 100644
--- a/core/java/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl
+++ b/core/java/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl
@@ -17,6 +17,6 @@
package android.net.vcn;
/** @hide */
-interface IVcnUnderlyingNetworkPolicyListener {
+oneway interface IVcnUnderlyingNetworkPolicyListener {
void onPolicyChanged();
}
\ No newline at end of file
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index eb8c251..8ebf757 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -359,8 +359,6 @@
/**
* Value indicating that the VCN for the subscription group is not configured, or that the
* callback is not privileged for the subscription group.
- *
- * @hide
*/
public static final int VCN_STATUS_CODE_NOT_CONFIGURED = 0;
@@ -369,8 +367,6 @@
*
* <p>A VCN is inactive if a {@link VcnConfig} is present for the subscription group, but the
* provisioning package is not privileged.
- *
- * @hide
*/
public static final int VCN_STATUS_CODE_INACTIVE = 1;
@@ -380,8 +376,6 @@
* <p>A VCN is active if a {@link VcnConfig} is present for the subscription, the provisioning
* package is privileged, and the VCN is not in Safe Mode. In other words, a VCN is considered
* active while it is connecting, fully connected, and disconnecting.
- *
- * @hide
*/
public static final int VCN_STATUS_CODE_ACTIVE = 2;
@@ -391,8 +385,6 @@
* <p>A VCN will be put into Safe Mode if any of the gateway connections were unable to
* establish a connection within a system-determined timeout (while underlying networks were
* available).
- *
- * @hide
*/
public static final int VCN_STATUS_CODE_SAFE_MODE = 3;
@@ -407,8 +399,6 @@
/**
* Value indicating that an internal failure occurred in this Gateway Connection.
- *
- * @hide
*/
public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0;
@@ -416,8 +406,6 @@
* Value indicating that an error with this Gateway Connection's configuration occurred.
*
* <p>For example, this error code will be returned after authentication failures.
- *
- * @hide
*/
public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1;
@@ -427,38 +415,19 @@
* <p>For example, this error code will be returned if an underlying {@link android.net.Network}
* for this Gateway Connection is lost, or if an error occurs while resolving the connection
* endpoint address.
- *
- * @hide
*/
public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2;
- // TODO: make VcnStatusCallback @SystemApi
/**
* VcnStatusCallback is the interface for Carrier apps to receive updates for their VCNs.
*
* <p>VcnStatusCallbacks may be registered before {@link VcnConfig}s are provided for a
* subscription group.
- *
- * @hide
*/
public abstract static class VcnStatusCallback {
private VcnStatusCallbackBinder mCbBinder;
/**
- * Invoked when the VCN for this Callback's subscription group enters safe mode.
- *
- * <p>A VCN will be put into safe mode if any of the gateway connections were unable to
- * establish a connection within a system-determined timeout (while underlying networks were
- * available).
- *
- * <p>A VCN-configuring app may opt to exit safe mode by (re)setting the VCN configuration
- * via {@link #setVcnConfig(ParcelUuid, VcnConfig)}.
- *
- * @hide
- */
- public void onEnteredSafeMode() {}
-
- /**
* Invoked when status of the VCN for this callback's subscription group changes.
*
* @param statusCode the code for the status change encountered by this {@link
@@ -467,15 +436,16 @@
public abstract void onVcnStatusChanged(@VcnStatusCode int statusCode);
/**
- * Invoked when a VCN Gateway Connection corresponding to this callback's subscription
+ * Invoked when a VCN Gateway Connection corresponding to this callback's subscription group
* encounters an error.
*
- * @param networkCapabilities an array of underlying NetworkCapabilities for the Gateway
- * Connection that encountered the error for identification purposes. These will be a
- * sorted list with no duplicates, matching one of the {@link
+ * @param networkCapabilities an array of NetworkCapabilities.NET_CAPABILITY_* capabilities
+ * for the Gateway Connection that encountered the error, for identification purposes.
+ * These will be a sorted list with no duplicates and will match {@link
+ * VcnGatewayConnectionConfig#getRequiredUnderlyingCapabilities()} for one of the {@link
* VcnGatewayConnectionConfig}s set in the {@link VcnConfig} for this subscription
* group.
- * @param errorCode {@link VcnErrorCode} to indicate the error that occurred
+ * @param errorCode the code to indicate the error that occurred
* @param detail Throwable to provide additional information about the error, or {@code
* null} if none
*/
@@ -496,6 +466,10 @@
* <p>A {@link VcnStatusCallback} will only be invoked if the registering package has carrier
* privileges for the specified subscription at the time of invocation.
*
+ * <p>A {@link VcnStatusCallback} is eligible to begin receiving callbacks once it is registered
+ * and there is a VCN active for its specified subscription group (this may happen after the
+ * callback is registered).
+ *
* <p>{@link VcnStatusCallback#onVcnStatusChanged(int)} will be invoked on registration with the
* current status for the specified subscription group's VCN. If the registrant is not
* privileged for this subscription group, {@link #VCN_STATUS_CODE_NOT_CONFIGURED} will be
@@ -505,7 +479,6 @@
* @param executor The {@link Executor} to be used for invoking callbacks
* @param callback The VcnStatusCallback to be registered
* @throws IllegalStateException if callback is currently registered with VcnManager
- * @hide
*/
public void registerVcnStatusCallback(
@NonNull ParcelUuid subscriptionGroup,
@@ -538,7 +511,6 @@
* was registered with.
*
* @param callback The callback to be unregistered
- * @hide
*/
public void unregisterVcnStatusCallback(@NonNull VcnStatusCallback callback) {
requireNonNull(callback, "callback must not be null");
@@ -599,12 +571,6 @@
}
@Override
- public void onEnteredSafeMode() {
- Binder.withCleanCallingIdentity(
- () -> mExecutor.execute(() -> mCallback.onEnteredSafeMode()));
- }
-
- @Override
public void onVcnStatusChanged(@VcnStatusCode int statusCode) {
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> mCallback.onVcnStatusChanged(statusCode)));
diff --git a/core/java/android/net/vcn/persistablebundleutils/CertUtils.java b/core/java/android/net/vcn/persistablebundleutils/CertUtils.java
new file mode 100644
index 0000000..b6036b4
--- /dev/null
+++ b/core/java/android/net/vcn/persistablebundleutils/CertUtils.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.persistablebundleutils;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Objects;
+
+/**
+ * CertUtils provides utility methods for constructing Certificate.
+ *
+ * @hide
+ */
+public class CertUtils {
+ private static final String CERT_TYPE_X509 = "X.509";
+
+ /** Decodes an ASN.1 DER encoded Certificate */
+ public static X509Certificate certificateFromByteArray(byte[] derEncoded) {
+ Objects.requireNonNull(derEncoded, "derEncoded is null");
+
+ try {
+ CertificateFactory certFactory = CertificateFactory.getInstance(CERT_TYPE_X509);
+ InputStream in = new ByteArrayInputStream(derEncoded);
+ return (X509Certificate) certFactory.generateCertificate(in);
+ } catch (CertificateException e) {
+ throw new IllegalArgumentException("Fail to decode certificate", e);
+ }
+ }
+}
diff --git a/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java b/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java
new file mode 100644
index 0000000..ce5ec75
--- /dev/null
+++ b/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.persistablebundleutils;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.vcn.util.PersistableBundleUtils;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Provides utility methods to convert ChildSaProposal to/from PersistableBundle.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = Visibility.PRIVATE)
+public final class ChildSaProposalUtils extends SaProposalUtilsBase {
+ /** Serializes a ChildSaProposal to a PersistableBundle. */
+ @NonNull
+ public static PersistableBundle toPersistableBundle(ChildSaProposal proposal) {
+ return SaProposalUtilsBase.toPersistableBundle(proposal);
+ }
+
+ /** Constructs a ChildSaProposal by deserializing a PersistableBundle. */
+ @NonNull
+ public static ChildSaProposal fromPersistableBundle(@NonNull PersistableBundle in) {
+ Objects.requireNonNull(in, "PersistableBundle was null");
+
+ final ChildSaProposal.Builder builder = new ChildSaProposal.Builder();
+
+ final PersistableBundle encryptionBundle = in.getPersistableBundle(ENCRYPT_ALGO_KEY);
+ Objects.requireNonNull(encryptionBundle, "Encryption algo bundle was null");
+ final List<EncryptionAlgoKeyLenPair> encryptList =
+ PersistableBundleUtils.toList(encryptionBundle, EncryptionAlgoKeyLenPair::new);
+ for (EncryptionAlgoKeyLenPair t : encryptList) {
+ builder.addEncryptionAlgorithm(t.encryptionAlgo, t.keyLen);
+ }
+
+ final int[] integrityAlgoIdArray = in.getIntArray(INTEGRITY_ALGO_KEY);
+ Objects.requireNonNull(integrityAlgoIdArray, "Integrity algo array was null");
+ for (int algo : integrityAlgoIdArray) {
+ builder.addIntegrityAlgorithm(algo);
+ }
+
+ final int[] dhGroupArray = in.getIntArray(DH_GROUP_KEY);
+ Objects.requireNonNull(dhGroupArray, "DH Group array was null");
+ for (int dh : dhGroupArray) {
+ builder.addDhGroup(dh);
+ }
+
+ return builder.build();
+ }
+}
diff --git a/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java b/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java
new file mode 100644
index 0000000..853a52d
--- /dev/null
+++ b/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.persistablebundleutils;
+
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.net.eap.EapSessionConfig;
+import android.net.eap.EapSessionConfig.EapAkaConfig;
+import android.net.eap.EapSessionConfig.EapAkaPrimeConfig;
+import android.net.eap.EapSessionConfig.EapMethodConfig;
+import android.net.eap.EapSessionConfig.EapMsChapV2Config;
+import android.net.eap.EapSessionConfig.EapSimConfig;
+import android.net.eap.EapSessionConfig.EapTtlsConfig;
+import android.net.eap.EapSessionConfig.EapUiccConfig;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.vcn.util.PersistableBundleUtils;
+
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.Objects;
+
+/**
+ * Provides utility methods to convert EapSessionConfig to/from PersistableBundle.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = Visibility.PRIVATE)
+public final class EapSessionConfigUtils {
+ private static final String EAP_ID_KEY = "EAP_ID_KEY";
+ private static final String EAP_SIM_CONFIG_KEY = "EAP_SIM_CONFIG_KEY";
+ private static final String EAP_TTLS_CONFIG_KEY = "EAP_TTLS_CONFIG_KEY";
+ private static final String EAP_AKA_CONFIG_KEY = "EAP_AKA_CONFIG_KEY";
+ private static final String EAP_MSCHAP_V2_CONFIG_KEY = "EAP_MSCHAP_V2_CONFIG_KEY";
+ private static final String EAP_AKA_PRIME_CONFIG_KEY = "EAP_AKA_PRIME_CONFIG_KEY";
+
+ /** Serializes an EapSessionConfig to a PersistableBundle. */
+ @NonNull
+ public static PersistableBundle toPersistableBundle(@NonNull EapSessionConfig config) {
+ final PersistableBundle result = new PersistableBundle();
+
+ result.putPersistableBundle(
+ EAP_ID_KEY, PersistableBundleUtils.fromByteArray(config.getEapIdentity()));
+
+ if (config.getEapSimConfig() != null) {
+ result.putPersistableBundle(
+ EAP_SIM_CONFIG_KEY,
+ EapSimConfigUtils.toPersistableBundle(config.getEapSimConfig()));
+ }
+
+ if (config.getEapTtlsConfig() != null) {
+ result.putPersistableBundle(
+ EAP_TTLS_CONFIG_KEY,
+ EapTtlsConfigUtils.toPersistableBundle(config.getEapTtlsConfig()));
+ }
+
+ if (config.getEapAkaConfig() != null) {
+ result.putPersistableBundle(
+ EAP_AKA_CONFIG_KEY,
+ EapAkaConfigUtils.toPersistableBundle(config.getEapAkaConfig()));
+ }
+
+ if (config.getEapMsChapV2Config() != null) {
+ result.putPersistableBundle(
+ EAP_MSCHAP_V2_CONFIG_KEY,
+ EapMsChapV2ConfigUtils.toPersistableBundle(config.getEapMsChapV2Config()));
+ }
+
+ if (config.getEapAkaPrimeConfig() != null) {
+ result.putPersistableBundle(
+ EAP_AKA_PRIME_CONFIG_KEY,
+ EapAkaPrimeConfigUtils.toPersistableBundle(config.getEapAkaPrimeConfig()));
+ }
+
+ return result;
+ }
+
+ /** Constructs an EapSessionConfig by deserializing a PersistableBundle. */
+ @NonNull
+ public static EapSessionConfig fromPersistableBundle(@NonNull PersistableBundle in) {
+ Objects.requireNonNull(in, "PersistableBundle was null");
+
+ final EapSessionConfig.Builder builder = new EapSessionConfig.Builder();
+
+ final PersistableBundle eapIdBundle = in.getPersistableBundle(EAP_ID_KEY);
+ Objects.requireNonNull(eapIdBundle, "EAP ID was null");
+ builder.setEapIdentity(PersistableBundleUtils.toByteArray(eapIdBundle));
+
+ final PersistableBundle simBundle = in.getPersistableBundle(EAP_SIM_CONFIG_KEY);
+ if (simBundle != null) {
+ EapSimConfigUtils.setBuilderByReadingPersistableBundle(simBundle, builder);
+ }
+
+ final PersistableBundle ttlsBundle = in.getPersistableBundle(EAP_TTLS_CONFIG_KEY);
+ if (ttlsBundle != null) {
+ EapTtlsConfigUtils.setBuilderByReadingPersistableBundle(ttlsBundle, builder);
+ }
+
+ final PersistableBundle akaBundle = in.getPersistableBundle(EAP_AKA_CONFIG_KEY);
+ if (akaBundle != null) {
+ EapAkaConfigUtils.setBuilderByReadingPersistableBundle(akaBundle, builder);
+ }
+
+ final PersistableBundle msChapV2Bundle = in.getPersistableBundle(EAP_MSCHAP_V2_CONFIG_KEY);
+ if (msChapV2Bundle != null) {
+ EapMsChapV2ConfigUtils.setBuilderByReadingPersistableBundle(msChapV2Bundle, builder);
+ }
+
+ final PersistableBundle akaPrimeBundle = in.getPersistableBundle(EAP_AKA_PRIME_CONFIG_KEY);
+ if (akaPrimeBundle != null) {
+ EapAkaPrimeConfigUtils.setBuilderByReadingPersistableBundle(akaPrimeBundle, builder);
+ }
+
+ return builder.build();
+ }
+
+ private static class EapMethodConfigUtils {
+ private static final String METHOD_TYPE = "METHOD_TYPE";
+
+ /** Serializes an EapMethodConfig to a PersistableBundle. */
+ @NonNull
+ public static PersistableBundle toPersistableBundle(@NonNull EapMethodConfig config) {
+ final PersistableBundle result = new PersistableBundle();
+ result.putInt(METHOD_TYPE, config.getMethodType());
+ return result;
+ }
+ }
+
+ private static class EapUiccConfigUtils extends EapMethodConfigUtils {
+ static final String SUB_ID_KEY = "SUB_ID_KEY";
+ static final String APP_TYPE_KEY = "APP_TYPE_KEY";
+
+ @NonNull
+ protected static PersistableBundle toPersistableBundle(@NonNull EapUiccConfig config) {
+ final PersistableBundle result = EapMethodConfigUtils.toPersistableBundle(config);
+ result.putInt(SUB_ID_KEY, config.getSubId());
+ result.putInt(APP_TYPE_KEY, config.getAppType());
+
+ return result;
+ }
+ }
+
+ private static final class EapSimConfigUtils extends EapUiccConfigUtils {
+ @NonNull
+ public static PersistableBundle toPersistableBundle(EapSimConfig config) {
+ return EapUiccConfigUtils.toPersistableBundle(config);
+ }
+
+ public static void setBuilderByReadingPersistableBundle(
+ @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) {
+ Objects.requireNonNull(in, "PersistableBundle was null");
+ builder.setEapSimConfig(in.getInt(SUB_ID_KEY), in.getInt(APP_TYPE_KEY));
+ }
+ }
+
+ private static class EapAkaConfigUtils extends EapUiccConfigUtils {
+ @NonNull
+ public static PersistableBundle toPersistableBundle(@NonNull EapAkaConfig config) {
+ return EapUiccConfigUtils.toPersistableBundle(config);
+ }
+
+ public static void setBuilderByReadingPersistableBundle(
+ @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) {
+ Objects.requireNonNull(in, "PersistableBundle was null");
+ builder.setEapAkaConfig(in.getInt(SUB_ID_KEY), in.getInt(APP_TYPE_KEY));
+ }
+ }
+
+ private static final class EapAkaPrimeConfigUtils extends EapAkaConfigUtils {
+ private static final String NETWORK_NAME_KEY = "NETWORK_NAME_KEY";
+ private static final String ALL_MISMATCHED_NETWORK_KEY = "ALL_MISMATCHED_NETWORK_KEY";
+
+ @NonNull
+ public static PersistableBundle toPersistableBundle(@NonNull EapAkaPrimeConfig config) {
+ final PersistableBundle result = EapUiccConfigUtils.toPersistableBundle(config);
+ result.putString(NETWORK_NAME_KEY, config.getNetworkName());
+ result.putBoolean(ALL_MISMATCHED_NETWORK_KEY, config.allowsMismatchedNetworkNames());
+
+ return result;
+ }
+
+ public static void setBuilderByReadingPersistableBundle(
+ @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) {
+ Objects.requireNonNull(in, "PersistableBundle was null");
+ builder.setEapAkaPrimeConfig(
+ in.getInt(SUB_ID_KEY),
+ in.getInt(APP_TYPE_KEY),
+ in.getString(NETWORK_NAME_KEY),
+ in.getBoolean(ALL_MISMATCHED_NETWORK_KEY));
+ }
+ }
+
+ private static final class EapMsChapV2ConfigUtils extends EapMethodConfigUtils {
+ private static final String USERNAME_KEY = "USERNAME_KEY";
+ private static final String PASSWORD_KEY = "PASSWORD_KEY";
+
+ @NonNull
+ public static PersistableBundle toPersistableBundle(@NonNull EapMsChapV2Config config) {
+ final PersistableBundle result = EapMethodConfigUtils.toPersistableBundle(config);
+ result.putString(USERNAME_KEY, config.getUsername());
+ result.putString(PASSWORD_KEY, config.getPassword());
+
+ return result;
+ }
+
+ public static void setBuilderByReadingPersistableBundle(
+ @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) {
+ Objects.requireNonNull(in, "PersistableBundle was null");
+ builder.setEapMsChapV2Config(in.getString(USERNAME_KEY), in.getString(PASSWORD_KEY));
+ }
+ }
+
+ private static final class EapTtlsConfigUtils extends EapMethodConfigUtils {
+ private static final String TRUST_CERT_KEY = "TRUST_CERT_KEY";
+ private static final String EAP_SESSION_CONFIG_KEY = "EAP_SESSION_CONFIG_KEY";
+
+ @NonNull
+ public static PersistableBundle toPersistableBundle(@NonNull EapTtlsConfig config) {
+ final PersistableBundle result = EapMethodConfigUtils.toPersistableBundle(config);
+ try {
+ if (config.getServerCaCert() != null) {
+ final PersistableBundle caBundle =
+ PersistableBundleUtils.fromByteArray(
+ config.getServerCaCert().getEncoded());
+ result.putPersistableBundle(TRUST_CERT_KEY, caBundle);
+ }
+ } catch (CertificateEncodingException e) {
+ throw new IllegalStateException("Fail to encode the certificate");
+ }
+
+ result.putPersistableBundle(
+ EAP_SESSION_CONFIG_KEY,
+ EapSessionConfigUtils.toPersistableBundle(config.getInnerEapSessionConfig()));
+
+ return result;
+ }
+
+ public static void setBuilderByReadingPersistableBundle(
+ @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) {
+ Objects.requireNonNull(in, "PersistableBundle was null");
+
+ final PersistableBundle caBundle = in.getPersistableBundle(TRUST_CERT_KEY);
+ X509Certificate caCert = null;
+ if (caBundle != null) {
+ caCert =
+ CertUtils.certificateFromByteArray(
+ PersistableBundleUtils.toByteArray(caBundle));
+ }
+
+ final PersistableBundle eapSessionConfigBundle =
+ in.getPersistableBundle(EAP_SESSION_CONFIG_KEY);
+ Objects.requireNonNull(eapSessionConfigBundle, "Inner EAP Session Config was null");
+ final EapSessionConfig eapSessionConfig =
+ EapSessionConfigUtils.fromPersistableBundle(eapSessionConfigBundle);
+
+ builder.setEapTtlsConfig(caCert, eapSessionConfig);
+ }
+ }
+}
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java
new file mode 100644
index 0000000..6acb34e
--- /dev/null
+++ b/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.persistablebundleutils;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.net.InetAddresses;
+import android.net.ipsec.ike.IkeDerAsn1DnIdentification;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeIdentification;
+import android.net.ipsec.ike.IkeIpv4AddrIdentification;
+import android.net.ipsec.ike.IkeIpv6AddrIdentification;
+import android.net.ipsec.ike.IkeKeyIdIdentification;
+import android.net.ipsec.ike.IkeRfc822AddrIdentification;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.vcn.util.PersistableBundleUtils;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.util.Objects;
+
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * Abstract utility class to convert IkeIdentification to/from PersistableBundle.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = Visibility.PRIVATE)
+public final class IkeIdentificationUtils {
+ private static final String ID_TYPE_KEY = "ID_TYPE_KEY";
+
+ private static final String DER_ASN1_DN_KEY = "DER_ASN1_DN_KEY";
+ private static final String FQDN_KEY = "FQDN_KEY";
+ private static final String KEY_ID_KEY = "KEY_ID_KEY";
+ private static final String IP4_ADDRESS_KEY = "IP4_ADDRESS_KEY";
+ private static final String IP6_ADDRESS_KEY = "IP6_ADDRESS_KEY";
+ private static final String RFC822_ADDRESS_KEY = "RFC822_ADDRESS_KEY";
+
+ private static final int ID_TYPE_DER_ASN1_DN = 1;
+ private static final int ID_TYPE_FQDN = 2;
+ private static final int ID_TYPE_IPV4_ADDR = 3;
+ private static final int ID_TYPE_IPV6_ADDR = 4;
+ private static final int ID_TYPE_KEY_ID = 5;
+ private static final int ID_TYPE_RFC822_ADDR = 6;
+
+ /** Serializes an IkeIdentification to a PersistableBundle. */
+ @NonNull
+ public static PersistableBundle toPersistableBundle(@NonNull IkeIdentification ikeId) {
+ if (ikeId instanceof IkeDerAsn1DnIdentification) {
+ final PersistableBundle result = createPersistableBundle(ID_TYPE_DER_ASN1_DN);
+ IkeDerAsn1DnIdentification id = (IkeDerAsn1DnIdentification) ikeId;
+ result.putPersistableBundle(
+ DER_ASN1_DN_KEY,
+ PersistableBundleUtils.fromByteArray(id.derAsn1Dn.getEncoded()));
+ return result;
+ } else if (ikeId instanceof IkeFqdnIdentification) {
+ final PersistableBundle result = createPersistableBundle(ID_TYPE_FQDN);
+ IkeFqdnIdentification id = (IkeFqdnIdentification) ikeId;
+ result.putString(FQDN_KEY, id.fqdn);
+ return result;
+ } else if (ikeId instanceof IkeIpv4AddrIdentification) {
+ final PersistableBundle result = createPersistableBundle(ID_TYPE_IPV4_ADDR);
+ IkeIpv4AddrIdentification id = (IkeIpv4AddrIdentification) ikeId;
+ result.putString(IP4_ADDRESS_KEY, id.ipv4Address.getHostAddress());
+ return result;
+ } else if (ikeId instanceof IkeIpv6AddrIdentification) {
+ final PersistableBundle result = createPersistableBundle(ID_TYPE_IPV6_ADDR);
+ IkeIpv6AddrIdentification id = (IkeIpv6AddrIdentification) ikeId;
+ result.putString(IP6_ADDRESS_KEY, id.ipv6Address.getHostAddress());
+ return result;
+ } else if (ikeId instanceof IkeKeyIdIdentification) {
+ final PersistableBundle result = createPersistableBundle(ID_TYPE_KEY_ID);
+ IkeKeyIdIdentification id = (IkeKeyIdIdentification) ikeId;
+ result.putPersistableBundle(KEY_ID_KEY, PersistableBundleUtils.fromByteArray(id.keyId));
+ return result;
+ } else if (ikeId instanceof IkeRfc822AddrIdentification) {
+ final PersistableBundle result = createPersistableBundle(ID_TYPE_RFC822_ADDR);
+ IkeRfc822AddrIdentification id = (IkeRfc822AddrIdentification) ikeId;
+ result.putString(RFC822_ADDRESS_KEY, id.rfc822Name);
+ return result;
+ } else {
+ throw new IllegalStateException("Unrecognized IkeIdentification subclass");
+ }
+ }
+
+ private static PersistableBundle createPersistableBundle(int idType) {
+ final PersistableBundle result = new PersistableBundle();
+ result.putInt(ID_TYPE_KEY, idType);
+ return result;
+ }
+
+ /** Constructs an IkeIdentification by deserializing a PersistableBundle. */
+ @NonNull
+ public static IkeIdentification fromPersistableBundle(@NonNull PersistableBundle in) {
+ Objects.requireNonNull(in, "PersistableBundle was null");
+ int idType = in.getInt(ID_TYPE_KEY);
+ switch (idType) {
+ case ID_TYPE_DER_ASN1_DN:
+ final PersistableBundle dnBundle = in.getPersistableBundle(DER_ASN1_DN_KEY);
+ Objects.requireNonNull(dnBundle, "ASN1 DN was null");
+ return new IkeDerAsn1DnIdentification(
+ new X500Principal(PersistableBundleUtils.toByteArray(dnBundle)));
+ case ID_TYPE_FQDN:
+ return new IkeFqdnIdentification(in.getString(FQDN_KEY));
+ case ID_TYPE_IPV4_ADDR:
+ final String v4AddressStr = in.getString(IP4_ADDRESS_KEY);
+ Objects.requireNonNull(v4AddressStr, "IPv4 address was null");
+ return new IkeIpv4AddrIdentification(
+ (Inet4Address) InetAddresses.parseNumericAddress(v4AddressStr));
+ case ID_TYPE_IPV6_ADDR:
+ final String v6AddressStr = in.getString(IP6_ADDRESS_KEY);
+ Objects.requireNonNull(v6AddressStr, "IPv6 address was null");
+ return new IkeIpv6AddrIdentification(
+ (Inet6Address) InetAddresses.parseNumericAddress(v6AddressStr));
+ case ID_TYPE_KEY_ID:
+ final PersistableBundle keyIdBundle = in.getPersistableBundle(KEY_ID_KEY);
+ Objects.requireNonNull(in, "Key ID was null");
+ return new IkeKeyIdIdentification(PersistableBundleUtils.toByteArray(keyIdBundle));
+ case ID_TYPE_RFC822_ADDR:
+ return new IkeRfc822AddrIdentification(in.getString(RFC822_ADDRESS_KEY));
+ default:
+ throw new IllegalStateException("Unrecognized IKE ID type: " + idType);
+ }
+ }
+}
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java
new file mode 100644
index 0000000..1459671
--- /dev/null
+++ b/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.persistablebundleutils;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.vcn.util.PersistableBundleUtils;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Provides utility methods to convert IkeSaProposal to/from PersistableBundle.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = Visibility.PRIVATE)
+public final class IkeSaProposalUtils extends SaProposalUtilsBase {
+ private static final String PRF_KEY = "PRF_KEY";
+
+ /** Serializes an IkeSaProposal to a PersistableBundle. */
+ @NonNull
+ public static PersistableBundle toPersistableBundle(IkeSaProposal proposal) {
+ final PersistableBundle result = SaProposalUtilsBase.toPersistableBundle(proposal);
+
+ final int[] prfArray =
+ proposal.getPseudorandomFunctions().stream().mapToInt(i -> i).toArray();
+ result.putIntArray(PRF_KEY, prfArray);
+
+ return result;
+ }
+
+ /** Constructs an IkeSaProposal by deserializing a PersistableBundle. */
+ @NonNull
+ public static IkeSaProposal fromPersistableBundle(@NonNull PersistableBundle in) {
+ Objects.requireNonNull(in, "PersistableBundle was null");
+
+ final IkeSaProposal.Builder builder = new IkeSaProposal.Builder();
+
+ final PersistableBundle encryptionBundle = in.getPersistableBundle(ENCRYPT_ALGO_KEY);
+ Objects.requireNonNull(encryptionBundle, "Encryption algo bundle was null");
+ final List<EncryptionAlgoKeyLenPair> encryptList =
+ PersistableBundleUtils.toList(encryptionBundle, EncryptionAlgoKeyLenPair::new);
+ for (EncryptionAlgoKeyLenPair t : encryptList) {
+ builder.addEncryptionAlgorithm(t.encryptionAlgo, t.keyLen);
+ }
+
+ final int[] integrityAlgoIdArray = in.getIntArray(INTEGRITY_ALGO_KEY);
+ Objects.requireNonNull(integrityAlgoIdArray, "Integrity algo array was null");
+ for (int algo : integrityAlgoIdArray) {
+ builder.addIntegrityAlgorithm(algo);
+ }
+
+ final int[] dhGroupArray = in.getIntArray(DH_GROUP_KEY);
+ Objects.requireNonNull(dhGroupArray, "DH Group array was null");
+ for (int dh : dhGroupArray) {
+ builder.addDhGroup(dh);
+ }
+
+ final int[] prfArray = in.getIntArray(PRF_KEY);
+ Objects.requireNonNull(prfArray, "PRF array was null");
+ for (int prf : prfArray) {
+ builder.addPseudorandomFunction(prf);
+ }
+
+ return builder.build();
+ }
+}
diff --git a/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java b/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java
new file mode 100644
index 0000000..0c9ee84
--- /dev/null
+++ b/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.persistablebundleutils;
+
+import android.annotation.NonNull;
+import android.net.ipsec.ike.SaProposal;
+import android.os.PersistableBundle;
+import android.util.Pair;
+
+import com.android.server.vcn.util.PersistableBundleUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Abstract utility class to convert SaProposal to/from PersistableBundle.
+ *
+ * @hide
+ */
+abstract class SaProposalUtilsBase {
+ static final String ENCRYPT_ALGO_KEY = "ENCRYPT_ALGO_KEY";
+ static final String INTEGRITY_ALGO_KEY = "INTEGRITY_ALGO_KEY";
+ static final String DH_GROUP_KEY = "DH_GROUP_KEY";
+
+ static class EncryptionAlgoKeyLenPair {
+ private static final String ALGO_KEY = "ALGO_KEY";
+ private static final String KEY_LEN_KEY = "KEY_LEN_KEY";
+
+ public final int encryptionAlgo;
+ public final int keyLen;
+
+ EncryptionAlgoKeyLenPair(int encryptionAlgo, int keyLen) {
+ this.encryptionAlgo = encryptionAlgo;
+ this.keyLen = keyLen;
+ }
+
+ EncryptionAlgoKeyLenPair(PersistableBundle in) {
+ Objects.requireNonNull(in, "PersistableBundle was null");
+
+ this.encryptionAlgo = in.getInt(ALGO_KEY);
+ this.keyLen = in.getInt(KEY_LEN_KEY);
+ }
+
+ public PersistableBundle toPersistableBundle() {
+ final PersistableBundle result = new PersistableBundle();
+
+ result.putInt(ALGO_KEY, encryptionAlgo);
+ result.putInt(KEY_LEN_KEY, keyLen);
+
+ return result;
+ }
+ }
+
+ /**
+ * Serializes common info of a SaProposal to a PersistableBundle.
+ *
+ * @hide
+ */
+ @NonNull
+ static PersistableBundle toPersistableBundle(SaProposal proposal) {
+ final PersistableBundle result = new PersistableBundle();
+
+ final List<EncryptionAlgoKeyLenPair> encryptAlgoKeyLenPairs = new ArrayList<>();
+ for (Pair<Integer, Integer> pair : proposal.getEncryptionAlgorithms()) {
+ encryptAlgoKeyLenPairs.add(new EncryptionAlgoKeyLenPair(pair.first, pair.second));
+ }
+ final PersistableBundle encryptionBundle =
+ PersistableBundleUtils.fromList(
+ encryptAlgoKeyLenPairs, EncryptionAlgoKeyLenPair::toPersistableBundle);
+ result.putPersistableBundle(ENCRYPT_ALGO_KEY, encryptionBundle);
+
+ final int[] integrityAlgoIdArray =
+ proposal.getIntegrityAlgorithms().stream().mapToInt(i -> i).toArray();
+ result.putIntArray(INTEGRITY_ALGO_KEY, integrityAlgoIdArray);
+
+ final int[] dhGroupArray = proposal.getDhGroups().stream().mapToInt(i -> i).toArray();
+ result.putIntArray(DH_GROUP_KEY, dhGroupArray);
+
+ return result;
+ }
+}
diff --git a/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java
new file mode 100644
index 0000000..e62acac
--- /dev/null
+++ b/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.persistablebundleutils;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.InetAddresses;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.IkeTrafficSelector;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4Address;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4DhcpServer;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4DnsServer;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4Netmask;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv6Address;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv6DnsServer;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest;
+import android.os.PersistableBundle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.vcn.util.PersistableBundleUtils;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Provides utility methods to convert TunnelModeChildSessionParams to/from PersistableBundle.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = Visibility.PRIVATE)
+public final class TunnelModeChildSessionParamsUtils {
+ private static final String TAG = TunnelModeChildSessionParamsUtils.class.getSimpleName();
+
+ private static final String INBOUND_TS_KEY = "INBOUND_TS_KEY";
+ private static final String OUTBOUND_TS_KEY = "OUTBOUND_TS_KEY";
+ private static final String SA_PROPOSALS_KEY = "SA_PROPOSALS_KEY";
+ private static final String HARD_LIFETIME_SEC_KEY = "HARD_LIFETIME_SEC_KEY";
+ private static final String SOFT_LIFETIME_SEC_KEY = "SOFT_LIFETIME_SEC_KEY";
+ private static final String CONFIG_REQUESTS_KEY = "CONFIG_REQUESTS_KEY";
+
+ private static class ConfigRequest {
+ private static final int TYPE_IPV4_ADDRESS = 1;
+ private static final int TYPE_IPV6_ADDRESS = 2;
+ private static final int TYPE_IPV4_DNS = 3;
+ private static final int TYPE_IPV6_DNS = 4;
+ private static final int TYPE_IPV4_DHCP = 5;
+ private static final int TYPE_IPV4_NETMASK = 6;
+
+ private static final String TYPE_KEY = "type";
+ private static final String VALUE_KEY = "address";
+ private static final String IP6_PREFIX_LEN = "ip6PrefixLen";
+
+ private static final int PREFIX_LEN_UNUSED = -1;
+
+ public final int type;
+ public final int ip6PrefixLen;
+
+ // Null when it is an empty request
+ @Nullable public final InetAddress address;
+
+ ConfigRequest(TunnelModeChildConfigRequest config) {
+ int prefixLen = PREFIX_LEN_UNUSED;
+
+ if (config instanceof ConfigRequestIpv4Address) {
+ type = TYPE_IPV4_ADDRESS;
+ address = ((ConfigRequestIpv4Address) config).getAddress();
+ } else if (config instanceof ConfigRequestIpv6Address) {
+ type = TYPE_IPV6_ADDRESS;
+ address = ((ConfigRequestIpv6Address) config).getAddress();
+ if (address != null) {
+ prefixLen = ((ConfigRequestIpv6Address) config).getPrefixLength();
+ }
+ } else if (config instanceof ConfigRequestIpv4DnsServer) {
+ type = TYPE_IPV4_DNS;
+ address = null;
+ } else if (config instanceof ConfigRequestIpv6DnsServer) {
+ type = TYPE_IPV6_DNS;
+ address = null;
+ } else if (config instanceof ConfigRequestIpv4DhcpServer) {
+ type = TYPE_IPV4_DHCP;
+ address = null;
+ } else if (config instanceof ConfigRequestIpv4Netmask) {
+ type = TYPE_IPV4_NETMASK;
+ address = null;
+ } else {
+ throw new IllegalStateException("Unknown TunnelModeChildConfigRequest");
+ }
+
+ ip6PrefixLen = prefixLen;
+ }
+
+ ConfigRequest(PersistableBundle in) {
+ Objects.requireNonNull(in, "PersistableBundle was null");
+
+ type = in.getInt(TYPE_KEY);
+ ip6PrefixLen = in.getInt(IP6_PREFIX_LEN);
+
+ String addressStr = in.getString(VALUE_KEY);
+ if (addressStr == null) {
+ address = null;
+ } else {
+ address = InetAddresses.parseNumericAddress(addressStr);
+ }
+ }
+
+ @NonNull
+ public PersistableBundle toPersistableBundle() {
+ final PersistableBundle result = new PersistableBundle();
+
+ result.putInt(TYPE_KEY, type);
+ result.putInt(IP6_PREFIX_LEN, ip6PrefixLen);
+
+ if (address != null) {
+ result.putString(VALUE_KEY, address.getHostAddress());
+ }
+
+ return result;
+ }
+ }
+
+ /** Serializes a TunnelModeChildSessionParams to a PersistableBundle. */
+ @NonNull
+ public static PersistableBundle toPersistableBundle(
+ @NonNull TunnelModeChildSessionParams params) {
+ final PersistableBundle result = new PersistableBundle();
+
+ final PersistableBundle saProposalBundle =
+ PersistableBundleUtils.fromList(
+ params.getSaProposals(), ChildSaProposalUtils::toPersistableBundle);
+ result.putPersistableBundle(SA_PROPOSALS_KEY, saProposalBundle);
+
+ final PersistableBundle inTsBundle =
+ PersistableBundleUtils.fromList(
+ params.getInboundTrafficSelectors(),
+ IkeTrafficSelectorUtils::toPersistableBundle);
+ result.putPersistableBundle(INBOUND_TS_KEY, inTsBundle);
+
+ final PersistableBundle outTsBundle =
+ PersistableBundleUtils.fromList(
+ params.getOutboundTrafficSelectors(),
+ IkeTrafficSelectorUtils::toPersistableBundle);
+ result.putPersistableBundle(OUTBOUND_TS_KEY, outTsBundle);
+
+ result.putInt(HARD_LIFETIME_SEC_KEY, params.getHardLifetimeSeconds());
+ result.putInt(SOFT_LIFETIME_SEC_KEY, params.getSoftLifetimeSeconds());
+
+ final List<ConfigRequest> reqList = new ArrayList<>();
+ for (TunnelModeChildConfigRequest req : params.getConfigurationRequests()) {
+ reqList.add(new ConfigRequest(req));
+ }
+ final PersistableBundle configReqListBundle =
+ PersistableBundleUtils.fromList(reqList, ConfigRequest::toPersistableBundle);
+ result.putPersistableBundle(CONFIG_REQUESTS_KEY, configReqListBundle);
+
+ return result;
+ }
+
+ private static List<IkeTrafficSelector> getTsFromPersistableBundle(
+ PersistableBundle in, String key) {
+ PersistableBundle tsBundle = in.getPersistableBundle(key);
+ Objects.requireNonNull(tsBundle, "Value for key " + key + " was null");
+ return PersistableBundleUtils.toList(
+ tsBundle, IkeTrafficSelectorUtils::fromPersistableBundle);
+ }
+
+ /** Constructs a TunnelModeChildSessionParams by deserializing a PersistableBundle. */
+ @NonNull
+ public static TunnelModeChildSessionParams fromPersistableBundle(
+ @NonNull PersistableBundle in) {
+ Objects.requireNonNull(in, "PersistableBundle was null");
+
+ final TunnelModeChildSessionParams.Builder builder =
+ new TunnelModeChildSessionParams.Builder();
+
+ final PersistableBundle proposalBundle = in.getPersistableBundle(SA_PROPOSALS_KEY);
+ Objects.requireNonNull(proposalBundle, "SA proposal was null");
+ final List<ChildSaProposal> proposals =
+ PersistableBundleUtils.toList(
+ proposalBundle, ChildSaProposalUtils::fromPersistableBundle);
+ for (ChildSaProposal p : proposals) {
+ builder.addSaProposal(p);
+ }
+
+ for (IkeTrafficSelector ts : getTsFromPersistableBundle(in, INBOUND_TS_KEY)) {
+ builder.addInboundTrafficSelectors(ts);
+ }
+
+ for (IkeTrafficSelector ts : getTsFromPersistableBundle(in, OUTBOUND_TS_KEY)) {
+ builder.addOutboundTrafficSelectors(ts);
+ }
+
+ builder.setLifetimeSeconds(
+ in.getInt(HARD_LIFETIME_SEC_KEY), in.getInt(SOFT_LIFETIME_SEC_KEY));
+ final PersistableBundle configReqListBundle = in.getPersistableBundle(CONFIG_REQUESTS_KEY);
+ Objects.requireNonNull(configReqListBundle, "Config request list was null");
+ final List<ConfigRequest> reqList =
+ PersistableBundleUtils.toList(configReqListBundle, ConfigRequest::new);
+
+ boolean hasIpv4AddressReq = false;
+ boolean hasIpv4NetmaskReq = false;
+ for (ConfigRequest req : reqList) {
+ switch (req.type) {
+ case ConfigRequest.TYPE_IPV4_ADDRESS:
+ hasIpv4AddressReq = true;
+ if (req.address == null) {
+ builder.addInternalAddressRequest(AF_INET);
+ } else {
+ builder.addInternalAddressRequest((Inet4Address) req.address);
+ }
+ break;
+ case ConfigRequest.TYPE_IPV6_ADDRESS:
+ if (req.address == null) {
+ builder.addInternalAddressRequest(AF_INET6);
+ } else {
+ builder.addInternalAddressRequest(
+ (Inet6Address) req.address, req.ip6PrefixLen);
+ }
+ break;
+ case ConfigRequest.TYPE_IPV4_NETMASK:
+ // Do not need to set netmask because it will be automatically set by the
+ // builder when an IPv4 internal address request is set.
+ hasIpv4NetmaskReq = true;
+ break;
+ case ConfigRequest.TYPE_IPV4_DNS:
+ if (req.address != null) {
+ Log.w(TAG, "Requesting a specific IPv4 DNS server is unsupported");
+ }
+ builder.addInternalDnsServerRequest(AF_INET);
+ break;
+ case ConfigRequest.TYPE_IPV6_DNS:
+ if (req.address != null) {
+ Log.w(TAG, "Requesting a specific IPv6 DNS server is unsupported");
+ }
+ builder.addInternalDnsServerRequest(AF_INET6);
+ break;
+ case ConfigRequest.TYPE_IPV4_DHCP:
+ if (req.address != null) {
+ Log.w(TAG, "Requesting a specific IPv4 DHCP server is unsupported");
+ }
+ builder.addInternalDhcpServerRequest(AF_INET);
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unrecognized config request type: " + req.type);
+ }
+ }
+
+ if (hasIpv4AddressReq != hasIpv4NetmaskReq) {
+ Log.w(
+ TAG,
+ String.format(
+ "Expect IPv4 address request and IPv4 netmask request either both"
+ + " exist or both absent, but found hasIpv4AddressReq exists? %b,"
+ + " hasIpv4AddressReq exists? %b, ",
+ hasIpv4AddressReq, hasIpv4NetmaskReq));
+ }
+
+ return builder.build();
+ }
+}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 74df1b2..a5b0e8d 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1288,10 +1288,12 @@
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 c8e682c..e068772 100644
--- a/core/java/android/os/CombinedVibrationEffect.java
+++ b/core/java/android/os/CombinedVibrationEffect.java
@@ -76,7 +76,9 @@
* 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 @@
* A combination of haptic effects that should be played in multiple vibrators in sequence.
*
* @see CombinedVibrationEffect#startSequential()
+ * @hide
*/
+ @TestApi
public static final class SequentialCombination {
private final ArrayList<CombinedVibrationEffect> mEffects = new ArrayList<>();
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 0587610..03e5f1d 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -503,6 +503,20 @@
}
/** @hide */
+ public static String effectStrengthToString(int effectStrength) {
+ switch (effectStrength) {
+ case EFFECT_STRENGTH_LIGHT:
+ return "LIGHT";
+ case EFFECT_STRENGTH_MEDIUM:
+ return "MEDIUM";
+ case EFFECT_STRENGTH_STRONG:
+ return "STRONG";
+ default:
+ return Integer.toString(effectStrength);
+ }
+ }
+
+ /** @hide */
@TestApi
public static class OneShot extends VibrationEffect implements Parcelable {
private final long mDuration;
@@ -936,8 +950,8 @@
@Override
public String toString() {
- return "Prebaked{mEffectId=" + mEffectId
- + ", mEffectStrength=" + mEffectStrength
+ return "Prebaked{mEffectId=" + effectIdToString(mEffectId)
+ + ", mEffectStrength=" + effectStrengthToString(mEffectStrength)
+ ", mFallback=" + mFallback
+ ", mFallbackEffect=" + mFallbackEffect
+ "}";
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 07272e7..50d2de3 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.vibrator.IVibrator;
import android.util.SparseBooleanArray;
import java.util.ArrayList;
@@ -33,20 +34,7 @@
* @hide
*/
public final class VibratorInfo implements Parcelable {
-
- /**
- * Capability to set amplitude values to vibrations.
- * @hide
- */
- // Internally this maps to the HAL constant IVibrator::CAP_AMPLITUDE_CONTROL
- public static final int CAPABILITY_AMPLITUDE_CONTROL = 4;
-
- /**
- * Capability to compose primitives into a single effect.
- * @hide
- */
- // Internally this maps to the HAL constant IVibrator::CAP_COMPOSE_EFFECTS
- public static final int CAPABILITY_COMPOSE_EFFECTS = 32;
+ private static final String TAG = "VibratorInfo";
private final int mId;
private final long mCapabilities;
@@ -108,7 +96,7 @@
return "VibratorInfo{"
+ "mId=" + mId
+ ", mCapabilities=" + Arrays.toString(getCapabilitiesNames())
- + ", mCapabilities flags=" + mCapabilities
+ + ", mCapabilities flags=" + Long.toBinaryString(mCapabilities)
+ ", mSupportedEffects=" + Arrays.toString(getSupportedEffectsNames())
+ ", mSupportedPrimitives=" + Arrays.toString(getSupportedPrimitivesNames())
+ '}';
@@ -125,7 +113,7 @@
* @return True if the hardware can control the amplitude of the vibrations, otherwise false.
*/
public boolean hasAmplitudeControl() {
- return hasCapability(CAPABILITY_AMPLITUDE_CONTROL);
+ return hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL);
}
/**
@@ -153,7 +141,7 @@
* @return Whether the primitive is supported.
*/
public boolean isPrimitiveSupported(@VibrationEffect.Composition.Primitive int primitiveId) {
- return hasCapability(CAPABILITY_COMPOSE_EFFECTS) && mSupportedPrimitives != null
+ return hasCapability(IVibrator.CAP_COMPOSE_EFFECTS) && mSupportedPrimitives != null
&& mSupportedPrimitives.get(primitiveId, false);
}
@@ -170,11 +158,26 @@
private String[] getCapabilitiesNames() {
List<String> names = new ArrayList<>();
- if (hasCapability(CAPABILITY_AMPLITUDE_CONTROL)) {
+ if (hasCapability(IVibrator.CAP_ON_CALLBACK)) {
+ names.add("ON_CALLBACK");
+ }
+ if (hasCapability(IVibrator.CAP_PERFORM_CALLBACK)) {
+ names.add("PERFORM_CALLBACK");
+ }
+ if (hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
+ names.add("COMPOSE_EFFECTS");
+ }
+ if (hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
+ names.add("ALWAYS_ON_CONTROL");
+ }
+ if (hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
names.add("AMPLITUDE_CONTROL");
}
- if (hasCapability(CAPABILITY_COMPOSE_EFFECTS)) {
- names.add("COMPOSE_EFFECTS");
+ if (hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
+ names.add("EXTERNAL_CONTROL");
+ }
+ if (hasCapability(IVibrator.CAP_EXTERNAL_AMPLITUDE_CONTROL)) {
+ names.add("EXTERNAL_AMPLITUDE_CONTROL");
}
return names.toArray(new String[names.size()]);
}
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 9ffc5aa0..bf28981 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -333,7 +333,7 @@
* started.
* @param pkgDataInfoMap Map from related package names to private data directory
* volume UUID and inode number.
- * @param whitelistedDataInfoMap Map from allowlisted package names to private data directory
+ * @param allowlistedDataInfoList Map from allowlisted package names to private data directory
* volume UUID and inode number.
* @param bindMountAppsData whether zygote needs to mount CE and DE data.
* @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data.
@@ -359,7 +359,7 @@
@Nullable Map<String, Pair<String, Long>>
pkgDataInfoMap,
@Nullable Map<String, Pair<String, Long>>
- whitelistedDataInfoMap,
+ allowlistedDataInfoList,
boolean bindMountAppsData,
boolean bindMountAppStorageDirs,
@Nullable String[] zygoteArgs) {
@@ -373,7 +373,7 @@
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,
packageName, zygotePolicyFlags, isTopApp, disabledCompatChanges,
- pkgDataInfoMap, whitelistedDataInfoMap, bindMountAppsData,
+ pkgDataInfoMap, allowlistedDataInfoList, bindMountAppsData,
bindMountAppStorageDirs, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
@@ -615,7 +615,7 @@
* @param disabledCompatChanges a list of disabled compat changes for the process being started.
* @param pkgDataInfoMap Map from related package names to private data directory volume UUID
* and inode number.
- * @param whitelistedDataInfoMap Map from allowlisted package names to private data directory
+ * @param allowlistedDataInfoList Map from allowlisted package names to private data directory
* volume UUID and inode number.
* @param bindMountAppsData whether zygote needs to mount CE and DE data.
* @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data.
@@ -642,7 +642,7 @@
@Nullable Map<String, Pair<String, Long>>
pkgDataInfoMap,
@Nullable Map<String, Pair<String, Long>>
- whitelistedDataInfoMap,
+ allowlistedDataInfoList,
boolean bindMountAppsData,
boolean bindMountAppStorageDirs,
@Nullable String[] extraArgs)
@@ -733,12 +733,12 @@
}
argsForZygote.add(sb.toString());
}
- if (whitelistedDataInfoMap != null && whitelistedDataInfoMap.size() > 0) {
+ if (allowlistedDataInfoList != null && allowlistedDataInfoList.size() > 0) {
StringBuilder sb = new StringBuilder();
- sb.append(Zygote.WHITELISTED_DATA_INFO_MAP);
+ sb.append(Zygote.ALLOWLISTED_DATA_INFO_MAP);
sb.append("=");
boolean started = false;
- for (Map.Entry<String, Pair<String, Long>> entry : whitelistedDataInfoMap.entrySet()) {
+ for (Map.Entry<String, Pair<String, Long>> entry : allowlistedDataInfoList.entrySet()) {
if (started) {
sb.append(',');
}
@@ -1318,7 +1318,7 @@
true /* startChildZygote */, null /* packageName */,
ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS /* zygotePolicyFlags */, false /* isTopApp */,
null /* disabledCompatChanges */, null /* pkgDataInfoMap */,
- null /* whitelistedDataInfoMap */, true /* bindMountAppsData*/,
+ null /* allowlistedDataInfoList */, true /* bindMountAppsData*/,
/* bindMountAppStorageDirs */ false, extraArgs);
} catch (ZygoteStartFailedEx ex) {
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 0041699..98b4e0b 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.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 @@
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 7c8874c..c967deb 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -49,6 +49,7 @@
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 @@
}
}
+ /**
+ * 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 @@
* @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 @@
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public void notifyAppIoResumed(@NonNull String volumeUuid, int uid, int tid,
+ public void notifyAppIoResumed(@NonNull UUID volumeUuid, int uid, int tid,
@AppIoBlockedReason int reason) {
+ Objects.requireNonNull(volumeUuid);
try {
- mStorageManager.notifyAppIoResumed(volumeUuid, uid, tid, reason);
+ mStorageManager.notifyAppIoResumed(convert(volumeUuid), uid, tid, reason);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index b12bb2e..396ba2d 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.os.IVold;
+import java.util.List;
import java.util.Set;
/**
@@ -112,4 +113,10 @@
* @param bytes number of bytes which need to be freed
*/
public abstract void freeCache(@Nullable String volumeUuid, long bytes);
+
+ /**
+ * Returns the {@link VolumeInfo#getId()} values for the volumes matching
+ * {@link VolumeInfo#isPrimary()}
+ */
+ public abstract List<String> getPrimaryVolumeIds();
}
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index 084b18e..913b827 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -668,7 +668,7 @@
public void getPrivilegesDescriptionStringForProfile(
@NonNull String profileName,
@NonNull @CallbackExecutor Executor executor,
- @NonNull Consumer<String> callback) {
+ @NonNull Consumer<CharSequence> callback) {
mRemoteService.postAsync(service -> {
AndroidFuture<String> future = new AndroidFuture<>();
service.getPrivilegesDescriptionStringForProfile(profileName, future);
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 6e89faf..e9bbcc79 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -505,6 +505,22 @@
"connectivity_thermal_power_manager";
/**
+ * Namespace for all statsd java features that can be applied immediately.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_STATSD_JAVA = "statsd_java";
+
+ /**
+ * Namespace for all statsd java features that are applied on boot.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot";
+
+ /**
* Namespace for all statsd native features that can be applied immediately.
*
* @hide
diff --git a/core/java/android/service/storage/ExternalStorageService.java b/core/java/android/service/storage/ExternalStorageService.java
index 1e07a87..bbe184b 100644
--- a/core/java/android/service/storage/ExternalStorageService.java
+++ b/core/java/android/service/storage/ExternalStorageService.java
@@ -239,14 +239,13 @@
}
@Override
- public void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason,
- RemoteCallback callback) throws RemoteException {
+ public void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason)
+ throws RemoteException {
mHandler.post(() -> {
try {
onAnrDelayStarted(packageName, uid, tid, reason);
- sendResult(packageName, null /* throwable */, callback);
} catch (Throwable t) {
- sendResult(packageName, t, callback);
+ // Ignored
}
});
}
diff --git a/core/java/android/service/storage/IExternalStorageService.aidl b/core/java/android/service/storage/IExternalStorageService.aidl
index ba98efa..0766b75 100644
--- a/core/java/android/service/storage/IExternalStorageService.aidl
+++ b/core/java/android/service/storage/IExternalStorageService.aidl
@@ -32,6 +32,5 @@
in RemoteCallback callback);
void freeCache(@utf8InCpp String sessionId, in String volumeUuid, long bytes,
in RemoteCallback callback);
- void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason,
- in RemoteCallback callback);
+ void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason);
}
\ No newline at end of file
diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java
index 4a5c95f..d23a1e5 100644
--- a/core/java/android/view/ImeFocusController.java
+++ b/core/java/android/view/ImeFocusController.java
@@ -116,8 +116,9 @@
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 @@
if (DEBUG) Log.v(TAG, "Restarting due to isRestartOnNextWindowFocus as true");
forceFocus = true;
}
+
// Update mNextServedView when focusedView changed.
- final View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView;
onViewFocusChanged(viewForWindowFocus, true);
// Starting new input when the next focused view is same as served view but the currently
diff --git a/core/java/android/view/InputEventAssigner.java b/core/java/android/view/InputEventAssigner.java
new file mode 100644
index 0000000..c159a12
--- /dev/null
+++ b/core/java/android/view/InputEventAssigner.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+
+/**
+ * Process input events and assign input event id to a specific frame.
+ *
+ * The assigned input event id is determined by where the current gesture is relative to the vsync.
+ * In the middle of the gesture (we already processed some input events, and already received at
+ * least 1 vsync), the latest InputEvent is assigned to the next frame.
+ * If a gesture just started, then the ACTION_DOWN event will be assigned to the next frame.
+ *
+ * Consider the following sequence:
+ * DOWN -> VSYNC 1 -> MOVE 1 -> MOVE 2 -> VSYNC 2.
+ *
+ * For VSYNC 1, we will assign the "DOWN" input event.
+ * For VSYNC 2, we will assign the "MOVE 2" input event.
+ *
+ * Consider another sequence:
+ * DOWN -> MOVE 1 -> MOVE 2 -> VSYNC 1 -> MOVE 3 -> VSYNC 2.
+ *
+ * For VSYNC 1, we will still assign the "DOWN" input event. That means that "MOVE 1" and "MOVE 2"
+ * events are not attributed to any frame.
+ * For VSYNC 2, the "MOVE 3" input event will be assigned.
+ *
+ * @hide
+ */
+public class InputEventAssigner {
+ private static final String TAG = "InputEventAssigner";
+ private boolean mHasUnprocessedDown = false;
+ private int mEventId = INVALID_INPUT_EVENT_ID;
+
+ /**
+ * Notify InputEventAssigner that the Choreographer callback has been processed. This will reset
+ * the 'down' state to assign the latest input event to the current frame.
+ */
+ public void onChoreographerCallback() {
+ // Mark completion of this frame. Use newest input event from now on.
+ mHasUnprocessedDown = false;
+ }
+
+ /**
+ * Process the provided input event to determine which event id to assign to the current frame.
+ * @param event the input event currently being processed
+ * @return the id of the input event to use for the current frame
+ */
+ public int processEvent(InputEvent event) {
+ if (event instanceof KeyEvent) {
+ // We will not do any special handling for key events
+ return event.getId();
+ }
+
+ if (event instanceof MotionEvent) {
+ MotionEvent motionEvent = (MotionEvent) event;
+ final int action = motionEvent.getActionMasked();
+
+ if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+ mHasUnprocessedDown = false;
+ }
+ if (motionEvent.isFromSource(SOURCE_TOUCHSCREEN) && action == MotionEvent.ACTION_DOWN) {
+ mHasUnprocessedDown = true;
+ mEventId = event.getId();
+ // This will remain 'true' even if we receive a MOVE event, as long as choreographer
+ // hasn't invoked the 'CALLBACK_INPUT' callback.
+ }
+ // Don't update the event id if we haven't processed DOWN yet.
+ if (!mHasUnprocessedDown) {
+ mEventId = event.getId();
+ }
+ return mEventId;
+ }
+
+ throw new IllegalArgumentException("Received unexpected " + event);
+ }
+}
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index d67439c..6801c27 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -3589,6 +3589,7 @@
msg.append(", deviceId=").append(getDeviceId());
msg.append(", source=0x").append(Integer.toHexString(getSource()));
msg.append(", displayId=").append(getDisplayId());
+ msg.append(", eventId=").append(getId());
}
msg.append(" }");
return msg.toString();
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index 5ce4c50..6c8753b 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -27,7 +27,7 @@
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 @@
* @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 @@
* @param extraMarginEnd extra margin in px
*/
public void setTopLineExtraMarginEnd(int extraMarginEnd) {
- mTopLineView.setHeaderTextMarginEnd(extraMarginEnd + mHeadingEndMargin);
+ mTopLineView.setHeaderTextMarginEnd(extraMarginEnd);
}
/**
@@ -181,12 +181,15 @@
* @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/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index b1b670f..ff84aa5 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -31,6 +31,7 @@
import static android.view.RemoteAnimationTargetProto.START_LEASH;
import static android.view.RemoteAnimationTargetProto.TASK_ID;
import static android.view.RemoteAnimationTargetProto.WINDOW_CONFIGURATION;
+import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import android.annotation.IntDef;
import android.app.PictureInPictureParams;
@@ -195,12 +196,30 @@
*/
public PictureInPictureParams pictureInPictureParams;
+ /**
+ * The {@link android.view.WindowManager.LayoutParams.WindowType} of this window. It's only used
+ * for non-app window.
+ */
+ public final @WindowManager.LayoutParams.WindowType int windowType;
+
public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
Rect localBounds, Rect screenSpaceBounds,
WindowConfiguration windowConfig, boolean isNotInRecents,
SurfaceControl startLeash, Rect startBounds,
PictureInPictureParams pictureInPictureParams) {
+ this(taskId, mode, leash, isTranslucent, clipRect, contentInsets, prefixOrderIndex,
+ position, localBounds, screenSpaceBounds, windowConfig, isNotInRecents, startLeash,
+ startBounds, pictureInPictureParams, INVALID_WINDOW_TYPE);
+ }
+
+ public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
+ Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
+ Rect localBounds, Rect screenSpaceBounds,
+ WindowConfiguration windowConfig, boolean isNotInRecents,
+ SurfaceControl startLeash, Rect startBounds,
+ PictureInPictureParams pictureInPictureParams,
+ @WindowManager.LayoutParams.WindowType int windowType) {
this.mode = mode;
this.taskId = taskId;
this.leash = leash;
@@ -217,6 +236,7 @@
this.startLeash = startLeash;
this.startBounds = startBounds == null ? null : new Rect(startBounds);
this.pictureInPictureParams = pictureInPictureParams;
+ this.windowType = windowType;
}
public RemoteAnimationTarget(Parcel in) {
@@ -236,6 +256,7 @@
startLeash = in.readTypedObject(SurfaceControl.CREATOR);
startBounds = in.readTypedObject(Rect.CREATOR);
pictureInPictureParams = in.readTypedObject(PictureInPictureParams.CREATOR);
+ windowType = in.readInt();
}
@Override
@@ -261,6 +282,7 @@
dest.writeTypedObject(startLeash, 0 /* flags */);
dest.writeTypedObject(startBounds, 0 /* flags */);
dest.writeTypedObject(pictureInPictureParams, 0 /* flags */);
+ dest.writeInt(windowType);
}
public void dump(PrintWriter pw, String prefix) {
@@ -274,6 +296,7 @@
pw.print(" sourceContainerBounds="); sourceContainerBounds.printShortString(pw);
pw.print(" screenSpaceBounds="); screenSpaceBounds.printShortString(pw);
pw.print(" localBounds="); localBounds.printShortString(pw);
+ pw.print(" windowType="); pw.print(windowType);
pw.println();
pw.print(prefix); pw.print("windowConfiguration="); pw.println(windowConfiguration);
pw.print(prefix); pw.print("leash="); pw.println(leash);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ebef464..ab7732b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -22188,9 +22188,6 @@
* and hardware acceleration.
*/
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
- // Clear the overscroll effect:
- // TODO: Use internal API instead of overriding the existing RenderEffect
- setRenderEffect(null);
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
/* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList.
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index c2f17c3..ec23a29 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -310,6 +310,18 @@
*/
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 @@
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 @@
// 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 @@
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 @@
}
/**
+ * @return the timeout value in milliseconds to adjust the selection span and actions for the
+ * selected text when TextClassifier has been initialized.
+ * @hide
+ */
+ public int getSmartSelectionInitializedTimeout() {
+ return mSmartSelectionInitializedTimeout;
+ }
+
+ /**
+ * @return the timeout value in milliseconds to adjust the selection span and actions for the
+ * selected text when TextClassifier has not been initialized.
+ * @hide
+ */
+ public int getSmartSelectionInitializingTimeout() {
+ return mSmartSelectionInitializingTimeout;
+ }
+
+ /**
* @return the duration in milliseconds before an end of a long press causes a tooltip to be
* hidden
* @hide
diff --git a/core/java/android/view/ViewFrameInfo.java b/core/java/android/view/ViewFrameInfo.java
index d4aaa61..36bf532 100644
--- a/core/java/android/view/ViewFrameInfo.java
+++ b/core/java/android/view/ViewFrameInfo.java
@@ -17,6 +17,7 @@
package android.view;
import android.graphics.FrameInfo;
+import android.os.IInputConstants;
/**
* The timing information of events taking place in ViewRootImpl
@@ -24,32 +25,14 @@
*/
public class ViewFrameInfo {
public long drawStart;
- public long oldestInputEventTime; // the time of the oldest input event consumed for this frame
- public long newestInputEventTime; // the time of the newest input event consumed for this frame
+
+
// Various flags set to provide extra metadata about the current frame. See flag definitions
// inside FrameInfo.
// @see android.graphics.FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED
public long flags;
- /**
- * Update the oldest event time.
- * @param eventTime the time of the input event
- */
- public void updateOldestInputEvent(long eventTime) {
- if (oldestInputEventTime == 0 || eventTime < oldestInputEventTime) {
- oldestInputEventTime = eventTime;
- }
- }
-
- /**
- * Update the newest event time.
- * @param eventTime the time of the input event
- */
- public void updateNewestInputEvent(long eventTime) {
- if (newestInputEventTime == 0 || eventTime > newestInputEventTime) {
- newestInputEventTime = eventTime;
- }
- }
+ private int mInputEventId;
/**
* Populate the missing fields using the data from ViewFrameInfo
@@ -58,8 +41,7 @@
public void populateFrameInfo(FrameInfo frameInfo) {
frameInfo.frameInfo[FrameInfo.FLAGS] |= flags;
frameInfo.frameInfo[FrameInfo.DRAW_START] = drawStart;
- // TODO(b/169866723): Use InputEventAssigner
- frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID] = newestInputEventTime;
+ frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID] = mInputEventId;
}
/**
@@ -67,8 +49,7 @@
*/
public void reset() {
drawStart = 0;
- oldestInputEventTime = 0;
- newestInputEventTime = 0;
+ mInputEventId = IInputConstants.INVALID_INPUT_EVENT_ID;
flags = 0;
}
@@ -78,4 +59,12 @@
public void markDrawStart() {
drawStart = System.nanoTime();
}
+
+ /**
+ * Assign the value for input event id
+ * @param eventId the id of the input event
+ */
+ public void setInputEvent(int eventId) {
+ mInputEventId = eventId;
+ }
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f8e65bd..390e3ae 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -457,6 +457,7 @@
FallbackEventHandler mFallbackEventHandler;
final Choreographer mChoreographer;
protected final ViewFrameInfo mViewFrameInfo = new ViewFrameInfo();
+ private final InputEventAssigner mInputEventAssigner = new InputEventAssigner();
/**
* Update the Choreographer's FrameInfo object with the timing information for the current
@@ -8352,16 +8353,7 @@
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
mPendingInputEventCount);
- long eventTime = q.mEvent.getEventTimeNano();
- long oldestEventTime = eventTime;
- if (q.mEvent instanceof MotionEvent) {
- MotionEvent me = (MotionEvent)q.mEvent;
- if (me.getHistorySize() > 0) {
- oldestEventTime = me.getHistoricalEventTimeNano(0);
- }
- }
- mViewFrameInfo.updateOldestInputEvent(oldestEventTime);
- mViewFrameInfo.updateNewestInputEvent(eventTime);
+ mViewFrameInfo.setInputEvent(mInputEventAssigner.processEvent(q.mEvent));
deliverInputEvent(q);
}
@@ -8497,6 +8489,11 @@
consumedBatches = false;
}
doProcessInputEvents();
+ if (consumedBatches) {
+ // Must be done after we processed the input events, to mark the completion of the frame
+ // from the input point of view
+ mInputEventAssigner.onChoreographerCallback();
+ }
return consumedBatches;
}
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 7cc2db1..72d403e 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -149,8 +149,15 @@
if (((attrs.inputFeatures &
WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0)) {
try {
- mRealWm.grantInputChannel(displayId, sc, window, mHostInputToken, attrs.flags,
+ if(mRealWm instanceof IWindowSession.Stub) {
+ mRealWm.grantInputChannel(displayId,
+ new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"),
+ window, mHostInputToken, attrs.flags, attrs.privateFlags, attrs.type,
+ outInputChannel);
+ } else {
+ mRealWm.grantInputChannel(displayId, sc, window, mHostInputToken, attrs.flags,
attrs.privateFlags, attrs.type, outInputChannel);
+ }
} catch (RemoteException e) {
Log.e(TAG, "Failed to grant input to surface: ", e);
}
@@ -293,8 +300,14 @@
if ((attrChanges & WindowManager.LayoutParams.FLAGS_CHANGED) != 0
&& state.mInputChannelToken != null) {
try {
- mRealWm.updateInputChannel(state.mInputChannelToken, state.mDisplayId, sc,
- attrs.flags, attrs.privateFlags, state.mInputRegion);
+ if(mRealWm instanceof IWindowSession.Stub) {
+ mRealWm.updateInputChannel(state.mInputChannelToken, state.mDisplayId,
+ new SurfaceControl(sc, "WindowlessWindowManager.relayout"),
+ attrs.flags, attrs.privateFlags, state.mInputRegion);
+ } else {
+ mRealWm.updateInputChannel(state.mInputChannelToken, state.mDisplayId, sc,
+ attrs.flags, attrs.privateFlags, state.mInputRegion);
+ }
} catch (RemoteException e) {
Log.e(TAG, "Failed to update surface input channel: ", e);
}
diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
index b1b443f..a641110 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.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 @@
* 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 0000000..b0f062d
--- /dev/null
+++ b/core/java/android/view/contentcapture/IContentCaptureOptionsCallback.aidl
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentcapture;
+
+import android.content.ContentCaptureOptions;
+
+/**
+ * Callback for changes to content capture options made by ContentCaptureService.
+ * Callback interface used by IContentCaptureManager to send asynchronous
+ * notifications back to its clients. Note that this is a
+ * one-way interface so the server does not block waiting for the client.
+ *
+ * @hide
+ */
+oneway interface IContentCaptureOptionsCallback {
+ void setContentCaptureOptions(in ContentCaptureOptions options);
+}
diff --git a/core/java/android/view/displayhash/DisplayHashResultCallback.java b/core/java/android/view/displayhash/DisplayHashResultCallback.java
index 15b29ad..04d29ee 100644
--- a/core/java/android/view/displayhash/DisplayHashResultCallback.java
+++ b/core/java/android/view/displayhash/DisplayHashResultCallback.java
@@ -66,12 +66,19 @@
*/
int DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN = -4;
+ /**
+ * The hash algorithm sent to generate the hash was invalid. This means the value is not one
+ * of the supported values in {@link DisplayHashManager#getSupportedHashAlgorithms()}
+ */
+ int DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM = -5;
+
/** @hide */
@IntDef(prefix = {"DISPLAY_HASH_ERROR_"}, value = {
DISPLAY_HASH_ERROR_UNKNOWN,
DISPLAY_HASH_ERROR_INVALID_BOUNDS,
DISPLAY_HASH_ERROR_MISSING_WINDOW,
- DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN
+ DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN,
+ DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM
})
@Retention(RetentionPolicy.SOURCE)
@interface DisplayHashErrorCode {
diff --git a/core/java/android/view/textclassifier/TextClassificationConstants.java b/core/java/android/view/textclassifier/TextClassificationConstants.java
index 2975afc..d0959f9 100644
--- a/core/java/android/view/textclassifier/TextClassificationConstants.java
+++ b/core/java/android/view/textclassifier/TextClassificationConstants.java
@@ -90,6 +90,12 @@
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 @@
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 @@
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 @@
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/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 1b62266..dc42ad5 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -29,7 +29,6 @@
import android.graphics.Paint;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
-import android.graphics.RenderEffect;
import android.graphics.RenderNode;
import android.os.Build;
import android.util.AttributeSet;
@@ -83,6 +82,8 @@
public @interface EdgeEffectType {
}
+ private static final float DEFAULT_MAX_STRETCH_INTENSITY = 1.5f;
+
@SuppressWarnings("UnusedDeclaration")
private static final String TAG = "EdgeEffect";
@@ -128,6 +129,8 @@
private long mStartTime;
private float mDuration;
+ private float mStretchIntensity = DEFAULT_MAX_STRETCH_INTENSITY;
+ private float mStretchDistance = -1f;
private final Interpolator mInterpolator;
@@ -146,6 +149,8 @@
private float mPullDistance;
private final Rect mBounds = new Rect();
+ private float mWidth;
+ private float mHeight;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769450)
private final Paint mPaint = new Paint();
private float mRadius;
@@ -202,6 +207,19 @@
mBaseGlowScale = h > 0 ? Math.min(oh / h, 1.f) : 1.f;
mBounds.set(mBounds.left, mBounds.top, width, (int) Math.min(height, h));
+
+ mWidth = width;
+ mHeight = height;
+ }
+
+ /**
+ * Configure the distance in pixels to stretch the content. This is only consumed as part
+ * if {@link #setType(int)} is set to {@link #TYPE_STRETCH}
+ * @param stretchDistance Stretch distance in pixels when the target View is overscrolled
+ * @hide
+ */
+ public void setStretchDistance(float stretchDistance) {
+ mStretchDistance = stretchDistance;
}
/**
@@ -437,6 +455,13 @@
}
/**
+ * @hide
+ */
+ public void setMaxStretchIntensity(float stretchIntensity) {
+ mStretchIntensity = stretchIntensity;
+ }
+
+ /**
* Set or clear the blend mode. A blend mode defines how source pixels
* (generated by a drawing command) are composited with the destination pixels
* (content of the render target).
@@ -520,23 +545,55 @@
RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
if (mTmpMatrix == null) {
mTmpMatrix = new Matrix();
- mTmpPoints = new float[4];
+ mTmpPoints = new float[12];
}
//noinspection deprecation
recordingCanvas.getMatrix(mTmpMatrix);
- mTmpPoints[0] = mBounds.width() * mDisplacement;
- mTmpPoints[1] = mDistance * mBounds.height();
- mTmpPoints[2] = mTmpPoints[0];
- mTmpPoints[3] = 0;
+
+ mTmpPoints[0] = 0;
+ mTmpPoints[1] = 0; // top-left
+ mTmpPoints[2] = mWidth;
+ mTmpPoints[3] = 0; // top-right
+ mTmpPoints[4] = mWidth;
+ mTmpPoints[5] = mHeight; // bottom-right
+ mTmpPoints[6] = 0;
+ mTmpPoints[7] = mHeight; // bottom-left
+ mTmpPoints[8] = mWidth * mDisplacement;
+ mTmpPoints[9] = 0; // drag start point
+ mTmpPoints[10] = mWidth * mDisplacement;
+ mTmpPoints[11] = mHeight * mDistance; // drag point
mTmpMatrix.mapPoints(mTmpPoints);
- float x = mTmpPoints[0] - mTmpPoints[2];
- float y = mTmpPoints[1] - mTmpPoints[3];
RenderNode renderNode = recordingCanvas.mNode;
- // TODO: use stretchy RenderEffect and use internal API when it is ready
- // TODO: wrap existing RenderEffect
- renderNode.setRenderEffect(RenderEffect.createOffsetEffect(x, y));
+ float left = renderNode.getLeft()
+ + min(mTmpPoints[0], mTmpPoints[2], mTmpPoints[4], mTmpPoints[6]);
+ float top = renderNode.getTop()
+ + min(mTmpPoints[1], mTmpPoints[3], mTmpPoints[5], mTmpPoints[7]);
+ float right = renderNode.getLeft()
+ + max(mTmpPoints[0], mTmpPoints[2], mTmpPoints[4], mTmpPoints[6]);
+ float bottom = renderNode.getTop()
+ + max(mTmpPoints[1], mTmpPoints[3], mTmpPoints[5], mTmpPoints[7]);
+ // assume rotations of increments of 90 degrees
+ float x = mTmpPoints[10] - mTmpPoints[8];
+ float width = right - left;
+ float vecX = Math.max(-1f, Math.min(1f, x / width));
+ float y = mTmpPoints[11] - mTmpPoints[9];
+ float height = bottom - top;
+ float vecY = Math.max(-1f, Math.min(1f, y / height));
+ renderNode.stretch(
+ left,
+ top,
+ right,
+ bottom,
+ vecX * mStretchIntensity,
+ vecY * mStretchIntensity,
+ // TODO (njawad/mount) figure out proper stretch distance from UX
+ // for now leverage placeholder logic if no stretch distance is provided to
+ // consume the displacement ratio times the minimum of the width or height
+ mStretchDistance > 0 ? mStretchDistance :
+ (mDisplacement * Math.min(mWidth, mHeight))
+ );
}
boolean oneLastFrame = false;
@@ -548,6 +605,18 @@
return mState != STATE_IDLE || oneLastFrame;
}
+ private float min(float f1, float f2, float f3, float f4) {
+ float min = Math.min(f1, f2);
+ min = Math.min(min, f3);
+ return Math.min(min, f4);
+ }
+
+ private float max(float f1, float f2, float f3, float f4) {
+ float max = Math.max(f1, f2);
+ max = Math.max(max, f3);
+ return Math.max(max, f4);
+ }
+
/**
* Return the maximum height that the edge effect will be drawn at given the original
* {@link #setSize(int, int) input size}.
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 23915e0..bf552e2 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -249,6 +249,26 @@
}
/**
+ * API used for prototyping stretch effect parameters in framework sample apps
+ * @hide
+ */
+ public void setEdgeEffectIntensity(float intensity) {
+ mEdgeGlowLeft.setMaxStretchIntensity(intensity);
+ mEdgeGlowRight.setMaxStretchIntensity(intensity);
+ invalidate();
+ }
+
+ /**
+ * API used for prototyping stretch effect parameters in the framework sample apps
+ * @hide
+ */
+ public void setStretchDistance(float distance) {
+ mEdgeGlowLeft.setStretchDistance(distance);
+ mEdgeGlowRight.setStretchDistance(distance);
+ invalidate();
+ }
+
+ /**
* Sets the right edge effect color.
*
* @param color The color for the right edge effect.
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 65f3da7..3006729 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -281,6 +281,26 @@
}
/**
+ * API used for prototyping stretch effect parameters in framework sample apps
+ * @hide
+ */
+ public void setEdgeEffectIntensity(float intensity) {
+ mEdgeGlowTop.setMaxStretchIntensity(intensity);
+ mEdgeGlowBottom.setMaxStretchIntensity(intensity);
+ invalidate();
+ }
+
+ /**
+ * API used for prototyping stretch effect parameters in the framework sample apps
+ * @hide
+ */
+ public void setStretchDistance(float distance) {
+ mEdgeGlowTop.setStretchDistance(distance);
+ mEdgeGlowBottom.setStretchDistance(distance);
+ invalidate();
+ }
+
+ /**
* Sets the bottom edge effect color.
*
* @param color The color for the bottom edge effect.
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index 6f18920..eb6bce4 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -36,6 +36,7 @@
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 @@
*/
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 @@
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 @@
@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 @@
/**
* 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 @@
}
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 3a9f3b9..eecd0cf 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -74,11 +74,11 @@
@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 3c081e2..7529536 100644
--- a/core/java/com/android/internal/infra/GlobalWhitelistState.java
+++ b/core/java/com/android/internal/infra/GlobalWhitelistState.java
@@ -99,6 +99,18 @@
}
/**
+ * 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 1d76090..3e93106 100644
--- a/core/java/com/android/internal/infra/WhitelistHelper.java
+++ b/core/java/com/android/internal/infra/WhitelistHelper.java
@@ -140,6 +140,15 @@
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/CallbackUtils.java b/core/java/com/android/internal/inputmethod/CallbackUtils.java
index 2113173..3958b9e 100644
--- a/core/java/com/android/internal/inputmethod/CallbackUtils.java
+++ b/core/java/com/android/internal/inputmethod/CallbackUtils.java
@@ -225,4 +225,31 @@
callback.onResult();
} catch (RemoteException ignored) { }
}
+
+ /**
+ * A utility method using given {@link IIInputContentUriTokenResultCallback} to callback the
+ * result.
+ *
+ * @param callback {@link IIInputContentUriTokenResultCallback} to be called back.
+ * @param resultSupplier the supplier from which the result is provided.
+ */
+ public static void onResult(@NonNull IIInputContentUriTokenResultCallback callback,
+ @NonNull Supplier<IInputContentUriToken> resultSupplier) {
+ IInputContentUriToken result = null;
+ Throwable exception = null;
+
+ try {
+ result = resultSupplier.get();
+ } catch (Throwable throwable) {
+ exception = throwable;
+ }
+
+ try {
+ if (exception != null) {
+ callback.onError(ThrowableHolder.of(exception));
+ return;
+ }
+ callback.onResult(result);
+ } catch (RemoteException ignored) { }
+ }
}
diff --git a/core/java/com/android/internal/inputmethod/Completable.java b/core/java/com/android/internal/inputmethod/Completable.java
index d6a4663..ba3a343 100644
--- a/core/java/com/android/internal/inputmethod/Completable.java
+++ b/core/java/com/android/internal/inputmethod/Completable.java
@@ -444,6 +444,13 @@
}
/**
+ * @return an instance of {@link Completable.IInputContentUriToken}.
+ */
+ public static Completable.IInputContentUriToken createIInputContentUriToken() {
+ return new Completable.IInputContentUriToken();
+ }
+
+ /**
* @return an instance of {@link Completable.Void}.
*/
public static Completable.Void createVoid() {
@@ -497,6 +504,12 @@
extends Values<List<android.view.inputmethod.InputMethodInfo>> { }
/**
+ * Completable object of {@link IInputContentUriToken>}.
+ */
+ public static final class IInputContentUriToken
+ extends Values<com.android.internal.inputmethod.IInputContentUriToken> { }
+
+ /**
* Await the result by the {@link Completable.Values}.
*
* @return the result once {@link ValueBase#onComplete()}.
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl b/core/java/com/android/internal/inputmethod/IIInputContentUriTokenResultCallback.aidl
similarity index 65%
copy from core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl
copy to core/java/com/android/internal/inputmethod/IIInputContentUriTokenResultCallback.aidl
index ddb5ef8..2e6d224 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl
+++ b/core/java/com/android/internal/inputmethod/IIInputContentUriTokenResultCallback.aidl
@@ -14,6 +14,12 @@
* limitations under the License.
*/
-package android.content.pm.verify.domain;
+package com.android.internal.inputmethod;
-parcelable DomainVerificationUserSelection;
+import com.android.internal.inputmethod.IInputContentUriToken;
+import com.android.internal.inputmethod.ThrowableHolder;
+
+oneway interface IIInputContentUriTokenResultCallback {
+ void onResult(in IInputContentUriToken result);
+ void onError(in ThrowableHolder exception);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index f0e26cf..e4dd7b0 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -19,25 +19,31 @@
import android.net.Uri;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.internal.inputmethod.IBooleanResultCallback;
import com.android.internal.inputmethod.IInputContentUriToken;
+import com.android.internal.inputmethod.IIInputContentUriTokenResultCallback;
+import com.android.internal.inputmethod.IVoidResultCallback;
/**
* Defines priviledged operations that only the current IME is allowed to call.
* Actual operations are implemented and handled by InputMethodManagerService.
*/
-interface IInputMethodPrivilegedOperations {
- void setImeWindowStatus(int vis, int backDisposition);
- void reportStartInput(in IBinder startInputToken);
- IInputContentUriToken createInputContentUriToken(in Uri contentUri, in String packageName);
- void reportFullscreenMode(boolean fullscreen);
- void setInputMethod(String id);
- void setInputMethodAndSubtype(String id, in InputMethodSubtype subtype);
- void hideMySoftInput(int flags);
- void showMySoftInput(int flags);
- void updateStatusIcon(String packageName, int iconId);
- boolean switchToPreviousInputMethod();
- boolean switchToNextInputMethod(boolean onlyCurrentIme);
- boolean shouldOfferSwitchingToNextInputMethod();
- void notifyUserAction();
- void applyImeVisibility(IBinder showOrHideInputToken, boolean setVisible);
+oneway interface IInputMethodPrivilegedOperations {
+ void setImeWindowStatus(int vis, int backDisposition, in IVoidResultCallback resultCallback);
+ void reportStartInput(in IBinder startInputToken, in IVoidResultCallback resultCallback);
+ void createInputContentUriToken(in Uri contentUri, in String packageName,
+ in IIInputContentUriTokenResultCallback resultCallback);
+ void reportFullscreenMode(boolean fullscreen, in IVoidResultCallback resultCallback);
+ void setInputMethod(String id, in IVoidResultCallback resultCallback);
+ void setInputMethodAndSubtype(String id, in InputMethodSubtype subtype,
+ in IVoidResultCallback resultCallback);
+ void hideMySoftInput(int flags, in IVoidResultCallback resultCallback);
+ void showMySoftInput(int flags, in IVoidResultCallback resultCallback);
+ void updateStatusIcon(String packageName, int iconId, in IVoidResultCallback resultCallback);
+ void switchToPreviousInputMethod(in IBooleanResultCallback resultCallback);
+ void switchToNextInputMethod(boolean onlyCurrentIme, in IBooleanResultCallback resultCallback);
+ void shouldOfferSwitchingToNextInputMethod(in IBooleanResultCallback resultCallback);
+ void notifyUserAction(in IVoidResultCallback resultCallback);
+ void applyImeVisibility(IBinder showOrHideInputToken, boolean setVisible,
+ in IVoidResultCallback resultCallback);
}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
index c353de8..93374ba 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
@@ -228,6 +228,8 @@
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/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index d6730e8..04cf3f3 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -95,7 +95,8 @@
}
/**
- * Calls {@link IInputMethodPrivilegedOperations#setImeWindowStatus(int, int)}.
+ * Calls {@link IInputMethodPrivilegedOperations#setImeWindowStatus(int, int,
+ * IVoidResultCallback)}.
*
* @param vis visibility flags
* @param backDisposition disposition flags
@@ -112,14 +113,17 @@
return;
}
try {
- ops.setImeWindowStatus(vis, backDisposition);
+ final Completable.Void value = Completable.createVoid();
+ ops.setImeWindowStatus(vis, backDisposition, ResultCallbacks.of(value));
+ Completable.getResult(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Calls {@link IInputMethodPrivilegedOperations#reportStartInput(IBinder)}.
+ * Calls {@link IInputMethodPrivilegedOperations#reportStartInput(IBinder,
+ * IVoidResultCallback)}.
*
* @param startInputToken {@link IBinder} token to distinguish startInput session
*/
@@ -130,14 +134,17 @@
return;
}
try {
- ops.reportStartInput(startInputToken);
+ final Completable.Void value = Completable.createVoid();
+ ops.reportStartInput(startInputToken, ResultCallbacks.of(value));
+ Completable.getResult(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Calls {@link IInputMethodPrivilegedOperations#createInputContentUriToken(Uri, String)}.
+ * Calls {@link IInputMethodPrivilegedOperations#createInputContentUriToken(Uri, String,
+ * IIInputContentUriTokenResultCallback)}.
*
* @param contentUri Content URI to which a temporary read permission should be granted
* @param packageName Indicates what package needs to have a temporary read permission
@@ -151,7 +158,10 @@
return null;
}
try {
- return ops.createInputContentUriToken(contentUri, packageName);
+ final Completable.IInputContentUriToken value =
+ Completable.createIInputContentUriToken();
+ ops.createInputContentUriToken(contentUri, packageName, ResultCallbacks.of(value));
+ return Completable.getResult(value);
} catch (RemoteException e) {
// For historical reasons, this error was silently ignored.
// Note that the caller already logs error so we do not need additional Log.e() here.
@@ -161,7 +171,8 @@
}
/**
- * Calls {@link IInputMethodPrivilegedOperations#reportFullscreenMode(boolean)}.
+ * Calls {@link IInputMethodPrivilegedOperations#reportFullscreenMode(boolean,
+ * IVoidResultCallback)}.
*
* @param fullscreen {@code true} if the IME enters full screen mode
*/
@@ -172,14 +183,17 @@
return;
}
try {
- ops.reportFullscreenMode(fullscreen);
+ final Completable.Void value = Completable.createVoid();
+ ops.reportFullscreenMode(fullscreen, ResultCallbacks.of(value));
+ Completable.getResult(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Calls {@link IInputMethodPrivilegedOperations#updateStatusIcon(String, int)}.
+ * Calls {@link IInputMethodPrivilegedOperations#updateStatusIcon(String, int,
+ * IVoidResultCallback)}.
*
* @param packageName package name from which the status icon should be loaded
* @param iconResId resource ID of the icon to be loaded
@@ -191,14 +205,16 @@
return;
}
try {
- ops.updateStatusIcon(packageName, iconResId);
+ final Completable.Void value = Completable.createVoid();
+ ops.updateStatusIcon(packageName, iconResId, ResultCallbacks.of(value));
+ Completable.getResult(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Calls {@link IInputMethodPrivilegedOperations#setInputMethod(String)}.
+ * Calls {@link IInputMethodPrivilegedOperations#setInputMethod(String, IVoidResultCallback)}.
*
* @param id IME ID of the IME to switch to
* @see android.view.inputmethod.InputMethodInfo#getId()
@@ -210,7 +226,9 @@
return;
}
try {
- ops.setInputMethod(id);
+ final Completable.Void value = Completable.createVoid();
+ ops.setInputMethod(id, ResultCallbacks.of(value));
+ Completable.getResult(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -218,7 +236,7 @@
/**
* Calls {@link IInputMethodPrivilegedOperations#setInputMethodAndSubtype(String,
- * InputMethodSubtype)}
+ * InputMethodSubtype, IVoidResultCallback)}
*
* @param id IME ID of the IME to switch to
* @param subtype {@link InputMethodSubtype} to switch to
@@ -231,14 +249,16 @@
return;
}
try {
- ops.setInputMethodAndSubtype(id, subtype);
+ final Completable.Void value = Completable.createVoid();
+ ops.setInputMethodAndSubtype(id, subtype, ResultCallbacks.of(value));
+ Completable.getResult(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int)}
+ * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, IVoidResultCallback)}
*
* @param flags additional operating flags
* @see android.view.inputmethod.InputMethodManager#HIDE_IMPLICIT_ONLY
@@ -251,14 +271,16 @@
return;
}
try {
- ops.hideMySoftInput(flags);
+ final Completable.Void value = Completable.createVoid();
+ ops.hideMySoftInput(flags, ResultCallbacks.of(value));
+ Completable.getResult(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Calls {@link IInputMethodPrivilegedOperations#showMySoftInput(int)}
+ * Calls {@link IInputMethodPrivilegedOperations#showMySoftInput(int, IVoidResultCallback)}
*
* @param flags additional operating flags
* @see android.view.inputmethod.InputMethodManager#SHOW_IMPLICIT
@@ -271,14 +293,17 @@
return;
}
try {
- ops.showMySoftInput(flags);
+ final Completable.Void value = Completable.createVoid();
+ ops.showMySoftInput(flags, ResultCallbacks.of(value));
+ Completable.getResult(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Calls {@link IInputMethodPrivilegedOperations#switchToPreviousInputMethod()}
+ * Calls {@link IInputMethodPrivilegedOperations#switchToPreviousInputMethod(
+ * IBooleanResultCallback)}
*
* @return {@code true} if handled
*/
@@ -289,14 +314,17 @@
return false;
}
try {
- return ops.switchToPreviousInputMethod();
+ final Completable.Boolean value = Completable.createBoolean();
+ ops.switchToPreviousInputMethod(ResultCallbacks.of(value));
+ return Completable.getResult(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Calls {@link IInputMethodPrivilegedOperations#switchToNextInputMethod(boolean)}
+ * Calls {@link IInputMethodPrivilegedOperations#switchToNextInputMethod(boolean,
+ * IBooleanResultCallback)}
*
* @param onlyCurrentIme {@code true} to switch to a {@link InputMethodSubtype} within the same
* IME
@@ -309,14 +337,17 @@
return false;
}
try {
- return ops.switchToNextInputMethod(onlyCurrentIme);
+ final Completable.Boolean value = Completable.createBoolean();
+ ops.switchToNextInputMethod(onlyCurrentIme, ResultCallbacks.of(value));
+ return Completable.getResult(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Calls {@link IInputMethodPrivilegedOperations#shouldOfferSwitchingToNextInputMethod()}
+ * Calls {@link IInputMethodPrivilegedOperations#shouldOfferSwitchingToNextInputMethod(
+ * IBooleanResultCallback)}
*
* @return {@code true} if the IEM should offer a way to globally switch IME
*/
@@ -327,14 +358,16 @@
return false;
}
try {
- return ops.shouldOfferSwitchingToNextInputMethod();
+ final Completable.Boolean value = Completable.createBoolean();
+ ops.shouldOfferSwitchingToNextInputMethod(ResultCallbacks.of(value));
+ return Completable.getResult(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Calls {@link IInputMethodPrivilegedOperations#notifyUserAction()}
+ * Calls {@link IInputMethodPrivilegedOperations#notifyUserAction(IVoidResultCallback)}
*/
@AnyThread
public void notifyUserAction() {
@@ -343,14 +376,17 @@
return;
}
try {
- ops.notifyUserAction();
+ final Completable.Void value = Completable.createVoid();
+ ops.notifyUserAction(ResultCallbacks.of(value));
+ Completable.getResult(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibility(IBinder, boolean)}.
+ * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibility(IBinder, boolean,
+ * IVoidResultCallback)}.
*
* @param showOrHideInputToken placeholder token that maps to window requesting
* {@link android.view.inputmethod.InputMethodManager#showSoftInput(View, int)} or
@@ -365,7 +401,9 @@
return;
}
try {
- ops.applyImeVisibility(showOrHideInputToken, setVisible);
+ final Completable.Void value = Completable.createVoid();
+ ops.applyImeVisibility(showOrHideInputToken, setVisible, ResultCallbacks.of(value));
+ Completable.getResult(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/com/android/internal/inputmethod/ResultCallbacks.java b/core/java/com/android/internal/inputmethod/ResultCallbacks.java
index 2a48c1f..c56ed2d 100644
--- a/core/java/com/android/internal/inputmethod/ResultCallbacks.java
+++ b/core/java/com/android/internal/inputmethod/ResultCallbacks.java
@@ -387,4 +387,41 @@
}
};
}
+
+ /**
+ * Creates {@link IIInputContentUriTokenResultCallback.Stub} that is to set
+ * {@link Completable.IInputContentUriToken} when receiving the result.
+ *
+ * @param value {@link Completable.IInputContentUriToken} to be set when receiving the result.
+ * @return {@link IIInputContentUriTokenResultCallback.Stub} that can be passed as a binder IPC
+ * parameter.
+ */
+ @AnyThread
+ public static IIInputContentUriTokenResultCallback.Stub of(
+ @NonNull Completable.IInputContentUriToken value) {
+ final AtomicReference<WeakReference<Completable.IInputContentUriToken>>
+ atomicRef = new AtomicReference<>(new WeakReference<>(value));
+
+ return new IIInputContentUriTokenResultCallback.Stub() {
+ @BinderThread
+ @Override
+ public void onResult(IInputContentUriToken result) {
+ final Completable.IInputContentUriToken value = unwrap(atomicRef);
+ if (value == null) {
+ return;
+ }
+ value.onComplete(result);
+ }
+
+ @BinderThread
+ @Override
+ public void onError(ThrowableHolder throwableHolder) {
+ final Completable.IInputContentUriToken value = unwrap(atomicRef);
+ if (value == null) {
+ return;
+ }
+ value.onError(throwableHolder);
+ }
+ };
+ }
}
diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index 1553e2e..f1cdf2b 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -49,7 +49,8 @@
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 @@
* 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 342456a..db0b48e 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -203,6 +203,9 @@
Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId);
mCancelled = true;
removeObservers();
+ if (mListener != null) {
+ mListener.onNotifyCujEvents(mSession, InteractionJankMonitor.ACTION_SESSION_CANCEL);
+ }
}
@Override
@@ -324,11 +327,8 @@
}
if (info.surfaceControlCallbackFired) {
totalFramesCount++;
-
- // Only count missed frames if it's not stuffed.
if ((info.jankType & PREDICTION_ERROR) != 0
- || ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0
- && (info.jankType & BUFFER_STUFFING) == 0)) {
+ || ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0)) {
Log.w(TAG, "Missed App frame:" + info.jankType);
missedAppFramesCount++;
}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 0294ec3..fbc92c1 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -99,7 +99,7 @@
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/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 6b1d408..8b5a62a 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -202,7 +202,7 @@
public static final String PKG_DATA_INFO_MAP = "--pkg-data-info-map";
/** List of allowlisted packages and its app data info: volume uuid and inode. */
- public static final String WHITELISTED_DATA_INFO_MAP = "--whitelisted-data-info-map";
+ public static final String ALLOWLISTED_DATA_INFO_MAP = "--allowlisted-data-info-map";
/** Bind mount app storage dirs to lower fs not via fuse */
public static final String BIND_MOUNT_APP_STORAGE_DIRS = "--bind-mount-storage-dirs";
@@ -324,7 +324,7 @@
* @param isTopApp true if the process is for top (high priority) application.
* @param pkgDataInfoList A list that stores related packages and its app data
* info: volume uuid and inode.
- * @param whitelistedDataInfoList Like pkgDataInfoList, but it's for allowlisted apps.
+ * @param allowlistedDataInfoList Like pkgDataInfoList, but it's for allowlisted apps.
* @param bindMountAppDataDirs True if the zygote needs to mount data dirs.
* @param bindMountAppStorageDirs True if the zygote needs to mount storage dirs.
*
@@ -334,14 +334,14 @@
static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
- boolean isTopApp, String[] pkgDataInfoList, String[] whitelistedDataInfoList,
+ boolean isTopApp, String[] pkgDataInfoList, String[] allowlistedDataInfoList,
boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) {
ZygoteHooks.preFork();
int pid = nativeForkAndSpecialize(
uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp,
- pkgDataInfoList, whitelistedDataInfoList, bindMountAppDataDirs,
+ pkgDataInfoList, allowlistedDataInfoList, bindMountAppDataDirs,
bindMountAppStorageDirs);
if (pid == 0) {
// Note that this event ends at the end of handleChildProc,
@@ -364,7 +364,7 @@
int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
String appDataDir, boolean isTopApp, String[] pkgDataInfoList,
- String[] whitelistedDataInfoList, boolean bindMountAppDataDirs,
+ String[] allowlistedDataInfoList, boolean bindMountAppDataDirs,
boolean bindMountAppStorageDirs);
/**
@@ -392,18 +392,18 @@
* volume uuid and CE dir inode. For example, pkgDataInfoList = [app_a_pkg_name,
* app_a_data_volume_uuid, app_a_ce_inode, app_b_pkg_name, app_b_data_volume_uuid,
* app_b_ce_inode, ...];
- * @param whitelistedDataInfoList Like pkgDataInfoList, but it's for allowlisted apps.
+ * @param allowlistedDataInfoList Like pkgDataInfoList, but it's for allowlisted apps.
* @param bindMountAppDataDirs True if the zygote needs to mount data dirs.
* @param bindMountAppStorageDirs True if the zygote needs to mount storage dirs.
*/
private static void specializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName,
boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
- String[] pkgDataInfoList, String[] whitelistedDataInfoList,
+ String[] pkgDataInfoList, String[] allowlistedDataInfoList,
boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) {
nativeSpecializeAppProcess(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
niceName, startChildZygote, instructionSet, appDataDir, isTopApp,
- pkgDataInfoList, whitelistedDataInfoList,
+ pkgDataInfoList, allowlistedDataInfoList,
bindMountAppDataDirs, bindMountAppStorageDirs);
// Note that this event ends at the end of handleChildProc.
@@ -428,7 +428,7 @@
private static native void nativeSpecializeAppProcess(int uid, int gid, int[] gids,
int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
- String[] pkgDataInfoList, String[] whitelistedDataInfoList,
+ String[] pkgDataInfoList, String[] allowlistedDataInfoList,
boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs);
/**
@@ -807,7 +807,7 @@
args.mRuntimeFlags, rlimits, args.mMountExternal,
args.mSeInfo, args.mNiceName, args.mStartChildZygote,
args.mInstructionSet, args.mAppDataDir, args.mIsTopApp,
- args.mPkgDataInfoList, args.mWhitelistedDataInfoList,
+ args.mPkgDataInfoList, args.mAllowlistedDataInfoList,
args.mBindMountAppDataDirs, args.mBindMountAppStorageDirs);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index 65b454d..ef83982 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -230,7 +230,7 @@
* A list that stores all allowlisted app data info: volume uuid and inode.
* Null if it does need to do app data isolation.
*/
- String[] mWhitelistedDataInfoList;
+ String[] mAllowlistedDataInfoList;
/**
* @see Zygote#BIND_MOUNT_APP_STORAGE_DIRS
@@ -475,8 +475,8 @@
}
} else if (arg.startsWith(Zygote.PKG_DATA_INFO_MAP)) {
mPkgDataInfoList = getAssignmentList(arg);
- } else if (arg.startsWith(Zygote.WHITELISTED_DATA_INFO_MAP)) {
- mWhitelistedDataInfoList = getAssignmentList(arg);
+ } else if (arg.startsWith(Zygote.ALLOWLISTED_DATA_INFO_MAP)) {
+ mAllowlistedDataInfoList = getAssignmentList(arg);
} else if (arg.equals(Zygote.BIND_MOUNT_APP_STORAGE_DIRS)) {
mBindMountAppStorageDirs = true;
} else if (arg.equals(Zygote.BIND_MOUNT_APP_DATA_DIRS)) {
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 37c7590..1673362 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -265,7 +265,7 @@
fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
parsedArgs.mInstructionSet, parsedArgs.mAppDataDir,
parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList,
- parsedArgs.mWhitelistedDataInfoList, parsedArgs.mBindMountAppDataDirs,
+ parsedArgs.mAllowlistedDataInfoList, parsedArgs.mBindMountAppDataDirs,
parsedArgs.mBindMountAppStorageDirs);
try {
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 1b1e0bf..8ecc809 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.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.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 @@
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 @@
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 @@
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 @@
mAppName.setOnVisibilityChangedListener((visibility) -> {
onAppNameVisibilityChanged();
});
- mUnreadBadge = findViewById(R.id.conversation_unread_count);
mConversationContentStart = getResources().getDimensionPixelSize(
R.dimen.conversation_content_start);
mInternalButtonPadding
@@ -426,17 +418,7 @@
/** @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 @@
}
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 @@
// 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 @@
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 8add34f..fc4cc57 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 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 @@
} else {
super.getBoundsOnScreen(outRect, clipToParent);
}
- extendRectToMinTouchSize(outRect);
}
/**
@@ -89,32 +107,12 @@
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 @@
@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 @@
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/OWNERS b/core/jni/OWNERS
index 35d1d7b..f6629fd 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -43,6 +43,7 @@
per-file EphemeralStorage* = file:platform/system/libhwbinder:/OWNERS
per-file *Zygote* = file:/ZYGOTE_OWNERS
+per-file fd_utils.* = file:/ZYGOTE_OWNERS
per-file Android.bp = file:platform/build/soong:/OWNERS
per-file android_animation_* = file:/core/java/android/animation/OWNERS
per-file android_app_admin_* = file:/core/java/android/app/admin/OWNERS
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 1c78750..5e142fd 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -45,6 +45,12 @@
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 @@
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 @@
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 @@
}
}
+/**
+ * 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 @@
ALOGD("channel '%s' ~ Publisher closed input channel or an error occurred. "
"events=0x%x", getInputChannelName().c_str(), events);
}
- return REMOVE_CALLBACK;
+ return toUnderlying(HandleEventResponse::REMOVE_CALLBACK);
}
if (events & ALOOPER_EVENT_INPUT) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
status_t status = consumeEvents(env, false /*consumeBatches*/, -1, nullptr);
mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
- return status == OK || status == NO_MEMORY ? KEEP_CALLBACK : REMOVE_CALLBACK;
+ return status == OK || status == NO_MEMORY
+ ? toUnderlying(HandleEventResponse::KEEP_CALLBACK)
+ : toUnderlying(HandleEventResponse::REMOVE_CALLBACK);
}
if (events & ALOOPER_EVENT_OUTPUT) {
- for (size_t i = 0; i < mFinishQueue.size(); i++) {
- const Finish& finish = mFinishQueue[i];
- status_t status = mInputConsumer.sendFinishedSignal(finish.seq, finish.handled);
- if (status != OK) {
- mFinishQueue.erase(mFinishQueue.begin(), mFinishQueue.begin() + i);
-
- if (status == WOULD_BLOCK) {
- if (kDebugDispatchCycle) {
- ALOGD("channel '%s' ~ Sent %zu queued finish events; %zu left.",
- getInputChannelName().c_str(), i, mFinishQueue.size());
- }
- return KEEP_CALLBACK; // try again later
- }
-
- ALOGW("Failed to send finished signal on channel '%s'. status=%d",
- getInputChannelName().c_str(), status);
- if (status != DEAD_OBJECT) {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- std::string message =
- android::base::StringPrintf("Failed to finish input event. status=%d",
- status);
- jniThrowRuntimeException(env, message.c_str());
- mMessageQueue->raiseAndClearException(env, "finishInputEvent");
- }
- return REMOVE_CALLBACK;
- }
- }
- if (kDebugDispatchCycle) {
- ALOGD("channel '%s' ~ Sent %zu queued finish events; none left.",
- getInputChannelName().c_str(), mFinishQueue.size());
- }
- mFinishQueue.clear();
- setFdEvents(ALOOPER_EVENT_INPUT);
- return KEEP_CALLBACK;
+ return toUnderlying(processOutboundEvents());
}
ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. "
"events=0x%x", getInputChannelName().c_str(), events);
- return KEEP_CALLBACK;
+ return toUnderlying(HandleEventResponse::KEEP_CALLBACK);
}
status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index c9062d8..836074f 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -304,15 +304,14 @@
static FileDescriptorTable* gOpenFdTable = nullptr;
// Must match values in com.android.internal.os.Zygote.
-// Note that there are gaps in the constants:
-// This is to further keep the values consistent with IVold.aidl
+// The values should be consistent with IVold.aidl
enum MountExternalKind {
MOUNT_EXTERNAL_NONE = 0,
MOUNT_EXTERNAL_DEFAULT = 1,
- MOUNT_EXTERNAL_INSTALLER = 5,
- MOUNT_EXTERNAL_PASS_THROUGH = 7,
- MOUNT_EXTERNAL_ANDROID_WRITABLE = 8,
- MOUNT_EXTERNAL_COUNT = 9
+ MOUNT_EXTERNAL_INSTALLER = 2,
+ MOUNT_EXTERNAL_PASS_THROUGH = 3,
+ MOUNT_EXTERNAL_ANDROID_WRITABLE = 4,
+ MOUNT_EXTERNAL_COUNT = 5
};
// Must match values in com.android.internal.os.Zygote.
@@ -1401,16 +1400,15 @@
}
static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list,
- jobjectArray whitelisted_data_info_list, uid_t uid, const char* process_name,
- jstring managed_nice_name, fail_fn_t fail_fn) {
+ jobjectArray allowlisted_data_info_list, uid_t uid,
+ const char* process_name, jstring managed_nice_name, fail_fn_t fail_fn) {
+ std::vector<std::string> merged_data_info_list;
+ insertPackagesToMergedList(env, merged_data_info_list, pkg_data_info_list, process_name,
+ managed_nice_name, fail_fn);
+ insertPackagesToMergedList(env, merged_data_info_list, allowlisted_data_info_list, process_name,
+ managed_nice_name, fail_fn);
- std::vector<std::string> merged_data_info_list;
- insertPackagesToMergedList(env, merged_data_info_list, pkg_data_info_list,
- process_name, managed_nice_name, fail_fn);
- insertPackagesToMergedList(env, merged_data_info_list, whitelisted_data_info_list,
- process_name, managed_nice_name, fail_fn);
-
- isolateAppData(env, merged_data_info_list, uid, process_name, managed_nice_name, fail_fn);
+ isolateAppData(env, merged_data_info_list, uid, process_name, managed_nice_name, fail_fn);
}
/**
@@ -1511,240 +1509,242 @@
}
// Utility routine to specialize a zygote child process.
-static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
- jint runtime_flags, jobjectArray rlimits,
- jlong permitted_capabilities, jlong effective_capabilities,
- jint mount_external, jstring managed_se_info,
- jstring managed_nice_name, bool is_system_server,
- bool is_child_zygote, jstring managed_instruction_set,
- jstring managed_app_data_dir, bool is_top_app,
- jobjectArray pkg_data_info_list,
- jobjectArray whitelisted_data_info_list,
- bool mount_data_dirs, bool mount_storage_dirs) {
- const char* process_name = is_system_server ? "system_server" : "zygote";
- auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1);
- auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
+static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags,
+ jobjectArray rlimits, jlong permitted_capabilities,
+ jlong effective_capabilities, jint mount_external,
+ jstring managed_se_info, jstring managed_nice_name,
+ bool is_system_server, bool is_child_zygote,
+ jstring managed_instruction_set, jstring managed_app_data_dir,
+ bool is_top_app, jobjectArray pkg_data_info_list,
+ jobjectArray allowlisted_data_info_list, bool mount_data_dirs,
+ bool mount_storage_dirs) {
+ const char* process_name = is_system_server ? "system_server" : "zygote";
+ auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1);
+ auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
- auto se_info = extract_fn(managed_se_info);
- auto nice_name = extract_fn(managed_nice_name);
- auto instruction_set = extract_fn(managed_instruction_set);
- auto app_data_dir = extract_fn(managed_app_data_dir);
+ auto se_info = extract_fn(managed_se_info);
+ auto nice_name = extract_fn(managed_nice_name);
+ auto instruction_set = extract_fn(managed_instruction_set);
+ auto app_data_dir = extract_fn(managed_app_data_dir);
- // Keep capabilities across UID change, unless we're staying root.
- if (uid != 0) {
- EnableKeepCapabilities(fail_fn);
- }
-
- SetInheritable(permitted_capabilities, fail_fn);
-
- DropCapabilitiesBoundingSet(fail_fn);
-
- bool need_pre_initialize_native_bridge =
- !is_system_server &&
- instruction_set.has_value() &&
- android::NativeBridgeAvailable() &&
- // Native bridge may be already initialized if this
- // is an app forked from app-zygote.
- !android::NativeBridgeInitialized() &&
- android::NeedsNativeBridge(instruction_set.value().c_str());
-
- MountEmulatedStorage(uid, mount_external, need_pre_initialize_native_bridge, fail_fn);
-
- // Make sure app is running in its own mount namespace before isolating its data directories.
- ensureInAppMountNamespace(fail_fn);
-
- // Sandbox data and jit profile directories by overlaying a tmpfs on those dirs and bind
- // mount all related packages separately.
- if (mount_data_dirs) {
- isolateAppData(env, pkg_data_info_list, whitelisted_data_info_list,
- uid, process_name, managed_nice_name, fail_fn);
- isolateJitProfile(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
- }
- // MOUNT_EXTERNAL_INSTALLER, MOUNT_EXTERNAL_PASS_THROUGH, MOUNT_EXTERNAL_ANDROID_WRITABLE apps
- // will have mount_storage_dirs == false here (set by ProcessList.needsStorageDataIsolation()),
- // and hence they won't bind mount storage dirs.
- if (mount_storage_dirs) {
- BindMountStorageDirs(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
- }
-
- // If this zygote isn't root, it won't be able to create a process group,
- // since the directory is owned by root.
- if (!is_system_server && getuid() == 0) {
- const int rc = createProcessGroup(uid, getpid());
- if (rc == -EROFS) {
- ALOGW("createProcessGroup failed, kernel missing CONFIG_CGROUP_CPUACCT?");
- } else if (rc != 0) {
- ALOGE("createProcessGroup(%d, %d) failed: %s", uid, /* pid= */ 0, strerror(-rc));
+ // Keep capabilities across UID change, unless we're staying root.
+ if (uid != 0) {
+ EnableKeepCapabilities(fail_fn);
}
- }
- SetGids(env, gids, is_child_zygote, fail_fn);
- SetRLimits(env, rlimits, fail_fn);
+ SetInheritable(permitted_capabilities, fail_fn);
- if (need_pre_initialize_native_bridge) {
- // Due to the logic behind need_pre_initialize_native_bridge we know that
- // instruction_set contains a value.
- android::PreInitializeNativeBridge(
- app_data_dir.has_value() ? app_data_dir.value().c_str() : nullptr,
- instruction_set.value().c_str());
- }
+ DropCapabilitiesBoundingSet(fail_fn);
- if (setresgid(gid, gid, gid) == -1) {
- fail_fn(CREATE_ERROR("setresgid(%d) failed: %s", gid, strerror(errno)));
- }
+ bool need_pre_initialize_native_bridge = !is_system_server && instruction_set.has_value() &&
+ android::NativeBridgeAvailable() &&
+ // Native bridge may be already initialized if this
+ // is an app forked from app-zygote.
+ !android::NativeBridgeInitialized() &&
+ android::NeedsNativeBridge(instruction_set.value().c_str());
- // Must be called when the new process still has CAP_SYS_ADMIN, in this case,
- // before changing uid from 0, which clears capabilities. The other
- // alternative is to call prctl(PR_SET_NO_NEW_PRIVS, 1) afterward, but that
- // breaks SELinux domain transition (see b/71859146). As the result,
- // privileged syscalls used below still need to be accessible in app process.
- SetUpSeccompFilter(uid, is_child_zygote);
+ MountEmulatedStorage(uid, mount_external, need_pre_initialize_native_bridge, fail_fn);
- // Must be called before losing the permission to set scheduler policy.
- SetSchedulerPolicy(fail_fn, is_top_app);
+ // Make sure app is running in its own mount namespace before isolating its data directories.
+ ensureInAppMountNamespace(fail_fn);
- if (setresuid(uid, uid, uid) == -1) {
- fail_fn(CREATE_ERROR("setresuid(%d) failed: %s", uid, strerror(errno)));
- }
-
- // The "dumpable" flag of a process, which controls core dump generation, is
- // overwritten by the value in /proc/sys/fs/suid_dumpable when the effective
- // user or group ID changes. See proc(5) for possible values. In most cases,
- // the value is 0, so core dumps are disabled for zygote children. However,
- // when running in a Chrome OS container, the value is already set to 2,
- // which allows the external crash reporter to collect all core dumps. Since
- // only system crashes are interested, core dump is disabled for app
- // processes. This also ensures compliance with CTS.
- int dumpable = prctl(PR_GET_DUMPABLE);
- if (dumpable == -1) {
- ALOGE("prctl(PR_GET_DUMPABLE) failed: %s", strerror(errno));
- RuntimeAbort(env, __LINE__, "prctl(PR_GET_DUMPABLE) failed");
- }
-
- if (dumpable == 2 && uid >= AID_APP) {
- if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) == -1) {
- ALOGE("prctl(PR_SET_DUMPABLE, 0) failed: %s", strerror(errno));
- RuntimeAbort(env, __LINE__, "prctl(PR_SET_DUMPABLE, 0) failed");
+ // Sandbox data and jit profile directories by overlaying a tmpfs on those dirs and bind
+ // mount all related packages separately.
+ if (mount_data_dirs) {
+ isolateAppData(env, pkg_data_info_list, allowlisted_data_info_list, uid, process_name,
+ managed_nice_name, fail_fn);
+ isolateJitProfile(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
}
- }
-
- // Set process properties to enable debugging if required.
- if ((runtime_flags & RuntimeFlags::DEBUG_ENABLE_JDWP) != 0) {
- EnableDebugger();
- }
- if ((runtime_flags & RuntimeFlags::PROFILE_FROM_SHELL) != 0) {
- // simpleperf needs the process to be dumpable to profile it.
- if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) == -1) {
- ALOGE("prctl(PR_SET_DUMPABLE) failed: %s", strerror(errno));
- RuntimeAbort(env, __LINE__, "prctl(PR_SET_DUMPABLE, 1) failed");
+ // MOUNT_EXTERNAL_INSTALLER, MOUNT_EXTERNAL_PASS_THROUGH, MOUNT_EXTERNAL_ANDROID_WRITABLE apps
+ // will have mount_storage_dirs == false here (set by ProcessList.needsStorageDataIsolation()),
+ // and hence they won't bind mount storage dirs.
+ if (mount_storage_dirs) {
+ BindMountStorageDirs(env, pkg_data_info_list, uid, process_name, managed_nice_name,
+ fail_fn);
}
- }
- HeapTaggingLevel heap_tagging_level;
- switch (runtime_flags & RuntimeFlags::MEMORY_TAG_LEVEL_MASK) {
- case RuntimeFlags::MEMORY_TAG_LEVEL_TBI:
- heap_tagging_level = M_HEAP_TAGGING_LEVEL_TBI;
- break;
- case RuntimeFlags::MEMORY_TAG_LEVEL_ASYNC:
- heap_tagging_level = M_HEAP_TAGGING_LEVEL_ASYNC;
- break;
- case RuntimeFlags::MEMORY_TAG_LEVEL_SYNC:
- heap_tagging_level = M_HEAP_TAGGING_LEVEL_SYNC;
- break;
- default:
- heap_tagging_level = M_HEAP_TAGGING_LEVEL_NONE;
- break;
- }
- mallopt(M_BIONIC_SET_HEAP_TAGGING_LEVEL, heap_tagging_level);
-
- // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART runtime.
- runtime_flags &= ~RuntimeFlags::MEMORY_TAG_LEVEL_MASK;
-
- // Avoid heap zero initialization for applications without MTE. Zero init may
- // cause app compat problems, use more memory, or reduce performance. While it
- // would be nice to have them for apps, we will have to wait until they are
- // proven out, have more efficient hardware, and/or apply them only to new
- // applications.
- if (!(runtime_flags & RuntimeFlags::NATIVE_HEAP_ZERO_INIT)) {
- mallopt(M_BIONIC_ZERO_INIT, 0);
- }
-
- // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART runtime.
- runtime_flags &= ~RuntimeFlags::NATIVE_HEAP_ZERO_INIT;
-
- bool forceEnableGwpAsan = false;
- switch (runtime_flags & RuntimeFlags::GWP_ASAN_LEVEL_MASK) {
- default:
- case RuntimeFlags::GWP_ASAN_LEVEL_NEVER:
- break;
- case RuntimeFlags::GWP_ASAN_LEVEL_ALWAYS:
- forceEnableGwpAsan = true;
- [[fallthrough]];
- case RuntimeFlags::GWP_ASAN_LEVEL_LOTTERY:
- android_mallopt(M_INITIALIZE_GWP_ASAN, &forceEnableGwpAsan, sizeof(forceEnableGwpAsan));
- }
- // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART runtime.
- runtime_flags &= ~RuntimeFlags::GWP_ASAN_LEVEL_MASK;
-
- if (NeedsNoRandomizeWorkaround()) {
- // Work around ARM kernel ASLR lossage (http://b/5817320).
- int old_personality = personality(0xffffffff);
- int new_personality = personality(old_personality | ADDR_NO_RANDOMIZE);
- if (new_personality == -1) {
- ALOGW("personality(%d) failed: %s", new_personality, strerror(errno));
+ // If this zygote isn't root, it won't be able to create a process group,
+ // since the directory is owned by root.
+ if (!is_system_server && getuid() == 0) {
+ const int rc = createProcessGroup(uid, getpid());
+ if (rc == -EROFS) {
+ ALOGW("createProcessGroup failed, kernel missing CONFIG_CGROUP_CPUACCT?");
+ } else if (rc != 0) {
+ ALOGE("createProcessGroup(%d, %d) failed: %s", uid, /* pid= */ 0, strerror(-rc));
+ }
}
- }
- SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities, fail_fn);
+ SetGids(env, gids, is_child_zygote, fail_fn);
+ SetRLimits(env, rlimits, fail_fn);
- __android_log_close();
- AStatsSocket_close();
+ if (need_pre_initialize_native_bridge) {
+ // Due to the logic behind need_pre_initialize_native_bridge we know that
+ // instruction_set contains a value.
+ android::PreInitializeNativeBridge(app_data_dir.has_value() ? app_data_dir.value().c_str()
+ : nullptr,
+ instruction_set.value().c_str());
+ }
- const char* se_info_ptr = se_info.has_value() ? se_info.value().c_str() : nullptr;
- const char* nice_name_ptr = nice_name.has_value() ? nice_name.value().c_str() : nullptr;
+ if (setresgid(gid, gid, gid) == -1) {
+ fail_fn(CREATE_ERROR("setresgid(%d) failed: %s", gid, strerror(errno)));
+ }
- if (selinux_android_setcontext(uid, is_system_server, se_info_ptr, nice_name_ptr) == -1) {
- fail_fn(CREATE_ERROR("selinux_android_setcontext(%d, %d, \"%s\", \"%s\") failed",
- uid, is_system_server, se_info_ptr, nice_name_ptr));
- }
+ // Must be called when the new process still has CAP_SYS_ADMIN, in this case,
+ // before changing uid from 0, which clears capabilities. The other
+ // alternative is to call prctl(PR_SET_NO_NEW_PRIVS, 1) afterward, but that
+ // breaks SELinux domain transition (see b/71859146). As the result,
+ // privileged syscalls used below still need to be accessible in app process.
+ SetUpSeccompFilter(uid, is_child_zygote);
- // Make it easier to debug audit logs by setting the main thread's name to the
- // nice name rather than "app_process".
- if (nice_name.has_value()) {
- SetThreadName(nice_name.value());
- } else if (is_system_server) {
- SetThreadName("system_server");
- }
+ // Must be called before losing the permission to set scheduler policy.
+ SetSchedulerPolicy(fail_fn, is_top_app);
- // Unset the SIGCHLD handler, but keep ignoring SIGHUP (rationale in SetSignalHandlers).
- UnsetChldSignalHandler();
+ if (setresuid(uid, uid, uid) == -1) {
+ fail_fn(CREATE_ERROR("setresuid(%d) failed: %s", uid, strerror(errno)));
+ }
- if (is_system_server) {
- env->CallStaticVoidMethod(gZygoteClass, gCallPostForkSystemServerHooks, runtime_flags);
+ // The "dumpable" flag of a process, which controls core dump generation, is
+ // overwritten by the value in /proc/sys/fs/suid_dumpable when the effective
+ // user or group ID changes. See proc(5) for possible values. In most cases,
+ // the value is 0, so core dumps are disabled for zygote children. However,
+ // when running in a Chrome OS container, the value is already set to 2,
+ // which allows the external crash reporter to collect all core dumps. Since
+ // only system crashes are interested, core dump is disabled for app
+ // processes. This also ensures compliance with CTS.
+ int dumpable = prctl(PR_GET_DUMPABLE);
+ if (dumpable == -1) {
+ ALOGE("prctl(PR_GET_DUMPABLE) failed: %s", strerror(errno));
+ RuntimeAbort(env, __LINE__, "prctl(PR_GET_DUMPABLE) failed");
+ }
+
+ if (dumpable == 2 && uid >= AID_APP) {
+ if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) == -1) {
+ ALOGE("prctl(PR_SET_DUMPABLE, 0) failed: %s", strerror(errno));
+ RuntimeAbort(env, __LINE__, "prctl(PR_SET_DUMPABLE, 0) failed");
+ }
+ }
+
+ // Set process properties to enable debugging if required.
+ if ((runtime_flags & RuntimeFlags::DEBUG_ENABLE_JDWP) != 0) {
+ EnableDebugger();
+ }
+ if ((runtime_flags & RuntimeFlags::PROFILE_FROM_SHELL) != 0) {
+ // simpleperf needs the process to be dumpable to profile it.
+ if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) == -1) {
+ ALOGE("prctl(PR_SET_DUMPABLE) failed: %s", strerror(errno));
+ RuntimeAbort(env, __LINE__, "prctl(PR_SET_DUMPABLE, 1) failed");
+ }
+ }
+
+ HeapTaggingLevel heap_tagging_level;
+ switch (runtime_flags & RuntimeFlags::MEMORY_TAG_LEVEL_MASK) {
+ case RuntimeFlags::MEMORY_TAG_LEVEL_TBI:
+ heap_tagging_level = M_HEAP_TAGGING_LEVEL_TBI;
+ break;
+ case RuntimeFlags::MEMORY_TAG_LEVEL_ASYNC:
+ heap_tagging_level = M_HEAP_TAGGING_LEVEL_ASYNC;
+ break;
+ case RuntimeFlags::MEMORY_TAG_LEVEL_SYNC:
+ heap_tagging_level = M_HEAP_TAGGING_LEVEL_SYNC;
+ break;
+ default:
+ heap_tagging_level = M_HEAP_TAGGING_LEVEL_NONE;
+ break;
+ }
+ mallopt(M_BIONIC_SET_HEAP_TAGGING_LEVEL, heap_tagging_level);
+
+ // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART
+ // runtime.
+ runtime_flags &= ~RuntimeFlags::MEMORY_TAG_LEVEL_MASK;
+
+ // Avoid heap zero initialization for applications without MTE. Zero init may
+ // cause app compat problems, use more memory, or reduce performance. While it
+ // would be nice to have them for apps, we will have to wait until they are
+ // proven out, have more efficient hardware, and/or apply them only to new
+ // applications.
+ if (!(runtime_flags & RuntimeFlags::NATIVE_HEAP_ZERO_INIT)) {
+ mallopt(M_BIONIC_ZERO_INIT, 0);
+ }
+
+ // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART
+ // runtime.
+ runtime_flags &= ~RuntimeFlags::NATIVE_HEAP_ZERO_INIT;
+
+ bool forceEnableGwpAsan = false;
+ switch (runtime_flags & RuntimeFlags::GWP_ASAN_LEVEL_MASK) {
+ default:
+ case RuntimeFlags::GWP_ASAN_LEVEL_NEVER:
+ break;
+ case RuntimeFlags::GWP_ASAN_LEVEL_ALWAYS:
+ forceEnableGwpAsan = true;
+ [[fallthrough]];
+ case RuntimeFlags::GWP_ASAN_LEVEL_LOTTERY:
+ android_mallopt(M_INITIALIZE_GWP_ASAN, &forceEnableGwpAsan, sizeof(forceEnableGwpAsan));
+ }
+ // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART
+ // runtime.
+ runtime_flags &= ~RuntimeFlags::GWP_ASAN_LEVEL_MASK;
+
+ if (NeedsNoRandomizeWorkaround()) {
+ // Work around ARM kernel ASLR lossage (http://b/5817320).
+ int old_personality = personality(0xffffffff);
+ int new_personality = personality(old_personality | ADDR_NO_RANDOMIZE);
+ if (new_personality == -1) {
+ ALOGW("personality(%d) failed: %s", new_personality, strerror(errno));
+ }
+ }
+
+ SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities,
+ fail_fn);
+
+ __android_log_close();
+ AStatsSocket_close();
+
+ const char* se_info_ptr = se_info.has_value() ? se_info.value().c_str() : nullptr;
+ const char* nice_name_ptr = nice_name.has_value() ? nice_name.value().c_str() : nullptr;
+
+ if (selinux_android_setcontext(uid, is_system_server, se_info_ptr, nice_name_ptr) == -1) {
+ fail_fn(CREATE_ERROR("selinux_android_setcontext(%d, %d, \"%s\", \"%s\") failed", uid,
+ is_system_server, se_info_ptr, nice_name_ptr));
+ }
+
+ // Make it easier to debug audit logs by setting the main thread's name to the
+ // nice name rather than "app_process".
+ if (nice_name.has_value()) {
+ SetThreadName(nice_name.value());
+ } else if (is_system_server) {
+ SetThreadName("system_server");
+ }
+
+ // Unset the SIGCHLD handler, but keep ignoring SIGHUP (rationale in SetSignalHandlers).
+ UnsetChldSignalHandler();
+
+ if (is_system_server) {
+ env->CallStaticVoidMethod(gZygoteClass, gCallPostForkSystemServerHooks, runtime_flags);
+ if (env->ExceptionCheck()) {
+ fail_fn("Error calling post fork system server hooks.");
+ }
+
+ // TODO(b/117874058): Remove hardcoded label here.
+ static const char* kSystemServerLabel = "u:r:system_server:s0";
+ if (selinux_android_setcon(kSystemServerLabel) != 0) {
+ fail_fn(CREATE_ERROR("selinux_android_setcon(%s)", kSystemServerLabel));
+ }
+ }
+
+ if (is_child_zygote) {
+ initUnsolSocketToSystemServer();
+ }
+
+ env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, runtime_flags,
+ is_system_server, is_child_zygote, managed_instruction_set);
+
+ // Reset the process priority to the default value.
+ setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_DEFAULT);
+
if (env->ExceptionCheck()) {
- fail_fn("Error calling post fork system server hooks.");
+ fail_fn("Error calling post fork hooks.");
}
-
- // TODO(oth): Remove hardcoded label here (b/117874058).
- static const char* kSystemServerLabel = "u:r:system_server:s0";
- if (selinux_android_setcon(kSystemServerLabel) != 0) {
- fail_fn(CREATE_ERROR("selinux_android_setcon(%s)", kSystemServerLabel));
- }
- }
-
- if (is_child_zygote) {
- initUnsolSocketToSystemServer();
- }
-
- env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, runtime_flags,
- is_system_server, is_child_zygote, managed_instruction_set);
-
- // Reset the process priority to the default value.
- setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_DEFAULT);
-
- if (env->ExceptionCheck()) {
- fail_fn("Error calling post fork hooks.");
- }
}
static uint64_t GetEffectiveCapabilityMask(JNIEnv* env) {
@@ -2069,12 +2069,11 @@
NO_PAC_FUNC
static jint com_android_internal_os_Zygote_nativeForkAndSpecialize(
- JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
- jint runtime_flags, jobjectArray rlimits,
- jint mount_external, jstring se_info, jstring nice_name,
+ JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, jint runtime_flags,
+ jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote,
jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
- jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list,
+ jobjectArray pkg_data_info_list, jobjectArray allowlisted_data_info_list,
jboolean mount_data_dirs, jboolean mount_storage_dirs) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
@@ -2109,14 +2108,11 @@
pid_t pid = zygote::ForkCommon(env, false, fds_to_close, fds_to_ignore, true);
if (pid == 0) {
- SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
- capabilities, capabilities,
- mount_external, se_info, nice_name, false,
- is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
- is_top_app == JNI_TRUE, pkg_data_info_list,
- whitelisted_data_info_list,
- mount_data_dirs == JNI_TRUE,
- mount_storage_dirs == JNI_TRUE);
+ SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities,
+ mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE,
+ instruction_set, app_data_dir, is_top_app == JNI_TRUE, pkg_data_info_list,
+ allowlisted_data_info_list, mount_data_dirs == JNI_TRUE,
+ mount_storage_dirs == JNI_TRUE);
}
return pid;
}
@@ -2148,12 +2144,11 @@
if (pid == 0) {
// System server prcoess does not need data isolation so no need to
// know pkg_data_info_list.
- SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
- permitted_capabilities, effective_capabilities,
- MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
+ SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities,
+ effective_capabilities, MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
false, nullptr, nullptr, /* is_top_app= */ false,
/* pkg_data_info_list */ nullptr,
- /* whitelisted_data_info_list */ nullptr, false, false);
+ /* allowlisted_data_info_list */ nullptr, false, false);
} else if (pid > 0) {
// The zygote process checks whether the child process has died or not.
ALOGI("System server process %d has been created", pid);
@@ -2261,7 +2256,7 @@
if (!path_cstr) {
RuntimeAbort(env, __LINE__, "path_cstr == nullptr");
}
- FileDescriptorWhitelist::Get()->Allow(path_cstr);
+ FileDescriptorAllowlist::Get()->Allow(path_cstr);
}
static void com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter(
@@ -2296,20 +2291,19 @@
* @param is_top_app If the process is for top (high priority) application
*/
static void com_android_internal_os_Zygote_nativeSpecializeAppProcess(
- JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
- jint runtime_flags, jobjectArray rlimits,
- jint mount_external, jstring se_info, jstring nice_name,
- jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
- jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list,
- jboolean mount_data_dirs, jboolean mount_storage_dirs) {
- jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
+ JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, jint runtime_flags,
+ jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
+ jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir,
+ jboolean is_top_app, jobjectArray pkg_data_info_list,
+ jobjectArray allowlisted_data_info_list, jboolean mount_data_dirs,
+ jboolean mount_storage_dirs) {
+ jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
- SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
- capabilities, capabilities,
- mount_external, se_info, nice_name, false,
- is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
- is_top_app == JNI_TRUE, pkg_data_info_list, whitelisted_data_info_list,
- mount_data_dirs == JNI_TRUE, mount_storage_dirs == JNI_TRUE);
+ SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities,
+ mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE,
+ instruction_set, app_data_dir, is_top_app == JNI_TRUE, pkg_data_info_list,
+ allowlisted_data_info_list, mount_data_dirs == JNI_TRUE,
+ mount_storage_dirs == JNI_TRUE);
}
/**
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 06a71cb..7fa627b 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -31,8 +31,8 @@
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
-// Static whitelist of open paths that the zygote is allowed to keep open.
-static const char* kPathWhitelist[] = {
+// Static allowlist of open paths that the zygote is allowed to keep open.
+static const char* kPathAllowlist[] = {
"/dev/null",
"/dev/socket/zygote",
"/dev/socket/zygote_secondary",
@@ -53,118 +53,114 @@
static const char kFdPath[] = "/proc/self/fd";
// static
-FileDescriptorWhitelist* FileDescriptorWhitelist::Get() {
- if (instance_ == nullptr) {
- instance_ = new FileDescriptorWhitelist();
- }
- return instance_;
+FileDescriptorAllowlist* FileDescriptorAllowlist::Get() {
+ if (instance_ == nullptr) {
+ instance_ = new FileDescriptorAllowlist();
+ }
+ return instance_;
}
static bool IsArtMemfd(const std::string& path) {
return android::base::StartsWith(path, "/memfd:/boot-image-methods.art");
}
-bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const {
- // Check the static whitelist path.
- for (const auto& whitelist_path : kPathWhitelist) {
- if (path == whitelist_path)
- return true;
- }
-
- // Check any paths added to the dynamic whitelist.
- for (const auto& whitelist_path : whitelist_) {
- if (path == whitelist_path)
- return true;
- }
-
- // Framework jars are allowed.
- static const char* kFrameworksPrefix[] = {
- "/system/framework/",
- "/system_ext/framework/",
- };
-
- static const char* kJarSuffix = ".jar";
-
- for (const auto& frameworks_prefix : kFrameworksPrefix) {
- if (android::base::StartsWith(path, frameworks_prefix)
- && android::base::EndsWith(path, kJarSuffix)) {
- return true;
+bool FileDescriptorAllowlist::IsAllowed(const std::string& path) const {
+ // Check the static allowlist path.
+ for (const auto& allowlist_path : kPathAllowlist) {
+ if (path == allowlist_path) return true;
}
- }
- // Jars from APEXes are allowed. This matches /apex/**/javalib/*.jar.
- static const char* kApexPrefix = "/apex/";
- static const char* kApexJavalibPathSuffix = "/javalib";
- if (android::base::StartsWith(path, kApexPrefix) && android::base::EndsWith(path, kJarSuffix) &&
- android::base::EndsWith(android::base::Dirname(path), kApexJavalibPathSuffix)) {
- return true;
- }
+ // Check any paths added to the dynamic allowlist.
+ for (const auto& allowlist_path : allowlist_) {
+ if (path == allowlist_path) return true;
+ }
- // the in-memory file created by ART through memfd_create is allowed.
- if (IsArtMemfd(path)) {
- return true;
- }
+ // Framework jars are allowed.
+ static const char* kFrameworksPrefix[] = {
+ "/system/framework/",
+ "/system_ext/framework/",
+ };
- // Whitelist files needed for Runtime Resource Overlay, like these:
- // /system/vendor/overlay/framework-res.apk
- // /system/vendor/overlay-subdir/pg/framework-res.apk
- // /vendor/overlay/framework-res.apk
- // /vendor/overlay/PG/android-framework-runtime-resource-overlay.apk
- // /data/resource-cache/system@vendor@overlay@framework-res.apk@idmap
- // /data/resource-cache/system@vendor@overlay-subdir@pg@framework-res.apk@idmap
- // See AssetManager.cpp for more details on overlay-subdir.
- static const char* kOverlayDir = "/system/vendor/overlay/";
- static const char* kVendorOverlayDir = "/vendor/overlay";
- static const char* kVendorOverlaySubdir = "/system/vendor/overlay-subdir/";
- static const char* kSystemProductOverlayDir = "/system/product/overlay/";
- static const char* kProductOverlayDir = "/product/overlay";
- static const char* kSystemSystemExtOverlayDir = "/system/system_ext/overlay/";
- static const char* kSystemExtOverlayDir = "/system_ext/overlay";
- static const char* kSystemOdmOverlayDir = "/system/odm/overlay";
- static const char* kOdmOverlayDir = "/odm/overlay";
- static const char* kSystemOemOverlayDir = "/system/oem/overlay";
- static const char* kOemOverlayDir = "/oem/overlay";
- static const char* kApkSuffix = ".apk";
+ static const char* kJarSuffix = ".jar";
- if ((android::base::StartsWith(path, kOverlayDir)
- || android::base::StartsWith(path, kVendorOverlaySubdir)
- || android::base::StartsWith(path, kVendorOverlayDir)
- || android::base::StartsWith(path, kSystemProductOverlayDir)
- || android::base::StartsWith(path, kProductOverlayDir)
- || android::base::StartsWith(path, kSystemSystemExtOverlayDir)
- || android::base::StartsWith(path, kSystemExtOverlayDir)
- || android::base::StartsWith(path, kSystemOdmOverlayDir)
- || android::base::StartsWith(path, kOdmOverlayDir)
- || android::base::StartsWith(path, kSystemOemOverlayDir)
- || android::base::StartsWith(path, kOemOverlayDir))
- && android::base::EndsWith(path, kApkSuffix)
- && path.find("/../") == std::string::npos) {
- return true;
- }
+ for (const auto& frameworks_prefix : kFrameworksPrefix) {
+ if (android::base::StartsWith(path, frameworks_prefix) &&
+ android::base::EndsWith(path, kJarSuffix)) {
+ return true;
+ }
+ }
- static const char* kOverlayIdmapPrefix = "/data/resource-cache/";
- static const char* kOverlayIdmapSuffix = ".apk@idmap";
- if (android::base::StartsWith(path, kOverlayIdmapPrefix)
- && android::base::EndsWith(path, kOverlayIdmapSuffix)
- && path.find("/../") == std::string::npos) {
- return true;
- }
+ // Jars from APEXes are allowed. This matches /apex/**/javalib/*.jar.
+ static const char* kApexPrefix = "/apex/";
+ static const char* kApexJavalibPathSuffix = "/javalib";
+ if (android::base::StartsWith(path, kApexPrefix) && android::base::EndsWith(path, kJarSuffix) &&
+ android::base::EndsWith(android::base::Dirname(path), kApexJavalibPathSuffix)) {
+ return true;
+ }
- // All regular files that are placed under this path are whitelisted automatically.
- static const char* kZygoteWhitelistPath = "/vendor/zygote_whitelist/";
- if (android::base::StartsWith(path, kZygoteWhitelistPath)
- && path.find("/../") == std::string::npos) {
- return true;
- }
+ // the in-memory file created by ART through memfd_create is allowed.
+ if (IsArtMemfd(path)) {
+ return true;
+ }
- return false;
+ // Allowlist files needed for Runtime Resource Overlay, like these:
+ // /system/vendor/overlay/framework-res.apk
+ // /system/vendor/overlay-subdir/pg/framework-res.apk
+ // /vendor/overlay/framework-res.apk
+ // /vendor/overlay/PG/android-framework-runtime-resource-overlay.apk
+ // /data/resource-cache/system@vendor@overlay@framework-res.apk@idmap
+ // /data/resource-cache/system@vendor@overlay-subdir@pg@framework-res.apk@idmap
+ // See AssetManager.cpp for more details on overlay-subdir.
+ static const char* kOverlayDir = "/system/vendor/overlay/";
+ static const char* kVendorOverlayDir = "/vendor/overlay";
+ static const char* kVendorOverlaySubdir = "/system/vendor/overlay-subdir/";
+ static const char* kSystemProductOverlayDir = "/system/product/overlay/";
+ static const char* kProductOverlayDir = "/product/overlay";
+ static const char* kSystemSystemExtOverlayDir = "/system/system_ext/overlay/";
+ static const char* kSystemExtOverlayDir = "/system_ext/overlay";
+ static const char* kSystemOdmOverlayDir = "/system/odm/overlay";
+ static const char* kOdmOverlayDir = "/odm/overlay";
+ static const char* kSystemOemOverlayDir = "/system/oem/overlay";
+ static const char* kOemOverlayDir = "/oem/overlay";
+ static const char* kApkSuffix = ".apk";
+
+ if ((android::base::StartsWith(path, kOverlayDir) ||
+ android::base::StartsWith(path, kVendorOverlaySubdir) ||
+ android::base::StartsWith(path, kVendorOverlayDir) ||
+ android::base::StartsWith(path, kSystemProductOverlayDir) ||
+ android::base::StartsWith(path, kProductOverlayDir) ||
+ android::base::StartsWith(path, kSystemSystemExtOverlayDir) ||
+ android::base::StartsWith(path, kSystemExtOverlayDir) ||
+ android::base::StartsWith(path, kSystemOdmOverlayDir) ||
+ android::base::StartsWith(path, kOdmOverlayDir) ||
+ android::base::StartsWith(path, kSystemOemOverlayDir) ||
+ android::base::StartsWith(path, kOemOverlayDir)) &&
+ android::base::EndsWith(path, kApkSuffix) && path.find("/../") == std::string::npos) {
+ return true;
+ }
+
+ static const char* kOverlayIdmapPrefix = "/data/resource-cache/";
+ static const char* kOverlayIdmapSuffix = ".apk@idmap";
+ if (android::base::StartsWith(path, kOverlayIdmapPrefix) &&
+ android::base::EndsWith(path, kOverlayIdmapSuffix) &&
+ path.find("/../") == std::string::npos) {
+ return true;
+ }
+
+ // All regular files that are placed under this path are allowlisted
+ // automatically. The directory name is maintained for compatibility.
+ static const char* kZygoteAllowlistPath = "/vendor/zygote_whitelist/";
+ if (android::base::StartsWith(path, kZygoteAllowlistPath) &&
+ path.find("/../") == std::string::npos) {
+ return true;
+ }
+
+ return false;
}
-FileDescriptorWhitelist::FileDescriptorWhitelist()
- : whitelist_() {
-}
+FileDescriptorAllowlist::FileDescriptorAllowlist() : allowlist_() {}
-FileDescriptorWhitelist* FileDescriptorWhitelist::instance_ = nullptr;
+FileDescriptorAllowlist* FileDescriptorAllowlist::instance_ = nullptr;
// Keeps track of all relevant information (flags, offset etc.) of an
// open zygote file descriptor.
@@ -217,7 +213,7 @@
fail_fn(android::base::StringPrintf("Unable to stat %d", fd));
}
- const FileDescriptorWhitelist* whitelist = FileDescriptorWhitelist::Get();
+ const FileDescriptorAllowlist* allowlist = FileDescriptorAllowlist::Get();
if (S_ISSOCK(f_stat.st_mode)) {
std::string socket_name;
@@ -225,16 +221,15 @@
fail_fn("Unable to get socket name");
}
- if (!whitelist->IsAllowed(socket_name)) {
- fail_fn(android::base::StringPrintf("Socket name not whitelisted : %s (fd=%d)",
- socket_name.c_str(),
- fd));
+ if (!allowlist->IsAllowed(socket_name)) {
+ fail_fn(android::base::StringPrintf("Socket name not allowlisted : %s (fd=%d)",
+ socket_name.c_str(), fd));
}
return new FileDescriptorInfo(fd);
}
- // We only handle whitelisted regular files and character devices. Whitelisted
+ // We only handle allowlisted regular files and character devices. Allowlisted
// character devices must provide a guarantee of sensible behaviour when
// reopened.
//
@@ -268,8 +263,8 @@
strerror(errno)));
}
- if (!whitelist->IsAllowed(file_path)) {
- fail_fn(android::base::StringPrintf("Not whitelisted (%d): %s", fd, file_path.c_str()));
+ if (!allowlist->IsAllowed(file_path)) {
+ fail_fn(android::base::StringPrintf("Not allowlisted (%d): %s", fd, file_path.c_str()));
}
// File descriptor flags : currently on FD_CLOEXEC. We can set these
diff --git a/core/jni/fd_utils.h b/core/jni/fd_utils.h
index 2caf157..14c318e 100644
--- a/core/jni/fd_utils.h
+++ b/core/jni/fd_utils.h
@@ -33,42 +33,40 @@
// This type is duplicated in com_android_internal_os_Zygote.cpp
typedef const std::function<void(std::string)>& fail_fn_t;
-// Whitelist of open paths that the zygote is allowed to keep open.
+// Allowlist of open paths that the zygote is allowed to keep open.
//
-// In addition to the paths listed in kPathWhitelist in file_utils.cpp, and
+// In addition to the paths listed in kPathAllowlist in file_utils.cpp, and
// paths dynamically added with Allow(), all files ending with ".jar"
-// under /system/framework" are whitelisted. See IsAllowed() for the canonical
+// under /system/framework" are allowlisted. See IsAllowed() for the canonical
// definition.
//
-// If the whitelisted path is associated with a regular file or a
+// If the allowlisted path is associated with a regular file or a
// character device, the file is reopened after a fork with the same
-// offset and mode. If the whilelisted path is associated with a
+// offset and mode. If the allowlisted path is associated with a
// AF_UNIX socket, the socket will refer to /dev/null after each
// fork, and all operations on it will fail.
-class FileDescriptorWhitelist {
- public:
- // Lazily creates the global whitelist.
- static FileDescriptorWhitelist* Get();
+class FileDescriptorAllowlist {
+public:
+ // Lazily creates the global allowlist.
+ static FileDescriptorAllowlist* Get();
- // Adds a path to the whitelist.
- void Allow(const std::string& path) {
- whitelist_.push_back(path);
- }
+ // Adds a path to the allowlist.
+ void Allow(const std::string& path) { allowlist_.push_back(path); }
- // Returns true iff. a given path is whitelisted. A path is whitelisted
- // if it belongs to the whitelist (see kPathWhitelist) or if it's a path
- // under /system/framework that ends with ".jar" or if it is a system
- // framework overlay.
- bool IsAllowed(const std::string& path) const;
+ // Returns true iff. a given path is allowlisted. A path is allowlisted
+ // if it belongs to the allowlist (see kPathAllowlist) or if it's a path
+ // under /system/framework that ends with ".jar" or if it is a system
+ // framework overlay.
+ bool IsAllowed(const std::string& path) const;
- private:
- FileDescriptorWhitelist();
+private:
+ FileDescriptorAllowlist();
- static FileDescriptorWhitelist* instance_;
+ static FileDescriptorAllowlist* instance_;
- std::vector<std::string> whitelist_;
+ std::vector<std::string> allowlist_;
- DISALLOW_COPY_AND_ASSIGN(FileDescriptorWhitelist);
+ DISALLOW_COPY_AND_ASSIGN(FileDescriptorAllowlist);
};
// A FileDescriptorTable is a collection of FileDescriptorInfo objects
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index ec502c3..f26bf7c 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -337,7 +337,7 @@
optional bool starting_displayed = 20;
optional bool starting_moved = 201;
optional bool visible_set_from_transferred_starting_window = 22;
- repeated .android.graphics.RectProto frozen_bounds = 23;
+ repeated .android.graphics.RectProto frozen_bounds = 23 [deprecated=true];
optional bool visible = 24;
reserved 25; // configuration_container
optional IdentifierProto identifier = 26 [deprecated=true];
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ecb7c1d3..072bb87 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1876,15 +1876,20 @@
<permission android:name="android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE"
android:protectionLevel="signature|privileged" />
- <!-- @SystemApi @hide Allows system APK to update Wifi/Cellular coex channels to avoid.
+ <!-- @SystemApi @hide Allows applications to update Wifi/Cellular coex channels to avoid.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|role" />
<!-- @SystemApi @hide Allows applications to access Wifi/Cellular coex channels being avoided.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi @hide Allows system APK to manage country code.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MANAGE_WIFI_COUNTRY_CODE"
+ android:protectionLevel="signature" />
<!-- @SystemApi @hide Allows an application to manage an automotive device's application network
preference as it relates to OEM_PAID and OEM_PRIVATE capable networks.
diff --git a/packages/SystemUI/res/layout/udfps_animation_view.xml b/core/res/res/color/text_color_primary_device_default_dark.xml
similarity index 66%
copy from packages/SystemUI/res/layout/udfps_animation_view.xml
copy to core/res/res/color/text_color_primary_device_default_dark.xml
index 380dd85..90d6b07 100644
--- a/packages/SystemUI/res/layout/udfps_animation_view.xml
+++ b/core/res/res/color/text_color_primary_device_default_dark.xml
@@ -14,8 +14,10 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.systemui.biometrics.UdfpsAnimationView
- 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"/>
+<!-- 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/packages/SystemUI/res/layout/udfps_animation_view.xml b/core/res/res/color/text_color_primary_device_default_light.xml
similarity index 66%
copy from packages/SystemUI/res/layout/udfps_animation_view.xml
copy to core/res/res/color/text_color_primary_device_default_light.xml
index 380dd85..bdc4fa9 100644
--- a/packages/SystemUI/res/layout/udfps_animation_view.xml
+++ b/core/res/res/color/text_color_primary_device_default_light.xml
@@ -14,8 +14,10 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.systemui.biometrics.UdfpsAnimationView
- 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"/>
+<!-- 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/packages/SystemUI/res/layout/udfps_animation_view.xml b/core/res/res/color/text_color_secondary_device_default_dark.xml
similarity index 66%
copy from packages/SystemUI/res/layout/udfps_animation_view.xml
copy to core/res/res/color/text_color_secondary_device_default_dark.xml
index 380dd85..799636ad 100644
--- a/packages/SystemUI/res/layout/udfps_animation_view.xml
+++ b/core/res/res/color/text_color_secondary_device_default_dark.xml
@@ -14,8 +14,10 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.systemui.biometrics.UdfpsAnimationView
- 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"/>
+<!-- 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/packages/SystemUI/res/layout/udfps_animation_view.xml b/core/res/res/color/text_color_secondary_device_default_light.xml
similarity index 66%
copy from packages/SystemUI/res/layout/udfps_animation_view.xml
copy to core/res/res/color/text_color_secondary_device_default_light.xml
index 380dd85..4793bb8 100644
--- a/packages/SystemUI/res/layout/udfps_animation_view.xml
+++ b/core/res/res/color/text_color_secondary_device_default_light.xml
@@ -14,8 +14,10 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.systemui.biometrics.UdfpsAnimationView
- 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"/>
+<!-- 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/packages/SystemUI/res/layout/udfps_animation_view.xml b/core/res/res/color/text_color_tertiary_device_default_dark.xml
similarity index 66%
copy from packages/SystemUI/res/layout/udfps_animation_view.xml
copy to core/res/res/color/text_color_tertiary_device_default_dark.xml
index 380dd85..c828631 100644
--- a/packages/SystemUI/res/layout/udfps_animation_view.xml
+++ b/core/res/res/color/text_color_tertiary_device_default_dark.xml
@@ -14,8 +14,10 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.systemui.biometrics.UdfpsAnimationView
- 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"/>
+<!-- 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/packages/SystemUI/res/layout/udfps_animation_view.xml b/core/res/res/color/text_color_tertiary_device_default_light.xml
similarity index 66%
copy from packages/SystemUI/res/layout/udfps_animation_view.xml
copy to core/res/res/color/text_color_tertiary_device_default_light.xml
index 380dd85..82c420a 100644
--- a/packages/SystemUI/res/layout/udfps_animation_view.xml
+++ b/core/res/res/color/text_color_tertiary_device_default_light.xml
@@ -14,8 +14,10 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.systemui.biometrics.UdfpsAnimationView
- 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"/>
+<!-- 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
similarity index 90%
rename from core/res/res/drawable/conversation_unread_bg.xml
rename to core/res/res/drawable/expand_button_pill_bg.xml
index d3e00cf..f95044a 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 ca4f0ed..a06ec9f 100644
--- a/core/res/res/drawable/ic_collapse_notification.xml
+++ b/core/res/res/drawable/ic_collapse_notification.xml
@@ -21,8 +21,5 @@
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 a080ce4..160a9c2 100644
--- a/core/res/res/drawable/ic_expand_notification.xml
+++ b/core/res/res/drawable/ic_expand_notification.xml
@@ -21,8 +21,5 @@
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 0000000..f92e6d6
--- /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 1de1d04..81a79c5 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 b83611bc..bad9a6b 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 471d874..7b52ec3 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 f3aa540..42fb4a2 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 45e11ba..bed5c31 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 1020c14..9b56321 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 b0a4c6e..633e216 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>
@@ -1973,6 +1964,8 @@
<string name="config_systemContacts" translatable="false">com.android.contacts</string>
<!-- The name of the package that will hold the speech recognizer role by default. -->
<string name="config_systemSpeechRecognizer" translatable="false"></string>
+ <!-- The name of the package that will hold the system Wi-Fi coex manager role. -->
+ <string name="config_systemWifiCoexManager" translateable="false"></string>
<!-- The name of the package that will be allowed to change its components' label/icon. -->
<string name="config_overrideComponentUiPackage" translatable="false"></string>
@@ -3957,6 +3950,10 @@
color supplied by the Notification.Builder if present. -->
<bool name="config_tintNotificationActionButtons">true</bool>
+ <!-- Flag indicating that tinted items (actions, expander, etc) are to be tinted using the
+ theme color, rather than the notification color. -->
+ <bool name="config_tintNotificationsWithTheme">true</bool>
+
<!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
<bool name="config_showAreaUpdateInfoSettings">false</bool>
@@ -4671,15 +4668,6 @@
<!-- WindowsManager JetPack display features -->
<string name="config_display_features" translatable="false" />
- <!-- Physical Display IDs of the display-devices that are swapped when a folding device folds.
- This list is expected to contain two elements: the first is the display to use
- when the device is folded, the second is the display to use when unfolded. If the array
- is empty or the display IDs are not recognized, this feature is turned off and the value
- ignored.
- TODO: b/170470621 - remove once we can have multiple Internal displays in DMS as
- well as a notification from DisplayStateManager. -->
- <string-array name="config_internalFoldedPhysicalDisplayIds" translatable="false" />
-
<!-- Aspect ratio of task level letterboxing. Values <= 1.0 will be ignored.
Note: Activity min/max aspect ratio restrictions will still be respected by the
activity-level letterboxing (size-compat mode). Therefore this override can control the
@@ -4710,6 +4698,14 @@
<!-- If true, hide the display cutout with display area -->
<bool name="config_hideDisplayCutoutWithDisplayArea">false</bool>
+ <!-- The timeout value in milliseconds used by SelectionActionModeHelper for each selections
+ when TextClassifier has been initialized. -->
+ <integer name="config_smartSelectionInitializedTimeoutMillis">200</integer>
+
+ <!-- The timeout value in milliseconds used by SelectionActionModeHelper for each selections
+ when TextClassifier has not been initialized. -->
+ <integer name="config_smartSelectionInitializingTimeoutMillis">500</integer>
+
<!-- Indicates that default fitness tracker app needs to request sensor and location permissions. -->
<bool name="config_trackerAppNeedsPermissions">false</bool>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index c2b6b99..10aa7b3 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -224,7 +224,7 @@
<dimen name="notification_content_margin_end">16dp</dimen>
<!-- The margin on the end of the top-line content views (accommodates the expander) -->
- <dimen name="notification_heading_margin_end">48dp</dimen>
+ <dimen name="notification_heading_margin_end">56dp</dimen>
<!-- The margin for text at the end of the image view for media notifications -->
<dimen name="notification_media_image_margin_end">72dp</dimen>
@@ -248,7 +248,7 @@
<dimen name="call_notification_collapsible_indent">64dp</dimen>
<!-- The size of icons for visual actions in the notification_material_action_list -->
- <dimen name="notification_actions_icon_size">48dp</dimen>
+ <dimen name="notification_actions_icon_size">56dp</dimen>
<!-- The size of icons for visual actions in the notification_material_action_list -->
<dimen name="notification_actions_icon_drawable_size">20dp</dimen>
@@ -314,10 +314,10 @@
<dimen name="notification_conversation_header_separating_margin">4dp</dimen>
<!-- The absolute size of the notification expand icon. -->
- <dimen name="notification_header_expand_icon_size">48dp</dimen>
+ <dimen name="notification_header_expand_icon_size">56dp</dimen>
- <!-- The top padding for the notification expand button. -->
- <dimen name="notification_expand_button_padding_top">1dp</dimen>
+ <!-- the height of the expand button pill -->
+ <dimen name="notification_expand_button_pill_height">24dp</dimen>
<!-- Vertical margin for the headerless notification content, when content has 1 line -->
<!-- 16 * 2 (margins) + 24 (1 line) = 56 (notification) -->
@@ -690,6 +690,13 @@
<!-- The default minimal size of a PiP task, in both dimensions. -->
<dimen name="default_minimal_size_pip_resizable_task">108dp</dimen>
+ <!--
+ The overridable minimal size of a PiP task, in both dimensions.
+ Different from default_minimal_size_pip_resizable_task, this is to limit the dimension
+ when the pinned stack size is overridden by app via minWidth/minHeight.
+ -->
+ <dimen name="overridable_minimal_size_pip_resizable_task">48dp</dimen>
+
<!-- Height of a task when in minimized mode from the top when launcher is resizable. -->
<dimen name="task_height_of_minimized_mode">80dp</dimen>
@@ -739,7 +746,7 @@
<dimen name="notification_right_icon_headerless_margin">20dp</dimen>
<!-- The top margin of the right icon in the "big" notification states -->
<!-- TODO(b/181048615): Move the large icon below the expander in big states -->
- <dimen name="notification_right_icon_big_margin_top">16dp</dimen>
+ <dimen name="notification_right_icon_big_margin_top">20dp</dimen>
<!-- The size of the left icon -->
<dimen name="notification_left_icon_size">@dimen/notification_icon_circle_size</dimen>
<!-- The left padding of the left icon -->
@@ -770,13 +777,10 @@
<dimen name="conversation_icon_circle_start">28dp</dimen>
<!-- Start of the content in the conversation template -->
<dimen name="conversation_content_start">80dp</dimen>
- <!-- Size of the expand button in the conversation layout -->
- <dimen name="conversation_expand_button_size">80dp</dimen>
- <!-- Top margin of the expand button for conversations when expanded -->
- <dimen name="conversation_expand_button_top_margin_expanded">18dp</dimen>
- <!-- Side margin of the expand button for conversations.
- width of expand asset (22) + 2 * this (13) == notification_header_expand_icon_size (48) -->
- <dimen name="conversation_expand_button_side_margin">13dp</dimen>
+ <!-- Height of the expand button in the conversation layout -->
+ <dimen name="conversation_expand_button_height">80dp</dimen>
+ <!-- this is the margin between the Conversation image and the content -->
+ <dimen name="conversation_image_start_margin">12dp</dimen>
<!-- Side margins of the conversation badge in relation to the conversation icon -->
<dimen name="conversation_badge_side_margin">36dp</dimen>
<!-- size of the notification badge when applied to the conversation icon -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 2004d0a..9bc92e1 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3168,6 +3168,8 @@
<public name="config_customMediaSessionPolicyProvider" />
<!-- @hide @SystemApi -->
<public name="config_systemSpeechRecognizer" />
+ <!-- @hide @SystemApi -->
+ <public name="config_systemWifiCoexManager" />
</public-group>
<public-group type="id" first-id="0x01020055">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 71ba44b..2b1168f 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 aecb015..ff9d26f 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 3706e4b..b0c1f25 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.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 @@
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 0000000..1271fa79
--- /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 4d04a7a..8de9454 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 @@
"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 eae41e3..7bc81cd 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 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.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 @@
.that(readFamily(serialized)).isEqualTo(expected);
}
+ @Test
+ public void invalidXml_unpaired_family() throws Exception {
+ String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font index='0'>test.ttc</font>"
+ + "</familyset>";
+
+ try (InputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) {
+ FontListParser.parse(is);
+ fail();
+ } catch (IOException | XmlPullParserException e) {
+ // pass
+ }
+ }
+
+ @Test
+ public void invalidXml_unpaired_font() throws Exception {
+ String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font index='0'>test.ttc"
+ + " </family>"
+ + "</familyset>";
+
+ try (InputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) {
+ FontListParser.parse(is);
+ fail();
+ } catch (IOException | XmlPullParserException e) {
+ // pass
+ }
+ }
+
+ @Test
+ public void invalidXml_unpaired_axis() throws Exception {
+ String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font index='0'>test.ttc"
+ + " <axis tag=\"wght\" styleValue=\"0\" >"
+ + " </font>"
+ + " </family>"
+ + "</familyset>";
+
+ try (InputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) {
+ FontListParser.parse(is);
+ fail();
+ } catch (IOException | XmlPullParserException e) {
+ // pass
+ }
+ }
+
+ @Test
+ public void invalidXml_unclosed_family() throws Exception {
+ String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'"
+ + " <font index='0'>test.ttc</font>"
+ + " </family>"
+ + "</familyset>";
+
+ try (InputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) {
+ FontListParser.parse(is);
+ fail();
+ } catch (IOException | XmlPullParserException e) {
+ // pass
+ }
+ }
+
+ @Test
+ public void invalidXml_unclosed_font() throws Exception {
+ String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font index='0'"
+ + " </family>"
+ + "</familyset>";
+
+ try (InputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) {
+ FontListParser.parse(is);
+ fail();
+ } catch (IOException | XmlPullParserException e) {
+ // pass
+ }
+ }
+
+ @Test
+ public void invalidXml_unclosed_axis() throws Exception {
+ String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font index='0'>test.ttc"
+ + " <axis tag=\"wght\" styleValue=\"0\""
+ + " </font>"
+ + " </family>"
+ + "</familyset>";
+
+ try (InputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) {
+ FontListParser.parse(is);
+ fail();
+ } catch (IOException | XmlPullParserException e) {
+ // pass
+ }
+ }
+
private FontConfig.FontFamily readFamily(String xml)
throws IOException, XmlPullParserException {
- StandardCharsets.UTF_8.name();
ByteArrayInputStream buffer = new ByteArrayInputStream(
xml.getBytes(StandardCharsets.UTF_8));
XmlPullParser parser = Xml.newPullParser();
diff --git a/core/tests/coretests/src/android/os/VibratorInfoTest.java b/core/tests/coretests/src/android/os/VibratorInfoTest.java
index 8941190..c06405a 100644
--- a/core/tests/coretests/src/android/os/VibratorInfoTest.java
+++ b/core/tests/coretests/src/android/os/VibratorInfoTest.java
@@ -20,6 +20,7 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import android.hardware.vibrator.IVibrator;
import android.platform.test.annotations.Presubmit;
import org.junit.Test;
@@ -33,16 +34,16 @@
@Test
public void testHasAmplitudeControl() {
assertFalse(createInfo(/* capabilities= */ 0).hasAmplitudeControl());
- assertTrue(createInfo(VibratorInfo.CAPABILITY_COMPOSE_EFFECTS
- | VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL).hasAmplitudeControl());
+ assertTrue(createInfo(IVibrator.CAP_COMPOSE_EFFECTS
+ | IVibrator.CAP_AMPLITUDE_CONTROL).hasAmplitudeControl());
}
@Test
public void testHasCapabilities() {
- assertTrue(createInfo(VibratorInfo.CAPABILITY_COMPOSE_EFFECTS)
- .hasCapability(VibratorInfo.CAPABILITY_COMPOSE_EFFECTS));
- assertFalse(createInfo(VibratorInfo.CAPABILITY_COMPOSE_EFFECTS)
- .hasCapability(VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL));
+ assertTrue(createInfo(IVibrator.CAP_COMPOSE_EFFECTS)
+ .hasCapability(IVibrator.CAP_COMPOSE_EFFECTS));
+ assertFalse(createInfo(IVibrator.CAP_COMPOSE_EFFECTS)
+ .hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL));
}
@Test
@@ -59,7 +60,7 @@
@Test
public void testIsPrimitiveSupported() {
- VibratorInfo info = new VibratorInfo(/* id= */ 0, VibratorInfo.CAPABILITY_COMPOSE_EFFECTS,
+ VibratorInfo info = new VibratorInfo(/* id= */ 0, IVibrator.CAP_COMPOSE_EFFECTS,
null, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK});
assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));
@@ -73,30 +74,30 @@
@Test
public void testEquals() {
VibratorInfo empty = new VibratorInfo(1, 0, null, null);
- VibratorInfo complete = new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL,
+ VibratorInfo complete = new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL,
new int[]{VibrationEffect.EFFECT_CLICK},
new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK});
assertEquals(complete, complete);
- assertEquals(complete, new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL,
+ assertEquals(complete, new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL,
new int[]{VibrationEffect.EFFECT_CLICK},
new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}));
assertFalse(empty.equals(new VibratorInfo(1, 0, new int[]{}, new int[]{})));
- assertFalse(complete.equals(new VibratorInfo(1, VibratorInfo.CAPABILITY_COMPOSE_EFFECTS,
+ assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_COMPOSE_EFFECTS,
new int[]{VibrationEffect.EFFECT_CLICK},
new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK})));
- assertFalse(complete.equals(new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL,
+ assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL,
new int[]{}, new int[]{})));
- assertFalse(complete.equals(new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL,
+ assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL,
null, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK})));
- assertFalse(complete.equals(new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL,
+ assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL,
new int[]{VibrationEffect.EFFECT_CLICK}, null)));
}
@Test
public void testSerialization() {
- VibratorInfo original = new VibratorInfo(1, VibratorInfo.CAPABILITY_COMPOSE_EFFECTS,
+ VibratorInfo original = new VibratorInfo(1, IVibrator.CAP_COMPOSE_EFFECTS,
new int[]{VibrationEffect.EFFECT_CLICK}, null);
Parcel parcel = Parcel.obtain();
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 3d964fb..f2a33de 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -70,5 +70,6 @@
<permission name="android.permission.READ_WIFI_CREDENTIAL" />
<permission name="android.permission.USE_BACKGROUND_BLUR" />
<permission name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />
+ <permission name="android.permission.FORCE_STOP_PACKAGES" />
</privapp-permissions>
</permissions>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index ea42246..77a38a9 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -166,6 +166,7 @@
<assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" />
<assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="audioserver" />
+ <assign-permission name="android.permission.OBSERVE_SENSOR_PRIVACY" uid="audioserver" />
<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
@@ -176,6 +177,7 @@
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="cameraserver" />
<assign-permission name="android.permission.WATCH_APPOPS" uid="cameraserver" />
<assign-permission name="android.permission.MANAGE_APP_OPS_MODES" uid="cameraserver" />
+ <assign-permission name="android.permission.OBSERVE_SENSOR_PRIVACY" uid="cameraserver" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="graphics" />
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index cabfad4..b7bf8ab 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -43,6 +43,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-2093859262": {
+ "message": "setClientVisible: %s clientVisible=%b Callers=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_APP_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/WindowToken.java"
+ },
"-2072089308": {
"message": "Attempted to add window with token that is a sub-window: %s. Aborting.",
"level": "WARN",
@@ -811,6 +817,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/Task.java"
},
+ "-1159577965": {
+ "message": "Focus requested for input consumer=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_FOCUS_LIGHT",
+ "at": "com\/android\/server\/wm\/InputMonitor.java"
+ },
"-1156118957": {
"message": "Updated config=%s",
"level": "DEBUG",
@@ -823,12 +835,6 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/NonAppWindowAnimationAdapter.java"
},
- "-1144293044": {
- "message": "SURFACE SET FREEZE LAYER: %s",
- "level": "INFO",
- "group": "WM_SHOW_TRANSACTIONS",
- "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
- },
"-1142279614": {
"message": "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s",
"level": "VERBOSE",
@@ -1831,6 +1837,12 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "63329306": {
+ "message": "commitVisibility: %s: visible=%b mVisibleRequested=%b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/WallpaperWindowToken.java"
+ },
"73987756": {
"message": "ControlAdapter onAnimationCancelled mSource: %s mControlTarget: %s",
"level": "INFO",
@@ -2461,6 +2473,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "691515534": {
+ "message": " Commit wallpaper becoming invisible: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"693423992": {
"message": "setAnimationLocked: setting mFocusMayChange true",
"level": "INFO",
diff --git a/errorprone/Android.bp b/errorprone/Android.bp
index d1e94df..a927f53 100644
--- a/errorprone/Android.bp
+++ b/errorprone/Android.bp
@@ -36,11 +36,12 @@
java_test_host {
name: "error_prone_android_framework_test",
- test_suites: ["general-tests"],
srcs: ["tests/java/**/*.java"],
java_resource_dirs: ["tests/res"],
java_resources: [":error_prone_android_framework_testdata"],
static_libs: [
+ "truth-prebuilt",
+ "kxml2-2.3.0",
"error_prone_android_framework_lib",
"error_prone_test_helpers",
"hamcrest-library",
@@ -48,6 +49,9 @@
"platform-test-annotations",
"junit",
],
+ test_options: {
+ unit_test: true,
+ },
}
filegroup {
diff --git a/errorprone/TEST_MAPPING b/errorprone/TEST_MAPPING
deleted file mode 100644
index ee4552f..0000000
--- a/errorprone/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "presubmit": [
- {
- "name": "error_prone_android_framework_test"
- }
- ]
-}
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 63df0db..95c7715 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -136,7 +136,7 @@
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 @@
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 @@
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 @@
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 @@
case XmlPullParser.END_TAG:
depth--;
break;
+ case XmlPullParser.END_DOCUMENT:
+ return;
}
}
}
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index f6f770b..da5162b 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -719,11 +719,11 @@
/** @hide */
public boolean stretch(float left, float top, float right, float bottom,
float vecX, float vecY, float maxStretchAmount) {
- if (1.0 < vecX || vecX < -1.0) {
- throw new IllegalArgumentException("vecX must be in the range [-1, 1], was " + vecX);
+ if (Float.isInfinite(vecX) || Float.isNaN(vecX)) {
+ throw new IllegalArgumentException("vecX must be a finite, non-NaN value " + vecX);
}
- if (1.0 < vecY || vecY < -1.0) {
- throw new IllegalArgumentException("vecY must be in the range [-1, 1], was " + vecY);
+ if (Float.isInfinite(vecY) || Float.isNaN(vecY)) {
+ throw new IllegalArgumentException("vecY must be a finite, non-NaN value " + vecY);
}
if (top >= bottom || left >= right) {
throw new IllegalArgumentException(
@@ -734,7 +734,16 @@
throw new IllegalArgumentException(
"The max stretch amount must be >0, got " + maxStretchAmount);
}
- return nStretch(mNativeRenderNode, left, top, right, bottom, vecX, vecY, maxStretchAmount);
+ return nStretch(
+ mNativeRenderNode,
+ left,
+ top,
+ right,
+ bottom,
+ vecX,
+ vecY,
+ maxStretchAmount
+ );
}
/**
diff --git a/keystore/java/android/security/LegacyVpnProfileStore.java b/keystore/java/android/security/LegacyVpnProfileStore.java
new file mode 100644
index 0000000..41cfb27
--- /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/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index c79c12c..72735a7 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -279,8 +279,10 @@
* }
*/
public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAuthArgs {
- private static final X500Principal DEFAULT_CERT_SUBJECT =
+ private static final X500Principal DEFAULT_ATTESTATION_CERT_SUBJECT =
new X500Principal("CN=Android Keystore Key");
+ private static final X500Principal DEFAULT_SELF_SIGNED_CERT_SUBJECT =
+ new X500Principal("CN=Fake");
private static final BigInteger DEFAULT_CERT_SERIAL_NUMBER = new BigInteger("1");
private static final Date DEFAULT_CERT_NOT_BEFORE = new Date(0L); // Jan 1 1970
private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048
@@ -366,7 +368,11 @@
}
if (certificateSubject == null) {
- certificateSubject = DEFAULT_CERT_SUBJECT;
+ if (attestationChallenge == null) {
+ certificateSubject = DEFAULT_SELF_SIGNED_CERT_SUBJECT;
+ } else {
+ certificateSubject = DEFAULT_ATTESTATION_CERT_SUBJECT;
+ }
}
if (certificateNotBefore == null) {
certificateNotBefore = DEFAULT_CERT_NOT_BEFORE;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 1320780..d31e637b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -327,6 +327,10 @@
return mImpl;
}
+ public ShellExecutor getMainExecutor() {
+ return mMainExecutor;
+ }
+
/**
* Hides the current input method, wherever it may be focused, via InputMethodManagerInternal.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 2f31acd..9ef3fb5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -340,7 +340,7 @@
mSettingsIcon.setVisibility(GONE);
} else {
mTaskView = new TaskView(mContext, mController.getTaskOrganizer());
- mTaskView.setListener(mContext.getMainExecutor(), mTaskViewListener);
+ mTaskView.setListener(mController.getMainExecutor(), mTaskViewListener);
mExpandedViewContainer.addView(mTaskView);
bringChildToFront(mTaskView);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 6f5f2eb..aab2334 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -234,8 +234,8 @@
final UserHandle user = intent.getParcelableExtra(EXTRA_USER);
mStarter.startShortcut(packageName, id, stage, position, opts, user);
} else {
- mStarter.startIntent(intent.getParcelableExtra(EXTRA_PENDING_INTENT), stage, position,
- opts);
+ mStarter.startIntent(intent.getParcelableExtra(EXTRA_PENDING_INTENT),
+ mContext, null, stage, position, opts);
}
}
@@ -295,7 +295,8 @@
@Nullable Bundle options);
void startShortcut(String packageName, String shortcutId, @StageType int stage,
@StagePosition int position, @Nullable Bundle options, UserHandle user);
- void startIntent(PendingIntent intent, @StageType int stage, @StagePosition int position,
+ void startIntent(PendingIntent intent, Context context, Intent fillInIntent,
+ @StageType int stage, @StagePosition int position,
@Nullable Bundle options);
void enterSplitScreen(int taskId, boolean leftOrTop);
void exitSplitScreen();
@@ -336,10 +337,11 @@
}
@Override
- public void startIntent(PendingIntent intent, int stage, int position,
+ public void startIntent(PendingIntent intent, Context context,
+ @Nullable Intent fillInIntent, int stage, int position,
@Nullable Bundle options) {
try {
- intent.send(null, 0, null, null, null, null, options);
+ intent.send(mContext, 0, fillInIntent, null, null, null, options);
} catch (PendingIntent.CanceledException e) {
Slog.e(TAG, "Failed to launch activity", e);
}
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 ac5d14c..702385e 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 @@
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 @@
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 @@
// -1 will be populated if an activity specifies defaultWidth/defaultHeight in <layout>
// without minWidth/minHeight
if (windowLayout.minWidth > 0 && windowLayout.minHeight > 0) {
- return new Size(windowLayout.minWidth, windowLayout.minHeight);
+ // If either dimension is smaller than the allowed minimum, adjust them
+ // according to mOverridableMinSize
+ return new Size(Math.max(windowLayout.minWidth, mOverridableMinSize),
+ Math.max(windowLayout.minHeight, mOverridableMinSize));
}
return null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 7ca5693..25a84bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -19,18 +19,17 @@
import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.UserHandle;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
-import java.io.PrintWriter;
-
/**
* Interface to engage split-screen feature.
* TODO: Figure out which of these are actually needed outside of the Shell
@@ -87,7 +86,7 @@
/** Callback interface for listening to changes in a split-screen stage. */
interface SplitScreenListener {
void onStagePositionChanged(@StageType int stage, @StagePosition int position);
- void onTaskStageChanged(int taskId, @StageType int stage);
+ void onTaskStageChanged(int taskId, @StageType int stage, boolean visible);
}
/** @return {@code true} if split-screen is currently visible. */
@@ -118,6 +117,7 @@
@StageType int stage, @StagePosition int position, @Nullable Bundle options);
void startShortcut(String packageName, String shortcutId, @StageType int stage,
@StagePosition int position, @Nullable Bundle options, UserHandle user);
- void startIntent(PendingIntent intent,
- @StageType int stage, @StagePosition int position, @Nullable Bundle options);
+ void startIntent(PendingIntent intent, Context context,
+ @Nullable Intent fillInIntent, @StageType int stage,
+ @StagePosition int position, @Nullable Bundle options);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 11548ad..bb6f6f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -30,6 +30,7 @@
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.LauncherApps;
import android.graphics.Rect;
import android.os.Bundle;
@@ -171,12 +172,13 @@
}
}
- public void startIntent(PendingIntent intent, @SplitScreen.StageType int stage,
+ public void startIntent(PendingIntent intent, Context context,
+ Intent fillInIntent, @SplitScreen.StageType int stage,
@SplitScreen.StagePosition int position, @Nullable Bundle options) {
options = resolveStartStage(stage, position, options);
try {
- intent.send(null, 0, null, null, null, null, options);
+ intent.send(context, 0, fillInIntent, null, null, null, options);
} catch (PendingIntent.CanceledException e) {
Slog.e(TAG, "Failed to launch activity", e);
}
@@ -348,10 +350,11 @@
}
@Override
- public void startIntent(PendingIntent intent, int stage, int position,
- @Nullable Bundle options) {
+ public void startIntent(PendingIntent intent, Context context, Intent fillInIntent,
+ int stage, int position, @Nullable Bundle options) {
mMainExecutor.execute(() -> {
- SplitScreenController.this.startIntent(intent, stage, position, options);
+ SplitScreenController.this.startIntent(intent, context, fillInIntent, stage,
+ position, options);
});
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index bbfbc40..b180bb5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -225,7 +225,7 @@
}
private void onStageChildTaskStatusChanged(
- StageListenerImpl stageListener, int taskId, boolean present) {
+ StageListenerImpl stageListener, int taskId, boolean present, boolean visible) {
int stage;
if (present) {
@@ -236,7 +236,7 @@
}
for (int i = mListeners.size() - 1; i >= 0; --i) {
- mListeners.get(i).onTaskStageChanged(taskId, stage);
+ mListeners.get(i).onTaskStageChanged(taskId, stage, visible);
}
}
@@ -495,8 +495,8 @@
}
@Override
- public void onChildTaskStatusChanged(int taskId, boolean present) {
- StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present);
+ public void onChildTaskStatusChanged(int taskId, boolean present, boolean visible) {
+ StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present, visible);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 10c742b..b8cdc4ab4d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -58,7 +58,7 @@
public interface StageListenerCallbacks {
void onRootTaskAppeared();
void onStatusChanged(boolean visible, boolean hasChildren);
- void onChildTaskStatusChanged(int taskId, boolean present);
+ void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
void onRootTaskVanished();
}
private final StageListenerCallbacks mCallbacks;
@@ -88,7 +88,7 @@
mChildrenLeashes.put(taskId, leash);
mChildrenTaskInfo.put(taskId, taskInfo);
updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
- mCallbacks.onChildTaskStatusChanged(taskId, true /* present */);
+ mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, taskInfo.isVisible);
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
@@ -105,6 +105,8 @@
mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
updateChildTaskSurface(
taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */);
+ mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
+ taskInfo.isVisible);
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
@@ -123,7 +125,7 @@
mChildrenTaskInfo.remove(taskId);
mChildrenLeashes.remove(taskId);
sendStatusChanged();
- mCallbacks.onChildTaskStatusChanged(taskId, false /* present */);
+ mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible);
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
@@ -152,7 +154,9 @@
void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener,
@SplitScreen.StageType int stage) {
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
- listener.onTaskStageChanged(mChildrenTaskInfo.keyAt(i), stage);
+ int taskId = mChildrenTaskInfo.keyAt(i);
+ listener.onTaskStageChanged(taskId, stage,
+ mChildrenTaskInfo.get(taskId).isVisible);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 19ecc49..c1c4c6d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -205,7 +205,7 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
}
@@ -217,12 +217,12 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
reset(mSplitScreenStarter);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(), any(),
eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any());
}
@@ -234,12 +234,12 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
reset(mSplitScreenStarter);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(), any(),
eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any());
}
@@ -251,7 +251,7 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
}
@@ -263,7 +263,7 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
}
@@ -276,13 +276,13 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
reset(mSplitScreenStarter);
// TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(), any(),
eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any());
}
@@ -295,13 +295,13 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
reset(mSplitScreenStarter);
// TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(), any(),
eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any());
}
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 d10c036..79ec624 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.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 @@
@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 @@
@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 @@
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1,
createPipParams(null)), null /* leash */);
- final Size minSize = new Size(100, 80);
+ final Size minSize = new Size(400, 320);
mSpiedPipTaskOrganizer.onTaskInfoChanged(createTaskInfo(mComponent2,
createPipParams(null), minSize));
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 77ceda9..d663c52 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -421,7 +421,6 @@
"libstatspull",
"libstatssocket",
"libpdfium",
- "libbinder_ndk",
],
static_libs: [
"libgif",
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index 912d04c5..e9b2f4a 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -80,6 +80,10 @@
explicit UiFrameInfoBuilder(int64_t* buffer) : mBuffer(buffer) {
memset(mBuffer, 0, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t));
set(FrameInfoIndex::FrameTimelineVsyncId) = INVALID_VSYNC_ID;
+ // The struct is zeroed by memset above. That also sets FrameInfoIndex::InputEventId to
+ // equal android::os::IInputConstants::INVALID_INPUT_EVENT_ID == 0.
+ // Therefore, we can skip setting the value for InputEventId here. If the value for
+ // INVALID_INPUT_EVENT_ID changes, this code would have to be updated, as well.
set(FrameInfoIndex::FrameDeadline) = std::numeric_limits<int64_t>::max();
}
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index 609706e..5540e2d 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -552,8 +552,8 @@
bool promotedToLayer() const {
return mLayerProperties.mType == LayerType::None && fitsOnLayer() &&
- (mComputedFields.mNeedLayerForFunctors ||
- mLayerProperties.mImageFilter != nullptr ||
+ (mComputedFields.mNeedLayerForFunctors || mLayerProperties.mImageFilter != nullptr ||
+ !mLayerProperties.getStretchEffect().isEmpty() ||
(!MathUtils::isZero(mPrimitiveFields.mAlpha) && mPrimitiveFields.mAlpha < 1 &&
mPrimitiveFields.mHasOverlappingRendering));
}
diff --git a/libs/hwui/effects/StretchEffect.cpp b/libs/hwui/effects/StretchEffect.cpp
index 51cbc75..d4fd105 100644
--- a/libs/hwui/effects/StretchEffect.cpp
+++ b/libs/hwui/effects/StretchEffect.cpp
@@ -15,13 +15,195 @@
*/
#include "StretchEffect.h"
+#include <SkImageFilter.h>
+#include <SkRefCnt.h>
+#include <SkRuntimeEffect.h>
+#include <SkString.h>
+#include <SkSurface.h>
+#include <include/effects/SkImageFilters.h>
+
+#include <memory>
namespace android::uirenderer {
-sk_sp<SkImageFilter> StretchEffect::getImageFilter() const {
- // TODO: Implement & Cache
- // Probably need to use mutable to achieve caching
- return nullptr;
+static const SkString stretchShader = SkString(R"(
+ uniform shader uContentTexture;
+
+ // multiplier to apply to scale effect
+ uniform float uMaxStretchIntensity;
+
+ // Maximum percentage to stretch beyond bounds of target
+ uniform float uStretchAffectedDist;
+
+ // Distance stretched as a function of the normalized overscroll times
+ // scale intensity
+ uniform float uDistanceStretchedX;
+ uniform float uDistanceStretchedY;
+ uniform float uDistDiffX;
+
+ // Difference between the peak stretch amount and overscroll amount normalized
+ uniform float uDistDiffY;
+
+ // Horizontal offset represented as a ratio of pixels divided by the target width
+ uniform float uScrollX;
+ // Vertical offset represented as a ratio of pixels divided by the target height
+ uniform float uScrollY;
+
+ // Normalized overscroll amount in the horizontal direction
+ uniform float uOverscrollX;
+
+ // Normalized overscroll amount in the vertical direction
+ uniform float uOverscrollY;
+ uniform float viewportWidth; // target height in pixels
+ uniform float viewportHeight; // target width in pixels
+
+ void computeOverscrollStart(
+ out float outPos,
+ float inPos,
+ float overscroll,
+ float uStretchAffectedDist,
+ float distanceStretched
+ ) {
+ float offsetPos = uStretchAffectedDist - inPos;
+ float posBasedVariation = smoothstep(0., uStretchAffectedDist, offsetPos);
+ float stretchIntensity = overscroll * posBasedVariation;
+ outPos = distanceStretched - (offsetPos / (1. + stretchIntensity));
+ }
+
+ void computeOverscrollEnd(
+ out float outPos,
+ float inPos,
+ float overscroll,
+ float reverseStretchDist,
+ float uStretchAffectedDist,
+ float distanceStretched
+ ) {
+ float offsetPos = inPos - reverseStretchDist;
+ float posBasedVariation = (smoothstep(0., uStretchAffectedDist, offsetPos));
+ float stretchIntensity = (-overscroll) * posBasedVariation;
+ outPos = 1 - (distanceStretched - (offsetPos / (1. + stretchIntensity)));
+ }
+
+ void computeOverscroll(
+ out float outPos,
+ float inPos,
+ float overscroll,
+ float uStretchAffectedDist,
+ float distanceStretched,
+ float distanceDiff
+ ) {
+ if (overscroll > 0) {
+ if (inPos <= uStretchAffectedDist) {
+ computeOverscrollStart(
+ outPos,
+ inPos,
+ overscroll,
+ uStretchAffectedDist,
+ distanceStretched
+ );
+ } else if (inPos >= distanceStretched) {
+ outPos = distanceDiff + inPos;
+ }
+ }
+ if (overscroll < 0) {
+ float stretchAffectedDist = 1. - uStretchAffectedDist;
+ if (inPos >= stretchAffectedDist) {
+ computeOverscrollEnd(
+ outPos,
+ inPos,
+ overscroll,
+ stretchAffectedDist,
+ uStretchAffectedDist,
+ distanceStretched
+ );
+ } else if (inPos < stretchAffectedDist) {
+ outPos = -distanceDiff + inPos;
+ }
+ }
+ }
+
+ vec4 main(vec2 coord) {
+ // Normalize SKSL pixel coordinate into a unit vector
+ float inU = coord.x / viewportWidth;
+ float inV = coord.y / viewportHeight;
+ float outU;
+ float outV;
+ float stretchIntensity;
+ // Add the normalized scroll position within scrolling list
+ inU += uScrollX;
+ inV += uScrollY;
+ outU = inU;
+ outV = inV;
+ computeOverscroll(
+ outU,
+ inU,
+ uOverscrollX,
+ uStretchAffectedDist,
+ uDistanceStretchedX,
+ uDistDiffX
+ );
+ computeOverscroll(
+ outV,
+ inV,
+ uOverscrollY,
+ uStretchAffectedDist,
+ uDistanceStretchedY,
+ uDistDiffY
+ );
+ coord.x = outU * viewportWidth;
+ coord.y = outV * viewportHeight;
+ return sample(uContentTexture, coord);
+ })");
+
+static const float ZERO = 0.f;
+
+sk_sp<SkImageFilter> StretchEffect::getImageFilter(const sk_sp<SkImage>& snapshotImage) const {
+ if (isEmpty()) {
+ return nullptr;
+ }
+
+ if (mStretchFilter != nullptr) {
+ return mStretchFilter;
+ }
+
+ float distanceNotStretchedX = maxStretchAmount / stretchArea.width();
+ float distanceNotStretchedY = maxStretchAmount / stretchArea.height();
+ float normOverScrollDistX = mStretchDirection.x();
+ float normOverScrollDistY = mStretchDirection.y();
+ float distanceStretchedX = maxStretchAmount / (1 + abs(normOverScrollDistX));
+ float distanceStretchedY = maxStretchAmount / (1 + abs(normOverScrollDistY));
+ float diffX = distanceStretchedX - distanceNotStretchedX;
+ float diffY = distanceStretchedY - distanceNotStretchedY;
+ float viewportWidth = stretchArea.width();
+ float viewportHeight = stretchArea.height();
+
+ if (mBuilder == nullptr) {
+ mBuilder = std::make_unique<SkRuntimeShaderBuilder>(getStretchEffect());
+ }
+
+ mBuilder->child("uContentTexture") = snapshotImage->makeShader(
+ SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions(SkFilterMode::kLinear));
+ mBuilder->uniform("uStretchAffectedDist").set(&maxStretchAmount, 1);
+ mBuilder->uniform("uDistanceStretchedX").set(&distanceStretchedX, 1);
+ mBuilder->uniform("uDistanceStretchedY").set(&distanceStretchedY, 1);
+ mBuilder->uniform("uDistDiffX").set(&diffX, 1);
+ mBuilder->uniform("uDistDiffY").set(&diffY, 1);
+ mBuilder->uniform("uOverscrollX").set(&normOverScrollDistX, 1);
+ mBuilder->uniform("uOverscrollY").set(&normOverScrollDistY, 1);
+ mBuilder->uniform("uScrollX").set(&ZERO, 1);
+ mBuilder->uniform("uScrollY").set(&ZERO, 1);
+ mBuilder->uniform("viewportWidth").set(&viewportWidth, 1);
+ mBuilder->uniform("viewportHeight").set(&viewportHeight, 1);
+
+ mStretchFilter = SkImageFilters::Shader(mBuilder->makeShader(nullptr, false),
+ SkRect{0, 0, viewportWidth, viewportHeight});
+
+ return mStretchFilter;
+}
+
+sk_sp<SkRuntimeEffect> StretchEffect::getStretchEffect() {
+ const static SkRuntimeEffect::Result instance = SkRuntimeEffect::Make(stretchShader);
+ return instance.effect;
}
} // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/effects/StretchEffect.h b/libs/hwui/effects/StretchEffect.h
index 7dfd639..d2da06b 100644
--- a/libs/hwui/effects/StretchEffect.h
+++ b/libs/hwui/effects/StretchEffect.h
@@ -18,9 +18,11 @@
#include "utils/MathUtils.h"
+#include <SkImage.h>
+#include <SkImageFilter.h>
#include <SkPoint.h>
#include <SkRect.h>
-#include <SkImageFilter.h>
+#include <SkRuntimeEffect.h>
namespace android::uirenderer {
@@ -31,15 +33,27 @@
SmoothStep,
};
+ StretchEffect(const SkRect& area, const SkVector& direction, float maxStretchAmount)
+ : stretchArea(area), maxStretchAmount(maxStretchAmount), mStretchDirection(direction) {}
+
+ StretchEffect() {}
+
bool isEmpty() const {
- return MathUtils::isZero(stretchDirection.x())
- && MathUtils::isZero(stretchDirection.y());
+ return MathUtils::isZero(mStretchDirection.x()) && MathUtils::isZero(mStretchDirection.y());
}
void setEmpty() {
*this = StretchEffect{};
}
+ StretchEffect& operator=(const StretchEffect& other) {
+ this->stretchArea = other.stretchArea;
+ this->mStretchDirection = other.mStretchDirection;
+ this->mStretchFilter = nullptr;
+ this->maxStretchAmount = other.maxStretchAmount;
+ return *this;
+ }
+
void mergeWith(const StretchEffect& other) {
if (other.isEmpty()) {
return;
@@ -48,7 +62,7 @@
*this = other;
return;
}
- stretchDirection += other.stretchDirection;
+ setStretchDirection(mStretchDirection + other.mStretchDirection);
if (isEmpty()) {
return setEmpty();
}
@@ -56,11 +70,23 @@
maxStretchAmount = std::max(maxStretchAmount, other.maxStretchAmount);
}
- sk_sp<SkImageFilter> getImageFilter() const;
+ sk_sp<SkImageFilter> getImageFilter(const sk_sp<SkImage>& snapshotImage) const;
SkRect stretchArea {0, 0, 0, 0};
- SkVector stretchDirection {0, 0};
float maxStretchAmount = 0;
+
+ void setStretchDirection(const SkVector& direction) {
+ mStretchFilter = nullptr;
+ mStretchDirection = direction;
+ }
+
+ const SkVector getStretchDirection() const { return mStretchDirection; }
+
+private:
+ static sk_sp<SkRuntimeEffect> getStretchEffect();
+ mutable SkVector mStretchDirection{0, 0};
+ mutable std::unique_ptr<SkRuntimeShaderBuilder> mBuilder;
+ mutable sk_sp<SkImageFilter> mStretchFilter;
};
} // namespace android::uirenderer
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 5f60437..fc7d0d1 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -180,14 +180,13 @@
}
static jboolean android_view_RenderNode_stretch(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr,
- jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat vX, jfloat vY, jfloat max) {
+ jfloat left, jfloat top, jfloat right,
+ jfloat bottom, jfloat vX, jfloat vY, jfloat max) {
+ StretchEffect effect =
+ StretchEffect(SkRect::MakeLTRB(left, top, right, bottom), {.fX = vX, .fY = vY}, max);
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
renderNode->mutateStagingProperties().mutateLayerProperties().mutableStretchEffect().mergeWith(
- StretchEffect{
- .stretchArea = SkRect::MakeLTRB(left, top, right, bottom),
- .stretchDirection = {.fX = vX, .fY = vY},
- .maxStretchAmount = max
- });
+ effect);
renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
return true;
}
@@ -659,10 +658,11 @@
return;
}
#ifdef __ANDROID__ // Layoutlib does not support CanvasContext
+ SkVector stretchDirection = effect->getStretchDirection();
env->CallVoidMethod(localref, gPositionListener_ApplyStretchMethod,
info.canvasContext.getFrameNumber(), area.left, area.top,
- area.right, area.bottom, effect->stretchDirection.fX,
- effect->stretchDirection.fY, effect->maxStretchAmount);
+ area.right, area.bottom, stretchDirection.fX, stretchDirection.fY,
+ effect->maxStretchAmount);
#endif
env->DeleteLocalRef(localref);
}
@@ -702,106 +702,110 @@
const char* const kClassPathName = "android/graphics/RenderNode";
static const JNINativeMethod gMethods[] = {
-// ----------------------------------------------------------------------------
-// Regular JNI
-// ----------------------------------------------------------------------------
- { "nCreate", "(Ljava/lang/String;)J", (void*) android_view_RenderNode_create },
- { "nGetNativeFinalizer", "()J", (void*) android_view_RenderNode_getNativeFinalizer },
- { "nOutput", "(J)V", (void*) android_view_RenderNode_output },
- { "nGetUsageSize", "(J)I", (void*) android_view_RenderNode_getUsageSize },
- { "nGetAllocatedSize", "(J)I", (void*) android_view_RenderNode_getAllocatedSize },
- { "nAddAnimator", "(JJ)V", (void*) android_view_RenderNode_addAnimator },
- { "nEndAllAnimators", "(J)V", (void*) android_view_RenderNode_endAllAnimators },
- { "nRequestPositionUpdates", "(JLandroid/graphics/RenderNode$PositionUpdateListener;)V", (void*) android_view_RenderNode_requestPositionUpdates },
+ // ----------------------------------------------------------------------------
+ // Regular JNI
+ // ----------------------------------------------------------------------------
+ {"nCreate", "(Ljava/lang/String;)J", (void*)android_view_RenderNode_create},
+ {"nGetNativeFinalizer", "()J", (void*)android_view_RenderNode_getNativeFinalizer},
+ {"nOutput", "(J)V", (void*)android_view_RenderNode_output},
+ {"nGetUsageSize", "(J)I", (void*)android_view_RenderNode_getUsageSize},
+ {"nGetAllocatedSize", "(J)I", (void*)android_view_RenderNode_getAllocatedSize},
+ {"nAddAnimator", "(JJ)V", (void*)android_view_RenderNode_addAnimator},
+ {"nEndAllAnimators", "(J)V", (void*)android_view_RenderNode_endAllAnimators},
+ {"nRequestPositionUpdates", "(JLandroid/graphics/RenderNode$PositionUpdateListener;)V",
+ (void*)android_view_RenderNode_requestPositionUpdates},
-// ----------------------------------------------------------------------------
-// Critical JNI via @CriticalNative annotation in RenderNode.java
-// ----------------------------------------------------------------------------
- { "nDiscardDisplayList", "(J)V", (void*) android_view_RenderNode_discardDisplayList },
- { "nIsValid", "(J)Z", (void*) android_view_RenderNode_isValid },
- { "nSetLayerType", "(JI)Z", (void*) android_view_RenderNode_setLayerType },
- { "nGetLayerType", "(J)I", (void*) android_view_RenderNode_getLayerType },
- { "nSetLayerPaint", "(JJ)Z", (void*) android_view_RenderNode_setLayerPaint },
- { "nSetStaticMatrix", "(JJ)Z", (void*) android_view_RenderNode_setStaticMatrix },
- { "nSetAnimationMatrix", "(JJ)Z", (void*) android_view_RenderNode_setAnimationMatrix },
- { "nGetAnimationMatrix", "(JJ)Z", (void*) android_view_RenderNode_getAnimationMatrix },
- { "nSetClipToBounds", "(JZ)Z", (void*) android_view_RenderNode_setClipToBounds },
- { "nGetClipToBounds", "(J)Z", (void*) android_view_RenderNode_getClipToBounds },
- { "nSetClipBounds", "(JIIII)Z", (void*) android_view_RenderNode_setClipBounds },
- { "nSetClipBoundsEmpty", "(J)Z", (void*) android_view_RenderNode_setClipBoundsEmpty },
- { "nSetProjectBackwards", "(JZ)Z", (void*) android_view_RenderNode_setProjectBackwards },
- { "nSetProjectionReceiver","(JZ)Z", (void*) android_view_RenderNode_setProjectionReceiver },
+ // ----------------------------------------------------------------------------
+ // Critical JNI via @CriticalNative annotation in RenderNode.java
+ // ----------------------------------------------------------------------------
+ {"nDiscardDisplayList", "(J)V", (void*)android_view_RenderNode_discardDisplayList},
+ {"nIsValid", "(J)Z", (void*)android_view_RenderNode_isValid},
+ {"nSetLayerType", "(JI)Z", (void*)android_view_RenderNode_setLayerType},
+ {"nGetLayerType", "(J)I", (void*)android_view_RenderNode_getLayerType},
+ {"nSetLayerPaint", "(JJ)Z", (void*)android_view_RenderNode_setLayerPaint},
+ {"nSetStaticMatrix", "(JJ)Z", (void*)android_view_RenderNode_setStaticMatrix},
+ {"nSetAnimationMatrix", "(JJ)Z", (void*)android_view_RenderNode_setAnimationMatrix},
+ {"nGetAnimationMatrix", "(JJ)Z", (void*)android_view_RenderNode_getAnimationMatrix},
+ {"nSetClipToBounds", "(JZ)Z", (void*)android_view_RenderNode_setClipToBounds},
+ {"nGetClipToBounds", "(J)Z", (void*)android_view_RenderNode_getClipToBounds},
+ {"nSetClipBounds", "(JIIII)Z", (void*)android_view_RenderNode_setClipBounds},
+ {"nSetClipBoundsEmpty", "(J)Z", (void*)android_view_RenderNode_setClipBoundsEmpty},
+ {"nSetProjectBackwards", "(JZ)Z", (void*)android_view_RenderNode_setProjectBackwards},
+ {"nSetProjectionReceiver", "(JZ)Z", (void*)android_view_RenderNode_setProjectionReceiver},
- { "nSetOutlineRoundRect", "(JIIIIFF)Z", (void*) android_view_RenderNode_setOutlineRoundRect },
- { "nSetOutlinePath", "(JJF)Z", (void*) android_view_RenderNode_setOutlinePath },
- { "nSetOutlineEmpty", "(J)Z", (void*) android_view_RenderNode_setOutlineEmpty },
- { "nSetOutlineNone", "(J)Z", (void*) android_view_RenderNode_setOutlineNone },
- { "nClearStretch", "(J)Z", (void*) android_view_RenderNode_clearStretch },
- { "nStretch", "(JFFFFFFF)Z", (void*) android_view_RenderNode_stretch },
- { "nHasShadow", "(J)Z", (void*) android_view_RenderNode_hasShadow },
- { "nSetSpotShadowColor", "(JI)Z", (void*) android_view_RenderNode_setSpotShadowColor },
- { "nGetSpotShadowColor", "(J)I", (void*) android_view_RenderNode_getSpotShadowColor },
- { "nSetAmbientShadowColor","(JI)Z", (void*) android_view_RenderNode_setAmbientShadowColor },
- { "nGetAmbientShadowColor","(J)I", (void*) android_view_RenderNode_getAmbientShadowColor },
- { "nSetClipToOutline", "(JZ)Z", (void*) android_view_RenderNode_setClipToOutline },
- { "nSetRevealClip", "(JZFFF)Z", (void*) android_view_RenderNode_setRevealClip },
+ {"nSetOutlineRoundRect", "(JIIIIFF)Z", (void*)android_view_RenderNode_setOutlineRoundRect},
+ {"nSetOutlinePath", "(JJF)Z", (void*)android_view_RenderNode_setOutlinePath},
+ {"nSetOutlineEmpty", "(J)Z", (void*)android_view_RenderNode_setOutlineEmpty},
+ {"nSetOutlineNone", "(J)Z", (void*)android_view_RenderNode_setOutlineNone},
+ {"nClearStretch", "(J)Z", (void*)android_view_RenderNode_clearStretch},
+ {"nStretch", "(JFFFFFFF)Z", (void*)android_view_RenderNode_stretch},
+ {"nHasShadow", "(J)Z", (void*)android_view_RenderNode_hasShadow},
+ {"nSetSpotShadowColor", "(JI)Z", (void*)android_view_RenderNode_setSpotShadowColor},
+ {"nGetSpotShadowColor", "(J)I", (void*)android_view_RenderNode_getSpotShadowColor},
+ {"nSetAmbientShadowColor", "(JI)Z", (void*)android_view_RenderNode_setAmbientShadowColor},
+ {"nGetAmbientShadowColor", "(J)I", (void*)android_view_RenderNode_getAmbientShadowColor},
+ {"nSetClipToOutline", "(JZ)Z", (void*)android_view_RenderNode_setClipToOutline},
+ {"nSetRevealClip", "(JZFFF)Z", (void*)android_view_RenderNode_setRevealClip},
- { "nSetAlpha", "(JF)Z", (void*) android_view_RenderNode_setAlpha },
- { "nSetRenderEffect", "(JJ)Z", (void*) android_view_RenderNode_setRenderEffect },
- { "nSetHasOverlappingRendering", "(JZ)Z",
- (void*) android_view_RenderNode_setHasOverlappingRendering },
- { "nSetUsageHint", "(JI)V", (void*) android_view_RenderNode_setUsageHint },
- { "nSetElevation", "(JF)Z", (void*) android_view_RenderNode_setElevation },
- { "nSetTranslationX", "(JF)Z", (void*) android_view_RenderNode_setTranslationX },
- { "nSetTranslationY", "(JF)Z", (void*) android_view_RenderNode_setTranslationY },
- { "nSetTranslationZ", "(JF)Z", (void*) android_view_RenderNode_setTranslationZ },
- { "nSetRotation", "(JF)Z", (void*) android_view_RenderNode_setRotation },
- { "nSetRotationX", "(JF)Z", (void*) android_view_RenderNode_setRotationX },
- { "nSetRotationY", "(JF)Z", (void*) android_view_RenderNode_setRotationY },
- { "nSetScaleX", "(JF)Z", (void*) android_view_RenderNode_setScaleX },
- { "nSetScaleY", "(JF)Z", (void*) android_view_RenderNode_setScaleY },
- { "nSetPivotX", "(JF)Z", (void*) android_view_RenderNode_setPivotX },
- { "nSetPivotY", "(JF)Z", (void*) android_view_RenderNode_setPivotY },
- { "nResetPivot", "(J)Z", (void*) android_view_RenderNode_resetPivot },
- { "nSetCameraDistance", "(JF)Z", (void*) android_view_RenderNode_setCameraDistance },
- { "nSetLeft", "(JI)Z", (void*) android_view_RenderNode_setLeft },
- { "nSetTop", "(JI)Z", (void*) android_view_RenderNode_setTop },
- { "nSetRight", "(JI)Z", (void*) android_view_RenderNode_setRight },
- { "nSetBottom", "(JI)Z", (void*) android_view_RenderNode_setBottom },
- { "nGetLeft", "(J)I", (void*) android_view_RenderNode_getLeft },
- { "nGetTop", "(J)I", (void*) android_view_RenderNode_getTop },
- { "nGetRight", "(J)I", (void*) android_view_RenderNode_getRight },
- { "nGetBottom", "(J)I", (void*) android_view_RenderNode_getBottom },
- { "nSetLeftTopRightBottom","(JIIII)Z", (void*) android_view_RenderNode_setLeftTopRightBottom },
- { "nOffsetLeftAndRight", "(JI)Z", (void*) android_view_RenderNode_offsetLeftAndRight },
- { "nOffsetTopAndBottom", "(JI)Z", (void*) android_view_RenderNode_offsetTopAndBottom },
+ {"nSetAlpha", "(JF)Z", (void*)android_view_RenderNode_setAlpha},
+ {"nSetRenderEffect", "(JJ)Z", (void*)android_view_RenderNode_setRenderEffect},
+ {"nSetHasOverlappingRendering", "(JZ)Z",
+ (void*)android_view_RenderNode_setHasOverlappingRendering},
+ {"nSetUsageHint", "(JI)V", (void*)android_view_RenderNode_setUsageHint},
+ {"nSetElevation", "(JF)Z", (void*)android_view_RenderNode_setElevation},
+ {"nSetTranslationX", "(JF)Z", (void*)android_view_RenderNode_setTranslationX},
+ {"nSetTranslationY", "(JF)Z", (void*)android_view_RenderNode_setTranslationY},
+ {"nSetTranslationZ", "(JF)Z", (void*)android_view_RenderNode_setTranslationZ},
+ {"nSetRotation", "(JF)Z", (void*)android_view_RenderNode_setRotation},
+ {"nSetRotationX", "(JF)Z", (void*)android_view_RenderNode_setRotationX},
+ {"nSetRotationY", "(JF)Z", (void*)android_view_RenderNode_setRotationY},
+ {"nSetScaleX", "(JF)Z", (void*)android_view_RenderNode_setScaleX},
+ {"nSetScaleY", "(JF)Z", (void*)android_view_RenderNode_setScaleY},
+ {"nSetPivotX", "(JF)Z", (void*)android_view_RenderNode_setPivotX},
+ {"nSetPivotY", "(JF)Z", (void*)android_view_RenderNode_setPivotY},
+ {"nResetPivot", "(J)Z", (void*)android_view_RenderNode_resetPivot},
+ {"nSetCameraDistance", "(JF)Z", (void*)android_view_RenderNode_setCameraDistance},
+ {"nSetLeft", "(JI)Z", (void*)android_view_RenderNode_setLeft},
+ {"nSetTop", "(JI)Z", (void*)android_view_RenderNode_setTop},
+ {"nSetRight", "(JI)Z", (void*)android_view_RenderNode_setRight},
+ {"nSetBottom", "(JI)Z", (void*)android_view_RenderNode_setBottom},
+ {"nGetLeft", "(J)I", (void*)android_view_RenderNode_getLeft},
+ {"nGetTop", "(J)I", (void*)android_view_RenderNode_getTop},
+ {"nGetRight", "(J)I", (void*)android_view_RenderNode_getRight},
+ {"nGetBottom", "(J)I", (void*)android_view_RenderNode_getBottom},
+ {"nSetLeftTopRightBottom", "(JIIII)Z",
+ (void*)android_view_RenderNode_setLeftTopRightBottom},
+ {"nOffsetLeftAndRight", "(JI)Z", (void*)android_view_RenderNode_offsetLeftAndRight},
+ {"nOffsetTopAndBottom", "(JI)Z", (void*)android_view_RenderNode_offsetTopAndBottom},
- { "nHasOverlappingRendering", "(J)Z", (void*) android_view_RenderNode_hasOverlappingRendering },
- { "nGetClipToOutline", "(J)Z", (void*) android_view_RenderNode_getClipToOutline },
- { "nGetAlpha", "(J)F", (void*) android_view_RenderNode_getAlpha },
- { "nGetCameraDistance", "(J)F", (void*) android_view_RenderNode_getCameraDistance },
- { "nGetScaleX", "(J)F", (void*) android_view_RenderNode_getScaleX },
- { "nGetScaleY", "(J)F", (void*) android_view_RenderNode_getScaleY },
- { "nGetElevation", "(J)F", (void*) android_view_RenderNode_getElevation },
- { "nGetTranslationX", "(J)F", (void*) android_view_RenderNode_getTranslationX },
- { "nGetTranslationY", "(J)F", (void*) android_view_RenderNode_getTranslationY },
- { "nGetTranslationZ", "(J)F", (void*) android_view_RenderNode_getTranslationZ },
- { "nGetRotation", "(J)F", (void*) android_view_RenderNode_getRotation },
- { "nGetRotationX", "(J)F", (void*) android_view_RenderNode_getRotationX },
- { "nGetRotationY", "(J)F", (void*) android_view_RenderNode_getRotationY },
- { "nIsPivotExplicitlySet", "(J)Z", (void*) android_view_RenderNode_isPivotExplicitlySet },
- { "nHasIdentityMatrix", "(J)Z", (void*) android_view_RenderNode_hasIdentityMatrix },
+ {"nHasOverlappingRendering", "(J)Z",
+ (void*)android_view_RenderNode_hasOverlappingRendering},
+ {"nGetClipToOutline", "(J)Z", (void*)android_view_RenderNode_getClipToOutline},
+ {"nGetAlpha", "(J)F", (void*)android_view_RenderNode_getAlpha},
+ {"nGetCameraDistance", "(J)F", (void*)android_view_RenderNode_getCameraDistance},
+ {"nGetScaleX", "(J)F", (void*)android_view_RenderNode_getScaleX},
+ {"nGetScaleY", "(J)F", (void*)android_view_RenderNode_getScaleY},
+ {"nGetElevation", "(J)F", (void*)android_view_RenderNode_getElevation},
+ {"nGetTranslationX", "(J)F", (void*)android_view_RenderNode_getTranslationX},
+ {"nGetTranslationY", "(J)F", (void*)android_view_RenderNode_getTranslationY},
+ {"nGetTranslationZ", "(J)F", (void*)android_view_RenderNode_getTranslationZ},
+ {"nGetRotation", "(J)F", (void*)android_view_RenderNode_getRotation},
+ {"nGetRotationX", "(J)F", (void*)android_view_RenderNode_getRotationX},
+ {"nGetRotationY", "(J)F", (void*)android_view_RenderNode_getRotationY},
+ {"nIsPivotExplicitlySet", "(J)Z", (void*)android_view_RenderNode_isPivotExplicitlySet},
+ {"nHasIdentityMatrix", "(J)Z", (void*)android_view_RenderNode_hasIdentityMatrix},
- { "nGetTransformMatrix", "(JJ)V", (void*) android_view_RenderNode_getTransformMatrix },
- { "nGetInverseTransformMatrix","(JJ)V", (void*) android_view_RenderNode_getInverseTransformMatrix },
+ {"nGetTransformMatrix", "(JJ)V", (void*)android_view_RenderNode_getTransformMatrix},
+ {"nGetInverseTransformMatrix", "(JJ)V",
+ (void*)android_view_RenderNode_getInverseTransformMatrix},
- { "nGetPivotX", "(J)F", (void*) android_view_RenderNode_getPivotX },
- { "nGetPivotY", "(J)F", (void*) android_view_RenderNode_getPivotY },
- { "nGetWidth", "(J)I", (void*) android_view_RenderNode_getWidth },
- { "nGetHeight", "(J)I", (void*) android_view_RenderNode_getHeight },
- { "nSetAllowForceDark", "(JZ)Z", (void*) android_view_RenderNode_setAllowForceDark },
- { "nGetAllowForceDark", "(J)Z", (void*) android_view_RenderNode_getAllowForceDark },
- { "nGetUniqueId", "(J)J", (void*) android_view_RenderNode_getUniqueId },
+ {"nGetPivotX", "(J)F", (void*)android_view_RenderNode_getPivotX},
+ {"nGetPivotY", "(J)F", (void*)android_view_RenderNode_getPivotY},
+ {"nGetWidth", "(J)I", (void*)android_view_RenderNode_getWidth},
+ {"nGetHeight", "(J)I", (void*)android_view_RenderNode_getHeight},
+ {"nSetAllowForceDark", "(JZ)Z", (void*)android_view_RenderNode_setAllowForceDark},
+ {"nGetAllowForceDark", "(J)Z", (void*)android_view_RenderNode_getAllowForceDark},
+ {"nGetUniqueId", "(J)J", (void*)android_view_RenderNode_getUniqueId},
};
int register_android_view_RenderNode(JNIEnv* env) {
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index c010212..cb0ff8d 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -169,8 +169,8 @@
displayList->mProjectedOutline = nullptr;
}
-static bool layerNeedsPaint(const LayerProperties& properties, float alphaMultiplier,
- SkPaint* paint) {
+static bool layerNeedsPaint(const sk_sp<SkImage>& snapshotImage, const LayerProperties& properties,
+ float alphaMultiplier, SkPaint* paint) {
if (alphaMultiplier < 1.0f || properties.alpha() < 255 ||
properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr ||
properties.getImageFilter() != nullptr || !properties.getStretchEffect().isEmpty()) {
@@ -179,7 +179,8 @@
paint->setColorFilter(sk_ref_sp(properties.getColorFilter()));
sk_sp<SkImageFilter> imageFilter = sk_ref_sp(properties.getImageFilter());
- sk_sp<SkImageFilter> stretchFilter = properties.getStretchEffect().getImageFilter();
+ sk_sp<SkImageFilter> stretchFilter =
+ properties.getStretchEffect().getImageFilter(snapshotImage);
sk_sp<SkImageFilter> filter;
if (imageFilter && stretchFilter) {
filter = SkImageFilters::Compose(
@@ -240,7 +241,8 @@
if (renderNode->getLayerSurface() && mComposeLayer) {
SkASSERT(properties.effectiveLayerType() == LayerType::RenderLayer);
SkPaint paint;
- layerNeedsPaint(layerProperties, alphaMultiplier, &paint);
+ sk_sp<SkImage> snapshotImage = renderNode->getLayerSurface()->makeImageSnapshot();
+ layerNeedsPaint(snapshotImage, layerProperties, alphaMultiplier, &paint);
SkSamplingOptions sampling(SkFilterMode::kLinear);
// surfaces for layers are created on LAYER_SIZE boundaries (which are >= layer size) so
@@ -254,8 +256,8 @@
canvas->drawAnnotation(bounds, String8::format(
"SurfaceID|%" PRId64, renderNode->uniqueId()).c_str(), nullptr);
}
- canvas->drawImageRect(renderNode->getLayerSurface()->makeImageSnapshot(), bounds,
- bounds, sampling, &paint, SkCanvas::kStrict_SrcRectConstraint);
+ canvas->drawImageRect(snapshotImage, bounds, bounds, sampling, &paint,
+ SkCanvas::kStrict_SrcRectConstraint);
if (!renderNode->getSkiaLayer()->hasRenderedSinceRepaint) {
renderNode->getSkiaLayer()->hasRenderedSinceRepaint = true;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index af7271e..61f9960 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -176,9 +176,6 @@
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 cee3635..8186fb7 100644
--- a/media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl
@@ -32,8 +32,8 @@
* Unique vendor ID. Identifies the engine the sound model
* was build for */
String vendorUuid;
- /** Opaque data transparent to Android framework */
- ParcelFileDescriptor data;
+ /** Opaque data transparent to Android framework. May be null if dataSize is 0. */
+ @nullable ParcelFileDescriptor data;
/** Size of the above data, in bytes. */
int dataSize;
}
diff --git a/media/java/android/media/AudioMetadata.java b/media/java/android/media/AudioMetadata.java
index ff9fd41..ca175b4 100644
--- a/media/java/android/media/AudioMetadata.java
+++ b/media/java/android/media/AudioMetadata.java
@@ -195,6 +195,61 @@
@NonNull public static final Key<Integer> KEY_AUDIO_ENCODING =
createKey("audio-encoding", Integer.class);
+
+ /**
+ * A key representing the audio presentation id being decoded by a next generation
+ * audio decoder.
+ *
+ * An Integer value representing presentation id.
+ *
+ * @see AudioPresentation#getPresentationId()
+ */
+ @NonNull public static final Key<Integer> KEY_PRESENTATION_ID =
+ createKey("presentation-id", Integer.class);
+
+ /**
+ * A key representing the audio program id being decoded by a next generation
+ * audio decoder.
+ *
+ * An Integer value representing program id.
+ *
+ * @see AudioPresentation#getProgramId()
+ */
+ @NonNull public static final Key<Integer> KEY_PROGRAM_ID =
+ createKey("program-id", Integer.class);
+
+
+ /**
+ * A key representing the audio presentation content classifier being rendered
+ * by a next generation audio decoder.
+ *
+ * An Integer value representing presentation content classifier.
+ *
+ * @see AudioPresentation.ContentClassifier
+ * One of {@link AudioPresentation#CONTENT_UNKNOWN},
+ * {@link AudioPresentation#CONTENT_MAIN},
+ * {@link AudioPresentation#CONTENT_MUSIC_AND_EFFECTS},
+ * {@link AudioPresentation#CONTENT_VISUALLY_IMPAIRED},
+ * {@link AudioPresentation#CONTENT_HEARING_IMPAIRED},
+ * {@link AudioPresentation#CONTENT_DIALOG},
+ * {@link AudioPresentation#CONTENT_COMMENTARY},
+ * {@link AudioPresentation#CONTENT_EMERGENCY},
+ * {@link AudioPresentation#CONTENT_VOICEOVER}.
+ */
+ @NonNull public static final Key<Integer> KEY_PRESENTATION_CONTENT_CLASSIFIER =
+ createKey("presentation-content-classifier", Integer.class);
+
+ /**
+ * A key representing the audio presentation language being rendered by a next
+ * generation audio decoder.
+ *
+ * A String value representing ISO 639-2 (three letter code).
+ *
+ * @see AudioPresentation#getLocale()
+ */
+ @NonNull public static final Key<String> KEY_PRESENTATION_LANGUAGE =
+ createKey("presentation-language", String.class);
+
private Format() {} // delete constructor
}
diff --git a/media/java/android/media/AudioPresentation.java b/media/java/android/media/AudioPresentation.java
index 894fbba..47358be 100644
--- a/media/java/android/media/AudioPresentation.java
+++ b/media/java/android/media/AudioPresentation.java
@@ -57,6 +57,64 @@
/** @hide */
@IntDef(
value = {
+ CONTENT_UNKNOWN,
+ CONTENT_MAIN,
+ CONTENT_MUSIC_AND_EFFECTS,
+ CONTENT_VISUALLY_IMPAIRED,
+ CONTENT_HEARING_IMPAIRED,
+ CONTENT_DIALOG,
+ CONTENT_COMMENTARY,
+ CONTENT_EMERGENCY,
+ CONTENT_VOICEOVER,
+ })
+
+ /**
+ * The ContentClassifier int definitions represent the AudioPresentation content
+ * classifier (as per TS 103 190-1 v1.2.1 4.3.3.8.1)
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ContentClassifier {}
+
+ /**
+ * Audio presentation classifier: Unknown.
+ */
+ public static final int CONTENT_UNKNOWN = -1;
+ /**
+ * Audio presentation classifier: Complete main.
+ */
+ public static final int CONTENT_MAIN = 0;
+ /**
+ * Audio presentation content classifier: Music and effects.
+ */
+ public static final int CONTENT_MUSIC_AND_EFFECTS = 1;
+ /**
+ * Audio presentation content classifier: Visually impaired.
+ */
+ public static final int CONTENT_VISUALLY_IMPAIRED = 2;
+ /**
+ * Audio presentation content classifier: Hearing impaired.
+ */
+ public static final int CONTENT_HEARING_IMPAIRED = 3;
+ /**
+ * Audio presentation content classifier: Dialog.
+ */
+ public static final int CONTENT_DIALOG = 4;
+ /**
+ * Audio presentation content classifier: Commentary.
+ */
+ public static final int CONTENT_COMMENTARY = 5;
+ /**
+ * Audio presentation content classifier: Emergency.
+ */
+ public static final int CONTENT_EMERGENCY = 6;
+ /**
+ * Audio presentation content classifier: Voice over.
+ */
+ public static final int CONTENT_VOICEOVER = 7;
+
+ /** @hide */
+ @IntDef(
+ value = {
MASTERING_NOT_INDICATED,
MASTERED_FOR_STEREO,
MASTERED_FOR_SURROUND,
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 56f6c45..53f6fe2 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -445,8 +445,7 @@
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 @@
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/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt
index a9fd6f2..d2ed73e 100644
--- a/packages/Connectivity/framework/api/module-lib-current.txt
+++ b/packages/Connectivity/framework/api/module-lib-current.txt
@@ -6,6 +6,7 @@
}
public class ConnectivityManager {
+ method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List<android.net.NetworkStateSnapshot> getAllNetworkStateSnapshot();
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @Nullable android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt
index 373fa3c..f5972fa 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 269bbf2..4a7b601 100644
--- a/packages/Connectivity/framework/src/android/net/CaptivePortal.java
+++ b/packages/Connectivity/framework/src/android/net/CaptivePortal.java
@@ -160,12 +160,11 @@
* @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/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index 39ec2edc..d7c6854 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -1259,6 +1259,25 @@
}
/**
+ * Return a list of {@link NetworkStateSnapshot}s, one for each network that is currently
+ * connected.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK,
+ android.Manifest.permission.NETWORK_SETTINGS})
+ @NonNull
+ public List<NetworkStateSnapshot> getAllNetworkStateSnapshot() {
+ try {
+ return mService.getAllNetworkStateSnapshot();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns the {@link Network} object currently serving a given type, or
* null if the given type is not connected.
*
diff --git a/packages/Connectivity/framework/src/android/net/ICaptivePortal.aidl b/packages/Connectivity/framework/src/android/net/ICaptivePortal.aidl
index fe21905..e35f8d4 100644
--- a/packages/Connectivity/framework/src/android/net/ICaptivePortal.aidl
+++ b/packages/Connectivity/framework/src/android/net/ICaptivePortal.aidl
@@ -23,5 +23,4 @@
oneway interface ICaptivePortal {
void appRequest(int request);
void appResponse(int response);
- void logEvent(int eventId, String packageName);
}
diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
index 160338d..cd49258 100644
--- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
+++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
@@ -31,6 +31,7 @@
import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.net.NetworkState;
+import android.net.NetworkStateSnapshot;
import android.net.OemNetworkPreferences;
import android.net.ProxyInfo;
import android.net.UidRange;
@@ -79,6 +80,8 @@
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
NetworkState[] getAllNetworkState();
+ List<NetworkStateSnapshot> getAllNetworkStateSnapshot();
+
boolean isActiveNetworkMetered();
boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress,
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 7f19662..c1dca5d 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -394,16 +394,20 @@
}
private void executeNotifyIfInUseCommand() {
- int status = getStatus();
-
- if (status == STATUS_IN_USE) {
- startForeground(NOTIFICATION_ID,
- buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED));
- } else if (status == STATUS_READY) {
- startForeground(NOTIFICATION_ID,
- buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED));
- } else {
- stopSelf();
+ switch (getStatus()) {
+ case STATUS_IN_USE:
+ startForeground(NOTIFICATION_ID,
+ buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED));
+ break;
+ case STATUS_READY:
+ startForeground(NOTIFICATION_ID,
+ buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED));
+ break;
+ case STATUS_IN_PROGRESS:
+ break;
+ case STATUS_NOT_STARTED:
+ default:
+ stopSelf();
}
}
diff --git a/packages/LocalTransport/OWNERS b/packages/LocalTransport/OWNERS
new file mode 100644
index 0000000..d99779e
--- /dev/null
+++ b/packages/LocalTransport/OWNERS
@@ -0,0 +1 @@
+include /services/backup/OWNERS
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index 139c8e5..63edc77 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -165,6 +165,9 @@
if (mParameters.isDeviceTransfer()) {
flags |= BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER;
}
+ if (mParameters.isEncrypted()) {
+ flags |= BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
+ }
return flags;
}
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
index 2946db3..1ba1bc6 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
@@ -28,10 +28,12 @@
private static final String KEY_FAKE_ENCRYPTION_FLAG = "fake_encryption_flag";
private static final String KEY_NON_INCREMENTAL_ONLY = "non_incremental_only";
private static final String KEY_IS_DEVICE_TRANSFER = "is_device_transfer";
+ private static final String KEY_IS_ENCRYPTED = "is_encrypted";
private boolean mFakeEncryptionFlag;
private boolean mIsNonIncrementalOnly;
private boolean mIsDeviceTransfer;
+ private boolean mIsEncrypted;
public LocalTransportParameters(Handler handler, ContentResolver resolver) {
super(handler, resolver, Settings.Secure.getUriFor(SETTING));
@@ -49,6 +51,10 @@
return mIsDeviceTransfer;
}
+ boolean isEncrypted() {
+ return mIsEncrypted;
+ }
+
public String getSettingValue(ContentResolver resolver) {
return Settings.Secure.getString(resolver, SETTING);
}
@@ -57,5 +63,6 @@
mFakeEncryptionFlag = parser.getBoolean(KEY_FAKE_ENCRYPTION_FLAG, false);
mIsNonIncrementalOnly = parser.getBoolean(KEY_NON_INCREMENTAL_ONLY, false);
mIsDeviceTransfer = parser.getBoolean(KEY_IS_DEVICE_TRANSFER, false);
+ mIsEncrypted = parser.getBoolean(KEY_IS_ENCRYPTED, false);
}
}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml
new file mode 100644
index 0000000..c799b99
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!-- The main content view -->
+<LinearLayout
+ android:id="@+id/content_parent"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
+ android:transitionGroup="true"
+ android:orientation="vertical">
+ <Toolbar
+ android:id="@+id/action_bar"
+ style="?android:attr/actionBarStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:theme="?android:attr/actionBarTheme" />
+ <FrameLayout
+ android:id="@+id/content_frame"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+</LinearLayout>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
index 637805f..ad94cd03 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -24,6 +24,7 @@
import android.widget.Toolbar;
import androidx.annotation.Nullable;
+import androidx.core.os.BuildCompat;
import androidx.fragment.app.FragmentActivity;
import com.google.android.material.appbar.CollapsingToolbarLayout;
@@ -40,8 +41,15 @@
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- super.setContentView(R.layout.collapsing_toolbar_base_layout);
- mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar);
+ // TODO(b/181723278): Update the version check after SDK for S is finalized
+ // The collapsing toolbar is only supported if the android platform version is S or higher.
+ // Otherwise the regular action bar will be shown.
+ if (BuildCompat.isAtLeastS()) {
+ super.setContentView(R.layout.collapsing_toolbar_base_layout);
+ mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar);
+ } else {
+ super.setContentView(R.layout.toolbar_base_layout);
+ }
final Toolbar toolbar = findViewById(R.id.action_bar);
setActionBar(toolbar);
@@ -90,6 +98,14 @@
super.setTitle(titleId);
}
+ @Override
+ public boolean onNavigateUp() {
+ if (!super.onNavigateUp()) {
+ finish();
+ }
+ return true;
+ }
+
/**
* Returns an instance of collapsing toolbar.
*/
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 79fbcc3..9624119 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1059,7 +1059,14 @@
<!-- Title for the accessibility preference to configure display color space correction. [CHAR LIMIT=NONE] -->
<string name="accessibility_display_daltonizer_preference_title">Color correction</string>
<!-- Subtitle for the accessibility preference to configure display color space correction. [CHAR LIMIT=NONE] -->
- <string name="accessibility_display_daltonizer_preference_subtitle"><![CDATA[Color correction allows you to adjust how colors are displayed on your device]]></string>
+ <string name="accessibility_display_daltonizer_preference_subtitle">
+ <![CDATA[
+ Adjust how colors display on your device. This can be helpful when you want to:<br/><br/>
+ <ol>
+ <li> See colors more accurately</li>
+ <li> Remove colors to help you focus</li>
+ </ol>
+ ]]></string>
<!-- Summary shown for color space correction preference when its value is overridden by another preference [CHAR LIMIT=35] -->
<string name="daltonizer_type_overridden">Overridden by <xliff:g id="title" example="Simulate color space">%1$s</xliff:g></string>
@@ -1295,6 +1302,8 @@
<!-- Name of the phone device. [CHAR LIMIT=30] -->
<string name="media_transfer_this_device_name">Phone speaker</string>
+ <!-- Name of the phone device with an active remote session. [CHAR LIMIT=30] -->
+ <string name="media_transfer_this_phone">This phone</string>
<!-- Warning message to tell user is have problem during profile connect, it need to turn off device and back on. [CHAR_LIMIT=NONE] -->
<string name="profile_connect_timeout_subtext">Problem connecting. Turn device off & back on</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java
index 228de03..35499c9 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 @@
* 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 @@
// 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 @@
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 245b7843..16d73a3 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 @@
@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 @@
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/quick_settings_footer.xml b/packages/SystemUI/res/layout/quick_settings_footer.xml
index 13572fa..db712e4 100644
--- a/packages/SystemUI/res/layout/quick_settings_footer.xml
+++ b/packages/SystemUI/res/layout/quick_settings_footer.xml
@@ -17,6 +17,7 @@
<com.android.systemui.util.NeverExactlyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:minHeight="48dp"
android:clickable="true"
android:paddingBottom="@dimen/qs_tile_padding_top"
android:paddingTop="@dimen/qs_tile_padding_top"
diff --git a/packages/SystemUI/res/layout/udfps_animation_view_enroll.xml b/packages/SystemUI/res/layout/udfps_animation_view_enroll.xml
new file mode 100644
index 0000000..9b5752d
--- /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
similarity index 83%
copy from packages/SystemUI/res/layout/udfps_animation_view.xml
copy to packages/SystemUI/res/layout/udfps_animation_view_fpm_other.xml
index 380dd85..f32faa0 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.xml b/packages/SystemUI/res/layout/udfps_animation_view_keyguard.xml
similarity index 83%
rename from packages/SystemUI/res/layout/udfps_animation_view.xml
rename to packages/SystemUI/res/layout/udfps_animation_view_keyguard.xml
index 380dd85..644d1ada 100644
--- a/packages/SystemUI/res/layout/udfps_animation_view.xml
+++ b/packages/SystemUI/res/layout/udfps_animation_view_keyguard.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.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"/>
+ 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 6ae306e..e24c9e9 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 761512c..b75a0bc 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -91,9 +91,6 @@
<!-- The number of columns in the QuickSettings -->
<integer name="quick_settings_num_columns">3</integer>
- <!-- The number of columns in the Quick Settings customizer -->
- <integer name="quick_settings_edit_num_columns">@integer/quick_settings_num_columns</integer>
-
<!-- The number of rows in the QuickSettings -->
<integer name="quick_settings_max_rows">3</integer>
@@ -415,6 +412,9 @@
<!-- Whether or not child notifications that are part of a group will have shadows. -->
<bool name="config_enableShadowOnChildNotifications">true</bool>
+ <!-- If true, group numbers are shown in the expander instead of via "+N" overflow number -->
+ <bool name="config_showNotificationGroupCountInExpander">true</bool>
+
<!-- Whether or not a view containing child notifications will have a custom background when
it has been expanded to reveal its children. -->
<bool name="config_showGroupNotificationBgWhenExpanded">false</bool>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 2d202fb..ec26b8d 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -608,6 +608,11 @@
<item name="android:windowCloseOnTouchOutside">true</item>
</style>
+ <!-- Privacy dialog -->
+ <style name="PrivacyDialog" parent="ScreenRecord">
+ <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
+ </style>
+
<!-- USB Contaminant dialog -->
<style name ="USBContaminant" />
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
index e5ced3e..54242be 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
@@ -21,5 +21,5 @@
*/
oneway interface ISplitScreenListener {
void onStagePositionChanged(int stage, int position);
- void onTaskStageChanged(int taskId, int stage);
-}
\ No newline at end of file
+ void onTaskStageChanged(int taskId, int stage, boolean visible);
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 5c943f6..bac4c43 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -19,6 +19,7 @@
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.content.ComponentName;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.Insets;
@@ -34,7 +35,7 @@
/**
* Temporary callbacks into SystemUI.
- * Next id = 30
+ * Next id = 43
*/
interface ISystemUiProxy {
@@ -251,5 +252,7 @@
void startShortcut(in String packageName, in String shortcutId, in int stage, in int position,
in Bundle options, in UserHandle user) = 40;
void startIntent(
- in PendingIntent intent, in int stage, in int position, in Bundle options) = 41;
+ in PendingIntent intent, in Intent fillInIntent, in int stage, in int position,
+ in Bundle options) = 41;
+ void removeFromSideStage(in int taskId) = 42;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 87f6b82..568fdea 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -16,6 +16,8 @@
package com.android.systemui.shared.system;
+import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+
import android.app.PictureInPictureParams;
import android.app.WindowConfiguration;
import android.graphics.Point;
@@ -44,6 +46,9 @@
public static final int ACTIVITY_TYPE_ASSISTANT = WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
public final int activityType;
+ public static final int TYPE_NAVIGATION_BAR = WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+ public final int windowType;
+
public final int taskId;
public final SurfaceControlCompat leash;
public final boolean isTranslucent;
@@ -74,6 +79,7 @@
contentInsets = app.contentInsets;
activityType = app.windowConfiguration.getActivityType();
pictureInPictureParams = app.pictureInPictureParams;
+ windowType = app.windowType;
mStartLeash = app.startLeash;
}
@@ -114,6 +120,7 @@
activityType = ACTIVITY_TYPE_UNDEFINED;
}
pictureInPictureParams = null;
+ windowType = INVALID_WINDOW_TYPE;
mStartLeash = null;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 2373d75..83c2d1e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -405,14 +405,19 @@
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
+ /**
+ * Set the amount (ratio) that the device has transitioned to doze.
+ *
+ * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
+ */
public void setDarkAmount(float darkAmount) {
- boolean isAwake = darkAmount != 0;
- boolean wasAwake = mDarkAmount != 0;
- if (isAwake == wasAwake) {
+ boolean isDozing = darkAmount != 0;
+ boolean wasDozing = mDarkAmount != 0;
+ if (isDozing == wasDozing) {
return;
}
mDarkAmount = darkAmount;
- setLayoutAnimationListener(isAwake ? null : mKeepAwakeListener);
+ setLayoutAnimationListener(isDozing ? null : mKeepAwakeListener);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java
index ed4d47c..a029003 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 @@
* 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 @@
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 5290986..28b5719 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 @@
}
@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 @@
@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 efc864a..ef7a340 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 @@
}
@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 8664e44..5f268cf 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 @@
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 @@
@NonNull StatusBarStateController statusBarStateController) {
super(context);
mContext = context;
+ mStatusBarStateController = statusBarStateController;
mMaxBurnInOffsetX = context.getResources()
.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
@@ -89,6 +91,10 @@
@Override
public void draw(@NonNull Canvas canvas) {
+ if (isIlluminationShowing()) {
+ return;
+ }
+
canvas.save();
canvas.translate(mBurnInOffsetX, mBurnInOffsetY);
mFingerprintDrawable.draw(canvas);
@@ -106,11 +112,16 @@
}
@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 44122cb..f4dd181 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.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 @@
return (int) ((1 - percent) * 255);
}
+ void onIlluminationStarting() {
+ getUdfpsAnimation().setIlluminationShowing(true);
+ postInvalidate();
+ }
+
+ void onIlluminationStopped() {
+ getUdfpsAnimation().setIlluminationShowing(false);
+ postInvalidate();
+ }
+
void setParent(@NonNull UdfpsView parent) {
mParent = parent;
}
- void setAnimation(@Nullable UdfpsAnimation animation) {
- if (mUdfpsAnimation != null) {
- mUdfpsAnimation.setAnimationView(null);
- }
-
- mUdfpsAnimation = animation;
- if (mUdfpsAnimation != null) {
- mUdfpsAnimation.setAnimationView(this);
- }
- }
-
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 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 0000000..19e77493
--- /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 0000000..3d2f5a0
--- /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 0000000..7d0b3e5
--- /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 71fba33..e1d7eb3 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 @@
@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 @@
@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 @@
@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 @@
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 @@
mFingerprintManager = checkNotNull(fingerprintManager);
mWindowManager = windowManager;
mFgExecutor = fgExecutor;
+ mStatusBar = statusBar;
mStatusBarStateController = statusBarStateController;
mSensorProps = findFirstUdfps();
@@ -217,9 +215,6 @@
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 @@
}
}
- 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 @@
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 @@
});
}
- @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 @@
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 2442633..942fa7a 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 @@
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 */);
- }
-
- void updateProgress(@NonNull UdfpsProgressBar progressBar) {
- progressBar.setProgress(mCurrentProgress);
+ if (mListener != null) {
+ mListener.onEnrollmentProgress(remaining, mTotalSteps);
+ }
}
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 97c215e..61ec127 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 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 @@
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 3997943..cd849e6 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.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 @@
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 @@
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 @@
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 @@
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);
+ void setAnimationView(@NonNull UdfpsAnimationView animation) {
+ mAnimationView = animation;
+ animation.setParent(this);
- mEnrollHelper = enrollHelper;
-
- 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 @@
@Override
public void dozeTimeTick() {
+ if (mAnimationView == null) {
+ return;
+ }
mAnimationView.dozeTimeTick();
}
@@ -143,12 +130,10 @@
@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 @@
@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 @@
@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/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 04c4e97..1c5715c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -25,6 +25,7 @@
import android.app.IWallpaperManager;
import android.app.KeyguardManager;
import android.app.NotificationManager;
+import android.app.StatsManager;
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
import android.app.role.RoleManager;
@@ -321,6 +322,12 @@
@Provides
@Singleton
+ static StatsManager provideStatsManager(Context context) {
+ return context.getSystemService(StatsManager.class);
+ }
+
+ @Provides
+ @Singleton
@Nullable
static TelecomManager provideTelecomManager(Context context) {
return context.getSystemService(TelecomManager.class);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
index bd3f5a6..7fb7d8b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
@@ -20,9 +20,13 @@
import android.content.Context
import android.content.Intent
import android.text.TextUtils
+import android.util.Log
import com.android.settingslib.media.MediaOutputConstants
import javax.inject.Inject
+private const val TAG = "MediaOutputDlgReceiver"
+private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+
/**
* BroadcastReceiver for handling media output intent
*/
@@ -32,8 +36,13 @@
override fun onReceive(context: Context, intent: Intent) {
if (TextUtils.equals(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG,
intent.action)) {
- mediaOutputDialogFactory.create(
- intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME), false)
+ val packageName: String? =
+ intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME)
+ if (!TextUtils.isEmpty(packageName)) {
+ mediaOutputDialogFactory.create(packageName!!, false)
+ } else if (DEBUG) {
+ Log.e(TAG, "Unable to launch media output dialog. Package name is empty.")
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 70b7b04..4491cc1 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -180,7 +180,7 @@
}
} else {
for (int i = 0; i < mNavigationBars.size(); i++) {
- mNavigationBars.get(i).onConfigurationChanged(newConfig);
+ mNavigationBars.valueAt(i).onConfigurationChanged(newConfig);
}
}
}
@@ -193,7 +193,7 @@
if (navBar == null) {
continue;
}
- NavigationBarView view = (NavigationBarView) mNavigationBars.get(i).getView();
+ NavigationBarView view = (NavigationBarView) navBar.getView();
if (view != null) {
view.updateStates();
}
@@ -399,7 +399,7 @@
if (i > 0) {
pw.println();
}
- mNavigationBars.get(i).dump(pw);
+ mNavigationBars.valueAt(i).dump(pw);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
index 7679d48..8ec9b68 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
@@ -47,7 +47,7 @@
context: Context,
private val list: List<PrivacyElement>,
activityStarter: (String, Int) -> Unit
-) : SystemUIDialog(context, R.style.ScreenRecord) {
+) : SystemUIDialog(context, R.style.PrivacyDialog) {
private val dismissListeners = mutableListOf<WeakReference<OnDialogDismissed>>()
private val dismissed = AtomicBoolean(false)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt
index c3cc3af..52f111e7 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 @@
import android.content.Context
import android.util.AttributeSet
-import com.android.systemui.R
open class SideLabelTileLayout(
context: Context,
@@ -28,9 +27,6 @@
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 47cb45b..ce8f6c1 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 @@
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 @@
mSecondLine.setVisibility(mShowAppLabel ? View.VISIBLE : View.GONE);
}
- public TextView getAppLabel() {
- return mSecondLine;
- }
-
@Override
protected boolean animationsEnabled() {
return false;
@@ -54,4 +50,9 @@
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 0000000..4ffcd8c
--- /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 7a91421..0adc844 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.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 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.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 @@
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 @@
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 @@
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 @@
}
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 @@
}
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 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 @@
.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 @@
.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 @@
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 void onSwiped(ViewHolder viewHolder, int direction) {
}
};
+
+ interface CustomizeView {
+ void setShowAppLabel(boolean showAppLabel);
+ void changeState(@NonNull QSTile.State state);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index 38e2ba4..33ca7d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -259,23 +259,30 @@
mIcon.setIcon(state, allowAnimations);
setContentDescription(state.contentDescription);
final StringBuilder stateDescription = new StringBuilder();
+ String text = "";
switch (state.state) {
case Tile.STATE_UNAVAILABLE:
- stateDescription.append(mContext.getString(R.string.tile_unavailable));
+ text = mContext.getString(R.string.tile_unavailable);
break;
case Tile.STATE_INACTIVE:
if (state instanceof QSTile.BooleanState) {
- stateDescription.append(mContext.getString(R.string.switch_bar_off));
+ text = mContext.getString(R.string.switch_bar_off);
}
break;
case Tile.STATE_ACTIVE:
if (state instanceof QSTile.BooleanState) {
- stateDescription.append(mContext.getString(R.string.switch_bar_on));
+ text = mContext.getString(R.string.switch_bar_on);
}
break;
default:
break;
}
+ if (!TextUtils.isEmpty(text)) {
+ stateDescription.append(text);
+ if (TextUtils.isEmpty(state.secondaryLabel)) {
+ state.secondaryLabel = text;
+ }
+ }
if (!TextUtils.isEmpty(state.stateDescription)) {
stateDescription.append(", ");
stateDescription.append(state.stateDescription);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
index 207b25d0..b59326a 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 @@
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 07d48f3..231037f 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 @@
// 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 @@
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/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 5b2a7e7..e7d4283 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -662,14 +662,29 @@
}
@Override
- public void startIntent(PendingIntent intent, int stage, int position, Bundle options) {
+ public void startIntent(PendingIntent intent, Intent fillInIntent,
+ int stage, int position, Bundle options) {
if (!verifyCaller("startIntent")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mSplitScreenOptional.ifPresent(s ->
- s.startIntent(intent, stage, position, options));
+ s.startIntent(intent, mContext, fillInIntent, stage, position, options));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void removeFromSideStage(int taskId) {
+ if (!verifyCaller("removeFromSideStage")) {
+ return;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mSplitScreenOptional.ifPresent(
+ s -> s.removeFromSideStage(taskId));
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -789,10 +804,10 @@
}
@Override
- public void onTaskStageChanged(int taskId, int stage) {
+ public void onTaskStageChanged(int taskId, int stage, boolean visible) {
try {
if (mISplitScreenListener != null) {
- mISplitScreenListener.onTaskStageChanged(taskId, stage);
+ mISplitScreenListener.onTaskStageChanged(taskId, stage, visible);
}
} catch (RemoteException e) {
Log.e(TAG_OPS, "onTaskStageChanged", e);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 805ac7c..3d6dea3 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 @@
setWindowFocusable(true);
if (mConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, false)) {
+ SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, true)) {
View decorView = mWindow.getDecorView();
// Wait until this window is attached to request because it is
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
index 6cdf6ab..58a54f6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
@@ -82,7 +82,7 @@
dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
if (intent != null) {
final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
- mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED, null, UserHandle.CURRENT);
+ mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
b.setContentIntent(pendingIntent);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index bf65132..9da6b8f 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 @@
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 @@
}
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 @@
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 (mAtTopEdge && mAtBottomEdge) {
- Log.d(TAG, "Reached both top and bottom edge, ending.");
+ if (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;
+ // We hit a boundary, clear the tiles, capture everything in the opposite direction,
+ // then finish.
+ mImageTileSet.clear();
+ mFinishOnBoundary = true;
+ mScrollingUp = !mScrollingUp;
+ }
+ } 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 {
+ 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 @@
// 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 @@
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 9ed9659..f2adaf0 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 @@
sb.append(g.toJson());
count++;
}
- mLastSaveLen += count;
+ mLastSaveLen = count;
sb.append("]");
return sb.toString();
}
@@ -249,9 +249,7 @@
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 01d3103..e5a960e 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 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 @@
Log.w(TAG, "No icon for slot " + mSlot + "; " + mIcon.icon);
return false;
}
+
+ if (drawable.getIntrinsicWidth() > MAX_IMAGE_SIZE
+ || drawable.getIntrinsicHeight() > MAX_IMAGE_SIZE) {
+ Log.w(TAG, "Drawable is too large " + mIcon);
+ return false;
+ }
+
if (withClear) {
setImageDrawable(null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 1251b58..5206328 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -2052,8 +2052,7 @@
mNotificationLaunchHeight,
zProgress);
setTranslationZ(translationZ);
- float extraWidthForClipping = params.getWidth() - getWidth()
- + MathUtils.lerp(0, mOutlineRadius * 2, params.getProgress());
+ float extraWidthForClipping = params.getWidth() - getWidth();
setExtraWidthForClipping(extraWidthForClipping);
int top = params.getTop();
float interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(params.getProgress());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 7691761..b0b91bd 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.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.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 @@
mCachedHeadsUpRemoteInput = null;
}
+
private RemoteInputView applyRemoteInput(View view, NotificationEntry entry,
boolean hasRemoteInput, PendingIntent existingPendingIntent,
RemoteInputView cachedView, NotificationViewWrapper wrapper) {
@@ -1271,6 +1275,15 @@
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 @@
&& 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 @@
return;
}
+ // explicitly resolve drawable resource using SystemUI's theme
Drawable snoozeDrawable = mContext.getDrawable(R.drawable.ic_snooze);
- mContainingNotification.updateNotificationColor();
- snoozeDrawable.setTint(mContainingNotification.getNotificationColor());
snoozeButton.setImageDrawable(snoozeDrawable);
final NotificationSnooze snoozeGuts = (NotificationSnooze) LayoutInflater.from(mContext)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 756fe6c..8446b4e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -133,7 +133,7 @@
*/
public static int getNotificationLaunchHeight(Context context) {
int zDistance = getZDistanceBetweenElements(context);
- return getBaseHeight(zDistance) * 2;
+ return NOTIFICATIONS_HAVE_SHADOWS ? 2 * getBaseHeight(zDistance) : 4 * zDistance;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 3833637..3739424 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.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 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 @@
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 @@
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 @@
mNotificationHeader.measure(widthMeasureSpec, headerHeightSpec);
}
if (mNotificationHeaderLowPriority != null) {
- headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY);
mNotificationHeaderLowPriority.measure(widthMeasureSpec, headerHeightSpec);
}
@@ -397,7 +405,20 @@
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 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 b25fced..bf36435 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.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 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 @@
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 @@
mActivityIntentHelper = new ActivityIntentHelper(mContext);
DateTimeView.setReceiverHandler(timeTickHandler);
-
- if (DEBUG_GESTURES) {
- mGestureRec = new GestureRecorder(mContext.getCacheDir() + "/statusbar_gestures.dat");
- }
}
@Override
@@ -2271,7 +2267,7 @@
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 @@
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 @@
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 @@
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 @@
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 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 598addc..34673f2 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 @@
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 @@
int launchResult = TaskStackBuilder.create(mContext)
.addNextIntentWithParentStack(intent)
.startActivities(getActivityOptions(
+ mStatusBar.getDisplayId(),
mActivityLaunchAnimator.getLaunchAnimation(
row, mStatusBar.isOccluded())),
new UserHandle(UserHandle.getUserId(appUid)));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 38f3bc8..59c1138 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -94,15 +94,6 @@
goingToFullShade,
oldState);
}
-
- @Override
- public void onDozeAmountChanged(float linearAmount, float amount) {
- if (DEBUG) {
- Log.d(TAG, String.format("onDozeAmountChanged: linearAmount=%f amount=%f",
- linearAmount, amount));
- }
- setDarkAmount(amount);
- }
};
@Inject
@@ -294,20 +285,6 @@
}
}
- /**
- * Set the amount (ratio) that the device has transitioned to doze.
- *
- * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
- */
- private void setDarkAmount(float darkAmount) {
- boolean isAwake = darkAmount != 0;
- if (darkAmount == mDarkAmount) {
- return;
- }
- mDarkAmount = darkAmount;
- mView.setVisibility(isAwake ? View.VISIBLE : View.GONE);
- }
-
private boolean isListAnimating() {
return mKeyguardVisibilityHelper.isVisibilityAnimating();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index 8845a05..5a80c05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -205,7 +205,7 @@
protected void onViewAttached() {
if (DEBUG) Log.d(TAG, "onViewAttached");
mAdapter.registerDataSetObserver(mDataSetObserver);
- mDataSetObserver.onChanged();
+ mAdapter.notifyDataSetChanged();
mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
mStatusBarStateController.addCallback(mStatusBarStateListener);
mScreenLifecycle.addObserver(mScreenObserver);
@@ -373,14 +373,13 @@
* @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
*/
private void setDarkAmount(float darkAmount) {
- boolean isAwake = darkAmount != 0;
+ boolean isFullyDozed = darkAmount == 1;
if (darkAmount == mDarkAmount) {
return;
}
mDarkAmount = darkAmount;
mListView.setDarkAmount(darkAmount);
- mView.setVisibility(isAwake ? View.VISIBLE : View.GONE);
- if (!isAwake) {
+ if (isFullyDozed) {
closeSwitcherIfOpenAndNotSimple(false);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 738cab1..5638503 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -439,7 +439,7 @@
private final NetworkCallback mNetworkCallback = new NetworkCallback() {
@Override
public void onAvailable(Network network) {
- if (DEBUG) Log.d(TAG, "onAvailable " + network.netId);
+ if (DEBUG) Log.d(TAG, "onAvailable " + network.getNetId());
updateState();
fireCallbacks();
};
@@ -448,7 +448,7 @@
// how long the VPN connection is held on to.
@Override
public void onLost(Network network) {
- if (DEBUG) Log.d(TAG, "onLost " + network.netId);
+ if (DEBUG) Log.d(TAG, "onLost " + network.getNetId());
updateState();
fireCallbacks();
};
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 25345d5..5dc7006 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 @@
/** 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 700f101..fb778e8 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 @@
}
@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 3d53062..62cc9b7 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 @@
MockitoAnnotations.initMocks(this);
TestableLooper.get(this).runWithLooper(() -> mTileAdapter =
- new TileAdapter(mContext, mQSTileHost, new UiEventLoggerFake()));
+ new TileAdapter(mContext, mQSTileHost, new UiEventLoggerFake(), /* qsFlag */false));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileBaseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileBaseViewTest.kt
new file mode 100644
index 0000000..998e070
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileBaseViewTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tileimpl
+
+import android.service.quicksettings.Tile
+import android.testing.AndroidTestingRunner
+import android.text.TextUtils
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.qs.QSIconView
+import com.android.systemui.plugins.qs.QSTile
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class QSTileBaseViewTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var iconView: QSIconView
+
+ private lateinit var tileView: QSTileBaseView
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ tileView = QSTileBaseView(context, iconView, false)
+ }
+
+ @Test
+ fun testSecondaryLabelNotModified_unavailable() {
+ val state = QSTile.State()
+ val testString = "TEST STRING"
+ state.state = Tile.STATE_UNAVAILABLE
+ state.secondaryLabel = testString
+
+ tileView.handleStateChanged(state)
+
+ assertThat(state.secondaryLabel as CharSequence).isEqualTo(testString)
+ }
+
+ @Test
+ fun testSecondaryLabelNotModified_booleanInactive() {
+ val state = QSTile.BooleanState()
+ val testString = "TEST STRING"
+ state.state = Tile.STATE_INACTIVE
+ state.secondaryLabel = testString
+
+ tileView.handleStateChanged(state)
+
+ assertThat(state.secondaryLabel as CharSequence).isEqualTo(testString)
+ }
+
+ @Test
+ fun testSecondaryLabelNotModified_booleanActive() {
+ val state = QSTile.BooleanState()
+ val testString = "TEST STRING"
+ state.state = Tile.STATE_ACTIVE
+ state.secondaryLabel = testString
+
+ tileView.handleStateChanged(state)
+
+ assertThat(state.secondaryLabel as CharSequence).isEqualTo(testString)
+ }
+
+ @Test
+ fun testSecondaryLabelNotModified_availableNotBoolean_inactive() {
+ val state = QSTile.State()
+ state.state = Tile.STATE_INACTIVE
+ state.secondaryLabel = ""
+
+ tileView.handleStateChanged(state)
+
+ assertThat(TextUtils.isEmpty(state.secondaryLabel)).isTrue()
+ }
+
+ @Test
+ fun testSecondaryLabelNotModified_availableNotBoolean_active() {
+ val state = QSTile.State()
+ state.state = Tile.STATE_ACTIVE
+ state.secondaryLabel = ""
+
+ tileView.handleStateChanged(state)
+
+ assertThat(TextUtils.isEmpty(state.secondaryLabel)).isTrue()
+ }
+
+ @Test
+ fun testSecondaryLabelDescription_unavailable() {
+ val state = QSTile.State()
+ state.state = Tile.STATE_UNAVAILABLE
+ state.secondaryLabel = ""
+
+ tileView.handleStateChanged(state)
+
+ assertThat(state.secondaryLabel as CharSequence).isEqualTo(
+ context.getString(R.string.tile_unavailable)
+ )
+ }
+
+ @Test
+ fun testSecondaryLabelDescription_booleanInactive() {
+ val state = QSTile.BooleanState()
+ state.state = Tile.STATE_INACTIVE
+ state.secondaryLabel = ""
+
+ tileView.handleStateChanged(state)
+
+ assertThat(state.secondaryLabel as CharSequence).isEqualTo(
+ context.getString(R.string.switch_bar_off)
+ )
+ }
+
+ @Test
+ fun testSecondaryLabelDescription_booleanActive() {
+ val state = QSTile.BooleanState()
+ state.state = Tile.STATE_ACTIVE
+ state.secondaryLabel = ""
+
+ tileView.handleStateChanged(state)
+
+ assertThat(state.secondaryLabel as CharSequence).isEqualTo(
+ context.getString(R.string.switch_bar_on)
+ )
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index 71f146b..f31639c 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.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 @@
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 065e2bb..88e6b66 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 @@
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 @@
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 @@
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 @@
mSecurityPolicy = securityPolicy;
mSystemActionPerformer = systemActionPerfomer;
mSystemSupport = systemSupport;
+ mTrace = trace;
mMainHandler = mainHandler;
mInvocationHandler = new InvocationHandler(mainHandler.getLooper());
mA11yWindowManager = a11yWindowManager;
@@ -291,6 +297,10 @@
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 @@
@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 @@
@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 @@
@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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
*/
@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 @@
*/
@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 @@
// 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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
@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 c63c2e1..b3be044 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 @@
*/
public class AccessibilityManagerService extends IAccessibilityManager.Stub
implements AbstractAccessibilityServiceConnection.SystemSupport,
+ AccessibilityTrace,
AccessibilityUserState.ServiceInfoChangeListener,
AccessibilityWindowManager.AccessibilityEventSender,
AccessibilitySecurityPolicy.AccessibilityUserManager,
@@ -243,6 +244,7 @@
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 @@
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 @@
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 @@
@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 @@
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 @@
// 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 @@
@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 @@
@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 @@
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 @@
@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 @@
@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 @@
*/
@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 @@
*/
@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 @@
@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 @@
@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 @@
@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 @@
}
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 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 @@
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 @@
@Override
public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) {
+ if (isA11yTracingEnabled()) {
+ logTrace(LOG_TAG + ".unregisterUiTestAutomationService",
+ "serviceClient=" + serviceClient);
+ }
synchronized (mLock) {
mUiAutomationManager.unregisterUiTestAutomationServiceLocked(serviceClient);
}
@@ -898,6 +985,11 @@
@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 @@
@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 @@
*/
@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 @@
*/
@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 @@
*/
@Override
public void onSystemActionsChanged() {
+ if (isA11yTracingEnabled()) {
+ logTrace(LOG_TAG + ".onSystemActionsChanged");
+ }
+
synchronized (mLock) {
AccessibilityUserState state = getCurrentUserStateLocked();
notifySystemActionsChangedLocked(state);
@@ -1080,6 +1189,10 @@
@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 @@
@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 @@
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 @@
@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 @@
@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 @@
@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 @@
*/
@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 @@
@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 @@
@Override
public void sendAccessibilityEventForCurrentUserLocked(AccessibilityEvent event) {
+ if (isA11yTracingEnabled()) {
+ logTrace(LOG_TAG + ".sendAccessibilityEventForCurrentUserLocked", "event=" + event);
+ }
+
sendAccessibilityEventLocked(event, mCurrentUserId);
}
@@ -2918,6 +3062,10 @@
*/
@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 @@
*/
@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 @@
*/
@Override
public long getRecommendedTimeoutMillis() {
+ if (isA11yTracingEnabled()) {
+ logTrace(LOG_TAG + ".getRecommendedTimeoutMillis");
+ }
+
synchronized(mLock) {
final AccessibilityUserState userState = getCurrentUserStateLocked();
return getRecommendedTimeoutMillisLocked(userState);
@@ -2970,6 +3126,10 @@
@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 @@
@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 @@
@Override
public void disassociateEmbeddedHierarchy(@NonNull IBinder token) {
+ if (isA11yTracingEnabled()) {
+ logTrace(LOG_TAG + ".disassociateEmbeddedHierarchy", "token=" + token);
+ }
+
synchronized (mLock) {
mA11yWindowManager.disassociateEmbeddedHierarchyLocked(token);
}
@@ -3084,6 +3253,9 @@
@Override
public FullScreenMagnificationController getFullScreenMagnificationController() {
+ if (isA11yTracingEnabled()) {
+ logTrace(LOG_TAG + ".getFullScreenMagnificationController");
+ }
synchronized (mLock) {
return mMagnificationController.getFullScreenMagnificationController();
}
@@ -3091,6 +3263,10 @@
@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 @@
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 @@
@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 @@
@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 @@
});
}
+
+ @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 6756268..7d75b73 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 @@
*/
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 @@
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 @@
@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 @@
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 @@
@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 @@
@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 @@
@Override
public boolean isAccessibilityButtonAvailable() {
+ if (mTrace.isA11yTracingEnabled()) {
+ mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".isAccessibilityButtonAvailable");
+ }
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
return false;
@@ -347,6 +373,10 @@
}
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 @@
}
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 @@
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 0000000..0c03877
--- /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 4473754..9547280 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 @@
private AbstractAccessibilityServiceConnection.SystemSupport mSystemSupport;
+ private AccessibilityTrace mTrace;
+
private int mUiAutomationFlags;
UiAutomationManager(Object lock) {
@@ -89,6 +91,7 @@
int id, Handler mainHandler,
AccessibilitySecurityPolicy securityPolicy,
AbstractAccessibilityServiceConnection.SystemSupport systemSupport,
+ AccessibilityTrace trace,
WindowManagerInternal windowManagerInternal,
SystemActionPerformer systemActionPerformer,
AccessibilityWindowManager awm, int flags) {
@@ -111,13 +114,14 @@
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 @@
UiAutomationService(Context context, AccessibilityServiceInfo accessibilityServiceInfo,
int id, Handler mainHandler, Object lock,
AccessibilitySecurityPolicy securityPolicy,
- SystemSupport systemSupport, WindowManagerInternal windowManagerInternal,
+ SystemSupport systemSupport, AccessibilityTrace trace,
+ WindowManagerInternal windowManagerInternal,
SystemActionPerformer systemActionPerformer, AccessibilityWindowManager awm) {
super(context, COMPONENT_NAME, accessibilityServiceInfo, id, mainHandler, lock,
- securityPolicy, systemSupport, windowManagerInternal, systemActionPerformer,
- awm);
+ securityPolicy, systemSupport, trace, windowManagerInternal,
+ systemActionPerformer, awm);
mMainHandler = mainHandler;
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 21cae45..a3a0cb4 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1330,7 +1330,7 @@
mPermissionControllerManager.getPrivilegesDescriptionStringForProfile(
deviceProfile, FgThread.getExecutor(), desc -> {
try {
- result.complete(desc);
+ result.complete(String.valueOf(desc));
} catch (Exception e) {
result.completeExceptionally(e);
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 02930dc..f4a8ccd 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.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.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 @@
private final LocalService mLocalService = new LocalService();
+ private final ContentCaptureManagerServiceStub mContentCaptureManagerServiceStub =
+ new ContentCaptureManagerServiceStub();
+
@Nullable
final LocalLog mRequestsHistory;
@@ -224,8 +229,7 @@
@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 @@
}
}
+ 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 @@
}
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 @@
}
@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 @@
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 53cdc33..225a8d4 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 @@
? "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 @@
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 b67bdc2..8ccfad6 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -97,6 +97,7 @@
":platform-compat-config",
":platform-compat-overrides",
":display-device-config",
+ ":display-layout-config",
":cec-config",
":device-state-config",
"java/com/android/server/EventLogTags.logtags",
@@ -222,7 +223,6 @@
"java/com/android/server/TestNetworkService.java",
"java/com/android/server/connectivity/AutodestructReference.java",
"java/com/android/server/connectivity/ConnectivityConstants.java",
- "java/com/android/server/connectivity/DataConnectionStats.java",
"java/com/android/server/connectivity/DnsManager.java",
"java/com/android/server/connectivity/KeepaliveTracker.java",
"java/com/android/server/connectivity/LingerMonitor.java",
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d1cd3be..0b7dbde 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.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.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.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;
@@ -204,7 +203,6 @@
import com.android.net.module.util.PermissionUtils;
import com.android.server.am.BatteryStatsService;
import com.android.server.connectivity.AutodestructReference;
-import com.android.server.connectivity.DataConnectionStats;
import com.android.server.connectivity.DnsManager;
import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
import com.android.server.connectivity.KeepaliveTracker;
@@ -331,7 +329,7 @@
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 +1040,14 @@
}
}
- 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 +1093,7 @@
// 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),
@@ -1215,9 +1212,6 @@
mSettingsObserver = new SettingsObserver(mContext, mHandler);
registerSettingsCallbacks();
- final DataConnectionStats dataConnectionStats = new DataConnectionStats(mContext, mHandler);
- dataConnectionStats.startMonitoring();
-
mKeepaliveTracker = new KeepaliveTracker(mContext, mHandler);
mNotifier = new NetworkNotificationManager(mContext, mTelephonyManager);
mQosCallbackTracker = new QosCallbackTracker(mHandler, mNetworkRequestCounter);
@@ -1403,7 +1397,7 @@
return null;
}
- private NetworkState getUnfilteredActiveNetworkState(int uid) {
+ private NetworkAgentInfo getNetworkAgentInfoForUid(int uid) {
NetworkAgentInfo nai = getDefaultNetworkForUid(uid);
final Network[] networks = getVpnUnderlyingNetworks(uid);
@@ -1419,12 +1413,7 @@
nai = null;
}
}
-
- if (nai != null) {
- return nai.getNetworkState();
- } else {
- return NetworkState.EMPTY;
- }
+ return nai;
}
/**
@@ -1477,24 +1466,31 @@
"%s %d(%d) on netId %d", action, nri.mUid, requestId, net.getNetId()));
}
- private void filterNetworkInfo(@NonNull NetworkInfo networkInfo,
- @NonNull NetworkCapabilities nc, int uid, boolean ignoreBlocked) {
- if (isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked)) {
- networkInfo.setDetailedState(DetailedState.BLOCKED, null, null);
- }
- networkInfo.setDetailedState(
- getLegacyLockdownState(networkInfo.getDetailedState()),
- "" /* reason */, null /* extraInfo */);
- }
-
/**
- * Apply any relevant filters to {@link NetworkState} for the given UID. For
+ * Apply any relevant filters to the specified {@link NetworkInfo} for the given UID. For
* example, this may mark the network as {@link DetailedState#BLOCKED} based
* on {@link #isNetworkWithCapabilitiesBlocked}.
*/
- private void filterNetworkStateForUid(NetworkState state, int uid, boolean ignoreBlocked) {
- if (state == null || state.networkInfo == null || state.linkProperties == null) return;
- filterNetworkInfo(state.networkInfo, state.networkCapabilities, uid, ignoreBlocked);
+ @NonNull
+ private NetworkInfo filterNetworkInfo(@NonNull NetworkInfo networkInfo, int type,
+ @NonNull NetworkCapabilities nc, int uid, boolean ignoreBlocked) {
+ final NetworkInfo filtered = new NetworkInfo(networkInfo);
+ // Many legacy types (e.g,. TYPE_MOBILE_HIPRI) are not actually a property of the network
+ // but only exists if an app asks about them or requests them. Ensure the requesting app
+ // gets the type it asks for.
+ filtered.setType(type);
+ final DetailedState state = isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked)
+ ? DetailedState.BLOCKED
+ : filtered.getDetailedState();
+ filtered.setDetailedState(getLegacyLockdownState(state),
+ "" /* reason */, null /* extraInfo */);
+ return filtered;
+ }
+
+ private NetworkInfo getFilteredNetworkInfo(NetworkAgentInfo nai, int uid,
+ boolean ignoreBlocked) {
+ return filterNetworkInfo(nai.networkInfo, nai.networkInfo.getType(),
+ nai.networkCapabilities, uid, ignoreBlocked);
}
/**
@@ -1508,10 +1504,11 @@
public NetworkInfo getActiveNetworkInfo() {
enforceAccessPermission();
final int uid = mDeps.getCallingUid();
- final NetworkState state = getUnfilteredActiveNetworkState(uid);
- filterNetworkStateForUid(state, uid, false);
- maybeLogBlockedNetworkInfo(state.networkInfo, uid);
- return state.networkInfo;
+ final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid);
+ if (nai == null) return null;
+ final NetworkInfo networkInfo = getFilteredNetworkInfo(nai, uid, false);
+ maybeLogBlockedNetworkInfo(networkInfo, uid);
+ return networkInfo;
}
@Override
@@ -1546,30 +1543,37 @@
@Override
public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) {
PermissionUtils.enforceNetworkStackPermission(mContext);
- final NetworkState state = getUnfilteredActiveNetworkState(uid);
- filterNetworkStateForUid(state, uid, ignoreBlocked);
- return state.networkInfo;
+ final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid);
+ if (nai == null) return null;
+ return getFilteredNetworkInfo(nai, uid, ignoreBlocked);
}
- private NetworkInfo getFilteredNetworkInfo(int networkType, int uid) {
+ /** Returns a NetworkInfo object for a network that doesn't exist. */
+ private NetworkInfo makeFakeNetworkInfo(int networkType, int uid) {
+ final NetworkInfo info = new NetworkInfo(networkType, 0 /* subtype */,
+ getNetworkTypeName(networkType), "" /* subtypeName */);
+ info.setIsAvailable(true);
+ // For compatibility with legacy code, return BLOCKED instead of DISCONNECTED when
+ // background data is restricted.
+ final NetworkCapabilities nc = new NetworkCapabilities(); // Metered.
+ final DetailedState state = isNetworkWithCapabilitiesBlocked(nc, uid, false)
+ ? DetailedState.BLOCKED
+ : DetailedState.DISCONNECTED;
+ info.setDetailedState(getLegacyLockdownState(state),
+ "" /* reason */, null /* extraInfo */);
+ return info;
+ }
+
+ private NetworkInfo getFilteredNetworkInfoForType(int networkType, int uid) {
if (!mLegacyTypeTracker.isTypeSupported(networkType)) {
return null;
}
final NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
- final NetworkInfo info;
- final NetworkCapabilities nc;
- if (nai != null) {
- info = new NetworkInfo(nai.networkInfo);
- info.setType(networkType);
- nc = nai.networkCapabilities;
- } else {
- info = new NetworkInfo(networkType, 0, getNetworkTypeName(networkType), "");
- info.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null);
- info.setIsAvailable(true);
- nc = new NetworkCapabilities();
+ if (nai == null) {
+ return makeFakeNetworkInfo(networkType, uid);
}
- filterNetworkInfo(info, nc, uid, false);
- return info;
+ return filterNetworkInfo(nai.networkInfo, networkType, nai.networkCapabilities, uid,
+ false);
}
@Override
@@ -1579,27 +1583,23 @@
if (getVpnUnderlyingNetworks(uid) != null) {
// A VPN is active, so we may need to return one of its underlying networks. This
// information is not available in LegacyTypeTracker, so we have to get it from
- // getUnfilteredActiveNetworkState.
- final NetworkState state = getUnfilteredActiveNetworkState(uid);
- if (state.networkInfo != null && state.networkInfo.getType() == networkType) {
- filterNetworkStateForUid(state, uid, false);
- return state.networkInfo;
+ // getNetworkAgentInfoForUid.
+ final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid);
+ if (nai == null) return null;
+ final NetworkInfo networkInfo = getFilteredNetworkInfo(nai, uid, false);
+ if (networkInfo.getType() == networkType) {
+ return networkInfo;
}
}
- return getFilteredNetworkInfo(networkType, uid);
+ return getFilteredNetworkInfoForType(networkType, uid);
}
@Override
public NetworkInfo getNetworkInfoForUid(Network network, int uid, boolean ignoreBlocked) {
enforceAccessPermission();
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
- if (nai != null) {
- final NetworkState state = nai.getNetworkState();
- filterNetworkStateForUid(state, uid, ignoreBlocked);
- return state.networkInfo;
- } else {
- return null;
- }
+ if (nai == null) return null;
+ return getFilteredNetworkInfo(nai, uid, ignoreBlocked);
}
@Override
@@ -1627,10 +1627,10 @@
return null;
}
final int uid = mDeps.getCallingUid();
- if (!isNetworkWithCapabilitiesBlocked(nai.networkCapabilities, uid, false)) {
- return nai.network;
+ if (isNetworkWithCapabilitiesBlocked(nai.networkCapabilities, uid, false)) {
+ return null;
}
- return null;
+ return nai.network;
}
@Override
@@ -1719,9 +1719,9 @@
public LinkProperties getActiveLinkProperties() {
enforceAccessPermission();
final int uid = mDeps.getCallingUid();
- NetworkState state = getUnfilteredActiveNetworkState(uid);
- if (state.linkProperties == null) return null;
- return linkPropertiesRestrictedForCallerPermissions(state.linkProperties,
+ NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid);
+ if (nai == null) return null;
+ return linkPropertiesRestrictedForCallerPermissions(nai.linkProperties,
Binder.getCallingPid(), uid);
}
@@ -1886,27 +1886,49 @@
}
}
+ // TODO: Consider delete this function or turn it into a no-op method.
@Override
public NetworkState[] getAllNetworkState() {
// This contains IMSI details, so make sure the caller is privileged.
PermissionUtils.enforceNetworkStackPermission(mContext);
final ArrayList<NetworkState> result = new ArrayList<>();
- for (Network network : getAllNetworks()) {
- final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
- // TODO: Consider include SUSPENDED networks.
+ for (NetworkStateSnapshot snapshot : getAllNetworkStateSnapshot()) {
+ // NetworkStateSnapshot doesn't contain NetworkInfo, so need to fetch it from the
+ // NetworkAgentInfo.
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(snapshot.network);
if (nai != null && nai.networkInfo.isConnected()) {
- // TODO (b/73321673) : NetworkState contains a copy of the
- // NetworkCapabilities, which may contain UIDs of apps to which the
- // network applies. Should the UIDs be cleared so as not to leak or
- // interfere ?
- result.add(nai.getNetworkState());
+ result.add(new NetworkState(new NetworkInfo(nai.networkInfo),
+ snapshot.linkProperties, snapshot.networkCapabilities, snapshot.network,
+ snapshot.subscriberId));
}
}
return result.toArray(new NetworkState[result.size()]);
}
@Override
+ @NonNull
+ public List<NetworkStateSnapshot> getAllNetworkStateSnapshot() {
+ // This contains IMSI details, so make sure the caller is privileged.
+ PermissionUtils.enforceNetworkStackPermission(mContext);
+
+ final ArrayList<NetworkStateSnapshot> result = new ArrayList<>();
+ for (Network network : getAllNetworks()) {
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+ // TODO: Consider include SUSPENDED networks, which should be considered as
+ // temporary shortage of connectivity of a connected network.
+ if (nai != null && nai.networkInfo.isConnected()) {
+ // TODO (b/73321673) : NetworkStateSnapshot contains a copy of the
+ // NetworkCapabilities, which may contain UIDs of apps to which the
+ // network applies. Should the UIDs be cleared so as not to leak or
+ // interfere ?
+ result.add(nai.getNetworkStateSnapshot());
+ }
+ }
+ return result;
+ }
+
+ @Override
public boolean isActiveNetworkMetered() {
enforceAccessPermission();
@@ -2384,13 +2406,6 @@
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 {
@@ -3190,16 +3205,16 @@
// 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);
}
@@ -3286,8 +3301,7 @@
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
@@ -3828,7 +3842,24 @@
removeListenRequestFromNetworks(req);
}
}
- mDefaultNetworkRequests.remove(nri);
+ if (mDefaultNetworkRequests.remove(nri)) {
+ // If this request was one of the defaults, then the UID rules need to be updated
+ // WARNING : if the app(s) for which this network request is the default are doing
+ // traffic, this will kill their connected sockets, even if an equivalent request
+ // is going to be reinstated right away ; unconnected traffic will go on the default
+ // until the new default is set, which will happen very soon.
+ // TODO : The only way out of this is to diff old defaults and new defaults, and only
+ // remove ranges for those requests that won't have a replacement
+ final NetworkAgentInfo satisfier = nri.getSatisfier();
+ if (null != satisfier) {
+ try {
+ mNetd.networkRemoveUidRanges(satisfier.network.getNetId(),
+ toUidRangeStableParcels(nri.getUids()));
+ } catch (RemoteException e) {
+ loge("Exception setting network preference default network", e);
+ }
+ }
+ }
mNetworkRequestCounter.decrementCount(nri.mUid);
mNetworkRequestInfoLogs.log("RELEASE " + nri);
@@ -4141,13 +4172,6 @@
// 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() {
@@ -4477,16 +4501,13 @@
case EVENT_SET_REQUIRE_VPN_FOR_UIDS:
handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj);
break;
- case EVENT_SET_OEM_NETWORK_PREFERENCE:
+ case EVENT_SET_OEM_NETWORK_PREFERENCE: {
final Pair<OemNetworkPreferences, IOnSetOemNetworkPreferenceListener> arg =
(Pair<OemNetworkPreferences,
IOnSetOemNetworkPreferenceListener>) msg.obj;
- try {
- handleSetOemNetworkPreference(arg.first, arg.second);
- } catch (RemoteException e) {
- loge("handleMessage.EVENT_SET_OEM_NETWORK_PREFERENCE failed", e);
- }
+ handleSetOemNetworkPreference(arg.first, arg.second);
break;
+ }
case EVENT_REPORT_NETWORK_ACTIVITY:
mNetworkActivityTracker.handleReportNetworkActivity();
break;
@@ -5244,11 +5265,20 @@
ensureAllNetworkRequestsHaveType(r);
mRequests = initializeRequests(r);
mNetworkRequestForCallback = nri.getNetworkRequestForCallback();
+ // Note here that the satisfier may have corresponded to an old request, that
+ // this code doesn't try to take over. While it is a small discrepancy in the
+ // structure of these requests, it will be fixed by the next rematch and it's
+ // not as bad as having an NRI not storing its real satisfier.
+ // Fixing this discrepancy would require figuring out in the copying code what
+ // is the new request satisfied by this, which is a bit complex and not very
+ // useful as no code is using it until rematch fixes it.
+ mSatisfier = nri.mSatisfier;
mMessenger = nri.mMessenger;
mBinder = nri.mBinder;
mPid = nri.mPid;
mUid = nri.mUid;
mPendingIntent = nri.mPendingIntent;
+ mNetworkRequestCounter.incrementCountOrThrow(mUid);
mCallingAttributionTag = nri.mCallingAttributionTag;
}
@@ -5295,6 +5325,8 @@
public String toString() {
return "uid/pid:" + mUid + "/" + mPid + " active request Id: "
+ (mActiveRequest == null ? null : mActiveRequest.requestId)
+ + " callback request Id: "
+ + mNetworkRequestForCallback.requestId
+ " " + mRequests
+ (mPendingIntent == null ? "" : " to trigger " + mPendingIntent);
}
@@ -7151,7 +7183,7 @@
toUidRangeStableParcels(nri.getUids()));
}
} catch (RemoteException | ServiceSpecificException e) {
- loge("Exception setting OEM network preference default network :" + e);
+ loge("Exception setting app default network", e);
}
}
@@ -7206,13 +7238,13 @@
private static class NetworkReassignment {
static class RequestReassignment {
@NonNull public final NetworkRequestInfo mNetworkRequestInfo;
- @NonNull public final NetworkRequest mOldNetworkRequest;
- @NonNull public final NetworkRequest mNewNetworkRequest;
+ @Nullable public final NetworkRequest mOldNetworkRequest;
+ @Nullable public final NetworkRequest mNewNetworkRequest;
@Nullable public final NetworkAgentInfo mOldNetwork;
@Nullable public final NetworkAgentInfo mNewNetwork;
RequestReassignment(@NonNull final NetworkRequestInfo networkRequestInfo,
- @NonNull final NetworkRequest oldNetworkRequest,
- @NonNull final NetworkRequest newNetworkRequest,
+ @Nullable final NetworkRequest oldNetworkRequest,
+ @Nullable final NetworkRequest newNetworkRequest,
@Nullable final NetworkAgentInfo oldNetwork,
@Nullable final NetworkAgentInfo newNetwork) {
mNetworkRequestInfo = networkRequestInfo;
@@ -7223,7 +7255,9 @@
}
public String toString() {
- return mNetworkRequestInfo.mRequests.get(0).requestId + " : "
+ final NetworkRequest requestToShow = null != mNewNetworkRequest
+ ? mNewNetworkRequest : mNetworkRequestInfo.mRequests.get(0);
+ return requestToShow.requestId + " : "
+ (null != mOldNetwork ? mOldNetwork.network.getNetId() : "null")
+ " → " + (null != mNewNetwork ? mNewNetwork.network.getNetId() : "null");
}
@@ -7236,7 +7270,7 @@
}
void addRequestReassignment(@NonNull final RequestReassignment reassignment) {
- if (!Build.IS_USER) {
+ if (Build.IS_DEBUGGABLE) {
// The code is never supposed to add two reassignments of the same request. Make
// sure this stays true, but without imposing this expensive check on all
// reassignments on all user devices.
@@ -7283,14 +7317,14 @@
}
private void updateSatisfiersForRematchRequest(@NonNull final NetworkRequestInfo nri,
- @NonNull final NetworkRequest previousRequest,
- @NonNull final NetworkRequest newRequest,
+ @Nullable final NetworkRequest previousRequest,
+ @Nullable final NetworkRequest newRequest,
@Nullable final NetworkAgentInfo previousSatisfier,
@Nullable final NetworkAgentInfo newSatisfier,
final long now) {
if (null != newSatisfier && mNoServiceNetwork != newSatisfier) {
if (VDBG) log("rematch for " + newSatisfier.toShortString());
- if (null != previousSatisfier && mNoServiceNetwork != previousSatisfier) {
+ if (null != previousRequest && null != previousSatisfier) {
if (VDBG || DDBG) {
log(" accepting network in place of " + previousSatisfier.toShortString());
}
@@ -7307,12 +7341,13 @@
newSatisfier.unlingerRequest(NetworkRequest.REQUEST_ID_NONE);
}
+ // if newSatisfier is not null, then newRequest may not be null.
newSatisfier.unlingerRequest(newRequest.requestId);
if (!newSatisfier.addRequest(newRequest)) {
Log.wtf(TAG, "BUG: " + newSatisfier.toShortString() + " already has "
+ newRequest);
}
- } else if (null != previousSatisfier) {
+ } else if (null != previousRequest && null != previousSatisfier) {
if (DBG) {
log("Network " + previousSatisfier.toShortString() + " stopped satisfying"
+ " request " + previousRequest.requestId);
@@ -7910,7 +7945,8 @@
*
* 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<>();
@@ -7924,7 +7960,7 @@
defaultNetworks.add(nai.network);
}
}
- return defaultNetworks.toArray(new Network[0]);
+ return defaultNetworks;
}
/**
@@ -7949,8 +7985,8 @@
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) {
}
}
@@ -8269,24 +8305,16 @@
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: {
@@ -8350,10 +8378,13 @@
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;
}
}
@@ -9030,23 +9061,27 @@
private void handleSetOemNetworkPreference(
@NonNull final OemNetworkPreferences preference,
- @NonNull final IOnSetOemNetworkPreferenceListener listener) throws RemoteException {
+ @Nullable final IOnSetOemNetworkPreferenceListener listener) {
Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
if (DBG) {
log("set OEM network preferences :" + preference.toString());
}
final ArraySet<NetworkRequestInfo> nris =
new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(preference);
- updateDefaultNetworksForOemNetworkPreference(nris);
+ replaceDefaultNetworkRequestsForPreference(nris);
mOemNetworkPreferences = preference;
// TODO http://b/176496396 persist data to shared preferences.
if (null != listener) {
- listener.onComplete();
+ try {
+ listener.onComplete();
+ } catch (RemoteException e) {
+ loge("handleMessage.EVENT_SET_OEM_NETWORK_PREFERENCE failed", e);
+ }
}
}
- private void updateDefaultNetworksForOemNetworkPreference(
+ private void replaceDefaultNetworkRequestsForPreference(
@NonNull final Set<NetworkRequestInfo> nris) {
// Pass in a defensive copy as this collection will be updated on remove.
handleRemoveNetworkRequests(new ArraySet<>(mDefaultNetworkRequests));
@@ -9058,10 +9093,10 @@
mDefaultNetworkRequests.addAll(nris);
final ArraySet<NetworkRequestInfo> perAppCallbackRequestsToUpdate =
getPerAppCallbackRequestsToUpdate();
- handleRemoveNetworkRequests(perAppCallbackRequestsToUpdate);
final ArraySet<NetworkRequestInfo> nrisToRegister = new ArraySet<>(nris);
nrisToRegister.addAll(
createPerAppCallbackRequestsToRegister(perAppCallbackRequestsToUpdate));
+ handleRemoveNetworkRequests(perAppCallbackRequestsToUpdate);
handleRegisterNetworkRequests(nrisToRegister);
}
@@ -9132,6 +9167,14 @@
return callbackRequestsToRegister;
}
+ private static void setNetworkRequestUids(@NonNull final List<NetworkRequest> requests,
+ @NonNull final Set<UidRange> uids) {
+ final Set<UidRange> ranges = new ArraySet<>(uids);
+ for (final NetworkRequest req : requests) {
+ req.networkCapabilities.setUids(ranges);
+ }
+ }
+
/**
* Class used to generate {@link NetworkRequestInfo} based off of {@link OemNetworkPreferences}.
*/
@@ -9208,7 +9251,11 @@
+ " called with invalid preference of " + preference);
}
- setOemNetworkRequestUids(requests, uids);
+ final ArraySet ranges = new ArraySet<Integer>();
+ for (final int uid : uids) {
+ ranges.add(new UidRange(uid, uid));
+ }
+ setNetworkRequestUids(requests, ranges);
return new NetworkRequestInfo(requests);
}
@@ -9241,16 +9288,5 @@
netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName());
return netCap;
}
-
- private void setOemNetworkRequestUids(@NonNull final List<NetworkRequest> requests,
- @NonNull final Set<Integer> uids) {
- final Set<UidRange> ranges = new ArraySet<>();
- for (final int uid : uids) {
- ranges.add(new UidRange(uid, uid));
- }
- for (final NetworkRequest req : requests) {
- req.networkCapabilities.setUids(ranges);
- }
- }
}
}
diff --git a/services/core/java/com/android/server/ConnectivityServiceInitializer.java b/services/core/java/com/android/server/ConnectivityServiceInitializer.java
index 097441f..b992208 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_NORMAL;
import android.content.Context;
-import android.net.INetworkStatsService;
-import android.os.ServiceManager;
import android.util.Log;
/**
@@ -37,7 +35,7 @@
// 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 @@
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 233a50d..27b648e 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_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.AppOpsManager;
import android.app.IActivityManager;
import android.app.KeyguardManager;
+import android.app.PendingIntent;
import android.app.admin.SecurityLog;
import android.app.usage.StorageStatsManager;
import android.content.BroadcastReceiver;
@@ -573,6 +578,12 @@
*/
private static final int PBKDF2_HASH_ROUNDS = 1024;
+ private static final String ANR_DELAY_MILLIS_DEVICE_CONFIG_KEY =
+ "anr_delay_millis";
+
+ private static final String ANR_DELAY_NOTIFY_EXTERNAL_STORAGE_SERVICE_DEVICE_CONFIG_KEY =
+ "anr_delay_notify_external_storage_service";
+
/**
* Mounted OBB tracking information. Used to track the current state of all
* OBBs.
@@ -943,25 +954,51 @@
}
}
- // TODO(b/170486601): Check transcoding status based on events pushed from the MediaProvider
private class ExternalStorageServiceAnrController implements AnrController {
@Override
public long getAnrDelayMillis(String packageName, int uid) {
- int delay = SystemProperties.getInt("sys.fuse.transcode_anr_delay", 0);
- Log.d(TAG, "getAnrDelayMillis: " + packageName + ". Delaying for " + delay + "ms");
+ if (!isAppIoBlocked(uid)) {
+ return 0;
+ }
+
+ int delay = DeviceConfig.getInt(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+ ANR_DELAY_MILLIS_DEVICE_CONFIG_KEY, 0);
+ Slog.v(TAG, "getAnrDelayMillis for " + packageName + ". " + delay + "ms");
return delay;
}
@Override
public void onAnrDelayStarted(String packageName, int uid) {
- Log.d(TAG, "onAnrDelayStarted: " + packageName);
+ if (!isAppIoBlocked(uid)) {
+ return;
+ }
+
+ boolean notifyExternalStorageService = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+ ANR_DELAY_NOTIFY_EXTERNAL_STORAGE_SERVICE_DEVICE_CONFIG_KEY, true);
+ if (notifyExternalStorageService) {
+ Slog.d(TAG, "onAnrDelayStarted for " + packageName
+ + ". Notifying external storage service");
+ try {
+ mStorageSessionController.notifyAnrDelayStarted(packageName, uid, 0 /* tid */,
+ StorageManager.APP_IO_BLOCKED_REASON_TRANSCODING);
+ } catch (ExternalStorageServiceException e) {
+ Slog.e(TAG, "Failed to notify ANR delay started for " + packageName, e);
+ }
+ } else {
+ // TODO(b/170973510): Implement framework spinning dialog for ANR delay
+ }
}
@Override
public boolean onAnrDelayCompleted(String packageName, int uid) {
- boolean show = SystemProperties.getBoolean("sys.fuse.transcode_anr_dialog_show", true);
- Log.d(TAG, "onAnrDelayCompleted: " + packageName + ". Show: " + show);
- return show;
+ if (isAppIoBlocked(uid)) {
+ Slog.d(TAG, "onAnrDelayCompleted for " + packageName + ". Showing ANR dialog...");
+ return true;
+ } else {
+ Slog.d(TAG, "onAnrDelayCompleted for " + packageName + ". Skipping ANR dialog...");
+ return false;
+ }
}
}
@@ -3371,6 +3408,54 @@
}
}
+ /**
+ * Returns PendingIntent which can be used by Apps with MANAGE_EXTERNAL_STORAGE permission
+ * to launch the manageSpaceActivity of the App specified by packageName.
+ */
+ @Override
+ @Nullable
+ public PendingIntent getManageSpaceActivityIntent(
+ @NonNull String packageName, int requestCode) {
+ // Only Apps with MANAGE_EXTERNAL_STORAGE permission should be able to call this API.
+ enforcePermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE);
+
+ // We want to call the manageSpaceActivity as a SystemService and clear identity
+ // of the calling App
+ int originalUid = Binder.getCallingUidOrThrow();
+ long token = Binder.clearCallingIdentity();
+
+ try {
+ ApplicationInfo appInfo = mIPackageManager.getApplicationInfo(packageName, 0,
+ UserHandle.getUserId(originalUid));
+ if (appInfo == null) {
+ throw new IllegalArgumentException(
+ "Invalid packageName");
+ }
+ if (appInfo.manageSpaceActivityName == null) {
+ Log.i(TAG, packageName + " doesn't have a manageSpaceActivity");
+ return null;
+ }
+ Context targetAppContext = mContext.createPackageContext(packageName, 0);
+
+ Intent intent = new Intent(Intent.ACTION_DEFAULT);
+ intent.setClassName(packageName,
+ appInfo.manageSpaceActivityName);
+ intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+
+ PendingIntent activity = PendingIntent.getActivity(targetAppContext, requestCode,
+ intent,
+ FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE);
+ return activity;
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalArgumentException(
+ "packageName not found");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
/** Not thread safe */
class AppFuseMountScope extends AppFuseBridge.MountScope {
private boolean mMounted = false;
@@ -4637,5 +4722,19 @@
Binder.restoreCallingIdentity(token);
}
}
+
+ @Override
+ public List<String> getPrimaryVolumeIds() {
+ final List<String> primaryVolumeIds = new ArrayList<>();
+ synchronized (mLock) {
+ for (int i = 0; i < mVolumes.size(); i++) {
+ final VolumeInfo vol = mVolumes.valueAt(i);
+ if (vol.isPrimary()) {
+ primaryVolumeIds.add(vol.getId());
+ }
+ }
+ }
+ return primaryVolumeIds;
+ }
}
}
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 8d5d3d9..ad2f524 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -35,6 +35,7 @@
import android.net.vcn.IVcnStatusCallback;
import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
import android.net.vcn.VcnConfig;
+import android.net.vcn.VcnManager;
import android.net.vcn.VcnManager.VcnErrorCode;
import android.net.vcn.VcnUnderlyingNetworkPolicy;
import android.net.wifi.WifiInfo;
@@ -724,6 +725,26 @@
}
}
+ private boolean isCallbackPermissioned(
+ @NonNull VcnStatusCallbackInfo cbInfo, @NonNull ParcelUuid subgroup) {
+ if (!subgroup.equals(cbInfo.mSubGroup)) {
+ return false;
+ }
+
+ if (!mLastSnapshot.packageHasPermissionsForSubscriptionGroup(subgroup, cbInfo.mPkgName)) {
+ return false;
+ }
+
+ if (!mLocationPermissionChecker.checkLocationPermission(
+ cbInfo.mPkgName,
+ "VcnStatusCallback" /* featureId */,
+ cbInfo.mUid,
+ null /* message */)) {
+ return false;
+ }
+ return true;
+ }
+
/** Registers the provided callback for receiving VCN status updates. */
@Override
public void registerVcnStatusCallback(
@@ -758,6 +779,27 @@
}
mRegisteredStatusCallbacks.put(cbBinder, cbInfo);
+
+ // now that callback is registered, send it the VCN's current status
+ final VcnConfig vcnConfig = mConfigs.get(subGroup);
+ final Vcn vcn = mVcns.get(subGroup);
+ final int vcnStatus;
+ if (vcnConfig == null || !isCallbackPermissioned(cbInfo, subGroup)) {
+ vcnStatus = VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED;
+ } else if (vcn == null) {
+ vcnStatus = VcnManager.VCN_STATUS_CODE_INACTIVE;
+ } else if (vcn.isActive()) {
+ vcnStatus = VcnManager.VCN_STATUS_CODE_ACTIVE;
+ } else {
+ // TODO(b/181789060): create Vcn.getStatus() and Log.WTF() for unknown status
+ vcnStatus = VcnManager.VCN_STATUS_CODE_SAFE_MODE;
+ }
+
+ try {
+ cbInfo.mCallback.onVcnStatusChanged(vcnStatus);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "VcnStatusCallback threw on VCN status change", e);
+ }
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -806,26 +848,6 @@
mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup");
}
- private boolean isCallbackPermissioned(@NonNull VcnStatusCallbackInfo cbInfo) {
- if (!mSubGroup.equals(cbInfo.mSubGroup)) {
- return false;
- }
-
- if (!mLastSnapshot.packageHasPermissionsForSubscriptionGroup(
- mSubGroup, cbInfo.mPkgName)) {
- return false;
- }
-
- if (!mLocationPermissionChecker.checkLocationPermission(
- cbInfo.mPkgName,
- "VcnStatusCallback" /* featureId */,
- cbInfo.mUid,
- null /* message */)) {
- return false;
- }
- return true;
- }
-
@Override
public void onEnteredSafeMode() {
synchronized (mLock) {
@@ -838,7 +860,7 @@
// Notify all registered StatusCallbacks for this subGroup
for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
- if (isCallbackPermissioned(cbInfo)) {
+ if (isCallbackPermissioned(cbInfo, mSubGroup)) {
Binder.withCleanCallingIdentity(
() ->
cbInfo.mCallback.onVcnStatusChanged(
@@ -862,7 +884,7 @@
// Notify all registered StatusCallbacks for this subGroup
for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
- if (isCallbackPermissioned(cbInfo)) {
+ if (isCallbackPermissioned(cbInfo, mSubGroup)) {
Binder.withCleanCallingIdentity(
() ->
cbInfo.mCallback.onGatewayConnectionError(
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
index 5d89bf1..56aabc20 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.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.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 @@
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 @@
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 @@
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 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 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 @@
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 @@
}
synchronized (mVpns) {
throwIfLockdownEnabled();
- mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress);
+ mVpns.get(user).startLegacyVpn(profile, null /* underlying */, egress);
}
}
@@ -396,7 +396,7 @@
}
private boolean isLockdownVpnEnabled() {
- return mKeyStore.contains(Credentials.LOCKDOWN_VPN);
+ return mVpnProfileStore.get(Credentials.LOCKDOWN_VPN) != null;
}
@Override
@@ -417,14 +417,14 @@
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 @@
return false;
}
setLockdownTracker(
- new LockdownVpnTracker(mContext, mHandler, mKeyStore, vpn, profile));
+ new LockdownVpnTracker(mContext, mHandler, vpn, profile));
}
return true;
@@ -495,7 +495,7 @@
return false;
}
- return vpn.startAlwaysOnVpn(mKeyStore);
+ return vpn.startAlwaysOnVpn();
}
}
@@ -510,7 +510,7 @@
logw("User " + userId + " has no Vpn configuration");
return false;
}
- return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore);
+ return vpn.isAlwaysOnPackageSupported(packageName);
}
}
@@ -531,11 +531,11 @@
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 @@
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 @@
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 @@
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 @@
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 d998ebb..277cb8c 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.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 @@
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 @@
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 e8a4fa2..f409d71 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -92,6 +92,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALLOWLISTS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
@@ -104,7 +105,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALLOWLISTS;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP;
@@ -2684,6 +2684,11 @@
}
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
@@ -4963,7 +4968,7 @@
}
@Override
- public List<ResolveInfo> queryIntentComponentsForIntentSender(
+ public ParceledListSlice<ResolveInfo> queryIntentComponentsForIntentSender(
IIntentSender pendingResult, int matchFlags) {
enforceCallingPermission(Manifest.permission.GET_INTENT_SENDER_INTENT,
"queryIntentComponentsForIntentSender()");
@@ -4981,15 +4986,15 @@
final int userId = res.key.userId;
switch (res.key.type) {
case ActivityManager.INTENT_SENDER_ACTIVITY:
- return mContext.getPackageManager().queryIntentActivitiesAsUser(
- intent, matchFlags, userId);
+ return new ParceledListSlice<>(mContext.getPackageManager()
+ .queryIntentActivitiesAsUser(intent, matchFlags, userId));
case ActivityManager.INTENT_SENDER_SERVICE:
case ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE:
- return mContext.getPackageManager().queryIntentServicesAsUser(
- intent, matchFlags, userId);
+ return new ParceledListSlice<>(mContext.getPackageManager()
+ .queryIntentServicesAsUser(intent, matchFlags, userId));
case ActivityManager.INTENT_SENDER_BROADCAST:
- return mContext.getPackageManager().queryBroadcastReceiversAsUser(
- intent, matchFlags, userId);
+ return new ParceledListSlice<>(mContext.getPackageManager()
+ .queryBroadcastReceiversAsUser(intent, matchFlags, userId));
default: // ActivityManager.INTENT_SENDER_ACTIVITY_RESULT
throw new IllegalStateException("Unsupported intent sender type: " + res.key.type);
}
@@ -6099,6 +6104,10 @@
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 c971bd2..5ad77a3 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 @@
private int mTaskId;
private boolean mIsTaskOverlay;
private boolean mIsLockTask;
+ private boolean mAsync;
private BroadcastOptions mBroadcastOptions;
final boolean mDumping;
@@ -342,6 +343,7 @@
mTaskId = INVALID_TASK_ID;
mIsTaskOverlay = false;
mIsLockTask = false;
+ mAsync = false;
mBroadcastOptions = null;
return Intent.parseCommandArgs(this, new Intent.CommandOptionHandler() {
@@ -406,6 +408,8 @@
mBroadcastOptions = BroadcastOptions.makeBasic();
}
mBroadcastOptions.setBackgroundActivityStartsAllowed(true);
+ } else if (opt.equals("--async")) {
+ mAsync = true;
} else {
return false;
}
@@ -756,7 +760,9 @@
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 @@
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 167c2b1..ce2852c 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.State;
import android.hardware.power.stats.StateResidency;
import android.hardware.power.stats.StateResidencyResult;
+import android.net.ConnectivityManager;
import android.net.INetworkManagementEventObserver;
+import android.net.Network;
import android.net.NetworkCapabilities;
import android.os.BatteryStats;
import android.os.BatteryStatsInternal;
@@ -77,8 +82,10 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.ParseUtils;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.net.module.util.NetworkCapabilitiesUtils;
import com.android.server.LocalServices;
import com.android.server.Watchdog;
+import com.android.server.connectivity.DataConnectionStats;
import com.android.server.net.BaseNetworkObserver;
import com.android.server.pm.UserManagerInternal;
@@ -291,6 +298,23 @@
return builder.toString();
}
+ private ConnectivityManager.NetworkCallback mNetworkCallback =
+ new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onCapabilitiesChanged(@NonNull Network network,
+ @NonNull NetworkCapabilities networkCapabilities) {
+ final String state = networkCapabilities.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ ? "CONNECTED" : "SUSPENDED";
+ noteConnectivityChanged(NetworkCapabilitiesUtils.getDisplayTransport(
+ networkCapabilities.getTransportTypes()), state);
+ }
+
+ @Override
+ public void onLost(Network network) {
+ noteConnectivityChanged(-1, "DISCONNECTED");
+ }
+ };
+
BatteryStatsService(Context context, File systemDir, Handler handler) {
// BatteryStatsImpl expects the ActivityManagerService handler, so pass that one through.
mContext = context;
@@ -330,8 +354,10 @@
mWorker.systemServicesReady();
final INetworkManagementService nms = INetworkManagementService.Stub.asInterface(
ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
+ final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
try {
nms.registerObserver(mActivityChangeObserver);
+ cm.registerDefaultNetworkCallback(mNetworkCallback);
} catch (RemoteException e) {
Slog.e(TAG, "Could not register INetworkManagement event observer " + e);
}
@@ -346,6 +372,9 @@
}
Watchdog.getInstance().addMonitor(this);
+
+ final DataConnectionStats dataConnectionStats = new DataConnectionStats(mContext, mHandler);
+ dataConnectionStats.startMonitoring();
}
private final class LocalService extends BatteryStatsInternal {
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 2906193..06cacc7 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.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.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 @@
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 f43c7f6..2c8794d 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.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.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 @@
final long origId = Binder.clearCallingIdentity();
try {
+ if (!TextUtils.equals(cpr.appInfo.packageName, callingPackage)) {
+ // Report component used since a content provider is being bound.
+ mService.mUsageStatsService.reportEvent(
+ cpr.appInfo.packageName, userId, Event.APP_COMPONENT_USED);
+ }
+
// Content provider is now in use, its package can't be stopped.
try {
checkTime(startTime,
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 3258f8a..d03a47a 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -329,6 +329,22 @@
info.append("Package is ").append((int) (loadingProgress * 100)).append("% loaded.\n");
}
+ // Retrieve controller with max ANR delay from AnrControllers
+ // Note that we retrieve the controller before dumping stacks because dumping stacks can
+ // take a few seconds, after which the cause of the ANR delay might have completed and
+ // there might no longer be a valid ANR controller to cancel the dialog in that case
+ AnrController anrController = mService.mActivityTaskManager.getAnrController(aInfo);
+ long anrDialogDelayMs = 0;
+ if (anrController != null) {
+ String packageName = aInfo.packageName;
+ int uid = aInfo.uid;
+ anrDialogDelayMs = anrController.getAnrDelayMillis(packageName, uid);
+ // Might execute an async binder call to a system app to show an interim
+ // ANR progress UI
+ anrController.onAnrDelayStarted(packageName, uid);
+ Slog.i(TAG, "ANR delay of " + anrDialogDelayMs + "ms started for " + packageName);
+ }
+
StringBuilder report = new StringBuilder();
report.append(MemoryPressureUtil.currentPsiState());
ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
@@ -417,20 +433,6 @@
return;
}
- // Retrieve max ANR delay from AnrControllers without the mService lock since the
- // controllers might in turn call into apps
- AnrController anrController = mService.mActivityTaskManager.getAnrController(aInfo);
- long anrDialogDelayMs = 0;
- if (anrController != null) {
- String packageName = aInfo.packageName;
- int uid = aInfo.uid;
- anrDialogDelayMs = anrController.getAnrDelayMillis(packageName, uid);
- // Might execute an async binder call to a system app to show an interim
- // ANR progress UI
- anrController.onAnrDelayStarted(packageName, uid);
- Slog.i(TAG, "ANR delay of " + anrDialogDelayMs + "ms started for " + packageName);
- }
-
synchronized (mService) {
// mBatteryStatsService can be null if the AMS is constructed with injector only. This
// will only happen in tests.
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 3ab95d1..9cd9902 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 @@
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 44dcc20..11125dd 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 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 @@
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 @@
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 @@
@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 @@
// 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 @@
// 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 @@
mFile.failWrite(stream);
}
}
+ mHistoricalRegistry.mDiscreteRegistry.writeAndClearAccessHistory();
}
static class Shell extends ShellCommand {
@@ -6115,6 +6126,7 @@
"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 0000000..7699045
--- /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 17fd32c..1c43fed 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_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.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.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.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.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 @@
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 @@
HistoricalRegistry(@NonNull Object lock) {
mInMemoryLock = lock;
+ mDiscreteRegistry = new DiscreteRegistry(lock);
}
HistoricalRegistry(@NonNull HistoricalRegistry other) {
@@ -352,36 +355,49 @@
}
}
- 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 @@
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 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 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.
- 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);
}
+
+ if ((historyFlags & HISTORY_FLAG_AGGREGATE) != 0) {
+ 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,
+ 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 @@
}
persistPendingHistory(pendingWrites);
}
+ mDiscreteRegistry.writeAndClearAccessHistory();
}
private void persistPendingHistory(@NonNull List<HistoricalOps> pendingWrites) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f5b9417..8363c9d2 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -6958,7 +6958,10 @@
private void onSetVolumeIndexOnDevice(@NonNull DeviceVolumeUpdate update) {
final VolumeStreamState streamState = mStreamStates[update.mStreamType];
if (update.hasVolumeIndex()) {
- final int index = update.getVolumeIndex();
+ int index = update.getVolumeIndex();
+ if (!checkSafeMediaVolume(update.mStreamType, index, update.mDevice)) {
+ index = safeMediaVolumeIndex(update.mDevice);
+ }
streamState.setIndex(index, update.mDevice, update.mCaller,
// trusted as index is always validated before message is posted
true /*hasModifyAudioSettings*/);
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index e19745e..050b28b 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.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 @@
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 @@
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 00a4e43..a888209 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 @@
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 @@
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 @@
"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 5cd0bbf..d9e21a8 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 @@
/**
* Checks if any of the publicly defined strengths are set.
*
+ * @param authenticators composed of one or more values from {@link Authenticators}
+ * @return true if biometric authentication is allowed.
+ */
+ static boolean isBiometricRequested(@Authenticators.Types int authenticators) {
+ return getPublicBiometricStrength(authenticators) != 0;
+ }
+
+ /**
+ * Checks if any of the publicly defined strengths are set.
+ *
* @param promptInfo should be first processed by
* {@link #combineAuthenticatorBundles(PromptInfo)}
* @return true if biometric authentication is allowed.
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 42fcd44..088249e 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -52,6 +52,7 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Slog;
@@ -59,6 +60,7 @@
import android.view.autofill.AutofillManagerInternal;
import android.widget.Toast;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.UiThread;
@@ -163,6 +165,10 @@
private static final boolean IS_EMULATOR =
SystemProperties.getBoolean("ro.kernel.qemu", false);
+ // DeviceConfig properties
+ private static final String PROPERTY_SHOW_ACCESS_NOTIFICATIONS = "show_access_notifications";
+ private static final boolean DEFAULT_SHOW_ACCESS_NOTIFICATIONS = true;
+
private final ActivityManagerInternal mAmInternal;
private final IUriGrantsManager mUgm;
private final UriGrantsManagerInternal mUgmInternal;
@@ -176,8 +182,14 @@
private HostClipboardMonitor mHostClipboardMonitor = null;
private Thread mHostMonitorThread = null;
+ @GuardedBy("mLock")
private final SparseArray<PerUserClipboard> mClipboards = new SparseArray<>();
+ @GuardedBy("mLock")
+ private boolean mShowAccessNotifications = DEFAULT_SHOW_ACCESS_NOTIFICATIONS;
+
+ private final Object mLock = new Object();
+
/**
* Instantiates the clipboard.
*/
@@ -204,7 +216,7 @@
new ClipData("host clipboard",
new String[]{"text/plain"},
new ClipData.Item(contents));
- synchronized(mClipboards) {
+ synchronized (mLock) {
setPrimaryClipInternal(getClipboard(0), clip,
android.os.Process.SYSTEM_UID, null);
}
@@ -213,6 +225,10 @@
mHostMonitorThread = new Thread(mHostClipboardMonitor);
mHostMonitorThread.start();
}
+
+ updateConfig();
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CLIPBOARD,
+ getContext().getMainExecutor(), properties -> updateConfig());
}
@Override
@@ -222,11 +238,18 @@
@Override
public void onUserStopped(@NonNull TargetUser user) {
- synchronized (mClipboards) {
+ synchronized (mLock) {
mClipboards.remove(user.getUserIdentifier());
}
}
+ private void updateConfig() {
+ synchronized (mLock) {
+ mShowAccessNotifications = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CLIPBOARD,
+ PROPERTY_SHOW_ACCESS_NOTIFICATIONS, DEFAULT_SHOW_ACCESS_NOTIFICATIONS);
+ }
+ }
+
private class ListenerInfo {
final int mUid;
final String mPackageName;
@@ -472,7 +495,7 @@
};
private PerUserClipboard getClipboard(@UserIdInt int userId) {
- synchronized (mClipboards) {
+ synchronized (mLock) {
PerUserClipboard puc = mClipboards.get(userId);
if (puc == null) {
puc = new PerUserClipboard(userId);
@@ -849,9 +872,10 @@
if (clipboard.primaryClip == null) {
return;
}
- if (Settings.Global.getInt(getContext().getContentResolver(),
- "clipboard_access_toast_enabled", 1) == 0) {
- return;
+ synchronized (mLock) {
+ if (!mShowAccessNotifications) {
+ return;
+ }
}
// Don't notify if the app accessing the clipboard is the same as the current owner.
if (UserHandle.isSameApp(uid, clipboard.primaryClipUid)) {
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index cac6cab..1d0e115 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -35,7 +35,7 @@
import android.net.NetworkInfo;
import android.net.NetworkMonitorManager;
import android.net.NetworkRequest;
-import android.net.NetworkState;
+import android.net.NetworkStateSnapshot;
import android.net.QosCallbackException;
import android.net.QosFilter;
import android.net.QosFilterParcelable;
@@ -890,15 +890,18 @@
mScore = score;
}
- public NetworkState getNetworkState() {
+ /**
+ * Return a {@link NetworkStateSnapshot} for this network.
+ */
+ @NonNull
+ public NetworkStateSnapshot getNetworkStateSnapshot() {
synchronized (this) {
// Network objects are outwardly immutable so there is no point in duplicating.
// Duplicating also precludes sharing socket factories and connection pools.
final String subscriberId = (networkAgentConfig != null)
? networkAgentConfig.subscriberId : null;
- return new NetworkState(new NetworkInfo(networkInfo),
- new LinkProperties(linkProperties),
- new NetworkCapabilities(networkCapabilities), network, subscriberId);
+ return new NetworkStateSnapshot(network, new NetworkCapabilities(networkCapabilities),
+ new LinkProperties(linkProperties), subscriberId, networkInfo.getType());
}
}
diff --git a/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java b/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java
index 816bf2b..0f5400d 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.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 @@
}
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 7ef315c..8bda532 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.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 @@
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 @@
* @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 @@
@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 67f495a..2e61ae1 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.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.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 @@
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 @@
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 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 @@
mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE));
- loadAlwaysOnPackage(keyStore);
+ loadAlwaysOnPackage();
}
/**
@@ -567,11 +587,9 @@
* </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 @@
final long oldId = Binder.clearCallingIdentity();
try {
- if (getVpnProfilePrivileged(packageName, keyStore) != null) {
+ if (getVpnProfilePrivileged(packageName) != null) {
return true;
}
} finally {
@@ -632,17 +650,15 @@
* @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 @@
* @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 @@
final VpnProfile profile;
final long oldId = Binder.clearCallingIdentity();
try {
- profile = getVpnProfilePrivileged(packageName, keyStore);
+ profile = getVpnProfilePrivileged(packageName);
} finally {
Binder.restoreCallingIdentity(oldId);
}
@@ -759,7 +774,7 @@
/** 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 @@
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 @@
/**
* 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 @@
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 @@
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 @@
* 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 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 @@
*
* @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 @@
// 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 @@
* 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 @@
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 @@
*/
@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 @@
* 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 @@
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 @@
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 @@
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 0000000..2f8aebf
--- /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 91674cd..1acd5d0 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 @@
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 @@
}
/**
- * 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)
- + "]");
+ private void loadLayoutsFromConfig() {
+ final File configFile = Environment.buildPath(
+ Environment.getVendorDirectory(), CONFIG_FILE_PATH);
+
+ if (!configFile.exists()) {
return;
}
- 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));
- 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 ce0ba9f..174d4b2 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 @@
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 @@
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 @@
// 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 2bcc35c..d6826be 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 @@
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 @@
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 @@
}
/**
- * 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 @@
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 @@
// 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 @@
if (newDisplay != oldDisplay) {
newDisplay.swapDisplaysLocked(oldDisplay);
}
- enableDisplayLocked(newDisplay, true);
+ enableDisplayLocked(newDisplay, displayLayout.isEnabled());
}
}
@@ -545,13 +540,7 @@
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 @@
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 18f39e6..ef33667 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 @@
* @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 @@
// 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 @@
* 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 int getLogicalDisplayId() {
return mLogicalDisplayId;
}
+
+ public boolean isEnabled() {
+ return mIsEnabled;
+ }
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 8d6bcad..18f7a06865 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -666,8 +666,19 @@
mSelectRequestBuffer.process();
resetSelectRequestBuffer();
- addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
- addAndStartAction(new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this));
+ List<HotplugDetectionAction> hotplugActions
+ = getActions(HotplugDetectionAction.class);
+ if (hotplugActions.isEmpty()) {
+ addAndStartAction(
+ new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
+ }
+
+ List<PowerStatusMonitorAction> powerStatusActions
+ = getActions(PowerStatusMonitorAction.class);
+ if (powerStatusActions.isEmpty()) {
+ addAndStartAction(
+ new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this));
+ }
HdmiDeviceInfo avr = getAvrDeviceInfo();
if (avr != null) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java b/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java
index 8c40424..6f7473d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java
@@ -106,9 +106,7 @@
addHandler(Constants.MESSAGE_SET_STREAM_PATH, mBystander);
addHandler(Constants.MESSAGE_STANDBY, mBystander);
addHandler(Constants.MESSAGE_SET_MENU_LANGUAGE, mBystander);
- addHandler(Constants.MESSAGE_DEVICE_VENDOR_ID, mBystander);
addHandler(Constants.MESSAGE_USER_CONTROL_RELEASED, mBystander);
- addHandler(Constants.MESSAGE_REPORT_POWER_STATUS, mBystander);
addHandler(Constants.MESSAGE_FEATURE_ABORT, mBystander);
addHandler(Constants.MESSAGE_INACTIVE_SOURCE, mBystander);
addHandler(Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS, mBystander);
@@ -133,6 +131,8 @@
addHandler(Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID, mBypasser);
addHandler(Constants.MESSAGE_GIVE_OSD_NAME, mBypasser);
addHandler(Constants.MESSAGE_SET_OSD_NAME, mBypasser);
+ addHandler(Constants.MESSAGE_DEVICE_VENDOR_ID, mBypasser);
+ addHandler(Constants.MESSAGE_REPORT_POWER_STATUS, mBypasser);
addHandler(Constants.MESSAGE_USER_CONTROL_PRESSED, mUserControlProcessedHandler);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index d41f4c7..4e8fcf7 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -159,6 +159,7 @@
import com.android.internal.content.PackageMonitor;
import com.android.internal.inputmethod.CallbackUtils;
import com.android.internal.inputmethod.IBooleanResultCallback;
+import com.android.internal.inputmethod.IIInputContentUriTokenResultCallback;
import com.android.internal.inputmethod.IInputBindResultResultCallback;
import com.android.internal.inputmethod.IInputContentUriToken;
import com.android.internal.inputmethod.IInputMethodInfoListResultCallback;
@@ -3536,6 +3537,20 @@
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)) {
@@ -5869,87 +5884,102 @@
@BinderThread
@Override
- public void setImeWindowStatus(int vis, int backDisposition) {
- mImms.setImeWindowStatus(mToken, vis, backDisposition);
+ public void setImeWindowStatus(int vis, int backDisposition,
+ IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback,
+ () -> mImms.setImeWindowStatus(mToken, vis, backDisposition));
}
@BinderThread
@Override
- public void reportStartInput(IBinder startInputToken) {
- mImms.reportStartInput(mToken, startInputToken);
+ public void reportStartInput(IBinder startInputToken, IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback,
+ () -> mImms.reportStartInput(mToken, startInputToken));
}
@BinderThread
@Override
- public IInputContentUriToken createInputContentUriToken(Uri contentUri,
- String packageName) {
- return mImms.createInputContentUriToken(mToken, contentUri, packageName);
+ public void createInputContentUriToken(Uri contentUri, String packageName,
+ IIInputContentUriTokenResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback,
+ () -> mImms.createInputContentUriToken(mToken, contentUri, packageName));
}
@BinderThread
@Override
- public void reportFullscreenMode(boolean fullscreen) {
- mImms.reportFullscreenMode(mToken, fullscreen);
+ public void reportFullscreenMode(boolean fullscreen, IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback,
+ () -> mImms.reportFullscreenMode(mToken, fullscreen));
}
@BinderThread
@Override
- public void setInputMethod(String id) {
- mImms.setInputMethod(mToken, id);
+ public void setInputMethod(String id, IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback, () -> mImms.setInputMethod(mToken, id));
}
@BinderThread
@Override
- public void setInputMethodAndSubtype(String id, InputMethodSubtype subtype) {
- mImms.setInputMethodAndSubtype(mToken, id, subtype);
+ public void setInputMethodAndSubtype(String id, InputMethodSubtype subtype,
+ IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback,
+ () -> mImms.setInputMethodAndSubtype(mToken, id, subtype));
}
@BinderThread
@Override
- public void hideMySoftInput(int flags) {
- mImms.hideMySoftInput(mToken, flags);
+ public void hideMySoftInput(int flags, IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback, () -> mImms.hideMySoftInput(mToken, flags));
}
@BinderThread
@Override
- public void showMySoftInput(int flags) {
- mImms.showMySoftInput(mToken, flags);
+ public void showMySoftInput(int flags, IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback, () -> mImms.showMySoftInput(mToken, flags));
}
@BinderThread
@Override
- public void updateStatusIcon(String packageName, @DrawableRes int iconId) {
- mImms.updateStatusIcon(mToken, packageName, iconId);
+ public void updateStatusIcon(String packageName, @DrawableRes int iconId,
+ IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback,
+ () -> mImms.updateStatusIcon(mToken, packageName, iconId));
}
@BinderThread
@Override
- public boolean switchToPreviousInputMethod() {
- return mImms.switchToPreviousInputMethod(mToken);
+ public void switchToPreviousInputMethod(IBooleanResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback, () -> mImms.switchToPreviousInputMethod(mToken));
}
@BinderThread
@Override
- public boolean switchToNextInputMethod(boolean onlyCurrentIme) {
- return mImms.switchToNextInputMethod(mToken, onlyCurrentIme);
+ public void switchToNextInputMethod(boolean onlyCurrentIme,
+ IBooleanResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback,
+ () -> mImms.switchToNextInputMethod(mToken, onlyCurrentIme));
}
@BinderThread
@Override
- public boolean shouldOfferSwitchingToNextInputMethod() {
- return mImms.shouldOfferSwitchingToNextInputMethod(mToken);
+ public void shouldOfferSwitchingToNextInputMethod(
+ IBooleanResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback,
+ () -> mImms.shouldOfferSwitchingToNextInputMethod(mToken));
}
@BinderThread
@Override
- public void notifyUserAction() {
- mImms.notifyUserAction(mToken);
+ public void notifyUserAction(IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback, () -> mImms.notifyUserAction(mToken));
}
@BinderThread
@Override
- public void applyImeVisibility(IBinder windowToken, boolean setVisible) {
- mImms.applyImeVisibility(mToken, windowToken, setVisible);
+ public void applyImeVisibility(IBinder windowToken, boolean setVisible,
+ IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback,
+ () -> mImms.applyImeVisibility(mToken, windowToken, setVisible));
}
}
}
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index 7e00fd6..364aa2c 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -193,6 +193,17 @@
0);
}
+ public int getLoadEscrowDataRetryLimit() {
+ return DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA,
+ "load_escrow_data_retry_count", DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT);
+ }
+
+ public int getLoadEscrowDataRetryIntervalSeconds() {
+ return DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA,
+ "load_escrow_data_retry_interval_seconds",
+ DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS);
+ }
+
public void reportMetric(boolean success) {
// TODO(b/179105110) design error code; and report the true value for other fields.
FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, 0, 1, 1,
@@ -251,11 +262,8 @@
List<UserInfo> users, List<UserInfo> rebootEscrowUsers) {
Objects.requireNonNull(retryHandler);
- final int retryLimit = DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA,
- "load_escrow_data_retry_count", DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT);
- final int retryIntervalInSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA,
- "load_escrow_data_retry_interval_seconds",
- DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS);
+ final int retryLimit = mInjector.getLoadEscrowDataRetryLimit();
+ final int retryIntervalInSeconds = mInjector.getLoadEscrowDataRetryIntervalSeconds();
if (attemptNumber < retryLimit) {
Slog.i(TAG, "Scheduling loadRebootEscrowData retry number: " + attemptNumber);
diff --git a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
index 9c471b8..ec80521 100644
--- a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
+++ b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
@@ -136,7 +136,7 @@
}
/** Bind to the service */
- public void bindToService(long timeOut) throws TimeoutException {
+ public void bindToService(long timeOut) throws RemoteException, TimeoutException {
if (mBinder == null || !mBinder.asBinder().isBinderAlive()) {
CountDownLatch connectionLatch = new CountDownLatch(1);
Intent intent = new Intent();
@@ -210,27 +210,25 @@
private void throwTypedException(
ParcelableException exception)
- throws IOException {
- if (exception.getCause() instanceof IOException) {
+ throws IOException, RemoteException {
+ if (exception != null && exception.getCause() instanceof IOException) {
exception.maybeRethrow(IOException.class);
- } else if (exception.getCause() instanceof IllegalStateException) {
- exception.maybeRethrow(IllegalStateException.class);
} else {
- // This should not happen. Wrap the cause in IllegalStateException so that it
- // doesn't disrupt the exception handling
- throw new IllegalStateException(exception.getCause());
+ // Wrap the exception and throw it as a RemoteException.
+ throw new RemoteException(TAG + " wrap/unwrap failed", exception,
+ true /* enableSuppression */, true /* writableStackTrace */);
}
}
private void waitForLatch(CountDownLatch latch, String reason, long timeOut)
- throws TimeoutException {
+ throws RemoteException, TimeoutException {
try {
if (!latch.await(timeOut, TimeUnit.SECONDS)) {
throw new TimeoutException("Latch wait for " + reason + " elapsed");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
- throw new IllegalStateException("Latch wait for " + reason + " interrupted");
+ throw new RemoteException("Latch wait for " + reason + " interrupted");
}
}
}
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index 3cc32be..851ea3d 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.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 @@
@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 LockdownVpnTracker(@NonNull Context context,
@NonNull Handler handler,
- @NonNull KeyStore keyStore,
@NonNull Vpn vpn,
@NonNull VpnProfile profile) {
mContext = Objects.requireNonNull(context);
@@ -140,7 +137,6 @@
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 @@
// 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 ebf1fe9..e0f5346 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_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.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 460b2f2..903652a 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 @@
try {
sealLocked();
- // Session that are staged, ready and not multi package will be installed during
- // this boot. As such, we need populate all the fields for successful installation.
- if (isMultiPackage()) {
+ // Session that are staged, committed and not multi package will be installed or
+ // restart verification during this boot. As such, we need populate all the fields
+ // for successful installation.
+ if (isMultiPackage() || !isStaged() || !isCommitted()) {
return;
}
final PackageInstallerSession root = hasParentSessionId()
? allSessions.get(getParentSessionId())
: this;
- if (root != null && root.isStagedSessionReady()) {
+ if (root != null) {
if (isApexSession()) {
validateApexInstallLocked();
} else {
diff --git a/services/core/java/com/android/server/pm/SettingsXml.java b/services/core/java/com/android/server/pm/SettingsXml.java
index ec643f5..c53fef7 100644
--- a/services/core/java/com/android/server/pm/SettingsXml.java
+++ b/services/core/java/com/android/server/pm/SettingsXml.java
@@ -83,7 +83,7 @@
@Override
public void close() throws IOException {
mWriteSection.closeCompletely();
- mXmlSerializer.endDocument();
+ mXmlSerializer.flush();
}
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
index c9067a3..b61fd8d 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
@@ -33,9 +33,9 @@
import com.android.internal.util.CollectionUtils;
import com.android.server.pm.PackageSetting;
import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState;
import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
-import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
import java.util.Arrays;
import java.util.function.Function;
@@ -169,8 +169,8 @@
}
ArraySet<String> allWebDomains = mCollector.collectAllWebDomains(pkg);
- SparseArray<DomainVerificationUserState> userStates =
- pkgState.getUserSelectionStates();
+ SparseArray<DomainVerificationInternalUserState> userStates =
+ pkgState.getUserStates();
if (userId == UserHandle.USER_ALL) {
int size = userStates.size();
if (size == 0) {
@@ -178,13 +178,13 @@
wasHeaderPrinted);
} else {
for (int index = 0; index < size; index++) {
- DomainVerificationUserState userState = userStates.valueAt(index);
+ DomainVerificationInternalUserState userState = userStates.valueAt(index);
printState(writer, pkgState, userState.getUserId(), userState, reusedSet,
allWebDomains, wasHeaderPrinted);
}
}
} else {
- DomainVerificationUserState userState = userStates.get(userId);
+ DomainVerificationInternalUserState userState = userStates.get(userId);
printState(writer, pkgState, userId, userState, reusedSet, allWebDomains,
wasHeaderPrinted);
}
@@ -192,8 +192,9 @@
boolean printState(@NonNull IndentingPrintWriter writer,
@NonNull DomainVerificationPkgState pkgState, @UserIdInt int userId,
- @Nullable DomainVerificationUserState userState, @NonNull ArraySet<String> reusedSet,
- @NonNull ArraySet<String> allWebDomains, boolean wasHeaderPrinted) {
+ @Nullable DomainVerificationInternalUserState userState,
+ @NonNull ArraySet<String> reusedSet, @NonNull ArraySet<String> allWebDomains,
+ boolean wasHeaderPrinted) {
reusedSet.clear();
reusedSet.addAll(allWebDomains);
if (userState != null) {
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
index ed37fa0..1721a18 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
@@ -132,6 +132,21 @@
/**
* Enforced when mutating user selection state inside an exposed API method.
*/
+ public boolean assertApprovedUserStateQuerent(int callingUid, @UserIdInt int callingUserId,
+ @NonNull String packageName, @UserIdInt int targetUserId) throws SecurityException {
+ if (callingUserId != targetUserId) {
+ mContext.enforcePermission(
+ Manifest.permission.INTERACT_ACROSS_USERS,
+ Binder.getCallingPid(), callingUid,
+ "Caller is not allowed to edit other users");
+ }
+
+ return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
+ }
+
+ /**
+ * Enforced when mutating user selection state inside an exposed API method.
+ */
public boolean assertApprovedUserSelector(int callingUid, @UserIdInt int callingUserId,
@Nullable String packageName, @UserIdInt int targetUserId) throws SecurityException {
if (callingUserId != targetUserId) {
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java
index c787356..4bad102 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java
@@ -32,7 +32,6 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
-import java.util.Map;
/**
* Reads and writes the old {@link android.content.pm.IntentFilterVerificationInfo} so that it can
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
index a68b3da..0c2b4c5 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
@@ -48,7 +48,7 @@
import java.util.UUID;
import java.util.function.Function;
-public interface DomainVerificationManagerInternal extends DomainVerificationManager {
+public interface DomainVerificationManagerInternal {
UUID DISABLED_ID = new UUID(0, 0);
@@ -69,8 +69,8 @@
* during the legacy transition period.
*
* TODO(b/177923646): The legacy values can be removed once the Settings API changes are
- * shipped. These values are not stable, so just deleting the constant and shifting others is
- * fine.
+ * shipped. These values are not stable, so just deleting the constant and shifting others is
+ * fine.
*/
int APPROVAL_LEVEL_LEGACY_ASK = 1;
@@ -84,14 +84,15 @@
/**
* The app has been chosen by the user through
- * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)}, indictag an explicit
- * choice to use this app to open an unverified domain.
+ * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)},
+ * indicating an explicit choice to use this app to open an unverified domain.
*/
int APPROVAL_LEVEL_SELECTION = 2;
/**
* The app is approved through the digital asset link statement being hosted at the domain
- * it is capturing. This is set through {@link #setDomainVerificationStatus(UUID, Set, int)} by
+ * it is capturing. This is set through
+ * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)} by
* the domain verification agent on device.
*/
int APPROVAL_LEVEL_VERIFIED = 3;
@@ -102,7 +103,7 @@
* declares against the digital asset link statements before allowing it to be installed.
*
* The user is still able to disable instant app link handling through
- * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}.
+ * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String, boolean)}.
*/
int APPROVAL_LEVEL_INSTANT_APP = 4;
@@ -122,7 +123,17 @@
APPROVAL_LEVEL_VERIFIED,
APPROVAL_LEVEL_INSTANT_APP
})
- @interface ApprovalLevel{}
+ @interface ApprovalLevel {
+ }
+
+ /** @see DomainVerificationManager#getDomainVerificationInfo(String) */
+ @Nullable
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.DOMAIN_VERIFICATION_AGENT,
+ android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
+ })
+ DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
+ throws NameNotFoundException;
/**
* Generate a new domain set ID to be used for attaching new packages.
@@ -173,9 +184,9 @@
/**
* Migrates verification state from a previous install to a new one. It is expected that the
* {@link PackageSetting#getDomainSetId()} already be set to the correct value, usually from
- * {@link #generateNewId()}. This will preserve {@link #STATE_SUCCESS} domains under the
- * assumption that the new package will pass the same server side config as the previous
- * package, as they have matching signatures.
+ * {@link #generateNewId()}. This will preserve {@link DomainVerificationManager#STATE_SUCCESS}
+ * domains under the assumption that the new package will pass the same server side config as
+ * the previous package, as they have matching signatures.
* <p>
* This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal
* lock. This should never be called from within the domain verification classes themselves.
@@ -229,8 +240,10 @@
* tag has already been entered.
* <p>
* This is <b>only</b> for restore, and will override package states, ignoring if their {@link
- * DomainVerificationInfo#getIdentifier()}s match. It's expected that any restored domains marked
- * as success verify against the server correctly, although the verification agent may decide to
+ * DomainVerificationInfo#getIdentifier()}s match. It's expected that any restored domains
+ * marked
+ * as success verify against the server correctly, although the verification agent may decide
+ * to
* re-verify them when it gets the chance.
*/
/*
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
index 6f28107..a7a52e0 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
@@ -23,29 +23,29 @@
import android.content.pm.verify.domain.DomainOwner;
import android.content.pm.verify.domain.DomainSet;
import android.content.pm.verify.domain.DomainVerificationInfo;
+import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException;
-import android.content.pm.verify.domain.DomainVerificationManagerImpl;
-import android.content.pm.verify.domain.DomainVerificationUserSelection;
+import android.content.pm.verify.domain.DomainVerificationUserState;
import android.content.pm.verify.domain.IDomainVerificationManager;
import android.os.ServiceSpecificException;
import java.util.List;
import java.util.UUID;
-class DomainVerificationManagerStub extends IDomainVerificationManager.Stub {
+public class DomainVerificationManagerStub extends IDomainVerificationManager.Stub {
@NonNull
- private DomainVerificationService mService;
+ private final DomainVerificationService mService;
- DomainVerificationManagerStub(DomainVerificationService service) {
+ public DomainVerificationManagerStub(DomainVerificationService service) {
mService = service;
}
@NonNull
@Override
- public List<String> getValidVerificationPackageNames() {
+ public List<String> queryValidVerificationPackageNames() {
try {
- return mService.getValidVerificationPackageNames();
+ return mService.queryValidVerificationPackageNames();
} catch (Exception e) {
throw rethrow(e);
}
@@ -95,10 +95,10 @@
@Nullable
@Override
- public DomainVerificationUserSelection getDomainVerificationUserSelection(
+ public DomainVerificationUserState getDomainVerificationUserState(
String packageName, @UserIdInt int userId) {
try {
- return mService.getDomainVerificationUserSelection(packageName, userId);
+ return mService.getDomainVerificationUserState(packageName, userId);
} catch (Exception e) {
throw rethrow(e);
}
@@ -117,13 +117,13 @@
private RuntimeException rethrow(Exception exception) throws RuntimeException {
if (exception instanceof InvalidDomainSetException) {
- int packedErrorCode = DomainVerificationManagerImpl.ERROR_INVALID_DOMAIN_SET;
+ int packedErrorCode = DomainVerificationManager.ERROR_INVALID_DOMAIN_SET;
packedErrorCode |= ((InvalidDomainSetException) exception).getReason() << 16;
return new ServiceSpecificException(packedErrorCode,
((InvalidDomainSetException) exception).getPackageName());
} else if (exception instanceof NameNotFoundException) {
return new ServiceSpecificException(
- DomainVerificationManagerImpl.ERROR_NAME_NOT_FOUND);
+ DomainVerificationManager.ERROR_NAME_NOT_FOUND);
} else if (exception instanceof RuntimeException) {
return (RuntimeException) exception;
} else {
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
index c864b29..abb8d2f 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
@@ -27,9 +27,9 @@
import android.util.TypedXmlSerializer;
import com.android.server.pm.SettingsXml;
+import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState;
import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
-import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
import org.xmlpull.v1.XmlPullParserException;
@@ -157,7 +157,7 @@
UUID id = UUID.fromString(idString);
final ArrayMap<String, Integer> stateMap = new ArrayMap<>();
- final SparseArray<DomainVerificationUserState> userStates = new SparseArray<>();
+ final SparseArray<DomainVerificationInternalUserState> userStates = new SparseArray<>();
SettingsXml.ChildSection child = section.children();
while (child.moveToNext()) {
@@ -176,10 +176,10 @@
}
private static void readUserStates(@NonNull SettingsXml.ReadSection section,
- @NonNull SparseArray<DomainVerificationUserState> userStates) {
+ @NonNull SparseArray<DomainVerificationInternalUserState> userStates) {
SettingsXml.ChildSection child = section.children();
while (child.moveToNext(TAG_USER_STATE)) {
- DomainVerificationUserState userState = createUserStateFromXml(child);
+ DomainVerificationInternalUserState userState = createUserStateFromXml(child);
if (userState != null) {
userStates.put(userState.getUserId(), userState);
}
@@ -205,12 +205,12 @@
.attribute(ATTR_HAS_AUTO_VERIFY_DOMAINS,
pkgState.isHasAutoVerifyDomains())) {
writeStateMap(parentSection, pkgState.getStateMap());
- writeUserStates(parentSection, pkgState.getUserSelectionStates());
+ writeUserStates(parentSection, pkgState.getUserStates());
}
}
private static void writeUserStates(@NonNull SettingsXml.WriteSection parentSection,
- @NonNull SparseArray<DomainVerificationUserState> states) throws IOException {
+ @NonNull SparseArray<DomainVerificationInternalUserState> states) throws IOException {
int size = states.size();
if (size == 0) {
return;
@@ -245,7 +245,7 @@
* entered.
*/
@Nullable
- public static DomainVerificationUserState createUserStateFromXml(
+ public static DomainVerificationInternalUserState createUserStateFromXml(
@NonNull SettingsXml.ReadSection section) {
int userId = section.getInt(ATTR_USER_ID);
if (userId == -1) {
@@ -260,7 +260,7 @@
readEnabledHosts(child, enabledHosts);
}
- return new DomainVerificationUserState(userId, enabledHosts, allowLinkHandling);
+ return new DomainVerificationInternalUserState(userId, enabledHosts, allowLinkHandling);
}
private static void readEnabledHosts(@NonNull SettingsXml.ReadSection section,
@@ -275,7 +275,7 @@
}
public static void writeUserStateToXml(@NonNull SettingsXml.WriteSection parentSection,
- @NonNull DomainVerificationUserState userState) throws IOException {
+ @NonNull DomainVerificationInternalUserState userState) throws IOException {
try (SettingsXml.WriteSection section =
parentSection.startSection(TAG_USER_STATE)
.attribute(ATTR_USER_ID, userState.getUserId())
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index dbd7f96..e85bbe4 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -34,8 +34,9 @@
import android.content.pm.verify.domain.DomainOwner;
import android.content.pm.verify.domain.DomainVerificationInfo;
import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException;
import android.content.pm.verify.domain.DomainVerificationState;
-import android.content.pm.verify.domain.DomainVerificationUserSelection;
+import android.content.pm.verify.domain.DomainVerificationUserState;
import android.content.pm.verify.domain.IDomainVerificationManager;
import android.os.UserHandle;
import android.util.ArrayMap;
@@ -55,9 +56,9 @@
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.PackageSetting;
import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState;
import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
-import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyUnavailable;
@@ -208,8 +209,7 @@
}
@NonNull
- @Override
- public List<String> getValidVerificationPackageNames() {
+ public List<String> queryValidVerificationPackageNames() {
mEnforcer.assertApprovedVerifier(mConnection.getCallingUid(), mProxy);
List<String> packageNames = new ArrayList<>();
synchronized (mLock) {
@@ -272,7 +272,6 @@
}
}
- @Override
public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
int state) throws InvalidDomainSetException, NameNotFoundException {
if (state < DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED) {
@@ -314,7 +313,7 @@
int size = verifiedDomains.size();
for (int index = 0; index < size; index++) {
- removeUserSelectionsForDomain(verifiedDomains.get(index));
+ removeUserStatesForDomain(verifiedDomains.get(index));
}
}
@@ -401,12 +400,12 @@
}
}
- private void removeUserSelectionsForDomain(@NonNull String domain) {
+ private void removeUserStatesForDomain(@NonNull String domain) {
synchronized (mLock) {
final int size = mAttachedPkgStates.size();
for (int index = 0; index < size; index++) {
DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
- SparseArray<DomainVerificationUserState> array = pkgState.getUserSelectionStates();
+ SparseArray<DomainVerificationInternalUserState> array = pkgState.getUserStates();
int arraySize = array.size();
for (int arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) {
array.valueAt(arrayIndex).removeHost(domain);
@@ -415,13 +414,6 @@
}
}
- @Override
- public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
- boolean allowed) throws NameNotFoundException {
- setDomainVerificationLinkHandlingAllowed(packageName, allowed,
- mConnection.getCallingUserId());
- }
-
public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
boolean allowed, @UserIdInt int userId) throws NameNotFoundException {
if (!mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
@@ -434,7 +426,7 @@
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
- pkgState.getOrCreateUserSelectionState(userId)
+ pkgState.getOrCreateUserState(userId)
.setLinkHandlingAllowed(allowed);
}
@@ -452,11 +444,11 @@
DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(pkgStateIndex);
if (userId == UserHandle.USER_ALL) {
for (int aUserId : mConnection.getAllUserIds()) {
- pkgState.getOrCreateUserSelectionState(aUserId)
+ pkgState.getOrCreateUserState(aUserId)
.setLinkHandlingAllowed(allowed);
}
} else {
- pkgState.getOrCreateUserSelectionState(userId)
+ pkgState.getOrCreateUserState(userId)
.setLinkHandlingAllowed(allowed);
}
}
@@ -468,7 +460,7 @@
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
- pkgState.getOrCreateUserSelectionState(userId)
+ pkgState.getOrCreateUserState(userId)
.setLinkHandlingAllowed(allowed);
}
}
@@ -476,14 +468,6 @@
mConnection.scheduleWriteSettings();
}
- @Override
- public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
- @NonNull Set<String> domains, boolean enabled)
- throws InvalidDomainSetException, NameNotFoundException {
- setDomainVerificationUserSelection(domainSetId, domains, enabled,
- mConnection.getCallingUserId());
- }
-
public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
@NonNull Set<String> domains, boolean enabled, @UserIdInt int userId)
throws InvalidDomainSetException, NameNotFoundException {
@@ -500,7 +484,8 @@
DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
false /* forAutoVerify */, callingUid, userId);
- DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
+ DomainVerificationInternalUserState userState =
+ pkgState.getOrCreateUserState(userId);
// Disable other packages if approving this one. Note that this check is only done for
// enabling. This allows an escape hatch in case multiple packages somehow get selected.
@@ -540,8 +525,8 @@
continue;
}
- DomainVerificationUserState approvedUserState =
- approvedPkgState.getUserSelectionState(userId);
+ DomainVerificationInternalUserState approvedUserState =
+ approvedPkgState.getUserState(userId);
if (approvedUserState == null) {
continue;
}
@@ -623,8 +608,8 @@
if (userId == UserHandle.USER_ALL) {
for (int aUserId : mConnection.getAllUserIds()) {
- DomainVerificationUserState userState =
- pkgState.getOrCreateUserSelectionState(aUserId);
+ DomainVerificationInternalUserState userState =
+ pkgState.getOrCreateUserState(aUserId);
if (enabled) {
userState.addHosts(domains);
} else {
@@ -632,7 +617,8 @@
}
}
} else {
- DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
+ DomainVerificationInternalUserState userState =
+ pkgState.getOrCreateUserState(userId);
if (enabled) {
userState.addHosts(domains);
} else {
@@ -643,17 +629,9 @@
@Nullable
@Override
- public DomainVerificationUserSelection getDomainVerificationUserSelection(
- @NonNull String packageName) throws NameNotFoundException {
- return getDomainVerificationUserSelection(packageName,
- mConnection.getCallingUserId());
- }
-
- @Nullable
- @Override
- public DomainVerificationUserSelection getDomainVerificationUserSelection(
+ public DomainVerificationUserState getDomainVerificationUserState(
@NonNull String packageName, @UserIdInt int userId) throws NameNotFoundException {
- if (!mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
+ if (!mEnforcer.assertApprovedUserStateQuerent(mConnection.getCallingUid(),
mConnection.getCallingUserId(), packageName, userId)) {
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
@@ -673,7 +651,7 @@
Map<String, Integer> domains = new ArrayMap<>(webDomainsSize);
ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
- DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+ DomainVerificationInternalUserState userState = pkgState.getUserState(userId);
Set<String> enabledHosts = userState == null ? emptySet() : userState.getEnabledHosts();
for (int index = 0; index < webDomainsSize; index++) {
@@ -682,11 +660,11 @@
int domainState;
if (state != null && DomainVerificationManager.isStateVerified(state)) {
- domainState = DomainVerificationUserSelection.DOMAIN_STATE_VERIFIED;
+ domainState = DomainVerificationUserState.DOMAIN_STATE_VERIFIED;
} else if (enabledHosts.contains(host)) {
- domainState = DomainVerificationUserSelection.DOMAIN_STATE_SELECTED;
+ domainState = DomainVerificationUserState.DOMAIN_STATE_SELECTED;
} else {
- domainState = DomainVerificationUserSelection.DOMAIN_STATE_NONE;
+ domainState = DomainVerificationUserState.DOMAIN_STATE_NONE;
}
domains.put(host, domainState);
@@ -694,17 +672,11 @@
boolean linkHandlingAllowed = userState == null || userState.isLinkHandlingAllowed();
- return new DomainVerificationUserSelection(pkgState.getId(), packageName,
+ return new DomainVerificationUserState(pkgState.getId(), packageName,
UserHandle.of(userId), linkHandlingAllowed, domains);
}
}
- @NonNull
- @Override
- public List<DomainOwner> getOwnersForDomain(@NonNull String domain) {
- return getOwnersForDomain(domain, mConnection.getCallingUserId());
- }
-
public List<DomainOwner> getOwnersForDomain(@NonNull String domain, @UserIdInt int userId) {
mEnforcer.assertOwnerQuerent(mConnection.getCallingUid(), mConnection.getCallingUserId(),
userId);
@@ -795,7 +767,7 @@
AndroidPackage newPkg = newPkgSetting.getPkg();
ArrayMap<String, Integer> newStateMap = new ArrayMap<>();
- SparseArray<DomainVerificationUserState> newUserStates = new SparseArray<>();
+ SparseArray<DomainVerificationInternalUserState> newUserStates = new SparseArray<>();
if (oldPkgState == null || oldPkg == null || newPkg == null) {
// Should be impossible, but to be safe, continue with a new blank state instead
@@ -838,21 +810,22 @@
}
}
- SparseArray<DomainVerificationUserState> oldUserStates =
- oldPkgState.getUserSelectionStates();
+ SparseArray<DomainVerificationInternalUserState> oldUserStates =
+ oldPkgState.getUserStates();
int oldUserStatesSize = oldUserStates.size();
if (oldUserStatesSize > 0) {
ArraySet<String> newWebDomains = mCollector.collectValidAutoVerifyDomains(newPkg);
for (int oldUserStatesIndex = 0; oldUserStatesIndex < oldUserStatesSize;
oldUserStatesIndex++) {
int userId = oldUserStates.keyAt(oldUserStatesIndex);
- DomainVerificationUserState oldUserState = oldUserStates.valueAt(
+ DomainVerificationInternalUserState oldUserState = oldUserStates.valueAt(
oldUserStatesIndex);
ArraySet<String> oldEnabledHosts = oldUserState.getEnabledHosts();
ArraySet<String> newEnabledHosts = new ArraySet<>(oldEnabledHosts);
newEnabledHosts.retainAll(newWebDomains);
- DomainVerificationUserState newUserState = new DomainVerificationUserState(
- userId, newEnabledHosts, oldUserState.isLinkHandlingAllowed());
+ DomainVerificationInternalUserState newUserState =
+ new DomainVerificationInternalUserState(userId, newEnabledHosts,
+ oldUserState.isLinkHandlingAllowed());
newUserStates.put(userId, newUserState);
}
}
@@ -926,7 +899,7 @@
webDomains = mCollector.collectAllWebDomains(pkg);
}
- pkgState.getOrCreateUserSelectionState(userId).addHosts(webDomains);
+ pkgState.getOrCreateUserState(userId).addHosts(webDomains);
}
}
@@ -1295,7 +1268,7 @@
}
@Override
- public void clearUserSelections(@Nullable List<String> packageNames, @UserIdInt int userId) {
+ public void clearUserStates(@Nullable List<String> packageNames, @UserIdInt int userId) {
mEnforcer.assertInternal(mConnection.getCallingUid());
synchronized (mLock) {
if (packageNames == null) {
@@ -1545,7 +1518,7 @@
return APPROVAL_LEVEL_NONE;
}
- DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+ DomainVerificationInternalUserState userState = pkgState.getUserState(userId);
if (userState != null && !userState.isLinkHandlingAllowed()) {
if (DEBUG_APPROVAL) {
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
index a8e937c..f3d1dbb 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
@@ -29,9 +29,9 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState;
import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
-import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
import org.xmlpull.v1.XmlPullParserException;
@@ -216,21 +216,22 @@
}
}
- SparseArray<DomainVerificationUserState> oldSelectionStates =
- oldState.getUserSelectionStates();
+ SparseArray<DomainVerificationInternalUserState> oldSelectionStates =
+ oldState.getUserStates();
- SparseArray<DomainVerificationUserState> newSelectionStates =
- newState.getUserSelectionStates();
+ SparseArray<DomainVerificationInternalUserState> newSelectionStates =
+ newState.getUserStates();
- DomainVerificationUserState newUserState = newSelectionStates.get(UserHandle.USER_SYSTEM);
+ DomainVerificationInternalUserState newUserState =
+ newSelectionStates.get(UserHandle.USER_SYSTEM);
if (newUserState != null) {
ArraySet<String> newEnabledHosts = newUserState.getEnabledHosts();
- DomainVerificationUserState oldUserState =
+ DomainVerificationInternalUserState oldUserState =
oldSelectionStates.get(UserHandle.USER_SYSTEM);
boolean linkHandlingAllowed = newUserState.isLinkHandlingAllowed();
if (oldUserState == null) {
- oldUserState = new DomainVerificationUserState(UserHandle.USER_SYSTEM,
+ oldUserState = new DomainVerificationInternalUserState(UserHandle.USER_SYSTEM,
newEnabledHosts, linkHandlingAllowed);
oldSelectionStates.put(UserHandle.USER_SYSTEM, oldUserState);
} else {
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
index d083d11..94767f5 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
@@ -24,7 +24,7 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.verify.domain.DomainVerificationState;
-import android.content.pm.verify.domain.DomainVerificationUserSelection;
+import android.content.pm.verify.domain.DomainVerificationUserState;
import android.os.Binder;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -118,7 +118,7 @@
case "set-app-links":
return runSetAppLinks(commandHandler);
case "set-app-links-user-selection":
- return runSetAppLinksUserSelection(commandHandler);
+ return runSetAppLinksUserState(commandHandler);
case "set-app-links-allowed":
return runSetAppLinksAllowed(commandHandler);
}
@@ -193,7 +193,7 @@
}
// pm set-app-links-user-selection --user <USER_ID> [--package <PACKAGE>] <ENABLED> <DOMAINS>...
- private boolean runSetAppLinksUserSelection(@NonNull BasicShellCommandHandler commandHandler) {
+ private boolean runSetAppLinksUserState(@NonNull BasicShellCommandHandler commandHandler) {
Integer userId = null;
String packageName = null;
@@ -224,7 +224,7 @@
return false;
}
- userId = translateUserId(userId, "runSetAppLinksUserSelection");
+ userId = translateUserId(userId, "runSetAppLinksUserState");
String enabledString = commandHandler.getNextArgRequired();
@@ -326,7 +326,7 @@
}
if (userId != null) {
- mCallback.clearUserSelections(packageNames, userId);
+ mCallback.clearUserStates(packageNames, userId);
} else {
mCallback.clearDomainVerificationState(packageNames);
}
@@ -457,10 +457,10 @@
throws PackageManager.NameNotFoundException;
/**
- * @see DomainVerificationManager#getDomainVerificationUserSelection(String)
+ * @see DomainVerificationManager#getDomainVerificationUserState(String)
*/
@Nullable
- DomainVerificationUserSelection getDomainVerificationUserSelection(
+ DomainVerificationUserState getDomainVerificationUserState(
@NonNull String packageName, @UserIdInt int userId)
throws PackageManager.NameNotFoundException;
@@ -486,7 +486,7 @@
* Reset all the user selections for the given package names, or all package names if null
* is provided.
*/
- void clearUserSelections(@Nullable List<String> packageNames, @UserIdInt int userId);
+ void clearUserStates(@Nullable List<String> packageNames, @UserIdInt int userId);
/**
* Broadcast a verification request for the given package names, or all package names if
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationInternalUserState.java
similarity index 74%
rename from services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
rename to services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationInternalUserState.java
index 8fbb33a..aa7407c 100644
--- a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationInternalUserState.java
@@ -29,7 +29,7 @@
* when a web URL Intent is sent and the application is the highest priority for that domain.
*/
@DataClass(genSetters = true, genEqualsHashCode = true, genToString = true, genBuilder = false)
-public class DomainVerificationUserState {
+public class DomainVerificationInternalUserState {
@UserIdInt
private final int mUserId;
@@ -43,32 +43,32 @@
*/
private boolean mLinkHandlingAllowed = true;
- public DomainVerificationUserState(@UserIdInt int userId) {
+ public DomainVerificationInternalUserState(@UserIdInt int userId) {
mUserId = userId;
mEnabledHosts = new ArraySet<>();
}
- public DomainVerificationUserState addHosts(@NonNull ArraySet<String> newHosts) {
+ public DomainVerificationInternalUserState addHosts(@NonNull ArraySet<String> newHosts) {
mEnabledHosts.addAll(newHosts);
return this;
}
- public DomainVerificationUserState addHosts(@NonNull Set<String> newHosts) {
+ public DomainVerificationInternalUserState addHosts(@NonNull Set<String> newHosts) {
mEnabledHosts.addAll(newHosts);
return this;
}
- public DomainVerificationUserState removeHost(String host) {
+ public DomainVerificationInternalUserState removeHost(String host) {
mEnabledHosts.remove(host);
return this;
}
- public DomainVerificationUserState removeHosts(@NonNull ArraySet<String> newHosts) {
+ public DomainVerificationInternalUserState removeHosts(@NonNull ArraySet<String> newHosts) {
mEnabledHosts.removeAll(newHosts);
return this;
}
- public DomainVerificationUserState removeHosts(@NonNull Set<String> newHosts) {
+ public DomainVerificationInternalUserState removeHosts(@NonNull Set<String> newHosts) {
mEnabledHosts.removeAll(newHosts);
return this;
}
@@ -81,8 +81,7 @@
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm
- // /verify/domain/models/DomainVerificationUserState.java
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationInternalUserState.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -90,7 +89,7 @@
/**
- * Creates a new DomainVerificationUserState.
+ * Creates a new DomainVerificationInternalUserState.
*
* @param enabledHosts
* List of domains which have been enabled by the user. *
@@ -98,7 +97,7 @@
* Whether to allow this package to automatically open links by auto verification.
*/
@DataClass.Generated.Member
- public DomainVerificationUserState(
+ public DomainVerificationInternalUserState(
@UserIdInt int userId,
@NonNull ArraySet<String> enabledHosts,
boolean linkHandlingAllowed) {
@@ -138,7 +137,7 @@
* Whether to allow this package to automatically open links by auto verification.
*/
@DataClass.Generated.Member
- public @NonNull DomainVerificationUserState setLinkHandlingAllowed( boolean value) {
+ public @NonNull DomainVerificationInternalUserState setLinkHandlingAllowed( boolean value) {
mLinkHandlingAllowed = value;
return this;
}
@@ -149,7 +148,7 @@
// You can override field toString logic by defining methods like:
// String fieldNameToString() { ... }
- return "DomainVerificationUserState { " +
+ return "DomainVerificationInternalUserState { " +
"userId = " + mUserId + ", " +
"enabledHosts = " + mEnabledHosts + ", " +
"linkHandlingAllowed = " + mLinkHandlingAllowed +
@@ -160,13 +159,13 @@
@DataClass.Generated.Member
public boolean equals(@android.annotation.Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
- // boolean fieldNameEquals(DomainVerificationUserState other) { ... }
+ // boolean fieldNameEquals(DomainVerificationInternalUserState other) { ... }
// boolean fieldNameEquals(FieldType otherValue) { ... }
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@SuppressWarnings("unchecked")
- DomainVerificationUserState that = (DomainVerificationUserState) o;
+ DomainVerificationInternalUserState that = (DomainVerificationInternalUserState) o;
//noinspection PointlessBooleanExpression
return true
&& mUserId == that.mUserId
@@ -188,10 +187,10 @@
}
@DataClass.Generated(
- time = 1612894390039L,
+ time = 1614714563905L,
codegenVersion = "1.0.22",
- sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java",
- inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate boolean mLinkHandlingAllowed\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(java.util.Set<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true, genBuilder=false)")
+ sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationInternalUserState.java",
+ inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate boolean mLinkHandlingAllowed\npublic com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState addHosts(java.util.Set<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState removeHost(java.lang.String)\npublic com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationInternalUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
index 48099aa..a089a60 100644
--- a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.verify.domain.DomainVerificationState;
import android.util.ArrayMap;
import android.util.SparseArray;
@@ -44,9 +43,9 @@
/**
* Whether or not the package declares any autoVerify domains. This is separate from an empty
- * check on the map itself, because an empty map means no response recorded, not necessarily no
- * domains declared. When this is false, {@link #mStateMap} will be empty, but
- * {@link #mUserSelectionStates} may contain any domains the user has explicitly chosen to
+ * check on the map itself, because an empty map means no response recorded, not necessarily
+ * no domains declared. When this is false, {@link #mStateMap} will be empty, but
+ * {@link #mUserStates} may contain any domains the user has explicitly chosen to
* allow this package to open, which may or may not be marked autoVerify.
*/
private final boolean mHasAutoVerifyDomains;
@@ -62,7 +61,7 @@
private final ArrayMap<String, Integer> mStateMap;
@NonNull
- private final SparseArray<DomainVerificationUserState> mUserSelectionStates;
+ private final SparseArray<DomainVerificationInternalUserState> mUserStates;
public DomainVerificationPkgState(@NonNull String packageName, @NonNull UUID id,
boolean hasAutoVerifyDomains) {
@@ -70,16 +69,17 @@
}
@Nullable
- public DomainVerificationUserState getUserSelectionState(@UserIdInt int userId) {
- return mUserSelectionStates.get(userId);
+ public DomainVerificationInternalUserState getUserState(@UserIdInt int userId) {
+ return mUserStates.get(userId);
}
@Nullable
- public DomainVerificationUserState getOrCreateUserSelectionState(@UserIdInt int userId) {
- DomainVerificationUserState userState = mUserSelectionStates.get(userId);
+ public DomainVerificationInternalUserState getOrCreateUserState(
+ @UserIdInt int userId) {
+ DomainVerificationInternalUserState userState = mUserStates.get(userId);
if (userState == null) {
- userState = new DomainVerificationUserState(userId);
- mUserSelectionStates.put(userId, userState);
+ userState = new DomainVerificationInternalUserState(userId);
+ mUserStates.put(userId, userState);
}
return userState;
}
@@ -89,20 +89,20 @@
}
public void removeUser(@UserIdInt int userId) {
- mUserSelectionStates.remove(userId);
+ mUserStates.remove(userId);
}
public void removeAllUsers() {
- mUserSelectionStates.clear();
+ mUserStates.clear();
}
- private int userSelectionStatesHashCode() {
- return mUserSelectionStates.contentHashCode();
+ private int userStatesHashCode() {
+ return mUserStates.contentHashCode();
}
- private boolean userSelectionStatesEquals(
- @NonNull SparseArray<DomainVerificationUserState> other) {
- return mUserSelectionStates.contentEquals(other);
+ private boolean userStatesEquals(
+ @NonNull SparseArray<DomainVerificationInternalUserState> other) {
+ return mUserStates.contentEquals(other);
}
@@ -113,7 +113,7 @@
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationPkgState.java
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -123,9 +123,15 @@
/**
* Creates a new DomainVerificationPkgState.
*
+ * @param hasAutoVerifyDomains
+ * Whether or not the package declares any autoVerify domains. This is separate from an empty
+ * check on the map itself, because an empty map means no response recorded, not necessarily
+ * no domains declared. When this is false, {@link #mStateMap} will be empty, but
+ * {@link #mUserStates} may contain any domains the user has explicitly chosen to
+ * allow this package to open, which may or may not be marked autoVerify.
* @param stateMap
* Map of domains to state integers. Only domains that are not set to the default value of
- * {@link DomainVerificationManager#STATE_NO_RESPONSE} are included.
+ * {@link DomainVerificationState#STATE_NO_RESPONSE} are included.
*
* TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations,
* such as storing no state when the package is marked as a linked app in SystemConfig.
@@ -136,7 +142,7 @@
@NonNull UUID id,
boolean hasAutoVerifyDomains,
@NonNull ArrayMap<String,Integer> stateMap,
- @NonNull SparseArray<DomainVerificationUserState> userSelectionStates) {
+ @NonNull SparseArray<DomainVerificationInternalUserState> userStates) {
this.mPackageName = packageName;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mPackageName);
@@ -147,9 +153,9 @@
this.mStateMap = stateMap;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mStateMap);
- this.mUserSelectionStates = userSelectionStates;
+ this.mUserStates = userStates;
com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mUserSelectionStates);
+ NonNull.class, null, mUserStates);
// onConstructed(); // You can define this method to get a callback
}
@@ -164,6 +170,13 @@
return mId;
}
+ /**
+ * Whether or not the package declares any autoVerify domains. This is separate from an empty
+ * check on the map itself, because an empty map means no response recorded, not necessarily
+ * no domains declared. When this is false, {@link #mStateMap} will be empty, but
+ * {@link #mUserStates} may contain any domains the user has explicitly chosen to
+ * allow this package to open, which may or may not be marked autoVerify.
+ */
@DataClass.Generated.Member
public boolean isHasAutoVerifyDomains() {
return mHasAutoVerifyDomains;
@@ -171,7 +184,7 @@
/**
* Map of domains to state integers. Only domains that are not set to the default value of
- * {@link DomainVerificationManager#STATE_NO_RESPONSE} are included.
+ * {@link DomainVerificationState#STATE_NO_RESPONSE} are included.
*
* TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations,
* such as storing no state when the package is marked as a linked app in SystemConfig.
@@ -182,8 +195,8 @@
}
@DataClass.Generated.Member
- public @NonNull SparseArray<DomainVerificationUserState> getUserSelectionStates() {
- return mUserSelectionStates;
+ public @NonNull SparseArray<DomainVerificationInternalUserState> getUserStates() {
+ return mUserStates;
}
@Override
@@ -197,7 +210,7 @@
"id = " + mId + ", " +
"hasAutoVerifyDomains = " + mHasAutoVerifyDomains + ", " +
"stateMap = " + mStateMap + ", " +
- "userSelectionStates = " + mUserSelectionStates +
+ "userStates = " + mUserStates +
" }";
}
@@ -218,7 +231,7 @@
&& Objects.equals(mId, that.mId)
&& mHasAutoVerifyDomains == that.mHasAutoVerifyDomains
&& Objects.equals(mStateMap, that.mStateMap)
- && userSelectionStatesEquals(that.mUserSelectionStates);
+ && userStatesEquals(that.mUserStates);
}
@Override
@@ -232,15 +245,15 @@
_hash = 31 * _hash + Objects.hashCode(mId);
_hash = 31 * _hash + Boolean.hashCode(mHasAutoVerifyDomains);
_hash = 31 * _hash + Objects.hashCode(mStateMap);
- _hash = 31 * _hash + userSelectionStatesHashCode();
+ _hash = 31 * _hash + userStatesHashCode();
return _hash;
}
@DataClass.Generated(
- time = 1608234185474L,
+ time = 1614818362549L,
codegenVersion = "1.0.22",
- sourceFile = "frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationPkgState.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull java.util.UUID mId\nprivate final boolean mHasAutoVerifyDomains\nprivate final @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.Integer> mStateMap\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationUserState> mUserSelectionStates\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationUserState getUserSelectionState(int)\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationUserState getOrCreateUserSelectionState(int)\npublic void setId(java.util.UUID)\npublic void removeUser(int)\npublic void removeAllUsers()\nprivate int userSelectionStatesHashCode()\nprivate boolean userSelectionStatesEquals(android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationUserState>)\nclass DomainVerificationPkgState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
+ sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java",
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull java.util.UUID mId\nprivate final boolean mHasAutoVerifyDomains\nprivate final @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.Integer> mStateMap\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState> mUserStates\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getUserState(int)\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getOrCreateUserState(int)\npublic void setId(java.util.UUID)\npublic void removeUser(int)\npublic void removeAllUsers()\nprivate int userStatesHashCode()\nprivate boolean userStatesEquals(android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState>)\nclass DomainVerificationPkgState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/policy/KeyCombinationManager.java b/services/core/java/com/android/server/policy/KeyCombinationManager.java
index 84ac124..7f55723 100644
--- a/services/core/java/com/android/server/policy/KeyCombinationManager.java
+++ b/services/core/java/com/android/server/policy/KeyCombinationManager.java
@@ -102,9 +102,11 @@
}
/**
- * Check if the key event could be triggered by combine key rule before dispatching to a window.
+ * Check if the key event could be intercepted by combination key rule before it is dispatched
+ * to a window.
+ * Return true if any active rule could be triggered by the key event, otherwise false.
*/
- void interceptKey(KeyEvent event, boolean interactive) {
+ boolean interceptKey(KeyEvent event, boolean interactive) {
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
final int keyCode = event.getKeyCode();
final int count = mActiveRules.size();
@@ -117,9 +119,9 @@
// exceed time from first key down.
forAllRules(mActiveRules, (rule)-> rule.cancel());
mActiveRules.clear();
- return;
+ return false;
} else if (count == 0) { // has some key down but no active rule exist.
- return;
+ return false;
}
}
@@ -127,7 +129,7 @@
mDownTimes.put(keyCode, eventTime);
} else {
// ignore old key, maybe a repeat key.
- return;
+ return false;
}
if (mDownTimes.size() == 1) {
@@ -141,7 +143,7 @@
} else {
// Ignore if rule already triggered.
if (mTriggeredRule != null) {
- return;
+ return true;
}
// check if second key can trigger rule, or remove the non-match rule.
@@ -156,6 +158,7 @@
mActiveRules.clear();
if (mTriggeredRule != null) {
mActiveRules.add(mTriggeredRule);
+ return true;
}
}
} else {
@@ -168,6 +171,7 @@
}
}
}
+ return false;
}
/**
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index bce218f..047e3b3 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -73,6 +73,8 @@
import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
+import static com.android.server.policy.SingleKeyGestureDetector.KEY_LONGPRESS;
+import static com.android.server.policy.SingleKeyGestureDetector.KEY_VERYLONGPRESS;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
@@ -430,7 +432,6 @@
volatile boolean mPowerKeyHandled;
volatile boolean mBackKeyHandled;
volatile boolean mBeganFromNonInteractive;
- volatile int mPowerKeyPressCounter;
volatile boolean mEndCallKeyHandled;
volatile boolean mCameraGestureTriggeredDuringGoingToSleep;
volatile boolean mGoingToSleep;
@@ -471,7 +472,6 @@
boolean mHasSoftInput = false;
boolean mHapticTextHandleEnabled;
boolean mUseTvRouting;
- int mVeryLongPressTimeout;
boolean mAllowStartActivityForLongPressOnPowerDuringSetup;
MetricsLogger mLogger;
boolean mWakeOnDpadKeyPress;
@@ -567,14 +567,13 @@
private final com.android.internal.policy.LogDecelerateInterpolator mLogDecelerateInterpolator
= new LogDecelerateInterpolator(100, 0);
- private final MutableBoolean mTmpBoolean = new MutableBoolean(false);
-
private boolean mPerDisplayFocusEnabled = false;
private volatile int mTopFocusedDisplayId = INVALID_DISPLAY;
private int mPowerButtonSuppressionDelayMillis = POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS;
private KeyCombinationManager mKeyCombinationManager;
+ private SingleKeyGestureDetector mSingleKeyGestureDetector;
private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3;
private static final int MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK = 4;
@@ -585,10 +584,7 @@
private static final int MSG_DISPATCH_SHOW_GLOBAL_ACTIONS = 10;
private static final int MSG_HIDE_BOOT_MESSAGE = 11;
private static final int MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK = 12;
- private static final int MSG_POWER_DELAYED_PRESS = 13;
- private static final int MSG_POWER_LONG_PRESS = 14;
private static final int MSG_SHOW_PICTURE_IN_PICTURE_MENU = 15;
- private static final int MSG_BACK_LONG_PRESS = 16;
private static final int MSG_ACCESSIBILITY_SHORTCUT = 17;
private static final int MSG_BUGREPORT_TV = 18;
private static final int MSG_ACCESSIBILITY_TV = 19;
@@ -596,8 +592,7 @@
private static final int MSG_SYSTEM_KEY_PRESS = 21;
private static final int MSG_HANDLE_ALL_APPS = 22;
private static final int MSG_LAUNCH_ASSIST = 23;
- private static final int MSG_POWER_VERY_LONG_PRESS = 25;
- private static final int MSG_RINGER_TOGGLE_CHORD = 26;
+ private static final int MSG_RINGER_TOGGLE_CHORD = 24;
private class PolicyHandler extends Handler {
@Override
@@ -638,22 +633,9 @@
case MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK:
launchVoiceAssistWithWakeLock();
break;
- case MSG_POWER_DELAYED_PRESS:
- powerPress((Long) msg.obj, msg.arg1 != 0, msg.arg2);
- finishPowerKeyPress();
- break;
- case MSG_POWER_LONG_PRESS:
- powerLongPress((Long) msg.obj /* eventTime */);
- break;
- case MSG_POWER_VERY_LONG_PRESS:
- powerVeryLongPress();
- break;
case MSG_SHOW_PICTURE_IN_PICTURE_MENU:
showPictureInPictureMenuInternal();
break;
- case MSG_BACK_LONG_PRESS:
- backLongPress();
- break;
case MSG_ACCESSIBILITY_SHORTCUT:
accessibilityShortcutActivated();
break;
@@ -764,13 +746,6 @@
}
};
- private Runnable mPossibleVeryLongPressReboot = new Runnable() {
- @Override
- public void run() {
- mActivityManagerInternal.prepareForPossibleShutdown();
- }
- };
-
private void handleRingerChordGesture() {
if (mRingerToggleChord == VOLUME_HUSH_OFF) {
return;
@@ -810,28 +785,13 @@
}
}
- private void interceptBackKeyDown() {
- mLogger.count("key_back_down", 1);
- // Reset back key state for long press
- mBackKeyHandled = false;
-
- if (hasLongPressOnBackBehavior()) {
- Message msg = mHandler.obtainMessage(MSG_BACK_LONG_PRESS);
- msg.setAsynchronous(true);
- mHandler.sendMessageDelayed(msg,
- ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
- }
- }
// returns true if the key was handled and should not be passed to the user
- private boolean interceptBackKeyUp(KeyEvent event) {
- mLogger.count("key_back_up", 1);
+ private boolean backKeyPress() {
+ mLogger.count("key_back_press", 1);
// Cache handled state
boolean handled = mBackKeyHandled;
- // Reset back long press state
- cancelPendingBackKeyAction();
-
if (mHasFeatureWatch) {
TelecomManager telecomManager = getTelecommService();
@@ -853,10 +813,9 @@
}
}
- if (mAutofillManagerInternal != null && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ if (mAutofillManagerInternal != null) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_BACK_KEY_TO_AUTOFILL));
}
-
return handled;
}
@@ -866,11 +825,6 @@
mPowerKeyWakeLock.acquire();
}
- // Cancel multi-press detection timeout.
- if (mPowerKeyPressCounter != 0) {
- mHandler.removeMessages(MSG_POWER_DELAYED_PRESS);
- }
-
mWindowManagerFuncs.onPowerKeyDown(interactive);
// Stop ringing or end call if configured to do so when power is pressed.
@@ -892,71 +846,20 @@
final boolean handledByPowerManager = mPowerManagerInternal.interceptPowerKeyDown(event);
- GestureLauncherService gestureService = LocalServices.getService(
- GestureLauncherService.class);
- boolean gesturedServiceIntercepted = false;
- if (gestureService != null) {
- gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive,
- mTmpBoolean);
- if (mTmpBoolean.value && mRequestedOrGoingToSleep) {
- mCameraGestureTriggeredDuringGoingToSleep = true;
- }
- }
-
// Inform the StatusBar; but do not allow it to consume the event.
sendSystemKeyToStatusBarAsync(event.getKeyCode());
- schedulePossibleVeryLongPressReboot();
-
// If the power key has still not yet been handled, then detect short
// press, long press, or multi press and decide what to do.
- mPowerKeyHandled = hungUp || gesturedServiceIntercepted
+ mPowerKeyHandled = mPowerKeyHandled || hungUp
|| handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted();
if (!mPowerKeyHandled) {
- if (interactive) {
- // When interactive, we're already awake.
- // Wait for a long press or for the button to be released to decide what to do.
- if (hasLongPressOnPowerBehavior()) {
- if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
- powerLongPress(event.getEventTime());
- } else {
- Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS,
- event.getEventTime());
- msg.setAsynchronous(true);
- mHandler.sendMessageDelayed(msg,
- ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
-
- if (hasVeryLongPressOnPowerBehavior()) {
- Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS);
- longMsg.setAsynchronous(true);
- mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout);
- }
- }
- }
- } else {
+ if (!interactive) {
wakeUpFromPowerKey(event.getDownTime());
-
if (mSupportLongPressPowerWhenNonInteractive && hasLongPressOnPowerBehavior()) {
- if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
- powerLongPress(event.getEventTime());
- } else {
- Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS,
- event.getEventTime());
- msg.setAsynchronous(true);
- mHandler.sendMessageDelayed(msg,
- ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
-
- if (hasVeryLongPressOnPowerBehavior()) {
- Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS);
- longMsg.setAsynchronous(true);
- mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout);
- }
- }
-
mBeganFromNonInteractive = true;
} else {
final int maxCount = getMaxMultiPressPowerCount();
-
if (maxCount <= 1) {
mPowerKeyHandled = true;
} else {
@@ -964,68 +867,37 @@
}
}
}
+ } else {
+ // handled by another power key policy.
+ if (!mSingleKeyGestureDetector.isKeyIntercepted(KEYCODE_POWER)) {
+ mSingleKeyGestureDetector.reset();
+ }
}
}
private void interceptPowerKeyUp(KeyEvent event, boolean interactive, boolean canceled) {
final boolean handled = canceled || mPowerKeyHandled;
- cancelPendingPowerKeyAction();
if (!handled) {
if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) == 0) {
// Abort possibly stuck animations only when power key up without long press case.
mHandler.post(mWindowManagerFuncs::triggerAnimationFailsafe);
}
-
- // Figure out how to handle the key now that it has been released.
- mPowerKeyPressCounter += 1;
-
- final int maxCount = getMaxMultiPressPowerCount();
- final long eventTime = event.getDownTime();
- if (mPowerKeyPressCounter < maxCount) {
- // This could be a multi-press. Wait a little bit longer to confirm.
- // Continue holding the wake lock.
- Message msg = mHandler.obtainMessage(MSG_POWER_DELAYED_PRESS,
- interactive ? 1 : 0, mPowerKeyPressCounter, eventTime);
- msg.setAsynchronous(true);
- mHandler.sendMessageDelayed(msg, ViewConfiguration.getMultiPressTimeout());
- return;
- }
-
- // No other actions. Handle it immediately.
- powerPress(eventTime, interactive, mPowerKeyPressCounter);
+ } else {
+ // handled by single key or another power key policy.
+ mSingleKeyGestureDetector.reset();
+ finishPowerKeyPress();
}
-
- // Done. Reset our state.
- finishPowerKeyPress();
}
private void finishPowerKeyPress() {
mBeganFromNonInteractive = false;
- mPowerKeyPressCounter = 0;
+ mPowerKeyHandled = false;
if (mPowerKeyWakeLock.isHeld()) {
mPowerKeyWakeLock.release();
}
}
- private void cancelPendingPowerKeyAction() {
- if (!mPowerKeyHandled) {
- mPowerKeyHandled = true;
- mHandler.removeMessages(MSG_POWER_LONG_PRESS);
- }
- if (hasVeryLongPressOnPowerBehavior()) {
- mHandler.removeMessages(MSG_POWER_VERY_LONG_PRESS);
- }
- cancelPossibleVeryLongPressReboot();
- }
-
- private void cancelPendingBackKeyAction() {
- if (!mBackKeyHandled) {
- mBackKeyHandled = true;
- mHandler.removeMessages(MSG_BACK_LONG_PRESS);
- }
- }
-
private void powerPress(long eventTime, boolean interactive, int count) {
if (mDefaultDisplayPolicy.isScreenOnEarly() && !mDefaultDisplayPolicy.isScreenOnFully()) {
Slog.i(TAG, "Suppressed redundant power key press while "
@@ -1176,6 +1048,7 @@
private void powerLongPress(long eventTime) {
final int behavior = getResolvedLongPressOnPowerBehavior();
+
switch (behavior) {
case LONG_PRESS_POWER_NOTHING:
break;
@@ -1183,7 +1056,7 @@
mPowerKeyHandled = true;
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
"Power - Long Press - Global Actions");
- showGlobalActionsInternal();
+ showGlobalActions();
break;
case LONG_PRESS_POWER_SHUT_OFF:
case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
@@ -1214,14 +1087,14 @@
private void powerVeryLongPress() {
switch (mVeryLongPressOnPowerBehavior) {
- case VERY_LONG_PRESS_POWER_NOTHING:
- break;
- case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS:
- mPowerKeyHandled = true;
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
- "Power - Very Long Press - Show Global Actions");
- showGlobalActionsInternal();
- break;
+ case VERY_LONG_PRESS_POWER_NOTHING:
+ break;
+ case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS:
+ mPowerKeyHandled = true;
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
+ "Power - Very Long Press - Show Global Actions");
+ showGlobalActions();
+ break;
}
}
@@ -1814,8 +1687,6 @@
com.android.internal.R.integer.config_triplePressOnPowerBehavior);
mShortPressOnSleepBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_shortPressOnSleepBehavior);
- mVeryLongPressTimeout = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_veryLongPressTimeout);
mAllowStartActivityForLongPressOnPowerDuringSetup = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_allowStartActivityForLongPressOnPowerInSetup);
@@ -1909,6 +1780,7 @@
}
});
initKeyCombinationRules();
+ initSingleKeyGestureRules();
}
private void initKeyCombinationRules() {
@@ -1921,7 +1793,7 @@
new TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_POWER) {
@Override
void execute() {
- cancelPendingPowerKeyAction();
+ mPowerKeyHandled = true;
interceptScreenshotChord();
}
@Override
@@ -1956,7 +1828,7 @@
}
@Override
void execute() {
- cancelPendingPowerKeyAction();
+ mPowerKeyHandled = true;
interceptRingerToggleChord();
}
@Override
@@ -1970,7 +1842,7 @@
new TwoKeysCombinationRule(KEYCODE_BACK, KEYCODE_DPAD_DOWN) {
@Override
void execute() {
- cancelPendingBackKeyAction();
+ mBackKeyHandled = true;
interceptAccessibilityGestureTv();
}
@@ -1984,7 +1856,7 @@
new TwoKeysCombinationRule(KEYCODE_DPAD_CENTER, KEYCODE_BACK) {
@Override
void execute() {
- cancelPendingBackKeyAction();
+ mBackKeyHandled = true;
interceptBugreportGestureTv();
}
@@ -1997,6 +1869,84 @@
}
/**
+ * Rule for single power key gesture.
+ */
+ private final class PowerKeyRule extends SingleKeyGestureDetector.SingleKeyRule {
+ PowerKeyRule(int gestures) {
+ super(KEYCODE_POWER, gestures);
+ }
+
+ @Override
+ int getMaxMultiPressCount() {
+ return getMaxMultiPressPowerCount();
+ }
+
+ @Override
+ void onPress(long downTime) {
+ powerPress(downTime, true, 1 /*count*/);
+ finishPowerKeyPress();
+ }
+
+ @Override
+ void onLongPress(long eventTime) {
+ powerLongPress(eventTime);
+ }
+
+ @Override
+ void onVeryLongPress(long eventTime) {
+ mActivityManagerInternal.prepareForPossibleShutdown();
+ powerVeryLongPress();
+ }
+
+ @Override
+ void onMultiPress(long downTime, int count) {
+ powerPress(downTime, true, count);
+ finishPowerKeyPress();
+ }
+ }
+
+ /**
+ * Rule for single back key gesture.
+ */
+ private final class BackKeyRule extends SingleKeyGestureDetector.SingleKeyRule {
+ BackKeyRule(int gestures) {
+ super(KEYCODE_BACK, gestures);
+ }
+
+ @Override
+ int getMaxMultiPressCount() {
+ return 1;
+ }
+
+ @Override
+ void onPress(long downTime) {
+ mBackKeyHandled |= backKeyPress();
+ }
+
+ @Override
+ void onLongPress(long downTime) {
+ backLongPress();
+ }
+ }
+
+ private void initSingleKeyGestureRules() {
+ mSingleKeyGestureDetector = new SingleKeyGestureDetector(mContext);
+
+ int powerKeyGestures = 0;
+ if (hasVeryLongPressOnPowerBehavior()) {
+ powerKeyGestures |= KEY_VERYLONGPRESS;
+ }
+ if (hasLongPressOnPowerBehavior()) {
+ powerKeyGestures |= KEY_LONGPRESS;
+ }
+ mSingleKeyGestureDetector.addRule(new PowerKeyRule(powerKeyGestures));
+
+ if (hasLongPressOnBackBehavior()) {
+ mSingleKeyGestureDetector.addRule(new BackKeyRule(KEY_LONGPRESS));
+ }
+ }
+
+ /**
* Read values from config.xml that may be overridden depending on
* the configuration of the device.
* eg. Disable long press on home goes to recents on sw600dp.
@@ -3427,8 +3377,21 @@
return result;
}
+ // Alternate TV power to power key for Android TV device.
+ final HdmiControlManager hdmiControlManager = getHdmiControlManager();
+ if (keyCode == KeyEvent.KEYCODE_TV_POWER && mHasFeatureLeanback
+ && (hdmiControlManager == null || !hdmiControlManager.shouldHandleTvPowerKey())) {
+ event = KeyEvent.obtain(
+ event.getDownTime(), event.getEventTime(),
+ event.getAction(), KeyEvent.KEYCODE_POWER,
+ event.getRepeatCount(), event.getMetaState(),
+ event.getDeviceId(), event.getScanCode(),
+ event.getFlags(), event.getSource(), event.getDisplayId(), null);
+ return interceptKeyBeforeQueueing(event, policyFlags);
+ }
+
if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
- mKeyCombinationManager.interceptKey(event, interactive);
+ handleKeyGesture(event, interactive);
}
// Enable haptics if down and virtual key without multiple repetitions. If this is a hard
@@ -3443,12 +3406,13 @@
switch (keyCode) {
case KeyEvent.KEYCODE_BACK: {
if (down) {
- interceptBackKeyDown();
+ mBackKeyHandled = false;
} else {
- boolean handled = interceptBackKeyUp(event);
-
+ if (!hasLongPressOnBackBehavior()) {
+ mBackKeyHandled |= backKeyPress();
+ }
// Don't pass back press to app if we've already handled it via long press
- if (handled) {
+ if (mBackKeyHandled) {
result &= ~ACTION_PASS_TO_USER;
}
}
@@ -3560,33 +3524,17 @@
case KeyEvent.KEYCODE_TV_POWER: {
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false; // wake-up will be handled separately
- HdmiControlManager hdmiControlManager = getHdmiControlManager();
- if (hdmiControlManager != null && hdmiControlManager.shouldHandleTvPowerKey()) {
- if (down) {
- hdmiControlManager.toggleAndFollowTvPower();
- }
- } else if (mHasFeatureLeanback) {
- KeyEvent fallbackEvent = KeyEvent.obtain(
- event.getDownTime(), event.getEventTime(),
- event.getAction(), KeyEvent.KEYCODE_POWER,
- event.getRepeatCount(), event.getMetaState(),
- event.getDeviceId(), event.getScanCode(),
- event.getFlags(), event.getSource(), event.getDisplayId(), null);
- if (down) {
- interceptPowerKeyDown(fallbackEvent, interactive);
- } else {
- interceptPowerKeyUp(fallbackEvent, interactive, canceled);
- }
+ if (down && hdmiControlManager != null) {
+ hdmiControlManager.toggleAndFollowTvPower();
}
- // Ignore this key for any device that is not connected to a TV via HDMI and
- // not an Android TV device.
break;
}
case KeyEvent.KEYCODE_POWER: {
EventLogTags.writeInterceptPower(
KeyEvent.actionToString(event.getAction()),
- mPowerKeyHandled ? 1 : 0, mPowerKeyPressCounter);
+ mPowerKeyHandled ? 1 : 0,
+ mSingleKeyGestureDetector.getKeyPressCounter(KeyEvent.KEYCODE_POWER));
// Any activity on the power button stops the accessibility shortcut
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false; // wake-up will be handled separately
@@ -3767,6 +3715,43 @@
return result;
}
+ private void handleKeyGesture(KeyEvent event, boolean interactive) {
+ if (mKeyCombinationManager.interceptKey(event, interactive)) {
+ // handled by combo keys manager.
+ mSingleKeyGestureDetector.reset();
+ return;
+ }
+
+ if (event.getKeyCode() == KEYCODE_POWER && event.getAction() == KeyEvent.ACTION_DOWN) {
+ mPowerKeyHandled = handleCameraGesture(event, interactive);
+ if (mPowerKeyHandled) {
+ // handled by camera gesture.
+ mSingleKeyGestureDetector.reset();
+ return;
+ }
+ }
+
+ mSingleKeyGestureDetector.interceptKey(event);
+ }
+
+ // The camera gesture will be detected by GestureLauncherService.
+ private boolean handleCameraGesture(KeyEvent event, boolean interactive) {
+ // camera gesture.
+ GestureLauncherService gestureService = LocalServices.getService(
+ GestureLauncherService.class);
+ if (gestureService == null) {
+ return false;
+ }
+
+ final MutableBoolean outLaunched = new MutableBoolean(false);
+ final boolean gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event,
+ interactive, outLaunched);
+ if (outLaunched.value && mRequestedOrGoingToSleep) {
+ mCameraGestureTriggeredDuringGoingToSleep = true;
+ }
+ return gesturedServiceIntercepted;
+ }
+
/**
* Handle statusbar expansion events.
* @param event
@@ -4766,15 +4751,6 @@
}
}
- private void schedulePossibleVeryLongPressReboot() {
- mHandler.removeCallbacks(mPossibleVeryLongPressReboot);
- mHandler.postDelayed(mPossibleVeryLongPressReboot, mVeryLongPressTimeout);
- }
-
- private void cancelPossibleVeryLongPressReboot() {
- mHandler.removeCallbacks(mPossibleVeryLongPressReboot);
- }
-
// TODO (multidisplay): Support multiple displays in WindowManagerPolicy.
private void updateScreenOffSleepToken(boolean acquire) {
if (acquire) {
diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
new file mode 100644
index 0000000..cae2093
--- /dev/null
+++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
@@ -0,0 +1,362 @@
+/*
+ * 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.policy;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.ViewConfiguration;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+/**
+ * Detect single key gesture: press, long press, very long press and multi press.
+ *
+ * Call {@link #reset} if current {@link KeyEvent} has been handled by another policy
+ */
+
+public final class SingleKeyGestureDetector {
+ private static final String TAG = "SingleKeyGesture";
+ private static final boolean DEBUG = false;
+
+ private static final int MSG_KEY_LONG_PRESS = 0;
+ private static final int MSG_KEY_VERY_LONG_PRESS = 1;
+ private static final int MSG_KEY_DELAYED_PRESS = 2;
+
+ private final long mLongPressTimeout;
+ private final long mVeryLongPressTimeout;
+
+ private volatile int mKeyPressCounter;
+
+ private final ArrayList<SingleKeyRule> mRules = new ArrayList();
+ private SingleKeyRule mActiveRule = null;
+
+ // Key code of current key down event, reset when key up.
+ private int mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN;
+ private volatile boolean mHandledByLongPress = false;
+ private final Handler mHandler;
+ private static final long MULTI_PRESS_TIMEOUT = ViewConfiguration.getMultiPressTimeout();
+
+
+ /** Supported gesture flags */
+ public static final int KEY_LONGPRESS = 1 << 1;
+ public static final int KEY_VERYLONGPRESS = 1 << 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "KEY_" }, value = {
+ KEY_LONGPRESS,
+ KEY_VERYLONGPRESS,
+ })
+ public @interface KeyGestureFlag {}
+
+ /**
+ * Rule definition for single keys gesture.
+ * E.g : define power key.
+ * <pre class="prettyprint">
+ * SingleKeyRule rule =
+ * new SingleKeyRule(KEYCODE_POWER, KEY_LONGPRESS|KEY_VERYLONGPRESS) {
+ * int getMaxMultiPressCount() { // maximum multi press count. }
+ * void onPress(long downTime) { // short press behavior. }
+ * void onLongPress(long eventTime) { // long press behavior. }
+ * void onVeryLongPress(long eventTime) { // very long press behavior. }
+ * void onMultiPress(long downTime, int count) { // multi press behavior. }
+ * };
+ * </pre>
+ */
+ abstract static class SingleKeyRule {
+ private final int mKeyCode;
+ private final int mSupportedGestures;
+
+ SingleKeyRule(int keyCode, @KeyGestureFlag int supportedGestures) {
+ mKeyCode = keyCode;
+ mSupportedGestures = supportedGestures;
+ }
+
+ /**
+ * True if the rule could intercept the key.
+ */
+ private boolean shouldInterceptKey(int keyCode) {
+ return keyCode == mKeyCode;
+ }
+
+ /**
+ * True if the rule support long press.
+ */
+ private boolean supportLongPress() {
+ return (mSupportedGestures & KEY_LONGPRESS) != 0;
+ }
+
+ /**
+ * True if the rule support very long press.
+ */
+ private boolean supportVeryLongPress() {
+ return (mSupportedGestures & KEY_VERYLONGPRESS) != 0;
+ }
+
+ /**
+ * Maximum count of multi presses.
+ * Return 1 will trigger onPress immediately when {@link KeyEvent.ACTION_UP}.
+ * Otherwise trigger onMultiPress immediately when reach max count when
+ * {@link KeyEvent.ACTION_DOWN}.
+ */
+ int getMaxMultiPressCount() {
+ return 1;
+ }
+
+ /**
+ * Called when short press has been detected.
+ */
+ abstract void onPress(long downTime);
+ /**
+ * Callback when multi press (>= 2) has been detected.
+ */
+ void onMultiPress(long downTime, int count) {}
+ /**
+ * Callback when long press has been detected.
+ */
+ void onLongPress(long eventTime) {}
+ /**
+ * Callback when very long press has been detected.
+ */
+ void onVeryLongPress(long eventTime) {}
+
+ @Override
+ public String toString() {
+ return "KeyCode = " + KeyEvent.keyCodeToString(mKeyCode)
+ + ", long press : " + supportLongPress()
+ + ", very Long press : " + supportVeryLongPress()
+ + ", max multi press count : " + getMaxMultiPressCount();
+ }
+ }
+
+ public SingleKeyGestureDetector(Context context) {
+ mLongPressTimeout = ViewConfiguration.get(context).getDeviceGlobalActionKeyTimeout();
+ mVeryLongPressTimeout = context.getResources().getInteger(
+ com.android.internal.R.integer.config_veryLongPressTimeout);
+ mHandler = new KeyHandler();
+ }
+
+ void addRule(SingleKeyRule rule) {
+ mRules.add(rule);
+ }
+
+ void interceptKey(KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ interceptKeyDown(event);
+ } else {
+ interceptKeyUp(event);
+ }
+ }
+
+ private void interceptKeyDown(KeyEvent event) {
+ final int keyCode = event.getKeyCode();
+ // same key down.
+ if (mDownKeyCode == keyCode) {
+ if (mActiveRule != null && (event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0
+ && mActiveRule.supportLongPress() && !mHandledByLongPress) {
+ if (DEBUG) {
+ Log.i(TAG, "Long press key " + KeyEvent.keyCodeToString(keyCode));
+ }
+ mHandledByLongPress = true;
+ mHandler.removeMessages(MSG_KEY_LONG_PRESS);
+ mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
+ mActiveRule.onLongPress(event.getEventTime());
+ }
+ return;
+ }
+
+ // When a different key is pressed, stop processing gestures for the currently active key.
+ if (mDownKeyCode != KeyEvent.KEYCODE_UNKNOWN
+ || (mActiveRule != null && !mActiveRule.shouldInterceptKey(keyCode))) {
+ if (DEBUG) {
+ Log.i(TAG, "Press another key " + KeyEvent.keyCodeToString(keyCode));
+ }
+ reset();
+ }
+ mDownKeyCode = keyCode;
+
+ // Picks a new rule, return if no rule picked.
+ if (mActiveRule == null) {
+ final int count = mRules.size();
+ for (int index = 0; index < count; index++) {
+ final SingleKeyRule rule = mRules.get(index);
+ if (rule.shouldInterceptKey(keyCode)) {
+ if (DEBUG) {
+ Log.i(TAG, "Intercept key by rule " + rule);
+ }
+ mActiveRule = rule;
+ break;
+ }
+ }
+ }
+ if (mActiveRule == null) {
+ return;
+ }
+
+ final long eventTime = event.getEventTime();
+ if (mKeyPressCounter == 0) {
+ if (mActiveRule.supportLongPress()) {
+ final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, keyCode, 0,
+ eventTime);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(msg, mLongPressTimeout);
+ }
+
+ if (mActiveRule.supportVeryLongPress()) {
+ final Message msg = mHandler.obtainMessage(MSG_KEY_VERY_LONG_PRESS, keyCode, 0,
+ eventTime);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(msg, mVeryLongPressTimeout);
+ }
+ } else {
+ mHandler.removeMessages(MSG_KEY_LONG_PRESS);
+ mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
+ mHandler.removeMessages(MSG_KEY_DELAYED_PRESS);
+
+ // Trigger multi press immediately when reach max count.( > 1)
+ if (mKeyPressCounter == mActiveRule.getMaxMultiPressCount() - 1) {
+ if (DEBUG) {
+ Log.i(TAG, "Trigger multi press " + mActiveRule.toString() + " for it"
+ + " reach the max count " + mKeyPressCounter);
+ }
+ mActiveRule.onMultiPress(eventTime, mKeyPressCounter + 1);
+ mKeyPressCounter = 0;
+ }
+ }
+ }
+
+ private boolean interceptKeyUp(KeyEvent event) {
+ mHandler.removeMessages(MSG_KEY_LONG_PRESS);
+ mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
+ mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN;
+ if (mActiveRule == null) {
+ return false;
+ }
+
+ if (mHandledByLongPress) {
+ mHandledByLongPress = false;
+ mKeyPressCounter = 0;
+ return true;
+ }
+
+ final long downTime = event.getDownTime();
+ if (event.getKeyCode() == mActiveRule.mKeyCode) {
+ // Directly trigger short press when max count is 1.
+ if (mActiveRule.getMaxMultiPressCount() == 1) {
+ if (DEBUG) {
+ Log.i(TAG, "press key " + KeyEvent.keyCodeToString(event.getKeyCode()));
+ }
+ mActiveRule.onPress(downTime);
+ return true;
+ }
+
+ // This could be a multi-press. Wait a little bit longer to confirm.
+ mKeyPressCounter++;
+ Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode,
+ mKeyPressCounter, downTime);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(msg, MULTI_PRESS_TIMEOUT);
+ return true;
+ }
+ reset();
+ return false;
+ }
+
+ int getKeyPressCounter(int keyCode) {
+ if (mActiveRule != null && mActiveRule.mKeyCode == keyCode) {
+ return mKeyPressCounter;
+ } else {
+ return 0;
+ }
+ }
+
+ void reset() {
+ if (mActiveRule != null) {
+ if (mDownKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
+ mHandler.removeMessages(MSG_KEY_LONG_PRESS);
+ mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
+ }
+
+ if (mKeyPressCounter > 0) {
+ mHandler.removeMessages(MSG_KEY_DELAYED_PRESS);
+ mKeyPressCounter = 0;
+ }
+ mActiveRule = null;
+ }
+
+ mHandledByLongPress = false;
+ mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN;
+ }
+
+ boolean isKeyIntercepted(int keyCode) {
+ if (mActiveRule != null && mActiveRule.shouldInterceptKey(keyCode)) {
+ return mHandledByLongPress;
+ }
+ return false;
+ }
+
+ private class KeyHandler extends Handler {
+ KeyHandler() {
+ super(Looper.getMainLooper());
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (mActiveRule == null) {
+ return;
+ }
+ final int keyCode = msg.arg1;
+ final long eventTime = (long) msg.obj;
+ switch(msg.what) {
+ case MSG_KEY_LONG_PRESS:
+ if (DEBUG) {
+ Log.i(TAG, "Detect long press " + KeyEvent.keyCodeToString(keyCode));
+ }
+ mHandledByLongPress = true;
+ mActiveRule.onLongPress(eventTime);
+ break;
+ case MSG_KEY_VERY_LONG_PRESS:
+ if (DEBUG) {
+ Log.i(TAG, "Detect very long press "
+ + KeyEvent.keyCodeToString(keyCode));
+ }
+ mHandledByLongPress = true;
+ mActiveRule.onVeryLongPress(eventTime);
+ break;
+ case MSG_KEY_DELAYED_PRESS:
+ if (DEBUG) {
+ Log.i(TAG, "Detect press " + KeyEvent.keyCodeToString(keyCode)
+ + ", count " + mKeyPressCounter);
+ }
+ if (mKeyPressCounter == 1) {
+ mActiveRule.onPress(eventTime);
+ } else {
+ mActiveRule.onMultiPress(eventTime, mKeyPressCounter);
+ }
+ mKeyPressCounter = 0;
+ break;
+ }
+ }
+ }
+}
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 de8823c..6366280 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.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 @@
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 @@
}
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 43047d1..e05c468 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 @@
}
validateUuid(model.uuid);
validateUuid(model.vendorUuid);
- Objects.requireNonNull(model.data);
+ if (model.dataSize > 0) {
+ Objects.requireNonNull(model.data);
+ }
}
static void validatePhraseModel(@Nullable PhraseSoundModel model) {
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 6aa7c8a..7ed7a59 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -3759,13 +3759,15 @@
ConnectivityManager.NetworkCallback {
@Override
public void onAvailable(Network network) {
- FrameworkStatsLog.write(FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED, network.netId,
+ FrameworkStatsLog.write(FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED,
+ network.getNetId(),
FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED__STATE__CONNECTED);
}
@Override
public void onLost(Network network) {
- FrameworkStatsLog.write(FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED, network.netId,
+ FrameworkStatsLog.write(FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED,
+ network.getNetId(),
FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED__STATE__DISCONNECTED);
}
}
diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java
index d2b05c0..9409eb5 100644
--- a/services/core/java/com/android/server/storage/StorageUserConnection.java
+++ b/services/core/java/com/android/server/storage/StorageUserConnection.java
@@ -53,11 +53,13 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
/**
* Controls the lifecycle of the {@link ActiveConnection} to an {@link ExternalStorageService}
@@ -72,6 +74,7 @@
private final Context mContext;
private final int mUserId;
private final StorageSessionController mSessionController;
+ private final StorageManagerInternal mSmInternal;
private final ActiveConnection mActiveConnection = new ActiveConnection();
@GuardedBy("mLock") private final Map<String, Session> mSessions = new HashMap<>();
@GuardedBy("mLock") private final Set<Integer> mUidsBlockedOnIo = new ArraySet<>();
@@ -81,6 +84,7 @@
mContext = Objects.requireNonNull(context);
mUserId = Preconditions.checkArgumentNonnegative(userId);
mSessionController = controller;
+ mSmInternal = LocalServices.getService(StorageManagerInternal.class);
mHandlerThread = new HandlerThread("StorageUserConnectionThread-" + mUserId);
mHandlerThread.start();
}
@@ -152,9 +156,13 @@
*/
public void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason)
throws ExternalStorageServiceException {
+ List<String> primarySessionIds = mSmInternal.getPrimaryVolumeIds();
synchronized (mSessionsLock) {
for (String sessionId : mSessions.keySet()) {
- mActiveConnection.notifyAnrDelayStarted(packageName, uid, tid, reason);
+ if (primarySessionIds.contains(sessionId)) {
+ mActiveConnection.notifyAnrDelayStarted(packageName, uid, tid, reason);
+ return;
+ }
}
}
}
@@ -201,8 +209,7 @@
return;
}
}
- StorageManagerInternal sm = LocalServices.getService(StorageManagerInternal.class);
- sm.resetUser(mUserId);
+ mSmInternal.resetUser(mUserId);
}
/**
@@ -317,6 +324,23 @@
}
}
+ private void asyncBestEffort(Consumer<IExternalStorageService> consumer) {
+ synchronized (mLock) {
+ if (mRemoteFuture == null) {
+ Slog.w(TAG, "Dropping async request service is not bound");
+ return;
+ }
+
+ IExternalStorageService service = mRemoteFuture.getNow(null);
+ if (service == null) {
+ Slog.w(TAG, "Dropping async request service is not connected");
+ return;
+ }
+
+ consumer.accept(service);
+ }
+ }
+
private void waitForAsyncVoid(AsyncStorageServiceCall asyncCall) throws Exception {
CompletableFuture<Void> opFuture = new CompletableFuture<>();
RemoteCallback callback = new RemoteCallback(result -> setResult(result, opFuture));
@@ -401,13 +425,13 @@
public void notifyAnrDelayStarted(String packgeName, int uid, int tid, int reason)
throws ExternalStorageServiceException {
- try {
- waitForAsyncVoid((service, callback) ->
- service.notifyAnrDelayStarted(packgeName, uid, tid, reason, callback));
- } catch (Exception e) {
- throw new ExternalStorageServiceException("Failed to notify ANR delay started: "
- + packgeName, e);
- }
+ asyncBestEffort(service -> {
+ try {
+ service.notifyAnrDelayStarted(packgeName, uid, tid, reason);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify ANR delay started", e);
+ }
+ });
}
private void setResult(Bundle result, CompletableFuture<Void> future) {
diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
index b6ddd93..b2db9f5 100644
--- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
+++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
@@ -65,7 +65,7 @@
@NonNull private final NetworkCallback mRouteSelectionCallback = new RouteSelectionCallback();
@NonNull private TelephonySubscriptionSnapshot mLastSnapshot;
- private boolean mIsRunning = true;
+ private boolean mIsQuitting = false;
@Nullable private UnderlyingNetworkRecord mCurrentRecord;
@Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress;
@@ -151,7 +151,7 @@
mVcnContext.ensureRunningOnLooperThread();
// Don't bother re-filing NetworkRequests if this Tracker has been torn down.
- if (!mIsRunning) {
+ if (mIsQuitting) {
return;
}
@@ -205,7 +205,7 @@
}
mCellBringupCallbacks.clear();
- mIsRunning = false;
+ mIsQuitting = true;
}
/** Returns whether the currently selected Network matches the given network. */
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index 6ad30b5..9d39c67 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -16,6 +16,8 @@
package com.android.server.vcn;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
+
import static com.android.server.VcnManagementService.VDBG;
import android.annotation.NonNull;
@@ -84,6 +86,13 @@
*/
private static final int MSG_EVENT_SUBSCRIPTIONS_CHANGED = MSG_EVENT_BASE + 2;
+ /**
+ * A GatewayConnection owned by this VCN quit.
+ *
+ * @param obj VcnGatewayConnectionConfig
+ */
+ private static final int MSG_EVENT_GATEWAY_CONNECTION_QUIT = MSG_EVENT_BASE + 3;
+
/** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */
private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE;
@@ -208,6 +217,9 @@
case MSG_EVENT_SUBSCRIPTIONS_CHANGED:
handleSubscriptionsChanged((TelephonySubscriptionSnapshot) msg.obj);
break;
+ case MSG_EVENT_GATEWAY_CONNECTION_QUIT:
+ handleGatewayConnectionQuit((VcnGatewayConnectionConfig) msg.obj);
+ break;
case MSG_CMD_TEARDOWN:
handleTeardown();
break;
@@ -263,7 +275,7 @@
// If preexisting VcnGatewayConnection(s) satisfy request, return
for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) {
- if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
+ if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
if (VDBG) {
Slog.v(
getLogTag(),
@@ -278,7 +290,7 @@
// up
for (VcnGatewayConnectionConfig gatewayConnectionConfig :
mConfig.getGatewayConnectionConfigs()) {
- if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
+ if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
Slog.v(
getLogTag(),
"Bringing up new VcnGatewayConnection for request " + request.requestId);
@@ -289,12 +301,21 @@
mSubscriptionGroup,
mLastSnapshot,
gatewayConnectionConfig,
- new VcnGatewayStatusCallbackImpl());
+ new VcnGatewayStatusCallbackImpl(gatewayConnectionConfig));
mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection);
}
}
}
+ private void handleGatewayConnectionQuit(VcnGatewayConnectionConfig config) {
+ Slog.v(getLogTag(), "VcnGatewayConnection quit: " + config);
+ mVcnGatewayConnections.remove(config);
+
+ // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied
+ // start a new GatewayConnection)
+ mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
+ }
+
private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) {
mLastSnapshot = snapshot;
@@ -305,9 +326,10 @@
}
}
- private boolean requestSatisfiedByGatewayConnectionConfig(
+ private boolean isRequestSatisfiedByGatewayConnectionConfig(
@NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) {
final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
+ builder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
for (int cap : config.getAllExposedCapabilities()) {
builder.addCapability(cap);
}
@@ -339,9 +361,23 @@
@VcnErrorCode int errorCode,
@Nullable String exceptionClass,
@Nullable String exceptionMessage);
+
+ /** Called by a VcnGatewayConnection to indicate that it has fully torn down. */
+ void onQuit();
}
private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback {
+ public final VcnGatewayConnectionConfig mGatewayConnectionConfig;
+
+ VcnGatewayStatusCallbackImpl(VcnGatewayConnectionConfig gatewayConnectionConfig) {
+ mGatewayConnectionConfig = gatewayConnectionConfig;
+ }
+
+ @Override
+ public void onQuit() {
+ sendMessage(obtainMessage(MSG_EVENT_GATEWAY_CONNECTION_QUIT, mGatewayConnectionConfig));
+ }
+
@Override
public void onEnteredSafeMode() {
sendMessage(obtainMessage(MSG_CMD_ENTER_SAFE_MODE));
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 06748a3..6bc9978 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -168,6 +168,8 @@
private static final String DISCONNECT_REASON_INTERNAL_ERROR = "Uncaught exception: ";
private static final String DISCONNECT_REASON_UNDERLYING_NETWORK_LOST =
"Underlying Network lost";
+ private static final String DISCONNECT_REASON_NETWORK_AGENT_UNWANTED =
+ "NetworkAgent was unwanted";
private static final String DISCONNECT_REASON_TEARDOWN = "teardown() called on VcnTunnel";
private static final int TOKEN_ALL = Integer.MIN_VALUE;
@@ -379,13 +381,16 @@
/** The reason why the disconnect was requested. */
@NonNull public final String reason;
- EventDisconnectRequestedInfo(@NonNull String reason) {
+ public final boolean shouldQuit;
+
+ EventDisconnectRequestedInfo(@NonNull String reason, boolean shouldQuit) {
this.reason = Objects.requireNonNull(reason);
+ this.shouldQuit = shouldQuit;
}
@Override
public int hashCode() {
- return Objects.hash(reason);
+ return Objects.hash(reason, shouldQuit);
}
@Override
@@ -395,7 +400,7 @@
}
final EventDisconnectRequestedInfo rhs = (EventDisconnectRequestedInfo) other;
- return reason.equals(rhs.reason);
+ return reason.equals(rhs.reason) && shouldQuit == rhs.shouldQuit;
}
}
@@ -488,8 +493,14 @@
*/
@NonNull private final VcnWakeLock mWakeLock;
- /** Running state of this VcnGatewayConnection. */
- private boolean mIsRunning = true;
+ /**
+ * Whether the VcnGatewayConnection is in the process of irreversibly quitting.
+ *
+ * <p>This variable is false for the lifecycle of the VcnGatewayConnection, until a command to
+ * teardown has been received. This may be flipped due to events such as the Network becoming
+ * unwanted, the owning VCN entering safe mode, or an irrecoverable internal failure.
+ */
+ private boolean mIsQuitting = false;
/**
* The token used by the primary/current/active session.
@@ -622,10 +633,8 @@
* <p>Once torn down, this VcnTunnel CANNOT be started again.
*/
public void teardownAsynchronously() {
- sendMessageAndAcquireWakeLock(
- EVENT_DISCONNECT_REQUESTED,
- TOKEN_ALL,
- new EventDisconnectRequestedInfo(DISCONNECT_REASON_TEARDOWN));
+ sendDisconnectRequestedAndAcquireWakelock(
+ DISCONNECT_REASON_TEARDOWN, true /* shouldQuit */);
// TODO: Notify VcnInstance (via callbacks) of permanent teardown of this tunnel, since this
// is also called asynchronously when a NetworkAgent becomes unwanted
@@ -646,6 +655,8 @@
cancelSafeModeAlarm();
mUnderlyingNetworkTracker.teardown();
+
+ mGatewayStatusCallback.onQuit();
}
/**
@@ -693,7 +704,7 @@
private void acquireWakeLock() {
mVcnContext.ensureRunningOnLooperThread();
- if (mIsRunning) {
+ if (!mIsQuitting) {
mWakeLock.acquire();
}
}
@@ -892,7 +903,7 @@
TOKEN_ALL,
0 /* arg2 */,
new EventDisconnectRequestedInfo(
- DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
+ DISCONNECT_REASON_UNDERLYING_NETWORK_LOST, false /* shouldQuit */));
mDisconnectRequestAlarm =
createScheduledAlarm(
DISCONNECT_REQUEST_ALARM,
@@ -909,7 +920,8 @@
// Cancel any existing disconnect due to previous loss of underlying network
removeEqualMessages(
EVENT_DISCONNECT_REQUESTED,
- new EventDisconnectRequestedInfo(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
+ new EventDisconnectRequestedInfo(
+ DISCONNECT_REASON_UNDERLYING_NETWORK_LOST, false /* shouldQuit */));
}
private void setRetryTimeoutAlarm(long delay) {
@@ -1041,11 +1053,8 @@
enterState();
} catch (Exception e) {
Slog.wtf(TAG, "Uncaught exception", e);
- sendMessageAndAcquireWakeLock(
- EVENT_DISCONNECT_REQUESTED,
- TOKEN_ALL,
- new EventDisconnectRequestedInfo(
- DISCONNECT_REASON_INTERNAL_ERROR + e.toString()));
+ sendDisconnectRequestedAndAcquireWakelock(
+ DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */);
}
}
@@ -1083,11 +1092,8 @@
processStateMsg(msg);
} catch (Exception e) {
Slog.wtf(TAG, "Uncaught exception", e);
- sendMessageAndAcquireWakeLock(
- EVENT_DISCONNECT_REQUESTED,
- TOKEN_ALL,
- new EventDisconnectRequestedInfo(
- DISCONNECT_REASON_INTERNAL_ERROR + e.toString()));
+ sendDisconnectRequestedAndAcquireWakelock(
+ DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */);
}
// Attempt to release the WakeLock - only possible if the Handler queue is empty
@@ -1104,11 +1110,8 @@
exitState();
} catch (Exception e) {
Slog.wtf(TAG, "Uncaught exception", e);
- sendMessageAndAcquireWakeLock(
- EVENT_DISCONNECT_REQUESTED,
- TOKEN_ALL,
- new EventDisconnectRequestedInfo(
- DISCONNECT_REASON_INTERNAL_ERROR + e.toString()));
+ sendDisconnectRequestedAndAcquireWakelock(
+ DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */);
}
}
@@ -1141,11 +1144,11 @@
}
}
- protected void handleDisconnectRequested(String msg) {
+ protected void handleDisconnectRequested(EventDisconnectRequestedInfo info) {
// TODO(b/180526152): notify VcnStatusCallback for Network loss
- Slog.v(TAG, "Tearing down. Cause: " + msg);
- mIsRunning = false;
+ Slog.v(TAG, "Tearing down. Cause: " + info.reason);
+ mIsQuitting = info.shouldQuit;
teardownNetwork();
@@ -1177,7 +1180,7 @@
private class DisconnectedState extends BaseState {
@Override
protected void enterState() {
- if (!mIsRunning) {
+ if (mIsQuitting) {
quitNow(); // Ignore all queued events; cleanup is complete.
}
@@ -1200,9 +1203,11 @@
}
break;
case EVENT_DISCONNECT_REQUESTED:
- mIsRunning = false;
+ if (((EventDisconnectRequestedInfo) msg.obj).shouldQuit) {
+ mIsQuitting = true;
- quitNow();
+ quitNow();
+ }
break;
default:
logUnhandledMessage(msg);
@@ -1284,10 +1289,11 @@
break;
case EVENT_DISCONNECT_REQUESTED:
+ EventDisconnectRequestedInfo info = ((EventDisconnectRequestedInfo) msg.obj);
+ mIsQuitting = info.shouldQuit;
teardownNetwork();
- String reason = ((EventDisconnectRequestedInfo) msg.obj).reason;
- if (reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) {
+ if (info.reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) {
// TODO(b/180526152): notify VcnStatusCallback for Network loss
// Will trigger EVENT_SESSION_CLOSED immediately.
@@ -1300,7 +1306,7 @@
case EVENT_SESSION_CLOSED:
mIkeSession = null;
- if (mIsRunning && mUnderlying != null) {
+ if (!mIsQuitting && mUnderlying != null) {
transitionTo(mSkipRetryTimeout ? mConnectingState : mRetryTimeoutState);
} else {
teardownNetwork();
@@ -1391,7 +1397,7 @@
transitionTo(mConnectedState);
break;
case EVENT_DISCONNECT_REQUESTED:
- handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
+ handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
break;
case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
mGatewayStatusCallback.onEnteredSafeMode();
@@ -1438,6 +1444,7 @@
mVcnContext.getVcnNetworkProvider()) {
@Override
public void unwanted() {
+ Slog.d(TAG, "NetworkAgent was unwanted");
teardownAsynchronously();
}
@@ -1471,7 +1478,7 @@
@NonNull IpSecTransform transform,
int direction) {
try {
- // TODO(b/180163196): Set underlying network of tunnel interface
+ tunnelIface.setUnderlyingNetwork(underlyingNetwork);
// Transforms do not need to be persisted; the IkeSession will keep them alive
mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform);
@@ -1577,7 +1584,7 @@
setupInterfaceAndNetworkAgent(mCurrentToken, mTunnelIface, mChildConfig);
break;
case EVENT_DISCONNECT_REQUESTED:
- handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
+ handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
break;
case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
mGatewayStatusCallback.onEnteredSafeMode();
@@ -1682,7 +1689,7 @@
transitionTo(mConnectingState);
break;
case EVENT_DISCONNECT_REQUESTED:
- handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
+ handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
break;
case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
mGatewayStatusCallback.onEnteredSafeMode();
@@ -1905,13 +1912,13 @@
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
- boolean isRunning() {
- return mIsRunning;
+ boolean isQuitting() {
+ return mIsQuitting;
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
- void setIsRunning(boolean isRunning) {
- mIsRunning = isRunning;
+ void setIsQuitting(boolean isQuitting) {
+ mIsQuitting = isQuitting;
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
@@ -1924,6 +1931,14 @@
mIkeSession = session;
}
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ void sendDisconnectRequestedAndAcquireWakelock(String reason, boolean shouldQuit) {
+ sendMessageAndAcquireWakeLock(
+ EVENT_DISCONNECT_REQUESTED,
+ TOKEN_ALL,
+ new EventDisconnectRequestedInfo(reason, shouldQuit));
+ }
+
private IkeSessionParams buildIkeParams() {
// TODO: Implement this once IkeSessionParams is persisted
return null;
diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
index bfeec01..a909695 100644
--- a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
+++ b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
@@ -67,9 +67,7 @@
mListeners.add(listener);
// Send listener all cached requests
- for (NetworkRequestEntry entry : mRequests.values()) {
- notifyListenerForEvent(listener, entry);
- }
+ resendAllRequests(listener);
}
/** Unregisters the specified listener from receiving future NetworkRequests. */
@@ -78,6 +76,14 @@
mListeners.remove(listener);
}
+ /** Sends all cached NetworkRequest(s) to the specified listener. */
+ @VisibleForTesting(visibility = Visibility.PACKAGE)
+ public void resendAllRequests(@NonNull NetworkRequestListener listener) {
+ for (NetworkRequestEntry entry : mRequests.values()) {
+ notifyListenerForEvent(listener, entry);
+ }
+ }
+
private void notifyListenerForEvent(
@NonNull NetworkRequestListener listener, @NonNull NetworkRequestEntry entry) {
listener.onNetworkRequested(entry.mRequest, entry.mScore, entry.mProviderId);
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 607da3c..e84ee672 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -120,7 +120,9 @@
if (newEffect.equals(mEffect)) {
return;
}
- mOriginalEffect = mEffect;
+ if (mOriginalEffect == null) {
+ mOriginalEffect = mEffect;
+ }
mEffect = newEffect;
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 90a763c..c9751bb 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -344,10 +344,11 @@
if (!isEffectValid(effect)) {
return;
}
- effect = fixupVibrationEffect(effect);
attrs = fixupVibrationAttributes(attrs);
Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs,
uid, opPkg, reason);
+ // Update with fixed up effect to keep the original effect in Vibration for debugging.
+ vib.updateEffect(fixupVibrationEffect(effect));
synchronized (mLock) {
Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib);
@@ -1138,6 +1139,9 @@
void dumpText(PrintWriter pw) {
pw.println("Vibrator Manager Service:");
synchronized (mLock) {
+ pw.println(" mVibrationSettings:");
+ pw.println(" " + mVibrationSettings);
+ pw.println();
pw.println(" mVibratorControllers:");
for (int i = 0; i < mVibrators.size(); i++) {
pw.println(" " + mVibrators.valueAt(i));
@@ -1146,14 +1150,15 @@
pw.println(" mCurrentVibration:");
pw.println(" " + (mCurrentVibration == null
? null : mCurrentVibration.getVibration().getDebugInfo()));
+ pw.println();
pw.println(" mNextVibration:");
pw.println(" " + (mNextVibration == null
? null : mNextVibration.getVibration().getDebugInfo()));
+ pw.println();
pw.println(" mCurrentExternalVibration:");
pw.println(" " + (mCurrentExternalVibration == null
? null : mCurrentExternalVibration.getDebugInfo()));
pw.println();
- pw.println(" mVibrationSettings=" + mVibrationSettings);
for (int i = 0; i < mPreviousVibrations.size(); i++) {
pw.println();
pw.print(" Previous vibrations for usage ");
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 3bbc81a..e6d37b6 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 @@
}
@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 33abcb4..77c369f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -132,7 +132,6 @@
import static com.android.server.wm.ActivityRecordProto.DEFER_HIDING_CLIENT;
import static com.android.server.wm.ActivityRecordProto.FILLS_PARENT;
import static com.android.server.wm.ActivityRecordProto.FRONT_OF_TASK;
-import static com.android.server.wm.ActivityRecordProto.FROZEN_BOUNDS;
import static com.android.server.wm.ActivityRecordProto.IS_ANIMATING;
import static com.android.server.wm.ActivityRecordProto.IS_WAITING_FOR_TRANSITION_START;
import static com.android.server.wm.ActivityRecordProto.LAST_ALL_DRAWN;
@@ -215,6 +214,7 @@
import static com.android.server.wm.WindowManagerService.MIN_TASK_LETTERBOX_ASPECT_RATIO;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
+import static com.android.server.wm.WindowManagerService.letterboxBackgroundTypeToString;
import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY;
import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM;
@@ -344,7 +344,6 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
-import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
@@ -582,9 +581,6 @@
private Task mLastParent;
- // Have we told the window clients to show themselves?
- private boolean mClientVisible;
-
boolean firstWindowDrawn;
/** Whether the visible window(s) of this activity is drawn. */
private boolean mReportedDrawn;
@@ -732,9 +728,6 @@
// windows, where the app hasn't had time to set a value on the window.
int mRotationAnimationHint = -1;
- ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>();
- ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>();
-
private AppSaturationInfo mLastAppSaturationInfo;
private final ColorDisplayService.ColorTransformController mColorTransformController =
@@ -915,7 +908,7 @@
pw.print(Integer.toHexString(taskDescription.getStatusBarColor()));
pw.print(" navigationBarColor=");
pw.println(Integer.toHexString(taskDescription.getNavigationBarColor()));
- pw.print(" backgroundColorFloating=");
+ pw.print(prefix); pw.print(" backgroundColorFloating=");
pw.println(Integer.toHexString(
taskDescription.getBackgroundColorFloating()));
}
@@ -1003,7 +996,7 @@
pw.print(prefix); pw.print("mOrientation=");
pw.println(ActivityInfo.screenOrientationToString(mOrientation));
pw.println(prefix + "mVisibleRequested=" + mVisibleRequested
- + " mVisible=" + mVisible + " mClientVisible=" + mClientVisible
+ + " mVisible=" + mVisible + " mClientVisible=" + isClientVisible()
+ ((mDeferHidingClient) ? " mDeferHidingClient=" + mDeferHidingClient : "")
+ " reportedDrawn=" + mReportedDrawn + " reportedVisible=" + reportedVisible);
if (paused) {
@@ -1035,10 +1028,6 @@
pw.println(" mVisibleSetFromTransferredStartingWindow="
+ mVisibleSetFromTransferredStartingWindow);
}
- if (!mFrozenBounds.isEmpty()) {
- pw.print(prefix); pw.print("mFrozenBounds="); pw.println(mFrozenBounds);
- pw.print(prefix); pw.print("mFrozenMergedConfig="); pw.println(mFrozenMergedConfig);
- }
if (mPendingRelaunchCount != 0) {
pw.print(prefix); pw.print("mPendingRelaunchCount="); pw.println(mPendingRelaunchCount);
}
@@ -1089,6 +1078,46 @@
pw.println(prefix + "configChanges=0x" + Integer.toHexString(info.configChanges));
}
}
+
+ dumpLetterboxInfo(pw, prefix);
+ }
+
+ private void dumpLetterboxInfo(PrintWriter pw, String prefix) {
+ final WindowState mainWin = findMainWindow();
+ if (mainWin == null) {
+ return;
+ }
+
+ boolean isLetterboxed = isLetterboxed(mainWin);
+ pw.println(prefix + "isLetterboxed=" + isLetterboxed);
+ if (!isLetterboxed) {
+ return;
+ }
+
+ pw.println(prefix + " letterboxReason=" + getLetterboxReasonString(mainWin));
+ pw.println(prefix + " letterboxBackgroundColor=" + Integer.toHexString(
+ getLetterboxBackgroundColor().toArgb()));
+ pw.println(prefix + " letterboxBackgroundType="
+ + letterboxBackgroundTypeToString(mWmService.getLetterboxBackgroundType()));
+ pw.println(prefix + " letterboxAspectRatio="
+ + computeAspectRatio(getBounds()));
+ }
+
+ /**
+ * Returns a string representing the reason for letterboxing. This method assumes the activity
+ * is letterboxed.
+ */
+ private String getLetterboxReasonString(WindowState mainWin) {
+ if (inSizeCompatMode()) {
+ return "SIZE_COMPAT_MODE";
+ }
+ if (isLetterboxedForFixedOrientationAndAspectRatio()) {
+ return "FIXED_ORIENTATION";
+ }
+ if (mainWin.isLetterboxedForDisplayCutout()) {
+ return "DISPLAY_CUTOUT";
+ }
+ return "UNKNOWN_REASON";
}
void setAppTimeTracker(AppTimeTracker att) {
@@ -1704,7 +1733,7 @@
keysPaused = false;
inHistory = false;
nowVisible = false;
- mClientVisible = true;
+ super.setClientVisible(true);
idle = false;
hasBeenLaunched = false;
mTaskSupervisor = supervisor;
@@ -3359,56 +3388,18 @@
return mPendingRelaunchCount > 0;
}
- boolean shouldFreezeBounds() {
- // For freeform windows, we can't freeze the bounds at the moment because this would make
- // the resizing unresponsive.
- if (task == null || task.inFreeformWindowingMode()) {
- return false;
- }
-
- // We freeze the bounds while drag resizing to deal with the time between
- // the divider/drag handle being released, and the handling it's new
- // configuration. If we are relaunched outside of the drag resizing state,
- // we need to be careful not to do this.
- return task.isDragResizing();
- }
-
@VisibleForTesting
void startRelaunching() {
if (mPendingRelaunchCount == 0) {
mRelaunchStartTime = SystemClock.elapsedRealtime();
}
- if (shouldFreezeBounds()) {
- freezeBounds();
- }
-
clearAllDrawn();
mPendingRelaunchCount++;
}
- /**
- * Freezes the task bounds. The size of this task reported the app will be fixed to the bounds
- * freezed by {@link Task#prepareFreezingBounds} until {@link #unfreezeBounds} gets called, even
- * if they change in the meantime. If the bounds are already frozen, the bounds will be frozen
- * with a queue.
- */
- private void freezeBounds() {
- mFrozenBounds.offer(new Rect(task.mPreparedFrozenBounds));
-
- if (task.mPreparedFrozenMergedConfig.equals(Configuration.EMPTY)) {
- // We didn't call prepareFreezingBounds on the task, so use the current value.
- mFrozenMergedConfig.offer(new Configuration(task.getConfiguration()));
- } else {
- mFrozenMergedConfig.offer(new Configuration(task.mPreparedFrozenMergedConfig));
- }
- // Calling unset() to make it equal to Configuration.EMPTY.
- task.mPreparedFrozenMergedConfig.unset();
- }
-
void finishRelaunching() {
mTaskSupervisor.getActivityMetricsLogger().notifyActivityRelaunched(this);
- unfreezeBounds();
if (mPendingRelaunchCount > 0) {
mPendingRelaunchCount--;
@@ -3430,30 +3421,11 @@
if (mPendingRelaunchCount == 0) {
return;
}
- unfreezeBounds();
mPendingRelaunchCount = 0;
mRelaunchStartTime = 0;
}
/**
- * Unfreezes the previously frozen bounds. See {@link #freezeBounds}.
- */
- private void unfreezeBounds() {
- if (mFrozenBounds.isEmpty()) {
- return;
- }
- mFrozenBounds.remove();
- if (!mFrozenMergedConfig.isEmpty()) {
- mFrozenMergedConfig.remove();
- }
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState win = mChildren.get(i);
- win.onUnfreezeBounds();
- }
- mWmService.mWindowPlacerLocked.performSurfacePlacement();
- }
-
- /**
* Perform clean-up of service connections in an activity record.
*/
private void cleanUpActivityServices() {
@@ -3801,7 +3773,7 @@
setVisibleRequested(true);
mVisibleSetFromTransferredStartingWindow = true;
}
- setClientVisible(fromActivity.mClientVisible);
+ setClientVisible(fromActivity.isClientVisible());
if (fromActivity.isAnimating()) {
transferAnimation(fromActivity);
@@ -4449,6 +4421,7 @@
return;
}
mVisibleRequested = visible;
+ setInsetsFrozen(!visible);
if (app != null) {
mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
}
@@ -5901,18 +5874,15 @@
return mReportedDrawn;
}
- boolean isClientVisible() {
- return mClientVisible;
- }
-
+ @Override
void setClientVisible(boolean clientVisible) {
- if (mClientVisible == clientVisible || (!clientVisible && mDeferHidingClient)) {
- return;
- }
+ // TODO(shell-transitions): Remove mDeferHidingClient once everything is shell-transitions.
+ // pip activities should just remain in clientVisible.
+ if (!clientVisible && mDeferHidingClient) return;
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"setClientVisible: %s clientVisible=%b Callers=%s", this, clientVisible,
Debug.getCallers(5));
- mClientVisible = clientVisible;
+ super.setClientVisible(clientVisible);
sendAppVisibilityToClients();
}
@@ -7456,8 +7426,7 @@
final int containingAppWidth = containingAppBounds.width();
final int containingAppHeight = containingAppBounds.height();
- final float containingRatio = Math.max(containingAppWidth, containingAppHeight)
- / (float) Math.min(containingAppWidth, containingAppHeight);
+ final float containingRatio = computeAspectRatio(containingAppBounds);
int activityWidth = containingAppWidth;
int activityHeight = containingAppHeight;
@@ -7521,6 +7490,18 @@
}
/**
+ * Returns the aspect ratio of the given {@code rect}.
+ */
+ private static float computeAspectRatio(Rect rect) {
+ final int width = rect.width();
+ final int height = rect.height();
+ if (width == 0 || height == 0) {
+ return 0;
+ }
+ return Math.max(width, height) / (float) Math.min(width, height);
+ }
+
+ /**
* @return {@code true} if this activity was reparented to another display but
* {@link #ensureActivityConfiguration} is not called.
*/
@@ -8199,7 +8180,7 @@
proto.write(TRANSLUCENT, !occludesParent());
proto.write(VISIBLE, mVisible);
proto.write(VISIBLE_REQUESTED, mVisibleRequested);
- proto.write(CLIENT_VISIBLE, mClientVisible);
+ proto.write(CLIENT_VISIBLE, isClientVisible());
proto.write(DEFER_HIDING_CLIENT, mDeferHidingClient);
proto.write(REPORTED_DRAWN, mReportedDrawn);
proto.write(REPORTED_VISIBLE, reportedVisible);
@@ -8214,9 +8195,6 @@
proto.write(STARTING_MOVED, startingMoved);
proto.write(VISIBLE_SET_FROM_TRANSFERRED_STARTING_WINDOW,
mVisibleSetFromTransferredStartingWindow);
- for (Rect bounds : mFrozenBounds) {
- bounds.dumpDebug(proto, FROZEN_BOUNDS);
- }
proto.write(STATE, mState.toString());
proto.write(FRONT_OF_TASK, isRootOfTask());
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a874dee..0c77d9f 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4042,17 +4042,9 @@
int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
boolean persistent, int userId) {
- final DisplayContent defaultDisplay =
- mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY);
-
mTempConfig.setTo(getGlobalConfiguration());
final int changes = mTempConfig.updateFrom(values);
if (changes == 0) {
- // Since calling to Activity.setRequestedOrientation leads to freezing the window with
- // setting WindowManagerService.mWaitingForConfig to true, it is important that we call
- // performDisplayOverrideConfigUpdate in order to send the new display configuration
- // (even if there are no actual changes) to unfreeze the window.
- defaultDisplay.performDisplayOverrideConfigUpdate(values);
return 0;
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 5b685b4..99289e0 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -1305,7 +1305,7 @@
if (isTransitionSet()) {
clear();
mNextAppTransitionType = NEXT_TRANSIT_TYPE_REMOTE;
- mRemoteAnimationController = new RemoteAnimationController(mService,
+ mRemoteAnimationController = new RemoteAnimationController(mService, mDisplayContent,
remoteAnimationAdapter, mHandler);
}
}
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 309b5ec..62a0080 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -141,14 +141,17 @@
mChangeListeners.get(i).onMergedOverrideConfigurationChanged(
mMergedOverrideConfiguration);
}
- dispatchConfigurationToChildren();
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ dispatchConfigurationToChild(getChildAt(i), mFullConfiguration);
+ }
}
- void dispatchConfigurationToChildren() {
- for (int i = getChildCount() - 1; i >= 0; --i) {
- final ConfigurationContainer child = getChildAt(i);
- child.onConfigurationChanged(mFullConfiguration);
- }
+ /**
+ * Dispatches the configuration to child when {@link #onConfigurationChanged(Configuration)} is
+ * called. This allows the derived classes to override how to dispatch the configuration.
+ */
+ void dispatchConfigurationToChild(E child, Configuration config) {
+ child.onConfigurationChanged(config);
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 759b7fe..5ccf576 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 @@
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 eff4ea6..119ffb7 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 @@
mWmService.mDisplayWindowSettings.setForcedSize(this, width, height);
}
+ @Override
void getStableRect(Rect out) {
final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
out.set(state.getDisplayFrame());
@@ -2943,10 +2944,6 @@
return dockFrame.bottom - imeFrame.top;
}
- void prepareFreezingTaskBounds() {
- forAllRootTasks(Task::prepareFreezingTaskBounds);
- }
-
void rotateBounds(@Rotation int oldRotation, @Rotation int newRotation, Rect inOutBounds) {
// Get display bounds on oldRotation as parent bounds for the rotation.
getBounds(mTmpRect, oldRotation);
@@ -3703,8 +3700,8 @@
// app.
assignWindowLayers(true /* setLayoutNeeded */);
// 3. The z-order of IME might have been changed. Update the above insets state.
- mInsetsStateController.updateAboveInsetsState(
- mInputMethodWindow, true /* notifyInsetsChange */);
+ mInsetsStateController.updateAboveInsetsState(mInputMethodWindow,
+ mInsetsStateController.getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME));
// 4. Update the IME control target to apply any inset change and animation.
// 5. Reparent the IME container surface to either the input target app, or the IME window
// parent.
@@ -4105,13 +4102,6 @@
*/
void onWindowAnimationFinished(@NonNull WindowContainer wc, int type) {
if (type == ANIMATION_TYPE_APP_TRANSITION || type == ANIMATION_TYPE_RECENTS) {
- // Unfreeze the insets state of the frozen target when the animation finished if exists.
- final Task task = wc.asTask();
- if (task != null) {
- task.forAllWindows(w -> {
- w.clearFrozenInsetsState();
- }, true /* traverseTopToBottom */);
- }
removeImeSurfaceImmediately();
}
}
@@ -4833,7 +4823,7 @@
}
mNoAnimationNotifyOnTransitionFinished.clear();
- mWallpaperController.hideDeferredWallpapersIfNeeded();
+ mWallpaperController.hideDeferredWallpapersIfNeededLegacy();
onAppTransitionDone();
@@ -5272,13 +5262,6 @@
|| windowingMode == WINDOWING_MODE_MULTI_WINDOW);
}
- static boolean canReuseExistingTask(int windowingMode, int activityType) {
- // Existing Tasks can be reused if a new root task will be created anyway, or for the
- // Dream - because there can only ever be one DreamActivity.
- return alwaysCreateRootTask(windowingMode, activityType)
- || activityType == ACTIVITY_TYPE_DREAM;
- }
-
@Nullable
Task getFocusedRootTask() {
return getItemFromTaskDisplayAreas(TaskDisplayArea::getFocusedRootTask);
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 1f7e152..316c20b 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -99,6 +99,9 @@
mTask.forAllActivities(a -> {
setActivityVisibilityState(a, starting, resumeTopActivity);
});
+ if (mTask.mAtmService.getTransitionController().getTransitionPlayer() != null) {
+ mTask.getDisplayContent().mWallpaperController.adjustWallpaperWindows();
+ }
}
private void setActivityVisibilityState(ActivityRecord r, ActivityRecord starting,
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 27ef147..28c5a6d9 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -410,15 +410,19 @@
return;
}
+ requestFocus(focusToken, focus.getName());
+ }
+
+ private void requestFocus(IBinder focusToken, String windowName) {
if (focusToken == mInputFocus) {
return;
}
mInputFocus = focusToken;
- mInputTransaction.setFocusedWindow(mInputFocus, focus.getName(), mDisplayId);
- EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Focus request " + focus,
+ mInputTransaction.setFocusedWindow(mInputFocus, windowName, mDisplayId);
+ EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Focus request " + windowName,
"reason=UpdateInputWindows");
- ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Focus requested for window=%s", focus);
+ ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Focus requested for window=%s", windowName);
}
void setFocusedAppLw(ActivityRecord newApp) {
@@ -470,6 +474,8 @@
boolean mInDrag;
+ private boolean mRecentsAnimationFocusOverride;
+
private void updateInputWindows(boolean inDrag) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
@@ -485,10 +491,16 @@
mInDrag = inDrag;
resetInputConsumers(mInputTransaction);
-
+ mRecentsAnimationFocusOverride = false;
mDisplayContent.forAllWindows(this, true /* traverseTopToBottom */);
- updateInputFocusRequest();
+ if (mRecentsAnimationFocusOverride) {
+ requestFocus(mRecentsAnimationInputConsumer.mWindowHandle.token,
+ mRecentsAnimationInputConsumer.mName);
+ } else {
+ updateInputFocusRequest();
+ }
+
if (!mUpdateInputWindowsImmediately) {
mDisplayContent.getPendingTransaction().merge(mInputTransaction);
@@ -526,6 +538,7 @@
mRecentsAnimationInputConsumer.mWindowHandle)) {
mRecentsAnimationInputConsumer.show(mInputTransaction, w.mActivityRecord);
mAddRecentsAnimationInputConsumerHandle = false;
+ mRecentsAnimationFocusOverride = true;
}
}
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index e18516d..62c155a 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 @@
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 @@
* 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 @@
} 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/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
index 5d6d513..e6e866d 100644
--- a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
@@ -16,6 +16,12 @@
package com.android.server.wm;
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_FRONT;
+import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_CLOSE;
+
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
import static com.android.server.wm.AnimationAdapterProto.REMOTE;
import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
@@ -26,6 +32,7 @@
import android.util.proto.ProtoOutputStream;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
+import android.view.WindowManager;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.policy.WindowManagerPolicy;
@@ -35,7 +42,7 @@
class NonAppWindowAnimationAdapter implements AnimationAdapter {
- private final WindowState mWindow;
+ private final WindowContainer mWindow;
private RemoteAnimationTarget mTarget;
private SurfaceControl mCapturedLeash;
@@ -47,22 +54,43 @@
return false;
}
- NonAppWindowAnimationAdapter(WindowState w,
- long durationHint, long statusBarTransitionDelay) {
+ NonAppWindowAnimationAdapter(WindowContainer w, long durationHint,
+ long statusBarTransitionDelay) {
mWindow = w;
mDurationHint = durationHint;
mStatusBarTransitionDelay = statusBarTransitionDelay;
}
+ static RemoteAnimationTarget[] startNonAppWindowAnimations(WindowManagerService service,
+ DisplayContent displayContent, @WindowManager.TransitionOldType int transit,
+ long durationHint, long statusBarTransitionDelay) {
+ final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
+ if (transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
+ || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER) {
+ startNonAppWindowAnimationsForKeyguardExit(
+ service, durationHint, statusBarTransitionDelay, targets);
+ } else if (transit == TRANSIT_OLD_TASK_OPEN || transit == TRANSIT_OLD_TASK_TO_FRONT
+ || transit == TRANSIT_OLD_WALLPAPER_CLOSE) {
+ final boolean shouldAttachNavBarToApp =
+ displayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
+ && service.getRecentsAnimationController() == null
+ && displayContent.getFixedRotationAnimationController() == null;
+ if (shouldAttachNavBarToApp) {
+ startNavigationBarWindowAnimation(
+ displayContent, durationHint, statusBarTransitionDelay, targets);
+ }
+ }
+ return targets.toArray(new RemoteAnimationTarget[targets.size()]);
+ }
+
/**
* Creates and starts remote animations for all the visible non app windows.
*
* @return RemoteAnimationTarget[] targets for all the visible non app windows
*/
- public static RemoteAnimationTarget[] startNonAppWindowAnimationsForKeyguardExit(
- WindowManagerService service, long durationHint, long statusBarTransitionDelay) {
- final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
-
+ private static void startNonAppWindowAnimationsForKeyguardExit(WindowManagerService service,
+ long durationHint, long statusBarTransitionDelay,
+ ArrayList<RemoteAnimationTarget> targets) {
final WindowManagerPolicy policy = service.mPolicy;
service.mRoot.forAllWindows(nonAppWindow -> {
if (nonAppWindow.mActivityRecord == null && policy.canBeHiddenByKeyguardLw(nonAppWindow)
@@ -74,7 +102,22 @@
targets.add(nonAppAdapter.createRemoteAnimationTarget());
}
}, true /* traverseTopToBottom */);
- return targets.toArray(new RemoteAnimationTarget[targets.size()]);
+ }
+
+ /**
+ * Creates and starts remote animation for the navigation bar windows.
+ *
+ * @return RemoteAnimationTarget[] targets for all the visible non app windows
+ */
+ private static void startNavigationBarWindowAnimation(DisplayContent displayContent,
+ long durationHint, long statusBarTransitionDelay,
+ ArrayList<RemoteAnimationTarget> targets) {
+ final WindowState navWindow = displayContent.getDisplayPolicy().getNavigationBar();
+ final NonAppWindowAnimationAdapter nonAppAdapter = new NonAppWindowAnimationAdapter(
+ navWindow.mToken, durationHint, statusBarTransitionDelay);
+ navWindow.mToken.startAnimation(navWindow.mToken.getPendingTransaction(),
+ nonAppAdapter, false /* hidden */, ANIMATION_TYPE_WINDOW_ANIMATION);
+ targets.add(nonAppAdapter.createRemoteAnimationTarget());
}
/**
@@ -84,7 +127,7 @@
mTarget = new RemoteAnimationTarget(-1, -1, getLeash(), false,
new Rect(), null, mWindow.getPrefixOrderIndex(), mWindow.getLastSurfacePosition(),
mWindow.getBounds(), null, mWindow.getWindowConfiguration(), true, null, null,
- null);
+ null, mWindow.getWindowType());
return mTarget;
}
@@ -120,8 +163,8 @@
@Override
public void dump(PrintWriter pw, String prefix) {
pw.print(prefix);
- pw.print("token=");
- pw.println(mWindow.mToken);
+ pw.print("window=");
+ pw.println(mWindow);
if (mTarget != null) {
pw.print(prefix);
pw.println("Target:");
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 42cb96f..31663b7 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -16,9 +16,6 @@
package com.android.server.wm;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
-
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
import static com.android.server.wm.AnimationAdapterProto.REMOTE;
import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
@@ -60,6 +57,7 @@
private static final long TIMEOUT_MS = 2000;
private final WindowManagerService mService;
+ private final DisplayContent mDisplayContent;
private final RemoteAnimationAdapter mRemoteAnimationAdapter;
private final ArrayList<RemoteAnimationRecord> mPendingAnimations = new ArrayList<>();
private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations =
@@ -72,9 +70,10 @@
private boolean mCanceled;
private boolean mLinkedToDeathOfRunner;
- RemoteAnimationController(WindowManagerService service,
+ RemoteAnimationController(WindowManagerService service, DisplayContent displayContent,
RemoteAnimationAdapter remoteAnimationAdapter, Handler handler) {
mService = service;
+ mDisplayContent = displayContent;
mRemoteAnimationAdapter = remoteAnimationAdapter;
mHandler = handler;
}
@@ -221,12 +220,11 @@
private RemoteAnimationTarget[] createNonAppWindowAnimations(
@WindowManager.TransitionOldType int transit) {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createNonAppWindowAnimations()");
- return (transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
- || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER)
- ? NonAppWindowAnimationAdapter.startNonAppWindowAnimationsForKeyguardExit(mService,
+ return NonAppWindowAnimationAdapter.startNonAppWindowAnimations(mService,
+ mDisplayContent,
+ transit,
mRemoteAnimationAdapter.getDuration(),
- mRemoteAnimationAdapter.getStatusBarTransitionDelay())
- : new RemoteAnimationTarget[0];
+ mRemoteAnimationAdapter.getStatusBarTransitionDelay());
}
private void onAnimationFinished() {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 3f9ea1f..0e8cadb 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -650,28 +650,12 @@
}
@Override
- void dispatchConfigurationToChildren() {
- final Configuration configuration = getConfiguration();
- for (int i = getChildCount() - 1; i >= 0; i--) {
- final DisplayContent displayContent = getChildAt(i);
- if (displayContent.isDefaultDisplay) {
- // The global configuration is also the override configuration of default display.
- displayContent.performDisplayOverrideConfigUpdate(configuration);
- } else {
- displayContent.onConfigurationChanged(configuration);
- }
- }
- }
-
- @Override
- public void onConfigurationChanged(Configuration newParentConfig) {
- prepareFreezingTaskBounds();
- super.onConfigurationChanged(newParentConfig);
- }
-
- private void prepareFreezingTaskBounds() {
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- mChildren.get(i).prepareFreezingTaskBounds();
+ void dispatchConfigurationToChild(DisplayContent child, Configuration config) {
+ if (child.isDefaultDisplay) {
+ // The global configuration is also the override configuration of default display.
+ child.performDisplayOverrideConfigUpdate(config);
+ } else {
+ child.onConfigurationChanged(config);
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 68f5c58..f850e85 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -21,6 +21,7 @@
import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -498,9 +499,6 @@
// TODO: Make final
int mUserId;
- final Rect mPreparedFrozenBounds = new Rect();
- final Configuration mPreparedFrozenMergedConfig = new Configuration();
-
// Id of the previous display the root task was on.
int mPrevDisplayId = INVALID_DISPLAY;
@@ -1182,10 +1180,6 @@
mTaskSupervisor.mNoAnimActivities.add(topActivity);
}
- // We might trigger a configuration change. Save the current task bounds for freezing.
- // TODO: Should this call be moved inside the resize method in WM?
- toRootTask.prepareFreezingTaskBounds();
-
if (toRootTaskWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
&& moveRootTaskMode == REPARENT_KEEP_ROOT_TASK_AT_FRONT) {
// Move recents to front so it is not behind root home task when going into docked
@@ -3363,15 +3357,6 @@
return isResizeable();
}
- /**
- * Prepares the task bounds to be frozen with the current size. See
- * {@link ActivityRecord#freezeBounds}.
- */
- void prepareFreezingBounds() {
- mPreparedFrozenBounds.set(getBounds());
- mPreparedFrozenMergedConfig.setTo(getConfiguration());
- }
-
@Override
void getAnimationFrames(Rect outFrame, Rect outInsets, Rect outStableInsets,
Rect outSurfaceInsets) {
@@ -7365,6 +7350,7 @@
return reuseOrCreateTask(info, intent, null /*voiceSession*/, null /*voiceInteractor*/,
toTop, null /*activity*/, null /*source*/, null /*options*/);
}
+
// TODO: Can be removed once we change callpoints creating root tasks to be creating tasks.
/** Either returns this current task to be re-used or creates a new child task. */
Task reuseOrCreateTask(ActivityInfo info, Intent intent, IVoiceInteractionSession voiceSession,
@@ -7372,7 +7358,7 @@
ActivityRecord source, ActivityOptions options) {
Task task;
- if (DisplayContent.canReuseExistingTask(getWindowingMode(), getActivityType())) {
+ if (canReuseAsLeafTask()) {
// This root task will only contain one task, so just return itself since all root
// tasks ara now tasks and all tasks are now root tasks.
task = reuseAsLeafTask(voiceSession, voiceInteractor, intent, info, activity);
@@ -7407,10 +7393,24 @@
return task;
}
+ /** Return {@code true} if this task can be reused as leaf task. */
+ private boolean canReuseAsLeafTask() {
+ // Cannot be reused as leaf task if this task is created by organizer or having child tasks.
+ if (mCreatedByOrganizer || !isLeafTask()) {
+ return false;
+ }
+
+ // Existing Tasks can be reused if a new root task will be created anyway, or for the
+ // Dream - because there can only ever be one DreamActivity.
+ final int windowingMode = getWindowingMode();
+ final int activityType = getActivityType();
+ return DisplayContent.alwaysCreateRootTask(windowingMode, activityType)
+ || activityType == ACTIVITY_TYPE_DREAM;
+ }
+
void addChild(WindowContainer child, final boolean toTop, boolean showForAllUsers) {
Task task = child.asTask();
try {
-
if (task != null) {
task.setForceShowForAllUsers(showForAllUsers);
}
@@ -7503,10 +7503,6 @@
});
}
- void prepareFreezingTaskBounds() {
- forAllLeafTasks(Task::prepareFreezingBounds, true /* traverseTopToBottom */);
- }
-
private int setBounds(Rect existing, Rect bounds) {
if (equivalentBounds(existing, bounds)) {
return BOUNDS_CHANGE_NONE;
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 0be43ab..ee5c1f01 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 @@
// 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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
// 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 @@
&& 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 @@
&& 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 @@
// 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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
}
// 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 @@
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 @@
}
}
- 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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
mTmpBounds.set(inOutBounds);
while (boundsConflict(taskBoundsToCheck, mTmpBounds)
- && displayBounds.contains(mTmpBounds)) {
- shiftBounds(direction, displayBounds, mTmpBounds);
+ && displayAreaBounds.contains(mTmpBounds)) {
+ shiftBounds(direction, displayAreaBounds, mTmpBounds);
}
if (!boundsConflict(taskBoundsToCheck, mTmpBounds)
- && displayBounds.contains(mTmpBounds)) {
+ && displayAreaBounds.contains(mTmpBounds)) {
// Found a candidate. Just use this.
inOutBounds.set(mTmpBounds);
if (DEBUG) appendLog("avoid-bounds-conflict=" + inOutBounds);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 98eb11f..ee4c66d 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -135,6 +135,11 @@
mSyncId = mSyncEngine.startSyncSet(this);
}
+ @VisibleForTesting
+ int getSyncId() {
+ return mSyncId;
+ }
+
/**
* Formally starts the transition. Participants can be collected before this is started,
* but this won't consider itself ready until started -- even if all the participants have
@@ -271,12 +276,17 @@
// Commit all going-invisible containers
for (int i = 0; i < mParticipants.size(); ++i) {
final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
- if (ar == null || ar.mVisibleRequested) {
- continue;
+ if (ar != null && !ar.isVisibleRequested()) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " Commit activity becoming invisible: %s", ar);
+ ar.commitVisibility(false /* visible */, false /* performLayout */);
}
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " Commit activity becoming invisible: %s", ar);
- ar.commitVisibility(false /* visible */, false /* performLayout */);
+ final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken();
+ if (wt != null && !wt.isVisibleRequested()) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " Commit wallpaper becoming invisible: %s", ar);
+ wt.commitVisibility(false /* visible */);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 1a3138d..7c5afa8 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -126,12 +126,18 @@
}
mFindResults.resetTopWallpaper = true;
- if (w.mActivityRecord != null && !w.mActivityRecord.isVisible()
- && !w.mActivityRecord.isAnimating(TRANSITION | PARENTS)) {
-
- // If this window's app token is hidden and not animating, it is of no interest to us.
- if (DEBUG_WALLPAPER) Slog.v(TAG, "Skipping hidden and not animating token: " + w);
- return false;
+ if (mService.mAtmService.getTransitionController().getTransitionPlayer() == null) {
+ if (w.mActivityRecord != null && !w.mActivityRecord.isVisible()
+ && !w.mActivityRecord.isAnimating(TRANSITION | PARENTS)) {
+ // If this window's app token is hidden and not animating, it is of no interest.
+ if (DEBUG_WALLPAPER) Slog.v(TAG, "Skipping hidden and not animating token: " + w);
+ return false;
+ }
+ } else {
+ if (w.mActivityRecord != null && !w.mActivityRecord.isVisibleRequested()) {
+ // An activity that is not going to remain visible shouldn't be the target.
+ return false;
+ }
}
if (DEBUG_WALLPAPER) Slog.v(TAG, "Win " + w + ": isOnScreen=" + w.isOnScreen()
+ " mDrawState=" + w.mWinAnimator.mDrawState);
@@ -227,7 +233,10 @@
}
boolean isWallpaperVisible() {
- return isWallpaperVisible(mWallpaperTarget);
+ for (int i = mWallpaperTokens.size() - 1; i >= 0; --i) {
+ if (mWallpaperTokens.get(i).isVisible()) return true;
+ }
+ return false;
}
/**
@@ -240,7 +249,7 @@
}
}
- private boolean isWallpaperVisible(WindowState wallpaperTarget) {
+ private boolean shouldWallpaperBeVisible(WindowState wallpaperTarget) {
if (DEBUG_WALLPAPER) {
Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + " prev="
+ mPrevWallpaperTarget);
@@ -255,18 +264,18 @@
}
void updateWallpaperVisibility() {
- final boolean visible = isWallpaperVisible(mWallpaperTarget);
+ final boolean visible = shouldWallpaperBeVisible(mWallpaperTarget);
for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
- token.updateWallpaperVisibility(visible);
+ token.setVisibility(visible);
}
}
- void hideDeferredWallpapersIfNeeded() {
- if (mDeferredHideWallpaper != null) {
- hideWallpapers(mDeferredHideWallpaper);
- mDeferredHideWallpaper = null;
+ void hideDeferredWallpapersIfNeededLegacy() {
+ for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
+ final WallpaperWindowToken token = mWallpaperTokens.get(i);
+ token.commitVisibility(false);
}
}
@@ -275,18 +284,9 @@
&& (mWallpaperTarget != winGoingAway || mPrevWallpaperTarget != null)) {
return;
}
- if (mWallpaperTarget != null
- && mWallpaperTarget.getDisplayContent().mAppTransition.isRunning()) {
- // Defer hiding the wallpaper when app transition is running until the animations
- // are done.
- mDeferredHideWallpaper = winGoingAway;
- return;
- }
-
- final boolean wasDeferred = (mDeferredHideWallpaper == winGoingAway);
for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
final WallpaperWindowToken token = mWallpaperTokens.get(i);
- token.hideWallpaperToken(wasDeferred, "hideWallpapers");
+ token.setVisibility(false);
if (DEBUG_WALLPAPER_LIGHT && token.isVisible()) {
Slog.d(TAG, "Hiding wallpaper " + token
+ " from " + winGoingAway + " target=" + mWallpaperTarget + " prev="
@@ -616,7 +616,7 @@
// The window is visible to the compositor...but is it visible to the user?
// That is what the wallpaper cares about.
- final boolean visible = mWallpaperTarget != null && isWallpaperVisible(mWallpaperTarget);
+ final boolean visible = mWallpaperTarget != null;
if (DEBUG_WALLPAPER) {
Slog.v(TAG, "Wallpaper visibility: " + visible + " at display "
+ mDisplayContent.getDisplayId());
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 43303d4..717775605 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -19,7 +19,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -34,6 +34,8 @@
import android.view.WindowManager;
import android.view.animation.Animation;
+import com.android.internal.protolog.common.ProtoLog;
+
import java.util.function.Consumer;
/**
@@ -43,6 +45,8 @@
private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperWindowToken" : TAG_WM;
+ private boolean mVisibleRequested = false;
+
WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit,
DisplayContent dc, boolean ownerCanManageAppTokens) {
this(service, token, explicit, dc, ownerCanManageAppTokens, null /* options */);
@@ -57,18 +61,16 @@
}
@Override
+ WallpaperWindowToken asWallpaperToken() {
+ return this;
+ }
+
+ @Override
void setExiting() {
super.setExiting();
mDisplayContent.mWallpaperController.removeWallpaperToken(this);
}
- void hideWallpaperToken(boolean wasDeferred, String reason) {
- for (int j = mChildren.size() - 1; j >= 0; j--) {
- final WindowState wallpaper = mChildren.get(j);
- wallpaper.hideWallpaperWindow(wasDeferred, reason);
- }
- }
-
void sendWindowWallpaperCommand(
String action, int x, int y, int z, Bundle extras, boolean sync) {
for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
@@ -93,24 +95,6 @@
}
}
- void updateWallpaperVisibility(boolean visible) {
- if (isVisible() != visible) {
- mWmService.mAtmService.getTransitionController().collect(this);
- // Need to do a layout to ensure the wallpaper now has the correct size.
- mDisplayContent.setLayoutNeeded();
- }
-
- final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
- for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
- final WindowState wallpaper = mChildren.get(wallpaperNdx);
- if (visible) {
- wallpaperController.updateWallpaperOffset(wallpaper, false /* sync */);
- }
-
- wallpaper.dispatchWallpaperVisibility(visible);
- }
- }
-
/**
* Starts {@param anim} on all children.
*/
@@ -122,16 +106,16 @@
}
void updateWallpaperWindows(boolean visible) {
-
if (isVisible() != visible) {
if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG,
"Wallpaper token " + token + " visible=" + visible);
- mWmService.mAtmService.getTransitionController().collect(this);
- // Need to do a layout to ensure the wallpaper now has the correct size.
- mDisplayContent.setLayoutNeeded();
+ setVisibility(visible);
+ }
+ final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
+ if (mWmService.mAtmService.getTransitionController().getTransitionPlayer() != null) {
+ return;
}
- final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
final WindowState wallpaperTarget = wallpaperController.getWallpaperTarget();
if (visible && wallpaperTarget != null) {
@@ -153,21 +137,54 @@
}
}
- for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
- final WindowState wallpaper = mChildren.get(wallpaperNdx);
+ setVisible(visible);
+ }
- if (visible) {
- wallpaperController.updateWallpaperOffset(wallpaper, false /* sync */);
+ private void setVisible(boolean visible) {
+ final boolean wasClientVisible = isClientVisible();
+ setClientVisible(visible);
+ if (visible && !wasClientVisible) {
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ final WindowState wallpaper = mChildren.get(i);
+ wallpaper.requestUpdateWallpaperIfNeeded();
}
-
- // First, make sure the client has the current visibility state.
- wallpaper.dispatchWallpaperVisibility(visible);
-
- if (DEBUG_LAYERS || DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "adjustWallpaper win "
- + wallpaper);
}
}
+ /**
+ * Sets the requested visibility of this token. The visibility may not be if this is part of a
+ * transition. In that situation, make sure to call {@link #commitVisibility} when done.
+ */
+ void setVisibility(boolean visible) {
+ // Before setting mVisibleRequested so we can track changes.
+ mWmService.mAtmService.getTransitionController().collect(this);
+
+ setVisibleRequested(visible);
+
+ // If in a transition, defer commits for activities that are going invisible
+ if (!visible && (mWmService.mAtmService.getTransitionController().inTransition()
+ || getDisplayContent().mAppTransition.isRunning())) {
+ return;
+ }
+
+ commitVisibility(visible);
+ }
+
+ /**
+ * Commits the visibility of this token. This will directly update the visibility without
+ * regard for other state (like being in a transition).
+ */
+ void commitVisibility(boolean visible) {
+ if (visible == isVisible()) return;
+
+ ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS,
+ "commitVisibility: %s: visible=%b mVisibleRequested=%b", this,
+ isVisible(), mVisibleRequested);
+
+ setVisibleRequested(visible);
+ setVisible(visible);
+ }
+
@Override
void adjustWindowParams(WindowState win, WindowManager.LayoutParams attrs) {
if (attrs.height == ViewGroup.LayoutParams.MATCH_PARENT
@@ -186,9 +203,10 @@
}
boolean hasVisibleNotDrawnWallpaper() {
+ if (!isVisible()) return false;
for (int j = mChildren.size() - 1; j >= 0; --j) {
final WindowState wallpaper = mChildren.get(j);
- if (wallpaper.hasVisibleNotDrawnWallpaper()) {
+ if (!wallpaper.isDrawn() && wallpaper.isVisible()) {
return true;
}
}
@@ -210,6 +228,21 @@
return false;
}
+ void setVisibleRequested(boolean visible) {
+ if (mVisibleRequested == visible) return;
+ mVisibleRequested = visible;
+ setInsetsFrozen(!visible);
+ }
+
+ @Override
+ boolean isVisibleRequested() {
+ return mVisibleRequested;
+ }
+
+ @Override
+ boolean isVisible() {
+ return isClientVisible();
+ }
@Override
public String toString() {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index dd4ee877..b3d2afb 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -29,6 +29,7 @@
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.os.UserHandle.USER_NULL;
import static android.view.SurfaceControl.Transaction;
+import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
@@ -2684,14 +2685,6 @@
@Nullable ArrayList<WindowContainer> sources) {
final Task task = asTask();
if (task != null && !enter && !task.isHomeOrRecentsRootTask()) {
- if (AppTransition.isClosingTransitOld(transit)) {
- // Freezes the insets state when the window is in app exiting transition, to
- // ensure the exiting window won't receive unexpected insets changes from the
- // next window.
- task.forAllWindows(w -> {
- w.freezeInsetsState();
- }, true /* traverseTopToBottom */);
- }
mDisplayContent.showImeScreenshot();
}
final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
@@ -3068,6 +3061,11 @@
}
/** Cheap way of doing cast and instanceof. */
+ WallpaperWindowToken asWallpaperToken() {
+ return null;
+ }
+
+ /** Cheap way of doing cast and instanceof. */
DisplayArea asDisplayArea() {
return null;
}
@@ -3307,4 +3305,8 @@
mListeners.remove(listener);
unregisterConfigurationChangeListener(listener);
}
+
+ @WindowManager.LayoutParams.WindowType int getWindowType() {
+ return INVALID_WINDOW_TYPE;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index e183ea0..7450782 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 @@
/**
* Is trace enabled or not.
*/
- boolean isEnabled();
+ boolean isAccessibilityTracingEnabled();
/**
* Add an accessibility trace entry.
@@ -667,4 +667,13 @@
* 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 cec0321..c9e1605 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3971,6 +3971,21 @@
? backgroundType : LETTERBOX_BACKGROUND_SOLID_COLOR;
}
+ /** Returns a string representing the given {@link LetterboxBackgroundType}. */
+ static String letterboxBackgroundTypeToString(
+ @LetterboxBackgroundType int backgroundType) {
+ switch (backgroundType) {
+ case LETTERBOX_BACKGROUND_SOLID_COLOR:
+ return "LETTERBOX_BACKGROUND_SOLID_COLOR";
+ case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND:
+ return "LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND";
+ case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING:
+ return "LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING";
+ default:
+ return "unknown=" + backgroundType;
+ }
+ }
+
@Override
public void setIgnoreOrientationRequest(int displayId, boolean ignoreOrientationRequest) {
if (!checkCallingPermission(
@@ -8024,6 +8039,11 @@
return dc.getImeTarget(IME_TARGET_LAYERING).getWindow().getName();
}
}
+
+ @Override
+ public boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) {
+ return WindowManagerService.this.shouldRestoreImeVisibility(imeTargetWindowToken);
+ }
}
void registerAppFreezeListener(AppFreezeListener listener) {
@@ -8681,6 +8701,22 @@
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 2a9e08e..0a4451f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -141,11 +141,9 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_POWER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.WINDOW_STATE_BLAST_SYNC_TIMEOUT;
@@ -358,7 +356,6 @@
private boolean mForceHideNonSystemOverlayWindow;
boolean mAppFreezing;
boolean mHidden = true; // Used to determine if to show child windows.
- boolean mWallpaperVisible; // for wallpaper, what was last vis report?
private boolean mDragResizing;
private boolean mDragResizingChangeReported = true;
private int mResizeMode;
@@ -797,7 +794,7 @@
* {@link InsetsStateController#notifyInsetsChanged}.
*/
boolean isReadyToDispatchInsetsState() {
- return isVisible() && mFrozenInsetsState == null;
+ return isVisibleRequested() && mFrozenInsetsState == null;
}
void seamlesslyRotateIfAllowed(Transaction transaction, @Rotation int oldRotation,
@@ -1190,16 +1187,6 @@
layoutYDiff = 0;
} else {
windowFrames.mContainingFrame.set(getBounds());
- if (mActivityRecord != null && !mActivityRecord.mFrozenBounds.isEmpty()) {
-
- // If the bounds are frozen, we still want to translate the window freely and only
- // freeze the size.
- Rect frozen = mActivityRecord.mFrozenBounds.peek();
- windowFrames.mContainingFrame.right =
- windowFrames.mContainingFrame.left + frozen.width();
- windowFrames.mContainingFrame.bottom =
- windowFrames.mContainingFrame.top + frozen.height();
- }
// IME is up and obscuring this window. Adjust the window position so it is visible.
if (isImeTarget) {
if (inFreeformWindowingMode()) {
@@ -1725,7 +1712,11 @@
@Override
boolean isVisibleRequested() {
- return isVisible() && (mActivityRecord == null || mActivityRecord.isVisibleRequested());
+ if (mToken != null && (mActivityRecord != null || mToken.asWallpaperToken() != null)) {
+ // Currently only ActivityRecord and WallpaperToken support visibleRequested.
+ return isVisible() && mToken.isVisibleRequested();
+ }
+ return isVisible();
}
/**
@@ -1755,8 +1746,11 @@
* {@code false} otherwise.
*/
boolean wouldBeVisibleIfPolicyIgnored() {
- return mHasSurface && !isParentWindowHidden()
- && !mAnimatingExit && !mDestroying && (!mIsWallpaper || mWallpaperVisible);
+ if (!mHasSurface || isParentWindowHidden() || mAnimatingExit || mDestroying) {
+ return false;
+ }
+ final boolean isWallpaper = mToken != null && mToken.asWallpaperToken() != null;
+ return !isWallpaper || mToken.isVisible();
}
/**
@@ -1814,6 +1808,10 @@
return ((!isParentWindowHidden() && atoken.isVisible())
|| isAnimating(TRANSITION | PARENTS));
}
+ final WallpaperWindowToken wtoken = mToken.asWallpaperToken();
+ if (wtoken != null) {
+ return !isParentWindowHidden() && wtoken.isVisible();
+ }
return !isParentWindowHidden() || isAnimating(TRANSITION | PARENTS);
}
@@ -1953,8 +1951,9 @@
// When there is keyguard, wallpaper could be placed over the secure app
// window but invisible. We need to check wallpaper visibility explicitly
// to determine if it's occluding apps.
- return ((!mIsWallpaper && mAttrs.format == PixelFormat.OPAQUE)
- || (mIsWallpaper && mWallpaperVisible))
+ final boolean isWallpaper = mToken != null && mToken.asWallpaperToken() != null;
+ return ((!isWallpaper && mAttrs.format == PixelFormat.OPAQUE)
+ || (isWallpaper && mToken.isVisible()))
&& isDrawn() && !isAnimating(TRANSITION | PARENTS);
}
@@ -2068,23 +2067,6 @@
super.onResize();
}
- void onUnfreezeBounds() {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = mChildren.get(i);
- c.onUnfreezeBounds();
- }
-
- if (!mHasSurface) {
- return;
- }
-
- mLayoutNeeded = true;
- setDisplayLayoutNeeded();
- if (!mWmService.mResizingWindows.contains(this)) {
- mWmService.mResizingWindows.add(this);
- }
- }
-
/**
* If the window has moved due to its containing content frame changing, then notify the
* listeners and optionally animate it. Simply checking a change of position is not enough,
@@ -3251,7 +3233,11 @@
void sendAppVisibilityToClients() {
super.sendAppVisibilityToClients();
- final boolean clientVisible = mActivityRecord.isClientVisible();
+ if (mToken == null) return;
+
+ final boolean clientVisible = mToken.isClientVisible();
+ // TODO(shell-transitions): This is currently only applicable to app windows, BUT we
+ // want to extend the "starting" concept to other windows.
if (mAttrs.type == TYPE_APPLICATION_STARTING && !clientVisible) {
// Don't hide the starting window.
return;
@@ -3579,10 +3565,6 @@
@Override
public Configuration getConfiguration() {
- if (mActivityRecord != null && mActivityRecord.mFrozenMergedConfig.size() > 0) {
- return mActivityRecord.mFrozenMergedConfig.peek();
- }
-
// If the process has not registered to any display area to listen to the configuration
// change, we can simply return the mFullConfiguration as default.
if (!registeredForDisplayAreaConfigChanges()) {
@@ -3639,9 +3621,11 @@
if (mActivityRecord != null && mActivityRecord.isRelaunching()) {
return;
}
- // If the activity is invisible or going invisible, don't report either since it is going
- // away. This is likely during a transition so we want to preserve the original state.
- if (mActivityRecord != null && !mActivityRecord.isVisibleRequested()) {
+ // If this is an activity or wallpaper and is invisible or going invisible, don't report
+ // either since it is going away. This is likely during a transition so we want to preserve
+ // the original state.
+ if ((mActivityRecord != null || mToken.asWallpaperToken() != null)
+ && !mToken.isVisibleRequested()) {
return;
}
@@ -3944,14 +3928,8 @@
return true;
}
- // If the bounds are currently frozen, it means that the layout size that the app sees
- // and the bounds we clip this window to might be different. In order to avoid holes, we
- // simulate that we are still resizing so the app fills the hole with the resizing
- // background.
- return (getDisplayContent().mDividerControllerLocked.isResizing()
- || mActivityRecord != null && !mActivityRecord.mFrozenBounds.isEmpty()) &&
- !task.inFreeformWindowingMode() && !isGoneForLayout();
-
+ return getDisplayContent().mDividerControllerLocked.isResizing()
+ && !task.inFreeformWindowingMode() && !isGoneForLayout();
}
void setDragResizing() {
@@ -4061,8 +4039,7 @@
if (mIsImWindow || mIsWallpaper || mIsFloatingLayer) {
pw.println(prefix + "mIsImWindow=" + mIsImWindow
+ " mIsWallpaper=" + mIsWallpaper
- + " mIsFloatingLayer=" + mIsFloatingLayer
- + " mWallpaperVisible=" + mWallpaperVisible);
+ + " mIsFloatingLayer=" + mIsFloatingLayer);
}
if (dumpAll) {
pw.print(prefix); pw.print("mBaseLayer="); pw.print(mBaseLayer);
@@ -4876,61 +4853,6 @@
return getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
}
- void hideWallpaperWindow(boolean wasDeferred, String reason) {
- for (int j = mChildren.size() - 1; j >= 0; --j) {
- final WindowState c = mChildren.get(j);
- c.hideWallpaperWindow(wasDeferred, reason);
- }
- if (!mWinAnimator.mLastHidden || wasDeferred) {
- mWinAnimator.hide(getGlobalTransaction(), reason);
- getDisplayContent().mWallpaperController.mDeferredHideWallpaper = null;
- dispatchWallpaperVisibility(false);
- final DisplayContent displayContent = getDisplayContent();
- if (displayContent != null) {
- displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
- if (DEBUG_LAYOUT_REPEATS) {
- mWmService.mWindowPlacerLocked.debugLayoutRepeats("hideWallpaperWindow " + this,
- displayContent.pendingLayoutChanges);
- }
- }
- }
- }
-
- /**
- * Check wallpaper window for visibility change and notify window if so.
- * @param visible Current visibility.
- */
- void dispatchWallpaperVisibility(final boolean visible) {
- final boolean hideAllowed =
- getDisplayContent().mWallpaperController.mDeferredHideWallpaper == null;
-
- // Only send notification if the visibility actually changed and we are not trying to hide
- // the wallpaper when we are deferring hiding of the wallpaper.
- if (mWallpaperVisible != visible && (hideAllowed || visible)) {
- mWallpaperVisible = visible;
- try {
- if (DEBUG_VISIBILITY || DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "Updating vis of wallpaper " + this
- + ": " + visible + " from:\n" + Debug.getCallers(4, " "));
- mClient.dispatchAppVisibility(visible);
- } catch (RemoteException e) {
- }
- }
- }
-
- boolean hasVisibleNotDrawnWallpaper() {
- if (mWallpaperVisible && !isDrawn()) {
- return true;
- }
- for (int j = mChildren.size() - 1; j >= 0; --j) {
- final WindowState c = mChildren.get(j);
- if (c.hasVisibleNotDrawnWallpaper()) {
- return true;
- }
- }
- return false;
- }
-
void updateReportedVisibility(UpdateReportedVisibilityResults results) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowState c = mChildren.get(i);
@@ -5855,4 +5777,9 @@
outSize.inset(-attrs.surfaceInsets.left, -attrs.surfaceInsets.top,
-attrs.surfaceInsets.right, -attrs.surfaceInsets.bottom);
}
+
+ @Override
+ @WindowManager.LayoutParams.WindowType int getWindowType() {
+ return mAttrs.type;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index ece256e..ebbebbb 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -57,7 +57,6 @@
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.Rect;
-import android.graphics.Region;
import android.os.Debug;
import android.os.Trace;
import android.util.Slog;
@@ -575,10 +574,7 @@
setSurfaceBoundariesLocked(t);
- if (mIsWallpaper && !w.mWallpaperVisible) {
- // Wallpaper is no longer visible and there is no wp target => hide it.
- hide(t, "prepareSurfaceLocked");
- } else if (w.isParentWindowHidden() || !w.isOnScreen()) {
+ if (w.isParentWindowHidden() || !w.isOnScreen()) {
hide(t, "prepareSurfaceLocked");
mWallpaperControllerLocked.hideWallpapers(w);
@@ -631,9 +627,6 @@
if (showSurfaceRobustlyLocked(t)) {
mAnimator.requestRemovalOfReplacedWindows(w);
mLastHidden = false;
- if (mIsWallpaper) {
- w.dispatchWallpaperVisibility(true);
- }
final DisplayContent displayContent = w.getDisplayContent();
if (!displayContent.getLastHasContent()) {
// This draw means the difference between unique content and mirroring.
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index e87ee91..5276d9c8 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -22,6 +22,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
@@ -112,6 +113,9 @@
*/
private final boolean mFromClientToken;
+ /** Have we told the window clients to show themselves? */
+ private boolean mClientVisible;
+
/**
* Used to fix the transform of the token to be rotated to a rotation different than it's
* display. The window frames and surfaces corresponding to this token will be layouted and
@@ -397,6 +401,21 @@
return builder;
}
+ boolean isClientVisible() {
+ return mClientVisible;
+ }
+
+ void setClientVisible(boolean clientVisible) {
+ if (mClientVisible == clientVisible) {
+ return;
+ }
+ ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
+ "setClientVisible: %s clientVisible=%b Callers=%s", this, clientVisible,
+ Debug.getCallers(5));
+ mClientVisible = clientVisible;
+ sendAppVisibilityToClients();
+ }
+
boolean hasFixedRotationTransform() {
return mFixedRotationTransformState != null;
}
@@ -736,4 +755,18 @@
boolean isFromClient() {
return mFromClientToken;
}
+
+ /** @see WindowState#freezeInsetsState() */
+ void setInsetsFrozen(boolean freeze) {
+ if (freeze) {
+ forAllWindows(WindowState::freezeInsetsState, true /* traverseTopToBottom */);
+ } else {
+ forAllWindows(WindowState::clearFrozenInsetsState, true /* traverseTopToBottom */);
+ }
+ }
+
+ @Override
+ @WindowManager.LayoutParams.WindowType int getWindowType() {
+ return windowType;
+ }
}
diff --git a/services/core/xsd/Android.bp b/services/core/xsd/Android.bp
index c285ef5..6a8f6d4 100644
--- a/services/core/xsd/Android.bp
+++ b/services/core/xsd/Android.bp
@@ -30,7 +30,6 @@
gen_writer: true,
}
-
xsd_config {
name: "display-device-config",
srcs: ["display-device-config/display-device-config.xsd"],
@@ -38,6 +37,12 @@
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 7d705c1..e4b9612 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 0000000..20b75be
--- /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 0000000..c542c0d
--- /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 0000000..8171885
--- /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 0000000..e69de29
--- /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 0000000..e69de29
--- /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 0000000..d802177
--- /dev/null
+++ b/services/core/xsd/display-layout-config/schema/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 28e9acf..04af5c9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1583,8 +1583,7 @@
}
public String[] getPersonalAppsForSuspension(int userId) {
- return new PersonalAppsSuspensionHelper(
- mContext.createContextAsUser(UserHandle.of(userId), 0 /* flags */))
+ return PersonalAppsSuspensionHelper.forUser(mContext, userId)
.getPersonalAppsForSuspension();
}
@@ -1599,6 +1598,10 @@
void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) {
mSafetyChecker = safetyChecker;
}
+
+ void dumpPerUserData(IndentingPrintWriter pw, @UserIdInt int userId) {
+ PersonalAppsSuspensionHelper.forUser(mContext, userId).dump(pw);
+ }
}
/**
@@ -9161,11 +9164,17 @@
}
}
- private void dumpDevicePolicyData(IndentingPrintWriter pw) {
+ private void dumpPerUserData(IndentingPrintWriter pw) {
int userCount = mUserData.size();
- for (int u = 0; u < userCount; u++) {
- DevicePolicyData policy = getUserData(mUserData.keyAt(u));
+ for (int userId = 0; userId < userCount; userId++) {
+ DevicePolicyData policy = getUserData(mUserData.keyAt(userId));
policy.dump(pw);
+ pw.println();
+
+ pw.increaseIndent();
+ mInjector.dumpPerUserData(pw, userId);
+ pw.decreaseIndent();
+ pw.println();
}
}
@@ -9183,7 +9192,7 @@
pw.println();
mDeviceAdminServiceController.dump(pw);
pw.println();
- dumpDevicePolicyData(pw);
+ dumpPerUserData(pw);
pw.println();
mConstants.dump(pw);
pw.println();
@@ -9229,20 +9238,30 @@
pw.increaseIndent();
dumpResources(pw, mContext, "cross_profile_apps", R.array.cross_profile_apps);
dumpResources(pw, mContext, "vendor_cross_profile_apps", R.array.vendor_cross_profile_apps);
+ dumpResources(pw, mContext, "config_packagesExemptFromSuspension",
+ R.array.config_packagesExemptFromSuspension);
pw.decreaseIndent();
pw.println();
}
static void dumpResources(IndentingPrintWriter pw, Context context, String resName, int resId) {
- String[] apps = context.getResources().getStringArray(resId);
- if (apps == null || apps.length == 0) {
- pw.printf("%s: empty\n", resName);
+ dumpApps(pw, resName, context.getResources().getStringArray(resId));
+ }
+
+ static void dumpApps(IndentingPrintWriter pw, String name, String[] apps) {
+ dumpApps(pw, name, Arrays.asList(apps));
+ }
+
+ static void dumpApps(IndentingPrintWriter pw, String name, List apps) {
+ if (apps == null || apps.isEmpty()) {
+ pw.printf("%s: empty\n", name);
return;
}
- pw.printf("%s: %d app%s\n", resName, apps.length, apps.length == 1 ? "" : "s");
+ int size = apps.size();
+ pw.printf("%s: %d app%s\n", name, size, size == 1 ? "" : "s");
pw.increaseIndent();
- for (int i = 0; i < apps.length; i++) {
- pw.printf("%d: %s\n", i, apps[i]);
+ for (int i = 0; i < size; i++) {
+ pw.printf("%d: %s\n", i, apps.get(i));
}
pw.decreaseIndent();
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
index c6871842..37dbfc1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
@@ -20,6 +20,7 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -29,10 +30,12 @@
import android.content.pm.ResolveInfo;
import android.os.IBinder;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Telephony;
import android.text.TextUtils;
import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
@@ -49,7 +52,7 @@
/**
* Utility class to find what personal apps should be suspended to limit personal device use.
*/
-public class PersonalAppsSuspensionHelper {
+public final class PersonalAppsSuspensionHelper {
private static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG;
// Flags to get all packages even if the user is still locked.
@@ -60,9 +63,17 @@
private final PackageManager mPackageManager;
/**
+ * Factory method
+ */
+ public static PersonalAppsSuspensionHelper forUser(Context context, @UserIdInt int userId) {
+ return new PersonalAppsSuspensionHelper(context.createContextAsUser(UserHandle.of(userId),
+ /* flags= */ 0));
+ }
+
+ /**
* @param context Context for the user whose apps should to be suspended.
*/
- public PersonalAppsSuspensionHelper(Context context) {
+ private PersonalAppsSuspensionHelper(Context context) {
mContext = context;
mPackageManager = context.getPackageManager();
}
@@ -181,4 +192,21 @@
iBinder == null ? null : IAccessibilityManager.Stub.asInterface(iBinder);
return new AccessibilityManager(mContext, service, userId);
}
+
+ void dump(IndentingPrintWriter pw) {
+ pw.println("PersonalAppsSuspensionHelper");
+ pw.increaseIndent();
+
+ DevicePolicyManagerService.dumpApps(pw, "critical packages", getCriticalPackages());
+ DevicePolicyManagerService.dumpApps(pw, "launcher packages", getSystemLauncherPackages());
+ DevicePolicyManagerService.dumpApps(pw, "accessibility services",
+ getAccessibilityServices());
+ DevicePolicyManagerService.dumpApps(pw, "input method packages", getInputMethodPackages());
+ pw.printf("SMS package: %s\n", Telephony.Sms.getDefaultSmsPackage(mContext));
+ pw.printf("Settings package: %s\n", getSettingsPackageName());
+ DevicePolicyManagerService.dumpApps(pw, "Packages subject to suspension",
+ getPersonalAppsForSuspension());
+
+ pw.decreaseIndent();
+ }
}
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 0d878b4..a262939 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -102,7 +102,7 @@
return false;
}
try {
- return !mIProfcollect.GetSupportedProvider().isEmpty();
+ return !mIProfcollect.get_supported_provider().isEmpty();
} catch (RemoteException e) {
Log.e(LOG_TAG, e.getMessage());
return false;
@@ -191,7 +191,7 @@
}
try {
- sSelfService.mIProfcollect.ProcessProfile();
+ sSelfService.mIProfcollect.process(false);
} catch (RemoteException e) {
Log.e(LOG_TAG, e.getMessage());
}
@@ -234,7 +234,7 @@
if (DEBUG) {
Log.d(LOG_TAG, "Tracing on app launch event: " + packageName);
}
- mIProfcollect.TraceOnce("applaunch");
+ mIProfcollect.trace_once("applaunch");
} catch (RemoteException e) {
Log.e(LOG_TAG, e.getMessage());
}
@@ -296,7 +296,7 @@
}
try {
- mIProfcollect.CreateProfileReport();
+ mIProfcollect.report();
} catch (RemoteException e) {
Log.e(LOG_TAG, e.getMessage());
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt
index 9447f39..8ef9239 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt
@@ -19,7 +19,7 @@
import android.content.pm.verify.domain.DomainSet
import android.content.pm.verify.domain.DomainVerificationInfo
import android.content.pm.verify.domain.DomainVerificationRequest
-import android.content.pm.verify.domain.DomainVerificationUserSelection
+import android.content.pm.verify.domain.DomainVerificationUserState
import android.os.Parcel
import android.os.Parcelable
import android.os.UserHandle
@@ -28,7 +28,6 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.util.UUID
-import kotlin.random.Random
@RunWith(Parameterized::class)
class DomainVerificationCoreApiTest {
@@ -92,9 +91,9 @@
}
),
Parameter(
- testName = "DomainVerificationUserSelection",
+ testName = "DomainVerificationUserState",
initial = {
- DomainVerificationUserSelection(
+ DomainVerificationUserState(
UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"),
"com.test.pkg",
UserHandle.of(10),
@@ -103,22 +102,22 @@
.associate { it.value to (it.index % 3) }
)
},
- unparcel = { DomainVerificationUserSelection.CREATOR.createFromParcel(it) },
+ unparcel = { DomainVerificationUserState.CREATOR.createFromParcel(it) },
assertion = { first, second ->
- assertAll<DomainVerificationUserSelection, UUID>(first, second,
+ assertAll<DomainVerificationUserState, UUID>(first, second,
{ it.identifier }, { it.component1() }, IS_EQUAL_TO
)
- assertAll<DomainVerificationUserSelection, String>(first, second,
+ assertAll<DomainVerificationUserState, String>(first, second,
{ it.packageName }, { it.component2() }, IS_EQUAL_TO
)
- assertAll<DomainVerificationUserSelection, UserHandle>(first, second,
+ assertAll<DomainVerificationUserState, UserHandle>(first, second,
{ it.user }, { it.component3() }, IS_EQUAL_TO
)
- assertAll<DomainVerificationUserSelection, Boolean>(
+ assertAll<DomainVerificationUserState, Boolean>(
first, second, { it.isLinkHandlingAllowed },
{ it.component4() }, IS_EQUAL_TO
)
- assertAll<DomainVerificationUserSelection, Map<String, Int>>(
+ assertAll<DomainVerificationUserState, Map<String, Int>>(
first, second, { it.hostToStateMap },
{ it.component5() }, IS_MAP_EQUAL_TO
)
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index 89394837..53f0ca2 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -155,6 +155,15 @@
assertApprovedVerifier(it.callingUid, it.proxy)
},
enforcer(
+ Type.SELECTION_QUERENT,
+ "approvedUserStateQuerent"
+ ) {
+ assertApprovedUserStateQuerent(
+ it.callingUid, it.callingUserId,
+ it.targetPackageName, it.userId
+ )
+ },
+ enforcer(
Type.SELECTOR,
"approvedUserSelector"
) {
@@ -170,7 +179,7 @@
ArraySet(setOf("example.com"))
)
},
- service(Type.INTERNAL, "setUserSelectionInternal") {
+ service(Type.INTERNAL, "setUserStateInternal") {
setDomainVerificationUserSelectionInternal(
it.userId,
it.targetPackageName,
@@ -184,11 +193,11 @@
service(Type.INTERNAL, "clearState") {
clearDomainVerificationState(listOf(it.targetPackageName))
},
- service(Type.INTERNAL, "clearUserSelections") {
- clearUserSelections(listOf(it.targetPackageName), it.userId)
+ service(Type.INTERNAL, "clearUserStates") {
+ clearUserStates(listOf(it.targetPackageName), it.userId)
},
- service(Type.VERIFIER, "getPackageNames") {
- validVerificationPackageNames
+ service(Type.VERIFIER, "queryValidPackageNames") {
+ queryValidVerificationPackageNames()
},
service(Type.QUERENT, "getInfo") {
getDomainVerificationInfo(it.targetPackageName)
@@ -208,26 +217,13 @@
DomainVerificationManager.STATE_SUCCESS
)
},
- service(Type.SELECTOR, "setLinkHandlingAllowed") {
- setDomainVerificationLinkHandlingAllowed(it.targetPackageName, true)
- },
service(Type.SELECTOR_USER, "setLinkHandlingAllowedUserId") {
setDomainVerificationLinkHandlingAllowed(it.targetPackageName, true, it.userId)
},
- service(Type.SELECTOR, "getUserSelection") {
- getDomainVerificationUserSelection(it.targetPackageName)
+ service(Type.SELECTION_QUERENT, "getUserStateUserId") {
+ getDomainVerificationUserState(it.targetPackageName, it.userId)
},
- service(Type.SELECTOR_USER, "getUserSelectionUserId") {
- getDomainVerificationUserSelection(it.targetPackageName, it.userId)
- },
- service(Type.SELECTOR, "setUserSelection") {
- setDomainVerificationUserSelection(
- it.targetDomainSetId,
- setOf("example.com"),
- true
- )
- },
- service(Type.SELECTOR_USER, "setUserSelectionUserId") {
+ service(Type.SELECTOR_USER, "setUserStateUserId") {
setDomainVerificationUserSelection(
it.targetDomainSetId,
setOf("example.com"),
@@ -244,10 +240,6 @@
service(Type.LEGACY_QUERENT, "getLegacyUserState") {
getLegacyState(it.targetPackageName, it.userId)
},
- service(Type.OWNER_QUERENT, "getOwnersForDomain") {
- // Re-use package name, since the result itself isn't relevant
- getOwnersForDomain(it.targetPackageName)
- },
service(Type.OWNER_QUERENT_USER, "getOwnersForDomainUserId") {
// Re-use package name, since the result itself isn't relevant
getOwnersForDomain(it.targetPackageName, it.userId)
@@ -362,6 +354,7 @@
Type.INTERNAL -> internal()
Type.QUERENT -> approvedQuerent()
Type.VERIFIER -> approvedVerifier()
+ Type.SELECTION_QUERENT -> approvedUserStateQuerent(verifyCrossUser = true)
Type.SELECTOR -> approvedUserSelector(verifyCrossUser = false)
Type.SELECTOR_USER -> approvedUserSelector(verifyCrossUser = true)
Type.LEGACY_QUERENT -> legacyQuerent()
@@ -371,7 +364,7 @@
}.run { /*exhaust*/ }
}
- fun internal() {
+ private fun internal() {
val context: Context = mockThrowOnUnmocked()
val target = params.construct(context)
@@ -385,13 +378,13 @@
}
}
- fun approvedQuerent() {
- val allowUserSelection = AtomicBoolean(false)
+ private fun approvedQuerent() {
+ val allowUserState = AtomicBoolean(false)
val allowPreferredApps = AtomicBoolean(false)
val allowQueryAll = AtomicBoolean(false)
val context: Context = mockThrowOnUnmocked {
initPermission(
- allowUserSelection,
+ allowUserState,
android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
)
initPermission(
@@ -418,7 +411,7 @@
assertFails { runMethod(target, NON_VERIFIER_UID) }
- allowUserSelection.set(true)
+ allowUserState.set(true)
assertFails { runMethod(target, NON_VERIFIER_UID) }
@@ -427,7 +420,7 @@
runMethod(target, NON_VERIFIER_UID)
}
- fun approvedVerifier() {
+ private fun approvedVerifier() {
val allowDomainVerificationAgent = AtomicBoolean(false)
val allowIntentVerificationAgent = AtomicBoolean(false)
val allowQueryAll = AtomicBoolean(false)
@@ -469,12 +462,61 @@
assertFails { runMethod(target, NON_VERIFIER_UID) }
}
- fun approvedUserSelector(verifyCrossUser: Boolean) {
- val allowUserSelection = AtomicBoolean(false)
+ private fun approvedUserStateQuerent(verifyCrossUser: Boolean) {
val allowInteractAcrossUsers = AtomicBoolean(false)
val context: Context = mockThrowOnUnmocked {
initPermission(
- allowUserSelection,
+ allowInteractAcrossUsers,
+ android.Manifest.permission.INTERACT_ACROSS_USERS
+ )
+ }
+ val target = params.construct(context)
+
+ fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
+ // User selector makes no distinction by UID
+ val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
+ if (throws) {
+ allUids.forEach {
+ assertFails {
+ runMethod(target, it, visible = true, callingUserId, targetUserId)
+ }
+ }
+ } else {
+ allUids.forEach {
+ runMethod(target, it, visible = true, callingUserId, targetUserId)
+ }
+ }
+
+ // User selector doesn't use QUERY_ALL, so the invisible package should always fail
+ allUids.forEach {
+ assertFails {
+ runMethod(target, it, visible = false, callingUserId, targetUserId)
+ }
+ }
+ }
+
+ val callingUserId = 0
+ val notCallingUserId = 1
+
+ runTestCases(callingUserId, callingUserId, throws = false)
+ if (verifyCrossUser) {
+ runTestCases(callingUserId, notCallingUserId, throws = true)
+ }
+
+ allowInteractAcrossUsers.set(true)
+
+ runTestCases(callingUserId, callingUserId, throws = false)
+ if (verifyCrossUser) {
+ runTestCases(callingUserId, notCallingUserId, throws = false)
+ }
+ }
+
+ private fun approvedUserSelector(verifyCrossUser: Boolean) {
+ val allowUserState = AtomicBoolean(false)
+ val allowInteractAcrossUsers = AtomicBoolean(false)
+ val context: Context = mockThrowOnUnmocked {
+ initPermission(
+ allowUserState,
android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
)
initPermission(
@@ -515,7 +557,7 @@
runTestCases(callingUserId, notCallingUserId, throws = true)
}
- allowUserSelection.set(true)
+ allowUserState.set(true)
runTestCases(callingUserId, callingUserId, throws = false)
if (verifyCrossUser) {
@@ -641,7 +683,7 @@
private fun ownerQuerent(verifyCrossUser: Boolean) {
val allowQueryAll = AtomicBoolean(false)
- val allowUserSelection = AtomicBoolean(false)
+ val allowUserState = AtomicBoolean(false)
val allowInteractAcrossUsers = AtomicBoolean(false)
val context: Context = mockThrowOnUnmocked {
initPermission(
@@ -649,7 +691,7 @@
android.Manifest.permission.QUERY_ALL_PACKAGES
)
initPermission(
- allowUserSelection,
+ allowUserState,
android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
)
initPermission(
@@ -690,7 +732,7 @@
runTestCases(callingUserId, notCallingUserId, throws = true)
}
- allowUserSelection.set(true)
+ allowUserState.set(true)
runTestCases(callingUserId, callingUserId, throws = false)
if (verifyCrossUser) {
@@ -769,6 +811,9 @@
// INTERNAL || domain verification agent
VERIFIER,
+ // No permissions, allows all apps to view domain state for visible packages
+ SELECTION_QUERENT,
+
// Holding the user setting permission
SELECTOR,
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt
index 439048c..8c31c65 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt
@@ -18,7 +18,7 @@
import android.content.pm.verify.domain.DomainVerificationRequest
import android.content.pm.verify.domain.DomainVerificationInfo
-import android.content.pm.verify.domain.DomainVerificationUserSelection
+import android.content.pm.verify.domain.DomainVerificationUserState
import com.android.server.pm.verify.domain.DomainVerificationPersistence
operator fun <F> android.util.Pair<F, *>.component1() = first
@@ -30,11 +30,11 @@
operator fun DomainVerificationInfo.component2() = packageName
operator fun DomainVerificationInfo.component3() = hostToStateMap
-operator fun DomainVerificationUserSelection.component1() = identifier
-operator fun DomainVerificationUserSelection.component2() = packageName
-operator fun DomainVerificationUserSelection.component3() = user
-operator fun DomainVerificationUserSelection.component4() = isLinkHandlingAllowed
-operator fun DomainVerificationUserSelection.component5() = hostToStateMap
+operator fun DomainVerificationUserState.component1() = identifier
+operator fun DomainVerificationUserState.component2() = packageName
+operator fun DomainVerificationUserState.component3() = user
+operator fun DomainVerificationUserState.component4() = isLinkHandlingAllowed
+operator fun DomainVerificationUserState.component5() = hostToStateMap
operator fun DomainVerificationPersistence.ReadResult.component1() = active
operator fun DomainVerificationPersistence.ReadResult.component2() = restored
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
index a92ab9e..6597577 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
@@ -22,14 +22,15 @@
import android.util.TypedXmlSerializer
import android.util.Xml
import com.android.server.pm.verify.domain.DomainVerificationPersistence
+import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState
import com.android.server.pm.verify.domain.models.DomainVerificationPkgState
import com.android.server.pm.verify.domain.models.DomainVerificationStateMap
-import com.android.server.pm.verify.domain.models.DomainVerificationUserState
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
+import org.xmlpull.v1.XmlPullParser
import java.io.File
import java.nio.charset.StandardCharsets
import java.util.UUID
@@ -41,21 +42,41 @@
internal fun File.writeXml(block: (serializer: TypedXmlSerializer) -> Unit) = apply {
outputStream().use {
- // Explicitly use string based XML so it can printed in the test failure output
- Xml.newFastSerializer()
+ // This must use the binary serializer the mirror the production behavior, as
+ // there are slight differences with the string based one.
+ Xml.newBinarySerializer()
.apply {
setOutput(it, StandardCharsets.UTF_8.name())
startDocument(null, true)
setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true)
+ // Write a wrapping tag to ensure the domain verification settings didn't
+ // close out the document, allowing other settings to be written
+ startTag(null, "wrapper-tag")
}
.apply(block)
+ .apply {
+ startTag(null, "trailing-tag")
+ endTag(null, "trailing-tag")
+ endTag(null, "wrapper-tag")
+ }
.endDocument()
}
}
internal fun <T> File.readXml(block: (parser: TypedXmlPullParser) -> T) =
inputStream().use {
- block(Xml.resolvePullParser(it))
+ val parser = Xml.resolvePullParser(it)
+ assertThat(parser.nextTag()).isEqualTo(XmlPullParser.START_TAG)
+ assertThat(parser.name).isEqualTo("wrapper-tag")
+ assertThat(parser.nextTag()).isEqualTo(XmlPullParser.START_TAG)
+ block(parser).also {
+ assertThat(parser.nextTag()).isEqualTo(XmlPullParser.START_TAG)
+ assertThat(parser.name).isEqualTo("trailing-tag")
+ assertThat(parser.nextTag()).isEqualTo(XmlPullParser.END_TAG)
+ assertThat(parser.name).isEqualTo("trailing-tag")
+ assertThat(parser.nextTag()).isEqualTo(XmlPullParser.END_TAG)
+ assertThat(parser.name).isEqualTo("wrapper-tag")
+ }
}
}
@@ -102,14 +123,14 @@
// A domain without a written state falls back to default
stateMap["missing-state.com"] = DomainVerificationManager.STATE_NO_RESPONSE
- userSelectionStates[1] = DomainVerificationUserState(1).apply {
+ userStates[1] = DomainVerificationInternalUserState(1).apply {
addHosts(setOf("example-user1.com", "example-user1.org"))
isLinkHandlingAllowed = true
}
}
val stateOne = mockEmptyPkgState(1).apply {
// It's valid to have a user selection without any autoVerify domains
- userSelectionStates[1] = DomainVerificationUserState(1).apply {
+ userStates[1] = DomainVerificationInternalUserState(1).apply {
addHosts(setOf("example-user1.com", "example-user1.org"))
isLinkHandlingAllowed = false
}
@@ -214,7 +235,7 @@
private fun mockPkgState(id: Int) = mockEmptyPkgState(id).apply {
stateMap["$packageName.com"] = id
- userSelectionStates[id] = DomainVerificationUserState(id).apply {
+ userStates[id] = DomainVerificationInternalUserState(id).apply {
addHosts(setOf("$packageName-user.com"))
isLinkHandlingAllowed = true
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
index 010eacf..0d8f275 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
@@ -122,8 +122,8 @@
service("clearState") {
clearDomainVerificationState(listOf(TEST_PKG))
},
- service("clearUserSelections") {
- clearUserSelections(listOf(TEST_PKG), TEST_USER_ID)
+ service("clearUserStates") {
+ clearUserStates(listOf(TEST_PKG), TEST_USER_ID)
},
service("setStatus") {
setDomainVerificationStatus(
@@ -147,19 +147,13 @@
DomainVerificationManager.STATE_SUCCESS
)
},
- service("setLinkHandlingAllowed") {
- setDomainVerificationLinkHandlingAllowed(TEST_PKG, true)
- },
service("setLinkHandlingAllowedUserId") {
setDomainVerificationLinkHandlingAllowed(TEST_PKG, true, TEST_USER_ID)
},
service("setLinkHandlingAllowedInternal") {
setDomainVerificationLinkHandlingAllowedInternal(TEST_PKG, true, TEST_USER_ID)
},
- service("setUserSelection") {
- setDomainVerificationUserSelection(TEST_UUID, setOf("example.com"), true)
- },
- service("setUserSelectionUserId") {
+ service("setUserStateUserId") {
setDomainVerificationUserSelection(
TEST_UUID,
setOf("example.com"),
@@ -167,7 +161,7 @@
TEST_USER_ID
)
},
- service("setUserSelectionInternal") {
+ service("setUserStateInternal") {
setDomainVerificationUserSelectionInternal(
TEST_USER_ID,
TEST_PKG,
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
similarity index 82%
rename from services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt
rename to services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
index 48056a2..0576125 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
@@ -16,18 +16,16 @@
package com.android.server.pm.test.verify.domain
-import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.parsing.component.ParsedActivity
import android.content.pm.parsing.component.ParsedIntentInfo
import android.content.pm.verify.domain.DomainVerificationManager
-import android.content.pm.verify.domain.DomainVerificationUserSelection
+import android.content.pm.verify.domain.DomainVerificationUserState
import android.os.Build
import android.os.PatternMatcher
import android.os.Process
import android.util.ArraySet
-import androidx.test.InstrumentationRegistry
import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.verify.domain.DomainVerificationService
@@ -41,7 +39,7 @@
import org.mockito.ArgumentMatchers.anyString
import java.util.UUID
-class DomainVerificationManagerUserSelectionOverrideTest {
+class DomainVerificationUserStateOverrideTest {
companion object {
private const val PKG_ONE = "com.test.one"
@@ -50,17 +48,19 @@
private val UUID_TWO = UUID.fromString("a3389c16-7f9f-4e86-85e3-500d1249c74c")
private val DOMAIN_ONE =
- DomainVerificationManagerUserSelectionOverrideTest::class.java.packageName
+ DomainVerificationUserStateOverrideTest::class.java.packageName
- private const val STATE_NONE = DomainVerificationUserSelection.DOMAIN_STATE_NONE
- private const val STATE_SELECTED = DomainVerificationUserSelection.DOMAIN_STATE_SELECTED
- private const val STATE_VERIFIED = DomainVerificationUserSelection.DOMAIN_STATE_VERIFIED
+ private const val STATE_NONE = DomainVerificationUserState.DOMAIN_STATE_NONE
+ private const val STATE_SELECTED = DomainVerificationUserState.DOMAIN_STATE_SELECTED
+ private const val STATE_VERIFIED = DomainVerificationUserState.DOMAIN_STATE_VERIFIED
+
+ private const val USER_ID = 0
}
private val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE)
private val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO)
- fun makeManager(): DomainVerificationManager =
+ fun makeService() =
DomainVerificationService(mockThrowOnUnmocked {
// Assume the test has every permission necessary
whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString()))
@@ -88,7 +88,7 @@
addPackage(pkg2)
// Starting state for all tests is to have domain 1 enabled for the first package
- setDomainVerificationUserSelection(UUID_ONE, setOf(DOMAIN_ONE), true)
+ setDomainVerificationUserSelection(UUID_ONE, setOf(DOMAIN_ONE), true, USER_ID)
assertThat(stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_SELECTED)
}
@@ -138,37 +138,37 @@
@Test
fun anotherPackageTakeoverSuccess() {
- val manager = makeManager()
+ val service = makeService()
// Attempt override by package 2
- manager.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true)
+ service.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true, USER_ID)
// 1 loses approval
- assertThat(manager.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_NONE)
+ assertThat(service.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_NONE)
// 2 gains approval
- assertThat(manager.stateFor(PKG_TWO, DOMAIN_ONE)).isEqualTo(STATE_SELECTED)
+ assertThat(service.stateFor(PKG_TWO, DOMAIN_ONE)).isEqualTo(STATE_SELECTED)
// 2 is the only owner
- assertThat(manager.getOwnersForDomain(DOMAIN_ONE).map { it.packageName })
+ assertThat(service.getOwnersForDomain(DOMAIN_ONE, USER_ID).map { it.packageName })
.containsExactly(PKG_TWO)
}
@Test(expected = IllegalArgumentException::class)
fun anotherPackageTakeoverFailure() {
- val manager = makeManager()
+ val service = makeService()
// Verify 1 to give it a higher approval level
- manager.setDomainVerificationStatus(UUID_ONE, setOf(DOMAIN_ONE),
+ service.setDomainVerificationStatus(UUID_ONE, setOf(DOMAIN_ONE),
DomainVerificationManager.STATE_SUCCESS)
- assertThat(manager.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_VERIFIED)
- assertThat(manager.getOwnersForDomain(DOMAIN_ONE).map { it.packageName })
+ assertThat(service.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_VERIFIED)
+ assertThat(service.getOwnersForDomain(DOMAIN_ONE, USER_ID).map { it.packageName })
.containsExactly(PKG_ONE)
// Attempt override by package 2
- manager.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true)
+ service.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true, USER_ID)
}
- private fun DomainVerificationManager.stateFor(pkgName: String, host: String) =
- getDomainVerificationUserSelection(pkgName)!!.hostToStateMap[host]
+ private fun DomainVerificationService.stateFor(pkgName: String, host: String) =
+ getDomainVerificationUserState(pkgName, USER_ID)!!.hostToStateMap[host]
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 9109881..51c9b0d 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(),
isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
- final Bundle idleOptions = bundleCaptor.getValue();
- final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+ final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue());
+ final int type = idleOptions.getTemporaryAppAllowlistType();
assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED, type);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index f7f5928..3870b02 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -641,6 +641,6 @@
private static JobStatus createJobStatus(JobInfo.Builder job, int uid,
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) {
return new JobStatus(job.build(), uid, null, -1, 0, null,
- earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, null, 0);
+ earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, null, 0, 0);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 91b3cb7..7925b69 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -685,7 +685,7 @@
final JobInfo job = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY).build();
return new JobStatus(job, 0, null, -1, 0, null, earliestRunTimeElapsedMillis,
- latestRunTimeElapsedMillis, 0, 0, null, 0);
+ latestRunTimeElapsedMillis, 0, 0, null, 0, 0);
}
private static JobStatus createJobStatus(JobInfo job) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java
new file mode 100644
index 0000000..d786a5d
--- /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 cfa2086..f897d5c 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 @@
@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 @@
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 @@
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 @@
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 4b2a9fc..80e81d6 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 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 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 @@
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 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 110bb21..bcc756a 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 @@
@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 @@
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 @@
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 6963a1a..00daa5c 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 @@
@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 @@
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 8062bfe..1603087 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 @@
@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 @@
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 @@
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 92221c9..bcd853c 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 @@
// Disable binder caches in this process.
PropertyInvalidatedCache.disableForTestMode();
- mLogicalDisplayMapper = new LogicalDisplayMapper(
- mContext, mDisplayDeviceRepo, mListenerMock);
+ mLogicalDisplayMapper = new LogicalDisplayMapper(mDisplayDeviceRepo, mListenerMock);
}
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index deaeb46..8b35af8 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -276,7 +276,7 @@
0 /* sourceUserId */, 0, "someTag",
invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis,
0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
- persistedExecutionTimesUTC, 0 /* innerFlagg */);
+ persistedExecutionTimesUTC, 0 /* innerFlag */, 0 /* dynamicConstraints */);
mTaskStoreUnderTest.add(js);
waitForPendingIo();
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
index f87d599..7d9ab37 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.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 @@
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 @@
}
@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 @@
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 @@
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(startingWorkType) > 0
- && mWorkCountTracker.canJobStart(startingWorkType) != WORK_TYPE_NONE) {
- final int pendingMultiType = getPendingMultiType(jobs, startingWorkType);
+ if (jobs.pending.get(workType) > 0) {
+ final int pendingMultiType = getPendingMultiType(jobs, workType);
+ final int startingWorkType = mWorkCountTracker.canJobStart(pendingMultiType);
+ if (startingWorkType == WORK_TYPE_NONE) {
+ continue;
+ }
+
jobs.removePending(pendingMultiType);
jobs.running.put(startingWorkType, jobs.running.get(startingWorkType) + 1);
mWorkCountTracker.stageJob(startingWorkType, pendingMultiType);
@@ -304,7 +312,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 2288a89..cc18317 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 @@
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 @@
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 @@
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 @@
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 @@
/* 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 @@
/*expected*/ true, 16,
/* min */ List.of(Pair.create(WORK_TYPE_TOP, 1), Pair.create(WORK_TYPE_BG, 8)),
/* max */ List.of(Pair.create(WORK_TYPE_TOP, 16), Pair.create(WORK_TYPE_BG, 16)));
+
+ check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+ .setInt(KEY_MAX_TOTAL, 16)
+ .setInt(KEY_MAX_TOP, 16)
+ .setInt(KEY_MIN_TOP, 1)
+ .setInt(KEY_MAX_FGS, 15)
+ .setInt(KEY_MIN_FGS, 2)
+ .setInt(KEY_MAX_EJ, 14)
+ .setInt(KEY_MIN_EJ, 3)
+ .setInt(KEY_MAX_BG, 13)
+ .setInt(KEY_MIN_BG, 4)
+ .setInt(KEY_MAX_BGUSER, 12)
+ .setInt(KEY_MIN_BGUSER, 5)
+ .build(),
+ /*default*/ 9,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 9)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 9)),
+ /*expected*/ true, 16,
+ /* min */ List.of(Pair.create(WORK_TYPE_TOP, 1), Pair.create(WORK_TYPE_FGS, 2),
+ Pair.create(WORK_TYPE_EJ, 3),
+ Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 5)),
+ /* max */
+ List.of(Pair.create(WORK_TYPE_TOP, 16), Pair.create(WORK_TYPE_FGS, 15),
+ Pair.create(WORK_TYPE_EJ, 14),
+ Pair.create(WORK_TYPE_BG, 13), Pair.create(WORK_TYPE_BGUSER, 12)));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index a896f1b..b51f4df 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -44,6 +44,7 @@
import android.content.pm.UserInfo;
import android.hardware.rebootescrow.IRebootEscrow;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.UserManager;
@@ -62,6 +63,7 @@
import org.mockito.ArgumentCaptor;
import java.io.File;
+import java.io.IOException;
import java.util.ArrayList;
import javax.crypto.SecretKey;
@@ -181,6 +183,18 @@
}
@Override
+ public int getLoadEscrowDataRetryLimit() {
+ // Try two times
+ return 2;
+ }
+
+ @Override
+ public int getLoadEscrowDataRetryIntervalSeconds() {
+ // Retry in 1 seconds
+ return 1;
+ }
+
+ @Override
public void reportMetric(boolean success) {
mInjected.reportMetric(success);
}
@@ -448,6 +462,46 @@
}
@Test
+ public void loadRebootEscrowDataIfAvailable_ServerBased_RetrySuccess() throws Exception {
+ setServerBasedRebootEscrowProvider();
+
+ when(mInjected.getBootCount()).thenReturn(0);
+ RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+ mService.setRebootEscrowListener(mockListener);
+ mService.prepareRebootEscrow();
+
+ clearInvocations(mServiceConnection);
+ mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ verify(mockListener).onPreparedForReboot(eq(true));
+ verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+ // Use x -> x for both wrap & unwrap functions.
+ when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+ assertTrue(mService.armRebootEscrowIfNeeded());
+ verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+ assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+ // pretend reboot happens here
+ when(mInjected.getBootCount()).thenReturn(1);
+ ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+ doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture());
+
+ when(mServiceConnection.unwrap(any(), anyLong()))
+ .thenThrow(new IOException())
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ HandlerThread thread = new HandlerThread("RebootEscrowManagerTest");
+ thread.start();
+ mService.loadRebootEscrowDataIfAvailable(new Handler(thread.getLooper()));
+ // Sleep 5s for the retry to complete
+ Thread.sleep(5 * 1000);
+ verify(mServiceConnection, times(2)).unwrap(any(), anyLong());
+ assertTrue(metricsSuccessCaptor.getValue());
+ verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
+ }
+
+ @Test
public void loadRebootEscrowDataIfAvailable_TooManyBootsInBetween_NoMetrics() throws Exception {
when(mInjected.getBootCount()).thenReturn(0);
diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
new file mode 100644
index 0000000..3025a95
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
@@ -0,0 +1,152 @@
+/*
+ * 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.policy;
+
+import static android.view.KeyEvent.ACTION_DOWN;
+import static android.view.KeyEvent.ACTION_UP;
+import static android.view.KeyEvent.KEYCODE_POWER;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.policy.SingleKeyGestureDetector.KEY_LONGPRESS;
+import static com.android.server.policy.SingleKeyGestureDetector.KEY_VERYLONGPRESS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.os.SystemClock;
+import android.view.KeyEvent;
+import android.view.ViewConfiguration;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test class for {@link SingleKeyGestureDetector}.
+ *
+ * Build/Install/Run:
+ * atest WmTests:SingleKeyGestureTests
+ */
+public class SingleKeyGestureTests {
+ private SingleKeyGestureDetector mDetector;
+
+ private int mMaxMultiPressPowerCount = 2;
+
+ private CountDownLatch mShortPressed = new CountDownLatch(1);
+ private CountDownLatch mLongPressed = new CountDownLatch(1);
+ private CountDownLatch mVeryLongPressed = new CountDownLatch(1);
+ private CountDownLatch mMultiPressed = new CountDownLatch(1);
+
+ private final Instrumentation mInstrumentation = getInstrumentation();
+ private final Context mContext = mInstrumentation.getTargetContext();
+ private long mWaitTimeout;
+ private long mLongPressTime;
+ private long mVeryLongPressTime;
+
+ @Before
+ public void setUp() {
+ mDetector = new SingleKeyGestureDetector(mContext);
+ initSingleKeyGestureRules();
+ mWaitTimeout = ViewConfiguration.getMultiPressTimeout() + 50;
+ mLongPressTime = ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout() + 50;
+ mVeryLongPressTime = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_veryLongPressTimeout) + 50;
+ }
+
+ private void initSingleKeyGestureRules() {
+ mDetector.addRule(new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_POWER,
+ KEY_LONGPRESS | KEY_VERYLONGPRESS) {
+ @Override
+ int getMaxMultiPressCount() {
+ return mMaxMultiPressPowerCount;
+ }
+ @Override
+ public void onPress(long downTime) {
+ mShortPressed.countDown();
+ }
+
+ @Override
+ void onLongPress(long downTime) {
+ mLongPressed.countDown();
+ }
+
+ @Override
+ void onVeryLongPress(long downTime) {
+ mVeryLongPressed.countDown();
+ }
+
+ @Override
+ void onMultiPress(long downTime, int count) {
+ mMultiPressed.countDown();
+ assertEquals(mMaxMultiPressPowerCount, count);
+ }
+ });
+ }
+
+ private void pressKey(long eventTime, int keyCode, long pressTime) {
+ final KeyEvent keyDown = new KeyEvent(eventTime, eventTime, ACTION_DOWN,
+ keyCode, 0 /* repeat */, 0 /* metaState */);
+ mDetector.interceptKey(keyDown);
+
+ // keep press down.
+ try {
+ Thread.sleep(pressTime);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ eventTime += pressTime;
+ final KeyEvent keyUp = new KeyEvent(eventTime, eventTime, ACTION_UP,
+ keyCode, 0 /* repeat */, 0 /* metaState */);
+
+ mDetector.interceptKey(keyUp);
+ }
+
+ @Test
+ public void testShortPress() throws InterruptedException {
+ final long eventTime = SystemClock.uptimeMillis();
+ pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */);
+ assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
+ public void testLongPress() throws InterruptedException {
+ final long eventTime = SystemClock.uptimeMillis();
+ pressKey(eventTime, KEYCODE_POWER, mLongPressTime);
+ assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
+ public void testVeryLongPress() throws InterruptedException {
+ final long eventTime = SystemClock.uptimeMillis();
+ pressKey(eventTime, KEYCODE_POWER, mVeryLongPressTime);
+ assertTrue(mVeryLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
+ public void testMultiPress() throws InterruptedException {
+ final long eventTime = SystemClock.uptimeMillis();
+ pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */);
+ pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */);
+ assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 137cf65..09a436c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -518,6 +518,7 @@
TYPE_WALLPAPER, TYPE_APPLICATION);
final WindowState wallpaper = windows[0];
assertTrue(wallpaper.mIsWallpaper);
+ wallpaper.mToken.asWallpaperToken().setVisibility(false);
// By default WindowState#mWallpaperVisible is false.
assertFalse(wallpaper.isVisible());
diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
index d663b64..cc1869e 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 @@
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/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 2fdd63e..ea97180 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -18,11 +18,13 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
import static android.view.WindowManager.TRANSIT_OLD_NONE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -35,7 +37,9 @@
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -94,7 +98,7 @@
mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50, true /* changeNeedsSnapshot */);
mAdapter.setCallingPidUid(123, 456);
runWithScissors(mWm.mH, () -> mHandler = new TestHandler(null, mClock), 0);
- mController = new RemoteAnimationController(mWm, mAdapter, mHandler);
+ mController = new RemoteAnimationController(mWm, mDisplayContent, mAdapter, mHandler);
}
private WindowState createAppOverlayWindow() {
@@ -520,6 +524,164 @@
}
}
+ @Test
+ public void testNonAppTarget_sendNavBar() throws Exception {
+ final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ mDisplayContent.mOpeningApps.add(win.mActivityRecord);
+ final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "NavigationBar");
+ mDisplayContent.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
+ final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
+ spyOn(policy);
+ doReturn(true).when(policy).shouldAttachNavBarToAppDuringTransition();
+
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+ win.mActivityRecord, new Point(50, 100), null,
+ new Rect(50, 100, 150, 150), null).mAdapter;
+ adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
+ mFinishedCallback);
+ mController.goodToGo(TRANSIT_OLD_TASK_OPEN);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+ final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+ ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+ verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_OPEN),
+ appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
+ finishedCaptor.capture());
+ boolean containNavTarget = false;
+ for (int i = 0; i < nonAppsCaptor.getValue().length; i++) {
+ if (nonAppsCaptor.getValue()[0].windowType == TYPE_NAVIGATION_BAR) {
+ containNavTarget = true;
+ break;
+ }
+ }
+ assertTrue(containNavTarget);
+ }
+
+ @Test
+ public void testNonAppTarget_notSendNavBar_notAttachToApp() throws Exception {
+ final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ mDisplayContent.mOpeningApps.add(win.mActivityRecord);
+ final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "NavigationBar");
+ mDisplayContent.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
+ final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
+ spyOn(policy);
+ doReturn(false).when(policy).shouldAttachNavBarToAppDuringTransition();
+
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+ win.mActivityRecord, new Point(50, 100), null,
+ new Rect(50, 100, 150, 150), null).mAdapter;
+ adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
+ mFinishedCallback);
+ mController.goodToGo(TRANSIT_OLD_TASK_OPEN);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+ final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+ ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+ verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_OPEN),
+ appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
+ finishedCaptor.capture());
+ boolean containNavTarget = false;
+ for (int i = 0; i < nonAppsCaptor.getValue().length; i++) {
+ if (nonAppsCaptor.getValue()[0].windowType == TYPE_NAVIGATION_BAR) {
+ containNavTarget = true;
+ break;
+ }
+ }
+ assertFalse(containNavTarget);
+ }
+
+ @Test
+ public void testNonAppTarget_notSendNavBar_controlledByFixedRotation() throws Exception {
+ final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ mDisplayContent.mOpeningApps.add(win.mActivityRecord);
+ final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "NavigationBar");
+ mDisplayContent.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
+ final FixedRotationAnimationController mockController =
+ mock(FixedRotationAnimationController.class);
+ doReturn(mockController).when(mDisplayContent).getFixedRotationAnimationController();
+ final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
+ spyOn(policy);
+ doReturn(true).when(policy).shouldAttachNavBarToAppDuringTransition();
+
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+ win.mActivityRecord, new Point(50, 100), null,
+ new Rect(50, 100, 150, 150), null).mAdapter;
+ adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
+ mFinishedCallback);
+ mController.goodToGo(TRANSIT_OLD_TASK_OPEN);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+ final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+ ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+ verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_OPEN),
+ appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
+ finishedCaptor.capture());
+ boolean containNavTarget = false;
+ for (int i = 0; i < nonAppsCaptor.getValue().length; i++) {
+ if (nonAppsCaptor.getValue()[0].windowType == TYPE_NAVIGATION_BAR) {
+ containNavTarget = true;
+ break;
+ }
+ }
+ assertFalse(containNavTarget);
+ }
+
+ @Test
+ public void testNonAppTarget_notSendNavBar_controlledByRecents() throws Exception {
+ final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ mDisplayContent.mOpeningApps.add(win.mActivityRecord);
+ final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "NavigationBar");
+ mDisplayContent.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
+ final RecentsAnimationController mockController =
+ mock(RecentsAnimationController.class);
+ doReturn(mockController).when(mWm).getRecentsAnimationController();
+ final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
+ spyOn(policy);
+ doReturn(true).when(policy).shouldAttachNavBarToAppDuringTransition();
+
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+ win.mActivityRecord, new Point(50, 100), null,
+ new Rect(50, 100, 150, 150), null).mAdapter;
+ adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
+ mFinishedCallback);
+ mController.goodToGo(TRANSIT_OLD_TASK_OPEN);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+ final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+ ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+ verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_OPEN),
+ appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
+ finishedCaptor.capture());
+ boolean containNavTarget = false;
+ for (int i = 0; i < nonAppsCaptor.getValue().length; i++) {
+ if (nonAppsCaptor.getValue()[0].windowType == TYPE_NAVIGATION_BAR) {
+ containNavTarget = true;
+ break;
+ }
+ }
+ assertFalse(containNavTarget);
+ }
+
private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) {
verify(binder, atLeast(0)).asBinder();
verifyNoMoreInteractions(binder);
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 fb2272e..5239462 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 @@
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 @@
// 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 @@
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 @@
if (sb < db) {
state.getSource(ITYPE_NAVIGATION_BAR).setFrame(dl, sb, dr, db);
}
+ // Recompute config and push to children.
+ display.onRequestedOverrideConfigurationChanged(display.getConfiguration());
}
private ActivityRecord createSourceActivity(TestDisplayContent display) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 401ace03c..154a899 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -316,9 +316,9 @@
mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */));
final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken,
"wallpaperWindow");
- wallpaperWindow.mWallpaperVisible = false;
+ wallpaperWindowToken.setVisibleRequested(false);
transition.collect(wallpaperWindowToken);
- wallpaperWindow.mWallpaperVisible = true;
+ wallpaperWindowToken.setVisibleRequested(true);
wallpaperWindow.mHasSurface = true;
// doesn't matter which order collected since participants is a set
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index d1d0ac6..8b4e947 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -24,6 +24,8 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -49,7 +51,9 @@
import android.view.InsetsState;
import android.view.RoundedCorners;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.WindowManager;
+import android.window.ITransitionPlayer;
import androidx.test.filters.SmallTest;
@@ -135,7 +139,8 @@
int expectedWidth = (int) (wallpaperWidth * (displayHeight / (double) wallpaperHeight));
// Check that the wallpaper is correctly scaled
- assertEquals(new Rect(0, 0, expectedWidth, displayHeight), wallpaperWindow.getFrame());
+ assertEquals(expectedWidth, wallpaperWindow.getFrame().width());
+ assertEquals(displayHeight, wallpaperWindow.getFrame().height());
Rect portraitFrame = wallpaperWindow.getFrame();
// Rotate the display
@@ -297,6 +302,46 @@
assertFalse(mAppWindow.mActivityRecord.hasFixedRotationTransform());
}
+ @Test
+ public void testWallpaperTokenVisibility() {
+ final DisplayContent dc = mWm.mRoot.getDefaultDisplay();
+ final WallpaperWindowToken token = new WallpaperWindowToken(mWm, mock(IBinder.class),
+ true, dc, true /* ownerCanManageAppTokens */);
+ final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, token,
+ "wallpaperWindow");
+ wallpaperWindow.setHasSurface(true);
+
+ // Set-up mock shell transitions
+ final IBinder mockBinder = mock(IBinder.class);
+ final ITransitionPlayer mockPlayer = mock(ITransitionPlayer.class);
+ doReturn(mockBinder).when(mockPlayer).asBinder();
+ mWm.mAtmService.getTransitionController().registerTransitionPlayer(mockPlayer);
+
+ Transition transit =
+ mWm.mAtmService.getTransitionController().createTransition(TRANSIT_OPEN);
+
+ // wallpaper windows are immediately visible when set to visible even during a transition
+ token.setVisibility(true);
+ assertTrue(wallpaperWindow.isVisible());
+ assertTrue(token.isVisibleRequested());
+ assertTrue(token.isVisible());
+ mWm.mAtmService.getTransitionController().abort(transit);
+
+ // In a transition, setting invisible should ONLY set requestedVisible false; otherwise
+ // wallpaper should remain "visible" until transition is over.
+ transit = mWm.mAtmService.getTransitionController().createTransition(TRANSIT_CLOSE);
+ transit.start();
+ token.setVisibility(false);
+ assertTrue(wallpaperWindow.isVisible());
+ assertFalse(token.isVisibleRequested());
+ assertTrue(token.isVisible());
+
+ transit.onTransactionReady(transit.getSyncId(), mock(SurfaceControl.Transaction.class));
+ transit.finishTransition();
+ assertFalse(wallpaperWindow.isVisible());
+ assertFalse(token.isVisible());
+ }
+
private WindowState createWallpaperTargetWindow(DisplayContent dc) {
final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService)
.setTask(dc.getDefaultTaskDisplayArea().getRootHomeTask())
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 99c96bd..bbb885eb 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 @@
}
@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 @@
sources.add(activity);
// Simulate the task applying the exit transition, verify the main window of the task
- // will be set the frozen insets state.
+ // will be set the frozen insets state before the animation starts
+ activity.setVisibility(false);
task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */,
false /* isVoiceInteraction */, sources);
verify(win).freezeInsetsState();
- // Simulate the task transition finished, verify the frozen insets state of the window
- // will be reset.
+ // Simulate the task transition finished.
+ activity.commitVisibility(false, false);
task.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION,
task.mSurfaceAnimator.getAnimation());
+
+ // Now make it visible again, verify that the insets are immediately unfrozen even before
+ // transition starts.
+ activity.setVisibility(true);
verify(win).clearFrozenInsetsState();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index ebc5c4f..1f38f46 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -245,6 +245,9 @@
private WindowToken createWindowToken(
DisplayContent dc, int windowingMode, int activityType, int type) {
+ if (type == TYPE_WALLPAPER) {
+ return createWallpaperToken(dc);
+ }
if (type < FIRST_APPLICATION_WINDOW || type > LAST_APPLICATION_WINDOW) {
return createTestWindowToken(type, dc);
}
@@ -252,6 +255,11 @@
return createActivityRecord(dc, windowingMode, activityType);
}
+ private WindowToken createWallpaperToken(DisplayContent dc) {
+ return new WallpaperWindowToken(mWm, mock(IBinder.class), true /* explicit */, dc,
+ true /* ownerCanManageAppTokens */);
+ }
+
WindowState createAppWindow(Task task, int type, String name) {
final ActivityRecord activity = createNonAttachedActivityRecord(task.getDisplayContent());
task.addChild(activity, 0);
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index b1e6683..f35b9e2 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 @@
// 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 @@
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/common/com/android/internal/telephony/SipMessageParsingUtils.java b/telephony/common/com/android/internal/telephony/SipMessageParsingUtils.java
index c7e7cd5..179248d 100644
--- a/telephony/common/com/android/internal/telephony/SipMessageParsingUtils.java
+++ b/telephony/common/com/android/internal/telephony/SipMessageParsingUtils.java
@@ -65,6 +65,11 @@
// compact form of the via header key
private static final String VIA_SIP_HEADER_KEY_COMPACT = "v";
+ // call-id header key
+ private static final String CALL_ID_SIP_HEADER_KEY = "call-id";
+ // compact form of the call-id header key
+ private static final String CALL_ID_SIP_HEADER_KEY_COMPACT = "i";
+
/**
* @return true if the SIP message start line is considered a request (based on known request
* methods).
@@ -124,6 +129,17 @@
return null;
}
+ /**
+ * Return the call-id header key's associated value.
+ * @param headerString The string containing the headers of the SIP message.
+ */
+ public static String getCallId(String headerString) {
+ // search for the call-Id header, there should only be one in the header.
+ List<Pair<String, String>> headers = parseHeaders(headerString, true,
+ CALL_ID_SIP_HEADER_KEY, CALL_ID_SIP_HEADER_KEY_COMPACT);
+ return !headers.isEmpty() ? headers.get(0).second : null;
+ }
+
private static String[] splitStartLineAndVerify(String startLine) {
String[] splitLine = startLine.split(" ");
if (isStartLineMalformed(splitLine)) return null;
diff --git a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
index bdf628b..cedf48b 100644
--- a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
+++ b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
@@ -24,9 +24,14 @@
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 @@
@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 @@
}
/**
- * 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 @@
* 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 @@
}
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 @@
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 @@
@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 @@
}
};
+ // 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 @@
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 9299fed..52d0f03 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -340,6 +340,7 @@
}
/**
+ * 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 09c07d3..815c08d 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -32,11 +32,12 @@
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 @@
/**
* 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 @@
* 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 @@
}
/**
+ * 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 @@
* {@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/SipMessage.java b/telephony/java/android/telephony/ims/SipMessage.java
index 9cfa640..ad6d73c 100644
--- a/telephony/java/android/telephony/ims/SipMessage.java
+++ b/telephony/java/android/telephony/ims/SipMessage.java
@@ -19,6 +19,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Build;
import android.os.Parcel;
@@ -46,6 +47,8 @@
private final String mStartLine;
private final String mHeaderSection;
private final byte[] mContent;
+ private final String mViaBranchParam;
+ private final String mCallIdParam;
/**
* Represents a partially encoded SIP message.
@@ -63,6 +66,9 @@
mStartLine = startLine;
mHeaderSection = headerSection;
mContent = content;
+
+ mViaBranchParam = SipMessageParsingUtils.getTransactionId(mHeaderSection);
+ mCallIdParam = SipMessageParsingUtils.getCallId(mHeaderSection);
}
/**
@@ -73,6 +79,8 @@
mHeaderSection = source.readString();
mContent = new byte[source.readInt()];
source.readByteArray(mContent);
+ mViaBranchParam = source.readString();
+ mCallIdParam = source.readString();
}
/**
@@ -97,6 +105,25 @@
return mContent;
}
+ /**
+ * @return the branch parameter enclosed in the Via header key's value. See RFC 3261 section
+ * 20.42 for more information on the Via header. If {@code null}, then there was either no
+ * Via parameter found in this SIP message's headers or no branch parameter found in the
+ * Via header.
+ */
+ public @Nullable String getViaBranchParameter() {
+ return mViaBranchParam;
+ }
+
+ /**
+ * @return the value associated with the call-id header of this SIP message. See RFC 3261
+ * section 20.8 for more information on the call-id header. If {@code null}, then there was no
+ * call-id header found in this SIP message's headers.
+ */
+ public @Nullable String getCallIdParameter() {
+ return mCallIdParam;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -108,6 +135,8 @@
dest.writeString(mHeaderSection);
dest.writeInt(mContent.length);
dest.writeByteArray(mContent);
+ dest.writeString(mViaBranchParam);
+ dest.writeString(mCallIdParam);
}
public static final @NonNull Creator<SipMessage> CREATOR = new Creator<SipMessage>() {
diff --git a/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java
index 9d91901..739946b 100644
--- a/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java
+++ b/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java
@@ -31,8 +31,6 @@
import android.text.TextUtils;
import android.util.Log;
-import com.android.internal.telephony.SipMessageParsingUtils;
-
import java.util.ArrayList;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -188,7 +186,7 @@
}
private void notifyLocalMessageFailedToBeReceived(SipMessage m, int reason) {
- String transactionId = SipMessageParsingUtils.getTransactionId(m.getHeaderSection());
+ String transactionId = m.getViaBranchParameter();
if (TextUtils.isEmpty(transactionId)) {
Log.w(LOG_TAG, "failure to parse SipMessage.");
throw new IllegalArgumentException("Malformed SipMessage, can not determine "
diff --git a/telephony/java/android/telephony/ims/aidl/SipDelegateConnectionAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/SipDelegateConnectionAidlWrapper.java
index c877aca..3cd2726 100644
--- a/telephony/java/android/telephony/ims/aidl/SipDelegateConnectionAidlWrapper.java
+++ b/telephony/java/android/telephony/ims/aidl/SipDelegateConnectionAidlWrapper.java
@@ -32,8 +32,6 @@
import android.util.ArraySet;
import android.util.Log;
-import com.android.internal.telephony.SipMessageParsingUtils;
-
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.Executor;
@@ -268,7 +266,7 @@
}
private void notifyLocalMessageFailedToSend(SipMessage m, int reason) {
- String transactionId = SipMessageParsingUtils.getTransactionId(m.getHeaderSection());
+ String transactionId = m.getViaBranchParameter();
if (TextUtils.isEmpty(transactionId)) {
Log.w(LOG_TAG, "sendMessage detected a malformed SipMessage and can not get a "
+ "transaction ID.");
diff --git a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
index 908869b..00c9168 100644
--- a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
@@ -31,6 +31,7 @@
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 @@
/**
* 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 @@
/**
* 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 @@
* @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 @@
}
/**
+ * 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 15d19a4..541292a 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -114,6 +114,7 @@
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/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 62ccb1a0..6bf4492 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -771,6 +771,24 @@
</intent-filter>
</activity>
+ <activity android:name="StretchShaderActivity"
+ android:label="RenderEffect/Stretch"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="com.android.test.hwui.TEST"/>
+ </intent-filter>
+ </activity>
+
+ <activity android:name="EdgeEffectStretchActivity"
+ android:label="RenderEffect/EdgeEffect stretch"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="com.android.test.hwui.TEST"/>
+ </intent-filter>
+ </activity>
+
<activity android:name="TextActivity"
android:label="Text/Simple Text"
android:theme="@android:style/Theme.NoTitleBar"
diff --git a/tests/HwAccelerationTest/res/layout/stretch_layout.xml b/tests/HwAccelerationTest/res/layout/stretch_layout.xml
new file mode 100644
index 0000000..df5f297
--- /dev/null
+++ b/tests/HwAccelerationTest/res/layout/stretch_layout.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/scroll_view"
+ android:edgeEffectType="stretch"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <HorizontalScrollView
+ android:id="@+id/horizontal_scroll_view"
+ android:edgeEffectType="stretch"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="200dp"
+ android:src="@drawable/sunset1"/>
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="200dp"
+ android:src="@drawable/sunset1"/>
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="200dp"
+ android:src="@drawable/sunset1"/>
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="200dp"
+ android:src="@drawable/sunset1"/>
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="200dp"
+ android:src="@drawable/sunset1"/>
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="200dp"
+ android:src="@drawable/sunset1"/>
+ </LinearLayout>
+ </HorizontalScrollView>
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="200dp"
+ android:src="@drawable/sunset1"/>
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="200dp"
+ android:src="@drawable/sunset1"/>
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="200dp"
+ android:src="@drawable/sunset1"/>
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="200dp"
+ android:src="@drawable/sunset1"/>
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="200dp"
+ android:src="@drawable/sunset1"/>
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="200dp"
+ android:src="@drawable/sunset1"/>
+
+ </LinearLayout>
+</ScrollView>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/EdgeEffectStretchActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/EdgeEffectStretchActivity.java
new file mode 100644
index 0000000..f0e6299
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/EdgeEffectStretchActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.HorizontalScrollView;
+import android.widget.ScrollView;
+
+public class EdgeEffectStretchActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.stretch_layout);
+ HorizontalScrollView hsv = findViewById(R.id.horizontal_scroll_view);
+ hsv.setStretchDistance(50f);
+
+ ScrollView sv = findViewById(R.id.scroll_view);
+ sv.setStretchDistance(50f);
+ }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java
new file mode 100644
index 0000000..9bd933a
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java
@@ -0,0 +1,540 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.RecordingCanvas;
+import android.graphics.Rect;
+import android.graphics.RenderEffect;
+import android.graphics.RuntimeShader;
+import android.graphics.Shader;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+public class StretchShaderActivity extends Activity {
+
+ private static final float MAX_STRETCH_INTENSITY = 1.5f;
+ private static final float STRETCH_AFFECTED_DISTANCE = 1.0f;
+
+ private float mScrollX = 0f;
+ private float mScrollY = 0f;
+
+ private float mMaxStretchIntensity = MAX_STRETCH_INTENSITY;
+ private float mStretchAffectedDistance = STRETCH_AFFECTED_DISTANCE;
+
+ private float mOverscrollX = 25f;
+ private float mOverscrollY = 25f;
+
+ private RuntimeShader mRuntimeShader;
+ private ImageView mImageView;
+ private ImageView mTestImageView;
+
+ private Bitmap mBitmap;
+
+ private StretchDrawable mStretchDrawable = new StretchDrawable();
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ LinearLayout linearLayout = new LinearLayout(this);
+ linearLayout.setOrientation(LinearLayout.VERTICAL);
+
+ mBitmap = ((BitmapDrawable) getDrawable(R.drawable.sunset1)).getBitmap();
+ mRuntimeShader = new RuntimeShader(SKSL, false);
+
+ BitmapShader bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP,
+ Shader.TileMode.CLAMP);
+ mRuntimeShader.setInputShader("uContentTexture", bitmapShader);
+
+ mImageView = new ImageView(this);
+
+ mImageView.setRenderEffect(RenderEffect.createShaderEffect(mRuntimeShader));
+ mImageView.setImageDrawable(new ColorDrawable(Color.CYAN));
+
+ TextView overscrollXText = new TextView(this);
+ overscrollXText.setText("Overscroll X");
+
+ SeekBar overscrollXBar = new SeekBar(this);
+ overscrollXBar.setProgress(0);
+ overscrollXBar.setMin(-50);
+ overscrollXBar.setMax(50);
+ overscrollXBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ mOverscrollX = progress;
+ overscrollXText.setText("Overscroll X: " + mOverscrollX);
+ updateShader();
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+
+ }
+ });
+
+ TextView overscrollYText = new TextView(this);
+ overscrollYText.setText("Overscroll Y");
+
+ SeekBar overscrollYBar = new SeekBar(this);
+ overscrollYBar.setProgress(0);
+ overscrollYBar.setMin(-50);
+ overscrollYBar.setMax(50);
+ overscrollYBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ mOverscrollY = progress;
+ overscrollYText.setText("Overscroll Y: " + mOverscrollY);
+ updateShader();
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+
+ }
+ });
+
+ TextView scrollXText = new TextView(this);
+ scrollXText.setText("Scroll X");
+ SeekBar scrollXSeekBar = new SeekBar(this);
+ scrollXSeekBar.setMin(0);
+ scrollXSeekBar.setMax(100);
+ scrollXSeekBar.setProgress(0);
+ scrollXSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ mScrollX = (progress / 100f);
+ scrollXText.setText("Scroll X: " + mScrollY);
+ updateShader();
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+
+ }
+ });
+
+ TextView scrollYText = new TextView(this);
+ scrollYText.setText("Scroll Y");
+ SeekBar scrollYSeekBar = new SeekBar(this);
+ scrollYSeekBar.setMin(0);
+ scrollYSeekBar.setMax(100);
+ scrollYSeekBar.setProgress(0);
+ scrollYSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ mScrollY = (progress / 100f);
+ scrollYText.setText("Scroll Y: " + mScrollY);
+ updateShader();
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+
+ }
+ });
+
+ TextView stretchIntensityText = new TextView(this);
+ int stretchProgress = (int) (mMaxStretchIntensity * 100);
+ stretchIntensityText.setText("StretchIntensity: " + mMaxStretchIntensity);
+ SeekBar stretchIntensitySeekbar = new SeekBar(this);
+ stretchIntensitySeekbar.setProgress(stretchProgress);
+ stretchIntensitySeekbar.setMin(1);
+ stretchIntensitySeekbar.setMax((int) (MAX_STRETCH_INTENSITY * 100));
+ stretchIntensitySeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ mMaxStretchIntensity = progress / 100f;
+ stretchIntensityText.setText("StretchIntensity: " + mMaxStretchIntensity);
+ updateShader();
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+
+ }
+ });
+
+ TextView stretchDistanceText = new TextView(this);
+ stretchDistanceText.setText("StretchDistance");
+ SeekBar stretchDistanceSeekbar = new SeekBar(this);
+ stretchDistanceSeekbar.setMin(0);
+ stretchDistanceSeekbar.setProgress((int) (mStretchAffectedDistance * 100));
+ stretchDistanceSeekbar.setMax(100);
+ stretchDistanceSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ mStretchAffectedDistance = progress / 100f;
+ stretchDistanceText.setText("StretchDistance: " + mStretchAffectedDistance);
+ updateShader();
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+
+ }
+ });
+
+
+ linearLayout.addView(mImageView,
+ new LinearLayout.LayoutParams(
+ mBitmap.getWidth(),
+ mBitmap.getHeight())
+ );
+
+ linearLayout.addView(overscrollXText,
+ new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT
+ ));
+ linearLayout.addView(overscrollXBar,
+ new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ );
+
+ linearLayout.addView(overscrollYText,
+ new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT
+ ));
+ linearLayout.addView(overscrollYBar,
+ new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ );
+
+ linearLayout.addView(scrollXText,
+ new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT
+ ));
+
+ linearLayout.addView(scrollXSeekBar,
+ new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ ));
+
+ linearLayout.addView(scrollYText,
+ new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT
+ ));
+
+ linearLayout.addView(scrollYSeekBar,
+ new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ ));
+
+ linearLayout.addView(stretchIntensityText,
+ new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT
+ )
+ );
+
+ linearLayout.addView(stretchIntensitySeekbar,
+ new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT
+ )
+ );
+
+ linearLayout.addView(stretchDistanceText,
+ new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT
+ ));
+
+ linearLayout.addView(stretchDistanceSeekbar,
+ new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT
+ ));
+
+ ImageView test = new ImageView(this);
+ mStretchDrawable.setBitmap(mBitmap);
+ test.setImageDrawable(mStretchDrawable);
+
+ mTestImageView = test;
+ linearLayout.addView(test,
+ new LinearLayout.LayoutParams(mBitmap.getWidth(), mBitmap.getHeight()));
+
+ setContentView(linearLayout);
+
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mImageView.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ updateShader();
+ mImageView.getViewTreeObserver().removeOnPreDrawListener(this);
+ return false;
+ }
+ });
+ }
+
+ private void updateShader() {
+ final float width = mImageView.getWidth();
+ final float height = mImageView.getHeight();
+ final float distanceNotStretched = mStretchAffectedDistance;
+ final float normOverScrollDistX = mOverscrollX / width;
+ final float normOverScrollDistY = mOverscrollY / height;
+ final float distanceStretchedX =
+ mStretchAffectedDistance
+ / (1 + Math.abs(normOverScrollDistX) * mMaxStretchIntensity);
+ final float distanceStretchedY =
+ mStretchAffectedDistance
+ / (1 + Math.abs(normOverScrollDistY) * mMaxStretchIntensity);
+ final float diffX = distanceStretchedX - distanceNotStretched;
+ final float diffY = distanceStretchedY - distanceNotStretched;
+ float uScrollX = mScrollX;
+ float uScrollY = mScrollY;
+
+ mRuntimeShader.setUniform("uMaxStretchIntensity", mMaxStretchIntensity);
+ mRuntimeShader.setUniform("uStretchAffectedDist", mStretchAffectedDistance);
+ mRuntimeShader.setUniform("uDistanceStretchedX", distanceStretchedX);
+ mRuntimeShader.setUniform("uDistanceStretchedY", distanceStretchedY);
+ mRuntimeShader.setUniform("uDistDiffX", diffX);
+ mRuntimeShader.setUniform("uDistDiffY", diffY);
+ mRuntimeShader.setUniform("uOverscrollX", normOverScrollDistX);
+ mRuntimeShader.setUniform("uOverscrollY", normOverScrollDistY);
+ mRuntimeShader.setUniform("uScrollX", uScrollX);
+ mRuntimeShader.setUniform("uScrollY", uScrollY);
+ mRuntimeShader.setUniform("viewportWidth", width);
+ mRuntimeShader.setUniform("viewportHeight", height);
+
+ mImageView.setRenderEffect(RenderEffect.createShaderEffect(mRuntimeShader));
+
+ mStretchDrawable.setStretchDistance(mStretchAffectedDistance);
+ mStretchDrawable.setOverscrollX(normOverScrollDistX);
+ mStretchDrawable.setOverscrollY(normOverScrollDistY);
+ }
+
+ private static class StretchDrawable extends Drawable {
+
+ private float mStretchDistance = 0;
+ private float mOverScrollX = 0f;
+ private float mOverScrollY = 0f;
+ private Bitmap mBitmap = null;
+
+ public void setStretchDistance(float stretchDistance) {
+ mStretchDistance = stretchDistance;
+ invalidateSelf();
+ }
+
+ public void setOverscrollX(float overscrollX) {
+ mOverScrollX = overscrollX;
+ invalidateSelf();
+ }
+
+ public void setOverscrollY(float overscrollY) {
+ mOverScrollY = overscrollY;
+ invalidateSelf();
+ }
+
+ public void setBitmap(Bitmap bitmap) {
+ mBitmap = bitmap;
+ invalidateSelf();
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mStretchDistance > 0 && canvas instanceof RecordingCanvas) {
+ Rect bounds = getBounds();
+ ((RecordingCanvas) canvas).mNode.stretch(
+ 0,
+ 0,
+ bounds.width(),
+ bounds.height(),
+ mOverScrollX,
+ mOverScrollY,
+ mStretchDistance
+ );
+ }
+ if (mBitmap != null) {
+ canvas.drawBitmap(mBitmap, 0f, 0f, null);
+ }
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+
+ }
+
+ @Override
+ public int getOpacity() {
+ return 0;
+ }
+ }
+
+ private static final String SKSL = "in shader uContentTexture;\n"
+ + "uniform float uMaxStretchIntensity; // multiplier to apply to scale effect\n"
+ + "uniform float uStretchAffectedDist; // Maximum percentage to stretch beyond bounds"
+ + " of target\n"
+ + "\n"
+ + "// Distance stretched as a function of the normalized overscroll times scale "
+ + "intensity\n"
+ + "uniform float uDistanceStretchedX;\n"
+ + "uniform float uDistanceStretchedY;\n"
+ + "uniform float uDistDiffX;\n"
+ + "uniform float uDistDiffY; // Difference between the peak stretch amount and "
+ + "overscroll amount normalized\n"
+ + "uniform float uScrollX; // Horizontal offset represented as a ratio of pixels "
+ + "divided by the target width\n"
+ + "uniform float uScrollY; // Vertical offset represented as a ratio of pixels "
+ + "divided by the target height\n"
+ + "uniform float uOverscrollX; // Normalized overscroll amount in the horizontal "
+ + "direction\n"
+ + "uniform float uOverscrollY; // Normalized overscroll amount in the vertical "
+ + "direction\n"
+ + "\n"
+ + "uniform float viewportWidth; // target height in pixels\n"
+ + "uniform float viewportHeight; // target width in pixels\n"
+ + "\n"
+ + "vec4 main(vec2 coord) {\n"
+ + "\n"
+ + " // Normalize SKSL pixel coordinate into a unit vector\n"
+ + " vec2 uv = vec2(coord.x / viewportWidth, coord.y / viewportHeight);\n"
+ + " float inU = uv.x;\n"
+ + " float inV = uv.y;\n"
+ + " float outU;\n"
+ + " float outV;\n"
+ + " float stretchIntensity;\n"
+ + "\n"
+ + " // Add the normalized scroll position within scrolling list\n"
+ + " inU += uScrollX;\n"
+ + " inV += uScrollY;\n"
+ + "\n"
+ + " outU = inU;\n"
+ + " outV = inV;\n"
+ + " if (uOverscrollX > 0) {\n"
+ + " if (inU <= uStretchAffectedDist) {\n"
+ + " inU = uStretchAffectedDist - inU;\n"
+ + " float posBasedVariation = smoothstep(0., uStretchAffectedDist, inU);\n"
+ + " stretchIntensity = uMaxStretchIntensity * uOverscrollX * "
+ + "posBasedVariation;\n"
+ + " outU = uDistanceStretchedX - (inU / (1. + stretchIntensity));\n"
+ + " } else {\n"
+ + " outU = uDistDiffX + inU;\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " if (uOverscrollX < 0) {\n"
+ + " float stretchAffectedDist = 1. - uStretchAffectedDist;\n"
+ + " if (inU >= stretchAffectedDist) {\n"
+ + " inU = inU - stretchAffectedDist;\n"
+ + " float posBasedVariation = (smoothstep(0., uStretchAffectedDist, "
+ + "inU));\n"
+ + " stretchIntensity = uMaxStretchIntensity * (-uOverscrollX) * "
+ + "posBasedVariation;\n"
+ + " outU = 1 - (uDistanceStretchedX - (inU / (1. + stretchIntensity)))"
+ + ";\n"
+ + " } else if (inU < stretchAffectedDist) {\n"
+ + " outU = -uDistDiffX + inU;\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " if (uOverscrollY > 0) {\n"
+ + " if (inV <= uStretchAffectedDist) {\n"
+ + " inV = uStretchAffectedDist - inV;\n"
+ + " float posBasedVariation = smoothstep(0., uStretchAffectedDist, inV);\n"
+ + " stretchIntensity = uMaxStretchIntensity * uOverscrollY * "
+ + "posBasedVariation;\n"
+ + " outV = uDistanceStretchedY - (inV / (1. + stretchIntensity));\n"
+ + " } else if (inV >= uStretchAffectedDist) {\n"
+ + " outV = uDistDiffY + inV;\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " if (uOverscrollY < 0) {\n"
+ + " float stretchAffectedDist = 1. - uStretchAffectedDist;\n"
+ + " if (inV >= stretchAffectedDist) {\n"
+ + " inV = inV - stretchAffectedDist;\n"
+ + " float posBasedVariation = (smoothstep(0., uStretchAffectedDist, inV));\n"
+ + " stretchIntensity = uMaxStretchIntensity * (-uOverscrollY) * "
+ + "posBasedVariation;\n"
+ + " outV = 1 - (uDistanceStretchedY - (inV / (1. + stretchIntensity)));\n"
+ + " } else if (inV < stretchAffectedDist) {\n"
+ + " outV = -uDistDiffY + inV;\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " uv.x = outU;\n"
+ + " uv.y = outV;\n"
+ + " coord.x = uv.x * viewportWidth;\n"
+ + " coord.y = uv.y * viewportHeight;\n"
+ + " return sample(uContentTexture, coord);\n"
+ + "}";
+}
diff --git a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
new file mode 100644
index 0000000..2e985fb
--- /dev/null
+++ b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.input
+
+import android.view.InputDevice.SOURCE_MOUSE
+import android.view.InputDevice.SOURCE_TOUCHSCREEN
+import android.view.InputEventAssigner
+import android.view.KeyEvent
+import android.view.MotionEvent
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * Create a MotionEvent with the provided action, eventTime, and source
+ */
+fun createMotionEvent(action: Int, eventTime: Long, source: Int): MotionEvent {
+ val downTime: Long = 10
+ val x = 1f
+ val y = 2f
+ val pressure = 3f
+ val size = 1f
+ val metaState = 0
+ val xPrecision = 0f
+ val yPrecision = 0f
+ val deviceId = 1
+ val edgeFlags = 0
+ val displayId = 0
+ return MotionEvent.obtain(downTime, eventTime, action, x, y, pressure, size, metaState,
+ xPrecision, yPrecision, deviceId, edgeFlags, source, displayId)
+}
+
+fun createKeyEvent(action: Int, eventTime: Long): KeyEvent {
+ val code = KeyEvent.KEYCODE_A
+ val repeat = 0
+ return KeyEvent(eventTime, eventTime, action, code, repeat)
+}
+
+class InputEventAssignerTest {
+ companion object {
+ private const val TAG = "InputEventAssignerTest"
+ }
+
+ /**
+ * A single MOVE event should be assigned to the next available frame.
+ */
+ @Test
+ fun testTouchGesture() {
+ val assigner = InputEventAssigner()
+ val event = createMotionEvent(MotionEvent.ACTION_MOVE, 10, SOURCE_TOUCHSCREEN)
+ val eventId = assigner.processEvent(event)
+ assertEquals(event.id, eventId)
+ }
+
+ /**
+ * DOWN event should be used until a vsync comes in. After vsync, the latest event should be
+ * produced.
+ */
+ @Test
+ fun testTouchDownWithMove() {
+ val assigner = InputEventAssigner()
+ val down = createMotionEvent(MotionEvent.ACTION_DOWN, 10, SOURCE_TOUCHSCREEN)
+ val move1 = createMotionEvent(MotionEvent.ACTION_MOVE, 12, SOURCE_TOUCHSCREEN)
+ val move2 = createMotionEvent(MotionEvent.ACTION_MOVE, 13, SOURCE_TOUCHSCREEN)
+ val move3 = createMotionEvent(MotionEvent.ACTION_MOVE, 14, SOURCE_TOUCHSCREEN)
+ val move4 = createMotionEvent(MotionEvent.ACTION_MOVE, 15, SOURCE_TOUCHSCREEN)
+ var eventId = assigner.processEvent(down)
+ assertEquals(down.id, eventId)
+ eventId = assigner.processEvent(move1)
+ assertEquals(down.id, eventId)
+ eventId = assigner.processEvent(move2)
+ // Even though we already had 2 move events, there was no choreographer callback yet.
+ // Therefore, we should still get the id of the down event
+ assertEquals(down.id, eventId)
+
+ // Now send CALLBACK_INPUT to the assigner. It should provide the latest motion event
+ assigner.onChoreographerCallback()
+ eventId = assigner.processEvent(move3)
+ assertEquals(move3.id, eventId)
+ eventId = assigner.processEvent(move4)
+ assertEquals(move4.id, eventId)
+ }
+
+ /**
+ * Similar to the above test, but with SOURCE_MOUSE. Since we don't have down latency
+ * concept for non-touchscreens, the latest input event will be used.
+ */
+ @Test
+ fun testMouseDownWithMove() {
+ val assigner = InputEventAssigner()
+ val down = createMotionEvent(MotionEvent.ACTION_DOWN, 10, SOURCE_MOUSE)
+ val move1 = createMotionEvent(MotionEvent.ACTION_MOVE, 12, SOURCE_MOUSE)
+ var eventId = assigner.processEvent(down)
+ assertEquals(down.id, eventId)
+ eventId = assigner.processEvent(move1)
+ assertEquals(move1.id, eventId)
+ }
+
+ /**
+ * KeyEvents are processed immediately, so the latest event should be returned.
+ */
+ @Test
+ fun testKeyEvent() {
+ val assigner = InputEventAssigner()
+ val down = createKeyEvent(KeyEvent.ACTION_DOWN, 20)
+ var eventId = assigner.processEvent(down)
+ assertEquals(down.id, eventId)
+ val up = createKeyEvent(KeyEvent.ACTION_UP, 21)
+ eventId = assigner.processEvent(up)
+ // DOWN is only sticky for Motions, not for keys
+ assertEquals(up.id, eventId)
+ assigner.onChoreographerCallback()
+ val down2 = createKeyEvent(KeyEvent.ACTION_DOWN, 22)
+ eventId = assigner.processEvent(down2)
+ assertEquals(down2.id, eventId)
+ }
+}
diff --git a/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt b/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt
index c19e5cc..c01d32b 100644
--- a/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt
+++ b/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt
@@ -17,6 +17,7 @@
package com.android.test.input
import android.graphics.FrameInfo
+import android.os.IInputConstants.INVALID_INPUT_EVENT_ID
import android.os.SystemClock
import android.view.ViewFrameInfo
import com.google.common.truth.Truth.assertThat
@@ -33,8 +34,7 @@
@Before
fun setUp() {
mViewFrameInfo.reset()
- mViewFrameInfo.updateOldestInputEvent(10)
- mViewFrameInfo.updateNewestInputEvent(20)
+ mViewFrameInfo.setInputEvent(139)
mViewFrameInfo.flags = mViewFrameInfo.flags or FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED
mTimeStarted = SystemClock.uptimeNanos()
mViewFrameInfo.markDrawStart()
@@ -43,8 +43,6 @@
@Test
fun testPopulateFields() {
assertThat(mViewFrameInfo.drawStart).isGreaterThan(mTimeStarted)
- assertThat(mViewFrameInfo.oldestInputEventTime).isEqualTo(10)
- assertThat(mViewFrameInfo.newestInputEventTime).isEqualTo(20)
assertThat(mViewFrameInfo.flags).isEqualTo(FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED)
}
@@ -53,8 +51,6 @@
mViewFrameInfo.reset()
// Ensure that the original object is reset correctly
assertThat(mViewFrameInfo.drawStart).isEqualTo(0)
- assertThat(mViewFrameInfo.oldestInputEventTime).isEqualTo(0)
- assertThat(mViewFrameInfo.newestInputEventTime).isEqualTo(0)
assertThat(mViewFrameInfo.flags).isEqualTo(0)
}
@@ -62,12 +58,13 @@
fun testUpdateFrameInfoFromViewFrameInfo() {
val frameInfo = FrameInfo()
// By default, all values should be zero
- // TODO(b/169866723): Use InputEventAssigner and assert INPUT_EVENT_ID
+ assertThat(frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID]).isEqualTo(INVALID_INPUT_EVENT_ID)
assertThat(frameInfo.frameInfo[FrameInfo.FLAGS]).isEqualTo(0)
assertThat(frameInfo.frameInfo[FrameInfo.DRAW_START]).isEqualTo(0)
// The values inside FrameInfo should match those from ViewFrameInfo after we update them
mViewFrameInfo.populateFrameInfo(frameInfo)
+ assertThat(frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID]).isEqualTo(139)
assertThat(frameInfo.frameInfo[FrameInfo.FLAGS]).isEqualTo(
FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED)
assertThat(frameInfo.frameInfo[FrameInfo.DRAW_START]).isGreaterThan(mTimeStarted)
diff --git a/tests/net/common/java/android/net/CaptivePortalTest.java b/tests/net/common/java/android/net/CaptivePortalTest.java
index 7a60cc1..15d3398 100644
--- a/tests/net/common/java/android/net/CaptivePortalTest.java
+++ b/tests/net/common/java/android/net/CaptivePortalTest.java
@@ -24,8 +24,8 @@
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.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import org.junit.Rule;
@@ -55,7 +55,7 @@
mCode = request;
}
- @Override
+ // This is only @Override on R-
public void logEvent(int eventId, String packageName) throws RemoteException {
mCode = eventId;
mPackageName = packageName;
@@ -98,12 +98,24 @@
assertEquals(result.mCode, CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED);
}
+ @IgnoreUpTo(Build.VERSION_CODES.R)
@Test
public void testLogEvent() {
+ /**
+ * From S testLogEvent is expected to do nothing but shouldn't crash (the API
+ * logEvent has been deprecated).
+ */
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);
+ }
+
+ @IgnoreAfter(Build.VERSION_CODES.R)
+ @Test
+ public void testLogEvent_UntilR() {
+ final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.logEvent(
+ 42, TEST_PACKAGE_NAME));
+ assertEquals(result.mCode, 42);
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 c10c573..2a2dc56 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.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.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 @@
// 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 @@
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 f7106ab..de74f38 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -103,6 +103,7 @@
import static com.android.testutils.ConcurrentUtils.durationOf;
import static com.android.testutils.ExceptionUtils.ignoreExceptions;
import static com.android.testutils.HandlerUtils.waitForIdleSerialExecutor;
+import static com.android.testutils.MiscAsserts.assertContainsAll;
import static com.android.testutils.MiscAsserts.assertContainsExactly;
import static com.android.testutils.MiscAsserts.assertEmpty;
import static com.android.testutils.MiscAsserts.assertLength;
@@ -149,6 +150,7 @@
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 +182,6 @@
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;
@@ -250,7 +251,6 @@
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 +282,7 @@
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;
@@ -351,6 +352,9 @@
private static final String TAG = "ConnectivityServiceTest";
private static final int TIMEOUT_MS = 500;
+ // Broadcasts can take a long time to be delivered. The test will not wait for that long unless
+ // there is a failure, so use a long timeout.
+ private static final int BROADCAST_TIMEOUT_MS = 30_000;
private static final int TEST_LINGER_DELAY_MS = 400;
private static final int TEST_NASCENT_DELAY_MS = 300;
// Chosen to be less than the linger and nascent timeout. This ensures that we can distinguish
@@ -413,6 +417,8 @@
private QosCallbackMockHelper mQosCallbackMockHelper;
private QosCallbackTracker mQosCallbackTracker;
private VpnManagerService mVpnManagerService;
+ private TestNetworkCallback mDefaultNetworkCallback;
+ private TestNetworkCallback mSystemDefaultNetworkCallback;
// State variables required to emulate NetworkPolicyManagerService behaviour.
private int mUidRules = RULE_NONE;
@@ -420,7 +426,7 @@
@Mock DeviceIdleInternal mDeviceIdleInternal;
@Mock INetworkManagementService mNetworkManagementService;
- @Mock INetworkStatsService mStatsService;
+ @Mock NetworkStatsManager mStatsManager;
@Mock IBatteryStats mBatteryStatsService;
@Mock IDnsResolver mMockDnsResolver;
@Mock INetd mMockNetd;
@@ -437,7 +443,7 @@
@Mock MockableSystemProperties mSystemProperties;
@Mock EthernetManager mEthernetManager;
@Mock NetworkPolicyManager mNetworkPolicyManager;
- @Mock KeyStore mKeyStore;
+ @Mock VpnProfileStore mVpnProfileStore;
@Mock SystemConfigManager mSystemConfigManager;
private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
@@ -536,6 +542,7 @@
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);
}
@@ -1121,7 +1128,7 @@
return mDeviceIdleInternal;
}
},
- mNetworkManagementService, mMockNetd, userId, mKeyStore);
+ mNetworkManagementService, mMockNetd, userId, mVpnProfileStore);
}
public void setUids(Set<UidRange> uids) {
@@ -1300,8 +1307,9 @@
return mVMSHandlerThread;
}
- public KeyStore getKeyStore() {
- return mKeyStore;
+ @Override
+ public VpnProfileStore getVpnProfileStore() {
+ return mVpnProfileStore;
}
public INetd getNetd() {
@@ -1468,7 +1476,6 @@
mDeps = makeDependencies();
returnRealCallingUid();
mService = new ConnectivityService(mServiceContext,
- mStatsService,
mMockDnsResolver,
mock(IpConnectivityLog.class),
mMockNetd,
@@ -1552,6 +1559,7 @@
@After
public void tearDown() throws Exception {
+ unregisterDefaultNetworkCallbacks();
setAlwaysOnNetworks(false);
if (mCellNetworkAgent != null) {
mCellNetworkAgent.disconnect();
@@ -1657,6 +1665,7 @@
assertNull(mCm.getActiveNetworkForUid(Process.myUid()));
// Test getAllNetworks()
assertEmpty(mCm.getAllNetworks());
+ assertEmpty(mCm.getAllNetworkStateSnapshot());
}
/**
@@ -1685,7 +1694,7 @@
}
public Intent expectBroadcast() throws Exception {
- return expectBroadcast(TIMEOUT_MS);
+ return expectBroadcast(BROADCAST_TIMEOUT_MS);
}
public void expectNoBroadcast(int timeoutMs) throws Exception {
@@ -5483,18 +5492,19 @@
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];
@@ -5508,8 +5518,9 @@
}
}
- 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
@@ -5529,46 +5540,46 @@
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();
@@ -5581,7 +5592,7 @@
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.
@@ -5598,9 +5609,9 @@
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
@@ -5610,22 +5621,22 @@
// 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
@@ -5637,17 +5648,17 @@
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
@@ -5656,13 +5667,13 @@
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
@@ -5672,27 +5683,27 @@
// 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
@@ -7506,8 +7517,7 @@
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";
@@ -7515,7 +7525,7 @@
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 {
@@ -9491,6 +9501,10 @@
fail("TOO_MANY_REQUESTS never thrown");
}
+ private UidRange createUidRange(int userId) {
+ return UidRange.createForUser(UserHandle.of(userId));
+ }
+
private void mockGetApplicationInfo(@NonNull final String packageName, @NonNull final int uid)
throws Exception {
final ApplicationInfo applicationInfo = new ApplicationInfo();
@@ -9825,6 +9839,54 @@
assertEquals(expectedPerAppNetwork, defaultNetwork);
assertEquals(expectedOemRequestsSize, defaultRequest.mRequests.size());
}
+ verifyMultipleDefaultCallbacks(expectedDefaultNetwork, expectedPerAppNetwork);
+ }
+
+ /**
+ * Verify default callbacks for 'available' fire as expected. This will only run if
+ * registerDefaultNetworkCallbacks() was executed prior and will only be different if the
+ * setOemNetworkPreference() per-app API was used for the current process.
+ * @param expectedSystemDefault the expected network for the system default.
+ * @param expectedPerAppDefault the expected network for the current process's default.
+ */
+ private void verifyMultipleDefaultCallbacks(
+ @NonNull final Network expectedSystemDefault,
+ @NonNull final Network expectedPerAppDefault) {
+ if (null != mSystemDefaultNetworkCallback && null != expectedSystemDefault
+ && mService.mNoServiceNetwork.network() != expectedSystemDefault) {
+ // getLastAvailableNetwork() is used as this method can be called successively with
+ // the same network to validate therefore expectAvailableThenValidatedCallbacks
+ // can't be used.
+ assertEquals(mSystemDefaultNetworkCallback.getLastAvailableNetwork(),
+ expectedSystemDefault);
+ }
+ if (null != mDefaultNetworkCallback && null != expectedPerAppDefault
+ && mService.mNoServiceNetwork.network() != expectedPerAppDefault) {
+ assertEquals(mDefaultNetworkCallback.getLastAvailableNetwork(),
+ expectedPerAppDefault);
+ }
+ }
+
+ private void registerDefaultNetworkCallbacks() {
+ // Using Manifest.permission.NETWORK_SETTINGS for registerSystemDefaultNetworkCallback()
+ mServiceContext.setPermission(
+ Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+ mSystemDefaultNetworkCallback = new TestNetworkCallback();
+ mDefaultNetworkCallback = new TestNetworkCallback();
+ mCm.registerSystemDefaultNetworkCallback(mSystemDefaultNetworkCallback,
+ new Handler(ConnectivityThread.getInstanceLooper()));
+ mCm.registerDefaultNetworkCallback(mDefaultNetworkCallback);
+ mServiceContext.setPermission(
+ Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED);
+ }
+
+ private void unregisterDefaultNetworkCallbacks() {
+ if (null != mDefaultNetworkCallback) {
+ mCm.unregisterNetworkCallback(mDefaultNetworkCallback);
+ }
+ if (null != mSystemDefaultNetworkCallback) {
+ mCm.unregisterNetworkCallback(mSystemDefaultNetworkCallback);
+ }
}
private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(
@@ -9873,12 +9935,11 @@
.build();
// Act on ConnectivityService.setOemNetworkPreference()
- final TestOemListenerCallback mOnSetOemNetworkPreferenceTestListener =
- new TestOemListenerCallback();
- mService.setOemNetworkPreference(pref, mOnSetOemNetworkPreferenceTestListener);
+ final TestOemListenerCallback oemPrefListener = new TestOemListenerCallback();
+ mService.setOemNetworkPreference(pref, oemPrefListener);
// Verify call returned successfully
- mOnSetOemNetworkPreferenceTestListener.expectOnComplete();
+ oemPrefListener.expectOnComplete();
}
private static class TestOemListenerCallback implements IOnSetOemNetworkPreferenceListener {
@@ -9908,6 +9969,7 @@
@OemNetworkPreferences.OemNetworkPreference final int networkPref =
OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
final int expectedOemPrefRequestSize = 1;
+ registerDefaultNetworkCallbacks();
// Setup the test process to use networkPref for their default network.
setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref);
@@ -9922,6 +9984,7 @@
// Verify that the active network is correct
verifyActiveNetwork(TRANSPORT_ETHERNET);
+ // default NCs will be unregistered in tearDown
}
@Test
@@ -9929,6 +9992,7 @@
@OemNetworkPreferences.OemNetworkPreference final int networkPref =
OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
final int expectedOemPrefRequestSize = 1;
+ registerDefaultNetworkCallbacks();
// Setup the test process to use networkPref for their default network.
setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref);
@@ -9949,6 +10013,7 @@
mEthernetNetworkAgent.getNetwork());
assertFalse(mCm.isActiveNetworkMetered());
+ // default NCs will be unregistered in tearDown
}
@Test
@@ -10105,7 +10170,6 @@
/**
* Test the tracked default requests clear previous OEM requests on setOemNetworkPreference().
- * @throws Exception
*/
@Test
public void testSetOemNetworkPreferenceClearPreviousOemValues() throws Exception {
@@ -10133,9 +10197,8 @@
}
/**
- * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID following in order:
+ * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID in the following order:
* NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID -> fallback
- * @throws Exception
*/
@Test
public void testMultilayerForPreferenceOemPaidEvaluatesCorrectly()
@@ -10201,9 +10264,8 @@
}
/**
- * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK following in order:
+ * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK in the following order:
* NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID
- * @throws Exception
*/
@Test
public void testMultilayerForPreferenceOemPaidNoFallbackEvaluatesCorrectly()
@@ -10264,10 +10326,9 @@
}
/**
- * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY following in order:
+ * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY in the following order:
* NET_CAPABILITY_OEM_PAID
* This preference should only apply to OEM_PAID networks.
- * @throws Exception
*/
@Test
public void testMultilayerForPreferenceOemPaidOnlyEvaluatesCorrectly()
@@ -10318,10 +10379,9 @@
}
/**
- * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY following in order:
+ * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY in the following order:
* NET_CAPABILITY_OEM_PRIVATE
* This preference should only apply to OEM_PRIVATE networks.
- * @throws Exception
*/
@Test
public void testMultilayerForPreferenceOemPrivateOnlyEvaluatesCorrectly()
@@ -10371,7 +10431,314 @@
true /* shouldDestroyNetwork */);
}
- private UidRange createUidRange(int userId) {
- return UidRange.createForUser(UserHandle.of(userId));
+ /**
+ * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID in the following order:
+ * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID -> fallback
+ */
+ @Test
+ public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidCorrectly()
+ throws Exception {
+ @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+ OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+ setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref);
+ final int expectedDefaultRequestSize = 2;
+ final int expectedOemPrefRequestSize = 3;
+ registerDefaultNetworkCallbacks();
+
+ // The fallback as well as the OEM preference should now be tracked.
+ assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size());
+
+ // Test lowest to highest priority requests.
+ // Bring up metered cellular. This will satisfy the fallback network.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mCellNetworkAgent.getNetwork());
+
+ // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mEthernetNetworkAgent.getNetwork());
+
+ // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mWiFiNetworkAgent.getNetwork(),
+ mWiFiNetworkAgent.getNetwork());
+
+ // Disconnecting unmetered Wi-Fi will put the pref on OEM_PAID and fallback on cellular.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mEthernetNetworkAgent.getNetwork());
+
+ // Disconnecting cellular should keep OEM network on OEM_PAID and fallback will be null.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ null,
+ mEthernetNetworkAgent.getNetwork());
+
+ // Disconnecting OEM_PAID will put both on null as it is the last network.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ null,
+ null);
+
+ // default NCs will be unregistered in tearDown
+ }
+
+ /**
+ * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK in the following order:
+ * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID
+ */
+ @Test
+ public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidNoFallbackCorrectly()
+ throws Exception {
+ @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+ OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
+ setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref);
+ final int expectedDefaultRequestSize = 2;
+ final int expectedOemPrefRequestSize = 2;
+ registerDefaultNetworkCallbacks();
+
+ // The fallback as well as the OEM preference should now be tracked.
+ assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size());
+
+ // Test lowest to highest priority requests.
+ // Bring up metered cellular. This will satisfy the fallback network but not the pref.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mService.mNoServiceNetwork.network());
+
+ // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mEthernetNetworkAgent.getNetwork());
+
+ // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mWiFiNetworkAgent.getNetwork(),
+ mWiFiNetworkAgent.getNetwork());
+
+ // Disconnecting unmetered Wi-Fi will put the OEM pref on OEM_PAID and fallback on cellular.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mEthernetNetworkAgent.getNetwork());
+
+ // Disconnecting cellular should keep OEM network on OEM_PAID and fallback will be null.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ null,
+ mEthernetNetworkAgent.getNetwork());
+
+ // Disconnecting OEM_PAID puts the fallback on null and the pref on the disconnected net.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ null,
+ mService.mNoServiceNetwork.network());
+
+ // default NCs will be unregistered in tearDown
+ }
+
+ /**
+ * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY in the following order:
+ * NET_CAPABILITY_OEM_PAID
+ * This preference should only apply to OEM_PAID networks.
+ */
+ @Test
+ public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidOnlyCorrectly()
+ throws Exception {
+ @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+ OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
+ setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref);
+ final int expectedDefaultRequestSize = 2;
+ final int expectedOemPrefRequestSize = 1;
+ registerDefaultNetworkCallbacks();
+
+ // The fallback as well as the OEM preference should now be tracked.
+ assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size());
+
+ // Test lowest to highest priority requests.
+ // Bring up metered cellular. This will satisfy the fallback network.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mService.mNoServiceNetwork.network());
+
+ // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mEthernetNetworkAgent.getNetwork());
+
+ // Bring up unmetered Wi-Fi. The OEM network shouldn't change, the fallback will take Wi-Fi.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mWiFiNetworkAgent.getNetwork(),
+ mEthernetNetworkAgent.getNetwork());
+
+ // Disconnecting unmetered Wi-Fi shouldn't change the OEM network with fallback on cellular.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mEthernetNetworkAgent.getNetwork());
+
+ // Disconnecting OEM_PAID will keep the fallback on cellular and nothing for OEM_PAID.
+ // OEM_PAID_ONLY not supporting a fallback now uses the disconnected network.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mService.mNoServiceNetwork.network());
+
+ // Disconnecting cellular will put the fallback on null and the pref on disconnected.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ null,
+ mService.mNoServiceNetwork.network());
+
+ // default NCs will be unregistered in tearDown
+ }
+
+ /**
+ * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY in the following order:
+ * NET_CAPABILITY_OEM_PRIVATE
+ * This preference should only apply to OEM_PRIVATE networks.
+ */
+ @Test
+ public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPrivateOnlyCorrectly()
+ throws Exception {
+ @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+ OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
+ setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref);
+ final int expectedDefaultRequestSize = 2;
+ final int expectedOemPrefRequestSize = 1;
+ registerDefaultNetworkCallbacks();
+
+ // The fallback as well as the OEM preference should now be tracked.
+ assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size());
+
+ // Test lowest to highest priority requests.
+ // Bring up metered cellular. This will satisfy the fallback network.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mService.mNoServiceNetwork.network());
+
+ // Bring up ethernet with OEM_PRIVATE. This will satisfy NET_CAPABILITY_OEM_PRIVATE.
+ startOemManagedNetwork(false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mEthernetNetworkAgent.getNetwork());
+
+ // Bring up unmetered Wi-Fi. The OEM network shouldn't change, the fallback will take Wi-Fi.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mWiFiNetworkAgent.getNetwork(),
+ mEthernetNetworkAgent.getNetwork());
+
+ // Disconnecting unmetered Wi-Fi shouldn't change the OEM network with fallback on cellular.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mEthernetNetworkAgent.getNetwork());
+
+ // Disconnecting OEM_PRIVATE will keep the fallback on cellular.
+ // OEM_PRIVATE_ONLY not supporting a fallback now uses to the disconnected network.
+ stopOemManagedNetwork();
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mService.mNoServiceNetwork.network());
+
+ // Disconnecting cellular will put the fallback on null and pref on disconnected.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ null,
+ mService.mNoServiceNetwork.network());
+
+ // default NCs will be unregistered in tearDown
+ }
+
+ @Test
+ public void testGetAllNetworkStateSnapshot() throws Exception {
+ verifyNoNetwork();
+
+ // Setup test cellular network with specified LinkProperties and NetworkCapabilities,
+ // verify the content of the snapshot matches.
+ final LinkProperties cellLp = new LinkProperties();
+ final LinkAddress myIpv4Addr = new LinkAddress(InetAddress.getByName("192.0.2.129"), 25);
+ final LinkAddress myIpv6Addr = new LinkAddress(InetAddress.getByName("2001:db8::1"), 64);
+ cellLp.setInterfaceName("test01");
+ cellLp.addLinkAddress(myIpv4Addr);
+ cellLp.addLinkAddress(myIpv6Addr);
+ cellLp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234")));
+ cellLp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254")));
+ cellLp.addRoute(new RouteInfo(myIpv4Addr, null));
+ cellLp.addRoute(new RouteInfo(myIpv6Addr, null));
+ final NetworkCapabilities cellNcTemplate = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR).addCapability(NET_CAPABILITY_MMS).build();
+
+ final TestNetworkCallback cellCb = new TestNetworkCallback();
+ mCm.requestNetwork(new NetworkRequest.Builder().addCapability(NET_CAPABILITY_MMS).build(),
+ cellCb);
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp, cellNcTemplate);
+ mCellNetworkAgent.connect(true);
+ cellCb.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+ List<NetworkStateSnapshot> snapshots = mCm.getAllNetworkStateSnapshot();
+ assertLength(1, snapshots);
+
+ // Compose the expected cellular snapshot for verification.
+ final NetworkCapabilities cellNc =
+ mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork());
+ final NetworkStateSnapshot cellSnapshot = new NetworkStateSnapshot(
+ mCellNetworkAgent.getNetwork(), cellNc, cellLp,
+ null, ConnectivityManager.TYPE_MOBILE);
+ assertEquals(cellSnapshot, snapshots.get(0));
+
+ // Connect wifi and verify the snapshots.
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+ waitForIdle();
+ // Compose the expected wifi snapshot for verification.
+ final NetworkCapabilities wifiNc =
+ mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork());
+ final NetworkStateSnapshot wifiSnapshot = new NetworkStateSnapshot(
+ mWiFiNetworkAgent.getNetwork(), wifiNc, new LinkProperties(), null,
+ ConnectivityManager.TYPE_WIFI);
+
+ snapshots = mCm.getAllNetworkStateSnapshot();
+ assertLength(2, snapshots);
+ assertContainsAll(snapshots, cellSnapshot, wifiSnapshot);
+
+ // Set cellular as suspended, verify the snapshots will not contain suspended networks.
+ // TODO: Consider include SUSPENDED networks, which should be considered as
+ // temporary shortage of connectivity of a connected network.
+ mCellNetworkAgent.suspend();
+ waitForIdle();
+ snapshots = mCm.getAllNetworkStateSnapshot();
+ assertLength(1, snapshots);
+ assertEquals(wifiSnapshot, snapshots.get(0));
+
+ // Disconnect wifi, verify the snapshots contain nothing.
+ mWiFiNetworkAgent.disconnect();
+ waitForIdle();
+ snapshots = mCm.getAllNetworkStateSnapshot();
+ assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertLength(0, snapshots);
+
+ mCellNetworkAgent.resume();
+ waitForIdle();
+ snapshots = mCm.getAllNetworkStateSnapshot();
+ assertLength(1, snapshots);
+ assertEquals(cellSnapshot, snapshots.get(0));
+
+ mCellNetworkAgent.disconnect();
+ waitForIdle();
+ verifyNoNetwork();
+ mCm.unregisterNetworkCallback(cellCb);
}
}
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 7489a0f..b8f7fbc 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.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 @@
@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 @@
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 @@
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 @@
// 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 @@
// 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 @@
}));
// 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 @@
// 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 @@
// 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 @@
.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 @@
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 @@
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 @@
}
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 @@
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 @@
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 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 @@
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 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 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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
.thenReturn(asUserContext);
final TestLooper testLooper = new TestLooper();
final Vpn vpn = new Vpn(testLooper.getLooper(), mContext, new TestDeps(), mNetService,
- mNetd, userId, mKeyStore, mSystemServices, mIkev2SessionCreator);
+ mNetd, userId, mVpnProfileStore, mSystemServices, mIkev2SessionCreator);
verify(mConnectivityManager, times(1)).registerNetworkProvider(argThat(
provider -> provider.getName().contains("VpnNetworkProvider")
));
diff --git a/tests/vcn/assets/self-signed-ca.pem b/tests/vcn/assets/self-signed-ca.pem
new file mode 100644
index 0000000..5135ea7
--- /dev/null
+++ b/tests/vcn/assets/self-signed-ca.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDPjCCAiagAwIBAgIICrKLpR7LxlowDQYJKoZIhvcNAQELBQAwPTELMAkGA1UE
+BhMCVVMxEDAOBgNVBAoTB0FuZHJvaWQxHDAaBgNVBAMTE2NhLnRlc3QuYW5kcm9p
+ZC5uZXQwHhcNMTkwNzE2MTcxNTUyWhcNMjkwNzEzMTcxNTUyWjA9MQswCQYDVQQG
+EwJVUzEQMA4GA1UEChMHQW5kcm9pZDEcMBoGA1UEAxMTY2EudGVzdC5hbmRyb2lk
+Lm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANsvTwad2Nie0VOy
+Xb1VtHL0R760Jm4vr14JWMcX4oiE6jUdTNdXQ0CGb65wvulP2aEeukFH0D/cvBMR
+Bv9+haEwo9/grIXg9ALNKp+GfuZYw/dfnUMHFn3g2+SUgP6BoMZc4lkHktjkDKxp
+99Q6h4NP/ip1labkhBeB9+Z6l78LTixKRKspNITWASJed9bjzshYxKHi6dJy3maQ
+1LwYKmK7PEGRpoDoT8yZhFbxsVDUojGnJKH1RLXVOn/psG6dI/+IsbTipAttj5zc
+g2VAD56PZG2Jd+vsup+g4Dy72hyy242x5c/H2LKZn4X0B0B+IXyii/ZVc+DJldQ5
+JqplOL8CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
+HQYDVR0OBBYEFGYUzuvZUaVJl8mcxejuFiUNGcTfMA0GCSqGSIb3DQEBCwUAA4IB
+AQDQYeqjvHsK2ZqSqxakDp0nu36Plbj48Wvx1ru7GW2faz7i0w/Zkxh06zniILCb
+QJRjDebSTHc5SSbCFrRTvqagaLDhbH42/hQncWqIoJqW+pmznJET4JiBO0sqzm05
+yQWsLI/h9Ir28Y2g5N+XPBU0VVVejQqH4iI0iwQx7y7ABssQ0Xa/K73VPbeGaKd6
+Prt4wjJvTlIL2yE2+0MggJ3F2rNptL5SDpg3g+4/YQ6wVRBFil95kUqplEsCtU4P
+t+8RghiEmsRx/8CywKfZ5Hex87ODhsSDmDApcefbd5gxoWVkqxZUkPcKwYv1ucm8
+u4r44fj4/9W0Zeooav5Yoh1q
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/tests/vcn/java/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
index 66590c9..7515971 100644
--- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
@@ -203,9 +203,6 @@
IVcnStatusCallback cbBinder =
new VcnStatusCallbackBinder(INLINE_EXECUTOR, mMockStatusCallback);
- cbBinder.onEnteredSafeMode();
- verify(mMockStatusCallback).onEnteredSafeMode();
-
cbBinder.onVcnStatusChanged(VCN_STATUS_CODE_ACTIVE);
verify(mMockStatusCallback).onVcnStatusChanged(VCN_STATUS_CODE_ACTIVE);
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtilsTest.java
new file mode 100644
index 0000000..bc8e9d3
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtilsTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.persistablebundleutils;
+
+import static android.telephony.TelephonyManager.APPTYPE_USIM;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.eap.EapSessionConfig;
+import android.os.PersistableBundle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class EapSessionConfigUtilsTest {
+ private static final byte[] EAP_ID = "test@android.net".getBytes(StandardCharsets.US_ASCII);
+ private static final String USERNAME = "username";
+ private static final String PASSWORD = "password";
+ private static final int SUB_ID = 1;
+ private static final String NETWORK_NAME = "android.net";
+ private static final boolean ALLOW_MISMATCHED_NETWORK_NAMES = true;
+
+ private EapSessionConfig.Builder createBuilderWithId() {
+ return new EapSessionConfig.Builder().setEapIdentity(EAP_ID);
+ }
+
+ private static void verifyPersistableBundleEncodeDecodeIsLossless(EapSessionConfig config) {
+ final PersistableBundle bundle = EapSessionConfigUtils.toPersistableBundle(config);
+ final EapSessionConfig resultConfig = EapSessionConfigUtils.fromPersistableBundle(bundle);
+
+ assertEquals(config, resultConfig);
+ }
+
+ @Test
+ public void testSetEapMsChapV2EncodeDecodeIsLossless() throws Exception {
+ final EapSessionConfig config =
+ createBuilderWithId().setEapMsChapV2Config(USERNAME, PASSWORD).build();
+
+ verifyPersistableBundleEncodeDecodeIsLossless(config);
+ }
+
+ @Test
+ public void testSetEapSimEncodeDecodeIsLossless() throws Exception {
+ final EapSessionConfig config =
+ createBuilderWithId().setEapSimConfig(SUB_ID, APPTYPE_USIM).build();
+
+ verifyPersistableBundleEncodeDecodeIsLossless(config);
+ }
+
+ @Test
+ public void testSetEapAkaEncodeDecodeIsLossless() throws Exception {
+ final EapSessionConfig config =
+ createBuilderWithId().setEapAkaConfig(SUB_ID, APPTYPE_USIM).build();
+
+ verifyPersistableBundleEncodeDecodeIsLossless(config);
+ }
+
+ @Test
+ public void testSetEapAkaPrimeEncodeDecodeIsLossless() throws Exception {
+ final EapSessionConfig config =
+ createBuilderWithId()
+ .setEapAkaPrimeConfig(
+ SUB_ID, APPTYPE_USIM, NETWORK_NAME, ALLOW_MISMATCHED_NETWORK_NAMES)
+ .build();
+
+ verifyPersistableBundleEncodeDecodeIsLossless(config);
+ }
+
+ @Test
+ public void testSetEapTtlsEncodeDecodeIsLossless() throws Exception {
+ final InputStream inputStream =
+ InstrumentationRegistry.getContext()
+ .getResources()
+ .getAssets()
+ .open("self-signed-ca.pem");
+ final CertificateFactory factory = CertificateFactory.getInstance("X.509");
+ final X509Certificate trustedCa =
+ (X509Certificate) factory.generateCertificate(inputStream);
+
+ final EapSessionConfig innerConfig =
+ new EapSessionConfig.Builder().setEapMsChapV2Config(USERNAME, PASSWORD).build();
+
+ final EapSessionConfig config =
+ new EapSessionConfig.Builder().setEapTtlsConfig(trustedCa, innerConfig).build();
+
+ verifyPersistableBundleEncodeDecodeIsLossless(config);
+ }
+}
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtilsTest.java
new file mode 100644
index 0000000..4f3930f
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtilsTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.persistablebundleutils;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.ipsec.ike.IkeDerAsn1DnIdentification;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeIdentification;
+import android.net.ipsec.ike.IkeIpv4AddrIdentification;
+import android.net.ipsec.ike.IkeIpv6AddrIdentification;
+import android.net.ipsec.ike.IkeKeyIdIdentification;
+import android.net.ipsec.ike.IkeRfc822AddrIdentification;
+import android.os.PersistableBundle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+
+import javax.security.auth.x500.X500Principal;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IkeIdentificationUtilsTest {
+ private static void verifyPersistableBundleEncodeDecodeIsLossless(IkeIdentification id) {
+ final PersistableBundle bundle = IkeIdentificationUtils.toPersistableBundle(id);
+ final IkeIdentification result = IkeIdentificationUtils.fromPersistableBundle(bundle);
+
+ assertEquals(result, id);
+ }
+
+ @Test
+ public void testPersistableBundleEncodeDecodeIpv4AddressId() throws Exception {
+ final Inet4Address ipv4Address = (Inet4Address) InetAddress.getByName("192.0.2.100");
+ verifyPersistableBundleEncodeDecodeIsLossless(new IkeIpv4AddrIdentification(ipv4Address));
+ }
+
+ @Test
+ public void testPersistableBundleEncodeDecodeIpv6AddressId() throws Exception {
+ final Inet6Address ipv6Address = (Inet6Address) InetAddress.getByName("2001:db8:2::100");
+ verifyPersistableBundleEncodeDecodeIsLossless(new IkeIpv6AddrIdentification(ipv6Address));
+ }
+
+ @Test
+ public void testPersistableBundleEncodeDecodeRfc822AddrId() throws Exception {
+ verifyPersistableBundleEncodeDecodeIsLossless(new IkeFqdnIdentification("ike.android.net"));
+ }
+
+ @Test
+ public void testPersistableBundleEncodeDecodeFqdnId() throws Exception {
+ verifyPersistableBundleEncodeDecodeIsLossless(
+ new IkeRfc822AddrIdentification("androidike@example.com"));
+ }
+
+ @Test
+ public void testPersistableBundleEncodeDecodeKeyId() throws Exception {
+ verifyPersistableBundleEncodeDecodeIsLossless(
+ new IkeKeyIdIdentification("androidIkeKeyId".getBytes()));
+ }
+
+ @Test
+ public void testPersistableBundleEncodeDecodeDerAsn1DnId() throws Exception {
+ verifyPersistableBundleEncodeDecodeIsLossless(
+ new IkeDerAsn1DnIdentification(
+ new X500Principal("CN=small.server.test.android.net, O=Android, C=US")));
+ }
+}
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.java
new file mode 100644
index 0000000..8ae8692
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.persistablebundleutils;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.net.ipsec.ike.SaProposal;
+import android.os.PersistableBundle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SaProposalUtilsTest {
+ @Test
+ public void testPersistableBundleEncodeDecodeIsLosslessIkeProposal() throws Exception {
+ final IkeSaProposal proposal =
+ new IkeSaProposal.Builder()
+ .addEncryptionAlgorithm(
+ SaProposal.ENCRYPTION_ALGORITHM_3DES, SaProposal.KEY_LEN_UNUSED)
+ .addEncryptionAlgorithm(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128)
+ .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96)
+ .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128)
+ .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC)
+ .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_SHA2_256)
+ .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP)
+ .addDhGroup(SaProposal.DH_GROUP_3072_BIT_MODP)
+ .build();
+
+ final PersistableBundle bundle = IkeSaProposalUtils.toPersistableBundle(proposal);
+ final SaProposal resultProposal = IkeSaProposalUtils.fromPersistableBundle(bundle);
+
+ assertEquals(proposal, resultProposal);
+ }
+
+ /** Package private so that TunnelModeChildSessionParamsUtilsTest can use it */
+ static ChildSaProposal buildTestChildSaProposal() {
+ return new ChildSaProposal.Builder()
+ .addEncryptionAlgorithm(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.KEY_LEN_AES_128)
+ .addEncryptionAlgorithm(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.KEY_LEN_AES_192)
+ .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP)
+ .addDhGroup(SaProposal.DH_GROUP_4096_BIT_MODP)
+ .build();
+ }
+
+ @Test
+ public void testPersistableBundleEncodeDecodeIsLosslessChildProposal() throws Exception {
+ final ChildSaProposal proposal = buildTestChildSaProposal();
+
+ final PersistableBundle bundle = ChildSaProposalUtils.toPersistableBundle(proposal);
+ final SaProposal resultProposal = ChildSaProposalUtils.fromPersistableBundle(bundle);
+
+ assertEquals(proposal, resultProposal);
+ }
+}
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtilsTest.java
new file mode 100644
index 0000000..b3cd0ab
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtilsTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.persistablebundleutils;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.InetAddresses;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.IkeTrafficSelector;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+import android.os.PersistableBundle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TunnelModeChildSessionParamsUtilsTest {
+ private TunnelModeChildSessionParams.Builder createBuilderMinimum() {
+ final ChildSaProposal saProposal = SaProposalUtilsTest.buildTestChildSaProposal();
+ return new TunnelModeChildSessionParams.Builder().addSaProposal(saProposal);
+ }
+
+ private static void verifyPersistableBundleEncodeDecodeIsLossless(
+ TunnelModeChildSessionParams params) {
+ final PersistableBundle bundle =
+ TunnelModeChildSessionParamsUtils.toPersistableBundle(params);
+ final TunnelModeChildSessionParams result =
+ TunnelModeChildSessionParamsUtils.fromPersistableBundle(bundle);
+
+ assertEquals(params, result);
+ }
+
+ @Test
+ public void testMinimumParamsEncodeDecodeIsLossless() throws Exception {
+ final TunnelModeChildSessionParams sessionParams = createBuilderMinimum().build();
+ verifyPersistableBundleEncodeDecodeIsLossless(sessionParams);
+ }
+
+ @Test
+ public void testSetTsEncodeDecodeIsLossless() throws Exception {
+ final IkeTrafficSelector tsInbound =
+ new IkeTrafficSelector(
+ 16,
+ 65520,
+ InetAddresses.parseNumericAddress("192.0.2.100"),
+ InetAddresses.parseNumericAddress("192.0.2.101"));
+ final IkeTrafficSelector tsOutbound =
+ new IkeTrafficSelector(
+ 32,
+ 256,
+ InetAddresses.parseNumericAddress("192.0.2.200"),
+ InetAddresses.parseNumericAddress("192.0.2.255"));
+
+ final TunnelModeChildSessionParams sessionParams =
+ createBuilderMinimum()
+ .addInboundTrafficSelectors(tsInbound)
+ .addOutboundTrafficSelectors(tsOutbound)
+ .build();
+ verifyPersistableBundleEncodeDecodeIsLossless(sessionParams);
+ }
+
+ @Test
+ public void testSetLifetimesEncodeDecodeIsLossless() throws Exception {
+ final int hardLifetime = (int) TimeUnit.HOURS.toSeconds(3L);
+ final int softLifetime = (int) TimeUnit.HOURS.toSeconds(1L);
+
+ final TunnelModeChildSessionParams sessionParams =
+ createBuilderMinimum().setLifetimeSeconds(hardLifetime, softLifetime).build();
+ verifyPersistableBundleEncodeDecodeIsLossless(sessionParams);
+ }
+
+ @Test
+ public void testSetConfigRequestsEncodeDecodeIsLossless() throws Exception {
+ final int ipv6PrefixLen = 64;
+ final Inet4Address ipv4Address =
+ (Inet4Address) InetAddresses.parseNumericAddress("192.0.2.100");
+ final Inet6Address ipv6Address =
+ (Inet6Address) InetAddresses.parseNumericAddress("2001:db8::1");
+
+ final TunnelModeChildSessionParams sessionParams =
+ createBuilderMinimum()
+ .addInternalAddressRequest(AF_INET)
+ .addInternalAddressRequest(AF_INET6)
+ .addInternalAddressRequest(ipv4Address)
+ .addInternalAddressRequest(ipv6Address, ipv6PrefixLen)
+ .addInternalDnsServerRequest(AF_INET)
+ .addInternalDnsServerRequest(AF_INET6)
+ .addInternalDhcpServerRequest(AF_INET)
+ .build();
+ verifyPersistableBundleEncodeDecodeIsLossless(sessionParams);
+ }
+}
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 9b500a7..73a6b88 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -100,6 +100,8 @@
public class VcnManagementServiceTest {
private static final String TEST_PACKAGE_NAME =
VcnManagementServiceTest.class.getPackage().getName();
+ private static final String TEST_CB_PACKAGE_NAME =
+ VcnManagementServiceTest.class.getPackage().getName() + ".callback";
private static final ParcelUuid TEST_UUID_1 = new ParcelUuid(new UUID(0, 0));
private static final ParcelUuid TEST_UUID_2 = new ParcelUuid(new UUID(1, 1));
private static final VcnConfig TEST_VCN_CONFIG;
@@ -288,6 +290,14 @@
private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot(
Set<ParcelUuid> activeSubscriptionGroups, Map<Integer, ParcelUuid> subIdToGroupMap) {
+ return triggerSubscriptionTrackerCbAndGetSnapshot(
+ activeSubscriptionGroups, subIdToGroupMap, true /* hasCarrierPrivileges */);
+ }
+
+ private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot(
+ Set<ParcelUuid> activeSubscriptionGroups,
+ Map<Integer, ParcelUuid> subIdToGroupMap,
+ boolean hasCarrierPrivileges) {
final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class);
doReturn(activeSubscriptionGroups).when(snapshot).getActiveSubscriptionGroups();
@@ -295,7 +305,7 @@
(activeSubscriptionGroups == null || activeSubscriptionGroups.isEmpty())
? Collections.emptySet()
: Collections.singleton(TEST_PACKAGE_NAME);
- doReturn(true)
+ doReturn(hasCarrierPrivileges)
.when(snapshot)
.packageHasPermissionsForSubscriptionGroup(
argThat(val -> activeSubscriptionGroups.contains(val)),
@@ -549,13 +559,6 @@
mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
}
- private void setUpVcnSubscription(int subId, ParcelUuid subGroup) {
- mVcnMgmtSvc.setVcnConfig(subGroup, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
-
- triggerSubscriptionTrackerCbAndGetSnapshot(
- Collections.singleton(subGroup), Collections.singletonMap(subId, subGroup));
- }
-
private void verifyMergedNetworkCapabilities(
NetworkCapabilities mergedCapabilities,
@Transport int transportType,
@@ -573,9 +576,23 @@
}
private void setupSubscriptionAndStartVcn(int subId, ParcelUuid subGrp, boolean isVcnActive) {
- setUpVcnSubscription(subId, subGrp);
+ setupSubscriptionAndStartVcn(subId, subGrp, isVcnActive, true /* hasCarrierPrivileges */);
+ }
+
+ private void setupSubscriptionAndStartVcn(
+ int subId, ParcelUuid subGrp, boolean isVcnActive, boolean hasCarrierPrivileges) {
+ mVcnMgmtSvc.systemReady();
+ triggerSubscriptionTrackerCbAndGetSnapshot(
+ Collections.singleton(subGrp),
+ Collections.singletonMap(subId, subGrp),
+ hasCarrierPrivileges);
+
final Vcn vcn = startAndGetVcnInstance(subGrp);
doReturn(isVcnActive).when(vcn).isActive();
+
+ doReturn(true)
+ .when(mLocationPermissionChecker)
+ .checkLocationPermission(eq(TEST_PACKAGE_NAME), any(), eq(TEST_UID), any());
}
private VcnUnderlyingNetworkPolicy startVcnAndGetPolicyForTransport(
@@ -721,7 +738,7 @@
verify(mMockPolicyListener).onPolicyChanged();
}
- private void verifyVcnCallback(
+ private void triggerVcnSafeMode(
@NonNull ParcelUuid subGroup, @NonNull TelephonySubscriptionSnapshot snapshot)
throws Exception {
verify(mMockDeps)
@@ -732,20 +749,20 @@
eq(snapshot),
mVcnCallbackCaptor.capture());
- mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
-
VcnCallback vcnCallback = mVcnCallbackCaptor.getValue();
vcnCallback.onEnteredSafeMode();
-
- verify(mMockPolicyListener).onPolicyChanged();
}
@Test
- public void testVcnCallbackOnEnteredSafeMode() throws Exception {
+ public void testVcnEnteringSafeModeNotifiesPolicyListeners() throws Exception {
TelephonySubscriptionSnapshot snapshot =
triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
- verifyVcnCallback(TEST_UUID_1, snapshot);
+ mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
+
+ triggerVcnSafeMode(TEST_UUID_1, snapshot);
+
+ verify(mMockPolicyListener).onPolicyChanged();
}
private void triggerVcnStatusCallbackOnEnteredSafeMode(
@@ -758,6 +775,9 @@
TelephonySubscriptionSnapshot snapshot =
triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(subGroup));
+ setupSubscriptionAndStartVcn(
+ TEST_SUBSCRIPTION_ID, subGroup, true /* isActive */, hasPermissionsforSubGroup);
+
doReturn(hasPermissionsforSubGroup)
.when(snapshot)
.packageHasPermissionsForSubscriptionGroup(eq(subGroup), eq(pkgName));
@@ -768,10 +788,7 @@
mVcnMgmtSvc.registerVcnStatusCallback(subGroup, mMockStatusCallback, pkgName);
- // Trigger systemReady() to set up LocationPermissionChecker
- mVcnMgmtSvc.systemReady();
-
- verifyVcnCallback(subGroup, snapshot);
+ triggerVcnSafeMode(subGroup, snapshot);
}
@Test
@@ -825,6 +842,83 @@
assertEquals(TEST_PACKAGE_NAME, cbInfo.mPkgName);
assertEquals(TEST_UID, cbInfo.mUid);
verify(mMockIBinder).linkToDeath(eq(cbInfo), anyInt());
+
+ verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED);
+ }
+
+ @Test
+ public void testRegisterVcnStatusCallback_MissingPermission() throws Exception {
+ setupSubscriptionAndStartVcn(
+ TEST_SUBSCRIPTION_ID,
+ TEST_UUID_1,
+ true /* isActive */,
+ false /* hasCarrierPrivileges */);
+
+ mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+
+ verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED);
+ }
+
+ @Test
+ public void testRegisterVcnStatusCallback_VcnInactive() throws Exception {
+ setupSubscriptionAndStartVcn(
+ TEST_SUBSCRIPTION_ID,
+ TEST_UUID_1,
+ true /* isActive */,
+ true /* hasCarrierPrivileges */);
+
+ // VCN is currently active. Lose carrier privileges for TEST_PACKAGE and hit teardown
+ // timeout so the VCN goes inactive.
+ final TelephonySubscriptionSnapshot snapshot =
+ triggerSubscriptionTrackerCbAndGetSnapshot(
+ Collections.singleton(TEST_UUID_1),
+ Collections.singletonMap(TEST_SUBSCRIPTION_ID, TEST_UUID_1),
+ false /* hasCarrierPrivileges */);
+ mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+ mTestLooper.dispatchAll();
+
+ // Giving TEST_PACKAGE privileges again will restart the VCN (which will indicate ACTIVE
+ // when the status callback is registered). Instead, setup permissions for TEST_CB_PACKAGE
+ // so that it's permissioned to receive INACTIVE (instead of NOT_CONFIGURED) without
+ // reactivating the VCN.
+ doReturn(true)
+ .when(snapshot)
+ .packageHasPermissionsForSubscriptionGroup(
+ eq(TEST_UUID_1), eq(TEST_CB_PACKAGE_NAME));
+ doReturn(true)
+ .when(mLocationPermissionChecker)
+ .checkLocationPermission(eq(TEST_CB_PACKAGE_NAME), any(), eq(TEST_UID), any());
+
+ mVcnMgmtSvc.registerVcnStatusCallback(
+ TEST_UUID_1, mMockStatusCallback, TEST_CB_PACKAGE_NAME);
+
+ verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_INACTIVE);
+ }
+
+ @Test
+ public void testRegisterVcnStatusCallback_VcnActive() throws Exception {
+ setupSubscriptionAndStartVcn(
+ TEST_SUBSCRIPTION_ID,
+ TEST_UUID_1,
+ true /* isActive */,
+ true /* hasCarrierPrivileges */);
+
+ mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+
+ verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_ACTIVE);
+ }
+
+ @Test
+ public void testRegisterVcnStatusCallback_VcnSafeMode() throws Exception {
+ setupSubscriptionAndStartVcn(
+ TEST_SUBSCRIPTION_ID,
+ TEST_UUID_1,
+ false /* isActive */,
+ true /* hasCarrierPrivileges */);
+
+ mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+
+ verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_SAFE_MODE);
}
@Test(expected = IllegalStateException.class)
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 69c21b9..69b2fb1 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -36,6 +36,7 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -143,11 +144,18 @@
.onIpSecTransformsMigrated(makeDummyIpSecTransform(), makeDummyIpSecTransform());
mTestLooper.dispatchAll();
+ verify(mIpSecSvc, times(2))
+ .setNetworkForTunnelInterface(
+ eq(TEST_IPSEC_TUNNEL_RESOURCE_ID),
+ eq(TEST_UNDERLYING_NETWORK_RECORD_1.network),
+ any());
+
for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT}) {
verify(mIpSecSvc)
.applyTunnelModeTransform(
eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any());
}
+
assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
}
@@ -290,4 +298,22 @@
verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback(
new TemporaryFailureException("vcn test"), VCN_ERROR_CODE_INTERNAL_ERROR);
}
+
+ @Test
+ public void testTeardown() throws Exception {
+ mGatewayConnection.teardownAsynchronously();
+ mTestLooper.dispatchAll();
+
+ assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+ assertTrue(mGatewayConnection.isQuitting());
+ }
+
+ @Test
+ public void testNonTeardownDisconnectRequest() throws Exception {
+ mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false);
+ mTestLooper.dispatchAll();
+
+ assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+ assertFalse(mGatewayConnection.isQuitting());
+ }
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
index 17ae19e..d07d2cf 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
@@ -19,6 +19,8 @@
import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -111,4 +113,22 @@
public void testSafeModeTimeoutNotifiesCallback() {
verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState);
}
+
+ @Test
+ public void testTeardown() throws Exception {
+ mGatewayConnection.teardownAsynchronously();
+ mTestLooper.dispatchAll();
+
+ assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+ assertTrue(mGatewayConnection.isQuitting());
+ }
+
+ @Test
+ public void testNonTeardownDisconnectRequest() throws Exception {
+ mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false);
+ mTestLooper.dispatchAll();
+
+ assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+ assertFalse(mGatewayConnection.isQuitting());
+ }
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
index 9ea641f..5f27fab 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
@@ -21,9 +21,12 @@
import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.net.IpSecManager;
@@ -54,7 +57,7 @@
}
@Test
- public void testEnterWhileNotRunningTriggersQuit() throws Exception {
+ public void testEnterWhileQuittingTriggersQuit() throws Exception {
final VcnGatewayConnection vgc =
new VcnGatewayConnection(
mVcnContext,
@@ -64,7 +67,7 @@
mGatewayStatusCallback,
mDeps);
- vgc.setIsRunning(false);
+ vgc.setIsQuitting(true);
vgc.transitionTo(vgc.mDisconnectedState);
mTestLooper.dispatchAll();
@@ -101,5 +104,18 @@
assertNull(mGatewayConnection.getCurrentState());
verify(mIpSecSvc).deleteTunnelInterface(eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), any());
verifySafeModeTimeoutAlarmAndGetCallback(true /* expectCanceled */);
+ assertTrue(mGatewayConnection.isQuitting());
+ verify(mGatewayStatusCallback).onQuit();
+ }
+
+ @Test
+ public void testNonTeardownDisconnectRequest() throws Exception {
+ mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false);
+ mTestLooper.dispatchAll();
+
+ assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
+ assertFalse(mGatewayConnection.isQuitting());
+ verify(mGatewayStatusCallback, never()).onQuit();
+ // No safe mode timer changes expected.
}
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
index 7385204..661e03a 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
@@ -18,6 +18,8 @@
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -79,10 +81,20 @@
// Should do nothing; already tearing down.
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+ assertTrue(mGatewayConnection.isQuitting());
}
@Test
public void testSafeModeTimeoutNotifiesCallback() {
verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState);
}
+
+ @Test
+ public void testNonTeardownDisconnectRequest() throws Exception {
+ mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false);
+ mTestLooper.dispatchAll();
+
+ assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+ assertFalse(mGatewayConnection.isQuitting());
+ }
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
index 5b0850b..85a0277 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
@@ -17,6 +17,9 @@
package com.android.server.vcn;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -96,4 +99,22 @@
public void testSafeModeTimeoutNotifiesCallback() {
verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState);
}
+
+ @Test
+ public void testTeardownDisconnectRequest() throws Exception {
+ mGatewayConnection.teardownAsynchronously();
+ mTestLooper.dispatchAll();
+
+ assertNull(mGatewayConnection.getCurrentState());
+ assertTrue(mGatewayConnection.isQuitting());
+ }
+
+ @Test
+ public void testNonTeardownDisconnectRequest() throws Exception {
+ mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false);
+ mTestLooper.dispatchAll();
+
+ assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
+ assertFalse(mGatewayConnection.isQuitting());
+ }
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java
index 9d33682..3dd710a 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java
@@ -16,6 +16,10 @@
package com.android.server.vcn;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.Matchers.any;
@@ -33,6 +37,7 @@
import android.net.vcn.VcnGatewayConnectionConfigTest;
import android.os.ParcelUuid;
import android.os.test.TestLooper;
+import android.util.ArraySet;
import com.android.server.VcnManagementService.VcnCallback;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
@@ -51,6 +56,11 @@
private static final ParcelUuid TEST_SUB_GROUP = new ParcelUuid(new UUID(0, 0));
private static final int NETWORK_SCORE = 0;
private static final int PROVIDER_ID = 5;
+ private static final int[][] TEST_CAPS =
+ new int[][] {
+ new int[] {NET_CAPABILITY_INTERNET, NET_CAPABILITY_MMS},
+ new int[] {NET_CAPABILITY_DUN}
+ };
private Context mContext;
private VcnContext mVcnContext;
@@ -91,13 +101,12 @@
mGatewayStatusCallbackCaptor = ArgumentCaptor.forClass(VcnGatewayStatusCallback.class);
final VcnConfig.Builder configBuilder = new VcnConfig.Builder(mContext);
- for (final int capability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) {
+ for (final int[] caps : TEST_CAPS) {
configBuilder.addGatewayConnectionConfig(
- VcnGatewayConnectionConfigTest.buildTestConfigWithExposedCaps(capability));
+ VcnGatewayConnectionConfigTest.buildTestConfigWithExposedCaps(caps));
}
- configBuilder.addGatewayConnectionConfig(VcnGatewayConnectionConfigTest.buildTestConfig());
- mConfig = configBuilder.build();
+ mConfig = configBuilder.build();
mVcn =
new Vcn(
mVcnContext,
@@ -130,8 +139,7 @@
@Test
public void testSubscriptionSnapshotUpdatesVcnGatewayConnections() {
final NetworkRequestListener requestListener = verifyAndGetRequestListener();
- startVcnGatewayWithCapabilities(
- requestListener, VcnGatewayConnectionConfigTest.EXPOSED_CAPS);
+ startVcnGatewayWithCapabilities(requestListener, TEST_CAPS[0]);
final Set<VcnGatewayConnection> gatewayConnections = mVcn.getVcnGatewayConnections();
assertFalse(gatewayConnections.isEmpty());
@@ -153,10 +161,19 @@
for (final int capability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) {
startVcnGatewayWithCapabilities(requestListener, capability);
}
+ }
- // Each Capability in EXPOSED_CAPS was split into a separate VcnGatewayConnection in #setUp.
- // Expect one VcnGatewayConnection per capability.
- final int numExpectedGateways = VcnGatewayConnectionConfigTest.EXPOSED_CAPS.length;
+ private void triggerVcnRequestListeners(NetworkRequestListener requestListener) {
+ for (final int[] caps : TEST_CAPS) {
+ startVcnGatewayWithCapabilities(requestListener, caps);
+ }
+ }
+
+ public Set<VcnGatewayConnection> startGatewaysAndGetGatewayConnections(
+ NetworkRequestListener requestListener) {
+ triggerVcnRequestListeners(requestListener);
+
+ final int numExpectedGateways = TEST_CAPS.length;
final Set<VcnGatewayConnection> gatewayConnections = mVcn.getVcnGatewayConnections();
assertEquals(numExpectedGateways, gatewayConnections.size());
@@ -168,7 +185,16 @@
any(),
mGatewayStatusCallbackCaptor.capture());
- // Doesn't matter which callback this gets - any Gateway entering safe mode should shut down
+ return gatewayConnections;
+ }
+
+ @Test
+ public void testGatewayEnteringSafemodeNotifiesVcn() {
+ final NetworkRequestListener requestListener = verifyAndGetRequestListener();
+ final Set<VcnGatewayConnection> gatewayConnections =
+ startGatewaysAndGetGatewayConnections(requestListener);
+
+ // Doesn't matter which callback this gets - any Gateway entering Safemode should shut down
// all Gateways
final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue();
statusCallback.onEnteredSafeMode();
@@ -181,4 +207,31 @@
verify(mVcnNetworkProvider).unregisterListener(requestListener);
verify(mVcnCallback).onEnteredSafeMode();
}
+
+ @Test
+ public void testGatewayQuit() {
+ final NetworkRequestListener requestListener = verifyAndGetRequestListener();
+ final Set<VcnGatewayConnection> gatewayConnections =
+ new ArraySet<>(startGatewaysAndGetGatewayConnections(requestListener));
+
+ final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue();
+ statusCallback.onQuit();
+ mTestLooper.dispatchAll();
+
+ // Verify that the VCN requests the networkRequests be resent
+ assertEquals(1, mVcn.getVcnGatewayConnections().size());
+ verify(mVcnNetworkProvider).resendAllRequests(requestListener);
+
+ // Verify that the VcnGatewayConnection is restarted
+ triggerVcnRequestListeners(requestListener);
+ mTestLooper.dispatchAll();
+ assertEquals(2, mVcn.getVcnGatewayConnections().size());
+ verify(mDeps, times(gatewayConnections.size() + 1))
+ .newVcnGatewayConnection(
+ eq(mVcnContext),
+ eq(TEST_SUB_GROUP),
+ eq(mSubscriptionSnapshot),
+ any(),
+ mGatewayStatusCallbackCaptor.capture());
+ }
}
diff --git a/tools/bit/make.cpp b/tools/bit/make.cpp
index df64a80..2a88732 100644
--- a/tools/bit/make.cpp
+++ b/tools/bit/make.cpp
@@ -89,8 +89,8 @@
}
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 @@
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 @@
}
std::ofstream stream(m_filename, std::ofstream::binary);
- writer.write(stream, json);
+ writer->write(json, &stream);
}
string
@@ -212,8 +213,8 @@
}
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;
}