Merge "Require apps to be in forground to be able to scan"
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/BroadcastWaiter.java b/apct-tests/perftests/multiuser/src/android/multiuser/BroadcastWaiter.java
index dcabca4..727c682 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/BroadcastWaiter.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/BroadcastWaiter.java
@@ -45,7 +45,6 @@
private final int mTimeoutInSecond;
private final Set<String> mActions;
- private final Set<String> mActionReceivedForUser = new HashSet<>();
private final List<BroadcastReceiver> mBroadcastReceivers = new ArrayList<>();
private final Map<String, Semaphore> mSemaphoresMap = new ConcurrentHashMap<>();
@@ -80,7 +79,6 @@
final String data = intent.getDataString();
Log.d(mTag, "Received " + action + " for user " + userId
+ (!TextUtils.isEmpty(data) ? " with " + data : ""));
- mActionReceivedForUser.add(action + userId);
getSemaphore(action, userId).release();
}
}
@@ -95,10 +93,6 @@
mBroadcastReceivers.forEach(mContext::unregisterReceiver);
}
- public boolean hasActionBeenReceivedForUser(String action, int userId) {
- return mActionReceivedForUser.contains(action + userId);
- }
-
private boolean waitActionForUser(String action, int userId) {
Log.d(mTag, "#waitActionForUser(action: " + action + ", userId: " + userId + ")");
@@ -129,7 +123,6 @@
public String runThenWaitForBroadcasts(int userId, FunctionalUtils.ThrowingRunnable runnable,
String... actions) {
for (String action : actions) {
- mActionReceivedForUser.remove(action + userId);
getSemaphore(action, userId).drainPermits();
}
runnable.run();
@@ -140,9 +133,4 @@
}
return null;
}
-
- public boolean waitActionForUserIfNotReceivedYet(String action, int userId) {
- return hasActionBeenReceivedForUser(action, userId)
- || waitActionForUser(action, userId);
- }
}
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index b2bd8d7..3f9b54c 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -167,7 +167,7 @@
/** Tests creating a new user. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
- public void createUser() {
+ public void createUser() throws RemoteException {
while (mRunner.keepRunning()) {
Log.i(TAG, "Starting timer");
final int userId = createUserNoFlags();
@@ -229,7 +229,7 @@
* Measures the time until unlock listener is triggered and user is unlocked.
*/
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
- public void startAndUnlockUser() {
+ public void startAndUnlockUser() throws RemoteException {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int userId = createUserNoFlags();
@@ -451,7 +451,7 @@
/** Tests creating a new profile. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
- public void managedProfileCreate() {
+ public void managedProfileCreate() throws RemoteException {
assumeTrue(mHasManagedUserFeature);
while (mRunner.keepRunning()) {
@@ -468,7 +468,7 @@
/** Tests starting (unlocking) an uninitialized profile. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
- public void managedProfileUnlock() {
+ public void managedProfileUnlock() throws RemoteException {
assumeTrue(mHasManagedUserFeature);
while (mRunner.keepRunning()) {
@@ -639,7 +639,7 @@
// TODO: This is just a POC. Do this properly and add more.
/** Tests starting (unlocking) a newly-created profile using the user-type-pkg-whitelist. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
- public void managedProfileUnlock_usingWhitelist() {
+ public void managedProfileUnlock_usingWhitelist() throws RemoteException {
assumeTrue(mHasManagedUserFeature);
final int origMode = getUserTypePackageWhitelistMode();
setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE
@@ -665,7 +665,7 @@
}
/** Tests starting (unlocking) a newly-created profile NOT using the user-type-pkg-whitelist. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
- public void managedProfileUnlock_notUsingWhitelist() {
+ public void managedProfileUnlock_notUsingWhitelist() throws RemoteException {
assumeTrue(mHasManagedUserFeature);
final int origMode = getUserTypePackageWhitelistMode();
setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE);
@@ -908,10 +908,8 @@
result != null && result.contains("Failed"));
}
- private void removeUser(int userId) {
- if (mBroadcastWaiter.hasActionBeenReceivedForUser(Intent.ACTION_USER_STARTED, userId)) {
- mBroadcastWaiter.waitActionForUserIfNotReceivedYet(Intent.ACTION_MEDIA_MOUNTED, userId);
- }
+ private void removeUser(int userId) throws RemoteException {
+ stopUserAfterWaitingForBroadcastIdle(userId, true);
try {
mUm.removeUser(userId);
final long startTime = System.currentTimeMillis();
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index 78214dc..711caf7 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -391,6 +391,12 @@
*/
public static final int REASON_MEDIA_NOTIFICATION_TRANSFER = 325;
+ /**
+ * Package installer.
+ * @hide
+ */
+ public static final int REASON_PACKAGE_INSTALLER = 326;
+
/** @hide The app requests out-out. */
public static final int REASON_OPT_OUT_REQUESTED = 1000;
@@ -472,6 +478,7 @@
REASON_DISALLOW_APPS_CONTROL,
REASON_ACTIVE_DEVICE_ADMIN,
REASON_MEDIA_NOTIFICATION_TRANSFER,
+ REASON_PACKAGE_INSTALLER,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ReasonCode {}
@@ -839,6 +846,8 @@
return "REASON_OPT_OUT_REQUESTED";
case REASON_MEDIA_NOTIFICATION_TRANSFER:
return "REASON_MEDIA_NOTIFICATION_TRANSFER";
+ case REASON_PACKAGE_INSTALLER:
+ return "REASON_PACKAGE_INSTALLER";
default:
return "(unknown:" + reasonCode + ")";
}
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 8defa16..4e52ed3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -4303,14 +4303,13 @@
int getStorageSeq() {
synchronized (mLock) {
- return mStorageController != null ? mStorageController.getTracker().getSeq() : -1;
+ return mStorageController.getTracker().getSeq();
}
}
boolean getStorageNotLow() {
synchronized (mLock) {
- return mStorageController != null
- ? mStorageController.getTracker().isStorageNotLow() : false;
+ return mStorageController.getTracker().isStorageNotLow();
}
}
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 88270494..53c56e7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -832,8 +832,8 @@
private void writeConstraintsToXml(TypedXmlSerializer out, JobStatus jobStatus)
throws IOException {
out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
+ final JobInfo job = jobStatus.getJob();
if (jobStatus.hasConnectivityConstraint()) {
- final JobInfo job = jobStatus.getJob();
final NetworkRequest network = jobStatus.getJob().getRequiredNetwork();
out.attribute(null, "net-capabilities-csv", intArrayToString(
network.getCapabilities()));
@@ -854,16 +854,16 @@
job.getMinimumNetworkChunkBytes());
}
}
- if (jobStatus.hasIdleConstraint()) {
+ if (job.isRequireDeviceIdle()) {
out.attribute(null, "idle", Boolean.toString(true));
}
- if (jobStatus.hasChargingConstraint()) {
+ if (job.isRequireCharging()) {
out.attribute(null, "charging", Boolean.toString(true));
}
- if (jobStatus.hasBatteryNotLowConstraint()) {
+ if (job.isRequireBatteryNotLow()) {
out.attribute(null, "battery-not-low", Boolean.toString(true));
}
- if (jobStatus.hasStorageNotLowConstraint()) {
+ if (job.isRequireStorageNotLow()) {
out.attribute(null, "storage-not-low", Boolean.toString(true));
}
out.endTag(null, XML_TAG_PARAMS_CONSTRAINTS);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
index 36a26f0..0a305a2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
@@ -273,6 +273,13 @@
return Integer.compare(job2.overrideState, job1.overrideState);
}
+ final boolean job1UI = job1.getJob().isUserInitiated();
+ final boolean job2UI = job2.getJob().isUserInitiated();
+ if (job1UI != job2UI) {
+ // Attempt to run user-initiated jobs ahead of all other jobs.
+ return job1UI ? -1 : 1;
+ }
+
final boolean job1EJ = job1.isRequestedExpeditedJob();
final boolean job2EJ = job2.isRequestedExpeditedJob();
if (job1EJ != job2EJ) {
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 b0b6a01..0b875cc 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
@@ -96,10 +96,16 @@
public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
public static final long NO_EARLIEST_RUNTIME = 0L;
- static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0
- static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; // 1 << 2
- static final int CONSTRAINT_BATTERY_NOT_LOW = JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
- static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW; // 1 << 3
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; // 1 << 2
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static final int CONSTRAINT_BATTERY_NOT_LOW =
+ JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static final int CONSTRAINT_STORAGE_NOT_LOW =
+ JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW; // 1 << 3
public static final int CONSTRAINT_TIMING_DELAY = 1 << 31;
public static final int CONSTRAINT_DEADLINE = 1 << 30;
public static final int CONSTRAINT_CONNECTIVITY = 1 << 28;
@@ -1820,7 +1826,8 @@
* separately from the job's explicitly requested constraints and MUST be satisfied before
* the job can run if the app doesn't have quota.
*/
- private void addDynamicConstraints(int constraints) {
+ @VisibleForTesting
+ public void addDynamicConstraints(int constraints) {
if ((constraints & CONSTRAINT_WITHIN_QUOTA) != 0) {
// Quota should never be used as a dynamic constraint.
Slog.wtf(TAG, "Tried to set quota as a dynamic constraint");
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index b84c8a4..2b59209 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -164,8 +164,9 @@
}
@GuardedBy("mLock")
- private boolean isAffordableLocked(long balance, long price, long ctp) {
- return balance >= price && mScribe.getRemainingConsumableCakesLocked() >= ctp;
+ private boolean isAffordableLocked(long balance, long price, long stockLimitHonoringCtp) {
+ return balance >= price
+ && mScribe.getRemainingConsumableCakesLocked() >= stockLimitHonoringCtp;
}
@GuardedBy("mLock")
@@ -303,7 +304,8 @@
note.recalculateCosts(economicPolicy, userId, pkgName);
final boolean isAffordable = isVip
|| isAffordableLocked(newBalance,
- note.getCachedModifiedPrice(), note.getCtp());
+ note.getCachedModifiedPrice(),
+ note.getStockLimitHonoringCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
mIrs.postAffordabilityChanged(userId, pkgName, note);
@@ -339,7 +341,7 @@
note.recalculateCosts(economicPolicy, userId, pkgName);
final boolean isAffordable = isVip
|| isAffordableLocked(newBalance,
- note.getCachedModifiedPrice(), note.getCtp());
+ note.getCachedModifiedPrice(), note.getStockLimitHonoringCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
mIrs.postAffordabilityChanged(userId, pkgName, note);
@@ -403,7 +405,8 @@
note.recalculateCosts(economicPolicy, userId, pkgName);
final boolean isAffordable = isVip
|| isAffordableLocked(newBalance,
- note.getCachedModifiedPrice(), note.getCtp());
+ note.getCachedModifiedPrice(),
+ note.getStockLimitHonoringCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
mIrs.postAffordabilityChanged(userId, pkgName, note);
@@ -541,7 +544,7 @@
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
final boolean isAffordable = isVip
|| isAffordableLocked(newBalance,
- note.getCachedModifiedPrice(), note.getCtp());
+ note.getCachedModifiedPrice(), note.getStockLimitHonoringCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
mIrs.postAffordabilityChanged(userId, pkgName, note);
@@ -882,7 +885,7 @@
mUpperThreshold = (mUpperThreshold == Long.MIN_VALUE)
? price : Math.min(mUpperThreshold, price);
}
- final long ctp = note.getCtp();
+ final long ctp = note.getStockLimitHonoringCtp();
if (ctp <= mRemainingConsumableCredits) {
mCtpThreshold = Math.max(mCtpThreshold, ctp);
}
@@ -1119,7 +1122,7 @@
note.recalculateCosts(economicPolicy, userId, pkgName);
note.setNewAffordability(isVip
|| isAffordableLocked(getBalanceLocked(userId, pkgName),
- note.getCachedModifiedPrice(), note.getCtp()));
+ note.getCachedModifiedPrice(), note.getStockLimitHonoringCtp()));
mIrs.postAffordabilityChanged(userId, pkgName, note);
// Update ongoing alarm
scheduleBalanceCheckLocked(userId, pkgName);
@@ -1146,7 +1149,7 @@
static final class ActionAffordabilityNote {
private final EconomyManagerInternal.ActionBill mActionBill;
private final EconomyManagerInternal.AffordabilityChangeListener mListener;
- private long mCtp;
+ private long mStockLimitHonoringCtp;
private long mModifiedPrice;
private boolean mIsAffordable;
@@ -1185,29 +1188,34 @@
return mModifiedPrice;
}
- private long getCtp() {
- return mCtp;
+ /** Returns the cumulative CTP of actions in this note that respect the stock limit. */
+ private long getStockLimitHonoringCtp() {
+ return mStockLimitHonoringCtp;
}
@VisibleForTesting
void recalculateCosts(@NonNull EconomicPolicy economicPolicy,
int userId, @NonNull String pkgName) {
long modifiedPrice = 0;
- long ctp = 0;
+ long stockLimitHonoringCtp = 0;
final List<EconomyManagerInternal.AnticipatedAction> anticipatedActions =
mActionBill.getAnticipatedActions();
for (int i = 0; i < anticipatedActions.size(); ++i) {
final EconomyManagerInternal.AnticipatedAction aa = anticipatedActions.get(i);
+ final EconomicPolicy.Action action = economicPolicy.getAction(aa.actionId);
final EconomicPolicy.Cost actionCost =
economicPolicy.getCostOfAction(aa.actionId, userId, pkgName);
modifiedPrice += actionCost.price * aa.numInstantaneousCalls
+ actionCost.price * (aa.ongoingDurationMs / 1000);
- ctp += actionCost.costToProduce * aa.numInstantaneousCalls
- + actionCost.costToProduce * (aa.ongoingDurationMs / 1000);
+ if (action.respectsStockLimit) {
+ stockLimitHonoringCtp +=
+ actionCost.costToProduce * aa.numInstantaneousCalls
+ + actionCost.costToProduce * (aa.ongoingDurationMs / 1000);
+ }
}
mModifiedPrice = modifiedPrice;
- mCtp = ctp;
+ mStockLimitHonoringCtp = stockLimitHonoringCtp;
}
boolean isCurrentlyAffordable() {
@@ -1267,7 +1275,8 @@
final ActionAffordabilityNote note =
actionAffordabilityNotes.valueAt(i);
final boolean isAffordable = isVip || isAffordableLocked(
- newBalance, note.getCachedModifiedPrice(), note.getCtp());
+ newBalance, note.getCachedModifiedPrice(),
+ note.getStockLimitHonoringCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
mIrs.postAffordabilityChanged(userId, pkgName, note);
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
index 46338fa..a6d064c 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
@@ -262,12 +262,17 @@
KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE,
DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE_CAKES);
+ // Apps must hold the SCHEDULE_EXACT_ALARM or USE_EXACT_ALARMS permission in order to use
+ // exact alarms. Since the user has the option of granting/revoking the permission, we can
+ // be a little lenient on the action cost checks and only stop the action if the app has
+ // run out of credits, and not when the system has run out of stock.
mActions.put(ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE,
new Action(ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE,
getConstantAsCake(mParser, properties,
KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP,
DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP_CAKES),
- exactAllowWhileIdleWakeupBasePrice));
+ exactAllowWhileIdleWakeupBasePrice,
+ /* respectsStockLimit */ false));
mActions.put(ACTION_ALARM_WAKEUP_EXACT,
new Action(ACTION_ALARM_WAKEUP_EXACT,
getConstantAsCake(mParser, properties,
@@ -275,7 +280,8 @@
DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_CTP_CAKES),
getConstantAsCake(mParser, properties,
KEY_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE,
- DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE_CAKES)));
+ DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE_CAKES),
+ /* respectsStockLimit */ false));
final long inexactAllowWhileIdleWakeupBasePrice =
getConstantAsCake(mParser, properties,
@@ -287,7 +293,8 @@
getConstantAsCake(mParser, properties,
KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP,
DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP_CAKES),
- inexactAllowWhileIdleWakeupBasePrice));
+ inexactAllowWhileIdleWakeupBasePrice,
+ /* respectsStockLimit */ false));
mActions.put(ACTION_ALARM_WAKEUP_INEXACT,
new Action(ACTION_ALARM_WAKEUP_INEXACT,
getConstantAsCake(mParser, properties,
@@ -295,7 +302,8 @@
DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP_CAKES),
getConstantAsCake(mParser, properties,
KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE,
- DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE_CAKES)));
+ DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE_CAKES),
+ /* respectsStockLimit */ false));
final long exactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake(mParser, properties,
KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_BASE_PRICE,
@@ -305,7 +313,8 @@
getConstantAsCake(mParser, properties,
KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP,
DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP_CAKES),
- exactAllowWhileIdleNonWakeupBasePrice));
+ exactAllowWhileIdleNonWakeupBasePrice,
+ /* respectsStockLimit */ false));
mActions.put(ACTION_ALARM_NONWAKEUP_EXACT,
new Action(ACTION_ALARM_NONWAKEUP_EXACT,
@@ -314,7 +323,8 @@
DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP_CAKES),
getConstantAsCake(mParser, properties,
KEY_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE,
- DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE_CAKES)));
+ DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE_CAKES),
+ /* respectsStockLimit */ false));
final long inexactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake(mParser, properties,
KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE,
@@ -342,7 +352,8 @@
DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_CTP_CAKES),
getConstantAsCake(mParser, properties,
KEY_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE,
- DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE_CAKES)));
+ DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE_CAKES),
+ /* respectsStockLimit */ false));
mRewards.put(REWARD_TOP_ACTIVITY, new Reward(REWARD_TOP_ACTIVITY,
getConstantAsCake(mParser, properties,
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
index b52f6f1..a4043dd 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
@@ -149,11 +149,22 @@
* the action unless a modifier lowers the cost to produce.
*/
public final long basePrice;
+ /**
+ * Whether the remaining stock limit affects an app's ability to perform this action.
+ * If {@code false}, then the action can be performed, even if the cost is higher
+ * than the remaining stock. This does not affect checking against an app's balance.
+ */
+ public final boolean respectsStockLimit;
Action(int id, long costToProduce, long basePrice) {
+ this(id, costToProduce, basePrice, true);
+ }
+
+ Action(int id, long costToProduce, long basePrice, boolean respectsStockLimit) {
this.id = id;
this.costToProduce = costToProduce;
this.basePrice = basePrice;
+ this.respectsStockLimit = respectsStockLimit;
}
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 1b0c5c2..5e6417c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -122,6 +122,10 @@
field public static final String LOADER_USAGE_STATS = "android.permission.LOADER_USAGE_STATS";
field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
field public static final String MANAGE_DEVICE_LOCK_STATE = "android.permission.MANAGE_DEVICE_LOCK_STATE";
+ field public static final String MANAGE_DEVICE_POLICY_ACROSS_USERS = "android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS";
+ field public static final String MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL = "android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL";
+ field public static final String MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL = "android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL";
+ field public static final String MANAGE_DEVICE_POLICY_TIME = "android.permission.MANAGE_DEVICE_POLICY_TIME";
field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
field public static final String MANAGE_EXTERNAL_STORAGE = "android.permission.MANAGE_EXTERNAL_STORAGE";
field public static final String MANAGE_MEDIA = "android.permission.MANAGE_MEDIA";
@@ -7499,9 +7503,9 @@
method @Nullable public String getAlwaysOnVpnPackage(@NonNull android.content.ComponentName);
method @NonNull @WorkerThread public android.os.Bundle getApplicationRestrictions(@Nullable android.content.ComponentName, String);
method @Deprecated @Nullable public String getApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName);
- method public boolean getAutoTimeEnabled(@NonNull android.content.ComponentName);
+ method @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public boolean getAutoTimeEnabled(@NonNull android.content.ComponentName);
method @Deprecated public boolean getAutoTimeRequired();
- method public boolean getAutoTimeZoneEnabled(@NonNull android.content.ComponentName);
+ method @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME_ZONE, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public boolean getAutoTimeZoneEnabled(@NonNull android.content.ComponentName);
method @NonNull public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(@NonNull android.content.ComponentName);
method public boolean getBluetoothContactSharingDisabled(@NonNull android.content.ComponentName);
method public boolean getCameraDisabled(@Nullable android.content.ComponentName);
@@ -7648,9 +7652,9 @@
method public boolean setApplicationHidden(@NonNull android.content.ComponentName, String, boolean);
method @WorkerThread public void setApplicationRestrictions(@Nullable android.content.ComponentName, String, android.os.Bundle);
method @Deprecated public void setApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName, @Nullable String) throws android.content.pm.PackageManager.NameNotFoundException;
- method public void setAutoTimeEnabled(@NonNull android.content.ComponentName, boolean);
+ method @RequiresPermission(value=android.Manifest.permission.SET_TIME, conditional=true) public void setAutoTimeEnabled(@NonNull android.content.ComponentName, boolean);
method @Deprecated public void setAutoTimeRequired(@NonNull android.content.ComponentName, boolean);
- method public void setAutoTimeZoneEnabled(@NonNull android.content.ComponentName, boolean);
+ method @RequiresPermission(value=android.Manifest.permission.SET_TIME_ZONE, conditional=true) public void setAutoTimeZoneEnabled(@NonNull android.content.ComponentName, boolean);
method public void setBackupServiceEnabled(@NonNull android.content.ComponentName, boolean);
method public void setBluetoothContactSharingDisabled(@NonNull android.content.ComponentName, boolean);
method public void setCameraDisabled(@NonNull android.content.ComponentName, boolean);
@@ -7729,8 +7733,8 @@
method @Deprecated public int setStorageEncryption(@NonNull android.content.ComponentName, boolean);
method public void setSystemSetting(@NonNull android.content.ComponentName, @NonNull String, String);
method public void setSystemUpdatePolicy(@NonNull android.content.ComponentName, android.app.admin.SystemUpdatePolicy);
- method public boolean setTime(@NonNull android.content.ComponentName, long);
- method public boolean setTimeZone(@NonNull android.content.ComponentName, String);
+ method @RequiresPermission(value=android.Manifest.permission.SET_TIME, conditional=true) public boolean setTime(@NonNull android.content.ComponentName, long);
+ method @RequiresPermission(value=android.Manifest.permission.SET_TIME_ZONE, conditional=true) public boolean setTimeZone(@NonNull android.content.ComponentName, String);
method public void setTrustAgentConfiguration(@NonNull android.content.ComponentName, @NonNull android.content.ComponentName, android.os.PersistableBundle);
method public void setUninstallBlocked(@Nullable android.content.ComponentName, String, boolean);
method public void setUsbDataSignalingEnabled(boolean);
@@ -7803,7 +7807,7 @@
field @Deprecated public static final String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
field public static final String EXTRA_PROVISIONING_IMEI = "android.app.extra.PROVISIONING_IMEI";
field public static final String EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION = "android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION";
- field public static final String EXTRA_PROVISIONING_KEEP_SCREEN_ON = "android.app.extra.PROVISIONING_KEEP_SCREEN_ON";
+ field @Deprecated public static final String EXTRA_PROVISIONING_KEEP_SCREEN_ON = "android.app.extra.PROVISIONING_KEEP_SCREEN_ON";
field public static final String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
field public static final String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
field public static final String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME";
@@ -40318,12 +40322,14 @@
public final class Tile implements android.os.Parcelable {
method public int describeContents();
+ method @Nullable public android.app.PendingIntent getActivityLaunchForClick();
method public CharSequence getContentDescription();
method public android.graphics.drawable.Icon getIcon();
method public CharSequence getLabel();
method public int getState();
method @Nullable public CharSequence getStateDescription();
method @Nullable public CharSequence getSubtitle();
+ method public void setActivityLaunchForClick(@Nullable android.app.PendingIntent);
method public void setContentDescription(CharSequence);
method public void setIcon(android.graphics.drawable.Icon);
method public void setLabel(CharSequence);
@@ -40352,6 +40358,7 @@
method public static final void requestListeningState(android.content.Context, android.content.ComponentName);
method public final void showDialog(android.app.Dialog);
method public final void startActivityAndCollapse(android.content.Intent);
+ method public final void startActivityAndCollapse(@NonNull android.app.PendingIntent);
method public final void unlockAndRun(Runnable);
field public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
field public static final String ACTION_QS_TILE_PREFERENCES = "android.service.quicksettings.action.QS_TILE_PREFERENCES";
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 2546294..447b113 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -82,6 +82,7 @@
}
public abstract class Context {
+ method @NonNull public android.os.IBinder getIApplicationThreadBinder();
method @NonNull public android.os.UserHandle getUser();
field public static final String PAC_PROXY_SERVICE = "pac_proxy";
field public static final String TEST_NETWORK_SERVICE = "test_network";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 712ac94..b4594e0 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -6607,6 +6607,7 @@
public class AudioManager {
method @Deprecated public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addAssistantServicesUids(@NonNull int[]);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public void addOnDevicesForAttributesChangedListener(@NonNull android.media.AudioAttributes, @NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnDevicesForAttributesChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnNonDefaultDevicesForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnNonDefaultDevicesForStrategyChangedListener) throws java.lang.SecurityException;
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException;
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForCapturePresetChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener) throws java.lang.SecurityException;
@@ -6647,6 +6648,7 @@
method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeAssistantServicesUids(@NonNull int[]);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean removeDeviceAsNonDefaultForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAttributes);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public void removeOnDevicesForAttributesChangedListener(@NonNull android.media.AudioManager.OnDevicesForAttributesChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnNonDefaultDevicesForStrategyChangedListener(@NonNull android.media.AudioManager.OnNonDefaultDevicesForStrategyChangedListener);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForCapturePresetChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener);
@@ -6704,6 +6706,10 @@
field public static final int EVENT_TIMEOUT = 2; // 0x2
}
+ public static interface AudioManager.OnDevicesForAttributesChangedListener {
+ method public void onDevicesForAttributesChanged(@NonNull android.media.AudioAttributes, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
+ }
+
public static interface AudioManager.OnNonDefaultDevicesForStrategyChangedListener {
method public void onNonDefaultDevicesForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
}
@@ -12539,6 +12545,19 @@
field public static final int INITIALIZATION_STATUS_UNKNOWN = 100; // 0x64
}
+ public abstract class VisualQueryDetectionService extends android.app.Service implements android.service.voice.SandboxedDetectionServiceBase {
+ ctor public VisualQueryDetectionService();
+ method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public void onStartDetection(@NonNull android.service.voice.VisualQueryDetectionService.Callback);
+ method public void onStopDetection();
+ method public void onUpdateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, long, @Nullable java.util.function.IntConsumer);
+ field public static final String SERVICE_INTERFACE = "android.service.voice.VisualQueryDetectionService";
+ }
+
+ public static final class VisualQueryDetectionService.Callback {
+ ctor public VisualQueryDetectionService.Callback();
+ }
+
public class VoiceInteractionService extends android.app.Service {
method @NonNull public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, android.service.voice.AlwaysOnHotwordDetector.Callback);
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index 871d15e..51ea04f 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -416,6 +416,15 @@
*/
public static final int SUBREASON_UNDELIVERED_BROADCAST = 26;
+ /**
+ * The process was killed because its associated SDK sandbox process (where it had loaded SDKs)
+ * had died; this would be set only when the reason is {@link #REASON_DEPENDENCY_DIED}.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_SDK_SANDBOX_DIED = 27;
+
// If there is any OEM code which involves additional app kill reasons, it should
// be categorized in {@link #REASON_OTHER}, with subreason code starting from 1000.
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 1120257..240dbe1 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2028,6 +2028,13 @@
}
/** @hide */
+ @NonNull
+ @Override
+ public IBinder getIApplicationThreadBinder() {
+ return getIApplicationThread().asBinder();
+ }
+
+ /** @hide */
@Override
public Handler getMainThreadHandler() {
return mMainThread.getHandler();
@@ -3039,7 +3046,8 @@
public void updateDeviceId(int updatedDeviceId) {
if (!isValidDeviceId(updatedDeviceId)) {
throw new IllegalArgumentException(
- "Not a valid ID of the default device or any virtual device: " + mDeviceId);
+ "Not a valid ID of the default device or any virtual device: "
+ + updatedDeviceId);
}
if (mIsExplicitDeviceId) {
throw new UnsupportedOperationException(
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index e729e7d..209b112 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -16,6 +16,9 @@
package android.app.admin;
+import static android.Manifest.permission.QUERY_ADMIN_POLICY;
+import static android.Manifest.permission.SET_TIME;
+import static android.Manifest.permission.SET_TIME_ZONE;
import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
@@ -3311,13 +3314,16 @@
* A {@code boolean} flag that indicates whether the screen should be on throughout the
* provisioning flow.
*
- * <p>The default value is {@code false}.
- *
* <p>This extra can either be passed as an extra to the {@link
* #ACTION_PROVISION_MANAGED_PROFILE} intent, or it can be returned by the
* admin app when performing the admin-integrated provisioning flow as a result of the
* {@link #ACTION_GET_PROVISIONING_MODE} activity.
+ *
+ * @deprecated from {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the flag wouldn't
+ * be functional. The screen is kept on throughout the provisioning flow.
*/
+
+ @Deprecated
public static final String EXTRA_PROVISIONING_KEEP_SCREEN_ON =
"android.app.extra.PROVISIONING_KEEP_SCREEN_ON";
@@ -8466,8 +8472,10 @@
}
/**
- * Called by a device owner, a profile owner for the primary user or a profile
- * owner of an organization-owned managed profile to turn auto time on and off.
+ * Called by a device owner, a profile owner for the primary user, a profile
+ * owner of an organization-owned managed profile or, starting from Android
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, holders of the permission
+ * {@link android.Manifest.permission#SET_TIME} to turn auto time on and off.
* Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME}
* to prevent the user from changing this setting.
* <p>
@@ -8478,8 +8486,10 @@
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param enabled Whether time should be obtained automatically from the network or not.
* @throws SecurityException if caller is not a device owner, a profile owner for the
- * primary user, or a profile owner of an organization-owned managed profile.
+ * primary user, or a profile owner of an organization-owned managed profile or a holder of the
+ * permission {@link android.Manifest.permission#SET_TIME}.
*/
+ @RequiresPermission(value = SET_TIME, conditional = true)
public void setAutoTimeEnabled(@NonNull ComponentName admin, boolean enabled) {
if (mService != null) {
try {
@@ -8491,10 +8501,18 @@
}
/**
+ * Returns true if auto time is enabled on the device.
+ *
+ * <p> Starting from Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, callers
+ * are also able to call this method if they hold the permission
+ *{@link android.Manifest.permission#SET_TIME}.
+ *
* @return true if auto time is enabled on the device.
- * @throws SecurityException if caller is not a device owner, a profile owner for the
- * primary user, or a profile owner of an organization-owned managed profile.
+ * @throws SecurityException if the caller is not a device owner, a profile
+ * owner for the primary user, or a profile owner of an organization-owned managed profile or a
+ * holder of the permission {@link android.Manifest.permission#SET_TIME}.
*/
+ @RequiresPermission(anyOf = {SET_TIME, QUERY_ADMIN_POLICY}, conditional = true)
public boolean getAutoTimeEnabled(@NonNull ComponentName admin) {
if (mService != null) {
try {
@@ -8507,8 +8525,10 @@
}
/**
- * Called by a device owner, a profile owner for the primary user or a profile
- * owner of an organization-owned managed profile to turn auto time zone on and off.
+ * Called by a device owner, a profile owner for the primary user, a profile
+ * owner of an organization-owned managed profile or, starting from Android
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, holders of the permission
+ * {@link android.Manifest.permission#SET_TIME} to turn auto time zone on and off.
* Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME}
* to prevent the user from changing this setting.
* <p>
@@ -8519,8 +8539,10 @@
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param enabled Whether time zone should be obtained automatically from the network or not.
* @throws SecurityException if caller is not a device owner, a profile owner for the
- * primary user, or a profile owner of an organization-owned managed profile.
+ * primary user, or a profile owner of an organization-owned managed profile or a holder of the
+ * permission {@link android.Manifest.permission#SET_TIME_ZONE}.
*/
+ @RequiresPermission(value = SET_TIME_ZONE, conditional = true)
public void setAutoTimeZoneEnabled(@NonNull ComponentName admin, boolean enabled) {
throwIfParentInstance("setAutoTimeZone");
if (mService != null) {
@@ -8533,10 +8555,18 @@
}
/**
+ * Returns true if auto time zone is enabled on the device.
+ *
+ * <p> Starting from Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, callers
+ * are also able to call this method if they hold the permission
+ *{@link android.Manifest.permission#SET_TIME}.
+ *
* @return true if auto time zone is enabled on the device.
- * @throws SecurityException if caller is not a device owner, a profile owner for the
- * primary user, or a profile owner of an organization-owned managed profile.
+ * @throws SecurityException if the caller is not a device owner, a profile
+ * owner for the primary user, or a profile owner of an organization-owned managed profile or a
+ * holder of the permission {@link android.Manifest.permission#SET_TIME_ZONE}.
*/
+ @RequiresPermission(anyOf = {SET_TIME_ZONE, QUERY_ADMIN_POLICY}, conditional = true)
public boolean getAutoTimeZoneEnabled(@NonNull ComponentName admin) {
throwIfParentInstance("getAutoTimeZone");
if (mService != null) {
@@ -11875,17 +11905,21 @@
}
/**
- * Called by a device owner or a profile owner of an organization-owned managed
- * profile to set the system wall clock time. This only takes effect if called when
- * {@link android.provider.Settings.Global#AUTO_TIME} is 0, otherwise {@code false}
- * will be returned.
+ * Called by a device owner, a profile owner of an organization-owned managed
+ * profile or, starting from Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * holders of the permission {@link android.Manifest.permission#SET_TIME} to set the system wall
+ * clock time. This only takes effect if called when
+ * {@link android.provider.Settings.Global#AUTO_TIME} is 0, otherwise {@code false} will be
+ * returned.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with
* @param millis time in milliseconds since the Epoch
* @return {@code true} if set time succeeded, {@code false} otherwise.
* @throws SecurityException if {@code admin} is not a device owner or a profile owner
- * of an organization-owned managed profile.
+ * of an organization-owned managed profile or a holder of the permission
+ * {@link android.Manifest.permission#SET_TIME}.
*/
+ @RequiresPermission(value = SET_TIME, conditional = true)
public boolean setTime(@NonNull ComponentName admin, long millis) {
throwIfParentInstance("setTime");
if (mService != null) {
@@ -11899,10 +11933,12 @@
}
/**
- * Called by a device owner or a profile owner of an organization-owned managed
- * profile to set the system's persistent default time zone. This only takes
- * effect if called when {@link android.provider.Settings.Global#AUTO_TIME_ZONE}
- * is 0, otherwise {@code false} will be returned.
+ * Called by a device owner, a profile owner of an organization-owned managed
+ * profile or, starting from Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * holders of the permission {@link android.Manifest.permission#SET_TIME_ZONE} to set the
+ * system's persistent default time zone. This only take effect if called when
+ * {@link android.provider.Settings.Global#AUTO_TIME_ZONE} is 0, otherwise {@code false} will be
+ * returned.
*
* @see android.app.AlarmManager#setTimeZone(String)
* @param admin Which {@link DeviceAdminReceiver} this request is associated with
@@ -11910,8 +11946,10 @@
* {@link java.util.TimeZone#getAvailableIDs}
* @return {@code true} if set timezone succeeded, {@code false} otherwise.
* @throws SecurityException if {@code admin} is not a device owner or a profile owner
- * of an organization-owned managed profile.
+ * of an organization-owned managed profile or a holder of the permissions
+ * {@link android.Manifest.permission#SET_TIME_ZONE}.
*/
+ @RequiresPermission(value = SET_TIME_ZONE, conditional = true)
public boolean setTimeZone(@NonNull ComponentName admin, String timeZone) {
throwIfParentInstance("setTimeZone");
if (mService != null) {
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 840f3a3..3a61ca1 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -271,6 +271,31 @@
public abstract void resetOp(int op, String packageName, @UserIdInt int userId);
/**
+ * Checks if the calling process has been granted permission to apply a device policy on a
+ * specific user.
+ *
+ * The given permission will be checked along with its associated cross-user permission, if it
+ * exists and the target user is different to the calling user.
+ *
+ * @param permission The name of the permission being checked.
+ * @param targetUserId The userId of the user which the caller needs permission to act on.
+ * @throws SecurityException If the calling process has not been granted the permission.
+ */
+ public abstract void enforcePermission(String permission, int targetUserId);
+
+ /**
+ * Return whether the calling process has been granted permission to apply a device policy on
+ * a specific user.
+ *
+ * The given permission will be checked along with its associated cross-user
+ * permission, if it exists and the target user is different to the calling user.
+ *
+ * @param permission The name of the permission being checked.
+ * @param targetUserId The userId of the user which the caller needs permission to act on.
+ */
+ public abstract boolean hasPermission(String permission, int targetUserId);
+
+ /**
* Returns whether new "turn off work" behavior is enabled via feature flag.
*/
public abstract boolean isKeepProfilesRunningEnabled();
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 7d7232e..20b0626 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -7491,6 +7491,18 @@
}
/**
+ * Get the binder object associated with the IApplicationThread of this Context.
+ *
+ * This can be used by a mainline module to uniquely identify a specific app process.
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public IBinder getIApplicationThreadBinder() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* @hide
*/
public Handler getMainThreadHandler() {
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 0a32dd78..0dc4adc 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -1277,6 +1277,14 @@
* @hide
*/
@Override
+ public IBinder getIApplicationThreadBinder() {
+ return mBase.getIApplicationThreadBinder();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
public Handler getMainThreadHandler() {
return mBase.getMainThreadHandler();
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index c5ebf34..a9f55bc 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1066,6 +1066,19 @@
public @interface SizeChangesSupportMode {}
/**
+ * This change id enables compat policy that ignores app requested orientation in
+ * response to an app calling {@link android.app.Activity#setRequestedOrientation}. See
+ * com.android.server.wm.LetterboxUiController#shouldIgnoreRequestedOrientation for
+ * details.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION =
+ 254631730L; // buganizer id
+
+ /**
* This change id forces the packages it is applied to never have Display API sandboxing
* applied for a letterbox or SCM activity. The Display APIs will continue to provide
* DisplayArea bounds.
diff --git a/core/java/android/service/quicksettings/Tile.java b/core/java/android/service/quicksettings/Tile.java
index 289b0e0..910fc44 100644
--- a/core/java/android/service/quicksettings/Tile.java
+++ b/core/java/android/service/quicksettings/Tile.java
@@ -227,7 +227,6 @@
/**
* Gets the Activity {@link PendingIntent} to be launched when the tile is clicked.
- * @hide
*/
@Nullable
public PendingIntent getActivityLaunchForClick() {
@@ -243,7 +242,6 @@
* (This is the default behavior if this method is never called.)
* @param pendingIntent a PendingIntent for an activity to be launched onclick, or {@code null}
* to handle the clicks in the `TileService`.
- * @hide
*/
public void setActivityLaunchForClick(@Nullable PendingIntent pendingIntent) {
if (pendingIntent != null && !pendingIntent.isActivity()) {
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index 506b3b8..7b6ff97 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -15,6 +15,7 @@
*/
package android.service.quicksettings;
+import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
@@ -41,6 +42,8 @@
import com.android.internal.R;
+import java.util.Objects;
+
/**
* A TileService provides the user a tile that can be added to Quick Settings.
* Quick Settings is a space provided that allows the user to change settings and
@@ -341,9 +344,9 @@
* Will collapse Quick Settings after launching.
*
* @param pendingIntent A PendingIntent for an Activity to be launched immediately.
- * @hide
*/
- public void startActivityAndCollapse(PendingIntent pendingIntent) {
+ public final void startActivityAndCollapse(@NonNull PendingIntent pendingIntent) {
+ Objects.requireNonNull(pendingIntent);
try {
mService.startActivity(mTileToken, pendingIntent);
} catch (RemoteException e) {
diff --git a/core/java/android/service/voice/AbstractHotwordDetector.java b/core/java/android/service/voice/AbstractDetector.java
similarity index 89%
rename from core/java/android/service/voice/AbstractHotwordDetector.java
rename to core/java/android/service/voice/AbstractDetector.java
index c90ab67..db0ede5 100644
--- a/core/java/android/service/voice/AbstractHotwordDetector.java
+++ b/core/java/android/service/voice/AbstractDetector.java
@@ -41,9 +41,17 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
-/** Base implementation of {@link HotwordDetector}. */
-abstract class AbstractHotwordDetector implements HotwordDetector {
- private static final String TAG = AbstractHotwordDetector.class.getSimpleName();
+/** Base implementation of {@link HotwordDetector}.
+ *
+ * This class provides methods to manage the detector lifecycle for both
+ * {@link HotwordDetectionService} and {@link VisualQueryDetectionService}. We keep the name of the
+ * interface {@link HotwordDetector} since {@link VisualQueryDetectionService} can be logically
+ * treated as a visual activation hotword detection and also because of the existing public
+ * interface. To avoid confusion on the naming between the trusted hotword framework and the actual
+ * isolated {@link HotwordDetectionService}, the hotword from the names is removed.
+ */
+abstract class AbstractDetector implements HotwordDetector {
+ private static final String TAG = AbstractDetector.class.getSimpleName();
private static final boolean DEBUG = false;
protected final Object mLock = new Object();
@@ -51,14 +59,14 @@
private final IVoiceInteractionManagerService mManagerService;
private final Handler mHandler;
private final HotwordDetector.Callback mCallback;
- private Consumer<AbstractHotwordDetector> mOnDestroyListener;
+ private Consumer<AbstractDetector> mOnDestroyListener;
private final AtomicBoolean mIsDetectorActive;
/**
* A token which is used by voice interaction system service to identify different detectors.
*/
private final IBinder mToken = new Binder();
- AbstractHotwordDetector(
+ AbstractDetector(
IVoiceInteractionManagerService managerService,
HotwordDetector.Callback callback) {
mManagerService = managerService;
@@ -139,7 +147,7 @@
}
}
- void registerOnDestroyListener(Consumer<AbstractHotwordDetector> onDestroyListener) {
+ void registerOnDestroyListener(Consumer<AbstractDetector> onDestroyListener) {
synchronized (mLock) {
if (mOnDestroyListener != null) {
throw new IllegalStateException("only one destroy listener can be registered");
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 9008bf7..e8e8f1a 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -81,7 +81,7 @@
* mark and track it as such.
*/
@SystemApi
-public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
+public class AlwaysOnHotwordDetector extends AbstractDetector {
//---- States of Keyphrase availability. Return codes for onAvailabilityChanged() ----//
/**
* Indicates that this hotword detector is no longer valid for any recognition
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index f1b7745..ffc6621 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -46,7 +46,7 @@
*
* @hide
**/
-class SoftwareHotwordDetector extends AbstractHotwordDetector {
+class SoftwareHotwordDetector extends AbstractDetector {
private static final String TAG = SoftwareHotwordDetector.class.getSimpleName();
private static final boolean DEBUG = false;
diff --git a/core/java/android/service/voice/VisualQueryDetectionService.java b/core/java/android/service/voice/VisualQueryDetectionService.java
new file mode 100644
index 0000000..d8266f3
--- /dev/null
+++ b/core/java/android/service/voice/VisualQueryDetectionService.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2022 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.service.voice;
+
+import android.annotation.DurationMillisLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.ContentCaptureOptions;
+import android.content.Intent;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.media.AudioFormat;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.speech.IRecognitionServiceManager;
+import android.util.Log;
+import android.view.contentcapture.IContentCaptureManager;
+
+import java.util.function.IntConsumer;
+
+/**
+ * Implemented by an application that wants to offer query detection with visual signals.
+ *
+ * This service leverages visual signals such as camera frames to detect and stream queries from the
+ * device microphone to the {@link VoiceInteractionService}, without the support of hotword. The
+ * system will bind an application's {@link VoiceInteractionService} first. When
+ * {@link VoiceInteractionService#createVisualQueryDetector(PersistableBundle, SharedMemory,
+ * Executor, VisualQueryDetector.Callback)} is called, the system will bind the application's
+ * {@link VisualQueryDetectionService}. When requested from {@link VoiceInteractionService}, the
+ * system calls into the {@link VisualQueryDetectionService#onStartDetection(Callback)} to enable
+ * detection. This method MUST be implemented to support visual query detection service.
+ *
+ * Note: Methods in this class may be called concurrently.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class VisualQueryDetectionService extends Service
+ implements SandboxedDetectionServiceBase {
+
+ private static final String TAG = VisualQueryDetectionService.class.getSimpleName();
+
+ private static final long UPDATE_TIMEOUT_MILLIS = 20000;
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ * To be supported, the service must also require the
+ * {@link android.Manifest.permission#BIND_VISUAL_QUERY_DETECTION_SERVICE} permission
+ * so that other applications can not abuse it.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.service.voice.VisualQueryDetectionService";
+
+
+ /** @hide */
+ public static final String KEY_INITIALIZATION_STATUS = "initialization_status";
+
+
+ private final ISandboxedDetectionService mInterface = new ISandboxedDetectionService.Stub() {
+
+ @Override
+ public void updateState(PersistableBundle options, SharedMemory sharedMemory,
+ IRemoteCallback callback) throws RemoteException {
+ Log.v(TAG, "#updateState" + (callback != null ? " with callback" : ""));
+ VisualQueryDetectionService.this.onUpdateStateInternal(
+ options,
+ sharedMemory,
+ callback);
+ }
+
+ @Override
+ public void ping(IRemoteCallback callback) throws RemoteException {
+ callback.sendResult(null);
+ }
+
+ @Override
+ public void detectFromDspSource(
+ SoundTrigger.KeyphraseRecognitionEvent event,
+ AudioFormat audioFormat,
+ long timeoutMillis,
+ IDspHotwordDetectionCallback callback) {
+ throw new UnsupportedOperationException("Not supported by VisualQueryDetectionService");
+ }
+
+ @Override
+ public void detectFromMicrophoneSource(
+ ParcelFileDescriptor audioStream,
+ @HotwordDetectionService.AudioSource int audioSource,
+ AudioFormat audioFormat,
+ PersistableBundle options,
+ IDspHotwordDetectionCallback callback) {
+ throw new UnsupportedOperationException("Not supported by VisualQueryDetectionService");
+ }
+
+ @Override
+ public void updateAudioFlinger(IBinder audioFlinger) {
+ Log.v(TAG, "Ignore #updateAudioFlinger");
+ }
+
+ @Override
+ public void updateContentCaptureManager(IContentCaptureManager manager,
+ ContentCaptureOptions options) {
+ Log.v(TAG, "Ignore #updateContentCaptureManager");
+ }
+
+ @Override
+ public void updateRecognitionServiceManager(IRecognitionServiceManager manager) {
+ Log.v(TAG, "Ignore #updateRecognitionServiceManager");
+ }
+
+ @Override
+ public void stopDetection() {
+ throw new UnsupportedOperationException("Not supported by VisualQueryDetectionService");
+ }
+ };
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @Override
+ @SystemApi
+ public void onUpdateState(
+ @Nullable PersistableBundle options,
+ @Nullable SharedMemory sharedMemory,
+ @DurationMillisLong long callbackTimeoutMillis,
+ @Nullable IntConsumer statusCallback) {
+ }
+
+ @Override
+ @Nullable
+ public IBinder onBind(@NonNull Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return mInterface.asBinder();
+ }
+ Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": "
+ + intent);
+ return null;
+ }
+
+ private void onUpdateStateInternal(@Nullable PersistableBundle options,
+ @Nullable SharedMemory sharedMemory, IRemoteCallback callback) {
+ IntConsumer intConsumer =
+ SandboxedDetectionServiceBase.createInitializationStatusConsumer(callback);
+ onUpdateState(options, sharedMemory, UPDATE_TIMEOUT_MILLIS, intConsumer);
+ }
+
+ /**
+ * This is called after the service is set up and the client should open the camera and the
+ * microphone to start recognition.
+ *
+ * Called when the {@link VoiceInteractionService} requests that this service
+ * {@link HotwordDetector#startRecognition()} start recognition on audio coming directly
+ * from the device microphone.
+ *
+ * @param callback The callback to use for responding to the detection request.
+ *
+ */
+ public void onStartDetection(@NonNull Callback callback) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Called when the {@link VoiceInteractionService}
+ * {@link HotwordDetector#stopRecognition()} requests that recognition be stopped.
+ */
+ public void onStopDetection() {
+ }
+
+ /**
+ * Callback for sending out signals and returning query results.
+ */
+ public static final class Callback {
+ //TODO: Add callback to send signals to VIS and SysUI.
+ }
+
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index a59578e..9e1518d 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -168,7 +168,7 @@
private KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;
- private final Set<HotwordDetector> mActiveHotwordDetectors = new ArraySet<>();
+ private final Set<HotwordDetector> mActiveDetectors = new ArraySet<>();
/**
* Called when a user has activated an affordance to launch voice assist from the Keyguard.
@@ -319,7 +319,7 @@
private void onSoundModelsChangedInternal() {
synchronized (this) {
// TODO: Stop recognition if a sound model that was being recognized gets deleted.
- mActiveHotwordDetectors.forEach(detector -> {
+ mActiveDetectors.forEach(detector -> {
if (detector instanceof AlwaysOnHotwordDetector) {
((AlwaysOnHotwordDetector) detector).onSoundModelsChanged();
}
@@ -429,7 +429,7 @@
// Allow only one concurrent recognition via the APIs.
safelyShutdownAllHotwordDetectors();
} else {
- for (HotwordDetector detector : mActiveHotwordDetectors) {
+ for (HotwordDetector detector : mActiveDetectors) {
if (detector.isUsingSandboxedDetectionService()
!= supportHotwordDetectionService) {
throw new IllegalStateException(
@@ -447,13 +447,13 @@
callback, mKeyphraseEnrollmentInfo, mSystemService,
getApplicationContext().getApplicationInfo().targetSdkVersion,
supportHotwordDetectionService);
- mActiveHotwordDetectors.add(dspDetector);
+ mActiveDetectors.add(dspDetector);
try {
dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed);
dspDetector.initialize(options, sharedMemory);
} catch (Exception e) {
- mActiveHotwordDetectors.remove(dspDetector);
+ mActiveDetectors.remove(dspDetector);
dspDetector.destroy();
throw e;
}
@@ -512,7 +512,7 @@
// Allow only one concurrent recognition via the APIs.
safelyShutdownAllHotwordDetectors();
} else {
- for (HotwordDetector detector : mActiveHotwordDetectors) {
+ for (HotwordDetector detector : mActiveDetectors) {
if (!detector.isUsingSandboxedDetectionService()) {
throw new IllegalStateException(
"It disallows to create trusted and non-trusted detectors "
@@ -528,14 +528,14 @@
SoftwareHotwordDetector softwareHotwordDetector =
new SoftwareHotwordDetector(
mSystemService, null, callback);
- mActiveHotwordDetectors.add(softwareHotwordDetector);
+ mActiveDetectors.add(softwareHotwordDetector);
try {
softwareHotwordDetector.registerOnDestroyListener(
this::onHotwordDetectorDestroyed);
softwareHotwordDetector.initialize(options, sharedMemory);
} catch (Exception e) {
- mActiveHotwordDetectors.remove(softwareHotwordDetector);
+ mActiveDetectors.remove(softwareHotwordDetector);
softwareHotwordDetector.destroy();
throw e;
}
@@ -586,7 +586,7 @@
private void safelyShutdownAllHotwordDetectors() {
synchronized (mLock) {
- mActiveHotwordDetectors.forEach(detector -> {
+ mActiveDetectors.forEach(detector -> {
try {
detector.destroy();
} catch (Exception ex) {
@@ -598,13 +598,13 @@
private void onHotwordDetectorDestroyed(@NonNull HotwordDetector detector) {
synchronized (mLock) {
- mActiveHotwordDetectors.remove(detector);
+ mActiveDetectors.remove(detector);
shutdownHotwordDetectionServiceIfRequiredLocked();
}
}
private void shutdownHotwordDetectionServiceIfRequiredLocked() {
- for (HotwordDetector detector : mActiveHotwordDetectors) {
+ for (HotwordDetector detector : mActiveDetectors) {
if (detector.isUsingSandboxedDetectionService()) {
return;
}
@@ -638,11 +638,11 @@
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("VOICE INTERACTION");
synchronized (mLock) {
- pw.println(" HotwordDetector(s)");
- if (mActiveHotwordDetectors.size() == 0) {
+ pw.println(" Sandboxed Detector(s)");
+ if (mActiveDetectors.size() == 0) {
pw.println(" NULL");
} else {
- mActiveHotwordDetectors.forEach(detector -> {
+ mActiveDetectors.forEach(detector -> {
detector.dump(" ", pw);
pw.println();
});
diff --git a/core/java/android/view/EventLogTags.logtags b/core/java/android/view/EventLogTags.logtags
index 1ad3472..cc0b18a 100644
--- a/core/java/android/view/EventLogTags.logtags
+++ b/core/java/android/view/EventLogTags.logtags
@@ -44,6 +44,12 @@
32007 imf_ime_anim_finish (token|3),(animation type|1),(alpha|5),(shown|1),(insets|3)
# IME animation is canceled.
32008 imf_ime_anim_cancel (token|3),(animation type|1),(pending insets|3)
+# IME remote animation is started.
+32009 imf_ime_remote_anim_start (token|3),(displayId|1),(direction|1),(alpha|5),(startY|5),(endY|5),(leash|3),(insets|3),(surface position|3),(ime frame|3)
+# IME remote animation is end.
+32010 imf_ime_remote_anim_end (token|3),(displayId|1),(direction|1),(endY|5),(leash|3),(insets|3),(surface position|3),(ime frame|3)
+# IME remote animation is canceled.
+32011 imf_ime_remote_anim_cancel (token|3),(displayId|1),(insets|3)
# 62000 - 62199 reserved for inputflinger
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 0aba80d..e38e537 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -721,8 +721,7 @@
*
* @return {@code true} if system bars are always consumed.
*/
- boolean getWindowInsets(in WindowManager.LayoutParams attrs, int displayId,
- out InsetsState outInsetsState);
+ boolean getWindowInsets(int displayId, in IBinder token, out InsetsState outInsetsState);
/**
* Returns a list of {@link android.view.DisplayInfo} for the logical display. This is not
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index f375ccb..43cf758 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -814,6 +814,45 @@
}
/**
+ * Activity level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the activity can be opted-in or opted-out
+ * from the compatibility treatment that avoids {@link
+ * android.app.Activity#setRequestedOrientation} loops. The loop can be trigerred by
+ * ignoreRequestedOrientation display setting enabled on the device or by the landscape natural
+ * orientation of the device.
+ *
+ * <p>The treatment is disabled by default but device manufacturers can enable the treatment
+ * using their discretion to improve display compatibility.
+ *
+ * <p>With this property set to {@code true}, the system could ignore {@link
+ * android.app.Activity#setRequestedOrientation} call from an app if one of the following
+ * conditions are true:
+ * <ul>
+ * <li>Activity is relaunching due to the previous {@link
+ * android.app.Activity#setRequestedOrientation} call.
+ * <li>Camera compatibility force rotation treatment is active for the package.
+ * </ul>
+ *
+ * <p>Setting this property to {@code false} informs the system that the activity must be
+ * opted-out from the compatibility treatment even if the device manufacturer has opted the app
+ * into the treatment.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <activity>
+ * <property
+ * android:name="android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION"
+ * android:value="true|false"/>
+ * </activity>
+ * </pre>
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION =
+ "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
+
+ /**
* @hide
*/
public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 9ed5c29..3b6ec80 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -44,8 +44,7 @@
String TAG = "ImeTracker";
/** The debug flag for IME visibility event log. */
- // TODO(b/239501597) : Have a system property to control this flag.
- boolean DEBUG_IME_VISIBILITY = false;
+ boolean DEBUG_IME_VISIBILITY = SystemProperties.getBoolean("persist.debug.imf_event", false);
/** The message to indicate if there is no valid {@link Token}. */
String TOKEN_NONE = "TOKEN_NONE";
diff --git a/core/java/android/window/TaskFragmentAnimationParams.aidl b/core/java/android/window/TaskFragmentAnimationParams.aidl
new file mode 100644
index 0000000..04dee58
--- /dev/null
+++ b/core/java/android/window/TaskFragmentAnimationParams.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 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.window;
+
+/**
+ * Data object for animation related override of TaskFragment.
+ * @hide
+ */
+parcelable TaskFragmentAnimationParams;
diff --git a/core/java/android/window/TaskFragmentAnimationParams.java b/core/java/android/window/TaskFragmentAnimationParams.java
new file mode 100644
index 0000000..a600a4d
--- /dev/null
+++ b/core/java/android/window/TaskFragmentAnimationParams.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 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.window;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Data object for animation related override of TaskFragment.
+ * @hide
+ */
+// TODO(b/206557124): Add more animation customization options.
+public final class TaskFragmentAnimationParams implements Parcelable {
+
+ /** The default {@link TaskFragmentAnimationParams} to use when there is no app override. */
+ public static final TaskFragmentAnimationParams DEFAULT =
+ new TaskFragmentAnimationParams.Builder().build();
+
+ @ColorInt
+ private final int mAnimationBackgroundColor;
+
+ private TaskFragmentAnimationParams(@ColorInt int animationBackgroundColor) {
+ mAnimationBackgroundColor = animationBackgroundColor;
+ }
+
+ /**
+ * The {@link ColorInt} to use for the background during the animation with this TaskFragment if
+ * the animation requires a background.
+ *
+ * The default value is {@code 0}, which is to use the theme window background.
+ */
+ @ColorInt
+ public int getAnimationBackgroundColor() {
+ return mAnimationBackgroundColor;
+ }
+
+ private TaskFragmentAnimationParams(Parcel in) {
+ mAnimationBackgroundColor = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mAnimationBackgroundColor);
+ }
+
+ @NonNull
+ public static final Creator<TaskFragmentAnimationParams> CREATOR =
+ new Creator<TaskFragmentAnimationParams>() {
+ @Override
+ public TaskFragmentAnimationParams createFromParcel(Parcel in) {
+ return new TaskFragmentAnimationParams(in);
+ }
+
+ @Override
+ public TaskFragmentAnimationParams[] newArray(int size) {
+ return new TaskFragmentAnimationParams[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "TaskFragmentAnimationParams{"
+ + " animationBgColor=" + Integer.toHexString(mAnimationBackgroundColor)
+ + "}";
+ }
+
+ @Override
+ public int hashCode() {
+ return mAnimationBackgroundColor;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof TaskFragmentAnimationParams)) {
+ return false;
+ }
+ final TaskFragmentAnimationParams other = (TaskFragmentAnimationParams) obj;
+ return mAnimationBackgroundColor == other.mAnimationBackgroundColor;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Builder to construct the {@link TaskFragmentAnimationParams}. */
+ public static final class Builder {
+
+ @ColorInt
+ private int mAnimationBackgroundColor = 0;
+
+ /**
+ * Sets the {@link ColorInt} to use for the background during the animation with this
+ * TaskFragment if the animation requires a background. The default value is
+ * {@code 0}, which is to use the theme window background.
+ *
+ * @param color a packed color int, {@code AARRGGBB}, for the animation background color.
+ * @return this {@link Builder}.
+ */
+ @NonNull
+ public Builder setAnimationBackgroundColor(@ColorInt int color) {
+ mAnimationBackgroundColor = color;
+ return this;
+ }
+
+ /** Constructs the {@link TaskFragmentAnimationParams}. */
+ @NonNull
+ public TaskFragmentAnimationParams build() {
+ return new TaskFragmentAnimationParams(mAnimationBackgroundColor);
+ }
+ }
+}
diff --git a/core/java/android/window/TaskFragmentOperation.aidl b/core/java/android/window/TaskFragmentOperation.aidl
new file mode 100644
index 0000000..c21700c
--- /dev/null
+++ b/core/java/android/window/TaskFragmentOperation.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 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.window;
+
+/**
+ * Data object of params for TaskFragment related {@link WindowContainerTransaction} operation.
+ * @hide
+ */
+parcelable TaskFragmentOperation;
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
new file mode 100644
index 0000000..bec6c58
--- /dev/null
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2022 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.window;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Data object of params for TaskFragment related {@link WindowContainerTransaction} operation.
+ *
+ * @see WindowContainerTransaction#setTaskFragmentOperation(IBinder, TaskFragmentOperation).
+ * @hide
+ */
+// TODO(b/263436063): move other TaskFragment related operation here.
+public final class TaskFragmentOperation implements Parcelable {
+
+ /** Sets the {@link TaskFragmentAnimationParams} for the given TaskFragment. */
+ public static final int OP_TYPE_SET_ANIMATION_PARAMS = 0;
+
+ @IntDef(prefix = { "OP_TYPE_" }, value = {
+ OP_TYPE_SET_ANIMATION_PARAMS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface OperationType {}
+
+ @OperationType
+ private final int mOpType;
+
+ @Nullable
+ private final TaskFragmentAnimationParams mAnimationParams;
+
+ private TaskFragmentOperation(@OperationType int opType,
+ @Nullable TaskFragmentAnimationParams animationParams) {
+ mOpType = opType;
+ mAnimationParams = animationParams;
+ }
+
+ private TaskFragmentOperation(Parcel in) {
+ mOpType = in.readInt();
+ mAnimationParams = in.readTypedObject(TaskFragmentAnimationParams.CREATOR);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mOpType);
+ dest.writeTypedObject(mAnimationParams, flags);
+ }
+
+ @NonNull
+ public static final Creator<TaskFragmentOperation> CREATOR =
+ new Creator<TaskFragmentOperation>() {
+ @Override
+ public TaskFragmentOperation createFromParcel(Parcel in) {
+ return new TaskFragmentOperation(in);
+ }
+
+ @Override
+ public TaskFragmentOperation[] newArray(int size) {
+ return new TaskFragmentOperation[size];
+ }
+ };
+
+ /**
+ * Gets the {@link OperationType} of this {@link TaskFragmentOperation}.
+ */
+ @OperationType
+ public int getOpType() {
+ return mOpType;
+ }
+
+ /**
+ * Gets the animation related override of TaskFragment.
+ */
+ @Nullable
+ public TaskFragmentAnimationParams getAnimationParams() {
+ return mAnimationParams;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("TaskFragmentOperation{ opType=").append(mOpType);
+ if (mAnimationParams != null) {
+ sb.append(", animationParams=").append(mAnimationParams);
+ }
+
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mOpType;
+ result = result * 31 + mAnimationParams.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof TaskFragmentOperation)) {
+ return false;
+ }
+ final TaskFragmentOperation other = (TaskFragmentOperation) obj;
+ return mOpType == other.mOpType
+ && Objects.equals(mAnimationParams, other.mAnimationParams);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Builder to construct the {@link TaskFragmentOperation}. */
+ public static final class Builder {
+
+ @OperationType
+ private final int mOpType;
+
+ @Nullable
+ private TaskFragmentAnimationParams mAnimationParams;
+
+ /**
+ * @param opType the {@link OperationType} of this {@link TaskFragmentOperation}.
+ */
+ public Builder(@OperationType int opType) {
+ mOpType = opType;
+ }
+
+ /**
+ * Sets the {@link TaskFragmentAnimationParams} for the given TaskFragment.
+ */
+ @NonNull
+ public Builder setAnimationParams(@Nullable TaskFragmentAnimationParams animationParams) {
+ mAnimationParams = animationParams;
+ return this;
+ }
+
+ /**
+ * Constructs the {@link TaskFragmentOperation}.
+ */
+ @NonNull
+ public TaskFragmentOperation build() {
+ return new TaskFragmentOperation(mOpType, mAnimationParams);
+ }
+ }
+}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 5793674..647ccf5 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -751,6 +751,30 @@
}
/**
+ * Sets the {@link TaskFragmentOperation} to apply to the given TaskFragment.
+ *
+ * @param fragmentToken client assigned unique token to create TaskFragment with specified in
+ * {@link TaskFragmentCreationParams#getFragmentToken()}.
+ * @param taskFragmentOperation the {@link TaskFragmentOperation} to apply to the given
+ * TaskFramgent.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setTaskFragmentOperation(@NonNull IBinder fragmentToken,
+ @NonNull TaskFragmentOperation taskFragmentOperation) {
+ Objects.requireNonNull(fragmentToken);
+ Objects.requireNonNull(taskFragmentOperation);
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(
+ HierarchyOp.HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION)
+ .setContainer(fragmentToken)
+ .setTaskFragmentOperation(taskFragmentOperation)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
* Sets/removes the always on top flag for this {@code windowContainer}. See
* {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}.
* Please note that this method is only intended to be used for a
@@ -1261,6 +1285,7 @@
public static final int HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT = 22;
public static final int HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS = 23;
public static final int HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH = 24;
+ public static final int HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION = 25;
// The following key(s) are for use with mLaunchOptions:
// When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1301,10 +1326,14 @@
@Nullable
private Intent mActivityIntent;
- // Used as options for WindowContainerTransaction#createTaskFragment().
+ /** Used as options for {@link #createTaskFragment}. */
@Nullable
private TaskFragmentCreationParams mTaskFragmentCreationOptions;
+ /** Used as options for {@link #setTaskFragmentOperation}. */
+ @Nullable
+ private TaskFragmentOperation mTaskFragmentOperation;
+
@Nullable
private PendingIntent mPendingIntent;
@@ -1424,6 +1453,7 @@
mLaunchOptions = copy.mLaunchOptions;
mActivityIntent = copy.mActivityIntent;
mTaskFragmentCreationOptions = copy.mTaskFragmentCreationOptions;
+ mTaskFragmentOperation = copy.mTaskFragmentOperation;
mPendingIntent = copy.mPendingIntent;
mShortcutInfo = copy.mShortcutInfo;
mAlwaysOnTop = copy.mAlwaysOnTop;
@@ -1447,6 +1477,7 @@
mLaunchOptions = in.readBundle();
mActivityIntent = in.readTypedObject(Intent.CREATOR);
mTaskFragmentCreationOptions = in.readTypedObject(TaskFragmentCreationParams.CREATOR);
+ mTaskFragmentOperation = in.readTypedObject(TaskFragmentOperation.CREATOR);
mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
mShortcutInfo = in.readTypedObject(ShortcutInfo.CREATOR);
mAlwaysOnTop = in.readBoolean();
@@ -1535,6 +1566,11 @@
}
@Nullable
+ public TaskFragmentOperation getTaskFragmentOperation() {
+ return mTaskFragmentOperation;
+ }
+
+ @Nullable
public PendingIntent getPendingIntent() {
return mPendingIntent;
}
@@ -1612,6 +1648,9 @@
case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH:
return "{setReparentLeafTaskIfRelaunch: container= " + mContainer
+ " reparentLeafTaskIfRelaunch= " + mReparentLeafTaskIfRelaunch + "}";
+ case HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION:
+ return "{setTaskFragmentOperation: fragmentToken= " + mContainer
+ + " operation= " + mTaskFragmentOperation + "}";
default:
return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
+ " mToTop=" + mToTop
@@ -1639,6 +1678,7 @@
dest.writeBundle(mLaunchOptions);
dest.writeTypedObject(mActivityIntent, flags);
dest.writeTypedObject(mTaskFragmentCreationOptions, flags);
+ dest.writeTypedObject(mTaskFragmentOperation, flags);
dest.writeTypedObject(mPendingIntent, flags);
dest.writeTypedObject(mShortcutInfo, flags);
dest.writeBoolean(mAlwaysOnTop);
@@ -1696,6 +1736,9 @@
private TaskFragmentCreationParams mTaskFragmentCreationOptions;
@Nullable
+ private TaskFragmentOperation mTaskFragmentOperation;
+
+ @Nullable
private PendingIntent mPendingIntent;
@Nullable
@@ -1775,6 +1818,12 @@
return this;
}
+ Builder setTaskFragmentOperation(
+ @Nullable TaskFragmentOperation taskFragmentOperation) {
+ mTaskFragmentOperation = taskFragmentOperation;
+ return this;
+ }
+
Builder setReparentLeafTaskIfRelaunch(boolean reparentLeafTaskIfRelaunch) {
mReparentLeafTaskIfRelaunch = reparentLeafTaskIfRelaunch;
return this;
@@ -1804,6 +1853,7 @@
hierarchyOp.mPendingIntent = mPendingIntent;
hierarchyOp.mAlwaysOnTop = mAlwaysOnTop;
hierarchyOp.mTaskFragmentCreationOptions = mTaskFragmentCreationOptions;
+ hierarchyOp.mTaskFragmentOperation = mTaskFragmentOperation;
hierarchyOp.mShortcutInfo = mShortcutInfo;
hierarchyOp.mReparentLeafTaskIfRelaunch = mReparentLeafTaskIfRelaunch;
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index 5b08879..06449d5 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -24,8 +24,10 @@
import android.app.ResourcesManager;
import android.app.WindowConfiguration;
import android.content.Context;
+import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.IBinder;
import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.view.Display;
@@ -89,23 +91,16 @@
isScreenRound = config.isScreenRound();
windowingMode = winConfig.getWindowingMode();
}
- final WindowInsets windowInsets = computeWindowInsets(bounds, isScreenRound, windowingMode);
+ final IBinder token = Context.getToken(mContext);
+ final WindowInsets windowInsets = getWindowInsetsFromServerForCurrentDisplay(token,
+ bounds, isScreenRound, windowingMode);
return new WindowMetrics(bounds, windowInsets, density);
}
- private WindowInsets computeWindowInsets(Rect bounds, boolean isScreenRound,
- @WindowConfiguration.WindowingMode int windowingMode) {
- // Initialize params which used for obtaining all system insets.
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
- params.token = Context.getToken(mContext);
- return getWindowInsetsFromServerForCurrentDisplay(params, bounds, isScreenRound,
- windowingMode);
- }
-
private WindowInsets getWindowInsetsFromServerForCurrentDisplay(
- WindowManager.LayoutParams attrs, Rect bounds, boolean isScreenRound,
+ IBinder token, Rect bounds, boolean isScreenRound,
@WindowConfiguration.WindowingMode int windowingMode) {
- return getWindowInsetsFromServerForDisplay(mContext.getDisplayId(), attrs, bounds,
+ return getWindowInsetsFromServerForDisplay(mContext.getDisplayId(), token, bounds,
isScreenRound, windowingMode);
}
@@ -113,22 +108,26 @@
* Retrieves WindowInsets for the given context and display, given the window bounds.
*
* @param displayId the ID of the logical display to calculate insets for
- * @param attrs the LayoutParams for the calling app
+ * @param token the token of Activity or WindowContext
* @param bounds the window bounds to calculate insets for
* @param isScreenRound if the display identified by displayId is round
* @param windowingMode the windowing mode of the window to calculate insets for
* @return WindowInsets calculated for the given window bounds, on the given display
*/
- private static WindowInsets getWindowInsetsFromServerForDisplay(int displayId,
- WindowManager.LayoutParams attrs, Rect bounds, boolean isScreenRound,
- int windowingMode) {
+ private static WindowInsets getWindowInsetsFromServerForDisplay(int displayId, IBinder token,
+ Rect bounds, boolean isScreenRound, int windowingMode) {
try {
final InsetsState insetsState = new InsetsState();
final boolean alwaysConsumeSystemBars = WindowManagerGlobal.getWindowManagerService()
- .getWindowInsets(attrs, displayId, insetsState);
- return insetsState.calculateInsets(bounds, null /* ignoringVisibilityState*/,
- isScreenRound, alwaysConsumeSystemBars, SOFT_INPUT_ADJUST_NOTHING, attrs.flags,
- SYSTEM_UI_FLAG_VISIBLE, attrs.type, windowingMode,
+ .getWindowInsets(displayId, token, insetsState);
+ final float overrideInvScale = CompatibilityInfo.getOverrideInvertedScale();
+ if (overrideInvScale != 1f) {
+ insetsState.scale(overrideInvScale);
+ }
+ return insetsState.calculateInsets(bounds, null /* ignoringVisibilityState */,
+ isScreenRound, alwaysConsumeSystemBars, SOFT_INPUT_ADJUST_NOTHING,
+ 0 /* flags */, SYSTEM_UI_FLAG_VISIBLE,
+ WindowManager.LayoutParams.INVALID_WINDOW_TYPE, windowingMode,
null /* typeSideMap */);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -149,7 +148,6 @@
Set<WindowMetrics> maxMetrics = new HashSet<>();
WindowInsets windowInsets;
DisplayInfo currentDisplayInfo;
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
for (int i = 0; i < possibleDisplayInfos.size(); i++) {
currentDisplayInfo = possibleDisplayInfos.get(i);
@@ -162,7 +160,7 @@
// Initialize insets based upon display rotation. Note any window-provided insets
// will not be set.
windowInsets = getWindowInsetsFromServerForDisplay(
- currentDisplayInfo.displayId, params,
+ currentDisplayInfo.displayId, null /* token */,
new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
currentDisplayInfo.getNaturalHeight()), isScreenRound,
WINDOWING_MODE_FULLSCREEN);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e031e5f..5f1a444 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3121,6 +3121,34 @@
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS"
android:protectionLevel="signature|role" />
+ <!-- Allows an application to manage date and time device policy. -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_TIME"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set device policies outside the current user
+ that are critical for securing data within the current user.
+ <p>Holding this permission allows the use of other held MANAGE_DEVICE_POLICY_*
+ permissions across all users on the device provided they are required for securing data
+ within the current user.-->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set device policies outside the current user
+ that are required for securing device ownership without accessing user data.
+ <p>Holding this permission allows the use of other held MANAGE_DEVICE_POLICY_*
+ permissions across all users on the device provided they do not grant access to user
+ data. -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set device policies outside the current user.
+ <p>Fuller form of {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS}
+ that removes the restriction on accessing user data.
+ <p>Holding this permission allows the use of any other held MANAGE_DEVICE_POLICY_*
+ permissions across all users on the device.-->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL"
+ android:protectionLevel="internal|role" />
+
<!-- @SystemApi @hide Allows an application to set a device owner on retail demo devices.-->
<permission android:name="android.permission.PROVISION_DEMO_DEVICE"
android:protectionLevel="signature|setup" />
@@ -5977,7 +6005,7 @@
<permission android:name="android.permission.START_VIEW_PERMISSION_USAGE"
android:label="@string/permlab_startViewPermissionUsage"
android:description="@string/permdesc_startViewPermissionUsage"
- android:protectionLevel="signature|installer" />
+ android:protectionLevel="signature|installer|module" />
<!--
@SystemApi
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2310367..aad88c4 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5360,6 +5360,11 @@
If given value is outside of this range, the option 0 (top) is assummed. -->
<integer name="config_letterboxDefaultPositionForTabletopModeReachability">0</integer>
+ <!-- Whether should ignore app requested orientation in response to an app
+ calling Activity#setRequestedOrientation. See
+ LetterboxUiController#shouldIgnoreRequestedOrientation for details. -->
+ <bool name="config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled">false</bool>
+
<!-- Whether displaying letterbox education is enabled for letterboxed fullscreen apps. -->
<bool name="config_letterboxIsEducationEnabled">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index af29b23..85bafb9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -945,7 +945,6 @@
<java-symbol type="string" name="serviceErased" />
<java-symbol type="string" name="serviceNotProvisioned" />
<java-symbol type="string" name="serviceRegistered" />
- <java-symbol type="string" name="setup_autofill" />
<java-symbol type="string" name="share" />
<java-symbol type="string" name="shareactionprovider_share_with" />
<java-symbol type="string" name="shareactionprovider_share_with_application" />
@@ -4430,6 +4429,7 @@
<java-symbol type="integer" name="config_letterboxDefaultPositionForVerticalReachability" />
<java-symbol type="integer" name="config_letterboxDefaultPositionForBookModeReachability" />
<java-symbol type="integer" name="config_letterboxDefaultPositionForTabletopModeReachability" />
+ <java-symbol type="bool" name="config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled" />
<java-symbol type="bool" name="config_letterboxIsEducationEnabled" />
<java-symbol type="dimen" name="config_letterboxDefaultMinAspectRatioForUnresizableApps" />
<java-symbol type="bool" name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled" />
diff --git a/core/tests/coretests/src/android/companion/virtual/OWNERS b/core/tests/coretests/src/android/companion/virtual/OWNERS
index 1a3e927..2e475a9 100644
--- a/core/tests/coretests/src/android/companion/virtual/OWNERS
+++ b/core/tests/coretests/src/android/companion/virtual/OWNERS
@@ -1,3 +1 @@
-set noparent
-
include /services/companion/java/com/android/server/companion/virtual/OWNERS
\ No newline at end of file
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index b910287..87fa63d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -17,6 +17,7 @@
package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
@@ -31,8 +32,10 @@
import android.os.Bundle;
import android.os.IBinder;
import android.util.ArrayMap;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentCreationParams;
import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentOperation;
import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
@@ -114,13 +117,14 @@
* @param activityIntent Intent to start the secondary Activity with.
* @param activityOptions ActivityOptions to start the secondary Activity with.
* @param windowingMode the windowing mode to set for the TaskFragments.
+ * @param splitAttributes the {@link SplitAttributes} to represent the split.
*/
void startActivityToSide(@NonNull WindowContainerTransaction wct,
@NonNull IBinder launchingFragmentToken, @NonNull Rect launchingFragmentBounds,
@NonNull Activity launchingActivity, @NonNull IBinder secondaryFragmentToken,
@NonNull Rect secondaryFragmentBounds, @NonNull Intent activityIntent,
@Nullable Bundle activityOptions, @NonNull SplitRule rule,
- @WindowingMode int windowingMode) {
+ @WindowingMode int windowingMode, @NonNull SplitAttributes splitAttributes) {
final IBinder ownerToken = launchingActivity.getActivityToken();
// Create or resize the launching TaskFragment.
@@ -131,6 +135,7 @@
createTaskFragmentAndReparentActivity(wct, launchingFragmentToken, ownerToken,
launchingFragmentBounds, windowingMode, launchingActivity);
}
+ updateAnimationParams(wct, launchingFragmentToken, splitAttributes);
// Create a TaskFragment for the secondary activity.
final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
@@ -144,6 +149,7 @@
.setPairedPrimaryFragmentToken(launchingFragmentToken)
.build();
createTaskFragment(wct, fragmentOptions);
+ updateAnimationParams(wct, secondaryFragmentToken, splitAttributes);
wct.startActivityInTaskFragment(secondaryFragmentToken, ownerToken, activityIntent,
activityOptions);
@@ -163,6 +169,7 @@
resizeTaskFragment(wct, fragmentToken, new Rect());
setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */);
updateWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED);
+ updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
}
/**
@@ -175,6 +182,7 @@
createTaskFragmentAndReparentActivity(
wct, fragmentToken, activity.getActivityToken(), new Rect(),
WINDOWING_MODE_UNDEFINED, activity);
+ updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
}
/**
@@ -270,6 +278,24 @@
wct.setWindowingMode(mFragmentInfos.get(fragmentToken).getToken(), windowingMode);
}
+ /**
+ * Updates the {@link TaskFragmentAnimationParams} for the given TaskFragment based on
+ * {@link SplitAttributes}.
+ */
+ void updateAnimationParams(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, @NonNull SplitAttributes splitAttributes) {
+ updateAnimationParams(wct, fragmentToken, createAnimationParamsOrDefault(splitAttributes));
+ }
+
+ void updateAnimationParams(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, @NonNull TaskFragmentAnimationParams animationParams) {
+ final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_SET_ANIMATION_PARAMS)
+ .setAnimationParams(animationParams)
+ .build();
+ wct.setTaskFragmentOperation(fragmentToken, operation);
+ }
+
void deleteTaskFragment(@NonNull WindowContainerTransaction wct,
@NonNull IBinder fragmentToken) {
if (!mFragmentInfos.containsKey(fragmentToken)) {
@@ -291,4 +317,14 @@
public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
mCallback.onTransactionReady(transaction);
}
+
+ private static TaskFragmentAnimationParams createAnimationParamsOrDefault(
+ @Nullable SplitAttributes splitAttributes) {
+ if (splitAttributes == null) {
+ return TaskFragmentAnimationParams.DEFAULT;
+ }
+ return new TaskFragmentAnimationParams.Builder()
+ .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
+ .build();
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index ce7d695..1e004a7 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -65,6 +65,7 @@
import android.util.Size;
import android.util.SparseArray;
import android.view.WindowMetrics;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentParentInfo;
import android.window.TaskFragmentTransaction;
@@ -1157,6 +1158,8 @@
taskId);
mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(),
activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED);
+ mPresenter.updateAnimationParams(wct, expandedContainer.getTaskFragmentToken(),
+ TaskFragmentAnimationParams.DEFAULT);
return expandedContainer;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 9db9f87..7b2af49 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -36,6 +36,7 @@
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowMetrics;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentCreationParams;
import android.window.WindowContainerTransaction;
@@ -176,7 +177,7 @@
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
splitAttributes);
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
- primaryActivity, primaryRectBounds, null);
+ primaryActivity, primaryRectBounds, splitAttributes, null /* containerToAvoid */);
// Create new empty task fragment
final int taskId = primaryContainer.getTaskId();
@@ -189,6 +190,7 @@
createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
primaryActivity.getActivityToken(), secondaryRectBounds,
windowingMode);
+ updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes);
// Set adjacent to each other so that the containers below will be invisible.
setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
@@ -222,7 +224,7 @@
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
splitAttributes);
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
- primaryActivity, primaryRectBounds, null);
+ primaryActivity, primaryRectBounds, splitAttributes, null /* containerToAvoid */);
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
splitAttributes);
@@ -236,7 +238,7 @@
containerToAvoid = curSecondaryContainer;
}
final TaskFragmentContainer secondaryContainer = prepareContainerForActivity(wct,
- secondaryActivity, secondaryRectBounds, containerToAvoid);
+ secondaryActivity, secondaryRectBounds, splitAttributes, containerToAvoid);
// Set adjacent to each other so that the containers below will be invisible.
setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
@@ -253,7 +255,8 @@
*/
private TaskFragmentContainer prepareContainerForActivity(
@NonNull WindowContainerTransaction wct, @NonNull Activity activity,
- @NonNull Rect bounds, @Nullable TaskFragmentContainer containerToAvoid) {
+ @NonNull Rect bounds, @NonNull SplitAttributes splitAttributes,
+ @Nullable TaskFragmentContainer containerToAvoid) {
TaskFragmentContainer container = mController.getContainerWithActivity(activity);
final int taskId = container != null ? container.getTaskId() : activity.getTaskId();
if (container == null || container == containerToAvoid) {
@@ -270,6 +273,7 @@
.getWindowingModeForSplitTaskFragment(bounds);
updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode);
}
+ updateAnimationParams(wct, container.getTaskFragmentToken(), splitAttributes);
return container;
}
@@ -314,7 +318,7 @@
rule, splitAttributes);
startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds,
- activityIntent, activityOptions, rule, windowingMode);
+ activityIntent, activityOptions, rule, windowingMode, splitAttributes);
if (isPlaceholder) {
// When placeholder is launched in split, we should keep the focus on the primary.
wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
@@ -365,6 +369,8 @@
primaryRectBounds);
updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode);
updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode);
+ updateAnimationParams(wct, primaryContainer.getTaskFragmentToken(), splitAttributes);
+ updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes);
}
private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
@@ -459,6 +465,24 @@
super.updateWindowingMode(wct, fragmentToken, windowingMode);
}
+ @Override
+ void updateAnimationParams(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, @NonNull TaskFragmentAnimationParams animationParams) {
+ final TaskFragmentContainer container = mController.getContainer(fragmentToken);
+ if (container == null) {
+ throw new IllegalStateException("Setting animation params for a task fragment that is"
+ + " not registered with controller.");
+ }
+
+ if (container.areLastRequestedAnimationParamsEqual(animationParams)) {
+ // Return early if the animation params were already requested
+ return;
+ }
+
+ container.setLastRequestAnimationParams(animationParams);
+ super.updateAnimationParams(wct, fragmentToken, animationParams);
+ }
+
/**
* Expands the split container if the current split bounds are smaller than the Activity or
* Intent that is added to the container.
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 6bfdfe7..076856c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -26,6 +26,7 @@
import android.os.Binder;
import android.os.IBinder;
import android.util.Size;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;
@@ -108,6 +109,13 @@
private int mLastRequestedWindowingMode = WINDOWING_MODE_UNDEFINED;
/**
+ * TaskFragmentAnimationParams that was requested last via
+ * {@link android.window.WindowContainerTransaction}.
+ */
+ @NonNull
+ private TaskFragmentAnimationParams mLastAnimationParams = TaskFragmentAnimationParams.DEFAULT;
+
+ /**
* When the TaskFragment has appeared in server, but is empty, we should remove the TaskFragment
* if it is still empty after the timeout.
*/
@@ -560,6 +568,21 @@
mLastRequestedWindowingMode = windowingModes;
}
+ /**
+ * Checks if last requested {@link TaskFragmentAnimationParams} are equal to the provided value.
+ */
+ boolean areLastRequestedAnimationParamsEqual(
+ @NonNull TaskFragmentAnimationParams animationParams) {
+ return mLastAnimationParams.equals(animationParams);
+ }
+
+ /**
+ * Updates the last requested {@link TaskFragmentAnimationParams}.
+ */
+ void setLastRequestAnimationParams(@NonNull TaskFragmentAnimationParams animationParams) {
+ mLastAnimationParams = animationParams;
+ }
+
/** Gets the parent leaf Task id. */
int getTaskId() {
return mTaskContainer.getTaskId();
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index 13a2c78..d189ae2 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -22,6 +22,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.window.extensions.embedding.SplitAttributes;
import org.junit.Before;
import org.junit.Test;
@@ -53,4 +54,15 @@
public void testGetActivityEmbeddingComponent() {
assertThat(mExtensions.getActivityEmbeddingComponent()).isNotNull();
}
+
+ @Test
+ public void testSplitAttributes_default() {
+ // Make sure the default value in the extensions aar.
+ final SplitAttributes splitAttributes = new SplitAttributes.Builder().build();
+ assertThat(splitAttributes.getLayoutDirection())
+ .isEqualTo(SplitAttributes.LayoutDirection.LOCALE);
+ assertThat(splitAttributes.getSplitType())
+ .isEqualTo(new SplitAttributes.SplitType.RatioSplitType(0.5f));
+ assertThat(splitAttributes.getAnimationBackgroundColor()).isEqualTo(0);
+ }
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index 6dae0a1..fcd4d62 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY;
@@ -60,12 +61,15 @@
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Color;
import android.graphics.Rect;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.util.Pair;
import android.util.Size;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentOperation;
import android.window.WindowContainerTransaction;
import androidx.test.core.app.ApplicationProvider;
@@ -163,7 +167,38 @@
WINDOWING_MODE_MULTI_WINDOW);
verify(mTransaction, never()).setWindowingMode(any(), anyInt());
+ }
+ @Test
+ public void testUpdateAnimationParams() {
+ final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID);
+
+ // Verify the default.
+ assertTrue(container.areLastRequestedAnimationParamsEqual(
+ TaskFragmentAnimationParams.DEFAULT));
+
+ final int bgColor = Color.GREEN;
+ final TaskFragmentAnimationParams animationParams =
+ new TaskFragmentAnimationParams.Builder()
+ .setAnimationBackgroundColor(bgColor)
+ .build();
+ mPresenter.updateAnimationParams(mTransaction, container.getTaskFragmentToken(),
+ animationParams);
+
+ final TaskFragmentOperation expectedOperation = new TaskFragmentOperation.Builder(
+ OP_TYPE_SET_ANIMATION_PARAMS)
+ .setAnimationParams(animationParams)
+ .build();
+ verify(mTransaction).setTaskFragmentOperation(container.getTaskFragmentToken(),
+ expectedOperation);
+ assertTrue(container.areLastRequestedAnimationParamsEqual(animationParams));
+
+ // No request to set the same animation params.
+ clearInvocations(mTransaction);
+ mPresenter.updateAnimationParams(mTransaction, container.getTaskFragmentToken(),
+ animationParams);
+
+ verify(mTransaction, never()).setTaskFragmentOperation(any(), any());
}
@Test
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 4978e04..84ab448 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/res/color/decor_caption_title_color.xml b/libs/WindowManager/Shell/res/color/decor_title_color.xml
similarity index 100%
rename from libs/WindowManager/Shell/res/color/decor_caption_title_color.xml
rename to libs/WindowManager/Shell/res/color/decor_title_color.xml
diff --git a/libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
similarity index 100%
rename from libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml
rename to libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
diff --git a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
similarity index 100%
rename from libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
rename to libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
diff --git a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
similarity index 96%
rename from libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
rename to libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
index 582a11c..8b4792a 100644
--- a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
@@ -20,7 +20,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
-android:background="@drawable/decor_caption_menu_background">
+android:background="@drawable/desktop_mode_decor_menu_background">
<Button
style="@style/CaptionButtonStyle"
android:id="@+id/fullscreen_button"
diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
similarity index 93%
rename from libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
rename to libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
index 51e634c..2a4cc02 100644
--- a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
@@ -16,10 +16,10 @@
-->
<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/caption"
+ android:id="@+id/desktop_mode_caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:background="@drawable/decor_caption_title">
+ android:background="@drawable/desktop_mode_decor_title">
<Button
style="@style/CaptionButtonStyle"
android:id="@+id/back_button"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 7aae633..d0aef20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -16,6 +16,12 @@
package com.android.wm.shell.common;
+import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_CANCEL;
+import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_END;
+import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_START;
+import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
+import static android.view.inputmethod.ImeTracker.TOKEN_NONE;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -26,6 +32,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
+import android.util.EventLog;
import android.util.Slog;
import android.util.SparseArray;
import android.view.IDisplayWindowInsetsController;
@@ -47,6 +54,7 @@
import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -274,29 +282,30 @@
}
if (hasImeSourceControl) {
- final Point lastSurfacePosition = mImeSourceControl != null
- ? mImeSourceControl.getSurfacePosition() : null;
- final boolean positionChanged =
- !imeSourceControl.getSurfacePosition().equals(lastSurfacePosition);
- final boolean leashChanged =
- !haveSameLeash(mImeSourceControl, imeSourceControl);
if (mAnimation != null) {
+ final Point lastSurfacePosition = hadImeSourceControl
+ ? mImeSourceControl.getSurfacePosition() : null;
+ final boolean positionChanged =
+ !imeSourceControl.getSurfacePosition().equals(lastSurfacePosition);
if (positionChanged) {
startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */);
}
} else {
- if (leashChanged) {
+ if (!haveSameLeash(mImeSourceControl, imeSourceControl)) {
applyVisibilityToLeash(imeSourceControl);
}
if (!mImeShowing) {
removeImeSurface();
}
- if (mImeSourceControl != null) {
- mImeSourceControl.release(SurfaceControl::release);
- }
}
- mImeSourceControl = imeSourceControl;
+ } else if (mAnimation != null) {
+ mAnimation.cancel();
}
+
+ if (hadImeSourceControl && mImeSourceControl != imeSourceControl) {
+ mImeSourceControl.release(SurfaceControl::release);
+ }
+ mImeSourceControl = imeSourceControl;
}
private void applyVisibilityToLeash(InsetsSourceControl imeSourceControl) {
@@ -469,6 +478,15 @@
ImeTracker.PHASE_WM_ANIMATION_RUNNING);
t.show(mImeSourceControl.getLeash());
}
+ if (DEBUG_IME_VISIBILITY) {
+ EventLog.writeEvent(IMF_IME_REMOTE_ANIM_START,
+ statsToken != null ? statsToken.getTag() : TOKEN_NONE,
+ mDisplayId, mAnimationDirection, alpha, startY , endY,
+ Objects.toString(mImeSourceControl.getLeash()),
+ Objects.toString(mImeSourceControl.getInsetsHint()),
+ Objects.toString(mImeSourceControl.getSurfacePosition()),
+ Objects.toString(mImeFrame));
+ }
t.apply();
mTransactionPool.release(t);
}
@@ -476,6 +494,11 @@
@Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
+ if (DEBUG_IME_VISIBILITY) {
+ EventLog.writeEvent(IMF_IME_REMOTE_ANIM_CANCEL,
+ statsToken != null ? statsToken.getTag() : TOKEN_NONE, mDisplayId,
+ Objects.toString(mImeSourceControl.getInsetsHint()));
+ }
}
@Override
@@ -499,6 +522,15 @@
ImeTracker.get().onCancelled(mStatsToken,
ImeTracker.PHASE_WM_ANIMATION_RUNNING);
}
+ if (DEBUG_IME_VISIBILITY) {
+ EventLog.writeEvent(IMF_IME_REMOTE_ANIM_END,
+ statsToken != null ? statsToken.getTag() : TOKEN_NONE,
+ mDisplayId, mAnimationDirection, endY,
+ Objects.toString(mImeSourceControl.getLeash()),
+ Objects.toString(mImeSourceControl.getInsetsHint()),
+ Objects.toString(mImeSourceControl.getSurfacePosition()),
+ Objects.toString(mImeFrame));
+ }
t.apply();
mTransactionPool.release(t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 039f0e3..fc2a828 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -374,8 +374,6 @@
// If this is a transferred starting window, we want it immediately visible.
&& (change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) == 0) {
t.setAlpha(leash, 0.f);
- // fix alpha in finish transaction in case the animator itself no-ops.
- finishT.setAlpha(leash, 1.f);
}
} else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
finishT.hide(leash);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index b500f5f..b430157 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -282,7 +282,7 @@
public boolean onTouch(View v, MotionEvent e) {
boolean isDrag = false;
int id = v.getId();
- if (id != R.id.caption_handle && id != R.id.caption) {
+ if (id != R.id.caption_handle && id != R.id.desktop_mode_caption) {
return false;
}
if (id == R.id.caption_handle) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 467f374..9c2beb9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -132,7 +132,7 @@
mRelayoutParams.reset();
mRelayoutParams.mRunningTaskInfo = taskInfo;
- mRelayoutParams.mLayoutResId = R.layout.caption_window_decoration;
+ mRelayoutParams.mLayoutResId = R.layout.desktop_mode_window_decor;
mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
mRelayoutParams.mCaptionWidthId = R.dimen.freeform_decor_caption_width;
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
@@ -212,7 +212,7 @@
* Sets up listeners when a new root view is created.
*/
private void setupRootView() {
- View caption = mResult.mRootView.findViewById(R.id.caption);
+ View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
caption.setOnTouchListener(mOnCaptionTouchListener);
View close = caption.findViewById(R.id.close_window);
close.setOnClickListener(mOnCaptionButtonClickListener);
@@ -243,7 +243,7 @@
*/
private void setCaptionVisibility(boolean visible) {
int v = visible ? View.VISIBLE : View.GONE;
- View captionView = mResult.mRootView.findViewById(R.id.caption);
+ View captionView = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
captionView.setVisibility(v);
if (!visible) closeHandleMenu();
}
@@ -265,7 +265,7 @@
*/
void setButtonVisibility(boolean visible) {
int visibility = visible ? View.VISIBLE : View.GONE;
- View caption = mResult.mRootView.findViewById(R.id.caption);
+ View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
View back = caption.findViewById(R.id.back_button);
View close = caption.findViewById(R.id.close_window);
back.setVisibility(visibility);
@@ -304,7 +304,7 @@
int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
String namePrefix = "Caption Menu";
- mHandleMenu = addWindow(R.layout.caption_handle_menu, namePrefix, t,
+ mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t,
x - mResult.mDecorContainerOffsetX, y - mResult.mDecorContainerOffsetY,
width, height);
mSyncQueue.runInSync(transaction -> {
@@ -336,7 +336,7 @@
*/
void closeHandleMenuIfNeeded(MotionEvent ev) {
if (isHandleMenuActive()) {
- if (!checkEventInCaptionView(ev, R.id.caption)) {
+ if (!checkEventInCaptionView(ev, R.id.desktop_mode_caption)) {
closeHandleMenu();
}
}
@@ -389,7 +389,7 @@
*/
void checkClickEvent(MotionEvent ev) {
if (mResult.mRootView == null) return;
- View caption = mResult.mRootView.findViewById(R.id.caption);
+ View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
PointF inputPoint = offsetCaptionLocation(ev);
if (!isHandleMenuActive()) {
View handle = caption.findViewById(R.id.caption_handle);
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
index 04b1bdd..cd0930c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
@@ -26,6 +26,9 @@
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.navBarLayerIsVisibleAtEnd
+import com.android.server.wm.flicker.navBarLayerPositionAtEnd
+import org.junit.Assume
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -91,6 +94,14 @@
flicker.assertLayersEnd { this.isVisible(testApp) }
}
+ @Postsubmit
+ @Test
+ fun navBarLayerIsVisibleAtEnd() = flicker.navBarLayerIsVisibleAtEnd()
+
+ @Postsubmit
+ @Test
+ fun navBarLayerPositionAtEnd() = flicker.navBarLayerPositionAtEnd()
+
/** {@inheritDoc} */
@FlakyTest
@Test
@@ -98,19 +109,28 @@
super.visibleLayersShownMoreThanOneConsecutiveEntry()
/** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
+ @Postsubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerIsVisibleAtStartAndEnd() {
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ super.navBarLayerIsVisibleAtStartAndEnd()
+ }
/** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
+ @Postsubmit
@Test
- override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() {
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ super.navBarLayerPositionAtStartAndEnd()
+ }
/** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
+ @Postsubmit
@Test
- override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() {
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ super.navBarWindowIsAlwaysVisible()
+ }
/** {@inheritDoc} */
@FlakyTest(bugId = 242088970)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 22df362..ffb1a4d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -22,6 +22,8 @@
import static android.view.WindowInsets.Type.ime;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
@@ -124,6 +126,15 @@
verify(mT).show(any());
}
+ @Test
+ public void insetsControlChanged_updateImeSourceControl() {
+ mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
+ assertNotNull(mPerDisplay.mImeSourceControl);
+
+ mPerDisplay.insetsControlChanged(new InsetsState(), new InsetsSourceControl[]{});
+ assertNull(mPerDisplay.mImeSourceControl);
+ }
+
private InsetsSourceControl[] insetsSourceControl() {
return new InsetsSourceControl[]{
new InsetsSourceControl(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index d06fb55..7ec4e21 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -30,10 +30,13 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static java.lang.Integer.MAX_VALUE;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
import android.test.suitebuilder.annotation.SmallTest;
@@ -135,12 +138,12 @@
@Test
public void instantiatePipController_addInitCallback() {
- verify(mShellInit, times(1)).addInitCallback(any(), any());
+ verify(mShellInit, times(1)).addInitCallback(any(), eq(mPipController));
}
@Test
public void instantiateController_registerDumpCallback() {
- verify(mMockShellCommandHandler, times(1)).addDumpCallback(any(), any());
+ verify(mMockShellCommandHandler, times(1)).addDumpCallback(any(), eq(mPipController));
}
@Test
@@ -156,7 +159,7 @@
@Test
public void instantiatePipController_registerExternalInterface() {
verify(mShellController, times(1)).addExternalInterface(
- eq(ShellSharedConstants.KEY_EXTRA_SHELL_PIP), any(), any());
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_PIP), any(), eq(mPipController));
}
@Test
@@ -252,6 +255,10 @@
final int displayId = 1;
final Rect bounds = new Rect(0, 0, 10, 10);
when(mMockPipBoundsAlgorithm.getDefaultBounds()).thenReturn(bounds);
+ when(mMockPipBoundsState.getBounds()).thenReturn(bounds);
+ when(mMockPipBoundsState.getMinSize()).thenReturn(new Point(1, 1));
+ when(mMockPipBoundsState.getMaxSize()).thenReturn(new Point(MAX_VALUE, MAX_VALUE));
+ when(mMockPipBoundsState.getBounds()).thenReturn(bounds);
when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
when(mMockPipBoundsState.getDisplayLayout()).thenReturn(mMockDisplayLayout1);
when(mMockDisplayController.getDisplayLayout(displayId)).thenReturn(mMockDisplayLayout2);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index fbc50c6..8d92d08 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -34,6 +34,7 @@
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.ShellExecutor;
@@ -61,10 +62,9 @@
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
- private ShellExecutor mExecutor;
- @Mock
private Context mTestUserContext;
+ private TestShellExecutor mExecutor;
private ShellController mController;
private TestConfigurationChangeListener mConfigChangeListener;
private TestKeyguardChangeListener mKeyguardChangeListener;
@@ -77,6 +77,7 @@
mKeyguardChangeListener = new TestKeyguardChangeListener();
mConfigChangeListener = new TestConfigurationChangeListener();
mUserChangeListener = new TestUserChangeListener();
+ mExecutor = new TestShellExecutor();
mController = new ShellController(mShellInit, mShellCommandHandler, mExecutor);
mController.onConfigurationChanged(getConfigurationCopy());
}
@@ -104,6 +105,7 @@
Bundle b = new Bundle();
mController.asShell().createExternalInterfaces(b);
+ mExecutor.flushAll();
assertTrue(b.getIBinder(EXTRA_TEST_BINDER) == callback);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index c764741..595c3b4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -936,7 +936,7 @@
TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
RunningTaskInfo taskInfo) {
final TransitionInfo.Change change =
- new TransitionInfo.Change(null /* token */, null /* leash */);
+ new TransitionInfo.Change(null /* token */, createMockSurface(true));
change.setMode(mode);
change.setTaskInfo(taskInfo);
mInfo.addChange(change);
@@ -961,7 +961,7 @@
final TransitionInfo.Change mChange;
ChangeBuilder(@WindowManager.TransitionType int mode) {
- mChange = new TransitionInfo.Change(null /* token */, null /* leash */);
+ mChange = new TransitionInfo.Change(null /* token */, createMockSurface(true));
mChange.setMode(mode);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
index a5e3a2e..3550721 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
@@ -205,28 +205,32 @@
"testEventReceiversOnMultipleDisplays", /*width=*/ 400, /*height=*/ 400,
/*densityDpi=*/ 320, surfaceView.getHolder().getSurface(),
DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
- int secondaryDisplayId = secondaryDisplay.getDisplay().getDisplayId();
+ try {
+ int secondaryDisplayId = secondaryDisplay.getDisplay().getDisplayId();
- final int taskId = 1;
- final ActivityManager.RunningTaskInfo taskInfo =
- createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
- final ActivityManager.RunningTaskInfo secondTaskInfo =
- createTaskInfo(taskId + 1, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
- final ActivityManager.RunningTaskInfo thirdTaskInfo =
- createTaskInfo(taskId + 2, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
+ final int taskId = 1;
+ final ActivityManager.RunningTaskInfo taskInfo =
+ createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
+ final ActivityManager.RunningTaskInfo secondTaskInfo =
+ createTaskInfo(taskId + 1, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
+ final ActivityManager.RunningTaskInfo thirdTaskInfo =
+ createTaskInfo(taskId + 2, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
- SurfaceControl surfaceControl = mock(SurfaceControl.class);
- final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
- final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
- mDesktopModeWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT,
- finishT);
- mDesktopModeWindowDecorViewModel.onTaskOpening(secondTaskInfo, surfaceControl,
- startT, finishT);
- mDesktopModeWindowDecorViewModel.onTaskOpening(thirdTaskInfo, surfaceControl,
- startT, finishT);
- mDesktopModeWindowDecorViewModel.destroyWindowDecoration(thirdTaskInfo);
- mDesktopModeWindowDecorViewModel.destroyWindowDecoration(taskInfo);
+ mDesktopModeWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT,
+ finishT);
+ mDesktopModeWindowDecorViewModel.onTaskOpening(secondTaskInfo, surfaceControl,
+ startT, finishT);
+ mDesktopModeWindowDecorViewModel.onTaskOpening(thirdTaskInfo, surfaceControl,
+ startT, finishT);
+ mDesktopModeWindowDecorViewModel.destroyWindowDecoration(thirdTaskInfo);
+ mDesktopModeWindowDecorViewModel.destroyWindowDecoration(taskInfo);
+ } finally {
+ secondaryDisplay.release();
+ }
});
verify(mMockInputMonitorFactory, times(2)).create(any(), any());
verify(mInputMonitor, times(1)).dispose();
@@ -239,7 +243,7 @@
r.run();
latch.countDown();
});
- latch.await(20, TimeUnit.MILLISECONDS);
+ latch.await(1, TimeUnit.SECONDS);
}
private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index dd9ab98..ec4f17f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -48,6 +48,7 @@
import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowManager.LayoutParams;
+import android.window.TaskConstants;
import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
@@ -232,7 +233,8 @@
verify(mMockSurfaceControlStartT)
.setColor(taskBackgroundSurface, new float[] {1.f, 1.f, 0.f});
verify(mMockSurfaceControlStartT).setShadowRadius(taskBackgroundSurface, 10);
- verify(mMockSurfaceControlStartT).setLayer(taskBackgroundSurface, -1);
+ verify(mMockSurfaceControlStartT).setLayer(taskBackgroundSurface,
+ TaskConstants.TASK_CHILD_LAYER_TASK_BACKGROUND);
verify(mMockSurfaceControlStartT).show(taskBackgroundSurface);
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
@@ -560,7 +562,8 @@
int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
String name = "Test Window";
WindowDecoration.AdditionalWindow additionalWindow =
- addWindow(R.layout.caption_handle_menu, name, mMockSurfaceControlAddWindowT,
+ addWindow(R.layout.desktop_mode_decor_handle_menu, name,
+ mMockSurfaceControlAddWindowT,
x - mRelayoutResult.mDecorContainerOffsetX,
y - mRelayoutResult.mDecorContainerOffsetY,
width, height);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index a84c42af..761edf6 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -5855,6 +5855,117 @@
}
}
+ // Each listener corresponds to a unique callback stub because each listener can subscribe to
+ // different AudioAttributes.
+ private final ConcurrentHashMap<OnDevicesForAttributesChangedListener,
+ IDevicesForAttributesCallbackStub> mDevicesForAttributesListenerToStub =
+ new ConcurrentHashMap<>();
+
+ private static final class IDevicesForAttributesCallbackStub
+ extends IDevicesForAttributesCallback.Stub {
+ ListenerInfo<OnDevicesForAttributesChangedListener> mInfo;
+
+ IDevicesForAttributesCallbackStub(@NonNull OnDevicesForAttributesChangedListener listener,
+ @NonNull Executor executor) {
+ mInfo = new ListenerInfo<>(listener, executor);
+ }
+
+ public void register(boolean register, AudioAttributes attributes) {
+ try {
+ if (register) {
+ getService().addOnDevicesForAttributesChangedListener(attributes, this);
+ } else {
+ getService().removeOnDevicesForAttributesChangedListener(this);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void onDevicesForAttributesChanged(AudioAttributes attributes, boolean forVolume,
+ List<AudioDeviceAttributes> devices) {
+ // forVolume is ignored. The case where it is `true` is not handled.
+ mInfo.mExecutor.execute(() ->
+ mInfo.mListener.onDevicesForAttributesChanged(
+ attributes, devices));
+ }
+ }
+
+ /**
+ * @hide
+ * Interface to be notified of when routing changes for the registered audio attributes.
+ */
+ @SystemApi
+ public interface OnDevicesForAttributesChangedListener {
+ /**
+ * Called on the listener to indicate that the audio devices for the given audio
+ * attributes have changed.
+ * @param attributes the {@link AudioAttributes} whose routing changed
+ * @param devices a list of newly routed audio devices
+ */
+ void onDevicesForAttributesChanged(@NonNull AudioAttributes attributes,
+ @NonNull List<AudioDeviceAttributes> devices);
+ }
+
+ /**
+ * @hide
+ * Adds a listener for being notified of routing changes for the given {@link AudioAttributes}.
+ * @param attributes the {@link AudioAttributes} to listen for routing changes
+ * @param executor
+ * @param listener
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING,
+ android.Manifest.permission.QUERY_AUDIO_STATE
+ })
+ public void addOnDevicesForAttributesChangedListener(@NonNull AudioAttributes attributes,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnDevicesForAttributesChangedListener listener) {
+ Objects.requireNonNull(attributes);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+
+ synchronized (mDevicesForAttributesListenerToStub) {
+ IDevicesForAttributesCallbackStub callbackStub =
+ mDevicesForAttributesListenerToStub.get(listener);
+
+ if (callbackStub == null) {
+ callbackStub = new IDevicesForAttributesCallbackStub(listener, executor);
+ mDevicesForAttributesListenerToStub.put(listener, callbackStub);
+ }
+
+ callbackStub.register(true, attributes);
+ }
+ }
+
+ /**
+ * @hide
+ * Removes a previously registered listener for being notified of routing changes for the given
+ * {@link AudioAttributes}.
+ * @param listener
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING,
+ android.Manifest.permission.QUERY_AUDIO_STATE
+ })
+ public void removeOnDevicesForAttributesChangedListener(
+ @NonNull OnDevicesForAttributesChangedListener listener) {
+ Objects.requireNonNull(listener);
+
+ synchronized (mDevicesForAttributesListenerToStub) {
+ IDevicesForAttributesCallbackStub callbackStub =
+ mDevicesForAttributesListenerToStub.get(listener);
+ if (callbackStub != null) {
+ callbackStub.register(false, null /* attributes */);
+ }
+
+ mDevicesForAttributesListenerToStub.remove(listener);
+ }
+ }
+
/**
* Get the audio devices that would be used for the routing of the given audio attributes.
* These are the devices anticipated to play sound from an {@link AudioTrack} created with
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 0de367d..5ee32d6 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -36,6 +36,7 @@
import android.media.ICapturePresetDevicesRoleDispatcher;
import android.media.ICommunicationDeviceDispatcher;
import android.media.IDeviceVolumeBehaviorDispatcher;
+import android.media.IDevicesForAttributesCallback;
import android.media.IMuteAwaitConnectionCallback;
import android.media.IPlaybackConfigDispatcher;
import android.media.IPreferredMixerAttributesDispatcher;
@@ -356,6 +357,12 @@
List<AudioDeviceAttributes> getDevicesForAttributesUnprotected(in AudioAttributes attributes);
+ void addOnDevicesForAttributesChangedListener(in AudioAttributes attributes,
+ in IDevicesForAttributesCallback callback);
+
+ oneway void removeOnDevicesForAttributesChangedListener(
+ in IDevicesForAttributesCallback callback);
+
int setAllowedCapturePolicy(in int capturePolicy);
int getAllowedCapturePolicy();
diff --git a/media/java/android/media/IDevicesForAttributesCallback.aidl b/media/java/android/media/IDevicesForAttributesCallback.aidl
new file mode 100644
index 0000000..489ecf6
--- /dev/null
+++ b/media/java/android/media/IDevicesForAttributesCallback.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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.media;
+
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+
+/**
+ * AIDL for AudioService to signal updates of audio devices routing for attributes.
+ *
+ * {@hide}
+ */
+oneway interface IDevicesForAttributesCallback {
+
+ void onDevicesForAttributesChanged(in AudioAttributes attributes, boolean forVolume,
+ in List<AudioDeviceAttributes> devices);
+
+}
+
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index 32a2ad3..9325999 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -431,17 +431,15 @@
* @see Image#close
*/
public Image dequeueInputImage() {
- synchronized (mCloseLock) {
- if (mDequeuedImages.size() >= mMaxImages) {
- throw new IllegalStateException(
- "Already dequeued max number of Images " + mMaxImages);
- }
- WriterSurfaceImage newImage = new WriterSurfaceImage(this);
- nativeDequeueInputImage(mNativeContext, newImage);
- mDequeuedImages.add(newImage);
- newImage.mIsImageValid = true;
- return newImage;
+ if (mDequeuedImages.size() >= mMaxImages) {
+ throw new IllegalStateException(
+ "Already dequeued max number of Images " + mMaxImages);
}
+ WriterSurfaceImage newImage = new WriterSurfaceImage(this);
+ nativeDequeueInputImage(mNativeContext, newImage);
+ mDequeuedImages.add(newImage);
+ newImage.mIsImageValid = true;
+ return newImage;
}
/**
@@ -500,52 +498,50 @@
throw new IllegalArgumentException("image shouldn't be null");
}
- synchronized (mCloseLock) {
- boolean ownedByMe = isImageOwnedByMe(image);
- if (ownedByMe && !(((WriterSurfaceImage) image).mIsImageValid)) {
- throw new IllegalStateException("Image from ImageWriter is invalid");
+ boolean ownedByMe = isImageOwnedByMe(image);
+ if (ownedByMe && !(((WriterSurfaceImage) image).mIsImageValid)) {
+ throw new IllegalStateException("Image from ImageWriter is invalid");
+ }
+
+ // For images from other components that have non-null owner, need to detach first,
+ // then attach. Images without owners must already be attachable.
+ if (!ownedByMe) {
+ if ((image.getOwner() instanceof ImageReader)) {
+ ImageReader prevOwner = (ImageReader) image.getOwner();
+
+ prevOwner.detachImage(image);
+ } else if (image.getOwner() != null) {
+ throw new IllegalArgumentException(
+ "Only images from ImageReader can be queued to"
+ + " ImageWriter, other image source is not supported yet!");
}
- // For images from other components that have non-null owner, need to detach first,
- // then attach. Images without owners must already be attachable.
- if (!ownedByMe) {
- if ((image.getOwner() instanceof ImageReader)) {
- ImageReader prevOwner = (ImageReader) image.getOwner();
+ attachAndQueueInputImage(image);
+ // This clears the native reference held by the original owner.
+ // When this Image is detached later by this ImageWriter, the
+ // native memory won't be leaked.
+ image.close();
+ return;
+ }
- prevOwner.detachImage(image);
- } else if (image.getOwner() != null) {
- throw new IllegalArgumentException(
- "Only images from ImageReader can be queued to"
- + " ImageWriter, other image source is not supported yet!");
- }
+ Rect crop = image.getCropRect();
+ nativeQueueInputImage(mNativeContext, image, image.getTimestamp(), image.getDataSpace(),
+ crop.left, crop.top, crop.right, crop.bottom, image.getTransform(),
+ image.getScalingMode());
- attachAndQueueInputImage(image);
- // This clears the native reference held by the original owner.
- // When this Image is detached later by this ImageWriter, the
- // native memory won't be leaked.
- image.close();
- return;
- }
-
- Rect crop = image.getCropRect();
- nativeQueueInputImage(mNativeContext, image, image.getTimestamp(), image.getDataSpace(),
- crop.left, crop.top, crop.right, crop.bottom, image.getTransform(),
- image.getScalingMode());
-
- /**
- * Only remove and cleanup the Images that are owned by this
- * ImageWriter. Images detached from other owners are only temporarily
- * owned by this ImageWriter and will be detached immediately after they
- * are released by downstream consumers, so there is no need to keep
- * track of them in mDequeuedImages.
- */
- if (ownedByMe) {
- mDequeuedImages.remove(image);
- // Do not call close here, as close is essentially cancel image.
- WriterSurfaceImage wi = (WriterSurfaceImage) image;
- wi.clearSurfacePlanes();
- wi.mIsImageValid = false;
- }
+ /**
+ * Only remove and cleanup the Images that are owned by this
+ * ImageWriter. Images detached from other owners are only temporarily
+ * owned by this ImageWriter and will be detached immediately after they
+ * are released by downstream consumers, so there is no need to keep
+ * track of them in mDequeuedImages.
+ */
+ if (ownedByMe) {
+ mDequeuedImages.remove(image);
+ // Do not call close here, as close is essentially cancel image.
+ WriterSurfaceImage wi = (WriterSurfaceImage) image;
+ wi.clearSurfacePlanes();
+ wi.mIsImageValid = false;
}
}
@@ -681,11 +677,11 @@
*/
@Override
public void close() {
+ setOnImageReleasedListener(null, null);
synchronized (mCloseLock) {
if (!mIsWriterValid) {
return;
}
- setOnImageReleasedListener(null, null);
for (Image image : mDequeuedImages) {
image.close();
}
@@ -817,14 +813,12 @@
}
final Handler handler;
- final boolean isWriterValid;
synchronized (iw.mListenerLock) {
handler = iw.mListenerHandler;
}
- synchronized (iw.mCloseLock) {
- isWriterValid = iw.mIsWriterValid;
- }
- if (handler != null && isWriterValid) {
+
+ if (handler != null) {
+ // The ListenerHandler will take care of ensuring that the parent ImageWriter is valid
handler.sendEmptyMessage(0);
}
}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 58078cf..aacea3d 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -609,6 +609,7 @@
jobject obj = env->NewObject(eventClazz, eventInit, tableId, version, sectionNum, dataLength);
env->SetObjectArrayElement(arr, size, obj);
env->DeleteLocalRef(obj);
+ env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::getMediaEvent(jobjectArray &arr, const int size,
@@ -639,6 +640,7 @@
audioDescriptor = env->NewObject(adClazz, adInit, adFade, adPan, versionTextTag,
adGainCenter, adGainFront, adGainSurround);
+ env->DeleteLocalRef(adClazz);
}
jlong dataLength = mediaEvent.dataLength;
@@ -685,6 +687,7 @@
env->DeleteLocalRef(audioDescriptor);
}
env->DeleteLocalRef(obj);
+ env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::getPesEvent(jobjectArray &arr, const int size,
@@ -701,6 +704,7 @@
jobject obj = env->NewObject(eventClazz, eventInit, streamId, dataLength, mpuSequenceNumber);
env->SetObjectArrayElement(arr, size, obj);
env->DeleteLocalRef(obj);
+ env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::getTsRecordEvent(jobjectArray &arr, const int size,
@@ -741,6 +745,7 @@
env->NewObject(eventClazz, eventInit, jpid, ts, sc, byteNumber, pts, firstMbInSlice);
env->SetObjectArrayElement(arr, size, obj);
env->DeleteLocalRef(obj);
+ env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::getMmtpRecordEvent(jobjectArray &arr, const int size,
@@ -762,6 +767,7 @@
mpuSequenceNumber, pts, firstMbInSlice, tsIndexMask);
env->SetObjectArrayElement(arr, size, obj);
env->DeleteLocalRef(obj);
+ env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::getDownloadEvent(jobjectArray &arr, const int size,
@@ -782,6 +788,7 @@
itemFragmentIndex, lastItemFragmentIndex, dataLength);
env->SetObjectArrayElement(arr, size, obj);
env->DeleteLocalRef(obj);
+ env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::getIpPayloadEvent(jobjectArray &arr, const int size,
@@ -795,6 +802,7 @@
jobject obj = env->NewObject(eventClazz, eventInit, dataLength);
env->SetObjectArrayElement(arr, size, obj);
env->DeleteLocalRef(obj);
+ env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::getTemiEvent(jobjectArray &arr, const int size,
@@ -815,6 +823,7 @@
env->SetObjectArrayElement(arr, size, obj);
env->DeleteLocalRef(array);
env->DeleteLocalRef(obj);
+ env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::getScramblingStatusEvent(jobjectArray &arr, const int size,
@@ -829,6 +838,7 @@
jobject obj = env->NewObject(eventClazz, eventInit, scramblingStatus);
env->SetObjectArrayElement(arr, size, obj);
env->DeleteLocalRef(obj);
+ env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::getIpCidChangeEvent(jobjectArray &arr, const int size,
@@ -842,6 +852,7 @@
jobject obj = env->NewObject(eventClazz, eventInit, cid);
env->SetObjectArrayElement(arr, size, obj);
env->DeleteLocalRef(obj);
+ env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::getRestartEvent(jobjectArray &arr, const int size,
@@ -854,6 +865,7 @@
jobject obj = env->NewObject(eventClazz, eventInit, startId);
env->SetObjectArrayElement(arr, size, obj);
env->DeleteLocalRef(obj);
+ env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::onFilterEvent(const vector<DemuxFilterEvent> &events) {
@@ -952,6 +964,7 @@
"Filter object has been freed. Ignoring callback.");
}
env->DeleteLocalRef(array);
+ env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) {
@@ -1058,6 +1071,7 @@
executeOnScanMessage(env, clazz, frontend, type, message);
env->DeleteLocalRef(frontend);
}
+ env->DeleteLocalRef(clazz);
}
void FrontendClientCallbackImpl::executeOnScanMessage(
@@ -1184,6 +1198,7 @@
"Atsc3PlpInfo;)V"),
array);
env->DeleteLocalRef(array);
+ env->DeleteLocalRef(plpClazz);
break;
}
case FrontendScanMessageType::MODULATION: {
@@ -2195,6 +2210,7 @@
static_cast<long>(s.get<FrontendStatus::Tag::innerFec>()));
env->SetObjectField(statusObj, field, newLongObj);
env->DeleteLocalRef(newLongObj);
+ env->DeleteLocalRef(longClazz);
break;
}
case FrontendStatus::Tag::modulationStatus: {
@@ -2360,6 +2376,7 @@
env->SetObjectField(statusObj, field, valObj);
env->DeleteLocalRef(valObj);
+ env->DeleteLocalRef(plpClazz);
break;
}
case FrontendStatus::Tag::modulations: {
@@ -2770,6 +2787,7 @@
env->SetObjectField(statusObj, field, valObj);
env->DeleteLocalRef(valObj);
+ env->DeleteLocalRef(plpClazz);
break;
}
}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
index 244b367..51fc7ed 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
@@ -18,6 +18,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settingslib.widget">
- <uses-sdk android:minSdkVersion="29" />
+ <uses-sdk android:minSdkVersion="21" />
</manifest>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml
index c799b99..02f69f6 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml
@@ -29,6 +29,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?android:attr/actionBarTheme" />
+ <androidx.appcompat.widget.Toolbar
+ android:id="@+id/support_action_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:theme="?android:attr/actionBarTheme"
+ android:visibility="gone" />
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
new file mode 100644
index 0000000..dcc6e5a
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.collapsingtoolbar;
+
+import android.app.ActionBar;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toolbar;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.android.settingslib.utils.BuildCompatUtils;
+import com.android.settingslib.widget.R;
+
+import com.google.android.material.appbar.AppBarLayout;
+import com.google.android.material.appbar.CollapsingToolbarLayout;
+import com.google.android.material.color.DynamicColors;
+
+/**
+ * A base Activity that has a collapsing toolbar layout is used for the activities intending to
+ * enable the collapsing toolbar function.
+ */
+public class CollapsingToolbarAppCompatActivity extends AppCompatActivity {
+
+ private class DelegateCallback implements CollapsingToolbarDelegate.HostCallback {
+ @Nullable
+ @Override
+ public ActionBar setActionBar(Toolbar toolbar) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public androidx.appcompat.app.ActionBar setActionBar(
+ androidx.appcompat.widget.Toolbar toolbar) {
+ CollapsingToolbarAppCompatActivity.super.setSupportActionBar(toolbar);
+ return CollapsingToolbarAppCompatActivity.super.getSupportActionBar();
+ }
+
+ @Override
+ public void setOuterTitle(CharSequence title) {
+ CollapsingToolbarAppCompatActivity.super.setTitle(title);
+ }
+ }
+
+ private CollapsingToolbarDelegate mToolbardelegate;
+
+ private int mCustomizeLayoutResId = 0;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (BuildCompatUtils.isAtLeastS()) {
+ DynamicColors.applyToActivityIfAvailable(this);
+ }
+ setTheme(R.style.Theme_SubSettingsBase);
+
+ if (mCustomizeLayoutResId > 0 && !BuildCompatUtils.isAtLeastS()) {
+ super.setContentView(mCustomizeLayoutResId);
+ return;
+ }
+
+ View view = getToolbarDelegate().onCreateView(getLayoutInflater(), null, this);
+ super.setContentView(view);
+ }
+
+ @Override
+ public void setContentView(int layoutResID) {
+ final ViewGroup parent = (mToolbardelegate == null) ? findViewById(R.id.content_frame)
+ : mToolbardelegate.getContentFrameLayout();
+ if (parent != null) {
+ parent.removeAllViews();
+ }
+ LayoutInflater.from(this).inflate(layoutResID, parent);
+ }
+
+ @Override
+ public void setContentView(View view) {
+ final ViewGroup parent = (mToolbardelegate == null) ? findViewById(R.id.content_frame)
+ : mToolbardelegate.getContentFrameLayout();
+ if (parent != null) {
+ parent.addView(view);
+ }
+ }
+
+ @Override
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ final ViewGroup parent = (mToolbardelegate == null) ? findViewById(R.id.content_frame)
+ : mToolbardelegate.getContentFrameLayout();
+ if (parent != null) {
+ parent.addView(view, params);
+ }
+ }
+
+ /**
+ * This method allows an activity to replace the default layout with a customize layout. Notice
+ * that it will no longer apply the features being provided by this class when this method
+ * gets called.
+ */
+ protected void setCustomizeContentView(int layoutResId) {
+ mCustomizeLayoutResId = layoutResId;
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ getToolbarDelegate().setTitle(title);
+ }
+
+ @Override
+ public void setTitle(int titleId) {
+ setTitle(getText(titleId));
+ }
+
+ @Override
+ public boolean onSupportNavigateUp() {
+ if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
+ getSupportFragmentManager().popBackStackImmediate();
+ }
+
+ // Closes the activity if there is no fragment inside the stack. Otherwise the activity will
+ // has a blank screen since there is no any fragment.
+ if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
+ finishAfterTransition();
+ }
+ return true;
+ }
+
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+
+ // Closes the activity if there is no fragment inside the stack. Otherwise the activity will
+ // has a blank screen since there is no any fragment. onBackPressed() in Activity.java only
+ // handles popBackStackImmediate(). This will close activity to avoid a blank screen.
+ if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
+ finishAfterTransition();
+ }
+ }
+
+ /**
+ * Returns an instance of collapsing toolbar.
+ */
+ @Nullable
+ public CollapsingToolbarLayout getCollapsingToolbarLayout() {
+ return getToolbarDelegate().getCollapsingToolbarLayout();
+ }
+
+ /**
+ * Return an instance of app bar.
+ */
+ @Nullable
+ public AppBarLayout getAppBarLayout() {
+ return getToolbarDelegate().getAppBarLayout();
+ }
+
+ private CollapsingToolbarDelegate getToolbarDelegate() {
+ if (mToolbardelegate == null) {
+ mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback());
+ }
+ return mToolbardelegate;
+ }
+}
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 8c8b478..01f92c4 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -117,12 +117,30 @@
@Override
public boolean onNavigateUp() {
- if (!super.onNavigateUp()) {
+ if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
+ getSupportFragmentManager().popBackStackImmediate();
+ }
+
+ // Closes the activity if there is no fragment inside the stack. Otherwise the activity will
+ // has a blank screen since there is no any fragment.
+ if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
finishAfterTransition();
}
return true;
}
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+
+ // Closes the activity if there is no fragment inside the stack. Otherwise the activity will
+ // has a blank screen since there is no any fragment. onBackPressed() in Activity.java only
+ // handles popBackStackImmediate(). This will close activity to avoid a blank screen.
+ if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
+ finishAfterTransition();
+ }
+ }
+
/**
* Returns an instance of collapsing toolbar.
*/
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
index 01698b7..1c2288a 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
@@ -19,9 +19,11 @@
import static android.text.Layout.HYPHENATION_FREQUENCY_NORMAL_FAST;
import android.app.ActionBar;
+import android.app.Activity;
import android.content.res.Configuration;
import android.graphics.text.LineBreakConfig;
import android.os.Build;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -30,6 +32,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.android.settingslib.widget.R;
@@ -42,7 +45,7 @@
* extend from {@link CollapsingToolbarBaseActivity} or from {@link CollapsingToolbarBaseFragment}.
*/
public class CollapsingToolbarDelegate {
-
+ private static final String TAG = "CTBdelegate";
/** Interface to be implemented by the host of the Collapsing Toolbar. */
public interface HostCallback {
/**
@@ -53,6 +56,13 @@
@Nullable
ActionBar setActionBar(Toolbar toolbar);
+ /** Sets support tool bar and return support action bar, this is for AppCompatActivity. */
+ @Nullable
+ default androidx.appcompat.app.ActionBar setActionBar(
+ androidx.appcompat.widget.Toolbar toolbar) {
+ return null;
+ }
+
/** Sets a title on the host. */
void setOuterTitle(CharSequence title);
}
@@ -79,6 +89,13 @@
/** Method to call that creates the root view of the collapsing toolbar. */
@SuppressWarnings("RestrictTo")
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) {
+ return onCreateView(inflater, container, null);
+ }
+
+ /** Method to call that creates the root view of the collapsing toolbar. */
+ @SuppressWarnings("RestrictTo")
+ View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ Activity activity) {
final View view =
inflater.inflate(R.layout.collapsing_toolbar_base_layout, container, false);
if (view instanceof CoordinatorLayout) {
@@ -99,17 +116,57 @@
}
}
autoSetCollapsingToolbarLayoutScrolling();
- mToolbar = view.findViewById(R.id.action_bar);
mContentFrameLayout = view.findViewById(R.id.content_frame);
- final ActionBar actionBar = mHostCallback.setActionBar(mToolbar);
+ if (activity instanceof AppCompatActivity) {
+ Log.d(TAG, "onCreateView: from AppCompatActivity and sub-class.");
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ initSupportActionBar(inflater);
+ } else {
+ initRSupportActionBar(view);
+ }
+ } else {
+ Log.d(TAG, "onCreateView: from NonAppCompatActivity.");
+ mToolbar = view.findViewById(R.id.action_bar);
+ final ActionBar actionBar = mHostCallback.setActionBar(mToolbar);
+ // Enable title and home button by default
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setHomeButtonEnabled(true);
+ actionBar.setDisplayShowTitleEnabled(true);
+ }
+ }
+ return view;
+ }
- // Enable title and home button by default
+ private void initSupportActionBar(@NonNull LayoutInflater inflater) {
+ if (mCollapsingToolbarLayout == null) {
+ return;
+ }
+ mCollapsingToolbarLayout.removeAllViews();
+ inflater.inflate(R.layout.support_toolbar, mCollapsingToolbarLayout);
+ final androidx.appcompat.widget.Toolbar supportToolbar =
+ mCollapsingToolbarLayout.findViewById(R.id.support_action_bar);
+ final androidx.appcompat.app.ActionBar actionBar =
+ mHostCallback.setActionBar(supportToolbar);
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayShowTitleEnabled(true);
}
- return view;
+ }
+
+ private void initRSupportActionBar(View view) {
+ view.findViewById(R.id.action_bar).setVisibility(View.GONE);
+ final androidx.appcompat.widget.Toolbar supportToolbar =
+ view.findViewById(R.id.support_action_bar);
+ supportToolbar.setVisibility(View.VISIBLE);
+ final androidx.appcompat.app.ActionBar actionBar =
+ mHostCallback.setActionBar(supportToolbar);
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setHomeButtonEnabled(true);
+ actionBar.setDisplayShowTitleEnabled(true);
+ }
}
/** Return an instance of CoordinatorLayout. */
@@ -160,9 +217,13 @@
new AppBarLayout.Behavior.DragCallback() {
@Override
public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
- // Header can be scrolling while device in landscape mode.
- return appBarLayout.getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE;
+ // Header can be scrolling while device in landscape mode and SDK > 33
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
+ return false;
+ } else {
+ return appBarLayout.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
+ }
}
});
params.setBehavior(behavior);
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
index d67ac3b..e4e34f8 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
@@ -49,7 +49,7 @@
*/
@RequiresApi(Build.VERSION_CODES.S)
public class CollapsingCoordinatorLayout extends CoordinatorLayout {
- private static final String TAG = "CollapsingCoordinatorLayout";
+ private static final String TAG = "CollapsingCoordinator";
private static final float TOOLBAR_LINE_SPACING_MULTIPLIER = 1.1f;
private CharSequence mToolbarTitle;
@@ -255,9 +255,13 @@
new AppBarLayout.Behavior.DragCallback() {
@Override
public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
- // Header can be scrolling while device in landscape mode.
- return appBarLayout.getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE;
+ // Header can be scrolling while device in landscape mode and SDK > 33
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
+ return false;
+ } else {
+ return appBarLayout.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
+ }
}
});
params.setBehavior(behavior);
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
index 59ae122..b39d09f 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
@@ -18,7 +18,11 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
- android:layout_width="match_parent">
+ android:layout_width="match_parent"
+ android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingRight="?android:attr/listPreferredItemPaddingRight"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
<TextView
android:id="@+id/switch_text"
@@ -50,7 +54,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
- android:layout_marginEnd="@dimen/settingslib_switchbar_subsettings_margin_end"
android:focusable="false"
android:clickable="false"
android:theme="@style/SwitchBar.Switch.Settingslib"/>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-sw600dp/dmiens.xml b/packages/SettingsLib/MainSwitchPreference/res/values-sw600dp/dmiens.xml
deleted file mode 100644
index 55a2589..0000000
--- a/packages/SettingsLib/MainSwitchPreference/res/values-sw600dp/dmiens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?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.
- -->
-
-<resources>
-
- <!-- SwitchBar sub settings margin start / end -->
- <dimen name="settingslib_switchbar_subsettings_margin_start">80dp</dimen>
-</resources>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-sw720dp-land/dmiens.xml b/packages/SettingsLib/MainSwitchPreference/res/values-sw720dp-land/dmiens.xml
deleted file mode 100644
index 53995bc..0000000
--- a/packages/SettingsLib/MainSwitchPreference/res/values-sw720dp-land/dmiens.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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.
- -->
-
-<resources>
-
- <!-- SwitchBar sub settings margin start / end -->
- <dimen name="settingslib_switchbar_subsettings_margin_start">128dp</dimen>
- <dimen name="settingslib_switchbar_subsettings_margin_end">128dp</dimen>
-</resources>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-sw720dp/dmiens.xml b/packages/SettingsLib/MainSwitchPreference/res/values-sw720dp/dmiens.xml
deleted file mode 100644
index 9015c58..0000000
--- a/packages/SettingsLib/MainSwitchPreference/res/values-sw720dp/dmiens.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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.
- -->
-
-<resources>
-
- <!-- SwitchBar sub settings margin start / end -->
- <dimen name="settingslib_switchbar_subsettings_margin_start">80dp</dimen>
- <dimen name="settingslib_switchbar_subsettings_margin_end">80dp</dimen>
-</resources>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
index 157a54e..88b2c87 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
@@ -27,6 +27,6 @@
<dimen name="settingslib_switch_title_margin">24dp</dimen>
<!-- SwitchBar sub settings margin start / end -->
- <dimen name="settingslib_switchbar_subsettings_margin_start">72dp</dimen>
+ <dimen name="settingslib_switchbar_subsettings_margin_start">56dp</dimen>
<dimen name="settingslib_switchbar_subsettings_margin_end">16dp</dimen>
</resources>
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index 86fec50..864a8bb 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -161,6 +161,19 @@
}
/**
+ * Set icon space reserved for title
+ */
+ public void setIconSpaceReserved(boolean iconSpaceReserved) {
+ if (mTextView != null && !BuildCompatUtils.isAtLeastS()) {
+ LayoutParams params = (LayoutParams) mTextView.getLayoutParams();
+ int iconSpace = getContext().getResources().getDimensionPixelSize(
+ R.dimen.settingslib_switchbar_subsettings_margin_start);
+ params.setMarginStart(iconSpaceReserved ? iconSpace : 0);
+ mTextView.setLayoutParams(params);
+ }
+ }
+
+ /**
* Show the MainSwitchBar
*/
public void show() {
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
index fc0e05f..53cc268 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
@@ -37,7 +37,6 @@
private final List<OnMainSwitchChangeListener> mSwitchChangeListeners = new ArrayList<>();
private MainSwitchBar mMainSwitchBar;
- private CharSequence mTitle;
public MainSwitchPreference(Context context) {
super(context);
@@ -68,6 +67,10 @@
holder.setDividerAllowedBelow(false);
mMainSwitchBar = (MainSwitchBar) holder.findViewById(R.id.settingslib_main_switch_bar);
+ // To support onPreferenceChange callback, it needs to call callChangeListener() when
+ // MainSwitchBar is clicked.
+ mMainSwitchBar.setOnClickListener((view) -> callChangeListener(isChecked()));
+ setIconSpaceReserved(isIconSpaceReserved());
updateStatus(isChecked());
registerListenerToSwitchBar();
}
@@ -82,6 +85,10 @@
final CharSequence title = a.getText(
androidx.preference.R.styleable.Preference_android_title);
setTitle(title);
+
+ final boolean bIconSpaceReserved = a.getBoolean(
+ androidx.preference.R.styleable.Preference_android_iconSpaceReserved, true);
+ setIconSpaceReserved(bIconSpaceReserved);
a.recycle();
}
}
@@ -96,9 +103,17 @@
@Override
public void setTitle(CharSequence title) {
- mTitle = title;
+ super.setTitle(title);
if (mMainSwitchBar != null) {
- mMainSwitchBar.setTitle(mTitle);
+ mMainSwitchBar.setTitle(title);
+ }
+ }
+
+ @Override
+ public void setIconSpaceReserved(boolean iconSpaceReserved) {
+ super.setIconSpaceReserved(iconSpaceReserved);
+ if (mMainSwitchBar != null) {
+ mMainSwitchBar.setIconSpaceReserved(iconSpaceReserved);
}
}
@@ -113,7 +128,7 @@
public void updateStatus(boolean checked) {
setChecked(checked);
if (mMainSwitchBar != null) {
- mMainSwitchBar.setTitle(mTitle);
+ mMainSwitchBar.setTitle(getTitle());
mMainSwitchBar.show();
}
}
@@ -125,6 +140,7 @@
if (!mSwitchChangeListeners.contains(listener)) {
mSwitchChangeListeners.add(listener);
}
+
if (mMainSwitchBar != null) {
mMainSwitchBar.addOnSwitchChangeListener(listener);
}
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
index bda478e..f1e028b 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
@@ -17,6 +17,7 @@
<resources>
<style name="PreferenceTheme.SettingsLib" parent="@style/PreferenceThemeOverlay">
<item name="preferenceCategoryTitleTextAppearance">@style/TextAppearance.CategoryTitle.SettingsLib</item>
+ <item name="preferenceScreenStyle">@style/SettingsPreferenceScreen.SettingsLib</item>
<item name="preferenceCategoryStyle">@style/SettingsCategoryPreference.SettingsLib</item>
<item name="preferenceStyle">@style/SettingsPreference.SettingsLib</item>
<item name="checkBoxPreferenceStyle">@style/SettingsCheckBoxPreference.SettingsLib</item>
@@ -28,6 +29,11 @@
<item name="footerPreferenceStyle">@style/Preference.Material</item>
</style>
+ <style name="SettingsPreferenceScreen.SettingsLib" parent="@style/Preference.PreferenceScreen.Material">
+ <item name="layout">@layout/settingslib_preference</item>
+ <item name="iconSpaceReserved">@bool/settingslib_config_icon_space_reserved</item>
+ </style>
+
<style name="SettingsCategoryPreference.SettingsLib" parent="@style/Preference.Category.Material">
<item name="iconSpaceReserved">@bool/settingslib_config_icon_space_reserved</item>
<item name="allowDividerAbove">@bool/settingslib_config_allow_divider</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles.xml b/packages/SettingsLib/SettingsTheme/res/values/styles.xml
index 328ab46..af3fc48 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/styles.xml
@@ -28,19 +28,19 @@
</style>
<style name="TextAppearance.TopIntroText"
- parent="@android:style/TextAppearance.DeviceDefault">
+ parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:textSize">14sp</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
<style name="TextAppearance.EntityHeaderTitle"
- parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
+ parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">20sp</item>
</style>
<style name="TextAppearance.EntityHeaderSummary"
- parent="@android:style/TextAppearance.DeviceDefault">
+ parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:textAlignment">viewStart</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:singleLine">true</item>
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 40613ce..139f3e1 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -27,6 +27,7 @@
"androidx.slice_slice-builders",
"androidx.slice_slice-core",
"androidx.slice_slice-view",
+ "androidx.compose.animation_animation",
"androidx.compose.material3_material3",
"androidx.compose.material_material-icons-extended",
"androidx.compose.runtime_runtime",
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index aa10cc8..a81e2e3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalAnimationApi::class)
+
package com.android.settingslib.spa.framework
import android.content.Intent
@@ -21,25 +23,31 @@
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.VisibleForTesting
+import androidx.compose.animation.AnimatedContentScope
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.unit.IntOffset
import androidx.core.view.WindowCompat
import androidx.navigation.NavGraph.Companion.findStartDestination
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import androidx.navigation.compose.rememberNavController
import com.android.settingslib.spa.R
import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.compose.AnimatedNavHost
import com.android.settingslib.spa.framework.compose.LocalNavController
import com.android.settingslib.spa.framework.compose.NavControllerWrapperImpl
+import com.android.settingslib.spa.framework.compose.composable
import com.android.settingslib.spa.framework.compose.localNavController
+import com.android.settingslib.spa.framework.compose.rememberAnimatedNavController
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.framework.util.PageEvent
import com.android.settingslib.spa.framework.util.getDestination
@@ -86,7 +94,7 @@
@VisibleForTesting
@Composable
fun BrowseContent(sppRepository: SettingsPageProviderRepository, initialIntent: Intent? = null) {
- val navController = rememberNavController()
+ val navController = rememberAnimatedNavController()
CompositionLocalProvider(navController.localNavController()) {
val controller = LocalNavController.current as NavControllerWrapperImpl
controller.NavContent(sppRepository.getAllProviders())
@@ -97,15 +105,41 @@
@Composable
private fun NavControllerWrapperImpl.NavContent(allProvider: Collection<SettingsPageProvider>) {
val nullPage = SettingsPage.createNull()
- NavHost(
+ AnimatedNavHost(
navController = navController,
startDestination = nullPage.sppName,
) {
+ val slideEffect = tween<IntOffset>(durationMillis = 300)
+ val fadeEffect = tween<Float>(durationMillis = 300)
composable(nullPage.sppName) {}
for (spp in allProvider) {
composable(
route = spp.name + spp.parameter.navRoute(),
arguments = spp.parameter,
+ enterTransition = {
+ slideIntoContainer(
+ AnimatedContentScope.SlideDirection.Left,
+ animationSpec = slideEffect
+ ) + fadeIn(animationSpec = fadeEffect)
+ },
+ exitTransition = {
+ slideOutOfContainer(
+ AnimatedContentScope.SlideDirection.Left,
+ animationSpec = slideEffect
+ ) + fadeOut(animationSpec = fadeEffect)
+ },
+ popEnterTransition = {
+ slideIntoContainer(
+ AnimatedContentScope.SlideDirection.Right,
+ animationSpec = slideEffect
+ ) + fadeIn(animationSpec = fadeEffect)
+ },
+ popExitTransition = {
+ slideOutOfContainer(
+ AnimatedContentScope.SlideDirection.Right,
+ animationSpec = slideEffect
+ ) + fadeOut(animationSpec = fadeEffect)
+ },
) { navBackStackEntry ->
spp.PageEvent(navBackStackEntry.arguments)
spp.Page(navBackStackEntry.arguments)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedComposeNavigator.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedComposeNavigator.kt
new file mode 100644
index 0000000..930a83f
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedComposeNavigator.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.compose
+
+import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.NavDestination
+import androidx.navigation.NavOptions
+import androidx.navigation.Navigator
+
+/**
+ * Navigator that navigates through [Composable]s. Every destination using this Navigator must
+ * set a valid [Composable] by setting it directly on an instantiated [Destination] or calling
+ * [composable].
+ */
+@ExperimentalAnimationApi
+@Navigator.Name("animatedComposable")
+public class AnimatedComposeNavigator : Navigator<AnimatedComposeNavigator.Destination>() {
+ internal val transitionsInProgress get() = state.transitionsInProgress
+
+ internal val backStack get() = state.backStack
+
+ internal val isPop = mutableStateOf(false)
+
+ override fun navigate(
+ entries: List<NavBackStackEntry>,
+ navOptions: NavOptions?,
+ navigatorExtras: Extras?
+ ) {
+ entries.forEach { entry ->
+ state.pushWithTransition(entry)
+ }
+ isPop.value = false
+ }
+
+ override fun createDestination(): Destination {
+ return Destination(this, content = { })
+ }
+
+ override fun popBackStack(popUpTo: NavBackStackEntry, savedState: Boolean) {
+ state.popWithTransition(popUpTo, savedState)
+ isPop.value = true
+ }
+
+ internal fun markTransitionComplete(entry: NavBackStackEntry) {
+ state.markTransitionComplete(entry)
+ }
+
+ /**
+ * NavDestination specific to [AnimatedComposeNavigator]
+ */
+ @ExperimentalAnimationApi
+ @NavDestination.ClassType(Composable::class)
+ public class Destination(
+ navigator: AnimatedComposeNavigator,
+ internal val content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit
+ ) : NavDestination(navigator)
+
+ internal companion object {
+ internal const val NAME = "animatedComposable"
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt
new file mode 100644
index 0000000..0137572
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.compose
+
+import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.AnimatedContentScope
+import androidx.compose.animation.ContentTransform
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.core.updateTransition
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.with
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveableStateHolder
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.NavDestination
+import androidx.navigation.NavDestination.Companion.hierarchy
+import androidx.navigation.NavGraph
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavHostController
+import androidx.navigation.Navigator
+import androidx.navigation.compose.DialogHost
+import androidx.navigation.compose.DialogNavigator
+import androidx.navigation.compose.LocalOwnersProvider
+import androidx.navigation.createGraph
+import androidx.navigation.get
+import kotlinx.coroutines.flow.map
+
+/**
+ * Provides in place in the Compose hierarchy for self contained navigation to occur.
+ *
+ * Once this is called, any Composable within the given [NavGraphBuilder] can be navigated to from
+ * the provided [navController].
+ *
+ * The builder passed into this method is [remember]ed. This means that for this NavHost, the
+ * contents of the builder cannot be changed.
+ *
+ * @param navController the navController for this host
+ * @param startDestination the route for the start destination
+ * @param modifier The modifier to be applied to the layout.
+ * @param route the route for the graph
+ * @param enterTransition callback to define enter transitions for destination in this host
+ * @param exitTransition callback to define exit transitions for destination in this host
+ * @param popEnterTransition callback to define popEnter transitions for destination in this host
+ * @param popExitTransition callback to define popExit transitions for destination in this host
+ * @param builder the builder used to construct the graph
+ */
+@Composable
+@ExperimentalAnimationApi
+public fun AnimatedNavHost(
+ navController: NavHostController,
+ startDestination: String,
+ modifier: Modifier = Modifier,
+ contentAlignment: Alignment = Alignment.Center,
+ route: String? = null,
+ enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) =
+ { fadeIn(animationSpec = tween(700)) },
+ exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) =
+ { fadeOut(animationSpec = tween(700)) },
+ popEnterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) =
+ enterTransition,
+ popExitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) =
+ exitTransition,
+ builder: NavGraphBuilder.() -> Unit
+) {
+ AnimatedNavHost(
+ navController,
+ remember(route, startDestination, builder) {
+ navController.createGraph(startDestination, route, builder)
+ },
+ modifier,
+ contentAlignment,
+ enterTransition,
+ exitTransition,
+ popEnterTransition,
+ popExitTransition
+ )
+}
+
+/**
+ * Provides in place in the Compose hierarchy for self contained navigation to occur.
+ *
+ * Once this is called, any Composable within the given [NavGraphBuilder] can be navigated to from
+ * the provided [navController].
+ *
+ * @param navController the navController for this host
+ * @param graph the graph for this host
+ * @param modifier The modifier to be applied to the layout.
+ * @param enterTransition callback to define enter transitions for destination in this host
+ * @param exitTransition callback to define exit transitions for destination in this host
+ * @param popEnterTransition callback to define popEnter transitions for destination in this host
+ * @param popExitTransition callback to define popExit transitions for destination in this host
+ */
+@ExperimentalAnimationApi
+@Composable
+public fun AnimatedNavHost(
+ navController: NavHostController,
+ graph: NavGraph,
+ modifier: Modifier = Modifier,
+ contentAlignment: Alignment = Alignment.Center,
+ enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) =
+ { fadeIn(animationSpec = tween(700)) },
+ exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) =
+ { fadeOut(animationSpec = tween(700)) },
+ popEnterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) =
+ enterTransition,
+ popExitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) =
+ exitTransition,
+) {
+
+ val lifecycleOwner = LocalLifecycleOwner.current
+ val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
+ "NavHost requires a ViewModelStoreOwner to be provided via LocalViewModelStoreOwner"
+ }
+ val onBackPressedDispatcherOwner = LocalOnBackPressedDispatcherOwner.current
+ val onBackPressedDispatcher = onBackPressedDispatcherOwner?.onBackPressedDispatcher
+
+ // on successful recompose we setup the navController with proper inputs
+ // after the first time, this will only happen again if one of the inputs changes
+ navController.setLifecycleOwner(lifecycleOwner)
+ navController.setViewModelStore(viewModelStoreOwner.viewModelStore)
+ if (onBackPressedDispatcher != null) {
+ navController.setOnBackPressedDispatcher(onBackPressedDispatcher)
+ }
+
+ navController.graph = graph
+
+ val saveableStateHolder = rememberSaveableStateHolder()
+
+ // Find the ComposeNavigator, returning early if it isn't found
+ // (such as is the case when using TestNavHostController)
+ val composeNavigator = navController.navigatorProvider.get<Navigator<out NavDestination>>(
+ AnimatedComposeNavigator.NAME
+ ) as? AnimatedComposeNavigator ?: return
+ val visibleEntries by remember(navController.visibleEntries) {
+ navController.visibleEntries.map {
+ it.filter { entry ->
+ entry.destination.navigatorName == AnimatedComposeNavigator.NAME
+ }
+ }
+ }.collectAsState(emptyList())
+
+ val backStackEntry = visibleEntries.lastOrNull()
+
+ if (backStackEntry != null) {
+ val finalEnter: AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition = {
+ val targetDestination = targetState.destination as AnimatedComposeNavigator.Destination
+
+ if (composeNavigator.isPop.value) {
+ targetDestination.hierarchy.firstNotNullOfOrNull { destination ->
+ popEnterTransitions[destination.route]?.invoke(this)
+ } ?: popEnterTransition.invoke(this)
+ } else {
+ targetDestination.hierarchy.firstNotNullOfOrNull { destination ->
+ enterTransitions[destination.route]?.invoke(this)
+ } ?: enterTransition.invoke(this)
+ }
+ }
+
+ val finalExit: AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition = {
+ val initialDestination =
+ initialState.destination as AnimatedComposeNavigator.Destination
+
+ if (composeNavigator.isPop.value) {
+ initialDestination.hierarchy.firstNotNullOfOrNull { destination ->
+ popExitTransitions[destination.route]?.invoke(this)
+ } ?: popExitTransition.invoke(this)
+ } else {
+ initialDestination.hierarchy.firstNotNullOfOrNull { destination ->
+ exitTransitions[destination.route]?.invoke(this)
+ } ?: exitTransition.invoke(this)
+ }
+ }
+
+ val transition = updateTransition(backStackEntry, label = "entry")
+ transition.AnimatedContent(
+ modifier,
+ transitionSpec = {
+ val zIndex = if (composeNavigator.isPop.value) {
+ visibleEntries.indexOf(initialState).toFloat()
+ } else {
+ visibleEntries.indexOf(targetState).toFloat()
+ }
+ // If the initialState of the AnimatedContent is not in visibleEntries, we are in
+ // a case where visible has cleared the old state for some reason, so instead of
+ // attempting to animate away from the initialState, we skip the animation.
+ if (initialState in visibleEntries) {
+ ContentTransform(finalEnter(this), finalExit(this), zIndex)
+ } else {
+ EnterTransition.None with ExitTransition.None
+ }
+ },
+ contentAlignment,
+ contentKey = { it.id }
+ ) {
+ // In some specific cases, such as clearing your back stack by changing your
+ // start destination, AnimatedContent can contain an entry that is no longer
+ // part of visible entries since it was cleared from the back stack and is not
+ // animating. In these cases the currentEntry will be null, and in those cases,
+ // AnimatedContent will just skip attempting to transition the old entry.
+ // See https://issuetracker.google.com/238686802
+ val currentEntry = visibleEntries.lastOrNull { entry ->
+ it == entry
+ }
+ // while in the scope of the composable, we provide the navBackStackEntry as the
+ // ViewModelStoreOwner and LifecycleOwner
+ currentEntry?.LocalOwnersProvider(saveableStateHolder) {
+ (currentEntry.destination as AnimatedComposeNavigator.Destination)
+ .content(this, currentEntry)
+ }
+ }
+ if (transition.currentState == transition.targetState) {
+ visibleEntries.forEach { entry ->
+ composeNavigator.markTransitionComplete(entry)
+ }
+ }
+ }
+
+ val dialogNavigator = navController.navigatorProvider.get<Navigator<out NavDestination>>(
+ "dialog"
+ ) as? DialogNavigator ?: return
+
+ // Show any dialog destinations
+ DialogHost(dialogNavigator)
+}
+
+@ExperimentalAnimationApi
+internal val enterTransitions =
+ mutableMapOf<String?,
+ (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)?>()
+
+@ExperimentalAnimationApi
+internal val exitTransitions =
+ mutableMapOf<String?, (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)?>()
+
+@ExperimentalAnimationApi
+internal val popEnterTransitions =
+ mutableMapOf<String?, (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)?>()
+
+@ExperimentalAnimationApi
+internal val popExitTransitions =
+ mutableMapOf<String?, (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)?>()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavGraphBuilder.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavGraphBuilder.kt
new file mode 100644
index 0000000..9e58603
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavGraphBuilder.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.compose
+
+import androidx.compose.animation.AnimatedContentScope
+import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.runtime.Composable
+import androidx.navigation.NamedNavArgument
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.NavDeepLink
+import androidx.navigation.NavGraph
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.compose.navigation
+import androidx.navigation.get
+
+/**
+ * Add the [Composable] to the [NavGraphBuilder]
+ *
+ * @param route route for the destination
+ * @param arguments list of arguments to associate with destination
+ * @param deepLinks list of deep links to associate with the destinations
+ * @param enterTransition callback to determine the destination's enter transition
+ * @param exitTransition callback to determine the destination's exit transition
+ * @param popEnterTransition callback to determine the destination's popEnter transition
+ * @param popExitTransition callback to determine the destination's popExit transition
+ * @param content composable for the destination
+ */
+@ExperimentalAnimationApi
+public fun NavGraphBuilder.composable(
+ route: String,
+ arguments: List<NamedNavArgument> = emptyList(),
+ deepLinks: List<NavDeepLink> = emptyList(),
+ enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)? = null,
+ exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)? = null,
+ popEnterTransition: (
+ AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?
+ )? = enterTransition,
+ popExitTransition: (
+ AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?
+ )? = exitTransition,
+ content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit
+) {
+ addDestination(
+ AnimatedComposeNavigator.Destination(
+ provider[AnimatedComposeNavigator::class],
+ content
+ ).apply {
+ this.route = route
+ arguments.forEach { (argumentName, argument) ->
+ addArgument(argumentName, argument)
+ }
+ deepLinks.forEach { deepLink ->
+ addDeepLink(deepLink)
+ }
+ enterTransition?.let { enterTransitions[route] = enterTransition }
+ exitTransition?.let { exitTransitions[route] = exitTransition }
+ popEnterTransition?.let { popEnterTransitions[route] = popEnterTransition }
+ popExitTransition?.let { popExitTransitions[route] = popExitTransition }
+ }
+ )
+}
+
+/**
+ * Construct a nested [NavGraph]
+ *
+ * @param startDestination the starting destination's route for this NavGraph
+ * @param route the destination's unique route
+ * @param arguments list of arguments to associate with destination
+ * @param deepLinks list of deep links to associate with the destinations
+ * @param enterTransition callback to define enter transitions for destination in this NavGraph
+ * @param exitTransition callback to define exit transitions for destination in this NavGraph
+ * @param popEnterTransition callback to define pop enter transitions for destination in this
+ * NavGraph
+ * @param popExitTransition callback to define pop exit transitions for destination in this NavGraph
+ * @param builder the builder used to construct the graph
+ *
+ * @return the newly constructed nested NavGraph
+ */
+@ExperimentalAnimationApi
+public fun NavGraphBuilder.navigation(
+ startDestination: String,
+ route: String,
+ arguments: List<NamedNavArgument> = emptyList(),
+ deepLinks: List<NavDeepLink> = emptyList(),
+ enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)? = null,
+ exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)? = null,
+ popEnterTransition: (
+ AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?
+ )? = enterTransition,
+ popExitTransition: (
+ AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?
+ )? = exitTransition,
+ builder: NavGraphBuilder.() -> Unit
+) {
+ navigation(startDestination, route, arguments, deepLinks, builder).apply {
+ enterTransition?.let { enterTransitions[route] = enterTransition }
+ exitTransition?.let { exitTransitions[route] = exitTransition }
+ popEnterTransition?.let { popEnterTransitions[route] = popEnterTransition }
+ popExitTransition?.let { popExitTransitions[route] = popExitTransition }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavHostController.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavHostController.kt
new file mode 100644
index 0000000..a8ac86c
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavHostController.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.compose
+
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.navigation.NavDestination
+import androidx.navigation.NavHostController
+import androidx.navigation.Navigator
+import androidx.navigation.compose.rememberNavController
+
+/**
+ * Creates a NavHostController that handles the adding of the [ComposeNavigator], [DialogNavigator]
+ * and [AnimatedComposeNavigator]. Additional [androidx.navigation.Navigator] instances should be
+ * added in a [androidx.compose.runtime.SideEffect] block.
+ *
+ * @see AnimatedNavHost
+ */
+@ExperimentalAnimationApi
+@Composable
+fun rememberAnimatedNavController(
+ vararg navigators: Navigator<out NavDestination>
+): NavHostController {
+ val animatedNavigator = remember { AnimatedComposeNavigator() }
+ return rememberNavController(animatedNavigator, *navigators)
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
index e878804..6cd6e95 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
@@ -17,7 +17,6 @@
package com.android.settingslib.spaprivileged.model.app
import android.app.AppOpsManager.MODE_ALLOWED
-import android.app.AppOpsManager.MODE_ERRORED
import android.app.AppOpsManager.Mode
import android.content.Context
import android.content.pm.ApplicationInfo
@@ -33,14 +32,14 @@
fun setAllowed(allowed: Boolean)
- @Mode
- fun getMode(): Int
+ @Mode fun getMode(): Int
}
class AppOpsController(
context: Context,
private val app: ApplicationInfo,
private val op: Int,
+ private val modeForNotAllowed: Int,
private val setModeByUid: Boolean = false,
) : IAppOpsController {
private val appOpsManager = context.appOpsManager
@@ -49,7 +48,7 @@
get() = _mode
override fun setAllowed(allowed: Boolean) {
- val mode = if (allowed) MODE_ALLOWED else MODE_ERRORED
+ val mode = if (allowed) MODE_ALLOWED else modeForNotAllowed
if (setModeByUid) {
appOpsManager.setUidMode(op, app.uid, mode)
} else {
@@ -58,12 +57,12 @@
_mode.postValue(mode)
}
- @Mode
- override fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName)
+ @Mode override fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName)
- private val _mode = object : MutableLiveData<Int>() {
- override fun onActive() {
- postValue(getMode())
+ private val _mode =
+ object : MutableLiveData<Int>() {
+ override fun onActive() {
+ postValue(getMode())
+ }
}
- }
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
index ee21b81..53af25b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
@@ -18,6 +18,7 @@
import android.app.AppOpsManager.MODE_ALLOWED
import android.app.AppOpsManager.MODE_DEFAULT
+import android.app.AppOpsManager.MODE_ERRORED
import android.content.Context
import android.content.pm.ApplicationInfo
import androidx.compose.runtime.Composable
@@ -25,6 +26,8 @@
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.util.asyncMapItem
import com.android.settingslib.spa.framework.util.filterItem
import com.android.settingslib.spaprivileged.model.app.AppOpsController
import com.android.settingslib.spaprivileged.model.app.AppRecord
@@ -37,6 +40,7 @@
data class AppOpPermissionRecord(
override val app: ApplicationInfo,
+ val hasRequestBroaderPermission: Boolean,
val hasRequestPermission: Boolean,
var appOpsController: IAppOpsController,
) : AppRecord
@@ -50,9 +54,26 @@
abstract val permission: String
/**
+ * When set, specifies the broader permission who trumps the [permission].
+ *
+ * When trumped, the [permission] is not changeable and model shows the [permission] as allowed.
+ */
+ open val broaderPermission: String? = null
+
+ /**
+ * Indicates whether [permission] has protection level appop flag.
+ *
+ * If true, it uses getAppOpPermissionPackages() to fetch bits to decide whether the permission
+ * is requested.
+ */
+ open val permissionHasAppopFlag: Boolean = true
+
+ open val modeForNotAllowed: Int = MODE_ERRORED
+
+ /**
* Use AppOpsManager#setUidMode() instead of AppOpsManager#setMode() when set allowed.
*
- * Security related app-ops should be set with setUidMode() instead of setMode().
+ * Security or privacy related app-ops should be set with setUidMode() instead of setMode().
*/
open val setModeByUid = false
@@ -60,31 +81,54 @@
private val notChangeablePackages =
setOf("android", "com.android.systemui", context.packageName)
+ private fun createAppOpsController(app: ApplicationInfo) =
+ AppOpsController(
+ context = context,
+ app = app,
+ op = appOp,
+ setModeByUid = setModeByUid,
+ modeForNotAllowed = modeForNotAllowed,
+ )
+
+ private fun createRecord(
+ app: ApplicationInfo,
+ hasRequestPermission: Boolean
+ ): AppOpPermissionRecord =
+ with(packageManagers) {
+ AppOpPermissionRecord(
+ app = app,
+ hasRequestBroaderPermission =
+ broaderPermission?.let { app.hasRequestPermission(it) } ?: false,
+ hasRequestPermission = hasRequestPermission,
+ appOpsController = createAppOpsController(app),
+ )
+ }
+
override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
- userIdFlow.map { userId ->
- packageManagers.getAppOpPermissionPackages(userId, permission)
- }.combine(appListFlow) { packageNames, appList ->
- appList.map { app ->
- AppOpPermissionRecord(
- app = app,
- hasRequestPermission = app.packageName in packageNames,
- appOpsController = createAppOpsController(app),
- )
+ if (permissionHasAppopFlag) {
+ userIdFlow
+ .map { userId -> packageManagers.getAppOpPermissionPackages(userId, permission) }
+ .combine(appListFlow) { packageNames, appList ->
+ appList.map { app ->
+ createRecord(
+ app = app,
+ hasRequestPermission = app.packageName in packageNames,
+ )
+ }
+ }
+ } else {
+ appListFlow.asyncMapItem { app ->
+ with(packageManagers) { createRecord(app, app.hasRequestPermission(permission)) }
}
}
- override fun transformItem(app: ApplicationInfo) = AppOpPermissionRecord(
- app = app,
- hasRequestPermission = with(packageManagers) { app.hasRequestPermission(permission) },
- appOpsController = createAppOpsController(app),
- )
-
- private fun createAppOpsController(app: ApplicationInfo) = AppOpsController(
- context = context,
- app = app,
- op = appOp,
- setModeByUid = setModeByUid,
- )
+ override fun transformItem(app: ApplicationInfo) =
+ with(packageManagers) {
+ createRecord(
+ app = app,
+ hasRequestPermission = app.hasRequestPermission(permission),
+ )
+ }
override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<AppOpPermissionRecord>>) =
recordListFlow.filterItem(::isChangeable)
@@ -95,15 +139,19 @@
*/
@Composable
override fun isAllowed(record: AppOpPermissionRecord): State<Boolean?> {
+ if (record.hasRequestBroaderPermission) {
+ // Broader permission trumps the specific permission.
+ return stateOf(true)
+ }
+
val mode = record.appOpsController.mode.observeAsState()
return remember {
derivedStateOf {
when (mode.value) {
null -> null
MODE_ALLOWED -> true
- MODE_DEFAULT -> with(packageManagers) {
- record.app.hasGrantPermission(permission)
- }
+ MODE_DEFAULT ->
+ with(packageManagers) { record.app.hasGrantPermission(permission) }
else -> false
}
}
@@ -111,7 +159,9 @@
}
override fun isChangeable(record: AppOpPermissionRecord) =
- record.hasRequestPermission && record.app.packageName !in notChangeablePackages
+ record.hasRequestPermission &&
+ !record.hasRequestBroaderPermission &&
+ record.app.packageName !in notChangeablePackages
override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) {
record.appOpsController.setAllowed(newAllowed)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 76cff0b..e9fcbd2 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -81,6 +81,13 @@
navArgument(USER_ID) { type = NavType.IntType },
)
+ /**
+ * Gets the route prefix to this page.
+ *
+ * Expose route prefix to enable enter from non-SPA pages.
+ */
+ fun getRoutePrefix(permissionType: String) = "$PAGE_NAME/$permissionType"
+
@Composable
fun navigator(permissionType: String, app: ApplicationInfo) =
navigator(route = "$PAGE_NAME/$permissionType/${app.toRoute()}")
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
index ce8fc9d..1ab6230 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
@@ -93,6 +93,14 @@
fun getAppListRoute(): String =
TogglePermissionAppListPageProvider.getRoute(permissionType)
+ /**
+ * Gets the route prefix to the toggle permission App Info page.
+ *
+ * Expose route prefix to enable enter from non-SPA pages.
+ */
+ fun getAppInfoRoutePrefix(): String =
+ TogglePermissionAppInfoPageProvider.getRoutePrefix(permissionType)
+
@Composable
fun InfoPageEntryItem(app: ApplicationInfo) {
val listModel = rememberContext(::createModel)
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
index 668bfdf..53e52d0 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
@@ -31,21 +31,18 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.Spy
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
-import org.mockito.Mockito.`when` as whenever
@RunWith(AndroidJUnit4::class)
class AppOpsControllerTest {
- @get:Rule
- val mockito: MockitoRule = MockitoJUnit.rule()
+ @get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
- @Spy
- private val context: Context = ApplicationProvider.getApplicationContext()
+ @Spy private val context: Context = ApplicationProvider.getApplicationContext()
- @Mock
- private lateinit var appOpsManager: AppOpsManager
+ @Mock private lateinit var appOpsManager: AppOpsManager
@Before
fun setUp() {
@@ -54,7 +51,13 @@
@Test
fun setAllowed_setToTrue() {
- val controller = AppOpsController(context = context, app = APP, op = OP)
+ val controller =
+ AppOpsController(
+ context = context,
+ app = APP,
+ op = OP,
+ modeForNotAllowed = MODE_ERRORED
+ )
controller.setAllowed(true)
@@ -63,7 +66,13 @@
@Test
fun setAllowed_setToFalse() {
- val controller = AppOpsController(context = context, app = APP, op = OP)
+ val controller =
+ AppOpsController(
+ context = context,
+ app = APP,
+ op = OP,
+ modeForNotAllowed = MODE_ERRORED
+ )
controller.setAllowed(false)
@@ -73,7 +82,13 @@
@Test
fun setAllowed_setToTrueByUid() {
val controller =
- AppOpsController(context = context, app = APP, op = OP, setModeByUid = true)
+ AppOpsController(
+ context = context,
+ app = APP,
+ op = OP,
+ modeForNotAllowed = MODE_ERRORED,
+ setModeByUid = true
+ )
controller.setAllowed(true)
@@ -83,7 +98,13 @@
@Test
fun setAllowed_setToFalseByUid() {
val controller =
- AppOpsController(context = context, app = APP, op = OP, setModeByUid = true)
+ AppOpsController(
+ context = context,
+ app = APP,
+ op = OP,
+ modeForNotAllowed = MODE_ERRORED,
+ setModeByUid = true
+ )
controller.setAllowed(false)
@@ -92,10 +113,15 @@
@Test
fun getMode() {
- whenever(
- appOpsManager.checkOpNoThrow(OP, APP.uid, APP.packageName)
- ).thenReturn(MODE_ALLOWED)
- val controller = AppOpsController(context = context, app = APP, op = OP)
+ whenever(appOpsManager.checkOpNoThrow(OP, APP.uid, APP.packageName))
+ .thenReturn(MODE_ALLOWED)
+ val controller =
+ AppOpsController(
+ context = context,
+ app = APP,
+ op = OP,
+ modeForNotAllowed = MODE_ERRORED
+ )
val mode = controller.getMode()
@@ -104,9 +130,10 @@
private companion object {
const val OP = 1
- val APP = ApplicationInfo().apply {
- packageName = "package.name"
- uid = 123
- }
+ val APP =
+ ApplicationInfo().apply {
+ packageName = "package.name"
+ uid = 123
+ }
}
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
index 966b869..da765ba 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
@@ -39,28 +39,23 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.Spy
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
-import org.mockito.Mockito.`when` as whenever
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class AppOpPermissionAppListTest {
- @get:Rule
- val mockito: MockitoRule = MockitoJUnit.rule()
+ @get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
- @get:Rule
- val composeTestRule = createComposeRule()
+ @get:Rule val composeTestRule = createComposeRule()
- @Spy
- private val context: Context = ApplicationProvider.getApplicationContext()
+ @Spy private val context: Context = ApplicationProvider.getApplicationContext()
- @Mock
- private lateinit var packageManagers: IPackageManagers
+ @Mock private lateinit var packageManagers: IPackageManagers
- @Mock
- private lateinit var appOpsManager: AppOpsManager
+ @Mock private lateinit var appOpsManager: AppOpsManager
private lateinit var listModel: TestAppOpPermissionAppListModel
@@ -79,9 +74,7 @@
@Test
fun transformItem_hasRequestPermission() = runTest {
- with(packageManagers) {
- whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(true)
- }
+ with(packageManagers) { whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(true) }
val record = listModel.transformItem(APP)
@@ -90,8 +83,30 @@
@Test
fun transformItem_notRequestPermission() = runTest {
+ with(packageManagers) { whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(false) }
+
+ val record = listModel.transformItem(APP)
+
+ assertThat(record.hasRequestPermission).isFalse()
+ }
+
+ @Test
+ fun transformItem_hasRequestBroaderPermission() = runTest {
+ listModel.broaderPermission = BROADER_PERMISSION
with(packageManagers) {
- whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(false)
+ whenever(APP.hasRequestPermission(BROADER_PERMISSION)).thenReturn(true)
+ }
+
+ val record = listModel.transformItem(APP)
+
+ assertThat(record.hasRequestBroaderPermission).isTrue()
+ }
+
+ @Test
+ fun transformItem_notRequestBroaderPermission() = runTest {
+ listModel.broaderPermission = BROADER_PERMISSION
+ with(packageManagers) {
+ whenever(APP.hasRequestPermission(BROADER_PERMISSION)).thenReturn(false)
}
val record = listModel.transformItem(APP)
@@ -101,14 +116,14 @@
@Test
fun filter() = runTest {
- with(packageManagers) {
- whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(false)
- }
- val record = AppOpPermissionRecord(
- app = APP,
- hasRequestPermission = false,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
- )
+ with(packageManagers) { whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(false) }
+ val record =
+ AppOpPermissionRecord(
+ app = APP,
+ hasRequestBroaderPermission = false,
+ hasRequestPermission = false,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
val recordListFlow = listModel.filter(flowOf(USER_ID), flowOf(listOf(record)))
@@ -118,11 +133,13 @@
@Test
fun isAllowed_allowed() {
- val record = AppOpPermissionRecord(
- app = APP,
- hasRequestPermission = true,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ALLOWED),
- )
+ val record =
+ AppOpPermissionRecord(
+ app = APP,
+ hasRequestBroaderPermission = false,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ALLOWED),
+ )
val isAllowed = getIsAllowed(record)
@@ -131,14 +148,14 @@
@Test
fun isAllowed_defaultAndHasGrantPermission() {
- with(packageManagers) {
- whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(true)
- }
- val record = AppOpPermissionRecord(
- app = APP,
- hasRequestPermission = true,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
- )
+ with(packageManagers) { whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(true) }
+ val record =
+ AppOpPermissionRecord(
+ app = APP,
+ hasRequestBroaderPermission = false,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
val isAllowed = getIsAllowed(record)
@@ -147,14 +164,14 @@
@Test
fun isAllowed_defaultAndNotGrantPermission() {
- with(packageManagers) {
- whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(false)
- }
- val record = AppOpPermissionRecord(
- app = APP,
- hasRequestPermission = true,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
- )
+ with(packageManagers) { whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(false) }
+ val record =
+ AppOpPermissionRecord(
+ app = APP,
+ hasRequestBroaderPermission = false,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
val isAllowed = getIsAllowed(record)
@@ -162,12 +179,34 @@
}
@Test
+ fun isAllowed_broaderPermissionTrumps() {
+ listModel.broaderPermission = BROADER_PERMISSION
+ with(packageManagers) {
+ whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(false)
+ whenever(APP.hasGrantPermission(BROADER_PERMISSION)).thenReturn(true)
+ }
+ val record =
+ AppOpPermissionRecord(
+ app = APP,
+ hasRequestBroaderPermission = true,
+ hasRequestPermission = false,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED),
+ )
+
+ val isAllowed = getIsAllowed(record)
+
+ assertThat(isAllowed).isTrue()
+ }
+
+ @Test
fun isAllowed_notAllowed() {
- val record = AppOpPermissionRecord(
- app = APP,
- hasRequestPermission = true,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED),
- )
+ val record =
+ AppOpPermissionRecord(
+ app = APP,
+ hasRequestBroaderPermission = false,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED),
+ )
val isAllowed = getIsAllowed(record)
@@ -176,11 +215,13 @@
@Test
fun isChangeable_notRequestPermission() {
- val record = AppOpPermissionRecord(
- app = APP,
- hasRequestPermission = false,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
- )
+ val record =
+ AppOpPermissionRecord(
+ app = APP,
+ hasRequestBroaderPermission = false,
+ hasRequestPermission = false,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
val isChangeable = listModel.isChangeable(record)
@@ -189,11 +230,13 @@
@Test
fun isChangeable_notChangeablePackages() {
- val record = AppOpPermissionRecord(
- app = NOT_CHANGEABLE_APP,
- hasRequestPermission = true,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
- )
+ val record =
+ AppOpPermissionRecord(
+ app = NOT_CHANGEABLE_APP,
+ hasRequestBroaderPermission = false,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
val isChangeable = listModel.isChangeable(record)
@@ -202,11 +245,13 @@
@Test
fun isChangeable_hasRequestPermissionAndChangeable() {
- val record = AppOpPermissionRecord(
- app = APP,
- hasRequestPermission = true,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
- )
+ val record =
+ AppOpPermissionRecord(
+ app = APP,
+ hasRequestBroaderPermission = false,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
val isChangeable = listModel.isChangeable(record)
@@ -214,13 +259,31 @@
}
@Test
+ fun isChangeable_broaderPermissionTrumps() {
+ listModel.broaderPermission = BROADER_PERMISSION
+ val record =
+ AppOpPermissionRecord(
+ app = APP,
+ hasRequestBroaderPermission = true,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
+
+ val isChangeable = listModel.isChangeable(record)
+
+ assertThat(isChangeable).isFalse()
+ }
+
+ @Test
fun setAllowed() {
val appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT)
- val record = AppOpPermissionRecord(
- app = APP,
- hasRequestPermission = true,
- appOpsController = appOpsController,
- )
+ val record =
+ AppOpPermissionRecord(
+ app = APP,
+ hasRequestBroaderPermission = false,
+ hasRequestPermission = true,
+ appOpsController = appOpsController,
+ )
listModel.setAllowed(record = record, newAllowed = true)
@@ -239,9 +302,7 @@
private fun getIsAllowed(record: AppOpPermissionRecord): Boolean? {
lateinit var isAllowedState: State<Boolean?>
- composeTestRule.setContent {
- isAllowedState = listModel.isAllowed(record)
- }
+ composeTestRule.setContent { isAllowedState = listModel.isAllowed(record) }
return isAllowedState.value
}
@@ -250,8 +311,12 @@
override val pageTitleResId = R.string.test_app_op_permission_title
override val switchTitleResId = R.string.test_app_op_permission_switch_title
override val footerResId = R.string.test_app_op_permission_footer
+
override val appOp = AppOpsManager.OP_MANAGE_MEDIA
override val permission = PERMISSION
+ override val permissionHasAppopFlag = true
+ override var broaderPermission: String? = null
+
override var setModeByUid = false
}
@@ -259,12 +324,9 @@
const val USER_ID = 0
const val PACKAGE_NAME = "package.name"
const val PERMISSION = "PERMISSION"
- val APP = ApplicationInfo().apply {
- packageName = PACKAGE_NAME
- }
- val NOT_CHANGEABLE_APP = ApplicationInfo().apply {
- packageName = "android"
- }
+ const val BROADER_PERMISSION = "BROADER_PERMISSION"
+ val APP = ApplicationInfo().apply { packageName = PACKAGE_NAME }
+ val NOT_CHANGEABLE_APP = ApplicationInfo().apply { packageName = "android" }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 4a1a4e6..810545c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -291,6 +291,18 @@
return false;
}
+ /**
+ * Check if a device class matches with a defined BluetoothClass device.
+ *
+ * @param device Must be one of the public constants in {@link BluetoothClass.Device}
+ * @return true if device class matches, false otherwise.
+ */
+ public static boolean isDeviceClassMatched(@NonNull BluetoothDevice bluetoothDevice,
+ int device) {
+ return bluetoothDevice.getBluetoothClass() != null
+ && bluetoothDevice.getBluetoothClass().getDeviceClass() == device;
+ }
+
private static boolean isAdvancedHeaderEnabled() {
if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED,
true)) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 2951001..c2c1b55 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1028,12 +1028,11 @@
if (BluetoothUuid.containsAnyUuid(uuids, PbapServerProfile.PBAB_CLIENT_UUIDS)) {
// The pairing dialog now warns of phone-book access for paired devices.
// No separate prompt is displayed after pairing.
- final BluetoothClass bluetoothClass = mDevice.getBluetoothClass();
if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) {
- if (bluetoothClass != null && (bluetoothClass.getDeviceClass()
- == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE
- || bluetoothClass.getDeviceClass()
- == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET)) {
+ if (BluetoothUtils.isDeviceClassMatched(mDevice,
+ BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE)
+ || BluetoothUtils.isDeviceClassMatched(mDevice,
+ BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET)) {
EventLog.writeEvent(0x534e4554, "138529441", -1, "");
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 221836b..f741f65 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -390,7 +390,10 @@
Log.d(TAG, "Bond " + device.getAnonymizedAddress() + " by CSIP");
mOngoingSetMemberPair = device;
syncConfigFromMainDevice(device, groupId);
- device.createBond(BluetoothDevice.TRANSPORT_LE);
+ if (!device.createBond(BluetoothDevice.TRANSPORT_LE)) {
+ Log.d(TAG, "Bonding could not be started");
+ mOngoingSetMemberPair = null;
+ }
}
private void syncConfigFromMainDevice(BluetoothDevice device, int groupId) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java
index feb5e0b..1401a4f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java
@@ -41,8 +41,8 @@
}
/**
- * Logs hearing aid device information to westworld, including device mode, device side, and
- * entry page id where the binding(connecting) process starts.
+ * Logs hearing aid device information to statsd, including device mode, device side, and entry
+ * page id where the binding(connecting) process starts.
*
* Only logs the info once after hearing aid is bonded(connected). Clears the map entry of this
* device when logging is completed.
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
index 562d480..e781f13 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
@@ -275,6 +275,10 @@
}
public int getDrawableResource(BluetoothClass btClass) {
+ if (btClass == null) {
+ Log.e(TAG, "No btClass.");
+ return R.drawable.ic_bt_le_audio_speakers;
+ }
switch (btClass.getDeviceClass()) {
case BluetoothClass.Device.AUDIO_VIDEO_UNCATEGORIZED:
case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index f92625b..b22195a 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -32,18 +32,19 @@
}
]
},
- {
- // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
- "name": "SystemUIGoogleBiometricsScreenshotTests",
- "options": [
- {
- "exclude-annotation": "org.junit.Ignore"
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- }
- ]
- },
+// Disable until can pass: b/259124654
+// {
+// // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
+// "name": "SystemUIGoogleBiometricsScreenshotTests",
+// "options": [
+// {
+// "exclude-annotation": "org.junit.Ignore"
+// },
+// {
+// "exclude-annotation": "androidx.test.filters.FlakyTest"
+// }
+// ]
+// },
{
// Permission indicators
"name": "CtsPermission4TestCases",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
new file mode 100644
index 0000000..a494f5e
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+ name: "AccessibilityMenu",
+ srcs: [
+ "src/**/*.java",
+ ],
+ system_ext_specific: true,
+ platform_apis: true,
+ resource_dirs: ["res"],
+ certificate: "platform",
+ // This app uses allowlisted privileged permissions.
+ privileged: true,
+}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
new file mode 100644
index 0000000..26748a9
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.systemui.accessibility.accessibilitymenu">
+ <application>
+ <service
+ android:name="com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService"
+ android:exported="false"
+ android:label="Accessibility Menu (System)"
+ android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService"/>
+ </intent-filter>
+ <meta-data
+ android:name="android.accessibilityservice"
+ android:resource="@xml/accessibilitymenu_service"/>
+ </service>
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/OWNERS b/packages/SystemUI/accessibility/accessibilitymenu/OWNERS
new file mode 100644
index 0000000..b74281e
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/view/accessibility/OWNERS
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_service.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_service.xml
new file mode 100644
index 0000000..96882d33
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_service.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"/>
\ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
new file mode 100644
index 0000000..8b75900
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.accessibilitymenu;
+
+import android.accessibilityservice.AccessibilityService;
+import android.view.accessibility.AccessibilityEvent;
+
+/** @hide */
+public class AccessibilityMenuService extends AccessibilityService {
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ }
+
+ @Override
+ public void onInterrupt() {
+ }
+}
diff --git a/packages/SystemUI/docs/modern-architecture.png b/packages/SystemUI/docs/modern-architecture.png
new file mode 100644
index 0000000..2636362
--- /dev/null
+++ b/packages/SystemUI/docs/modern-architecture.png
Binary files differ
diff --git a/packages/SystemUI/docs/status-bar-data-pipeline.md b/packages/SystemUI/docs/status-bar-data-pipeline.md
new file mode 100644
index 0000000..9fcdce1
--- /dev/null
+++ b/packages/SystemUI/docs/status-bar-data-pipeline.md
@@ -0,0 +1,261 @@
+# Status Bar Data Pipeline
+
+## Background
+
+The status bar is the UI shown at the top of the user's screen that gives them
+information about the time, notifications, and system status like mobile
+conectivity and battery level. This document is about the implementation of the
+wifi and mobile system icons on the right side:
+
+
+
+In Android U, the data pipeline that determines what mobile and wifi icons to
+show in the status bar has been re-written with a new architecture. This format
+generally follows Android best practices to
+[app architecture](https://developer.android.com/topic/architecture#recommended-app-arch).
+This document serves as a guide for the new architecture, and as a guide for how
+OEMs can add customizations to the new architecture.
+
+## Architecture
+
+In the new architecture, there is a separate pipeline for each type of icon. For
+Android U, **only the wifi icon and mobile icons have been implemented in the
+new architecture**.
+
+As shown in the Android best practices guide, each new pipeline has a data
+layer, a domain layer, and a UI layer:
+
+
+
+The classes in the data layer are `repository` instances. The classes in the
+domain layer are `interactor` instances. The classes in the UI layer are
+`viewmodel` instances and `viewbinder` instances. In this document, "repository"
+and "data layer" will be used interchangably (and the same goes for the other
+layers).
+
+The wifi logic is in `statusbar/pipeline/wifi` and the mobile logic is in
+`statusbar/pipeline/mobile`.
+
+#### Repository (data layer)
+
+System callbacks, broadcast receivers, configuration values are all defined
+here, and exposed through the appropriate interface. Where appropriate, we
+define `Model` objects at this layer so that clients do not have to rely on
+system-defined interfaces.
+
+#### Interactor (domain layer)
+
+Here is where we define the business logic and transform the data layer objects
+into something consumable by the ViewModel classes. For example,
+`MobileIconsInteractor` defines the CBRS filtering logic by exposing a
+`filteredSubscriptions` list.
+
+#### ViewModel (UI layer)
+
+View models should define the final piece of business logic mapping to UI logic.
+For example, the mobile view model checks the `IconInteractor.isRoaming` flow to
+decide whether or not to show the roaming indicator.
+
+#### ViewBinder
+
+These have already been implemented and configured. ViewBinders replace the old
+`applyMobileState` mechanism that existed in the `IconManager` classes of the
+old pipeline. A view binder associates a ViewModel with a View, and keeps the
+view up-to-date with the most recent information from the model.
+
+Any new fields added to the ViewModel classes need to be equivalently bound to
+the view here.
+
+### Putting it all together
+
+Putting that altogether, we have this overall architecture diagram for the
+icons:
+
+
+
+### Mobile icons architecture
+
+Because there can be multiple mobile connections at the same time, the mobile
+pipeline is split up hierarchically. At each level (data, domain, and UI), there
+is a singleton parent class that manages information relevant to **all** mobile
+connections, and multiple instances of child classes that manage information for
+a **single** mobile connection.
+
+For example, `MobileConnectionsRepository` is a singleton at the data layer that
+stores information relevant to **all** mobile connections, and it also manages a
+list of child `MobileConnectionRepository` classes. `MobileConnectionRepository`
+is **not** a singleton, and each individual `MobileConnectionRepository`
+instance fully qualifies the state of a **single** connection. This pattern is
+repeated at the `Interactor` and `ViewModel` layers for mobile.
+
+
+
+Note: Since there is at most one wifi connection, the wifi pipeline is not split
+up in the same way.
+
+## Customizations
+
+The new pipeline completely replaces these classes:
+
+* `WifiStatusTracker`
+* `MobileStatusTracker`
+* `NetworkSignalController` and `NetworkSignalControllerImpl`
+* `MobileSignalController`
+* `WifiSignalController`
+* `StatusBarSignalPolicy` (including `SignalIconState`, `MobileIconState`, and
+ `WifiIconState`)
+
+Any customizations in any of these classes will need to be migrated to the new
+pipeline. As a general rule, any change that would have gone into
+`NetworkControllerImpl` would be done in `MobileConnectionsRepository`, and any
+change for `MobileSignalController` can be done in `MobileConnectionRepository`
+(see above on the relationship between those repositories).
+
+### Sample customization: New service
+
+Some customizations require listening to additional services to get additional
+data. This new architecture makes it easy to add additional services to the
+status bar data pipeline to get icon customizations.
+
+Below is a general guide to how a new service should be added. However, there
+may be exceptions to this guide for specific use cases.
+
+1. In the data layer (`repository` classes), add a new `StateFlow` that listens
+ to the service:
+
+ ```kotlin
+ class MobileConnectionsRepositoryImpl {
+ ...
+ val fooVal: StateFlow<Int> =
+ conflatedCallbackFlow {
+ val callback = object : FooServiceCallback(), FooListener {
+ override fun onFooChanged(foo: Int) {
+ trySend(foo)
+ }
+ }
+
+ fooService.registerCallback(callback)
+
+ awaitClose { fooService.unregisterCallback(callback) }
+ }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), FOO_DEFAULT_VAL)
+ }
+ ```
+
+1. In the domain layer (`interactor` classes), either use this new flow to
+ process values, or just expose the flow as-is for the UI layer.
+
+ For example, if `bar` should only be true when `foo` is positive:
+
+ ```kotlin
+ class MobileIconsInteractor {
+ ...
+ val bar: StateFlow<Boolean> =
+ mobileConnectionsRepo
+ .mapLatest { foo -> foo > 0 }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = false)
+ }
+ ```
+
+1. In the UI layer (`viewmodel` classes), update the existing flows to process
+ the new value from the interactor.
+
+ For example, if the icon should be hidden when `bar` is true:
+
+ ```kotlin
+ class MobileIconViewModel {
+ ...
+ iconId: Flow<Int> = combine(
+ iconInteractor.level,
+ iconInteractor.numberOfLevels,
+ iconInteractor.bar,
+ ) { level, numberOfLevels, bar ->
+ if (bar) {
+ null
+ } else {
+ calcIcon(level, numberOfLevels)
+ }
+ }
+ ```
+
+## Demo mode
+
+SystemUI demo mode is a first-class citizen in the new pipeline. It is
+implemented as an entirely separate repository,
+`DemoMobileConnectionsRepository`. When the system moves into demo mode, the
+implementation of the data layer is switched to the demo repository via the
+`MobileRepositorySwitcher` class.
+
+Because the demo mode repositories implement the same interfaces as the
+production classes, any changes made above will have to be implemented for demo
+mode as well.
+
+1. Following from above, if `fooVal` is added to the
+ `MobileConnectionsRepository` interface:
+
+ ```kotlin
+ class DemoMobileConnectionsRepository {
+ private val _fooVal = MutableStateFlow(FOO_DEFAULT_VALUE)
+ override val fooVal: StateFlow<Int> = _fooVal.asStateFlow()
+
+ // Process the state. **See below on how to add the command to the CLI**
+ fun processEnabledMobileState(state: Mobile) {
+ ...
+ _fooVal.value = state.fooVal
+ }
+ }
+ ```
+
+1. (Optional) If you want to enable the command line interface for setting and
+ testing this value in demo mode, you can add parsing logic to
+ `DemoModeMobileConnectionDataSource` and `FakeNetworkEventModel`:
+
+ ```kotlin
+ sealed interface FakeNetworkEventModel {
+ data class Mobile(
+ ...
+ // Add new fields here
+ val fooVal: Int?
+ )
+ }
+ ```
+
+ ```kotlin
+ class DemoModeMobileConnectionDataSource {
+ // Currently, the demo commands are implemented as an extension function on Bundle
+ private fun Bundle.activeMobileEvent(): Mobile {
+ ...
+ val fooVal = getString("fooVal")?.toInt()
+ return Mobile(
+ ...
+ fooVal = fooVal,
+ )
+ }
+ }
+ ```
+
+If step 2 is implemented, then you will be able to pass demo commands via the
+command line:
+
+```
+adb shell am broadcast -a com.android.systemui.demo -e command network -e mobile show -e fooVal <test value>
+```
+
+## Migration plan
+
+For Android U, the new pipeline will be enabled and default. However, the old
+pipeline code will still be around just in case the new pipeline doesn’t do well
+in the testing phase.
+
+For Android V, the old pipeline will be completely removed and the new pipeline
+will be the one source of truth.
+
+Our ask for OEMs is to default to using the new pipeline in Android U. If there
+are customizations that seem difficult to migrate over to the new pipeline,
+please file a bug with us and we’d be more than happy to consult on the best
+solution. The new pipeline was designed with customizability in mind, so our
+hope is that working the new pipeline will be easier and faster.
+
+Note: The new pipeline currently only supports the wifi and mobile icons. The
+other system status bar icons may be migrated to a similar architecture in the
+future.
diff --git a/packages/SystemUI/docs/status-bar-mobile-pipeline.png b/packages/SystemUI/docs/status-bar-mobile-pipeline.png
new file mode 100644
index 0000000..620563d
--- /dev/null
+++ b/packages/SystemUI/docs/status-bar-mobile-pipeline.png
Binary files differ
diff --git a/packages/SystemUI/docs/status-bar-pipeline.png b/packages/SystemUI/docs/status-bar-pipeline.png
new file mode 100644
index 0000000..1c568c9
--- /dev/null
+++ b/packages/SystemUI/docs/status-bar-pipeline.png
Binary files differ
diff --git a/packages/SystemUI/docs/status-bar.png b/packages/SystemUI/docs/status-bar.png
new file mode 100644
index 0000000..3a5af0e
--- /dev/null
+++ b/packages/SystemUI/docs/status-bar.png
Binary files differ
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 7243ca4..d38efcd 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -460,7 +460,6 @@
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 1ef523b..7073f6a 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -661,7 +661,9 @@
<item>7</item> <!-- WAKE_REASON_WAKE_MOTION -->
<item>9</item> <!-- WAKE_REASON_LID -->
<item>10</item> <!-- WAKE_REASON_DISPLAY_GROUP_ADDED -->
- <item>12</item> <!-- WAKE_REASON_UNFOLD_DEVICE -->
+ <item>15</item> <!-- WAKE_REASON_TAP -->
+ <item>16</item> <!-- WAKE_REASON_LIFT -->
+ <item>17</item> <!-- WAKE_REASON_BIOMETRIC -->
</integer-array>
<!-- Whether the communal service should be enabled -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 07e8bad..d6ccdfe 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2301,13 +2301,13 @@
<string name="accessibility_floating_button_docking_tooltip">Move button to the edge to hide it temporarily</string>
<!-- Text for the undo action button of the message view of the accessibility floating menu to perform undo operation. [CHAR LIMIT=30]-->
<string name="accessibility_floating_button_undo">Undo</string>
-
+ <!-- Text for the message view with undo action of the accessibility floating menu to show which feature shortcut was removed. [CHAR LIMIT=30]-->
+ <string name="accessibility_floating_button_undo_message_label_text"><xliff:g id="feature name" example="Magnification">%s</xliff:g> shortcut removed</string>
<!-- Text for the message view with undo action of the accessibility floating menu to show how many features shortcuts were removed. [CHAR LIMIT=30]-->
- <string name="accessibility_floating_button_undo_message_text">{count, plural,
- =1 {{label} shortcut removed}
+ <string name="accessibility_floating_button_undo_message_number_text">{count, plural,
+ =1 {# shortcut removed}
other {# shortcuts removed}
}</string>
-
<!-- Action in accessibility menu to move the accessibility floating button to the top left of the screen. [CHAR LIMIT=30] -->
<string name="accessibility_floating_button_action_move_top_left">Move top left</string>
<!-- Action in accessibility menu to move the accessibility floating button to the top right of the screen. [CHAR LIMIT=30] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 061ca4f..67e3400 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -177,6 +177,8 @@
@Override
public void startAppearAnimation() {
+ setAlpha(1f);
+ setTranslationY(0);
if (mAppearAnimator.isRunning()) {
mAppearAnimator.cancel();
}
@@ -213,7 +215,6 @@
/** Animate subviews according to expansion or time. */
private void animate(float progress) {
- setAlpha(progress);
Interpolator standardDecelerate = Interpolators.STANDARD_DECELERATE;
Interpolator legacyDecelerate = Interpolators.LEGACY_DECELERATE;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index 777d10c..6d54d38 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -182,9 +182,9 @@
if (mFloatingMenu == null) {
if (mFeatureFlags.isEnabled(A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS)) {
final Display defaultDisplay = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
- mFloatingMenu = new MenuViewLayerController(
- mContext.createWindowContext(defaultDisplay,
- TYPE_NAVIGATION_BAR_PANEL, /* options= */ null), mWindowManager,
+ final Context windowContext = mContext.createWindowContext(defaultDisplay,
+ TYPE_NAVIGATION_BAR_PANEL, /* options= */ null);
+ mFloatingMenu = new MenuViewLayerController(windowContext, mWindowManager,
mAccessibilityManager);
} else {
mFloatingMenu = new AccessibilityFloatingMenu(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
index ee048e1..c2bc140 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
@@ -19,8 +19,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
-import android.content.ComponentCallbacks;
-import android.content.res.Configuration;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
@@ -34,7 +32,7 @@
* Controls the interaction between {@link MagnetizedObject} and
* {@link MagnetizedObject.MagneticTarget}.
*/
-class DismissAnimationController implements ComponentCallbacks {
+class DismissAnimationController {
private static final float COMPLETELY_OPAQUE = 1.0f;
private static final float COMPLETELY_TRANSPARENT = 0.0f;
private static final float CIRCLE_VIEW_DEFAULT_SCALE = 1.0f;
@@ -105,16 +103,6 @@
mMagnetizedObject.addTarget(magneticTarget);
}
- @Override
- public void onConfigurationChanged(@NonNull Configuration newConfig) {
- updateResources();
- }
-
- @Override
- public void onLowMemory() {
- // Do nothing
- }
-
void showDismissView(boolean show) {
if (show) {
mDismissView.show();
@@ -165,7 +153,7 @@
}
}
- private void updateResources() {
+ void updateResources() {
final float maxDismissSize = mDismissView.getResources().getDimensionPixelSize(
R.dimen.dismiss_circle_size);
mMinDismissSize = mDismissView.getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipView.java
index 4400534..5ec024e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipView.java
@@ -21,6 +21,7 @@
import static android.view.View.MeasureSpec.UNSPECIFIED;
import android.annotation.SuppressLint;
+import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -48,7 +49,7 @@
* . It's just shown on the left or right of the anchor view.
*/
@SuppressLint("ViewConstructor")
-class MenuEduTooltipView extends FrameLayout {
+class MenuEduTooltipView extends FrameLayout implements ComponentCallbacks {
private int mFontSize;
private int mTextViewMargin;
private int mTextViewPadding;
@@ -73,9 +74,7 @@
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
-
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
updateResources();
updateMessageView();
updateArrowView();
@@ -83,6 +82,25 @@
updateLocationAndVisibility();
}
+ @Override
+ public void onLowMemory() {
+ // Do nothing.
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ getContext().registerComponentCallbacks(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ getContext().unregisterComponentCallbacks(this);
+ }
+
void show(CharSequence message) {
mMessageView.setText(message);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
index 05e1d3f..f79c3d2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
@@ -30,13 +30,21 @@
import android.annotation.FloatRange;
import android.annotation.IntDef;
+import android.content.ComponentCallbacks;
import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
import android.database.ContentObserver;
+import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.Log;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.annotation.NonNull;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.annotations.VisibleForTesting;
@@ -50,6 +58,9 @@
* Stores and observe the settings contents for the menu view.
*/
class MenuInfoRepository {
+ private static final String TAG = "MenuInfoRepository";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Build.IS_DEBUGGABLE;
+
@FloatRange(from = 0.0, to = 1.0)
private static final float DEFAULT_MENU_POSITION_X_PERCENT = 1.0f;
@@ -60,6 +71,10 @@
private static final int DEFAULT_MIGRATION_TOOLTIP_VALUE_PROMPT = MigrationPrompt.DISABLED;
private final Context mContext;
+ private final Configuration mConfiguration;
+ private final AccessibilityManager mAccessibilityManager;
+ private final AccessibilityManager.AccessibilityServicesStateChangeListener
+ mA11yServicesStateChangeListener = manager -> onTargetFeaturesChanged();
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final OnSettingsContentsChanged mSettingsContentsCallback;
private Position mPercentagePosition;
@@ -74,12 +89,12 @@
int ENABLED = 1;
}
- private final ContentObserver mMenuTargetFeaturesContentObserver =
+ @VisibleForTesting
+ final ContentObserver mMenuTargetFeaturesContentObserver =
new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
- mSettingsContentsCallback.onTargetFeaturesChanged(
- getTargets(mContext, ACCESSIBILITY_BUTTON));
+ onTargetFeaturesChanged();
}
};
@@ -102,8 +117,35 @@
}
};
- MenuInfoRepository(Context context, OnSettingsContentsChanged settingsContentsChanged) {
+ @VisibleForTesting
+ final ComponentCallbacks mComponentCallbacks = new ComponentCallbacks() {
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ final int diff = newConfig.diff(mConfiguration);
+
+ if (DEBUG) {
+ Log.d(TAG, "onConfigurationChanged = " + Configuration.configurationDiffToString(
+ diff));
+ }
+
+ if ((diff & ActivityInfo.CONFIG_LOCALE) != 0) {
+ onTargetFeaturesChanged();
+ }
+
+ mConfiguration.setTo(newConfig);
+ }
+
+ @Override
+ public void onLowMemory() {
+ // Do nothing.
+ }
+ };
+
+ MenuInfoRepository(Context context, AccessibilityManager accessibilityManager,
+ OnSettingsContentsChanged settingsContentsChanged) {
mContext = context;
+ mAccessibilityManager = accessibilityManager;
+ mConfiguration = new Configuration(context.getResources().getConfiguration());
mSettingsContentsCallback = settingsContentsChanged;
mPercentagePosition = getStartPosition();
@@ -172,6 +214,11 @@
UserHandle.USER_CURRENT);
}
+ private void onTargetFeaturesChanged() {
+ mSettingsContentsCallback.onTargetFeaturesChanged(
+ getTargets(mContext, ACCESSIBILITY_BUTTON));
+ }
+
private Position getStartPosition() {
final String absolutePositionString = Prefs.getString(mContext,
Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null);
@@ -181,7 +228,7 @@
: Position.fromString(absolutePositionString);
}
- void registerContentObservers() {
+ void registerObserversAndCallbacks() {
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
/* notifyForDescendants */ false, mMenuTargetFeaturesContentObserver,
@@ -202,12 +249,20 @@
Settings.Secure.getUriFor(ACCESSIBILITY_FLOATING_MENU_OPACITY),
/* notifyForDescendants */ false, mMenuFadeOutContentObserver,
UserHandle.USER_CURRENT);
+ mContext.registerComponentCallbacks(mComponentCallbacks);
+
+ mAccessibilityManager.addAccessibilityServicesStateChangeListener(
+ mA11yServicesStateChangeListener);
}
- void unregisterContentObservers() {
+ void unregisterObserversAndCallbacks() {
mContext.getContentResolver().unregisterContentObserver(mMenuTargetFeaturesContentObserver);
mContext.getContentResolver().unregisterContentObserver(mMenuSizeContentObserver);
mContext.getContentResolver().unregisterContentObserver(mMenuFadeOutContentObserver);
+ mContext.unregisterComponentCallbacks(mComponentCallbacks);
+
+ mAccessibilityManager.removeAccessibilityServicesStateChangeListener(
+ mA11yServicesStateChangeListener);
}
interface OnSettingsContentsChanged {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
index 9875ad0..3e2b06b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
@@ -20,6 +20,7 @@
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.annotation.IntDef;
+import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -33,6 +34,8 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+
import com.android.settingslib.Utils;
import com.android.systemui.R;
@@ -44,7 +47,7 @@
* the {@link MenuView}.
*/
class MenuMessageView extends LinearLayout implements
- ViewTreeObserver.OnComputeInternalInsetsListener {
+ ViewTreeObserver.OnComputeInternalInsetsListener, ComponentCallbacks {
private final TextView mTextView;
private final Button mUndoButton;
@@ -61,6 +64,7 @@
MenuMessageView(Context context) {
super(context);
+ setLayoutDirection(LAYOUT_DIRECTION_LOCALE);
setVisibility(GONE);
mTextView = new TextView(context);
@@ -72,13 +76,16 @@
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
-
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
updateResources();
}
@Override
+ public void onLowMemory() {
+ // Do nothing.
+ }
+
+ @Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -92,6 +99,7 @@
updateResources();
+ getContext().registerComponentCallbacks(this);
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
}
@@ -99,6 +107,7 @@
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
+ getContext().unregisterComponentCallbacks(this);
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 986aa51..28269d9 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -19,6 +19,7 @@
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.annotation.SuppressLint;
+import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PointF;
@@ -46,7 +47,7 @@
*/
@SuppressLint("ViewConstructor")
class MenuView extends FrameLayout implements
- ViewTreeObserver.OnComputeInternalInsetsListener {
+ ViewTreeObserver.OnComputeInternalInsetsListener, ComponentCallbacks {
private static final int INDEX_MENU_ITEM = 0;
private final List<AccessibilityTarget> mTargetFeatures = new ArrayList<>();
private final AccessibilityTargetAdapter mAdapter;
@@ -106,14 +107,31 @@
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
-
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
loadLayoutResources();
mTargetFeaturesView.setOverScrollMode(mMenuViewAppearance.getMenuScrollMode());
}
+ @Override
+ public void onLowMemory() {
+ // Do nothing.
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ getContext().registerComponentCallbacks(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ getContext().unregisterComponentCallbacks(this);
+ }
+
void setOnTargetFeaturesChangeListener(OnTargetFeaturesChangeListener listener) {
mFeaturesChangeListener = listener;
}
@@ -299,7 +317,7 @@
mMenuViewModel.getSizeTypeData().observeForever(mSizeTypeObserver);
mMenuViewModel.getMoveToTuckedData().observeForever(mMoveToTuckedObserver);
setVisibility(VISIBLE);
- mMenuViewModel.registerContentObservers();
+ mMenuViewModel.registerObserversAndCallbacks();
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
getViewTreeObserver().addOnDrawListener(mSystemGestureExcludeUpdater);
}
@@ -312,7 +330,7 @@
mMenuViewModel.getTargetFeaturesData().removeObserver(mTargetFeaturesObserver);
mMenuViewModel.getSizeTypeData().removeObserver(mSizeTypeObserver);
mMenuViewModel.getMoveToTuckedData().removeObserver(mMoveToTuckedObserver);
- mMenuViewModel.unregisterContentObservers();
+ mMenuViewModel.unregisterObserversAndCallbacks();
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
getViewTreeObserver().removeOnDrawListener(mSystemGestureExcludeUpdater);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 6f5b39c..15a8d09 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -25,20 +25,22 @@
import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
import static com.android.systemui.accessibility.floatingmenu.MenuMessageView.Index;
+import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
import android.annotation.StringDef;
import android.annotation.SuppressLint;
+import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
-import android.util.PluralsMessageFormatter;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
@@ -61,9 +63,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import java.util.Optional;
/**
@@ -74,7 +74,7 @@
*/
@SuppressLint("ViewConstructor")
class MenuViewLayer extends FrameLayout implements
- ViewTreeObserver.OnComputeInternalInsetsListener, View.OnClickListener {
+ ViewTreeObserver.OnComputeInternalInsetsListener, View.OnClickListener, ComponentCallbacks {
private static final int SHOW_MESSAGE_DELAY_MS = 3000;
private final WindowManager mWindowManager;
@@ -137,8 +137,8 @@
AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
serviceInfoList.forEach(info -> {
if (getAccessibilityServiceFragmentType(info) == INVISIBLE_TOGGLE) {
- setAccessibilityServiceState(mContext, info.getComponentName(), /* enabled= */
- false);
+ setAccessibilityServiceState(getContext(),
+ info.getComponentName(), /* enabled= */ false);
}
});
@@ -150,11 +150,14 @@
AccessibilityManager accessibilityManager, IAccessibilityFloatingMenu floatingMenu) {
super(context);
+ // Simplifies the translation positioning and animations
+ setLayoutDirection(LAYOUT_DIRECTION_LTR);
+
mWindowManager = windowManager;
mAccessibilityManager = accessibilityManager;
mFloatingMenu = floatingMenu;
- mMenuViewModel = new MenuViewModel(context);
+ mMenuViewModel = new MenuViewModel(context, accessibilityManager);
mMenuViewAppearance = new MenuViewAppearance(context, windowManager);
mMenuView = new MenuView(context, mMenuViewModel, mMenuViewAppearance);
mMenuAnimationController = mMenuView.getMenuAnimationController();
@@ -209,20 +212,30 @@
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
mDismissView.updateResources();
+ mDismissAnimationController.updateResources();
+ }
+
+ @Override
+ public void onLowMemory() {
+ // Do nothing.
}
private String getMessageText(List<AccessibilityTarget> newTargetFeatures) {
Preconditions.checkArgument(newTargetFeatures.size() > 0,
"The list should at least have one feature.");
- final Map<String, Object> arguments = new HashMap<>();
- arguments.put("count", newTargetFeatures.size());
- arguments.put("label", newTargetFeatures.get(0).getLabel());
- return PluralsMessageFormatter.format(getResources(), arguments,
- R.string.accessibility_floating_button_undo_message_text);
+ final int featuresSize = newTargetFeatures.size();
+ final Resources resources = getResources();
+ if (featuresSize == 1) {
+ return resources.getString(
+ R.string.accessibility_floating_button_undo_message_label_text,
+ newTargetFeatures.get(0).getLabel());
+ }
+
+ return icuMessageFormat(resources,
+ R.string.accessibility_floating_button_undo_message_number_text, featuresSize);
}
@Override
@@ -246,7 +259,7 @@
mMenuViewModel.getMigrationTooltipVisibilityData().observeForever(
mMigrationTooltipObserver);
mMessageView.setUndoListener(view -> undo());
- mContext.registerComponentCallbacks(mDismissAnimationController);
+ getContext().registerComponentCallbacks(this);
}
@Override
@@ -261,7 +274,7 @@
mMenuViewModel.getMigrationTooltipVisibilityData().removeObserver(
mMigrationTooltipObserver);
mHandler.removeCallbacksAndMessages(/* token= */ null);
- mContext.unregisterComponentCallbacks(mDismissAnimationController);
+ getContext().unregisterComponentCallbacks(this);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
index 5fea3b0..eec8467 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
@@ -17,6 +17,7 @@
package com.android.systemui.accessibility.floatingmenu;
import android.content.Context;
+import android.view.accessibility.AccessibilityManager;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
@@ -41,8 +42,9 @@
private final MutableLiveData<Position> mPercentagePositionData = new MutableLiveData<>();
private final MenuInfoRepository mInfoRepository;
- MenuViewModel(Context context) {
- mInfoRepository = new MenuInfoRepository(context, /* settingsContentsChanged= */ this);
+ MenuViewModel(Context context, AccessibilityManager accessibilityManager) {
+ mInfoRepository = new MenuInfoRepository(context,
+ accessibilityManager, /* settingsContentsChanged= */ this);
}
@Override
@@ -111,11 +113,11 @@
return mTargetFeaturesData;
}
- void registerContentObservers() {
- mInfoRepository.registerContentObservers();
+ void registerObserversAndCallbacks() {
+ mInfoRepository.registerObserversAndCallbacks();
}
- void unregisterContentObservers() {
- mInfoRepository.unregisterContentObservers();
+ void unregisterObserversAndCallbacks() {
+ mInfoRepository.unregisterObserversAndCallbacks();
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 76c1158..96bfa43 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -643,6 +643,7 @@
@Provides
@Singleton
+ @Nullable
static BluetoothAdapter provideBluetoothAdapter(BluetoothManager bluetoothManager) {
return bluetoothManager.getAdapter();
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 44fd353..764a3d0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -419,7 +419,13 @@
unreleasedFlag(1204, "persist.wm.debug.predictive_back_sysui_enable", teamfood = true)
// TODO(b/255697805): Tracking Bug
- @JvmField val TRACKPAD_GESTURE_BACK = unreleasedFlag(1205, "trackpad_gesture_back", teamfood = false)
+ @JvmField
+ val TRACKPAD_GESTURE_BACK = unreleasedFlag(1205, "trackpad_gesture_back", teamfood = false)
+
+ // TODO(b/263826204): Tracking Bug
+ @JvmField
+ val WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM =
+ unreleasedFlag(1206, "persist.wm.debug.predictive_back_bouncer_anim", teamfood = true)
// 1300 - screenshots
// TODO(b/254512719): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 2a3a33e..2cf5fb9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -77,6 +77,7 @@
/** Runnable to show the primary bouncer. */
val showRunnable = Runnable {
+ repository.setPrimaryVisible(true)
repository.setPrimaryShow(
KeyguardBouncerModel(
promptReason = repository.bouncerPromptReason ?: 0,
@@ -85,7 +86,6 @@
)
)
repository.setPrimaryShowingSoon(false)
- repository.setPrimaryVisible(true)
primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.VISIBLE)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
index 4d914fe..15fed32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -49,9 +49,9 @@
featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON_BACKEND) || useNewWifiIcon()
/**
- * Returns true if we should apply some coloring to the wifi icon that was rendered with the new
+ * Returns true if we should apply some coloring to the icons that were rendered with the new
* pipeline to help with debugging.
*/
- fun useWifiDebugColoring(): Boolean =
+ fun useDebugColoring(): Boolean =
featureFlags.isEnabled(Flags.NEW_STATUS_BAR_ICONS_DEBUG_COLORING)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 0d01715..0993ab370 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.dagger
+import android.net.wifi.WifiManager
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.table.TableLogBuffer
@@ -35,8 +36,11 @@
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyImpl
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.DisabledWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import dagger.Binds
@@ -78,9 +82,23 @@
@ClassKey(MobileUiAdapter::class)
abstract fun bindFeature(impl: MobileUiAdapter): CoreStartable
- @Module
companion object {
- @JvmStatic
+ @Provides
+ @SysUISingleton
+ fun provideRealWifiRepository(
+ wifiManager: WifiManager?,
+ disabledWifiRepository: DisabledWifiRepository,
+ wifiRepositoryImplFactory: WifiRepositoryImpl.Factory,
+ ): RealWifiRepository {
+ // If we have a null [WifiManager], then the wifi repository should be permanently
+ // disabled.
+ return if (wifiManager == null) {
+ disabledWifiRepository
+ } else {
+ wifiRepositoryImplFactory.create(wifiManager)
+ }
+ }
+
@Provides
@SysUISingleton
@WifiTableLog
@@ -88,7 +106,6 @@
return factory.create("WifiTableLog", 100)
}
- @JvmStatic
@Provides
@SysUISingleton
@AirplaneTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
index dd93541..5960387 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.pipeline.mobile.data.model
import android.telephony.Annotation.NetworkType
-import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
/**
@@ -26,21 +25,17 @@
* methods on [MobileMappingsProxy] to generate an icon lookup key.
*/
sealed interface ResolvedNetworkType {
- @NetworkType val type: Int
val lookupKey: String
object UnknownNetworkType : ResolvedNetworkType {
- override val type: Int = NETWORK_TYPE_UNKNOWN
override val lookupKey: String = "unknown"
}
data class DefaultNetworkType(
- @NetworkType override val type: Int,
override val lookupKey: String,
) : ResolvedNetworkType
data class OverrideNetworkType(
- @NetworkType override val type: Int,
override val lookupKey: String,
) : ResolvedNetworkType
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 40e9ba1..d04996b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.telephony.SubscriptionInfo
-import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
import com.android.systemui.log.table.TableLogBuffer
@@ -52,13 +51,12 @@
* listener + model.
*/
val connectionInfo: Flow<MobileConnectionModel>
+
+ /** The total number of levels. Used with [SignalDrawable]. */
+ val numberOfLevels: StateFlow<Int>
+
/** Observable tracking [TelephonyManager.isDataConnectionAllowed] */
val dataEnabled: StateFlow<Boolean>
- /**
- * True if this connection represents the default subscription per
- * [SubscriptionManager.getDefaultDataSubscriptionId]
- */
- val isDefaultDataSubscription: StateFlow<Boolean>
/**
* See [TelephonyManager.getCdmaEnhancedRoamingIndicatorDisplayNumber]. This bit only matters if
@@ -70,4 +68,9 @@
/** The service provider name for this network connection, or the default name */
val networkName: StateFlow<NetworkNameModel>
+
+ companion object {
+ /** The default number of levels to use for [numberOfLevels]. */
+ const val DEFAULT_NUM_LEVELS = 4
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index 498c0b9..97b4c2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -18,7 +18,6 @@
import android.provider.Settings
import android.telephony.CarrierConfigManager
-import android.telephony.SubscriptionManager
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.MobileMappings.Config
@@ -38,9 +37,6 @@
/** Observable for the subscriptionId of the current mobile data connection */
val activeMobileDataSubscriptionId: StateFlow<Int>
- /** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */
- val defaultDataSubId: StateFlow<Int>
-
/** The current connectivity status for the default mobile network connection */
val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index db9d24f..0c8593d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -139,11 +139,6 @@
override val defaultMobileIconGroup: Flow<SignalIcon.MobileIconGroup> =
activeRepo.flatMapLatest { it.defaultMobileIconGroup }
- override val defaultDataSubId: StateFlow<Int> =
- activeRepo
- .flatMapLatest { it.defaultDataSubId }
- .stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.defaultDataSubId.value)
-
override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
activeRepo
.flatMapLatest { it.defaultMobileNetworkConnectivity }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index 0b5f9d5..0e164e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -34,6 +34,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile
@@ -139,14 +140,6 @@
private fun <K, V> Map<K, V>.reverse() = entries.associateBy({ it.value }) { it.key }
- // TODO(b/261029387): add a command for this value
- override val defaultDataSubId =
- activeMobileDataSubscriptionId.stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- INVALID_SUBSCRIPTION_ID
- )
-
// TODO(b/261029387): not yet supported
override val defaultMobileNetworkConnectivity = MutableStateFlow(MobileConnectivityModel())
@@ -199,7 +192,6 @@
val connection = getRepoForSubId(subId)
// This is always true here, because we split out disabled states at the data-source level
connection.dataEnabled.value = true
- connection.isDefaultDataSubscription.value = state.dataType != null
connection.networkName.value = NetworkNameModel.Derived(state.name)
connection.cdmaRoaming.value = state.roaming
@@ -261,15 +253,13 @@
private fun SignalIcon.MobileIconGroup?.toResolvedNetworkType(): ResolvedNetworkType {
val key = mobileMappingsReverseLookup.value[this] ?: "dis"
- return DefaultNetworkType(DEMO_NET_TYPE, key)
+ return DefaultNetworkType(key)
}
companion object {
private const val TAG = "DemoMobileConnectionsRepo"
private const val DEFAULT_SUB_ID = 1
-
- private const val DEMO_NET_TYPE = 1234
}
}
@@ -279,9 +269,9 @@
) : MobileConnectionRepository {
override val connectionInfo = MutableStateFlow(MobileConnectionModel())
- override val dataEnabled = MutableStateFlow(true)
+ override val numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
- override val isDefaultDataSubscription = MutableStateFlow(true)
+ override val dataEnabled = MutableStateFlow(true)
override val cdmaRoaming = MutableStateFlow(false)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 5cfff82..0fa0fea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -48,6 +48,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
@@ -63,6 +64,7 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
@@ -78,7 +80,6 @@
private val telephonyManager: TelephonyManager,
private val globalSettings: GlobalSettings,
broadcastDispatcher: BroadcastDispatcher,
- defaultDataSubId: StateFlow<Int>,
globalMobileDataSettingChangedEvent: Flow<Unit>,
mobileMappingsProxy: MobileMappingsProxy,
bgDispatcher: CoroutineDispatcher,
@@ -185,14 +186,12 @@
OVERRIDE_NETWORK_TYPE_NONE
) {
DefaultNetworkType(
- telephonyDisplayInfo.networkType,
mobileMappingsProxy.toIconKey(
telephonyDisplayInfo.networkType
)
)
} else {
OverrideNetworkType(
- telephonyDisplayInfo.overrideNetworkType,
mobileMappingsProxy.toIconKeyOverride(
telephonyDisplayInfo.overrideNetworkType
)
@@ -214,6 +213,12 @@
.stateIn(scope, SharingStarted.WhileSubscribed(), state)
}
+ // This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL]
+ // once it's wired up inside of [CarrierConfigTracker].
+ override val numberOfLevels: StateFlow<Int> =
+ flowOf(DEFAULT_NUM_LEVELS)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_NUM_LEVELS)
+
/** Produces whenever the mobile data setting changes for this subId */
private val localMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
val observer =
@@ -284,20 +289,6 @@
private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
- override val isDefaultDataSubscription: StateFlow<Boolean> = run {
- val initialValue = defaultDataSubId.value == subId
- defaultDataSubId
- .mapLatest { it == subId }
- .distinctUntilChanged()
- .logDiffsForTable(
- mobileLogger,
- columnPrefix = "",
- columnName = "isDefaultDataSub",
- initialValue = initialValue,
- )
- .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue)
- }
-
class Factory
@Inject
constructor(
@@ -315,7 +306,6 @@
subId: Int,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
- defaultDataSubId: StateFlow<Int>,
globalMobileDataSettingChangedEvent: Flow<Unit>,
): MobileConnectionRepository {
val mobileLogger = logFactory.create(tableBufferLogName(subId), 100)
@@ -328,7 +318,6 @@
telephonyManager.createForSubscriptionId(subId),
globalSettings,
broadcastDispatcher,
- defaultDataSubId,
globalMobileDataSettingChangedEvent,
mobileMappingsProxy,
bgDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index d407abe..c88c700 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -35,7 +35,6 @@
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
import android.telephony.TelephonyManager
import androidx.annotation.VisibleForTesting
-import com.android.internal.telephony.PhoneConstants
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.MobileMappings.Config
import com.android.systemui.R
@@ -60,7 +59,6 @@
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -142,24 +140,10 @@
.logInputChange(logger, "onActiveDataSubscriptionIdChanged")
.stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
- private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
- MutableSharedFlow(extraBufferCapacity = 1)
-
- override val defaultDataSubId: StateFlow<Int> =
+ private val defaultDataSubIdChangedEvent =
broadcastDispatcher
- .broadcastFlow(
- IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
- ) { intent, _ ->
- intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
- }
- .distinctUntilChanged()
+ .broadcastFlow(IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED))
.logInputChange(logger, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED")
- .onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
- .stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- SubscriptionManager.getDefaultDataSubscriptionId()
- )
private val carrierConfigChangedEvent =
broadcastDispatcher
@@ -167,7 +151,7 @@
.logInputChange(logger, "ACTION_CARRIER_CONFIG_CHANGED")
override val defaultDataSubRatConfig: StateFlow<Config> =
- merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
+ merge(defaultDataSubIdChangedEvent, carrierConfigChangedEvent)
.mapLatest { Config.readConfig(context) }
.distinctUntilChanged()
.logInputChange(logger, "defaultDataSubRatConfig")
@@ -272,7 +256,6 @@
subId,
defaultNetworkName,
networkNameSeparator,
- defaultDataSubId,
globalMobileDataSettingChangedEvent,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 31ac7a1..9427c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -23,12 +23,11 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.util.CarrierConfigTracker
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@@ -171,11 +170,12 @@
}
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
- /**
- * This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL]
- * once it's wired up inside of [CarrierConfigTracker]
- */
- override val numberOfLevels: StateFlow<Int> = MutableStateFlow(4)
+ override val numberOfLevels: StateFlow<Int> =
+ connectionRepository.numberOfLevels.stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ connectionRepository.numberOfLevels.value,
+ )
override val isDataConnected: StateFlow<Boolean> =
connectionInfo
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index ab442b5..3e81c7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -19,6 +19,7 @@
import android.content.res.ColorStateList
import android.view.View
import android.view.View.GONE
+import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.widget.ImageView
@@ -30,7 +31,13 @@
import com.android.systemui.R
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
@@ -40,7 +47,8 @@
fun bind(
view: ViewGroup,
viewModel: LocationBasedMobileViewModel,
- ) {
+ ): ModernStatusBarViewBinding {
+ val mobileGroupView = view.requireViewById<ViewGroup>(R.id.mobile_group)
val activityContainer = view.requireViewById<View>(R.id.inout_container)
val activityIn = view.requireViewById<ImageView>(R.id.mobile_in)
val activityOut = view.requireViewById<ImageView>(R.id.mobile_out)
@@ -49,12 +57,39 @@
val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
val roamingView = view.requireViewById<ImageView>(R.id.mobile_roaming)
val roamingSpace = view.requireViewById<Space>(R.id.mobile_roaming_space)
+ val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot)
view.isVisible = true
iconView.isVisible = true
+ // TODO(b/238425913): We should log this visibility state.
+ @StatusBarIconView.VisibleState
+ val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN)
+
+ val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
+ val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
+
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ visibilityState.collect { state ->
+ when (state) {
+ STATE_ICON -> {
+ mobileGroupView.visibility = VISIBLE
+ dotView.visibility = GONE
+ }
+ STATE_DOT -> {
+ mobileGroupView.visibility = INVISIBLE
+ dotView.visibility = VISIBLE
+ }
+ STATE_HIDDEN -> {
+ mobileGroupView.visibility = INVISIBLE
+ dotView.visibility = INVISIBLE
+ }
+ }
+ }
+ }
+
// Set the icon for the triangle
launch {
viewModel.iconId.distinctUntilChanged().collect { iconId ->
@@ -89,15 +124,43 @@
// Set the tint
launch {
- viewModel.tint.collect { tint ->
+ iconTint.collect { tint ->
val tintList = ColorStateList.valueOf(tint)
iconView.imageTintList = tintList
networkTypeView.imageTintList = tintList
roamingView.imageTintList = tintList
activityIn.imageTintList = tintList
activityOut.imageTintList = tintList
+ dotView.setDecorColor(tint)
}
}
+
+ launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } }
+ }
+ }
+
+ return object : ModernStatusBarViewBinding {
+ override fun getShouldIconBeVisible(): Boolean {
+ // If this view model exists, then the icon should be visible.
+ return true
+ }
+
+ override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
+ visibilityState.value = state
+ }
+
+ override fun onIconTintChanged(newTint: Int) {
+ if (viewModel.useDebugColoring) {
+ return
+ }
+ iconTint.value = newTint
+ }
+
+ override fun onDecorTintChanged(newTint: Int) {
+ if (viewModel.useDebugColoring) {
+ return
+ }
+ decorTint.value = newTint
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
index e86fee2..ed9a188 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
@@ -17,50 +17,20 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.view
import android.content.Context
-import android.graphics.Rect
import android.util.AttributeSet
import android.view.LayoutInflater
import com.android.systemui.R
-import com.android.systemui.statusbar.BaseStatusBarFrameLayout
-import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
-import java.util.ArrayList
+import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView
class ModernStatusBarMobileView(
context: Context,
attrs: AttributeSet?,
-) : BaseStatusBarFrameLayout(context, attrs) {
+) : ModernStatusBarView(context, attrs) {
var subId: Int = -1
- private lateinit var slot: String
- override fun getSlot() = slot
-
- override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
- // TODO
- }
-
- override fun setStaticDrawableColor(color: Int) {
- // TODO
- }
-
- override fun setDecorColor(color: Int) {
- // TODO
- }
-
- override fun setVisibleState(state: Int, animate: Boolean) {
- // TODO
- }
-
- override fun getVisibleState(): Int {
- return STATE_ICON
- }
-
- override fun isIconVisible(): Boolean {
- return true
- }
-
companion object {
/**
@@ -77,9 +47,8 @@
.inflate(R.layout.status_bar_mobile_signal_group_new, null)
as ModernStatusBarMobileView)
.also {
- it.slot = slot
it.subId = viewModel.subscriptionId
- MobileIconBinder.bind(it, viewModel)
+ it.initView(slot) { MobileIconBinder.bind(it, viewModel) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
index b0dc41f..24cd930 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
@@ -18,11 +18,7 @@
import android.graphics.Color
import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOf
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
/**
* A view model for an individual mobile icon that embeds the notion of a [StatusBarLocation]. This
@@ -33,50 +29,51 @@
*/
abstract class LocationBasedMobileViewModel(
val commonImpl: MobileIconViewModelCommon,
- val logger: ConnectivityPipelineLogger,
+ statusBarPipelineFlags: StatusBarPipelineFlags,
+ debugTint: Int,
) : MobileIconViewModelCommon by commonImpl {
- abstract val tint: Flow<Int>
+ val useDebugColoring: Boolean = statusBarPipelineFlags.useDebugColoring()
+
+ val defaultColor: Int =
+ if (useDebugColoring) {
+ debugTint
+ } else {
+ Color.WHITE
+ }
companion object {
fun viewModelForLocation(
commonImpl: MobileIconViewModelCommon,
- logger: ConnectivityPipelineLogger,
+ statusBarPipelineFlags: StatusBarPipelineFlags,
loc: StatusBarLocation,
): LocationBasedMobileViewModel =
when (loc) {
- StatusBarLocation.HOME -> HomeMobileIconViewModel(commonImpl, logger)
- StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModel(commonImpl, logger)
- StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl, logger)
+ StatusBarLocation.HOME ->
+ HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags)
+ StatusBarLocation.KEYGUARD ->
+ KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags)
+ StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
}
}
}
class HomeMobileIconViewModel(
commonImpl: MobileIconViewModelCommon,
- logger: ConnectivityPipelineLogger,
-) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) {
- override val tint: Flow<Int> =
- flowOf(Color.CYAN)
- .distinctUntilChanged()
- .logOutputChange(logger, "HOME tint(${commonImpl.subscriptionId})")
-}
+ statusBarPipelineFlags: StatusBarPipelineFlags,
+) :
+ MobileIconViewModelCommon,
+ LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.CYAN)
class QsMobileIconViewModel(
commonImpl: MobileIconViewModelCommon,
- logger: ConnectivityPipelineLogger,
-) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) {
- override val tint: Flow<Int> =
- flowOf(Color.GREEN)
- .distinctUntilChanged()
- .logOutputChange(logger, "QS tint(${commonImpl.subscriptionId})")
-}
+ statusBarPipelineFlags: StatusBarPipelineFlags,
+) :
+ MobileIconViewModelCommon,
+ LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.GREEN)
class KeyguardMobileIconViewModel(
commonImpl: MobileIconViewModelCommon,
- logger: ConnectivityPipelineLogger,
-) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) {
- override val tint: Flow<Int> =
- flowOf(Color.MAGENTA)
- .distinctUntilChanged()
- .logOutputChange(logger, "KEYGUARD tint(${commonImpl.subscriptionId})")
-}
+ statusBarPipelineFlags: StatusBarPipelineFlags,
+) :
+ MobileIconViewModelCommon,
+ LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.MAGENTA)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index b9318b1..24370d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -19,6 +19,7 @@
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
@@ -41,6 +42,7 @@
private val logger: ConnectivityPipelineLogger,
private val constants: ConnectivityConstants,
@Application private val scope: CoroutineScope,
+ private val statusBarPipelineFlags: StatusBarPipelineFlags,
) {
@VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>()
@@ -60,7 +62,11 @@
)
.also { mobileIconSubIdCache[subId] = it }
- return LocationBasedMobileViewModel.viewModelForLocation(common, logger, location)
+ return LocationBasedMobileViewModel.viewModelForLocation(
+ common,
+ statusBarPipelineFlags,
+ location,
+ )
}
private fun removeInvalidModelsFromCache(subIds: List<Int>) {
@@ -75,6 +81,7 @@
private val logger: ConnectivityPipelineLogger,
private val constants: ConnectivityConstants,
@Application private val scope: CoroutineScope,
+ private val statusBarPipelineFlags: StatusBarPipelineFlags,
) {
fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel {
return MobileIconsViewModel(
@@ -83,6 +90,7 @@
logger,
constants,
scope,
+ statusBarPipelineFlags,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
new file mode 100644
index 0000000..f67876b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 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.statusbar.pipeline.shared.ui.binder
+
+import com.android.systemui.statusbar.StatusBarIconView
+
+/**
+ * Defines interface for an object that acts as the binding between a modern status bar view and its
+ * view-model.
+ *
+ * Users of the view binder classes in the modern status bar pipeline should use this to control the
+ * binder after it is bound.
+ */
+interface ModernStatusBarViewBinding {
+ /** Returns true if the icon should be visible and false otherwise. */
+ fun getShouldIconBeVisible(): Boolean
+
+ /** Notifies that the visibility state has changed. */
+ fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
+
+ /** Notifies that the icon tint has been updated. */
+ fun onIconTintChanged(newTint: Int)
+
+ /** Notifies that the decor tint has been updated (used only for the dot). */
+ fun onDecorTintChanged(newTint: Int)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
new file mode 100644
index 0000000..cc0ec54
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2022 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.statusbar.pipeline.shared.ui.view
+
+import android.content.Context
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.view.Gravity
+import com.android.systemui.R
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.statusbar.BaseStatusBarFrameLayout
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
+
+/**
+ * A new and more modern implementation of [BaseStatusBarFrameLayout] that gets updated by view
+ * binders communicating via [ModernStatusBarViewBinding].
+ */
+open class ModernStatusBarView(context: Context, attrs: AttributeSet?) :
+ BaseStatusBarFrameLayout(context, attrs) {
+
+ private lateinit var slot: String
+ private lateinit var binding: ModernStatusBarViewBinding
+
+ @StatusBarIconView.VisibleState
+ private var iconVisibleState: Int = STATE_HIDDEN
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ binding.onVisibilityStateChanged(value)
+ }
+
+ override fun getSlot() = slot
+
+ override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
+ val newTint = DarkIconDispatcher.getTint(areas, this, tint)
+ binding.onIconTintChanged(newTint)
+ binding.onDecorTintChanged(newTint)
+ }
+
+ override fun setStaticDrawableColor(color: Int) {
+ binding.onIconTintChanged(color)
+ }
+
+ override fun setDecorColor(color: Int) {
+ binding.onDecorTintChanged(color)
+ }
+
+ override fun setVisibleState(@StatusBarIconView.VisibleState state: Int, animate: Boolean) {
+ iconVisibleState = state
+ }
+
+ @StatusBarIconView.VisibleState
+ override fun getVisibleState(): Int {
+ return iconVisibleState
+ }
+
+ override fun isIconVisible(): Boolean {
+ return binding.getShouldIconBeVisible()
+ }
+
+ /**
+ * Initializes this view.
+ *
+ * Creates a dot view, and uses [bindingCreator] to get and set the binding.
+ */
+ fun initView(slot: String, bindingCreator: () -> ModernStatusBarViewBinding) {
+ // The dot view requires [slot] to be set, and the [binding] may require an instantiated dot
+ // view. So, this is the required order.
+ this.slot = slot
+ initDotView()
+ this.binding = bindingCreator.invoke()
+ }
+
+ /**
+ * Creates a [StatusBarIconView] that is always in DOT mode and adds it to this view.
+ *
+ * Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView] and
+ * [com.android.systemui.statusbar.StatusBarMobileView].
+ */
+ private fun initDotView() {
+ // TODO(b/238425913): Could we just have this dot view be part of the layout with a dot
+ // drawable so we don't need to inflate it manually? Would that not work with animations?
+ val dotView =
+ StatusBarIconView(mContext, slot, null).also {
+ it.id = R.id.status_bar_dot
+ // Hard-code this view to always be in the DOT state so that whenever it's visible
+ // it will show a dot
+ it.visibleState = STATE_DOT
+ }
+
+ val width = mContext.resources.getDimensionPixelSize(R.dimen.status_bar_icon_size)
+ val lp = LayoutParams(width, width)
+ lp.gravity = Gravity.CENTER_VERTICAL or Gravity.START
+ addView(dotView, lp)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
index a682a57..4251d18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
@@ -23,6 +23,33 @@
/** Provides information about the current wifi network. */
sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
+ /**
+ * A model representing that we couldn't fetch any wifi information.
+ *
+ * This is only used with [DisabledWifiRepository], where [WifiManager] is null.
+ */
+ object Unavailable : WifiNetworkModel() {
+ override fun toString() = "WifiNetwork.Unavailable"
+ override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
+ if (prevVal is Unavailable) {
+ return
+ }
+
+ logFull(row)
+ }
+
+ override fun logFull(row: TableRowLogger) {
+ row.logChange(COL_NETWORK_TYPE, TYPE_UNAVAILABLE)
+ row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT)
+ row.logChange(COL_VALIDATED, false)
+ row.logChange(COL_LEVEL, LEVEL_DEFAULT)
+ row.logChange(COL_SSID, null)
+ row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
+ row.logChange(COL_ONLINE_SIGN_UP, false)
+ row.logChange(COL_PASSPOINT_NAME, null)
+ }
+ }
+
/** A model representing that we have no active wifi network. */
object Inactive : WifiNetworkModel() {
override fun toString() = "WifiNetwork.Inactive"
@@ -87,13 +114,8 @@
/**
* The wifi signal level, guaranteed to be 0 <= level <= 4.
- *
- * Null if we couldn't fetch the level for some reason.
- *
- * TODO(b/238425913): The level will only be null if we have a null WifiManager. Is there a
- * way we can guarantee a non-null WifiManager?
*/
- val level: Int? = null,
+ val level: Int,
/** See [android.net.wifi.WifiInfo.ssid]. */
val ssid: String? = null,
@@ -108,7 +130,7 @@
val passpointProviderFriendlyName: String? = null,
) : WifiNetworkModel() {
init {
- require(level == null || level in MIN_VALID_LEVEL..MAX_VALID_LEVEL) {
+ require(level in MIN_VALID_LEVEL..MAX_VALID_LEVEL) {
"0 <= wifi level <= 4 required; level was $level"
}
}
@@ -125,11 +147,7 @@
row.logChange(COL_VALIDATED, isValidated)
}
if (prevVal !is Active || prevVal.level != level) {
- if (level != null) {
- row.logChange(COL_LEVEL, level)
- } else {
- row.logChange(COL_LEVEL, LEVEL_DEFAULT)
- }
+ row.logChange(COL_LEVEL, level)
}
if (prevVal !is Active || prevVal.ssid != ssid) {
row.logChange(COL_SSID, ssid)
@@ -190,6 +208,7 @@
}
const val TYPE_CARRIER_MERGED = "CarrierMerged"
+const val TYPE_UNAVAILABLE = "Unavailable"
const val TYPE_INACTIVE = "Inactive"
const val TYPE_ACTIVE = "Active"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index 53525f2..ac4d55c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -34,3 +34,13 @@
/** Observable for the current wifi network activity. */
val wifiActivity: StateFlow<DataActivityModel>
}
+
+/**
+ * A no-op interface used for Dagger bindings.
+ *
+ * [WifiRepositorySwitcher] needs to inject the "real" wifi repository, which could either be the
+ * full [WifiRepositoryImpl] or just [DisabledWifiRepository]. Having this interface lets us bind
+ * [RealWifiRepository], and then [WifiRepositorySwitcher] will automatically get the correct real
+ * repository.
+ */
+interface RealWifiRepository : WifiRepository
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
index be86620..2cb81c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
@@ -58,7 +58,7 @@
class WifiRepositorySwitcher
@Inject
constructor(
- private val realImpl: WifiRepositoryImpl,
+ private val realImpl: RealWifiRepository,
private val demoImpl: DemoWifiRepository,
private val demoModeController: DemoModeController,
@Application scope: CoroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
index 7890074..be3d7d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
@@ -89,7 +89,7 @@
WifiNetworkModel.Active(
networkId = DEMO_NET_ID,
isValidated = validated ?: true,
- level = level,
+ level = level ?: 0,
ssid = ssid,
// These fields below aren't supported in demo mode, since they aren't needed to satisfy
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
new file mode 100644
index 0000000..5d4a666
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 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.statusbar.pipeline.wifi.data.repository.prod
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * Implementation of wifi repository used when wifi is permanently disabled on the device.
+ *
+ * This repo should only exist when [WifiManager] is null, which means that we can never fetch any
+ * wifi information.
+ */
+@SysUISingleton
+class DisabledWifiRepository @Inject constructor() : RealWifiRepository {
+ override val isWifiEnabled: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
+
+ override val isWifiDefault: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
+
+ override val wifiNetwork: StateFlow<WifiNetworkModel> = MutableStateFlow(NETWORK).asStateFlow()
+
+ override val wifiActivity: StateFlow<DataActivityModel> =
+ MutableStateFlow(ACTIVITY).asStateFlow()
+
+ companion object {
+ private val NETWORK = WifiNetworkModel.Unavailable
+ private val ACTIVITY = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index c8c94e1..c47c20d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -29,7 +29,6 @@
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.TrafficStateCallback
-import android.util.Log
import com.android.settingslib.Utils
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -40,11 +39,11 @@
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -53,12 +52,9 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
@@ -68,178 +64,177 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
@SuppressLint("MissingPermission")
-class WifiRepositoryImpl @Inject constructor(
+class WifiRepositoryImpl
+@Inject
+constructor(
broadcastDispatcher: BroadcastDispatcher,
connectivityManager: ConnectivityManager,
logger: ConnectivityPipelineLogger,
@WifiTableLog wifiTableLogBuffer: TableLogBuffer,
@Main mainExecutor: Executor,
@Application scope: CoroutineScope,
- wifiManager: WifiManager?,
-) : WifiRepository {
+ wifiManager: WifiManager,
+) : RealWifiRepository {
- private val wifiStateChangeEvents: Flow<Unit> = broadcastDispatcher.broadcastFlow(
- IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)
- )
- .logInputChange(logger, "WIFI_STATE_CHANGED_ACTION intent")
+ private val wifiStateChangeEvents: Flow<Unit> =
+ broadcastDispatcher
+ .broadcastFlow(IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION))
+ .logInputChange(logger, "WIFI_STATE_CHANGED_ACTION intent")
private val wifiNetworkChangeEvents: MutableSharedFlow<Unit> =
MutableSharedFlow(extraBufferCapacity = 1)
+ // Because [WifiManager] doesn't expose a wifi enabled change listener, we do it
+ // internally by fetching [WifiManager.isWifiEnabled] whenever we think the state may
+ // have changed.
override val isWifiEnabled: StateFlow<Boolean> =
- if (wifiManager == null) {
- MutableStateFlow(false).asStateFlow()
- } else {
- // Because [WifiManager] doesn't expose a wifi enabled change listener, we do it
- // internally by fetching [WifiManager.isWifiEnabled] whenever we think the state may
- // have changed.
- merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
- .mapLatest { wifiManager.isWifiEnabled }
- .distinctUntilChanged()
- .logDiffsForTable(
- wifiTableLogBuffer,
- columnPrefix = "",
- columnName = "isWifiEnabled",
- initialValue = wifiManager.isWifiEnabled,
- )
- .stateIn(
- scope = scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = wifiManager.isWifiEnabled
- )
- }
+ merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
+ .mapLatest { wifiManager.isWifiEnabled }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ wifiTableLogBuffer,
+ columnPrefix = "",
+ columnName = "isWifiEnabled",
+ initialValue = wifiManager.isWifiEnabled,
+ )
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = wifiManager.isWifiEnabled,
+ )
- override val isWifiDefault: StateFlow<Boolean> = conflatedCallbackFlow {
- // Note: This callback doesn't do any logging because we already log every network change
- // in the [wifiNetwork] callback.
- val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
- override fun onCapabilitiesChanged(
- network: Network,
- networkCapabilities: NetworkCapabilities
- ) {
- // This method will always be called immediately after the network becomes the
- // default, in addition to any time the capabilities change while the network is
- // the default.
- // If this network contains valid wifi info, then wifi is the default network.
- val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
- trySend(wifiInfo != null)
+ override val isWifiDefault: StateFlow<Boolean> =
+ conflatedCallbackFlow {
+ // Note: This callback doesn't do any logging because we already log every network
+ // change in the [wifiNetwork] callback.
+ val callback =
+ object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
+ override fun onCapabilitiesChanged(
+ network: Network,
+ networkCapabilities: NetworkCapabilities
+ ) {
+ // This method will always be called immediately after the network
+ // becomes the default, in addition to any time the capabilities change
+ // while the network is the default.
+ // If this network contains valid wifi info, then wifi is the default
+ // network.
+ val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
+ trySend(wifiInfo != null)
+ }
+
+ override fun onLost(network: Network) {
+ // The system no longer has a default network, so wifi is definitely not
+ // default.
+ trySend(false)
+ }
+ }
+
+ connectivityManager.registerDefaultNetworkCallback(callback)
+ awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
}
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ wifiTableLogBuffer,
+ columnPrefix = "",
+ columnName = "isWifiDefault",
+ initialValue = false,
+ )
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
- override fun onLost(network: Network) {
- // The system no longer has a default network, so wifi is definitely not default.
- trySend(false)
+ override val wifiNetwork: StateFlow<WifiNetworkModel> =
+ conflatedCallbackFlow {
+ var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
+
+ val callback =
+ object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
+ override fun onCapabilitiesChanged(
+ network: Network,
+ networkCapabilities: NetworkCapabilities
+ ) {
+ logger.logOnCapabilitiesChanged(network, networkCapabilities)
+
+ wifiNetworkChangeEvents.tryEmit(Unit)
+
+ val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
+ if (wifiInfo?.isPrimary == true) {
+ val wifiNetworkModel =
+ createWifiNetworkModel(
+ wifiInfo,
+ network,
+ networkCapabilities,
+ wifiManager,
+ )
+ logger.logTransformation(
+ WIFI_NETWORK_CALLBACK_NAME,
+ oldValue = currentWifi,
+ newValue = wifiNetworkModel,
+ )
+ currentWifi = wifiNetworkModel
+ trySend(wifiNetworkModel)
+ }
+ }
+
+ override fun onLost(network: Network) {
+ logger.logOnLost(network)
+
+ wifiNetworkChangeEvents.tryEmit(Unit)
+
+ val wifi = currentWifi
+ if (
+ wifi is WifiNetworkModel.Active &&
+ wifi.networkId == network.getNetId()
+ ) {
+ val newNetworkModel = WifiNetworkModel.Inactive
+ logger.logTransformation(
+ WIFI_NETWORK_CALLBACK_NAME,
+ oldValue = wifi,
+ newValue = newNetworkModel,
+ )
+ currentWifi = newNetworkModel
+ trySend(newNetworkModel)
+ }
+ }
+ }
+
+ connectivityManager.registerNetworkCallback(WIFI_NETWORK_CALLBACK_REQUEST, callback)
+
+ awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
}
- }
-
- connectivityManager.registerDefaultNetworkCallback(callback)
- awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
- }
- .distinctUntilChanged()
- .logDiffsForTable(
- wifiTableLogBuffer,
- columnPrefix = "",
- columnName = "isWifiDefault",
- initialValue = false,
- )
- .stateIn(
- scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = false
- )
-
- override val wifiNetwork: StateFlow<WifiNetworkModel> = conflatedCallbackFlow {
- var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
-
- val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
- override fun onCapabilitiesChanged(
- network: Network,
- networkCapabilities: NetworkCapabilities
- ) {
- logger.logOnCapabilitiesChanged(network, networkCapabilities)
-
- wifiNetworkChangeEvents.tryEmit(Unit)
-
- val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
- if (wifiInfo?.isPrimary == true) {
- val wifiNetworkModel = createWifiNetworkModel(
- wifiInfo,
- network,
- networkCapabilities,
- wifiManager,
- )
- logger.logTransformation(
- WIFI_NETWORK_CALLBACK_NAME,
- oldValue = currentWifi,
- newValue = wifiNetworkModel
- )
- currentWifi = wifiNetworkModel
- trySend(wifiNetworkModel)
- }
- }
-
- override fun onLost(network: Network) {
- logger.logOnLost(network)
-
- wifiNetworkChangeEvents.tryEmit(Unit)
-
- val wifi = currentWifi
- if (wifi is WifiNetworkModel.Active && wifi.networkId == network.getNetId()) {
- val newNetworkModel = WifiNetworkModel.Inactive
- logger.logTransformation(
- WIFI_NETWORK_CALLBACK_NAME,
- oldValue = wifi,
- newValue = newNetworkModel
- )
- currentWifi = newNetworkModel
- trySend(newNetworkModel)
- }
- }
- }
-
- connectivityManager.registerNetworkCallback(WIFI_NETWORK_CALLBACK_REQUEST, callback)
-
- awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
- }
- .distinctUntilChanged()
- .logDiffsForTable(
- wifiTableLogBuffer,
- columnPrefix = "wifiNetwork",
- initialValue = WIFI_NETWORK_DEFAULT,
- )
- // There will be multiple wifi icons in different places that will frequently
- // subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures that
- // new subscribes will get the latest value immediately upon subscription. Otherwise, the
- // views could show stale data. See b/244173280.
- .stateIn(
- scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = WIFI_NETWORK_DEFAULT
- )
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ wifiTableLogBuffer,
+ columnPrefix = "wifiNetwork",
+ initialValue = WIFI_NETWORK_DEFAULT,
+ )
+ // There will be multiple wifi icons in different places that will frequently
+ // subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures
+ // that new subscribes will get the latest value immediately upon subscription.
+ // Otherwise, the views could show stale data. See b/244173280.
+ .stateIn(
+ scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = WIFI_NETWORK_DEFAULT,
+ )
override val wifiActivity: StateFlow<DataActivityModel> =
- if (wifiManager == null) {
- Log.w(SB_LOGGING_TAG, "Null WifiManager; skipping activity callback")
- flowOf(ACTIVITY_DEFAULT)
- } else {
- conflatedCallbackFlow {
- val callback = TrafficStateCallback { state ->
- logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state))
- trySend(state.toWifiDataActivityModel())
- }
- wifiManager.registerTrafficStateCallback(mainExecutor, callback)
- awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
+ conflatedCallbackFlow {
+ val callback = TrafficStateCallback { state ->
+ logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state))
+ trySend(state.toWifiDataActivityModel())
}
+ wifiManager.registerTrafficStateCallback(mainExecutor, callback)
+ awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
}
- .logDiffsForTable(
- wifiTableLogBuffer,
- columnPrefix = ACTIVITY_PREFIX,
- initialValue = ACTIVITY_DEFAULT,
- )
- .stateIn(
- scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = ACTIVITY_DEFAULT
- )
+ .logDiffsForTable(
+ wifiTableLogBuffer,
+ columnPrefix = ACTIVITY_PREFIX,
+ initialValue = ACTIVITY_DEFAULT,
+ )
+ .stateIn(
+ scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = ACTIVITY_DEFAULT,
+ )
companion object {
private const val ACTIVITY_PREFIX = "wifiActivity"
@@ -271,19 +266,19 @@
wifiInfo: WifiInfo,
network: Network,
networkCapabilities: NetworkCapabilities,
- wifiManager: WifiManager?,
+ wifiManager: WifiManager,
): WifiNetworkModel {
return if (wifiInfo.isCarrierMerged) {
WifiNetworkModel.CarrierMerged
} else {
WifiNetworkModel.Active(
- network.getNetId(),
- isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED),
- level = wifiManager?.calculateSignalLevel(wifiInfo.rssi),
- wifiInfo.ssid,
- wifiInfo.isPasspointAp,
- wifiInfo.isOsuAp,
- wifiInfo.passpointProviderFriendlyName
+ network.getNetId(),
+ isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED),
+ level = wifiManager.calculateSignalLevel(wifiInfo.rssi),
+ wifiInfo.ssid,
+ wifiInfo.isPasspointAp,
+ wifiInfo.isOsuAp,
+ wifiInfo.passpointProviderFriendlyName
)
}
}
@@ -308,4 +303,28 @@
private const val WIFI_NETWORK_CALLBACK_NAME = "wifiNetworkModel"
}
+
+ @SysUISingleton
+ class Factory
+ @Inject
+ constructor(
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val connectivityManager: ConnectivityManager,
+ private val logger: ConnectivityPipelineLogger,
+ @WifiTableLog private val wifiTableLogBuffer: TableLogBuffer,
+ @Main private val mainExecutor: Executor,
+ @Application private val scope: CoroutineScope,
+ ) {
+ fun create(wifiManager: WifiManager): WifiRepositoryImpl {
+ return WifiRepositoryImpl(
+ broadcastDispatcher,
+ connectivityManager,
+ logger,
+ wifiTableLogBuffer,
+ mainExecutor,
+ scope,
+ wifiManager,
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index 93041ce..980560a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -65,6 +65,7 @@
override val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
when (info) {
+ is WifiNetworkModel.Unavailable -> null
is WifiNetworkModel.Inactive -> null
is WifiNetworkModel.CarrierMerged -> null
is WifiNetworkModel.Active -> when {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index cc67c84..2aff12c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -30,6 +30,7 @@
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import kotlinx.coroutines.InternalCoroutinesApi
@@ -49,31 +50,12 @@
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
object WifiViewBinder {
- /**
- * Defines interface for an object that acts as the binding between the view and its view-model.
- *
- * Users of the [WifiViewBinder] class should use this to control the binder after it is bound.
- */
- interface Binding {
- /** Returns true if the wifi icon should be visible and false otherwise. */
- fun getShouldIconBeVisible(): Boolean
-
- /** Notifies that the visibility state has changed. */
- fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
-
- /** Notifies that the icon tint has been updated. */
- fun onIconTintChanged(newTint: Int)
-
- /** Notifies that the decor tint has been updated (used only for the dot). */
- fun onDecorTintChanged(newTint: Int)
- }
-
/** Binds the view to the view-model, continuing to update the former based on the latter. */
@JvmStatic
fun bind(
view: ViewGroup,
viewModel: LocationBasedWifiViewModel,
- ): Binding {
+ ): ModernStatusBarViewBinding {
val groupView = view.requireViewById<ViewGroup>(R.id.wifi_group)
val iconView = view.requireViewById<ImageView>(R.id.wifi_signal)
val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot)
@@ -148,7 +130,7 @@
}
}
- return object : Binding {
+ return object : ModernStatusBarViewBinding {
override fun getShouldIconBeVisible(): Boolean {
return viewModel.wifiIcon.value is WifiIcon.Visible
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index be7782c..7a73486 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -16,17 +16,12 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.view
+import android.annotation.SuppressLint
import android.content.Context
-import android.graphics.Rect
import android.util.AttributeSet
-import android.view.Gravity
import android.view.LayoutInflater
import com.android.systemui.R
-import com.android.systemui.plugins.DarkIconDispatcher
-import com.android.systemui.statusbar.BaseStatusBarFrameLayout
-import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
-import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView
import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
@@ -36,83 +31,14 @@
*/
class ModernStatusBarWifiView(
context: Context,
- attrs: AttributeSet?
-) : BaseStatusBarFrameLayout(context, attrs) {
-
- private lateinit var slot: String
- private lateinit var binding: WifiViewBinder.Binding
-
- @StatusBarIconView.VisibleState
- private var iconVisibleState: Int = STATE_HIDDEN
- set(value) {
- if (field == value) {
- return
- }
- field = value
- binding.onVisibilityStateChanged(value)
- }
-
- override fun getSlot() = slot
-
- override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
- val newTint = DarkIconDispatcher.getTint(areas, this, tint)
- binding.onIconTintChanged(newTint)
- binding.onDecorTintChanged(newTint)
- }
-
- override fun setStaticDrawableColor(color: Int) {
- binding.onIconTintChanged(color)
- }
-
- override fun setDecorColor(color: Int) {
- binding.onDecorTintChanged(color)
- }
-
- override fun setVisibleState(@StatusBarIconView.VisibleState state: Int, animate: Boolean) {
- iconVisibleState = state
- }
-
- @StatusBarIconView.VisibleState
- override fun getVisibleState(): Int {
- return iconVisibleState
- }
-
- override fun isIconVisible(): Boolean {
- return binding.getShouldIconBeVisible()
- }
-
- private fun initView(
- slotName: String,
- wifiViewModel: LocationBasedWifiViewModel,
- ) {
- slot = slotName
- initDotView()
- binding = WifiViewBinder.bind(this, wifiViewModel)
- }
-
- // Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView].
- private fun initDotView() {
- // TODO(b/238425913): Could we just have this dot view be part of
- // R.layout.new_status_bar_wifi_group with a dot drawable so we don't need to inflate it
- // manually? Would that not work with animations?
- val dotView = StatusBarIconView(mContext, slot, null).also {
- it.id = R.id.status_bar_dot
- // Hard-code this view to always be in the DOT state so that whenever it's visible it
- // will show a dot
- it.visibleState = STATE_DOT
- }
-
- val width = mContext.resources.getDimensionPixelSize(R.dimen.status_bar_icon_size)
- val lp = LayoutParams(width, width)
- lp.gravity = Gravity.CENTER_VERTICAL or Gravity.START
- addView(dotView, lp)
- }
-
+ attrs: AttributeSet?,
+) : ModernStatusBarView(context, attrs) {
companion object {
/**
* Inflates a new instance of [ModernStatusBarWifiView], binds it to a view model, and
* returns it.
*/
+ @SuppressLint("InflateParams")
@JvmStatic
fun constructAndBind(
context: Context,
@@ -123,7 +49,7 @@
LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
as ModernStatusBarWifiView
).also {
- it.initView(slot, wifiViewModel)
+ it.initView(slot) { WifiViewBinder.bind(it, wifiViewModel) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
index a4615cc..02c3a65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
@@ -47,7 +47,7 @@
/** True if the airplane spacer view should be visible. */
val isAirplaneSpacerVisible: Flow<Boolean>,
) {
- val useDebugColoring: Boolean = statusBarPipelineFlags.useWifiDebugColoring()
+ val useDebugColoring: Boolean = statusBarPipelineFlags.useDebugColoring()
val defaultColor: Int =
if (useDebugColoring) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index ab464cc..824b597 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -82,6 +82,7 @@
/** Returns the icon to use based on the given network. */
private fun WifiNetworkModel.icon(): WifiIcon {
return when (this) {
+ is WifiNetworkModel.Unavailable -> WifiIcon.Hidden
is WifiNetworkModel.CarrierMerged -> WifiIcon.Hidden
is WifiNetworkModel.Inactive -> WifiIcon.Visible(
res = WIFI_NO_NETWORK,
@@ -89,27 +90,23 @@
"${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}"
)
)
- is WifiNetworkModel.Active ->
- when (this.level) {
- null -> WifiIcon.Hidden
- else -> {
- val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
- when {
- this.isValidated ->
- WifiIcon.Visible(
- WIFI_FULL_ICONS[this.level],
- ContentDescription.Loaded(levelDesc)
- )
- else ->
- WifiIcon.Visible(
- WIFI_NO_INTERNET_ICONS[this.level],
- ContentDescription.Loaded(
- "$levelDesc,${context.getString(NO_INTERNET)}"
- )
- )
- }
- }
+ is WifiNetworkModel.Active -> {
+ val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
+ when {
+ this.isValidated ->
+ WifiIcon.Visible(
+ WIFI_FULL_ICONS[this.level],
+ ContentDescription.Loaded(levelDesc),
+ )
+ else ->
+ WifiIcon.Visible(
+ WIFI_NO_INTERNET_ICONS[this.level],
+ ContentDescription.Loaded(
+ "$levelDesc,${context.getString(NO_INTERNET)}"
+ ),
+ )
}
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
index 3e111e6..302d6a9 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
@@ -38,7 +38,7 @@
@Inject
constructor(
private val inputManager: InputManager,
- private val bluetoothAdapter: BluetoothAdapter,
+ private val bluetoothAdapter: BluetoothAdapter?,
@Background private val handler: Handler,
@Background private val executor: Executor,
) : InputManager.InputDeviceListener, BluetoothAdapter.OnMetadataChangedListener {
@@ -141,7 +141,7 @@
}
private fun onStylusBluetoothConnected(btAddress: String) {
- val device: BluetoothDevice = bluetoothAdapter.getRemoteDevice(btAddress) ?: return
+ val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return
try {
bluetoothAdapter.addOnMetadataChangedListener(device, executor, this)
} catch (e: IllegalArgumentException) {
@@ -150,7 +150,7 @@
}
private fun onStylusBluetoothDisconnected(btAddress: String) {
- val device: BluetoothDevice = bluetoothAdapter.getRemoteDevice(btAddress) ?: return
+ val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return
try {
bluetoothAdapter.removeOnMetadataChangedListener(device, this)
} catch (e: IllegalArgumentException) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
index a36105e..a4b9b08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
@@ -22,6 +22,7 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
@@ -29,8 +30,12 @@
import com.android.wm.shell.bubbles.DismissView;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
/** Tests for {@link DismissAnimationController}. */
@SmallTest
@@ -40,10 +45,16 @@
private DismissAnimationController mDismissAnimationController;
private DismissView mDismissView;
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AccessibilityManager mAccessibilityManager;
+
@Before
public void setUp() throws Exception {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
- final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
final MenuView stubMenuView = new MenuView(mContext, stubMenuViewModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index 0cdd6e2..7356184 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -31,6 +31,7 @@
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.FlingAnimation;
@@ -43,9 +44,13 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.util.Optional;
@@ -61,12 +66,18 @@
private MenuView mMenuView;
private TestMenuAnimationController mMenuAnimationController;
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AccessibilityManager mAccessibilityManager;
+
@Before
public void setUp() throws Exception {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
- final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager);
mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance));
mViewPropertyAnimator = spy(mMenuView.animate());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
index e62a329..06340af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
@@ -16,16 +16,23 @@
package com.android.systemui.accessibility.floatingmenu;
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
+import android.content.Context;
+import android.content.res.Configuration;
import android.testing.AndroidTestingRunner;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -34,6 +41,10 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
/** Tests for {@link MenuInfoRepository}. */
@RunWith(AndroidTestingRunner.class)
@SmallTest
@@ -42,13 +53,28 @@
public MockitoRule mockito = MockitoJUnit.rule();
@Mock
+ private AccessibilityManager mAccessibilityManager;
+
+ @Mock
private MenuInfoRepository.OnSettingsContentsChanged mMockSettingsContentsChanged;
private MenuInfoRepository mMenuInfoRepository;
+ private final List<String> mShortcutTargets = new ArrayList<>();
@Before
public void setUp() {
- mMenuInfoRepository = new MenuInfoRepository(mContext, mMockSettingsContentsChanged);
+ mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
+ mShortcutTargets.add(MAGNIFICATION_CONTROLLER_NAME);
+ doReturn(mShortcutTargets).when(mAccessibilityManager).getAccessibilityShortcutTargets(
+ anyInt());
+
+ mMenuInfoRepository = new MenuInfoRepository(mContext, mAccessibilityManager,
+ mMockSettingsContentsChanged);
+ }
+
+ @After
+ public void tearDown() {
+ mShortcutTargets.clear();
}
@Test
@@ -64,4 +90,14 @@
verify(mMockSettingsContentsChanged).onFadeEffectInfoChanged(any(MenuFadeEffectInfo.class));
}
+
+ @Test
+ public void localeChange_verifyTargetFeaturesChanged() {
+ final Configuration configuration = new Configuration();
+ configuration.setLocale(Locale.TAIWAN);
+
+ mMenuInfoRepository.mComponentCallbacks.onConfigurationChanged(configuration);
+
+ verify(mMockSettingsContentsChanged).onTargetFeaturesChanged(any());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 78ee627..f17b1cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -29,6 +29,7 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
@@ -56,6 +57,9 @@
public MockitoRule mockito = MockitoJUnit.rule();
@Mock
+ private AccessibilityManager mAccessibilityManager;
+
+ @Mock
private DismissAnimationController.DismissCallback mStubDismissCallback;
private RecyclerView mStubListView;
@@ -69,7 +73,7 @@
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
- final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager);
final int halfScreenHeight =
stubWindowManager.getCurrentWindowMetrics().getBounds().height() / 2;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index d29ebb8..ed9562d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -31,6 +31,7 @@
import android.testing.TestableLooper;
import android.view.MotionEvent;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.filters.SmallTest;
@@ -42,8 +43,12 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.Collections;
@@ -64,10 +69,16 @@
private RecyclerView mStubListView;
private DismissView mDismissView;
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AccessibilityManager mAccessibilityManager;
+
@Before
public void setUp() throws Exception {
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
- final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
windowManager);
mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index 742ee53..5a1a6db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -29,6 +29,7 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
@@ -37,8 +38,12 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
/** Tests for {@link MenuView}. */
@RunWith(AndroidTestingRunner.class)
@@ -52,12 +57,18 @@
private String mLastPosition;
private MenuViewAppearance mStubMenuViewAppearance;
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AccessibilityManager mAccessibilityManager;
+
@Before
public void setUp() throws Exception {
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mNightMode = mUiModeManager.getNightMode();
mUiModeManager.setNightMode(MODE_NIGHT_YES);
- final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager);
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
mStubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager);
mMenuView = spy(new MenuView(mContext, stubMenuViewModel, mStubMenuViewAppearance));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index d6a9ee3..53cd71f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -19,6 +19,7 @@
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import kotlinx.coroutines.flow.MutableStateFlow
// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionRepository
@@ -29,12 +30,11 @@
private val _connectionInfo = MutableStateFlow(MobileConnectionModel())
override val connectionInfo = _connectionInfo
+ override val numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
+
private val _dataEnabled = MutableStateFlow(true)
override val dataEnabled = _dataEnabled
- private val _isDefaultDataSubscription = MutableStateFlow(true)
- override val isDefaultDataSubscription = _isDefaultDataSubscription
-
override val cdmaRoaming = MutableStateFlow(false)
override val networkName =
@@ -47,8 +47,4 @@
fun setDataEnabled(enabled: Boolean) {
_dataEnabled.value = enabled
}
-
- fun setIsDefaultDataSubscription(isDefault: Boolean) {
- _isDefaultDataSubscription.value = isDefault
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index 7f93328..49d4bdc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -57,9 +57,6 @@
private val _activeMobileDataSubscriptionId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
- private val _defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
- override val defaultDataSubId = _defaultDataSubId
-
private val _mobileConnectivity = MutableStateFlow(MobileConnectivityModel())
override val defaultMobileNetworkConnectivity = _mobileConnectivity
@@ -84,10 +81,6 @@
_subscriptions.value = subs
}
- fun setDefaultDataSubId(id: Int) {
- _defaultDataSubId.value = id
- }
-
fun setMobileConnectivity(model: MobileConnectivityModel) {
_mobileConnectivity.value = model
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index c63dd2a..d6b8c0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -61,6 +61,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
@@ -117,7 +118,6 @@
telephonyManager,
globalSettings,
fakeBroadcastDispatcher,
- connectionsRepo.defaultDataSubId,
connectionsRepo.globalMobileDataSettingChangedEvent,
mobileMappings,
IMMEDIATE,
@@ -319,7 +319,7 @@
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val type = NETWORK_TYPE_LTE
- val expected = DefaultNetworkType(type, mobileMappings.toIconKey(type))
+ val expected = DefaultNetworkType(mobileMappings.toIconKey(type))
val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
callback.onDisplayInfoChanged(ti)
@@ -336,7 +336,7 @@
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val type = OVERRIDE_NETWORK_TYPE_LTE_CA
- val expected = OverrideNetworkType(type, mobileMappings.toIconKeyOverride(type))
+ val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type))
val ti =
mock<TelephonyDisplayInfo>().also {
whenever(it.networkType).thenReturn(type)
@@ -380,33 +380,6 @@
}
@Test
- fun isDefaultDataSubscription_isDefault() =
- runBlocking(IMMEDIATE) {
- connectionsRepo.setDefaultDataSubId(SUB_1_ID)
-
- var latest: Boolean? = null
- val job = underTest.isDefaultDataSubscription.onEach { latest = it }.launchIn(this)
-
- assertThat(latest).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun isDefaultDataSubscription_isNotDefault() =
- runBlocking(IMMEDIATE) {
- // Our subId is SUB_1_ID
- connectionsRepo.setDefaultDataSubId(123)
-
- var latest: Boolean? = null
- val job = underTest.isDefaultDataSubscription.onEach { latest = it }.launchIn(this)
-
- assertThat(latest).isFalse()
-
- job.cancel()
- }
-
- @Test
fun isDataConnectionAllowed_subIdSettingUpdate_valueUpdated() =
runBlocking(IMMEDIATE) {
val subIdSettingName = "${Settings.Global.MOBILE_DATA}$SUB_1_ID"
@@ -431,6 +404,17 @@
}
@Test
+ fun numberOfLevels_isDefault() =
+ runBlocking(IMMEDIATE) {
+ var latest: Int? = null
+ val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS)
+
+ job.cancel()
+ }
+
+ @Test
fun `roaming - cdma - queries telephony manager`() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index b8cd7a4..0da15e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -307,35 +307,6 @@
}
@Test
- fun testDefaultDataSubId_updatesOnBroadcast() =
- runBlocking(IMMEDIATE) {
- var latest: Int? = null
- val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
-
- fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
- receiver.onReceive(
- context,
- Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
- .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_2_ID)
- )
- }
-
- assertThat(latest).isEqualTo(SUB_2_ID)
-
- fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
- receiver.onReceive(
- context,
- Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
- .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
- )
- }
-
- assertThat(latest).isEqualTo(SUB_1_ID)
-
- job.cancel()
- }
-
- @Test
fun mobileConnectivity_default() {
assertThat(underTest.defaultMobileNetworkConnectivity.value)
.isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index ff72715..a29146b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -21,6 +21,7 @@
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.flow.MutableStateFlow
@@ -65,7 +66,7 @@
private val _level = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
override val level = _level
- private val _numberOfLevels = MutableStateFlow(4)
+ private val _numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
override val numberOfLevels = _numberOfLevels
fun setIconGroup(group: SignalIcon.MobileIconGroup) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 5abe335..61e13b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.CellSignalStrength
-import android.telephony.SubscriptionInfo
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest
import com.android.settingslib.SignalIcon.MobileIconGroup
@@ -34,7 +33,6 @@
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.THREE_G
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -178,12 +176,26 @@
}
@Test
+ fun numberOfLevels_comesFromRepo() =
+ runBlocking(IMMEDIATE) {
+ var latest: Int? = null
+ val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.numberOfLevels.value = 5
+ assertThat(latest).isEqualTo(5)
+
+ connectionRepository.numberOfLevels.value = 4
+ assertThat(latest).isEqualTo(4)
+
+ job.cancel()
+ }
+
+ @Test
fun iconGroup_three_g() =
runBlocking(IMMEDIATE) {
connectionRepository.setConnectionInfo(
MobileConnectionModel(
- resolvedNetworkType =
- DefaultNetworkType(THREE_G, mobileMappingsProxy.toIconKey(THREE_G))
+ resolvedNetworkType = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
),
)
@@ -200,8 +212,7 @@
runBlocking(IMMEDIATE) {
connectionRepository.setConnectionInfo(
MobileConnectionModel(
- resolvedNetworkType =
- DefaultNetworkType(THREE_G, mobileMappingsProxy.toIconKey(THREE_G))
+ resolvedNetworkType = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
),
)
@@ -212,7 +223,6 @@
MobileConnectionModel(
resolvedNetworkType =
DefaultNetworkType(
- FOUR_G,
mobileMappingsProxy.toIconKey(FOUR_G),
),
),
@@ -230,10 +240,7 @@
connectionRepository.setConnectionInfo(
MobileConnectionModel(
resolvedNetworkType =
- OverrideNetworkType(
- FIVE_G_OVERRIDE,
- mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE)
- )
+ OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE))
),
)
@@ -251,10 +258,7 @@
connectionRepository.setConnectionInfo(
MobileConnectionModel(
resolvedNetworkType =
- DefaultNetworkType(
- NETWORK_TYPE_UNKNOWN,
- mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN)
- ),
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN)),
),
)
@@ -509,8 +513,6 @@
private const val CDMA_LEVEL = 2
private const val SUB_1_ID = 1
- private val SUB_1 =
- mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
private val DEFAULT_NAME = NetworkNameModel.Default("test default name")
private val DERIVED_NAME = NetworkNameModel.Derived("test derived name")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
new file mode 100644
index 0000000..a2c1209
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2022 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.statusbar.pipeline.mobile.ui.view
+
+import android.content.res.ColorStateList
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.ViewUtils
+import android.view.View
+import android.widget.ImageView
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.QsMobileIconViewModel
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+@OptIn(ExperimentalCoroutinesApi::class)
+class ModernStatusBarMobileViewTest : SysuiTestCase() {
+
+ private lateinit var testableLooper: TestableLooper
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var constants: ConnectivityConstants
+
+ private lateinit var viewModel: LocationBasedMobileViewModel
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+
+ val interactor = FakeMobileIconInteractor(tableLogBuffer)
+
+ val viewModelCommon =
+ MobileIconViewModel(
+ subscriptionId = 1,
+ interactor,
+ logger,
+ constants,
+ testScope.backgroundScope,
+ )
+ viewModel = QsMobileIconViewModel(viewModelCommon, statusBarPipelineFlags)
+ }
+
+ // Note: The following tests are more like integration tests, since they stand up a full
+ // [WifiViewModel] and test the interactions between the view, view-binder, and view-model.
+
+ @Test
+ fun setVisibleState_icon_iconShownDotHidden() {
+ val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+
+ view.setVisibleState(StatusBarIconView.STATE_ICON, /* animate= */ false)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.getGroupView().visibility).isEqualTo(View.VISIBLE)
+ assertThat(view.getDotView().visibility).isEqualTo(View.GONE)
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun setVisibleState_dot_iconHiddenDotShown() {
+ val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+
+ view.setVisibleState(StatusBarIconView.STATE_DOT, /* animate= */ false)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.getGroupView().visibility).isEqualTo(View.INVISIBLE)
+ assertThat(view.getDotView().visibility).isEqualTo(View.VISIBLE)
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun setVisibleState_hidden_iconAndDotHidden() {
+ val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+
+ view.setVisibleState(StatusBarIconView.STATE_HIDDEN, /* animate= */ false)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.getGroupView().visibility).isEqualTo(View.INVISIBLE)
+ assertThat(view.getDotView().visibility).isEqualTo(View.INVISIBLE)
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun isIconVisible_alwaysTrue() {
+ val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.isIconVisible).isTrue()
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun onDarkChanged_iconHasNewColor() {
+ whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
+ val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ val color = 0x12345678
+ view.onDarkChanged(arrayListOf(), 1.0f, color)
+ testableLooper.processAllMessages()
+
+ assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun setStaticDrawableColor_iconHasNewColor() {
+ whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
+ val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ val color = 0x23456789
+ view.setStaticDrawableColor(color)
+ testableLooper.processAllMessages()
+
+ assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
+
+ ViewUtils.detachView(view)
+ }
+
+ private fun View.getGroupView(): View {
+ return this.requireViewById(R.id.mobile_group)
+ }
+
+ private fun View.getIconView(): ImageView {
+ return this.requireViewById(R.id.mobile_signal)
+ }
+
+ private fun View.getDotView(): View {
+ return this.requireViewById(R.id.status_bar_dot)
+ }
+}
+
+private const val SLOT_NAME = "TestSlotName"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index 043d55a..c960a06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -20,6 +20,7 @@
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
@@ -45,6 +46,7 @@
private lateinit var qsIcon: QsMobileIconViewModel
private lateinit var keyguardIcon: KeyguardMobileIconViewModel
private lateinit var interactor: FakeMobileIconInteractor
+ @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var constants: ConnectivityConstants
@Mock private lateinit var tableLogBuffer: TableLogBuffer
@@ -68,9 +70,9 @@
commonImpl =
MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope)
- homeIcon = HomeMobileIconViewModel(commonImpl, logger)
- qsIcon = QsMobileIconViewModel(commonImpl, logger)
- keyguardIcon = KeyguardMobileIconViewModel(commonImpl, logger)
+ homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags)
+ qsIcon = QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
+ keyguardIcon = KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
index d6cb762..58b50c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -19,6 +19,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
@@ -45,6 +46,7 @@
private lateinit var underTest: MobileIconsViewModel
private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+ @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var constants: ConnectivityConstants
@@ -67,6 +69,7 @@
logger,
constants,
testScope.backgroundScope,
+ statusBarPipelineFlags,
)
interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
new file mode 100644
index 0000000..3fe6983
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2022 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.statusbar.pipeline.shared.ui.view
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class ModernStatusBarViewTest : SysuiTestCase() {
+
+ private lateinit var binding: TestBinding
+
+ @Test
+ fun initView_hasCorrectSlot() {
+ val view = ModernStatusBarView(context, null)
+ val binding = TestBinding()
+
+ view.initView("slotName") { binding }
+
+ assertThat(view.slot).isEqualTo("slotName")
+ }
+
+ @Test
+ fun getVisibleState_icon_returnsIcon() {
+ val view = createAndInitView()
+
+ view.setVisibleState(STATE_ICON, /* animate= */ false)
+
+ assertThat(view.visibleState).isEqualTo(STATE_ICON)
+ }
+
+ @Test
+ fun getVisibleState_dot_returnsDot() {
+ val view = createAndInitView()
+
+ view.setVisibleState(STATE_DOT, /* animate= */ false)
+
+ assertThat(view.visibleState).isEqualTo(STATE_DOT)
+ }
+
+ @Test
+ fun getVisibleState_hidden_returnsHidden() {
+ val view = createAndInitView()
+
+ view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
+
+ assertThat(view.visibleState).isEqualTo(STATE_HIDDEN)
+ }
+
+ @Test
+ fun onDarkChanged_bindingReceivesIconAndDecorTint() {
+ val view = createAndInitView()
+
+ view.onDarkChanged(arrayListOf(), 1.0f, 0x12345678)
+
+ assertThat(binding.iconTint).isEqualTo(0x12345678)
+ assertThat(binding.decorTint).isEqualTo(0x12345678)
+ }
+
+ @Test
+ fun setStaticDrawableColor_bindingReceivesIconTint() {
+ val view = createAndInitView()
+
+ view.setStaticDrawableColor(0x12345678)
+
+ assertThat(binding.iconTint).isEqualTo(0x12345678)
+ }
+
+ @Test
+ fun setDecorColor_bindingReceivesDecorColor() {
+ val view = createAndInitView()
+
+ view.setDecorColor(0x23456789)
+
+ assertThat(binding.decorTint).isEqualTo(0x23456789)
+ }
+
+ @Test
+ fun isIconVisible_usesBinding_true() {
+ val view = createAndInitView()
+
+ binding.shouldIconBeVisibleInternal = true
+
+ assertThat(view.isIconVisible).isEqualTo(true)
+ }
+
+ @Test
+ fun isIconVisible_usesBinding_false() {
+ val view = createAndInitView()
+
+ binding.shouldIconBeVisibleInternal = false
+
+ assertThat(view.isIconVisible).isEqualTo(false)
+ }
+
+ private fun createAndInitView(): ModernStatusBarView {
+ val view = ModernStatusBarView(context, null)
+ binding = TestBinding()
+ view.initView(SLOT_NAME) { binding }
+ return view
+ }
+
+ inner class TestBinding : ModernStatusBarViewBinding {
+ var iconTint: Int? = null
+ var decorTint: Int? = null
+ var onVisibilityStateChangedCalled: Boolean = false
+
+ var shouldIconBeVisibleInternal: Boolean = true
+
+ override fun onIconTintChanged(newTint: Int) {
+ iconTint = newTint
+ }
+
+ override fun onDecorTintChanged(newTint: Int) {
+ decorTint = newTint
+ }
+
+ override fun onVisibilityStateChanged(state: Int) {
+ onVisibilityStateChangedCalled = true
+ }
+
+ override fun getShouldIconBeVisible(): Boolean {
+ return shouldIconBeVisibleInternal
+ }
+ }
+}
+
+private const val SLOT_NAME = "TestSlotName"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
index 30fd308..30ac8d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
@@ -34,12 +34,6 @@
}
}
- @Test
- fun active_levelNull_noException() {
- WifiNetworkModel.Active(NETWORK_ID, level = null)
- // No assert, just need no crash
- }
-
@Test(expected = IllegalArgumentException::class)
fun active_levelNegative_exceptionThrown() {
WifiNetworkModel.Active(NETWORK_ID, level = MIN_VALID_LEVEL - 1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
new file mode 100644
index 0000000..3c4e85b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 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.statusbar.pipeline.wifi.data.repository.prod
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class DisabledWifiRepositoryTest : SysuiTestCase() {
+
+ private lateinit var underTest: DisabledWifiRepository
+
+ @Before
+ fun setUp() {
+ underTest = DisabledWifiRepository()
+ }
+
+ @Test
+ fun enabled_alwaysFalse() {
+ assertThat(underTest.isWifiEnabled.value).isEqualTo(false)
+ }
+
+ @Test
+ fun default_alwaysFalse() {
+ assertThat(underTest.isWifiDefault.value).isEqualTo(false)
+ }
+
+ @Test
+ fun network_alwaysUnavailable() {
+ assertThat(underTest.wifiNetwork.value).isEqualTo(WifiNetworkModel.Unavailable)
+ }
+
+ @Test
+ fun activity_alwaysFalse() {
+ assertThat(underTest.wifiActivity.value)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index befb290..8f07615 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -33,7 +33,6 @@
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -98,13 +97,6 @@
}
@Test
- fun isWifiEnabled_nullWifiManager_getsFalse() = runBlocking(IMMEDIATE) {
- underTest = createRepo(wifiManagerToUse = null)
-
- assertThat(underTest.isWifiEnabled.value).isFalse()
- }
-
- @Test
fun isWifiEnabled_initiallyGetsWifiManagerValue() = runBlocking(IMMEDIATE) {
whenever(wifiManager.isWifiEnabled).thenReturn(true)
@@ -721,21 +713,6 @@
}
@Test
- fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) {
- underTest = createRepo(wifiManagerToUse = null)
-
- var latest: DataActivityModel? = null
- val job = underTest
- .wifiActivity
- .onEach { latest = it }
- .launchIn(this)
-
- assertThat(latest).isEqualTo(ACTIVITY_DEFAULT)
-
- job.cancel()
- }
-
- @Test
fun wifiActivity_callbackGivesNone_activityFlowHasNone() = runBlocking(IMMEDIATE) {
var latest: DataActivityModel? = null
val job = underTest
@@ -801,7 +778,7 @@
job.cancel()
}
- private fun createRepo(wifiManagerToUse: WifiManager? = wifiManager): WifiRepositoryImpl {
+ private fun createRepo(): WifiRepositoryImpl {
return WifiRepositoryImpl(
broadcastDispatcher,
connectivityManager,
@@ -809,7 +786,7 @@
tableLogger,
executor,
scope,
- wifiManagerToUse,
+ wifiManager,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
index 2ecb17b..01d59f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
@@ -52,6 +52,22 @@
}
@Test
+ fun ssid_unavailableNetwork_outputsNull() =
+ runBlocking(IMMEDIATE) {
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Unavailable)
+
+ var latest: String? = "default"
+ val job = underTest
+ .ssid
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
fun ssid_inactiveNetwork_outputsNull() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
@@ -85,6 +101,7 @@
fun ssid_isPasspointAccessPoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
networkId = 1,
+ level = 1,
isPasspointAccessPoint = true,
passpointProviderFriendlyName = "friendly",
))
@@ -104,6 +121,7 @@
fun ssid_isOnlineSignUpForPasspoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
networkId = 1,
+ level = 1,
isOnlineSignUpForPasspointAccessPoint = true,
passpointProviderFriendlyName = "friendly",
))
@@ -123,6 +141,7 @@
fun ssid_unknownSsid_outputsNull() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
networkId = 1,
+ level = 1,
ssid = WifiManager.UNKNOWN_SSID,
))
@@ -141,6 +160,7 @@
fun ssid_validSsid_outputsSsid() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
networkId = 1,
+ level = 1,
ssid = "MyAwesomeWifiNetwork",
))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 59c10cd..b8ace2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.view
import android.content.res.ColorStateList
-import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
@@ -27,7 +26,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.lifecycle.InstantTaskExecutorRule
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
@@ -52,8 +50,6 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import org.junit.Before
-import org.junit.Ignore
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -70,7 +66,8 @@
private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock
private lateinit var logger: ConnectivityPipelineLogger
- @Mock private lateinit var tableLogBuffer: TableLogBuffer
+ @Mock
+ private lateinit var tableLogBuffer: TableLogBuffer
@Mock
private lateinit var connectivityConstants: ConnectivityConstants
@Mock
@@ -83,9 +80,6 @@
private lateinit var scope: CoroutineScope
private lateinit var airplaneModeViewModel: AirplaneModeViewModel
- @JvmField @Rule
- val instantTaskExecutor = InstantTaskExecutorRule()
-
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -118,40 +112,6 @@
).home
}
- @Test
- fun constructAndBind_hasCorrectSlot() {
- val view = ModernStatusBarWifiView.constructAndBind(context, "slotName", viewModel)
-
- assertThat(view.slot).isEqualTo("slotName")
- }
-
- @Test
- fun getVisibleState_icon_returnsIcon() {
- val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
-
- view.setVisibleState(STATE_ICON, /* animate= */ false)
-
- assertThat(view.visibleState).isEqualTo(STATE_ICON)
- }
-
- @Test
- fun getVisibleState_dot_returnsDot() {
- val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
-
- view.setVisibleState(STATE_DOT, /* animate= */ false)
-
- assertThat(view.visibleState).isEqualTo(STATE_DOT)
- }
-
- @Test
- fun getVisibleState_hidden_returnsHidden() {
- val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
-
- view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
-
- assertThat(view.visibleState).isEqualTo(STATE_HIDDEN)
- }
-
// Note: The following tests are more like integration tests, since they stand up a full
// [WifiViewModel] and test the interactions between the view, view-binder, and view-model.
@@ -235,24 +195,24 @@
}
@Test
- @Ignore("b/262660044")
fun onDarkChanged_iconHasNewColor() {
- whenever(statusBarPipelineFlags.useWifiDebugColoring()).thenReturn(false)
+ whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
ViewUtils.attachView(view)
testableLooper.processAllMessages()
- val areas = ArrayList(listOf(Rect(0, 0, 1000, 1000)))
val color = 0x12345678
- view.onDarkChanged(areas, 1.0f, color)
+ view.onDarkChanged(arrayListOf(), 1.0f, color)
testableLooper.processAllMessages()
assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
+
+ ViewUtils.detachView(view)
}
@Test
fun setStaticDrawableColor_iconHasNewColor() {
- whenever(statusBarPipelineFlags.useWifiDebugColoring()).thenReturn(false)
+ whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
ViewUtils.attachView(view)
testableLooper.processAllMessages()
@@ -262,6 +222,8 @@
testableLooper.processAllMessages()
assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
+
+ ViewUtils.detachView(view)
}
private fun View.getIconGroupView(): View {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index 12b93819..726e813 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -379,6 +379,12 @@
expected = null,
),
+ // network = Unavailable => not shown
+ TestCase(
+ network = WifiNetworkModel.Unavailable,
+ expected = null,
+ ),
+
// network = Active & validated = false => not shown
TestCase(
network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 3),
@@ -397,12 +403,6 @@
description = "Full internet level 4 icon",
),
),
-
- // network has null level => not shown
- TestCase(
- network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = null),
- expected = null,
- ),
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 4158434..e5cfec9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -228,7 +228,7 @@
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
- wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, ssid = null))
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, ssid = null, level = 1))
var activityIn: Boolean? = null
val activityInJob = underTest
@@ -553,7 +553,8 @@
companion object {
private const val NETWORK_ID = 2
- private val ACTIVE_VALID_WIFI_NETWORK = WifiNetworkModel.Active(NETWORK_ID, ssid = "AB")
+ private val ACTIVE_VALID_WIFI_NETWORK =
+ WifiNetworkModel.Active(NETWORK_ID, ssid = "AB", level = 1)
}
}
diff --git a/services/api/current.txt b/services/api/current.txt
index 090a449..b173726 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -40,6 +40,7 @@
public interface ActivityManagerLocal {
method public boolean bindSdkSandboxService(@NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String, @NonNull String, int) throws android.os.RemoteException;
method public boolean canStartForegroundService(int, int, @NonNull String);
+ method public void killSdkSandboxClientAppProcess(@NonNull android.os.IBinder);
}
}
@@ -212,6 +213,19 @@
}
+package com.android.server.security {
+
+ public class FileIntegrityService extends com.android.server.SystemService {
+ method public void onStart();
+ method public static void setUpFsVerity(@NonNull String) throws java.io.IOException;
+ }
+
+ public class KeyChainSystemService extends com.android.server.SystemService {
+ method public void onStart();
+ }
+
+}
+
package com.android.server.stats {
public final class StatsHelper {
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 3ff6ba7..38c7dd1 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -375,7 +375,7 @@
case MSG_RUN_GET_RESTORE_SETS: {
// Like other async operations, this is entered with the wakelock held
- RestoreSet[] sets = null;
+ List<RestoreSet> sets = null;
RestoreGetSetsParams params = (RestoreGetSetsParams) msg.obj;
String callerLogString = "BH/MSG_RUN_GET_RESTORE_SETS";
try {
@@ -394,7 +394,12 @@
} finally {
if (params.observer != null) {
try {
- params.observer.restoreSetsAvailable(sets);
+ if (sets == null) {
+ params.observer.restoreSetsAvailable(null);
+ } else {
+ params.observer.restoreSetsAvailable(
+ sets.toArray(new RestoreSet[0]));
+ }
} catch (RemoteException re) {
Slog.e(TAG, "Unable to report listing to observer");
} catch (Exception e) {
diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
index d3e4f13..70d7fac 100644
--- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
+++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
@@ -45,6 +45,7 @@
import com.android.server.backup.transport.TransportConnection;
import com.android.server.backup.utils.BackupEligibilityRules;
+import java.util.List;
import java.util.function.BiFunction;
/**
@@ -60,7 +61,7 @@
private final int mUserId;
private final BackupEligibilityRules mBackupEligibilityRules;
@Nullable private final String mPackageName;
- public RestoreSet[] mRestoreSets = null;
+ public List<RestoreSet> mRestoreSets = null;
boolean mEnded = false;
boolean mTimedOut = false;
@@ -174,10 +175,10 @@
}
synchronized (mBackupManagerService.getQueueLock()) {
- for (int i = 0; i < mRestoreSets.length; i++) {
- if (token == mRestoreSets[i].token) {
+ for (int i = 0; i < mRestoreSets.size(); i++) {
+ if (token == mRestoreSets.get(i).token) {
final long oldId = Binder.clearCallingIdentity();
- RestoreSet restoreSet = mRestoreSets[i];
+ RestoreSet restoreSet = mRestoreSets.get(i);
try {
return sendRestoreToHandlerLocked(
(transportClient, listener) ->
@@ -267,10 +268,10 @@
}
synchronized (mBackupManagerService.getQueueLock()) {
- for (int i = 0; i < mRestoreSets.length; i++) {
- if (token == mRestoreSets[i].token) {
+ for (int i = 0; i < mRestoreSets.size(); i++) {
+ if (token == mRestoreSets.get(i).token) {
final long oldId = Binder.clearCallingIdentity();
- RestoreSet restoreSet = mRestoreSets[i];
+ RestoreSet restoreSet = mRestoreSets.get(i);
try {
return sendRestoreToHandlerLocked(
(transportClient, listener) ->
@@ -390,7 +391,7 @@
}
}
- public void setRestoreSets(RestoreSet[] restoreSets) {
+ public void setRestoreSets(List<RestoreSet> restoreSets) {
mRestoreSets = restoreSets;
}
diff --git a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
index 21005bb..daf34152 100644
--- a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -180,11 +180,11 @@
/**
* See {@link IBackupTransport#getAvailableRestoreSets()}
*/
- public RestoreSet[] getAvailableRestoreSets() throws RemoteException {
+ public List<RestoreSet> getAvailableRestoreSets() throws RemoteException {
AndroidFuture<List<RestoreSet>> resultFuture = mTransportFutures.newFuture();
mTransportBinder.getAvailableRestoreSets(resultFuture);
List<RestoreSet> result = getFutureResult(resultFuture);
- return result == null ? null : result.toArray(new RestoreSet[] {});
+ return result;
}
/**
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 373080a..ff6fd4b 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -273,7 +273,7 @@
String[] signerDigestHexStrings = computePackageSignerSha256Digests(
packageInfo.signingInfo);
- // log to Westworld
+ // log to statsd
FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED,
packageInfo.packageName,
packageInfo.getLongVersionCode(),
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 7b8ca91..9bedbd0 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1000,10 +1000,6 @@
@Override
public void notifySubscriptionInfoChanged() {
if (VDBG) log("notifySubscriptionInfoChanged:");
- if (!checkNotifyPermission("notifySubscriptionInfoChanged()")) {
- return;
- }
-
synchronized (mRecords) {
if (!mHasNotifySubscriptionInfoChangedOccurred) {
log("notifySubscriptionInfoChanged: first invocation mRecords.size="
@@ -1030,10 +1026,6 @@
@Override
public void notifyOpportunisticSubscriptionInfoChanged() {
if (VDBG) log("notifyOpptSubscriptionInfoChanged:");
- if (!checkNotifyPermission("notifyOpportunisticSubscriptionInfoChanged()")) {
- return;
- }
-
synchronized (mRecords) {
if (!mHasNotifyOpportunisticSubscriptionInfoChangedOccurred) {
log("notifyOpptSubscriptionInfoChanged: first invocation mRecords.size="
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 4ebd714..bcea40e5 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -54,6 +54,7 @@
import static android.os.PowerExemptionManager.REASON_OPT_OUT_REQUESTED;
import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_PLATFORM_VPN;
import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_VPN;
+import static android.os.PowerExemptionManager.REASON_PACKAGE_INSTALLER;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_TOP;
@@ -194,6 +195,7 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.os.TimeoutRecord;
import com.android.internal.os.TransferPipe;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FrameworkStatsLog;
@@ -202,6 +204,7 @@
import com.android.server.SystemService;
import com.android.server.am.ActivityManagerService.ItemMatcher;
import com.android.server.am.LowMemDetector.MemFactor;
+import com.android.server.pm.KnownPackages;
import com.android.server.uri.NeededUriGrants;
import com.android.server.wm.ActivityServiceConnectionsHolder;
@@ -2382,6 +2385,13 @@
.getPotentialUserAllowedExemptionReason(callerUid, packageName);
}
}
+ if (reason == REASON_DENIED) {
+ if (ArrayUtils.contains(mAm.getPackageManagerInternal().getKnownPackageNames(
+ KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM), packageName)) {
+ reason = REASON_PACKAGE_INSTALLER;
+ }
+ }
+
switch (reason) {
case REASON_SYSTEM_UID:
case REASON_SYSTEM_ALLOW_LISTED:
@@ -2397,6 +2407,7 @@
case REASON_ACTIVE_DEVICE_ADMIN:
case REASON_ROLE_EMERGENCY:
case REASON_ALLOWLISTED_PACKAGE:
+ case REASON_PACKAGE_INSTALLER:
return PERMISSION_GRANTED;
default:
return PERMISSION_DENIED;
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
index 9f2cc7f..5175a31 100644
--- a/services/core/java/com/android/server/am/ActivityManagerLocal.java
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.os.IBinder;
import android.os.RemoteException;
/**
@@ -95,6 +96,15 @@
throws RemoteException;
/**
+ * Kill an app process associated with an SDK sandbox.
+ *
+ * @param clientApplicationThreadBinder binder value of the
+ * {@link android.app.IApplicationThread} of a client app process associated with a
+ * sandbox. This is obtained using {@link Context#getIApplicationThreadBinder()}.
+ */
+ void killSdkSandboxClientAppProcess(@NonNull IBinder clientApplicationThreadBinder);
+
+ /**
* Start a foreground service delegate.
* @param options foreground service delegate options.
* @param connection a service connection served as callback to caller.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 191460c..28aed2e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -13953,7 +13953,7 @@
}
} else {
BroadcastFilter bf = (BroadcastFilter)target;
- if (bf.requiredPermission == null) {
+ if (bf.exported && bf.requiredPermission == null) {
allProtected = false;
break;
}
@@ -16958,6 +16958,20 @@
}
@Override
+ public void killSdkSandboxClientAppProcess(IBinder clientApplicationThreadBinder) {
+ synchronized (ActivityManagerService.this) {
+ ProcessRecord r = getRecordForAppLOSP(clientApplicationThreadBinder);
+ if (r != null) {
+ r.killLocked(
+ "sdk sandbox died",
+ ApplicationExitInfo.REASON_DEPENDENCY_DIED,
+ ApplicationExitInfo.SUBREASON_SDK_SANDBOX_DIED,
+ true);
+ }
+ }
+ }
+
+ @Override
public void onUserRemoved(@UserIdInt int userId) {
// Clean up any ActivityTaskManager state (by telling it the user is stopped)
mAtmInternal.onUserStopped(userId);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0f7a725..78bff95 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -106,6 +106,7 @@
import android.media.ICapturePresetDevicesRoleDispatcher;
import android.media.ICommunicationDeviceDispatcher;
import android.media.IDeviceVolumeBehaviorDispatcher;
+import android.media.IDevicesForAttributesCallback;
import android.media.IMuteAwaitConnectionCallback;
import android.media.IPlaybackConfigDispatcher;
import android.media.IPreferredMixerAttributesDispatcher;
@@ -3124,6 +3125,25 @@
return mAudioSystem.getDevicesForAttributes(attributes, forVolume);
}
+ /**
+ * @see AudioManager#addOnDevicesForAttributesChangedListener(
+ * AudioAttributes, Executor, OnDevicesForAttributesChangedListener)
+ */
+ public void addOnDevicesForAttributesChangedListener(AudioAttributes attributes,
+ IDevicesForAttributesCallback callback) {
+ mAudioSystem.addOnDevicesForAttributesChangedListener(
+ attributes, false /* forVolume */, callback);
+ }
+
+ /**
+ * @see AudioManager#removeOnDevicesForAttributesChangedListener(
+ * OnDevicesForAttributesChangedListener)
+ */
+ public void removeOnDevicesForAttributesChangedListener(
+ IDevicesForAttributesCallback callback) {
+ mAudioSystem.removeOnDevicesForAttributesChangedListener(callback);
+ }
+
// pre-condition: event.getKeyCode() is one of KeyEvent.KEYCODE_VOLUME_UP,
// KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_MUTE
public void handleVolumeKey(@NonNull KeyEvent event, boolean isOnTv,
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 7fefc55..7839ada 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -22,10 +22,15 @@
import android.media.AudioDeviceAttributes;
import android.media.AudioMixerAttributes;
import android.media.AudioSystem;
+import android.media.IDevicesForAttributesCallback;
import android.media.ISoundDose;
import android.media.ISoundDoseCallback;
import android.media.audiopolicy.AudioMix;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
import android.os.SystemClock;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
@@ -64,8 +69,21 @@
private static final boolean USE_CACHE_FOR_GETDEVICES = true;
private ConcurrentHashMap<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
+ mLastDevicesForAttr = new ConcurrentHashMap<>();
+ private ConcurrentHashMap<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
mDevicesForAttrCache;
+ private final Object mDeviceCacheLock = new Object();
private int[] mMethodCacheHit;
+ /**
+ * Map that stores all attributes + forVolume pairs that are registered for
+ * routing change callback. The key is the {@link IBinder} that corresponds
+ * to the remote callback.
+ */
+ private final ArrayMap<IBinder, List<Pair<AudioAttributes, Boolean>>> mRegisteredAttributesMap =
+ new ArrayMap<>();
+ private final RemoteCallbackList<IDevicesForAttributesCallback>
+ mDevicesForAttributesCallbacks = new RemoteCallbackList<>();
+
private static final Object sRoutingListenerLock = new Object();
@GuardedBy("sRoutingListenerLock")
private static @Nullable OnRoutingUpdatedListener sRoutingListener;
@@ -96,6 +114,34 @@
if (listener != null) {
listener.onRoutingUpdatedFromNative();
}
+
+ synchronized (mRegisteredAttributesMap) {
+ final int nbCallbacks = mDevicesForAttributesCallbacks.beginBroadcast();
+
+ for (int i = 0; i < nbCallbacks; i++) {
+ IDevicesForAttributesCallback cb =
+ mDevicesForAttributesCallbacks.getBroadcastItem(i);
+ List<Pair<AudioAttributes, Boolean>> attrList =
+ mRegisteredAttributesMap.get(cb.asBinder());
+
+ if (attrList == null) {
+ throw new IllegalStateException("Attribute list must not be null");
+ }
+
+ for (Pair<AudioAttributes, Boolean> attr : attrList) {
+ ArrayList<AudioDeviceAttributes> devices =
+ getDevicesForAttributes(attr.first, attr.second);
+ if (!mLastDevicesForAttr.containsKey(attr)
+ || !sameDeviceList(devices, mLastDevicesForAttr.get(attr))) {
+ try {
+ cb.onDevicesForAttributesChanged(
+ attr.first, attr.second, devices);
+ } catch (RemoteException e) { }
+ }
+ }
+ }
+ mDevicesForAttributesCallbacks.finishBroadcast();
+ }
}
interface OnRoutingUpdatedListener {
@@ -109,6 +155,66 @@
}
/**
+ * @see AudioManager#addOnDevicesForAttributesChangedListener(
+ * AudioAttributes, Executor, OnDevicesForAttributesChangedListener)
+ */
+ public void addOnDevicesForAttributesChangedListener(AudioAttributes attributes,
+ boolean forVolume, @NonNull IDevicesForAttributesCallback listener) {
+ List<Pair<AudioAttributes, Boolean>> res;
+ final Pair<AudioAttributes, Boolean> attr = new Pair(attributes, forVolume);
+ synchronized (mRegisteredAttributesMap) {
+ res = mRegisteredAttributesMap.get(listener.asBinder());
+ if (res == null) {
+ res = new ArrayList<>();
+ mRegisteredAttributesMap.put(listener.asBinder(), res);
+ mDevicesForAttributesCallbacks.register(listener);
+ }
+
+ if (!res.contains(attr)) {
+ res.add(attr);
+ }
+ }
+
+ // Make query on registration to populate cache
+ getDevicesForAttributes(attributes, forVolume);
+ }
+
+ /**
+ * @see AudioManager#removeOnDevicesForAttributesChangedListener(
+ * OnDevicesForAttributesChangedListener)
+ */
+ public void removeOnDevicesForAttributesChangedListener(
+ @NonNull IDevicesForAttributesCallback listener) {
+ synchronized (mRegisteredAttributesMap) {
+ if (!mRegisteredAttributesMap.containsKey(listener.asBinder())) {
+ Log.w(TAG, "listener to be removed is not found.");
+ return;
+ }
+ mRegisteredAttributesMap.remove(listener.asBinder());
+ mDevicesForAttributesCallbacks.unregister(listener);
+ }
+ }
+
+ /**
+ * Helper function to compare lists of {@link AudioDeviceAttributes}
+ * @return true if the two lists contains the same devices, false otherwise.
+ */
+ private boolean sameDeviceList(@NonNull List<AudioDeviceAttributes> a,
+ @NonNull List<AudioDeviceAttributes> b) {
+ for (AudioDeviceAttributes device : a) {
+ if (!b.contains(device)) {
+ return false;
+ }
+ }
+ for (AudioDeviceAttributes device : b) {
+ if (!a.contains(device)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Implementation of AudioSystem.VolumeRangeInitRequestCallback
*/
@Override
@@ -159,8 +265,11 @@
if (DEBUG_CACHE) {
Log.d(TAG, "---- clearing cache ----------");
}
- if (mDevicesForAttrCache != null) {
- synchronized (mDevicesForAttrCache) {
+ synchronized (mDeviceCacheLock) {
+ if (mDevicesForAttrCache != null) {
+ // Save latest cache to determine routing updates
+ mLastDevicesForAttr.putAll(mDevicesForAttrCache);
+
mDevicesForAttrCache.clear();
}
}
@@ -189,7 +298,7 @@
if (USE_CACHE_FOR_GETDEVICES) {
ArrayList<AudioDeviceAttributes> res;
final Pair<AudioAttributes, Boolean> key = new Pair(attributes, forVolume);
- synchronized (mDevicesForAttrCache) {
+ synchronized (mDeviceCacheLock) {
res = mDevicesForAttrCache.get(key);
if (res == null) {
res = AudioSystem.getDevicesForAttributes(attributes, forVolume);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index f4eed2b..affeef1 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -852,6 +852,10 @@
mAutomaticBrightnessController.stop();
}
+ if (mScreenOffBrightnessSensorController != null) {
+ mScreenOffBrightnessSensorController.stop();
+ }
+
if (mBrightnessSetting != null) {
mBrightnessSetting.unregisterListener(mBrightnessSettingListener);
}
@@ -1093,6 +1097,9 @@
mBrightnessEventRingBuffer =
new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
+ if (mScreenOffBrightnessSensorController != null) {
+ mScreenOffBrightnessSensorController.stop();
+ }
loadScreenOffBrightnessSensor();
int[] sensorValueToLux = mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux();
if (mScreenOffBrightnessSensor != null && sensorValueToLux != null) {
diff --git a/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java b/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java
index 6f50dac..4d394c2 100644
--- a/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java
+++ b/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java
@@ -92,6 +92,10 @@
}
}
+ void stop() {
+ setLightSensorEnabled(false);
+ }
+
float getAutomaticScreenBrightness() {
if (mLastSensorValue < 0 || mLastSensorValue >= mSensorValueToLux.length
|| (!mRegistered
diff --git a/services/core/java/com/android/server/grammaticalinflection/OWNERS b/services/core/java/com/android/server/grammaticalinflection/OWNERS
new file mode 100644
index 0000000..5f16ba9
--- /dev/null
+++ b/services/core/java/com/android/server/grammaticalinflection/OWNERS
@@ -0,0 +1,4 @@
+# Bug template url: https://b.corp.google.com/issues/new?component=1082762&template=1601534
+allenwtsu@google.com
+goldmanj@google.com
+calvinpan@google.com
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index c15b538..5b9a663 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -48,12 +48,12 @@
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
+import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
import static com.android.server.EventLogTags.IMF_HIDE_IME;
import static com.android.server.EventLogTags.IMF_SHOW_IME;
import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT;
import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_IME_VISIBILITY;
import static java.lang.annotation.RetentionPolicy.SOURCE;
diff --git a/services/core/java/com/android/server/pm/AppStateHelper.java b/services/core/java/com/android/server/pm/AppStateHelper.java
index 32479ee..1a2b01e 100644
--- a/services/core/java/com/android/server/pm/AppStateHelper.java
+++ b/services/core/java/com/android/server/pm/AppStateHelper.java
@@ -37,7 +37,7 @@
import com.android.server.LocalServices;
import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -159,13 +159,21 @@
return false;
}
- private static boolean containsAny(Collection<String> list, Collection<String> which) {
- if (list.isEmpty()) {
- return false;
- }
- for (var element : which) {
- if (list.contains(element)) {
+ /**
+ * True if {@code arr} contains any element in {@code which}.
+ * Both {@code arr} and {@code which} must be sorted in advance.
+ */
+ private static boolean containsAny(String[] arr, List<String> which) {
+ int s1 = arr.length;
+ int s2 = which.size();
+ for (int i = 0, j = 0; i < s1 && j < s2; ) {
+ int val = arr[i].compareTo(which.get(j));
+ if (val == 0) {
return true;
+ } else if (val < 0) {
+ ++i;
+ } else {
+ ++j;
}
}
return false;
@@ -174,9 +182,9 @@
private void addLibraryDependency(ArraySet<String> results, List<String> libPackageNames) {
var pmInternal = LocalServices.getService(PackageManagerInternal.class);
- var libraryNames = new ArraySet<String>();
- var staticSharedLibraryNames = new ArraySet<String>();
- var sdkLibraryNames = new ArraySet<String>();
+ var libraryNames = new ArrayList<String>();
+ var staticSharedLibraryNames = new ArrayList<String>();
+ var sdkLibraryNames = new ArrayList<String>();
for (var packageName : libPackageNames) {
var pkg = pmInternal.getAndroidPackage(packageName);
if (pkg == null) {
@@ -199,11 +207,19 @@
return;
}
- pmInternal.forEachPackage(pkg -> {
- if (containsAny(pkg.getUsesLibraries(), libraryNames)
- || containsAny(pkg.getUsesOptionalLibraries(), libraryNames)
- || containsAny(pkg.getUsesStaticLibraries(), staticSharedLibraryNames)
- || containsAny(pkg.getUsesSdkLibraries(), sdkLibraryNames)) {
+ Collections.sort(libraryNames);
+ Collections.sort(sdkLibraryNames);
+ Collections.sort(staticSharedLibraryNames);
+
+ pmInternal.forEachPackageState(pkgState -> {
+ var pkg = pkgState.getPkg();
+ if (pkg == null) {
+ return;
+ }
+ if (containsAny(pkg.getUsesLibrariesSorted(), libraryNames)
+ || containsAny(pkg.getUsesOptionalLibrariesSorted(), libraryNames)
+ || containsAny(pkg.getUsesStaticLibrariesSorted(), staticSharedLibraryNames)
+ || containsAny(pkg.getUsesSdkLibrariesSorted(), sdkLibraryNames)) {
results.add(pkg.getPackageName());
}
});
diff --git a/services/core/java/com/android/server/pm/AppsFilterBase.java b/services/core/java/com/android/server/pm/AppsFilterBase.java
index 1021e07..12f6a18 100644
--- a/services/core/java/com/android/server/pm/AppsFilterBase.java
+++ b/services/core/java/com/android/server/pm/AppsFilterBase.java
@@ -399,6 +399,24 @@
Slog.wtf(TAG, "No setting found for non system uid " + callingUid);
return true;
}
+
+ if (DEBUG_TRACING) {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "getAppId");
+ }
+ final int callingAppId = UserHandle.getAppId(callingUid);
+ final int targetAppId = targetPkgSetting.getAppId();
+ if (DEBUG_TRACING) {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+ if (callingAppId == targetAppId
+ || callingAppId < Process.FIRST_APPLICATION_UID
+ || targetAppId < Process.FIRST_APPLICATION_UID) {
+ if (DEBUG_LOGGING) {
+ log(callingSetting, targetPkgSetting, "same app id or core app id");
+ }
+ return false;
+ }
+
final PackageStateInternal callingPkgSetting;
if (DEBUG_TRACING) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "callingSetting instanceof");
@@ -446,27 +464,6 @@
}
}
- if (DEBUG_TRACING) {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "getAppId");
- }
- final int callingAppId;
- if (callingPkgSetting != null) {
- callingAppId = callingPkgSetting.getAppId();
- } else {
- // all should be the same
- callingAppId = callingSharedPkgSettings.valueAt(0).getAppId();
- }
- final int targetAppId = targetPkgSetting.getAppId();
- if (DEBUG_TRACING) {
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
- }
- if (callingAppId == targetAppId) {
- if (DEBUG_LOGGING) {
- log(callingSetting, targetPkgSetting, "same app id");
- }
- return false;
- }
-
try {
if (DEBUG_TRACING) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "requestsQueryAllPackages");
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index cda7503..fe122f8 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -614,11 +614,14 @@
size += getDirectorySize(path);
if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) {
for (String splitSourceDir : info.applicationInfo.splitSourceDirs) {
- path = Paths.get(splitSourceDir).toFile();
- if (path.isFile()) {
- path = path.getParentFile();
+ File pathSplitSourceDir = Paths.get(splitSourceDir).toFile();
+ if (pathSplitSourceDir.isFile()) {
+ pathSplitSourceDir = pathSplitSourceDir.getParentFile();
}
- size += getDirectorySize(path);
+ if (path.getAbsolutePath().equals(pathSplitSourceDir.getAbsolutePath())) {
+ continue;
+ }
+ size += getDirectorySize(pathSplitSourceDir);
}
}
return size;
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageInternal.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageInternal.java
index 8d43fe7..9eca7d6 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageInternal.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageInternal.java
@@ -16,6 +16,8 @@
package com.android.server.pm.parsing.pkg;
+import android.annotation.NonNull;
+
import com.android.internal.content.om.OverlayConfig;
import com.android.server.pm.pkg.AndroidPackage;
@@ -31,5 +33,15 @@
*/
public interface AndroidPackageInternal extends AndroidPackage,
OverlayConfig.PackageProvider.Package {
+ @NonNull
+ String[] getUsesLibrariesSorted();
+ @NonNull
+ String[] getUsesOptionalLibrariesSorted();
+
+ @NonNull
+ String[] getUsesSdkLibrariesSorted();
+
+ @NonNull
+ String[] getUsesStaticLibrariesSorted();
}
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index e361c93..ed9382b 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -93,6 +93,7 @@
import java.io.File;
import java.security.PublicKey;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -405,6 +406,15 @@
private List<AndroidPackageSplit> mSplits;
@NonNull
+ private String[] mUsesLibrariesSorted;
+ @NonNull
+ private String[] mUsesOptionalLibrariesSorted;
+ @NonNull
+ private String[] mUsesSdkLibrariesSorted;
+ @NonNull
+ private String[] mUsesStaticLibrariesSorted;
+
+ @NonNull
public static PackageImpl forParsing(@NonNull String packageName, @NonNull String baseCodePath,
@NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp) {
return new PackageImpl(packageName, baseCodePath, codePath, manifestArray, isCoreApp);
@@ -1379,6 +1389,19 @@
@NonNull
@Override
+ public String[] getUsesLibrariesSorted() {
+ if (mUsesLibrariesSorted == null) {
+ // Note lazy-sorting here doesn't break immutability because it always
+ // return the same content. In the case of multi-threading, data race in accessing
+ // mUsesLibrariesSorted might result in unnecessary creation of sorted copies
+ // which is OK because the case is quite rare.
+ mUsesLibrariesSorted = sortLibraries(usesLibraries);
+ }
+ return mUsesLibrariesSorted;
+ }
+
+ @NonNull
+ @Override
public List<String> getUsesNativeLibraries() {
return usesNativeLibraries;
}
@@ -1391,6 +1414,15 @@
@NonNull
@Override
+ public String[] getUsesOptionalLibrariesSorted() {
+ if (mUsesOptionalLibrariesSorted == null) {
+ mUsesOptionalLibrariesSorted = sortLibraries(usesOptionalLibraries);
+ }
+ return mUsesOptionalLibrariesSorted;
+ }
+
+ @NonNull
+ @Override
public List<String> getUsesOptionalNativeLibraries() {
return usesOptionalNativeLibraries;
}
@@ -1405,6 +1437,15 @@
@Override
public List<String> getUsesSdkLibraries() { return usesSdkLibraries; }
+ @NonNull
+ @Override
+ public String[] getUsesSdkLibrariesSorted() {
+ if (mUsesSdkLibrariesSorted == null) {
+ mUsesSdkLibrariesSorted = sortLibraries(usesSdkLibraries);
+ }
+ return mUsesSdkLibrariesSorted;
+ }
+
@Nullable
@Override
public String[][] getUsesSdkLibrariesCertDigests() { return usesSdkLibrariesCertDigests; }
@@ -1419,6 +1460,15 @@
return usesStaticLibraries;
}
+ @NonNull
+ @Override
+ public String[] getUsesStaticLibrariesSorted() {
+ if (mUsesStaticLibrariesSorted == null) {
+ mUsesStaticLibrariesSorted = sortLibraries(usesStaticLibraries);
+ }
+ return mUsesStaticLibrariesSorted;
+ }
+
@Nullable
@Override
public String[][] getUsesStaticLibrariesCertDigests() {
@@ -2650,6 +2700,16 @@
return this;
}
+ private static String[] sortLibraries(List<String> libraryNames) {
+ int size = libraryNames.size();
+ if (size == 0) {
+ return EmptyArray.STRING;
+ }
+ var arr = libraryNames.toArray(EmptyArray.STRING);
+ Arrays.sort(arr);
+ return arr;
+ }
+
private void assignDerivedFields2() {
mBaseAppInfoFlags = PackageInfoUtils.appInfoFlags(this, null);
mBaseAppInfoPrivateFlags = PackageInfoUtils.appInfoPrivateFlags(this, null);
diff --git a/services/core/java/com/android/server/power/ShutdownCheckPoints.java b/services/core/java/com/android/server/power/ShutdownCheckPoints.java
index 1a9eae6..32f1bcf 100644
--- a/services/core/java/com/android/server/power/ShutdownCheckPoints.java
+++ b/services/core/java/com/android/server/power/ShutdownCheckPoints.java
@@ -54,6 +54,7 @@
private static final int MAX_DUMP_FILES = 20;
private static final SimpleDateFormat DATE_FORMAT =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS z");
+ private static final File[] EMPTY_FILE_ARRAY = {};
private final ArrayList<CheckPoint> mCheckPoints;
private final Injector mInjector;
@@ -381,6 +382,9 @@
return true;
}
});
+ if (files == null) {
+ return EMPTY_FILE_ARRAY;
+ }
Arrays.sort(files);
return files;
}
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
index 5ae6973..6c0e1a4 100644
--- a/services/core/java/com/android/server/security/FileIntegrityService.java
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -59,6 +60,7 @@
* A {@link SystemService} that provides file integrity related operations.
* @hide
*/
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
public class FileIntegrityService extends SystemService {
private static final String TAG = "FileIntegrityService";
@@ -71,7 +73,10 @@
private final ArrayList<X509Certificate> mTrustedCertificates =
new ArrayList<X509Certificate>();
- /** Gets the instance of the service */
+ /**
+ * Gets the instance of the service.
+ * @hide
+ */
public static FileIntegrityService getService() {
return LocalServices.getService(FileIntegrityService.class);
}
@@ -139,6 +144,7 @@
}
};
+ /** @hide */
public FileIntegrityService(final Context context) {
super(context);
try {
@@ -149,6 +155,7 @@
LocalServices.addService(FileIntegrityService.class, this);
}
+ /** @hide */
@Override
public void onStart() {
loadAllCertificates();
@@ -158,6 +165,7 @@
/**
* Returns whether the signature over the file's fs-verity digest can be verified by one of the
* known certiticates.
+ * @hide
*/
public boolean verifyPkcs7DetachedSignature(String signaturePath, String filePath)
throws IOException {
@@ -183,6 +191,16 @@
return false;
}
+ /**
+ * Enables fs-verity, if supported by the filesystem.
+ * @see <a href="https://www.kernel.org/doc/html/latest/filesystems/fsverity.html">
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+ public static void setUpFsVerity(@NonNull String filePath) throws IOException {
+ VerityUtils.setUpFsverity(filePath);
+ }
+
private void loadAllCertificates() {
// A better alternative to load certificates would be to read from .fs-verity kernel
// keyring, which fsverity_init loads to during earlier boot time from the same sources
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 8613b50..966329e 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -1749,7 +1749,7 @@
setExternalControl(true, vibHolder.stats);
}
if (DEBUG) {
- Slog.e(TAG, "Playing external vibration: " + vib);
+ Slog.d(TAG, "Playing external vibration: " + vib);
}
// Vibrator will start receiving data from external channels after this point.
// Report current time as the vibration start time, for debugging.
@@ -1763,7 +1763,7 @@
if (mCurrentExternalVibration != null
&& mCurrentExternalVibration.isHoldingSameVibration(vib)) {
if (DEBUG) {
- Slog.e(TAG, "Stopping external vibration" + vib);
+ Slog.d(TAG, "Stopping external vibration: " + vib);
}
endExternalVibrateLocked(
new Vibration.EndInfo(Vibration.Status.FINISHED),
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index fcb135e..11013e8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1451,8 +1451,7 @@
updatePictureInPictureMode(null, false);
} else {
mLastReportedMultiWindowMode = inMultiWindowMode;
- ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
- false /* ignoreVisibility */);
+ ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS);
}
}
}
@@ -3981,6 +3980,7 @@
}
void finishRelaunching() {
+ mLetterboxUiController.setRelauchingAfterRequestedOrientationChanged(false);
mTaskSupervisor.getActivityMetricsLogger().notifyActivityRelaunched(this);
if (mPendingRelaunchCount > 0) {
@@ -7724,13 +7724,17 @@
}
void setRequestedOrientation(int requestedOrientation) {
+ if (mLetterboxUiController.shouldIgnoreRequestedOrientation(requestedOrientation)) {
+ return;
+ }
setOrientation(requestedOrientation, this);
// Push the new configuration to the requested app in case where it's not pushed, e.g. when
// the request is handled at task level with letterbox.
if (!getMergedOverrideConfiguration().equals(
mLastReportedConfiguration.getMergedConfiguration())) {
- ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+ ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */,
+ false /* ignoreVisibility */, true /* isRequestedOrientationChanged */);
}
mAtmService.getTaskChangeNotificationController().notifyActivityRequestedOrientationChanged(
@@ -9060,7 +9064,13 @@
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow) {
return ensureActivityConfiguration(globalChanges, preserveWindow,
- false /* ignoreVisibility */);
+ false /* ignoreVisibility */, false /* isRequestedOrientationChanged */);
+ }
+
+ boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
+ boolean ignoreVisibility) {
+ return ensureActivityConfiguration(globalChanges, preserveWindow, ignoreVisibility,
+ false /* isRequestedOrientationChanged */);
}
/**
@@ -9074,11 +9084,13 @@
* (stopped state). This is useful for the case where we know the
* activity will be visible soon and we want to ensure its configuration
* before we make it visible.
+ * @param isRequestedOrientationChanged whether this is triggered in response to an app calling
+ * {@link android.app.Activity#setRequestedOrientation}.
* @return False if the activity was relaunched and true if it wasn't relaunched because we
* can't or the app handles the specific configuration that is changing.
*/
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
- boolean ignoreVisibility) {
+ boolean ignoreVisibility, boolean isRequestedOrientationChanged) {
final Task rootTask = getRootTask();
if (rootTask.mConfigWillChange) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Skipping config check "
@@ -9202,6 +9214,9 @@
} else {
mRelaunchReason = RELAUNCH_REASON_NONE;
}
+ if (isRequestedOrientationChanged) {
+ mLetterboxUiController.setRelauchingAfterRequestedOrientationChanged(true);
+ }
if (mState == PAUSING) {
// A little annoying: we are waiting for this activity to finish pausing. Let's not
// do anything now, but just flag that it needs to be restarted when done pausing.
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index b4af69e..034f5c8 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2645,9 +2645,9 @@
if (differentTopTask && !mAvoidMoveToFront) {
mStartActivity.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
- if (mSourceRecord == null || (mSourceRootTask.getTopNonFinishingActivity() != null
- && mSourceRootTask.getTopNonFinishingActivity().getTask()
- == mSourceRecord.getTask())) {
+ // TODO(b/264487981): Consider using BackgroundActivityStartController to determine
+ // whether to bring the launching activity to the front.
+ if (mSourceRecord == null || inTopNonFinishingTask(mSourceRecord)) {
// We really do want to push this one into the user's face, right now.
if (mLaunchTaskBehind && mSourceRecord != null) {
intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
@@ -2706,6 +2706,20 @@
mRootWindowContainer.getDefaultTaskDisplayArea(), mTargetRootTask);
}
+ private boolean inTopNonFinishingTask(ActivityRecord r) {
+ if (r == null || r.getTask() == null) {
+ return false;
+ }
+
+ final Task rTask = r.getTask();
+ final Task parent = rTask.getCreatedByOrganizerTask() != null
+ ? rTask.getCreatedByOrganizerTask() : r.getRootTask();
+ final ActivityRecord topNonFinishingActivity = parent != null
+ ? parent.getTopNonFinishingActivity() : null;
+
+ return topNonFinishingActivity != null && topNonFinishingActivity.getTask() == rTask;
+ }
+
private void resumeTargetRootTaskIfNeeded() {
if (mDoResume) {
final ActivityRecord next = mTargetRootTask.topRunningActivity(
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 473a6e5..b39ca5b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1451,6 +1451,7 @@
mUserLeaving = true;
}
+ mService.deferWindowLayout();
final Transition newTransition = task.mTransitionController.isShellTransitionsEnabled()
? task.mTransitionController.isCollecting() ? null
: task.mTransitionController.createTransition(TRANSIT_TO_FRONT) : null;
@@ -1458,9 +1459,6 @@
reason = reason + " findTaskToMoveToFront";
boolean reparented = false;
if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) {
- final Rect bounds = options.getLaunchBounds();
- task.setBounds(bounds);
-
Task targetRootTask =
mRootWindowContainer.getOrCreateRootTask(null, options, task, ON_TOP);
@@ -1473,14 +1471,11 @@
// task.reparent() should already placed the task on top,
// still need moveTaskToFrontLocked() below for any transition settings.
}
- if (targetRootTask.shouldResizeRootTaskWithLaunchBounds()) {
- targetRootTask.resize(bounds, !PRESERVE_WINDOWS, !DEFER_RESUME);
- } else {
- // WM resizeTask must be done after the task is moved to the correct stack,
- // because Task's setBounds() also updates dim layer's bounds, but that has
- // dependency on the root task.
- task.resize(false /* relayout */, false /* forced */);
- }
+ // The resizeTask must be done after the task is moved to the correct root task,
+ // because Task's setBounds() also updates dim layer's bounds, but that has
+ // dependency on the root task.
+ final Rect bounds = options.getLaunchBounds();
+ task.setBounds(bounds);
}
if (!reparented) {
@@ -1510,6 +1505,7 @@
}
} finally {
mUserLeaving = false;
+ mService.continueWindowLayout();
}
}
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index 7bd8c53..6b5f068 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -99,7 +99,7 @@
throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
}
return mService.getRecentTasks().createRecentTaskInfo(task,
- false /* stripExtras */, true /* getTasksAllowed */);
+ false /* stripExtras */);
} finally {
Binder.restoreCallingIdentity(origId);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 82237bb..f2cb755 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -85,6 +85,7 @@
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
import static android.window.DisplayAreaOrganizer.FEATURE_IME;
import static android.window.DisplayAreaOrganizer.FEATURE_ROOT;
import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
@@ -140,7 +141,6 @@
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainerChildProto.DISPLAY_CONTENT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_IME_VISIBILITY;
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;
@@ -500,7 +500,6 @@
// Accessed directly by all users.
private boolean mLayoutNeeded;
int pendingLayoutChanges;
- boolean mLayoutAndAssignWindowLayersScheduled;
/**
* Used to gate application window layout until we have sent the complete configuration.
@@ -1109,12 +1108,12 @@
* mDisplayMetrics.densityDpi / DENSITY_DEFAULT;
isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
mInsetsStateController = new InsetsStateController(this);
+ initializeDisplayBaseInfo();
mDisplayFrames = new DisplayFrames(mInsetsStateController.getRawInsetsState(),
mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation),
calculateRoundedCornersForRotation(mDisplayInfo.rotation),
calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation),
calculateDisplayShapeForRotation(mDisplayInfo.rotation));
- initializeDisplayBaseInfo();
mHoldScreenWakeLock = mWmService.mPowerManager.newWakeLock(
PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 7266d21..ba0413d 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -287,7 +287,7 @@
* <li>The activity has fixed orientation but not "locked" or "nosensor" one.
* </ul>
*/
- private boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
+ boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
return activity != null && !activity.inMultiWindowMode()
&& activity.getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED
// "locked" and "nosensor" values are often used by camera apps that can't
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 35e1fbb..1df534f 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -19,7 +19,6 @@
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
@@ -42,7 +41,6 @@
import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.res.Resources;
-import android.graphics.Rect;
import android.util.ArrayMap;
import android.util.IntArray;
import android.util.SparseArray;
@@ -276,21 +274,22 @@
/**
* @see WindowState#getInsetsState()
*/
- InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) {
- final WindowToken token = mDisplayContent.getWindowToken(attrs.token);
- if (token != null) {
- final InsetsState rotatedState = token.getFixedRotationTransformInsetsState();
- if (rotatedState != null) {
- return rotatedState;
+ void getInsetsForWindowMetrics(@Nullable WindowToken token,
+ @NonNull InsetsState outInsetsState) {
+ final InsetsState srcState = token != null && token.isFixedRotationTransforming()
+ ? token.getFixedRotationTransformInsetsState()
+ : mStateController.getRawInsetsState();
+ outInsetsState.set(srcState, true /* copySources */);
+ for (int i = mShowingTransientTypes.size() - 1; i >= 0; i--) {
+ final InsetsSource source = outInsetsState.peekSource(mShowingTransientTypes.get(i));
+ if (source != null) {
+ source.setVisible(false);
}
}
- final boolean alwaysOnTop = token != null && token.isAlwaysOnTop();
- // Always use windowing mode fullscreen when get insets for window metrics to make sure it
- // contains all insets types.
- final InsetsState originalState = enforceInsetsPolicyForTarget(attrs,
- WINDOWING_MODE_FULLSCREEN, alwaysOnTop, mStateController.getRawInsetsState());
- InsetsState state = adjustVisibilityForTransientTypes(originalState);
- return adjustInsetsForRoundedCorners(token, state, state == originalState);
+ adjustInsetsForRoundedCorners(token, outInsetsState, false /* copyState */);
+ if (token != null && token.hasSizeCompatBounds()) {
+ outInsetsState.scale(1f / token.getCompatScale());
+ }
}
/**
@@ -423,10 +422,9 @@
final Task task = activityRecord != null ? activityRecord.getTask() : null;
if (task != null && !task.getWindowConfiguration().tasksAreFloating()) {
// Use task bounds to calculating rounded corners if the task is not floating.
- final Rect roundedCornerFrame = new Rect(task.getBounds());
final InsetsState state = copyState ? new InsetsState(originalState)
: originalState;
- state.setRoundedCornerFrame(roundedCornerFrame);
+ state.setRoundedCornerFrame(task.getBounds());
return state;
}
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 5171f5b..659f8d7 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -33,7 +33,6 @@
import static com.android.server.wm.InsetsSourceProviderProto.SERVER_VISIBLE;
import static com.android.server.wm.InsetsSourceProviderProto.SOURCE;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_INSETS_CONTROL;
-import static com.android.server.wm.WindowManagerService.H.LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -508,11 +507,6 @@
return;
}
mClientVisible = clientVisible;
- if (!mDisplayContent.mLayoutAndAssignWindowLayersScheduled) {
- mDisplayContent.mLayoutAndAssignWindowLayersScheduled = true;
- mDisplayContent.mWmService.mH.obtainMessage(
- LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED, mDisplayContent).sendToTarget();
- }
updateVisibility();
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 9b84233..5e4f2ae 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -222,6 +222,11 @@
// See RefreshCallbackItem for context.
private boolean mIsCameraCompatRefreshCycleThroughStopEnabled = true;
+ // Whether should ignore app requested orientation in response to an app
+ // calling Activity#setRequestedOrientation. See
+ // LetterboxUiController#shouldIgnoreRequestedOrientation for details.
+ private final boolean mIsPolicyForIgnoringRequestedOrientationEnabled;
+
LetterboxConfiguration(Context systemUiContext) {
this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext,
() -> readLetterboxHorizontalReachabilityPositionFromConfig(systemUiContext,
@@ -274,10 +279,13 @@
R.bool.config_letterboxIsEnabledForTranslucentActivities);
mIsCameraCompatTreatmentEnabled = mContext.getResources().getBoolean(
R.bool.config_isWindowManagerCameraCompatTreatmentEnabled);
+ mIsCompatFakeFocusEnabled = mContext.getResources().getBoolean(
+ R.bool.config_isCompatFakeFocusEnabled);
+ mIsPolicyForIgnoringRequestedOrientationEnabled = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled);
+
mLetterboxConfigurationPersister = letterboxConfigurationPersister;
mLetterboxConfigurationPersister.start();
- mIsCompatFakeFocusEnabled = mContext.getResources()
- .getBoolean(R.bool.config_isCompatFakeFocusEnabled);
}
/**
@@ -1034,6 +1042,15 @@
mIsCompatFakeFocusEnabled = enabled;
}
+ /**
+ * Whether should ignore app requested orientation in response to an app calling
+ * {@link android.app.Activity#setRequestedOrientation}. See {@link
+ * LetterboxUiController#shouldIgnoreRequestedOrientation} for details.
+ */
+ boolean isPolicyForIgnoringRequestedOrientationEnabled() {
+ return mIsPolicyForIgnoringRequestedOrientationEnabled;
+ }
+
/** Whether camera compatibility treatment is enabled. */
boolean isCameraCompatTreatmentEnabled(boolean checkDeviceConfig) {
return mIsCameraCompatTreatmentEnabled
@@ -1044,7 +1061,7 @@
// DeviceConfig.OnPropertiesChangedListener
private static boolean isCameraCompatTreatmentAllowed() {
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- "enable_camera_compat_treatment", false);
+ "enable_compat_camera_treatment", true);
}
/** Whether camera compatibility refresh is enabled. */
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index fd7e082..0c8a645 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -17,10 +17,13 @@
package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.ActivityInfo.screenOrientationToString;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
@@ -55,6 +58,8 @@
import android.annotation.Nullable;
import android.app.ActivityManager.TaskDescription;
+import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
@@ -74,6 +79,7 @@
import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
import java.io.PrintWriter;
+import java.util.function.BooleanSupplier;
/** Controls behaviour of the letterbox UI for {@link mActivityRecord}. */
// TODO(b/185262487): Improve test coverage of this class. Parts of it are tested in
@@ -131,12 +137,37 @@
// DisplayRotationCompatPolicy.
private boolean mIsRefreshAfterRotationRequested;
+ @Nullable
+ private final Boolean mBooleanPropertyIgnoreRequestedOrientation;
+
+ private boolean mIsRelauchingAfterRequestedOrientationChanged;
+
LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) {
mLetterboxConfiguration = wmService.mLetterboxConfiguration;
// Given activityRecord may not be fully constructed since LetterboxUiController
// is created in its constructor. It shouldn't be used in this constructor but it's safe
// to use it after since controller is only used in ActivityRecord.
mActivityRecord = activityRecord;
+
+ PackageManager packageManager = wmService.mContext.getPackageManager();
+ mBooleanPropertyIgnoreRequestedOrientation =
+ readComponentProperty(packageManager, mActivityRecord.packageName,
+ mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled,
+ PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION);
+ }
+
+ @Nullable
+ private static Boolean readComponentProperty(PackageManager packageManager, String packageName,
+ BooleanSupplier gatingCondition, String propertyName) {
+ if (!gatingCondition.getAsBoolean()) {
+ return null;
+ }
+ try {
+ return packageManager.getProperty(propertyName, packageName).getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ // No such property name.
+ }
+ return null;
}
/** Cleans up {@link Letterbox} if it exists.*/
@@ -154,6 +185,72 @@
}
/**
+ * Whether should ignore app requested orientation in response to an app
+ * calling {@link android.app.Activity#setRequestedOrientation}.
+ *
+ * <p>This is needed to avoid getting into {@link android.app.Activity#setRequestedOrientation}
+ * loop when {@link DisplayContent#getIgnoreOrientationRequest} is enabled or device has
+ * landscape natural orientation which app developers don't expect. For example, the loop can
+ * look like this:
+ * <ol>
+ * <li>App sets default orientation to "unspecified" at runtime
+ * <li>App requests to "portrait" after checking some condition (e.g. display rotation).
+ * <li>(2) leads to fullscreen -> letterboxed bounds change and activity relaunch because
+ * app can't handle the corresponding config changes.
+ * <li>Loop goes back to (1)
+ * </ol>
+ *
+ * <p>This treatment is enabled when the following conditions are met:
+ * <ul>
+ * <li>Flag gating the treatment is enabled
+ * <li>Opt-out component property isn't enabled
+ * <li>Opt-in component property or per-app override are enabled
+ * <li>Activity is relaunched after {@link android.app.Activity#setRequestedOrientation}
+ * call from an app or camera compat force rotation treatment is active for the activity.
+ * </ul>
+ */
+ boolean shouldIgnoreRequestedOrientation(@ScreenOrientation int requestedOrientation) {
+ if (!mLetterboxConfiguration.isPolicyForIgnoringRequestedOrientationEnabled()) {
+ return false;
+ }
+ if (Boolean.FALSE.equals(mBooleanPropertyIgnoreRequestedOrientation)) {
+ return false;
+ }
+ if (!Boolean.TRUE.equals(mBooleanPropertyIgnoreRequestedOrientation)
+ && !mActivityRecord.info.isChangeEnabled(
+ OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION)) {
+ return false;
+ }
+ if (mIsRelauchingAfterRequestedOrientationChanged) {
+ Slog.w(TAG, "Ignoring orientation update to "
+ + screenOrientationToString(requestedOrientation)
+ + " due to relaunching after setRequestedOrientation for " + mActivityRecord);
+ return true;
+ }
+ DisplayContent displayContent = mActivityRecord.mDisplayContent;
+ if (displayContent == null) {
+ return false;
+ }
+ if (displayContent.mDisplayRotationCompatPolicy != null
+ && displayContent.mDisplayRotationCompatPolicy
+ .isTreatmentEnabledForActivity(mActivityRecord)) {
+ Slog.w(TAG, "Ignoring orientation update to "
+ + screenOrientationToString(requestedOrientation)
+ + " due to camera compat treatment for " + mActivityRecord);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Sets whether an activity is relaunching after the app has called {@link
+ * android.app.Activity#setRequestedOrientation}.
+ */
+ void setRelauchingAfterRequestedOrientationChanged(boolean isRelaunching) {
+ mIsRelauchingAfterRequestedOrientationChanged = isRelaunching;
+ }
+
+ /**
* Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}
* following the camera compat force rotation in {@link DisplayRotationCompatPolicy}.
*/
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 4be1c83..9e95918 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -976,7 +976,7 @@
continue;
}
- res.add(createRecentTaskInfo(task, true /* stripExtras */, getTasksAllowed));
+ res.add(createRecentTaskInfo(task, true /* stripExtras */));
}
return res;
}
@@ -1889,8 +1889,7 @@
/**
* Creates a new RecentTaskInfo from a Task.
*/
- ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras,
- boolean getTasksAllowed) {
+ ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras) {
final ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
// If the recent Task is detached, we consider it will be re-attached to the default
// TaskDisplayArea because we currently only support recent overview in the default TDA.
@@ -1902,9 +1901,6 @@
rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID;
rti.persistentId = rti.taskId;
rti.lastSnapshotData.set(tr.mLastTaskSnapshotData);
- if (!getTasksAllowed) {
- Task.trimIneffectiveInfo(tr, rti);
- }
// Fill in organized child task info for the task created by organizer.
if (tr.mCreatedByOrganizer) {
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 1cc1a57..614b405 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -173,10 +173,6 @@
}
// Fill in some deprecated values
rti.id = rti.taskId;
-
- if (!mAllowed) {
- Task.trimIneffectiveInfo(task, rti);
- }
return rti;
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8609e10..3b5b5a9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -423,9 +423,6 @@
// This number will be assigned when we evaluate OOM scores for all visible tasks.
int mLayerRank = LAYER_RANK_INVISIBLE;
- /** Helper object used for updating override configuration. */
- private Configuration mTmpConfig = new Configuration();
-
/* Unique identifier for this task. */
final int mTaskId;
/* User for which this task was created. */
@@ -796,16 +793,11 @@
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "resizeTask_" + mTaskId);
- boolean updatedConfig = false;
- mTmpConfig.setTo(getResolvedOverrideConfiguration());
- if (setBounds(bounds) != BOUNDS_CHANGE_NONE) {
- updatedConfig = !mTmpConfig.equals(getResolvedOverrideConfiguration());
- }
// This variable holds information whether the configuration didn't change in a
// significant way and the activity was kept the way it was. If it's false, it means
// the activity had to be relaunched due to configuration change.
boolean kept = true;
- if (updatedConfig) {
+ if (setBounds(bounds, forced) != BOUNDS_CHANGE_NONE) {
final ActivityRecord r = topRunningActivityLocked();
if (r != null) {
kept = r.ensureActivityConfiguration(0 /* globalChanges */,
@@ -822,8 +814,6 @@
}
}
}
- resize(kept, forced);
-
saveLaunchingStateIfNeeded();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -2693,12 +2683,6 @@
return canSpecifyOrientation() && getDisplayArea().canSpecifyOrientation(orientation);
}
- void resize(boolean relayout, boolean forced) {
- if (setBounds(getRequestedOverrideBounds(), forced) != BOUNDS_CHANGE_NONE && relayout) {
- getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
- }
- }
-
@Override
void onDisplayChanged(DisplayContent dc) {
final boolean isRootTask = isRootTask();
@@ -3418,27 +3402,6 @@
info.isSleeping = shouldSleepActivities();
}
- /**
- * Removes the activity info if the activity belongs to a different uid, which is
- * different from the app that hosts the task.
- */
- static void trimIneffectiveInfo(Task task, TaskInfo info) {
- final ActivityRecord baseActivity = task.getActivity(r -> !r.finishing,
- false /* traverseTopToBottom */);
- final int baseActivityUid =
- baseActivity != null ? baseActivity.getUid() : task.effectiveUid;
-
- if (info.topActivityInfo != null
- && task.effectiveUid != info.topActivityInfo.applicationInfo.uid) {
- info.topActivity = null;
- info.topActivityInfo = null;
- }
-
- if (task.effectiveUid != baseActivityUid) {
- info.baseActivity = null;
- }
- }
-
@Nullable PictureInPictureParams getPictureInPictureParams() {
final Task topTask = getTopMostTask();
if (topTask == null) return null;
@@ -4771,14 +4734,6 @@
}
}
- /**
- * Returns true if this root task should be resized to match the bounds specified by
- * {@link ActivityOptions#setLaunchBounds} when launching an activity into the root task.
- */
- boolean shouldResizeRootTaskWithLaunchBounds() {
- return inPinnedWindowingMode();
- }
-
void checkTranslucentActivityWaiting(ActivityRecord top) {
if (mTranslucentActivityWaiting != top) {
mUndrawnActivitiesBelowTopTranslucent.clear();
@@ -5603,29 +5558,6 @@
return true;
}
- // TODO: Can only be called from special methods in ActivityTaskSupervisor.
- // Need to consolidate those calls points into this resize method so anyone can call directly.
- void resize(Rect displayedBounds, boolean preserveWindows, boolean deferResume) {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "task.resize_" + getRootTaskId());
- mAtmService.deferWindowLayout();
- try {
- // TODO: Why not just set this on the root task directly vs. on each tasks?
- // Update override configurations of all tasks in the root task.
- forAllTasks(task -> {
- if (task.isResizeable()) {
- task.setBounds(displayedBounds);
- }
- }, true /* traverseTopToBottom */);
-
- if (!deferResume) {
- ensureVisibleActivitiesConfiguration(topRunningActivity(), preserveWindows);
- }
- } finally {
- mAtmService.continueWindowLayout();
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
- }
-
boolean willActivityBeVisible(IBinder token) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r == null) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index b887861..dd489aa 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -96,6 +96,7 @@
import android.view.SurfaceControl;
import android.window.ITaskFragmentOrganizer;
import android.window.ScreenCapture;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizerToken;
@@ -306,6 +307,10 @@
@Nullable
private final IBinder mFragmentToken;
+ /** The animation override params for animation running on this TaskFragment. */
+ @NonNull
+ private TaskFragmentAnimationParams mAnimationParams = TaskFragmentAnimationParams.DEFAULT;
+
/**
* The bounds of the embedded TaskFragment relative to the parent Task.
* {@code null} if it is not {@link #mIsEmbedded}
@@ -453,6 +458,15 @@
&& organizer.asBinder().equals(mTaskFragmentOrganizer.asBinder());
}
+ void setAnimationParams(@NonNull TaskFragmentAnimationParams animationParams) {
+ mAnimationParams = animationParams;
+ }
+
+ @NonNull
+ TaskFragmentAnimationParams getAnimationParams() {
+ return mAnimationParams;
+ }
+
TaskFragment getAdjacentTaskFragment() {
return mAdjacentTaskFragment;
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 302538f..ad1e879 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -647,6 +647,7 @@
t.setCornerRadius(targetLeash, 0);
t.setShadowRadius(targetLeash, 0);
t.setMatrix(targetLeash, 1, 0, 0, 1);
+ t.setAlpha(targetLeash, 1);
// The bounds sent to the transition is always a real bounds. This means we lose
// information about "null" bounds (inheriting from parent). Core will fix-up
// non-organized window surface bounds; however, since Core can't touch organized
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 5de143d9..7f9e808 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -756,9 +756,7 @@
private void updateWallpaperTokens(boolean visible) {
for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
- if (token.updateWallpaperWindows(visible)) {
- token.mDisplayContent.assignWindowLayers(false /* setLayoutNeeded */);
- }
+ token.updateWallpaperWindows(visible);
}
}
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 87f4ad4..210d5a5 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -100,7 +100,7 @@
}
/** Returns {@code true} if visibility is changed. */
- boolean updateWallpaperWindows(boolean visible) {
+ void updateWallpaperWindows(boolean visible) {
boolean changed = false;
if (mVisibleRequested != visible) {
ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b",
@@ -117,7 +117,6 @@
linkFixedRotationTransform(wallpaperTarget.mToken);
}
}
- return changed;
}
final WindowState wallpaperTarget =
@@ -143,7 +142,6 @@
}
setVisible(visible);
- return changed;
}
private void setVisible(boolean visible) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 6737052..9e94fdf 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -265,14 +265,6 @@
}
/**
- * Callback which is triggered while changing the parent, after setting up the surface but
- * before asking the parent to assign child layers.
- */
- interface PreAssignChildLayersCallback {
- void onPreAssignChildLayers();
- }
-
- /**
* True if this an AppWindowToken and the activity which created this was launched with
* ActivityOptions.setLaunchTaskBehind.
*
@@ -605,11 +597,6 @@
*/
@Override
void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
- onParentChanged(newParent, oldParent, null);
- }
-
- void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent,
- PreAssignChildLayersCallback callback) {
super.onParentChanged(newParent, oldParent);
if (mParent == null) {
return;
@@ -627,13 +614,8 @@
reparentSurfaceControl(getSyncTransaction(), mParent.mSurfaceControl);
}
- if (callback != null) {
- callback.onPreAssignChildLayers();
- }
-
// Either way we need to ask the parent to assign us a Z-order.
mParent.assignChildLayers();
- scheduleAnimation();
}
void createSurfaceControl(boolean force) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
index 6abb8fd..42b556f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
@@ -56,7 +56,4 @@
static final boolean SHOW_STACK_CRAWLS = false;
static final boolean DEBUG_WINDOW_CROP = false;
static final boolean DEBUG_UNKNOWN_APP_VISIBILITY = false;
-
- // TODO(b/239501597) : Have a system property to control this flag.
- public static final boolean DEBUG_IME_VISIBILITY = false;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b83f423..2c8b844 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5358,7 +5358,6 @@
public static final int ANIMATION_FAILSAFE = 60;
public static final int RECOMPUTE_FOCUS = 61;
public static final int ON_POINTER_DOWN_OUTSIDE_FOCUS = 62;
- public static final int LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED = 63;
public static final int WINDOW_STATE_BLAST_SYNC_TIMEOUT = 64;
public static final int REPARENT_TASK_TO_DEFAULT_DISPLAY = 65;
public static final int INSETS_CHANGED = 66;
@@ -5635,14 +5634,6 @@
}
break;
}
- case LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED: {
- synchronized (mGlobalLock) {
- final DisplayContent displayContent = (DisplayContent) msg.obj;
- displayContent.mLayoutAndAssignWindowLayersScheduled = false;
- displayContent.layoutAndAssignWindowLayersIfNeeded();
- }
- break;
- }
case WINDOW_STATE_BLAST_SYNC_TIMEOUT: {
synchronized (mGlobalLock) {
final WindowState ws = (WindowState) msg.obj;
@@ -8924,28 +8915,17 @@
}
@Override
- public boolean getWindowInsets(WindowManager.LayoutParams attrs, int displayId,
- InsetsState outInsetsState) {
- final int uid = Binder.getCallingUid();
+ public boolean getWindowInsets(int displayId, IBinder token, InsetsState outInsetsState) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
- final DisplayContent dc = getDisplayContentOrCreate(displayId, attrs.token);
+ final DisplayContent dc = getDisplayContentOrCreate(displayId, token);
if (dc == null) {
throw new WindowManager.InvalidDisplayException("Display#" + displayId
+ "could not be found!");
}
- final WindowToken token = dc.getWindowToken(attrs.token);
- final float overrideScale = mAtmService.mCompatModePackages.getCompatScale(
- attrs.packageName, uid);
- final InsetsState state = dc.getInsetsPolicy().getInsetsForWindowMetrics(attrs);
- outInsetsState.set(state, true /* copySources */);
- if (WindowState.hasCompatScale(attrs, token, overrideScale)) {
- final float compatScale = token != null && token.hasSizeCompatBounds()
- ? token.getCompatScale() * overrideScale
- : overrideScale;
- outInsetsState.scale(1f / compatScale);
- }
+ final WindowToken winToken = dc.getWindowToken(token);
+ dc.getInsetsPolicy().getInsetsForWindowMetrics(winToken, outInsetsState);
return dc.getDisplayPolicy().areSystemBarsForcedConsumedLw();
}
} finally {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index b624e80..7d15902 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -21,6 +21,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS;
@@ -44,6 +45,7 @@
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_SHORTCUT;
@@ -88,7 +90,9 @@
import android.window.ITransitionPlayer;
import android.window.IWindowContainerTransactionCallback;
import android.window.IWindowOrganizerController;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentCreationParams;
+import android.window.TaskFragmentOperation;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
@@ -658,7 +662,8 @@
}
}
- if (windowingMode > -1) {
+ final int prevWindowingMode = container.getWindowingMode();
+ if (windowingMode > -1 && prevWindowingMode != windowingMode) {
if (mService.isInLockTaskMode()
&& WindowConfiguration.inMultiWindowMode(windowingMode)) {
throw new UnsupportedOperationException("Not supported to set multi-window"
@@ -672,9 +677,8 @@
return effects;
}
- final int prevMode = container.getWindowingMode();
container.setWindowingMode(windowingMode);
- if (prevMode != container.getWindowingMode()) {
+ if (prevWindowingMode != container.getWindowingMode()) {
// The activity in the container may become focusable or non-focusable due to
// windowing modes changes (such as entering or leaving pinned windowing mode),
// so also apply the lifecycle effects to this transaction.
@@ -1138,6 +1142,10 @@
fragment.setCompanionTaskFragment(companion);
break;
}
+ case HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION: {
+ effects |= applyTaskFragmentOperation(hop, errorCallbackToken, organizer);
+ break;
+ }
default: {
// The other operations may change task order so they are skipped while in lock
// task mode. The above operations are still allowed because they don't move
@@ -1270,6 +1278,47 @@
return effects;
}
+ /** Applies change set through {@link WindowContainerTransaction#setTaskFragmentOperation}. */
+ private int applyTaskFragmentOperation(@NonNull WindowContainerTransaction.HierarchyOp hop,
+ @Nullable IBinder errorCallbackToken, @Nullable ITaskFragmentOrganizer organizer) {
+ final IBinder fragmentToken = hop.getContainer();
+ final TaskFragment taskFragment = mLaunchTaskFragments.get(fragmentToken);
+ final TaskFragmentOperation operation = hop.getTaskFragmentOperation();
+ if (operation == null) {
+ final Throwable exception = new IllegalArgumentException(
+ "TaskFragmentOperation must be non-null");
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
+ HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION, exception);
+ return 0;
+ }
+ final int opType = operation.getOpType();
+ if (taskFragment == null || !taskFragment.isAttached()) {
+ final Throwable exception = new IllegalArgumentException(
+ "Not allowed to apply operation on invalid fragment tokens opType=" + opType);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
+ HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION, exception);
+ return 0;
+ }
+
+ int effect = 0;
+ switch (opType) {
+ case OP_TYPE_SET_ANIMATION_PARAMS: {
+ final TaskFragmentAnimationParams animationParams = operation.getAnimationParams();
+ if (animationParams == null) {
+ final Throwable exception = new IllegalArgumentException(
+ "TaskFragmentAnimationParams must be non-null");
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
+ HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION, exception);
+ break;
+ }
+ taskFragment.setAnimationParams(animationParams);
+ break;
+ }
+ // TODO(b/263436063): move other TaskFragment related operation here.
+ }
+ return effect;
+ }
+
/** A helper method to send minimum dimension violation error to the client. */
private void sendMinimumDimensionViolation(TaskFragment taskFragment, Point minDimensions,
IBinder errorCallbackToken, String reason) {
@@ -1698,6 +1747,7 @@
break;
case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
case HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT:
+ case HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION:
enforceTaskFragmentOrganized(func, hop.getContainer(), organizer);
break;
case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT:
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ae31ee8..59c6737 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -364,7 +364,7 @@
boolean mHidden = true; // Used to determine if to show child windows.
private boolean mDragResizing;
private boolean mDragResizingChangeReported = true;
- private boolean mRedrawForSyncReported;
+ private boolean mRedrawForSyncReported = true;
/**
* Used to assosciate a given set of state changes sent from MSG_RESIZED
@@ -1276,24 +1276,15 @@
* @see ActivityRecord#hasSizeCompatBounds()
*/
boolean hasCompatScale() {
- return hasCompatScale(mAttrs, mActivityRecord, mOverrideScale);
- }
-
- /**
- * @return {@code true} if the application runs in size compatibility mode.
- * @see android.content.res.CompatibilityInfo#supportsScreen
- * @see ActivityRecord#hasSizeCompatBounds()
- */
- static boolean hasCompatScale(WindowManager.LayoutParams attrs, WindowToken token,
- float overrideScale) {
- if ((attrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0) {
+ if ((mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0) {
return true;
}
- if (attrs.type == TYPE_APPLICATION_STARTING) {
+ if (mAttrs.type == TYPE_APPLICATION_STARTING) {
// Exclude starting window because it is not displayed by the application.
return false;
}
- return token != null && token.hasSizeCompatBounds() || overrideScale != 1f;
+ return mActivityRecord != null && mActivityRecord.hasSizeCompatBounds()
+ || mOverrideScale != 1f;
}
/**
@@ -5956,8 +5947,11 @@
mSyncSeqId++;
if (getSyncMethod() == BLASTSyncEngine.METHOD_BLAST) {
mPrepareSyncSeqId = mSyncSeqId;
+ requestRedrawForSync();
+ } else if (mHasSurface && mWinAnimator.mDrawState != DRAW_PENDING) {
+ // Only need to request redraw if the window has reported draw.
+ requestRedrawForSync();
}
- requestRedrawForSync();
return true;
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index a0ba8fd..5ecf737 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -428,10 +428,6 @@
mShownAlpha = mAlpha;
}
- private boolean isInBlastSync() {
- return mService.useBLASTSync() && mWin.useBLASTSync();
- }
-
void prepareSurfaceLocked(SurfaceControl.Transaction t) {
final WindowState w = mWin;
if (!hasSurface()) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8c2065e..7e93522 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -18,7 +18,13 @@
import static android.Manifest.permission.BIND_DEVICE_ADMIN;
import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL;
+import static android.Manifest.permission.QUERY_ADMIN_POLICY;
import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
+import static android.Manifest.permission.SET_TIME;
+import static android.Manifest.permission.SET_TIME_ZONE;
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.AppOpsManager.MODE_ALLOWED;
@@ -137,6 +143,7 @@
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
@@ -715,7 +722,10 @@
+ "management app's authentication policy";
private static final String NOT_SYSTEM_CALLER_MSG = "Only the system can %s";
+ private static final String PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG =
+ "enable_permission_based_access";
private static final String ENABLE_COEXISTENCE_FLAG = "enable_coexistence";
+ private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false;
private static final boolean DEFAULT_ENABLE_COEXISTENCE_FLAG = false;
// TODO(b/258425381) remove the flag after rollout.
@@ -8033,9 +8043,15 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
+ if (isPermissionCheckFlagEnabled()) {
+ // The effect of this policy is device-wide.
+ enforcePermission(SET_TIME, UserHandle.USER_ALL);
+ } else {
+ Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
+ caller));
+ }
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME, enabled ? 1 : 0));
@@ -8057,8 +8073,14 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
+
+ if (isPermissionCheckFlagEnabled()) {
+ enforceCanQuery(SET_TIME, UserHandle.USER_ALL);
+ } else {
+ Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
+ caller));
+ }
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0;
}
@@ -8074,8 +8096,15 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
+
+ if (isPermissionCheckFlagEnabled()) {
+ // The effect of this policy is device-wide.
+ enforcePermission(SET_TIME_ZONE, UserHandle.USER_ALL);
+ } else {
+ Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
+ caller));
+ }
if (isCoexistenceEnabled(caller)) {
mDevicePolicyEngine.setGlobalPolicy(
@@ -8107,8 +8136,15 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
+
+ if (isPermissionCheckFlagEnabled()) {
+ // The effect of this policy is device-wide.
+ enforceCanQuery(SET_TIME_ZONE, UserHandle.USER_ALL);
+ } else {
+ Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
+ caller));
+ }
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
}
@@ -13031,8 +13067,14 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ if (isPermissionCheckFlagEnabled()) {
+ // This is a global action.
+ enforcePermission(SET_TIME, UserHandle.USER_ALL);
+ } else {
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ }
// Don't allow set time when auto time is on.
if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) == 1) {
@@ -13051,8 +13093,14 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ if (isPermissionCheckFlagEnabled()) {
+ // This is a global action.
+ enforcePermission(SET_TIME_ZONE, UserHandle.USER_ALL);
+ } else {
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ }
// Don't allow set timezone when auto timezone is on.
if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) {
@@ -13657,6 +13705,16 @@
broadcastIntentToDevicePolicyManagerRoleHolder(intent, parentHandle);
}
+ @Override
+ public void enforcePermission(String permission, int targetUserId) {
+ DevicePolicyManagerService.this.enforcePermission(permission, targetUserId);
+ }
+
+ @Override
+ public boolean hasPermission(String permission, int targetUserId) {
+ return DevicePolicyManagerService.this.hasPermission(permission, targetUserId);
+ }
+
private void broadcastIntentToCrossProfileManifestReceivers(
Intent intent, UserHandle userHandle, boolean requiresPermission) {
final int userId = userHandle.getIdentifier();
@@ -18408,14 +18466,25 @@
}
}
+
private String getDevicePolicyManagementRoleHolderPackageName(Context context) {
RoleManager roleManager = context.getSystemService(RoleManager.class);
- List<String> roleHolders =
- roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
- if (roleHolders.isEmpty()) {
- return null;
- }
- return roleHolders.get(0);
+
+ // Calling identity needs to be cleared as this method is used in the permissions checks.
+ return mInjector.binderWithCleanCallingIdentity(() -> {
+ List<String> roleHolders =
+ roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
+ if (roleHolders.isEmpty()) {
+ return null;
+ }
+ return roleHolders.get(0);
+ });
+ }
+
+ private boolean isDevicePolicyManagementRoleHolder(CallerIdentity caller) {
+ String devicePolicyManagementRoleHolderPackageName =
+ getDevicePolicyManagementRoleHolderPackageName(mContext);
+ return caller.getPackageName().equals(devicePolicyManagementRoleHolderPackageName);
}
private void resetInteractAcrossProfilesAppOps() {
@@ -19513,6 +19582,192 @@
});
}
+ // DPC types
+ private static final int DEFAULT_DEVICE_OWNER = 0;
+ private static final int FINANCED_DEVICE_OWNER = 1;
+ private static final int PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE = 2;
+ private static final int PROFILE_OWNER_ON_USER_0 = 3;
+ private static final int PROFILE_OWNER = 4;
+
+ // Permissions of existing DPC types.
+ private static final List<String> DEFAULT_DEVICE_OWNER_PERMISSIONS = List.of(
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
+ SET_TIME,
+ SET_TIME_ZONE);
+ private static final List<String> FINANCED_DEVICE_OWNER_PERMISSIONS = List.of(
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
+ private static final List<String> PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS =
+ List.of(
+ MANAGE_DEVICE_POLICY_ACROSS_USERS,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
+ SET_TIME,
+ SET_TIME_ZONE);
+ private static final List<String> PROFILE_OWNER_ON_USER_0_PERMISSIONS = List.of(
+ SET_TIME,
+ SET_TIME_ZONE);
+ private static final List<String> PROFILE_OWNER_PERMISSIONS = List.of(
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
+
+ private static final HashMap<Integer, List<String>> DPC_PERMISSIONS = new HashMap<>();
+ {
+ DPC_PERMISSIONS.put(DEFAULT_DEVICE_OWNER, DEFAULT_DEVICE_OWNER_PERMISSIONS);
+ DPC_PERMISSIONS.put(FINANCED_DEVICE_OWNER, FINANCED_DEVICE_OWNER_PERMISSIONS);
+ DPC_PERMISSIONS.put(PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE,
+ PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS);
+ DPC_PERMISSIONS.put(PROFILE_OWNER_ON_USER_0, PROFILE_OWNER_ON_USER_0_PERMISSIONS);
+ DPC_PERMISSIONS.put(PROFILE_OWNER, PROFILE_OWNER_PERMISSIONS);
+ }
+
+ //TODO(b/254253251) Fill this map in as new permissions are added for policies.
+ private static final HashMap<String, Integer> ACTIVE_ADMIN_POLICIES = new HashMap<>();
+
+ private static final HashMap<String, String> CROSS_USER_PERMISSIONS =
+ new HashMap<>();
+ {
+ // Auto time is intrinsically global so there is no cross-user permission.
+ CROSS_USER_PERMISSIONS.put(SET_TIME, null);
+ CROSS_USER_PERMISSIONS.put(SET_TIME_ZONE, null);
+ }
+
+ /**
+ * Checks if the calling process has been granted permission to apply a device policy on a
+ * specific user.
+ * The given permission will be checked along with its associated cross-user permission if it
+ * exists and the target user is different to the calling user.
+ *
+ * @param permission The name of the permission being checked.
+ * @param targetUserId The userId of the user which the caller needs permission to act on.
+ * @throws SecurityException if the caller has not been granted the given permission,
+ * the associtated cross-user permission if the caller's user is different to the target user.
+ */
+ private void enforcePermission(String permission, int targetUserId)
+ throws SecurityException {
+ if (!hasPermission(permission, targetUserId)) {
+ throw new SecurityException("Caller does not have the required permissions for "
+ + "this user. Permissions required: {"
+ + permission
+ + ", "
+ + CROSS_USER_PERMISSIONS.get(permission)
+ + "}");
+ }
+ }
+
+ /**
+ * Return whether the calling process has been granted permission to query a device policy on
+ * a specific user.
+ *
+ * @param permission The name of the permission being checked.
+ * @param targetUserId The userId of the user which the caller needs permission to act on.
+ * @throws SecurityException if the caller has not been granted the given permission,
+ * the associatated cross-user permission if the caller's user is different to the target user
+ * and if the user has not been granted {@link QUERY_ADMIN_POLICY}.
+ */
+ private void enforceCanQuery(String permission, int targetUserId) throws SecurityException {
+ if (hasPermission(QUERY_ADMIN_POLICY)) {
+ return;
+ }
+ enforcePermission(permission, targetUserId);
+ }
+
+ /**
+ * Return whether the calling process has been granted permission to apply a device policy on
+ * a specific user.
+ *
+ * @param permission The name of the permission being checked.
+ * @param targetUserId The userId of the user which the caller needs permission to act on.
+ */
+ private boolean hasPermission(String permission, int targetUserId) {
+ boolean hasPermissionOnOwnUser = hasPermission(permission);
+ boolean hasPermissionOnTargetUser = true;
+ if (hasPermissionOnOwnUser & getCallerIdentity().getUserId() != targetUserId) {
+ hasPermissionOnTargetUser = hasPermission(CROSS_USER_PERMISSIONS.get(permission));
+ }
+ return hasPermissionOnOwnUser && hasPermissionOnTargetUser;
+ }
+
+ /**
+ * Return whether the calling process has been granted the given permission.
+ *
+ * @param permission The name of the permission being checked.
+ */
+ private boolean hasPermission(String permission) {
+ if (permission == null) {
+ return true;
+ }
+
+ CallerIdentity caller = getCallerIdentity();
+
+ // Check if the caller holds the permission
+ if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
+ return true;
+ }
+ // Check the permissions of DPCs
+ if (isDefaultDeviceOwner(caller)) {
+ return DPC_PERMISSIONS.get(DEFAULT_DEVICE_OWNER).contains(permission);
+ }
+ if (isFinancedDeviceOwner(caller)) {
+ return DPC_PERMISSIONS.get(FINANCED_DEVICE_OWNER).contains(permission);
+ }
+ if (isProfileOwnerOfOrganizationOwnedDevice(caller)) {
+ return DPC_PERMISSIONS.get(PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE).contains(
+ permission);
+ }
+ if (isProfileOwnerOnUser0(caller)) {
+ return DPC_PERMISSIONS.get(PROFILE_OWNER_ON_USER_0).contains(permission);
+ }
+ if (isProfileOwner(caller)) {
+ return DPC_PERMISSIONS.get(PROFILE_OWNER).contains(permission);
+ }
+ // Check the permission for the role-holder
+ if (isDevicePolicyManagementRoleHolder(caller)) {
+ return anyDpcHasPermission(permission, mContext.getUserId());
+ }
+ // Check if the caller is an active admin that uses a certain policy.
+ if (ACTIVE_ADMIN_POLICIES.containsKey(permission)) {
+ return getActiveAdminForCallerLocked(
+ null, ACTIVE_ADMIN_POLICIES.get(permission), false) != null;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns whether there is a DPC on the given user that has been granted the given permission.
+ *
+ * @param permission The name of the permission being checked.
+ * @param userId The id of the user to check.
+ */
+ private boolean anyDpcHasPermission(String permission, int userId) {
+ if (mOwners.isDefaultDeviceOwnerUserId(userId)) {
+ return DPC_PERMISSIONS.get(DEFAULT_DEVICE_OWNER).contains(permission);
+ }
+ if (mOwners.isFinancedDeviceOwnerUserId(userId)) {
+ return DPC_PERMISSIONS.get(FINANCED_DEVICE_OWNER).contains(permission);
+ }
+ if (mOwners.isProfileOwnerOfOrganizationOwnedDevice(userId)) {
+ return DPC_PERMISSIONS.get(PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE).contains(
+ permission);
+ }
+ if (userId == 0 && mOwners.hasProfileOwner(0)) {
+ return DPC_PERMISSIONS.get(PROFILE_OWNER_ON_USER_0).contains(permission);
+ }
+ if (mOwners.hasProfileOwner(userId)) {
+ return DPC_PERMISSIONS.get(PROFILE_OWNER).contains(permission);
+ }
+ return false;
+ }
+
+ private boolean isPermissionCheckFlagEnabled() {
+ return DeviceConfig.getBoolean(
+ NAMESPACE_DEVICE_POLICY_MANAGER,
+ PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG,
+ DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG);
+ }
+
// TODO(b/260560985): properly gate coexistence changes
private boolean isCoexistenceEnabled(CallerIdentity caller) {
return isCoexistenceFlagEnabled()
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 6f172e4..581a199 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -19,6 +19,7 @@
import static android.app.admin.DevicePolicyManager.DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT;
import static android.app.admin.DevicePolicyManager.DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static com.android.server.devicepolicy.DeviceStateCacheImpl.NO_DEVICE_OWNER;
@@ -455,6 +456,23 @@
}
}
+ boolean isDefaultDeviceOwnerUserId(int userId) {
+ synchronized (mData) {
+ return mData.mDeviceOwner != null
+ && mData.mDeviceOwnerUserId == userId
+ && getDeviceOwnerType(getDeviceOwnerPackageName()) == DEVICE_OWNER_TYPE_DEFAULT;
+ }
+ }
+
+ boolean isFinancedDeviceOwnerUserId(int userId) {
+ synchronized (mData) {
+ return mData.mDeviceOwner != null
+ && mData.mDeviceOwnerUserId == userId
+ && getDeviceOwnerType(getDeviceOwnerPackageName())
+ == DEVICE_OWNER_TYPE_FINANCED;
+ }
+ }
+
boolean hasProfileOwner(int userId) {
synchronized (mData) {
return getProfileOwnerComponent(userId) != null;
diff --git a/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java b/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
index e0812d6..73ddbe8c 100644
--- a/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
@@ -78,6 +78,7 @@
import org.robolectric.shadows.ShadowPackageManager;
import java.util.ArrayDeque;
+import java.util.Arrays;
@RunWith(RobolectricTestRunner.class)
@Config(
@@ -196,7 +197,7 @@
mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP);
TransportMock transportMock = setUpTransport(mTransport);
when(transportMock.transport.getAvailableRestoreSets())
- .thenReturn(new RestoreSet[] {mRestoreSet1, mRestoreSet2});
+ .thenReturn(Arrays.asList(mRestoreSet1, mRestoreSet2));
IRestoreSession restoreSession = createActiveRestoreSession(PACKAGE_1, mTransport);
int result = restoreSession.getAvailableRestoreSets(mObserver, mMonitor);
@@ -214,7 +215,8 @@
public void testGetAvailableRestoreSets_forEmptyRestoreSets() throws Exception {
mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP);
TransportMock transportMock = setUpTransport(mTransport);
- when(transportMock.transport.getAvailableRestoreSets()).thenReturn(new RestoreSet[0]);
+ when(transportMock.transport.getAvailableRestoreSets()).thenReturn(
+ Arrays.asList(new RestoreSet[0]));
IRestoreSession restoreSession = createActiveRestoreSession(PACKAGE_1, mTransport);
int result = restoreSession.getAvailableRestoreSets(mObserver, mMonitor);
@@ -593,7 +595,7 @@
new ActiveRestoreSession(
mBackupManagerService, packageName, transport.transportName,
mBackupEligibilityRules);
- restoreSession.setRestoreSets(restoreSets);
+ restoreSession.setRestoreSets(Arrays.asList(restoreSets));
return restoreSession;
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 1619856..f0e3f3f 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -89,6 +89,10 @@
"sortReceivers",
"sortServices",
"setAllComponentsDirectBootAware",
+ "getUsesLibrariesSorted",
+ "getUsesOptionalLibrariesSorted",
+ "getUsesSdkLibrariesSorted",
+ "getUsesStaticLibrariesSorted",
// Tested through setting minor/major manually
"setLongVersionCode",
"getLongVersionCode",
diff --git a/services/tests/mockingservicestests/src/com/android/server/companion/virtual/OWNERS b/services/tests/mockingservicestests/src/com/android/server/companion/virtual/OWNERS
new file mode 100644
index 0000000..2e475a9
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/companion/virtual/OWNERS
@@ -0,0 +1 @@
+include /services/companion/java/com/android/server/companion/virtual/OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java b/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java
index ea04a19..5b10dc4 100644
--- a/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java
@@ -35,6 +35,7 @@
import com.android.server.testutils.OffsettableClock;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -73,6 +74,15 @@
);
}
+ @After
+ public void tearDown() {
+ if (mController != null) {
+ // Stop the update Brightness loop.
+ mController.stop();
+ mController = null;
+ }
+ }
+
@Test
public void testBrightness() throws Exception {
when(mSensorManager.registerListener(any(SensorEventListener.class), eq(mLightSensor),
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 1c44da1..5560b41 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -6,6 +6,7 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
@@ -208,6 +209,34 @@
assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size());
}
+ /**
+ * Test that dynamic constraints aren't written to disk.
+ */
+ @Test
+ public void testDynamicConstraintsNotPersisted() throws Exception {
+ JobInfo.Builder b = new Builder(42, mComponent).setPersisted(true);
+ JobStatus js = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null);
+ js.addDynamicConstraints(JobStatus.CONSTRAINT_BATTERY_NOT_LOW
+ | JobStatus.CONSTRAINT_CHARGING
+ | JobStatus.CONSTRAINT_IDLE
+ | JobStatus.CONSTRAINT_STORAGE_NOT_LOW);
+ assertTrue(js.hasBatteryNotLowConstraint());
+ assertTrue(js.hasChargingConstraint());
+ assertTrue(js.hasIdleConstraint());
+ assertTrue(js.hasStorageNotLowConstraint());
+ mTaskStoreUnderTest.add(js);
+ waitForPendingIo();
+
+ final JobSet jobStatusSet = new JobSet();
+ mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+ assertEquals("Job count is incorrect.", 1, jobStatusSet.size());
+ JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+ assertFalse(loaded.hasBatteryNotLowConstraint());
+ assertFalse(loaded.hasChargingConstraint());
+ assertFalse(loaded.hasIdleConstraint());
+ assertFalse(loaded.hasStorageNotLowConstraint());
+ }
+
@Test
public void testExtractUidFromJobFileName() {
File file = new File(mTestContext.getFilesDir(), "randomName");
diff --git a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
index 3268df2..8c3838b 100644
--- a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
@@ -496,8 +496,10 @@
Random random = new Random(1); // Always use the same series of pseudo random values.
for (int i = 0; i < 5000; ++i) {
+ final boolean ui = random.nextBoolean();
+ final boolean ej = !ui && random.nextBoolean();
JobStatus job = createJobStatus("testPendingJobSorting_Random",
- createJobInfo(i).setExpedited(random.nextBoolean()), random.nextInt(250));
+ createJobInfo(i).setExpedited(ej).setUserInitiated(ui), random.nextInt(250));
job.enqueueTime = random.nextInt(1_000_000);
jobQueue.add(job);
}
@@ -531,8 +533,10 @@
jobQueue.clear();
for (int i = 0; i < 300; ++i) {
+ final boolean ui = random.nextBoolean();
+ final boolean ej = !ui && random.nextBoolean();
JobStatus job = createJobStatus("testPendingJobSortingTransitivity",
- createJobInfo(i).setExpedited(random.nextBoolean()), random.nextInt(50));
+ createJobInfo(i).setExpedited(ej).setUserInitiated(ui), random.nextInt(50));
job.enqueueTime = random.nextInt(1_000_000);
job.overrideState = random.nextInt(4);
jobQueue.add(job);
@@ -553,8 +557,10 @@
jobQueue.clear();
for (int i = 0; i < 300; ++i) {
+ final boolean ui = random.nextFloat() < .02;
+ final boolean ej = !ui && random.nextFloat() < .03;
JobStatus job = createJobStatus("testPendingJobSortingTransitivity_Concentrated",
- createJobInfo(i).setExpedited(random.nextFloat() < .03),
+ createJobInfo(i).setExpedited(ej).setUserInitiated(ui),
random.nextInt(20));
job.enqueueTime = random.nextInt(250);
job.overrideState = random.nextFloat() < .01
@@ -653,10 +659,11 @@
}
private void checkPendingJobInvariants(PendingJobQueue jobQueue) {
+ final SparseBooleanArray eJobSeen = new SparseBooleanArray();
final SparseBooleanArray regJobSeen = new SparseBooleanArray();
// Latest priority enqueue times seen for each priority+namespace for each app.
final SparseArrayMap<String, SparseLongArray> latestPriorityRegEnqueueTimesPerUid =
- new SparseArrayMap();
+ new SparseArrayMap<>();
final SparseArrayMap<String, SparseLongArray> latestPriorityEjEnqueueTimesPerUid =
new SparseArrayMap<>();
final int noEntry = -1;
@@ -672,7 +679,8 @@
// Invariant #1: All jobs are sorted by override state
// Invariant #2: All jobs (for a UID) are sorted by priority order
// Invariant #3: Jobs (for a UID) with the same priority are sorted by enqueue time.
- // Invariant #4: EJs (for a UID) should be before regular jobs
+ // Invariant #4: User-initiated jobs (for a UID) should be before all other jobs.
+ // Invariant #5: EJs (for a UID) should be before regular jobs
// Invariant 1
if (prevOverrideState != job.overrideState) {
@@ -683,6 +691,7 @@
// to avoid confusion in the other checks.
latestPriorityEjEnqueueTimesPerUid.clear();
latestPriorityRegEnqueueTimesPerUid.clear();
+ eJobSeen.clear();
regJobSeen.clear();
prevOverrideState = job.overrideState;
}
@@ -718,10 +727,24 @@
}
latestPriorityEnqueueTimes.put(priority, job.enqueueTime);
- // Invariant 4
- if (!job.isRequestedExpeditedJob()) {
+ if (job.isRequestedExpeditedJob()) {
+ eJobSeen.put(uid, true);
+ } else if (!job.getJob().isUserInitiated()) {
regJobSeen.put(uid, true);
- } else if (regJobSeen.get(uid)) {
+ }
+
+ // Invariant 4
+ if (job.getJob().isUserInitiated()) {
+ if (eJobSeen.get(uid)) {
+ fail("UID " + uid + " had a UIJ ordered after an EJ");
+ }
+ if (regJobSeen.get(uid)) {
+ fail("UID " + uid + " had a UIJ ordered after a regular job");
+ }
+ }
+
+ // Invariant 5
+ if (job.isRequestedExpeditedJob() && regJobSeen.get(uid)) {
fail("UID " + uid + " had an EJ ordered after a regular job");
}
}
@@ -733,6 +756,9 @@
+ "/o" + job.overrideState
+ "/p" + job.getEffectivePriority()
+ "/b" + job.lastEvaluatedBias
- + "/" + job.isRequestedExpeditedJob() + "@" + job.enqueueTime;
+ + "/"
+ + (job.isRequestedExpeditedJob()
+ ? "e" : (job.getJob().isUserInitiated() ? "u" : "r"))
+ + "@" + job.enqueueTime;
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
new file mode 100644
index 0000000..6d778afe
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2022 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.wm;
+
+import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
+
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.Property;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+ /**
+ * Test class for {@link LetterboxUiControllerTest}.
+ *
+ * Build/Install/Run:
+ * atest WmTests:LetterboxUiControllerTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class LetterboxUiControllerTest extends WindowTestsBase {
+
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+ private ActivityRecord mActivity;
+ private DisplayContent mDisplayContent;
+ private LetterboxUiController mController;
+ private LetterboxConfiguration mLetterboxConfiguration;
+
+ @Before
+ public void setUp() throws Exception {
+ mActivity = setUpActivityWithComponent();
+
+ mLetterboxConfiguration = mWm.mLetterboxConfiguration;
+ spyOn(mLetterboxConfiguration);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
+ public void testShouldIgnoreRequestedOrientation_activityRelaunching_returnsTrue() {
+ prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch();
+
+ assertTrue(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED));
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
+ public void testShouldIgnoreRequestedOrientation_cameraCompatTreatment_returnsTrue() {
+ doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(anyBoolean());
+
+ // Recreate DisplayContent with DisplayRotationCompatPolicy
+ mActivity = setUpActivityWithComponent();
+ mController = new LetterboxUiController(mWm, mActivity);
+ prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch();
+ mController.setRelauchingAfterRequestedOrientationChanged(false);
+
+ spyOn(mDisplayContent.mDisplayRotationCompatPolicy);
+ doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy)
+ .isTreatmentEnabledForActivity(eq(mActivity));
+
+ assertTrue(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED));
+ }
+
+ @Test
+ public void testShouldIgnoreRequestedOrientation_overrideDisabled_returnsFalse() {
+ prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch();
+
+ assertFalse(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED));
+ }
+
+ @Test
+ public void testShouldIgnoreRequestedOrientation_propertyIsTrue_returnsTrue()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isPolicyForIgnoringRequestedOrientationEnabled();
+ mockThatProperty(PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION, /* value */ true);
+ mController = new LetterboxUiController(mWm, mActivity);
+ prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch();
+
+ assertTrue(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED));
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
+ public void testShouldIgnoreRequestedOrientation_propertyIsFalseAndOverride_returnsFalse()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isPolicyForIgnoringRequestedOrientationEnabled();
+ mockThatProperty(PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+ prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch();
+
+ assertFalse(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED));
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
+ public void testShouldIgnoreRequestedOrientation_flagIsDisabled_returnsFalse() {
+ prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch();
+ doReturn(false).when(mLetterboxConfiguration)
+ .isPolicyForIgnoringRequestedOrientationEnabled();
+
+ assertFalse(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED));
+ }
+
+ private void mockThatProperty(String propertyName, boolean value) throws Exception {
+ Property property = new Property(propertyName, /* value */ value, /* packageName */ "",
+ /* className */ "");
+ PackageManager pm = mWm.mContext.getPackageManager();
+ spyOn(pm);
+ doReturn(property).when(pm).getProperty(eq(propertyName), anyString());
+ }
+
+ private void prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch() {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isPolicyForIgnoringRequestedOrientationEnabled();
+ mController.setRelauchingAfterRequestedOrientationChanged(true);
+ }
+
+ private ActivityRecord setUpActivityWithComponent() {
+ mDisplayContent = new TestDisplayContent
+ .Builder(mAtm, /* dw */ 1000, /* dh */ 2000).build();
+ Task task = new TaskBuilder(mSupervisor).setDisplay(mDisplayContent).build();
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setOnTop(true)
+ .setTask(task)
+ // Set the component to be that of the test class in order to enable compat changes
+ .setComponent(ComponentName.createRelative(mContext,
+ com.android.server.wm.LetterboxUiControllerTest.class.getName()))
+ .build();
+ return activity;
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 367f91b..d364dbb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -30,7 +30,6 @@
import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.os.Process.NOBODY_UID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -1208,34 +1207,20 @@
@Test
public void testCreateRecentTaskInfo_detachedTask() {
- final Task task = createTaskBuilder(".Task").build();
- new ActivityBuilder(mSupervisor.mService)
- .setTask(task)
- .setUid(NOBODY_UID)
- .setComponent(getUniqueComponentName())
- .build();
+ final Task task = createTaskBuilder(".Task").setCreateActivity(true).build();
final TaskDisplayArea tda = task.getDisplayArea();
assertTrue(task.isAttached());
assertTrue(task.supportsMultiWindow());
- RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
- true /* getTasksAllowed */);
+ RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true);
assertTrue(info.supportsMultiWindow);
- info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
- false /* getTasksAllowed */);
-
- assertTrue(info.topActivity == null);
- assertTrue(info.topActivityInfo == null);
- assertTrue(info.baseActivity == null);
-
// The task can be put in split screen even if it is not attached now.
task.removeImmediately();
- info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
- true /* getTasksAllowed */);
+ info = mRecentTasks.createRecentTaskInfo(task, true);
assertTrue(info.supportsMultiWindow);
@@ -1244,8 +1229,7 @@
doReturn(false).when(tda).supportsNonResizableMultiWindow();
doReturn(false).when(task).isResizeable();
- info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
- true /* getTasksAllowed */);
+ info = mRecentTasks.createRecentTaskInfo(task, true);
assertFalse(info.supportsMultiWindow);
@@ -1253,8 +1237,7 @@
// the device supports it.
doReturn(true).when(tda).supportsNonResizableMultiWindow();
- info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
- true /* getTasksAllowed */);
+ info = mRecentTasks.createRecentTaskInfo(task, true);
assertTrue(info.supportsMultiWindow);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 2cce62e..a48a0bc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -94,6 +94,7 @@
import android.provider.DeviceConfig.Properties;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
+import android.view.InsetsState;
import android.view.WindowManager;
import androidx.test.filters.MediumTest;
@@ -1466,6 +1467,12 @@
assertEquals(new Rect(notchHeight, 0, 0, 0), mActivity.getLetterboxInsets());
assertTrue(displayPolicy.isFullyTransparentAllowed(w, ITYPE_STATUS_BAR));
assertActivityMaxBoundsSandboxed();
+
+ // The insets state for metrics should be rotated (landscape).
+ final InsetsState insetsState = new InsetsState();
+ mActivity.mDisplayContent.getInsetsPolicy().getInsetsForWindowMetrics(
+ mActivity, insetsState);
+ assertEquals(dh, insetsState.getDisplayFrame().width());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 26fe521..b70d8bd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -23,6 +23,7 @@
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE;
@@ -73,6 +74,7 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.graphics.Color;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Binder;
@@ -83,8 +85,10 @@
import android.view.RemoteAnimationDefinition;
import android.view.SurfaceControl;
import android.window.ITaskFragmentOrganizer;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentCreationParams;
import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentOperation;
import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentOrganizerToken;
import android.window.TaskFragmentParentInfo;
@@ -689,6 +693,59 @@
}
@Test
+ public void testApplyTransaction_enforceTaskFragmentOrganized_setTaskFragmentOperation() {
+ final Task task = createTask(mDisplayContent);
+ mTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setFragmentToken(mFragmentToken)
+ .build();
+ mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+ final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_SET_ANIMATION_PARAMS)
+ .setAnimationParams(TaskFragmentAnimationParams.DEFAULT)
+ .build();
+ mTransaction.setTaskFragmentOperation(mFragmentToken, operation);
+ mOrganizer.applyTransaction(mTransaction, TASK_FRAGMENT_TRANSIT_CHANGE,
+ false /* shouldApplyIndependently */);
+
+ // Not allowed because TaskFragment is not organized by the caller organizer.
+ assertApplyTransactionDisallowed(mTransaction);
+
+ mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+ "Test:TaskFragmentOrganizer" /* processName */);
+
+ assertApplyTransactionAllowed(mTransaction);
+ }
+
+ @Test
+ public void testSetTaskFragmentOperation() {
+ final Task task = createTask(mDisplayContent);
+ mTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setOrganizer(mOrganizer)
+ .setFragmentToken(mFragmentToken)
+ .build();
+ assertEquals(TaskFragmentAnimationParams.DEFAULT, mTaskFragment.getAnimationParams());
+
+ mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+ final TaskFragmentAnimationParams animationParams =
+ new TaskFragmentAnimationParams.Builder()
+ .setAnimationBackgroundColor(Color.GREEN)
+ .build();
+ final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_SET_ANIMATION_PARAMS)
+ .setAnimationParams(animationParams)
+ .build();
+ mTransaction.setTaskFragmentOperation(mFragmentToken, operation);
+ mOrganizer.applyTransaction(mTransaction, TASK_FRAGMENT_TRANSIT_CHANGE,
+ false /* shouldApplyIndependently */);
+ assertApplyTransactionAllowed(mTransaction);
+
+ assertEquals(animationParams, mTaskFragment.getAnimationParams());
+ assertEquals(Color.GREEN, mTaskFragment.getAnimationParams().getAnimationBackgroundColor());
+ }
+
+ @Test
public void testApplyTransaction_createTaskFragment_failForDifferentUid() {
final ActivityRecord activity = createActivityRecord(mDisplayContent);
final int uid = Binder.getCallingUid();
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
similarity index 95%
rename from services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java
rename to services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
index 689423a..4d6d320 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
@@ -93,21 +93,27 @@
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * A class that provides trusted hotword detector to communicate with the {@link
- * HotwordDetectionService}.
+ * A class that provides sandboxed detector to communicate with the {@link
+ * HotwordDetectionService} and {@link VisualQueryDetectionService}.
*
- * This class provides the methods to do initialization with the {@link HotwordDetectionService}
- * and handle external source detection. It also provides the methods to check if we can egress
- * the data from the {@link HotwordDetectionService}.
+ * Trusted hotword detectors such as {@link SoftwareHotwordDetector} and
+ * {@link AlwaysOnHotwordDetector} will leverage this class to communitcate with
+ * {@link HotwordDetectionService}; similarly, {@link VisualQueryDetector} will communicate with
+ * {@link VisualQueryDetectionService}.
+ *
+ * This class provides the methods to do initialization with the {@link HotwordDetectionService} and
+ * {@link VisualQueryDetectionService} handles external source detection for
+ * {@link HotwordDetectionService}. It also provides the methods to check if we can egress the data
+ * from the {@link HotwordDetectionService} and {@link VisualQueryDetectionService}.
*
* The subclass should override the {@link #informRestartProcessLocked()} to handle the trusted
* process restart.
*/
-abstract class HotwordDetectorSession {
- private static final String TAG = "HotwordDetectorSession";
+abstract class DetectorSession {
+ private static final String TAG = "DetectorSession";
static final boolean DEBUG = false;
- private static final String OP_MESSAGE =
+ private static final String HOTWORD_DETECTION_OP_MESSAGE =
"Providing hotword detection result to VoiceInteractionService";
// The error codes are used for onError callback
@@ -173,7 +179,7 @@
@GuardedBy("mLock")
ParcelFileDescriptor mCurrentAudioSink;
@GuardedBy("mLock")
- @NonNull HotwordDetectionConnection.ServiceConnection mRemoteHotwordDetectionService;
+ @NonNull HotwordDetectionConnection.ServiceConnection mRemoteDetectionService;
boolean mDebugHotwordLogging = false;
@GuardedBy("mLock")
private double mProximityMeters = PROXIMITY_UNKNOWN;
@@ -185,13 +191,13 @@
boolean mPerformingExternalSourceHotwordDetection;
@NonNull final IBinder mToken;
- HotwordDetectorSession(
- @NonNull HotwordDetectionConnection.ServiceConnection remoteHotwordDetectionService,
+ DetectorSession(
+ @NonNull HotwordDetectionConnection.ServiceConnection remoteDetectionService,
@NonNull Object lock, @NonNull Context context, @NonNull IBinder token,
@NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
Identity voiceInteractorIdentity,
@NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
- mRemoteHotwordDetectionService = remoteHotwordDetectionService;
+ mRemoteDetectionService = remoteDetectionService;
mLock = lock;
mContext = context;
mToken = token;
@@ -219,7 +225,7 @@
if (DEBUG) {
Slog.d(TAG, "updateStateAfterProcessStartLocked");
}
- AndroidFuture<Void> voidFuture = mRemoteHotwordDetectionService.postAsync(service -> {
+ AndroidFuture<Void> voidFuture = mRemoteDetectionService.postAsync(service -> {
AndroidFuture<Void> future = new AndroidFuture<>();
IRemoteCallback statusCallback = new IRemoteCallback.Stub() {
@Override
@@ -319,7 +325,7 @@
Slog.v(TAG, "call updateStateAfterProcessStartLocked");
updateStateAfterProcessStartLocked(options, sharedMemory);
} else {
- mRemoteHotwordDetectionService.run(
+ mRemoteDetectionService.run(
service -> service.updateState(options, sharedMemory, /* callback= */ null));
}
}
@@ -407,7 +413,7 @@
// TODO: handle cancellations well
// TODO: what if we cancelled and started a new one?
- mRemoteHotwordDetectionService.run(
+ mRemoteDetectionService.run(
service -> {
service.detectFromMicrophoneSource(
serviceAudioSource,
@@ -512,7 +518,7 @@
void destroyLocked() {
mDestroyed = true;
mDebugHotwordLogging = false;
- mRemoteHotwordDetectionService = null;
+ mRemoteDetectionService = null;
if (mAttentionManagerInternal != null) {
mAttentionManagerInternal.onStopProximityUpdates(mProximityCallbackInternal);
}
@@ -524,9 +530,9 @@
}
@SuppressWarnings("GuardedBy")
- void updateRemoteHotwordDetectionServiceLocked(
- @NonNull HotwordDetectionConnection.ServiceConnection remoteHotwordDetectionService) {
- mRemoteHotwordDetectionService = remoteHotwordDetectionService;
+ void updateRemoteSandboxedDetectionServiceLocked(
+ @NonNull HotwordDetectionConnection.ServiceConnection remoteDetectionService) {
+ mRemoteDetectionService = remoteDetectionService;
}
void reportErrorLocked(int status) {
@@ -628,9 +634,9 @@
int hotwordOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD);
mAppOpsManager.noteOpNoThrow(hotwordOp,
mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
- mVoiceInteractorIdentity.attributionTag, OP_MESSAGE);
+ mVoiceInteractorIdentity.attributionTag, HOTWORD_DETECTION_OP_MESSAGE);
enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity,
- CAPTURE_AUDIO_HOTWORD, OP_MESSAGE);
+ CAPTURE_AUDIO_HOTWORD, HOTWORD_DETECTION_OP_MESSAGE);
}
});
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
index 84bd7160..ad84f00 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
@@ -32,7 +32,6 @@
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SharedMemory;
-import android.service.voice.AlwaysOnHotwordDetector;
import android.service.voice.HotwordDetectedResult;
import android.service.voice.HotwordDetectionService;
import android.service.voice.HotwordDetector;
@@ -59,7 +58,7 @@
* {@link android.service.voice.VoiceInteractionService#createAlwaysOnHotwordDetector(String,
* Locale, PersistableBundle, SharedMemory, AlwaysOnHotwordDetector.Callback)}.
*/
-final class DspTrustedHotwordDetectorSession extends HotwordDetectorSession {
+final class DspTrustedHotwordDetectorSession extends DetectorSession {
private static final String TAG = "DspTrustedHotwordDetectorSession";
// The validation timeout value is 3 seconds for onDetect of DSP trigger event.
@@ -182,7 +181,7 @@
};
mValidatingDspTrigger = true;
- mRemoteHotwordDetectionService.run(service -> {
+ mRemoteDetectionService.run(service -> {
// We use the VALIDATION_TIMEOUT_MILLIS to inform that the client needs to invoke
// the callback before timeout value. In order to reduce the latency impact between
// server side and client side, we need to use another timeout value
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 276bccd..d501af7 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -89,7 +89,7 @@
Executors.newSingleThreadScheduledExecutor();
@Nullable private final ScheduledFuture<?> mCancellationTaskFuture;
private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied;
- @NonNull private final ServiceConnectionFactory mServiceConnectionFactory;
+ @NonNull private final ServiceConnectionFactory mHotwordDetectionServiceConnectionFactory;
private final int mDetectorType;
/**
* Time after which each HotwordDetectionService process is stopped and replaced by a new one.
@@ -99,7 +99,7 @@
final Object mLock;
final int mVoiceInteractionServiceUid;
- final ComponentName mDetectionComponentName;
+ final ComponentName mHotwordDetectionComponentName;
final int mUser;
final Context mContext;
volatile HotwordDetectionServiceIdentity mIdentity;
@@ -122,27 +122,30 @@
* to record the detectors.
*/
@GuardedBy("mLock")
- private final SparseArray<HotwordDetectorSession> mHotwordDetectorSessions =
+ private final SparseArray<DetectorSession> mDetectorSessions =
new SparseArray<>();
HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
- Identity voiceInteractorIdentity, ComponentName serviceName, int userId,
+ Identity voiceInteractorIdentity, ComponentName hotwordDetectionServiceName, int userId,
boolean bindInstantServiceAllowed, int detectorType) {
mLock = lock;
mContext = context;
mVoiceInteractionServiceUid = voiceInteractionServiceUid;
mVoiceInteractorIdentity = voiceInteractorIdentity;
- mDetectionComponentName = serviceName;
+ mHotwordDetectionComponentName = hotwordDetectionServiceName;
mUser = userId;
mDetectorType = detectorType;
mReStartPeriodSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_VOICE_INTERACTION,
KEY_RESTART_PERIOD_IN_SECONDS, 0);
- final Intent intent = new Intent(HotwordDetectionService.SERVICE_INTERFACE);
- intent.setComponent(mDetectionComponentName);
+ final Intent hotwordDetectionServiceIntent =
+ new Intent(HotwordDetectionService.SERVICE_INTERFACE);
+ hotwordDetectionServiceIntent.setComponent(mHotwordDetectionComponentName);
initAudioFlingerLocked();
- mServiceConnectionFactory = new ServiceConnectionFactory(intent, bindInstantServiceAllowed);
- mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
+ mHotwordDetectionServiceConnectionFactory =
+ new ServiceConnectionFactory(hotwordDetectionServiceIntent,
+ bindInstantServiceAllowed);
+ mRemoteHotwordDetectionService = mHotwordDetectionServiceConnectionFactory.createLocked();
mLastRestartInstant = Instant.now();
if (mReStartPeriodSeconds <= 0) {
@@ -176,8 +179,8 @@
try {
mAudioFlinger.linkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
} catch (RemoteException e) {
- Slog.w(TAG, "Audio server died before we registered a DeathRecipient; retrying init.",
- e);
+ Slog.w(TAG, "Audio server died before we registered a DeathRecipient; "
+ + "retrying init.", e);
initAudioFlingerLocked();
}
}
@@ -200,10 +203,10 @@
void cancelLocked() {
Slog.v(TAG, "cancelLocked");
clearDebugHotwordLoggingTimeoutLocked();
- runForEachHotwordDetectorSessionLocked((session) -> {
+ runForEachDetectorSessionLocked((session) -> {
session.destroyLocked();
});
- mHotwordDetectorSessions.clear();
+ mDetectorSessions.clear();
mDebugHotwordLogging = false;
mRemoteHotwordDetectionService.unbind();
LocalServices.getService(PermissionManagerServiceInternal.class)
@@ -223,7 +226,7 @@
@SuppressWarnings("GuardedBy")
void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory,
@NonNull IBinder token) {
- final HotwordDetectorSession session = getDetectorSessionByTokenLocked(token);
+ final DetectorSession session = getDetectorSessionByTokenLocked(token);
if (session == null) {
Slog.v(TAG, "Not found the detector by token");
return;
@@ -259,7 +262,7 @@
if (DEBUG) {
Slog.d(TAG, "startListeningFromExternalSourceLocked");
}
- final HotwordDetectorSession session = getDetectorSessionByTokenLocked(token);
+ final DetectorSession session = getDetectorSessionByTokenLocked(token);
if (session == null) {
Slog.v(TAG, "Not found the detector by token");
return;
@@ -321,7 +324,7 @@
Slog.v(TAG, "setDebugHotwordLoggingLocked: " + logging);
clearDebugHotwordLoggingTimeoutLocked();
mDebugHotwordLogging = logging;
- runForEachHotwordDetectorSessionLocked((session) -> {
+ runForEachDetectorSessionLocked((session) -> {
session.setDebugHotwordLoggingLocked(logging);
});
@@ -331,7 +334,7 @@
Slog.v(TAG, "Timeout to reset mDebugHotwordLogging to false");
synchronized (mLock) {
mDebugHotwordLogging = false;
- runForEachHotwordDetectorSessionLocked((session) -> {
+ runForEachDetectorSessionLocked((session) -> {
session.setDebugHotwordLoggingLocked(false);
});
}
@@ -350,24 +353,24 @@
private void restartProcessLocked() {
// TODO(b/244598068): Check HotwordAudioStreamManager first
Slog.v(TAG, "Restarting hotword detection process");
- ServiceConnection oldConnection = mRemoteHotwordDetectionService;
+ ServiceConnection oldHotwordConnection = mRemoteHotwordDetectionService;
HotwordDetectionServiceIdentity previousIdentity = mIdentity;
mLastRestartInstant = Instant.now();
// Recreate connection to reset the cache.
- mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
+ mRemoteHotwordDetectionService = mHotwordDetectionServiceConnectionFactory.createLocked();
Slog.v(TAG, "Started the new process, dispatching processRestarted to detector");
- runForEachHotwordDetectorSessionLocked((session) -> {
- session.updateRemoteHotwordDetectionServiceLocked(mRemoteHotwordDetectionService);
+ runForEachDetectorSessionLocked((session) -> {
+ session.updateRemoteSandboxedDetectionServiceLocked(mRemoteHotwordDetectionService);
session.informRestartProcessLocked();
});
if (DEBUG) {
Slog.i(TAG, "processRestarted is dispatched done, unbinding from the old process");
}
- oldConnection.ignoreConnectionStatusEvents();
- oldConnection.unbind();
+ oldHotwordConnection.ignoreConnectionStatusEvents();
+ oldHotwordConnection.unbind();
if (previousIdentity != null) {
removeServiceUidForAudioPolicy(previousIdentity.getIsolatedUid());
}
@@ -437,12 +440,12 @@
pw.print(prefix); pw.print("mBound=");
pw.println(mRemoteHotwordDetectionService.isBound());
pw.print(prefix); pw.print("mRestartCount=");
- pw.println(mServiceConnectionFactory.mRestartCount);
+ pw.println(mHotwordDetectionServiceConnectionFactory.mRestartCount);
pw.print(prefix); pw.print("mLastRestartInstant="); pw.println(mLastRestartInstant);
pw.print(prefix); pw.print("mDetectorType=");
pw.println(HotwordDetector.detectorTypeToString(mDetectorType));
- pw.print(prefix); pw.println("HotwordDetectorSession(s)");
- runForEachHotwordDetectorSessionLocked((session) -> {
+ pw.print(prefix); pw.println("DetectorSession(s)");
+ runForEachDetectorSessionLocked((session) -> {
session.dumpLocked(prefix, pw);
});
}
@@ -537,9 +540,9 @@
}
}
synchronized (HotwordDetectionConnection.this.mLock) {
- runForEachHotwordDetectorSessionLocked((session) -> {
+ runForEachDetectorSessionLocked((session) -> {
session.reportErrorLocked(
- HotwordDetectorSession.HOTWORD_DETECTION_SERVICE_DIED);
+ DetectorSession.HOTWORD_DETECTION_SERVICE_DIED);
});
}
// Can improve to log exit reason if needed
@@ -599,12 +602,12 @@
int detectorType) {
// We only support one Dsp trusted hotword detector and one software hotword detector at
// the same time, remove existing one.
- HotwordDetectorSession removeSession = mHotwordDetectorSessions.get(detectorType);
+ DetectorSession removeSession = mDetectorSessions.get(detectorType);
if (removeSession != null) {
removeSession.destroyLocked();
- mHotwordDetectorSessions.remove(detectorType);
+ mDetectorSessions.remove(detectorType);
}
- final HotwordDetectorSession session;
+ final DetectorSession session;
if (detectorType == HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP) {
session = new DspTrustedHotwordDetectorSession(mRemoteHotwordDetectionService,
mLock, mContext, token, callback, mVoiceInteractionServiceUid,
@@ -615,30 +618,30 @@
mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
mScheduledExecutorService, mDebugHotwordLogging);
}
- mHotwordDetectorSessions.put(detectorType, session);
+ mDetectorSessions.put(detectorType, session);
session.initialize(options, sharedMemory);
}
@SuppressWarnings("GuardedBy")
void destroyDetectorLocked(@NonNull IBinder token) {
- final HotwordDetectorSession session = getDetectorSessionByTokenLocked(token);
+ final DetectorSession session = getDetectorSessionByTokenLocked(token);
if (session != null) {
session.destroyLocked();
- final int index = mHotwordDetectorSessions.indexOfValue(session);
- if (index < 0 || index > mHotwordDetectorSessions.size() - 1) {
+ final int index = mDetectorSessions.indexOfValue(session);
+ if (index < 0 || index > mDetectorSessions.size() - 1) {
return;
}
- mHotwordDetectorSessions.removeAt(index);
+ mDetectorSessions.removeAt(index);
}
}
@SuppressWarnings("GuardedBy")
- private HotwordDetectorSession getDetectorSessionByTokenLocked(IBinder token) {
+ private DetectorSession getDetectorSessionByTokenLocked(IBinder token) {
if (token == null) {
return null;
}
- for (int i = 0; i < mHotwordDetectorSessions.size(); i++) {
- final HotwordDetectorSession session = mHotwordDetectorSessions.valueAt(i);
+ for (int i = 0; i < mDetectorSessions.size(); i++) {
+ final DetectorSession session = mDetectorSessions.valueAt(i);
if (!session.isDestroyed() && session.isSameToken(token)) {
return session;
}
@@ -648,7 +651,7 @@
@SuppressWarnings("GuardedBy")
private DspTrustedHotwordDetectorSession getDspTrustedHotwordDetectorSessionLocked() {
- final HotwordDetectorSession session = mHotwordDetectorSessions.get(
+ final DetectorSession session = mDetectorSessions.get(
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP);
if (session == null || session.isDestroyed()) {
Slog.v(TAG, "Not found the Dsp detector");
@@ -659,7 +662,7 @@
@SuppressWarnings("GuardedBy")
private SoftwareTrustedHotwordDetectorSession getSoftwareTrustedHotwordDetectorSessionLocked() {
- final HotwordDetectorSession session = mHotwordDetectorSessions.get(
+ final DetectorSession session = mDetectorSessions.get(
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE);
if (session == null || session.isDestroyed()) {
Slog.v(TAG, "Not found the software detector");
@@ -669,10 +672,10 @@
}
@SuppressWarnings("GuardedBy")
- private void runForEachHotwordDetectorSessionLocked(
- @NonNull Consumer<HotwordDetectorSession> action) {
- for (int i = 0; i < mHotwordDetectorSessions.size(); i++) {
- HotwordDetectorSession session = mHotwordDetectorSessions.valueAt(i);
+ private void runForEachDetectorSessionLocked(
+ @NonNull Consumer<DetectorSession> action) {
+ for (int i = 0; i < mDetectorSessions.size(); i++) {
+ DetectorSession session = mDetectorSessions.valueAt(i);
action.accept(session);
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
index 4eb997a..3ad963d 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
@@ -55,7 +55,7 @@
* {@link android.service.voice.VoiceInteractionService#createHotwordDetector(PersistableBundle,
* SharedMemory, HotwordDetector.Callback)}.
*/
-final class SoftwareTrustedHotwordDetectorSession extends HotwordDetectorSession {
+final class SoftwareTrustedHotwordDetectorSession extends DetectorSession {
private static final String TAG = "SoftwareTrustedHotwordDetectorSession";
private IMicrophoneHotwordDetectionVoiceInteractionCallback mSoftwareCallback;
@@ -155,7 +155,7 @@
}
};
- mRemoteHotwordDetectionService.run(
+ mRemoteDetectionService.run(
service -> service.detectFromMicrophoneSource(
null,
AUDIO_SOURCE_MICROPHONE,
@@ -179,7 +179,7 @@
}
mPerformingSoftwareHotwordDetection = false;
- mRemoteHotwordDetectionService.run(ISandboxedDetectionService::stopDetection);
+ mRemoteDetectionService.run(ISandboxedDetectionService::stopDetection);
closeExternalAudioStreamLocked("stopping requested");
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index b064695..31babb8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -18,6 +18,7 @@
import android.app.Instrumentation
import android.app.WallpaperManager
+import android.content.res.Resources
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
@@ -29,8 +30,8 @@
import com.android.server.wm.flicker.helpers.NewTasksAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.DEFAULT_TASK_DISPLAY_AREA
import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.SPLASH_SCREEN
@@ -64,9 +65,7 @@
class TaskTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
private val launchNewTaskApp = NewTasksAppHelper(instrumentation)
private val simpleApp = SimpleAppHelper(instrumentation)
- private val wallpaper by lazy {
- getWallpaperPackage(instrumentation) ?: error("Unable to obtain wallpaper")
- }
+ private val wallpaper by lazy { getWallpaperPackage(instrumentation) }
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
@@ -143,8 +142,7 @@
val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
flicker.assertLayers {
- this
- .invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
+ this.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
it.visibleRegion(launchNewTaskApp.componentMatcher).coversExactly(displayBounds)
}
.isInvisible(backgroundColorLayer)
@@ -159,7 +157,7 @@
"SIMPLE_ACTIVITY's splashscreen coversExactly displayBounds",
isOptional = true
) {
- it.visibleRegion(ComponentSplashScreenMatcher( simpleApp.componentMatcher))
+ it.visibleRegion(ComponentSplashScreenMatcher(simpleApp.componentMatcher))
.coversExactly(displayBounds)
}
.invoke("SIMPLE_ACTIVITY coversExactly displayBounds") {
@@ -178,7 +176,8 @@
isOptional = true
) {
it.visibleRegion(
- ComponentSplashScreenMatcher(launchNewTaskApp.componentMatcher))
+ ComponentSplashScreenMatcher(launchNewTaskApp.componentMatcher)
+ )
.coversExactly(displayBounds)
}
.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
@@ -215,10 +214,20 @@
override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
companion object {
- private fun getWallpaperPackage(instrumentation: Instrumentation): IComponentMatcher? {
+ private fun getWallpaperPackage(instrumentation: Instrumentation): IComponentMatcher {
val wallpaperManager = WallpaperManager.getInstance(instrumentation.targetContext)
return wallpaperManager.wallpaperInfo?.component?.toFlickerComponent()
+ ?: getStaticWallpaperPackage(instrumentation)
+ }
+
+ private fun getStaticWallpaperPackage(instrumentation: Instrumentation): IComponentMatcher {
+ val resourceId =
+ Resources.getSystem()
+ .getIdentifier("image_wallpaper_component", "string", "android")
+ return ComponentNameMatcher.unflattenFromString(
+ instrumentation.targetContext.resources.getString(resourceId)
+ )
}
@Parameterized.Parameters(name = "{0}")
diff --git a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
index 2e93c87..3a520bc 100644
--- a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
+++ b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
@@ -37,7 +37,7 @@
public class TouchLatencyActivity extends AppCompatActivity {
private static final int REFRESH_RATE_SLIDER_MIN = 20;
- private static final int REFRESH_RATE_SLIDER_STEP = 5;
+ private static final int REFRESH_RATE_SLIDER_STEP = 1;
private Menu mMenu;
private Mode[] mDisplayModes;
diff --git a/tests/VectorDrawableTest/OWNERS b/tests/VectorDrawableTest/OWNERS
new file mode 100644
index 0000000..27e1668
--- /dev/null
+++ b/tests/VectorDrawableTest/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 24939
+
+include /graphics/java/android/graphics/OWNERS