Merge "Recycle obtained TypedArrays"
diff --git a/Android.bp b/Android.bp
index 7d02d7d..ee5db70 100644
--- a/Android.bp
+++ b/Android.bp
@@ -109,6 +109,7 @@
":platform-compat-native-aidl",
// AIDL sources from external directories
+ ":android.hardware.graphics.common-V3-java-source",
":android.hardware.security.keymint-V1-java-source",
":android.hardware.security.secureclock-V1-java-source",
":android.hardware.tv.tuner-V1-java-source",
@@ -288,6 +289,7 @@
// TODO: remove when moved to the below package
"frameworks/base/packages/ConnectivityT/framework-t/aidl-export",
"packages/modules/Connectivity/framework/aidl-export",
+ "hardware/interfaces/graphics/common/aidl",
],
},
dxflags: [
@@ -537,6 +539,7 @@
// TODO: remove when moved to the below package
"frameworks/base/packages/ConnectivityT/framework-t/aidl-export",
"packages/modules/Connectivity/framework/aidl-export",
+ "hardware/interfaces/graphics/common/aidl",
],
},
// These are libs from framework-internal-utils that are required (i.e. being referenced)
diff --git a/apex/OWNERS b/apex/OWNERS
index b3e81b9..e867586 100644
--- a/apex/OWNERS
+++ b/apex/OWNERS
@@ -1 +1 @@
-file:platform/packages/modules/common:/OWNERS
+file:platform/packages/modules/common:/OWNERS #{LAST_RESORT_SUGGESTION}
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index 96114dc..ffa534e 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -1376,6 +1376,11 @@
}
}
+ private boolean isAllowedBlobAccess(int uid, String packageName) {
+ return (!Process.isSupplemental(uid) && !Process.isIsolated(uid)
+ && !mPackageManagerInternal.isInstantApp(packageName, UserHandle.getUserId(uid)));
+ }
+
private class PackageChangedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
@@ -1437,8 +1442,7 @@
final int callingUid = Binder.getCallingUid();
verifyCallingPackage(callingUid, packageName);
- if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
- packageName, UserHandle.getUserId(callingUid))) {
+ if (!isAllowedBlobAccess(callingUid, packageName)) {
throw new SecurityException("Caller not allowed to create session; "
+ "callingUid=" + callingUid + ", callingPackage=" + packageName);
}
@@ -1487,8 +1491,7 @@
final int callingUid = Binder.getCallingUid();
verifyCallingPackage(callingUid, packageName);
- if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
- packageName, UserHandle.getUserId(callingUid))) {
+ if (!isAllowedBlobAccess(callingUid, packageName)) {
throw new SecurityException("Caller not allowed to open blob; "
+ "callingUid=" + callingUid + ", callingPackage=" + packageName);
}
@@ -1519,8 +1522,7 @@
final int callingUid = Binder.getCallingUid();
verifyCallingPackage(callingUid, packageName);
- if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
- packageName, UserHandle.getUserId(callingUid))) {
+ if (!isAllowedBlobAccess(callingUid, packageName)) {
throw new SecurityException("Caller not allowed to open blob; "
+ "callingUid=" + callingUid + ", callingPackage=" + packageName);
}
@@ -1544,8 +1546,7 @@
final int callingUid = Binder.getCallingUid();
verifyCallingPackage(callingUid, packageName);
- if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
- packageName, UserHandle.getUserId(callingUid))) {
+ if (!isAllowedBlobAccess(callingUid, packageName)) {
throw new SecurityException("Caller not allowed to open blob; "
+ "callingUid=" + callingUid + ", callingPackage=" + packageName);
}
@@ -1628,8 +1629,7 @@
final int callingUid = Binder.getCallingUid();
verifyCallingPackage(callingUid, packageName);
- if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
- packageName, UserHandle.getUserId(callingUid))) {
+ if (!isAllowedBlobAccess(callingUid, packageName)) {
throw new SecurityException("Caller not allowed to open blob; "
+ "callingUid=" + callingUid + ", callingPackage=" + packageName);
}
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 cea1945..e23e067 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -417,6 +417,9 @@
break;
case Constants.KEY_CONN_CONGESTION_DELAY_FRAC:
case Constants.KEY_CONN_PREFETCH_RELAX_FRAC:
+ case Constants.KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC:
+ case Constants.KEY_CONN_USE_CELL_SIGNAL_STRENGTH:
+ case Constants.KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS:
mConstants.updateConnectivityConstantsLocked();
break;
case Constants.KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS:
@@ -489,6 +492,12 @@
private static final String KEY_MIN_EXP_BACKOFF_TIME_MS = "min_exp_backoff_time_ms";
private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
+ private static final String KEY_CONN_USE_CELL_SIGNAL_STRENGTH =
+ "conn_use_cell_signal_strength";
+ private static final String KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS =
+ "conn_update_all_jobs_min_interval_ms";
+ private static final String KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC =
+ "conn_low_signal_strength_relax_frac";
private static final String KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS =
"prefetch_force_batch_relax_threshold_ms";
private static final String KEY_ENABLE_API_QUOTAS = "enable_api_quotas";
@@ -514,6 +523,9 @@
private static final long DEFAULT_MIN_EXP_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
+ private static final boolean DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH = true;
+ private static final long DEFAULT_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = MINUTE_IN_MILLIS;
+ private static final float DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC = 0.5f;
private static final long DEFAULT_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS = HOUR_IN_MILLIS;
private static final boolean DEFAULT_ENABLE_API_QUOTAS = true;
private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250;
@@ -569,6 +581,23 @@
* we consider matching it against a metered network.
*/
public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC;
+ /**
+ * Whether to use the cell signal strength to determine if a particular job is eligible to
+ * run.
+ */
+ public boolean CONN_USE_CELL_SIGNAL_STRENGTH = DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH;
+ /**
+ * When throttling updating all tracked jobs, make sure not to update them more frequently
+ * than this value.
+ */
+ public long CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS =
+ DEFAULT_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS;
+ /**
+ * The fraction of a job's running window that must pass before we consider running it on
+ * low signal strength networks.
+ */
+ public float CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC =
+ DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC;
/**
* The amount of time within which we would consider the app to be launching relatively soon
@@ -661,6 +690,18 @@
CONN_PREFETCH_RELAX_FRAC = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_CONN_PREFETCH_RELAX_FRAC,
DEFAULT_CONN_PREFETCH_RELAX_FRAC);
+ CONN_USE_CELL_SIGNAL_STRENGTH = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_CONN_USE_CELL_SIGNAL_STRENGTH,
+ DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH);
+ CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS,
+ DEFAULT_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS);
+ CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC = DeviceConfig.getFloat(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC,
+ DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC);
}
private void updatePrefetchConstantsLocked() {
@@ -739,6 +780,11 @@
pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
pw.print(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
+ pw.print(KEY_CONN_USE_CELL_SIGNAL_STRENGTH, CONN_USE_CELL_SIGNAL_STRENGTH).println();
+ pw.print(KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS, CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS)
+ .println();
+ pw.print(KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC, CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC)
+ .println();
pw.print(KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS).println();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index c678755..892e0c0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -35,6 +35,10 @@
import android.os.Looper;
import android.os.Message;
import android.os.UserHandle;
+import android.telephony.CellSignalStrength;
+import android.telephony.SignalStrength;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -60,6 +64,7 @@
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.function.Predicate;
/**
@@ -213,9 +218,16 @@
* is only done in {@link #maybeAdjustRegisteredCallbacksLocked()} and may sometimes be stale.
*/
private final List<UidStats> mSortedStats = new ArrayList<>();
+ @GuardedBy("mLock")
private long mLastCallbackAdjustmentTimeElapsed;
+ @GuardedBy("mLock")
+ private final SparseArray<CellSignalStrengthCallback> mSignalStrengths = new SparseArray<>();
+
+ @GuardedBy("mLock")
+ private long mLastAllJobUpdateTimeElapsed;
private static final int MSG_ADJUST_CALLBACKS = 0;
+ private static final int MSG_UPDATE_ALL_TRACKED_JOBS = 1;
private final Handler mHandler;
@@ -529,11 +541,7 @@
@GuardedBy("mLock")
public void onBatteryStateChangedLocked() {
// Update job bookkeeping out of band to avoid blocking broadcast progress.
- JobSchedulerBackgroundThread.getHandler().post(() -> {
- synchronized (mLock) {
- updateTrackedJobsLocked(-1, null);
- }
- });
+ mHandler.sendEmptyMessage(MSG_UPDATE_ALL_TRACKED_JOBS);
}
private boolean isUsable(NetworkCapabilities capabilities) {
@@ -650,6 +658,82 @@
}
}
+ @GuardedBy("mLock")
+ private boolean isStrongEnough(JobStatus jobStatus, NetworkCapabilities capabilities,
+ Constants constants) {
+ final int priority = jobStatus.getEffectivePriority();
+ if (priority >= JobInfo.PRIORITY_HIGH) {
+ return true;
+ }
+ if (!constants.CONN_USE_CELL_SIGNAL_STRENGTH) {
+ return true;
+ }
+ if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+ return true;
+ }
+ if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
+ // Exclude VPNs because it's currently not possible to determine the VPN's underlying
+ // network, and thus the correct signal strength of the VPN's network.
+ // Transmitting data over a VPN is generally more battery-expensive than on the
+ // underlying network, so:
+ // TODO: find a good way to reduce job use of VPN when it'll be very expensive
+ // For now, we just pretend VPNs are always strong enough
+ return true;
+ }
+
+ // VCNs running over WiFi will declare TRANSPORT_CELLULAR. When connected, a VCN will
+ // most likely be the default network. We ideally don't want this to restrict jobs when the
+ // VCN incorrectly declares the CELLULAR transport, but there's currently no way to
+ // determine if a network is a VCN. When there is:
+ // TODO(216127782): exclude VCN running over WiFi from this check
+
+ int signalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ // Use the best strength found.
+ final Set<Integer> subscriptionIds = capabilities.getSubscriptionIds();
+ for (int subId : subscriptionIds) {
+ CellSignalStrengthCallback callback = mSignalStrengths.get(subId);
+ if (callback != null) {
+ signalStrength = Math.max(signalStrength, callback.signalStrength);
+ } else {
+ Slog.wtf(TAG,
+ "Subscription ID " + subId + " doesn't have a registered callback");
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Cell signal strength for job=" + signalStrength);
+ }
+ // Treat "NONE_OR_UNKNOWN" as "NONE".
+ if (signalStrength <= CellSignalStrength.SIGNAL_STRENGTH_POOR) {
+ // If signal strength is poor, don't run MIN or LOW priority jobs, and only
+ // run DEFAULT priority jobs if the device is charging or the job has been waiting
+ // long enough.
+ if (priority > JobInfo.PRIORITY_DEFAULT) {
+ return true;
+ }
+ if (priority < JobInfo.PRIORITY_DEFAULT) {
+ return false;
+ }
+ // DEFAULT job.
+ return (mService.isBatteryCharging() && mService.isBatteryNotLow())
+ || jobStatus.getFractionRunTime() > constants.CONN_PREFETCH_RELAX_FRAC;
+ }
+ if (signalStrength <= CellSignalStrength.SIGNAL_STRENGTH_MODERATE) {
+ // If signal strength is moderate, only run MIN priority jobs when the device
+ // is charging, or the job is already running.
+ if (priority >= JobInfo.PRIORITY_LOW) {
+ return true;
+ }
+ // MIN job.
+ if (mService.isBatteryCharging() && mService.isBatteryNotLow()) {
+ return true;
+ }
+ final UidStats uidStats = getUidStats(
+ jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
+ return uidStats.runningJobs.contains(jobStatus);
+ }
+ return true;
+ }
+
private static NetworkCapabilities.Builder copyCapabilities(
@NonNull final NetworkRequest request) {
final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
@@ -717,10 +801,12 @@
// Second, is the network congested?
if (isCongestionDelayed(jobStatus, network, capabilities, constants)) return false;
- // Third, is the network a strict match?
+ if (!isStrongEnough(jobStatus, capabilities, constants)) return false;
+
+ // Is the network a strict match?
if (isStrictSatisfied(jobStatus, network, capabilities, constants)) return true;
- // Third, is the network a relaxed match?
+ // Is the network a relaxed match?
if (isRelaxedSatisfied(jobStatus, network, capabilities, constants)) return true;
return false;
@@ -985,6 +1071,24 @@
return changed;
}
+ @GuardedBy("mLock")
+ private void updateAllTrackedJobsLocked(boolean allowThrottle) {
+ if (allowThrottle) {
+ final long throttleTimeLeftMs =
+ (mLastAllJobUpdateTimeElapsed + mConstants.CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS)
+ - sElapsedRealtimeClock.millis();
+ if (throttleTimeLeftMs > 0) {
+ Message msg = mHandler.obtainMessage(MSG_UPDATE_ALL_TRACKED_JOBS, 1, 0);
+ mHandler.sendMessageDelayed(msg, throttleTimeLeftMs);
+ return;
+ }
+ }
+
+ mHandler.removeMessages(MSG_UPDATE_ALL_TRACKED_JOBS);
+ updateTrackedJobsLocked(-1, null);
+ mLastAllJobUpdateTimeElapsed = sElapsedRealtimeClock.millis();
+ }
+
/**
* Update any jobs tracked by this controller that match given filters.
*
@@ -1088,7 +1192,11 @@
Slog.v(TAG, "onCapabilitiesChanged: " + network);
}
synchronized (mLock) {
- mAvailableNetworks.put(network, capabilities);
+ final NetworkCapabilities oldCaps = mAvailableNetworks.put(network, capabilities);
+ if (oldCaps != null) {
+ maybeUnregisterSignalStrengthCallbackLocked(oldCaps);
+ }
+ maybeRegisterSignalStrengthCallbackLocked(capabilities);
updateTrackedJobsLocked(-1, network);
postAdjustCallbacks();
}
@@ -1100,7 +1208,10 @@
Slog.v(TAG, "onLost: " + network);
}
synchronized (mLock) {
- mAvailableNetworks.remove(network);
+ final NetworkCapabilities capabilities = mAvailableNetworks.remove(network);
+ if (capabilities != null) {
+ maybeUnregisterSignalStrengthCallbackLocked(capabilities);
+ }
for (int u = 0; u < mCurrentDefaultNetworkCallbacks.size(); ++u) {
UidDefaultNetworkCallback callback = mCurrentDefaultNetworkCallbacks.valueAt(u);
if (Objects.equals(callback.mDefaultNetwork, network)) {
@@ -1111,6 +1222,63 @@
postAdjustCallbacks();
}
}
+
+ @GuardedBy("mLock")
+ private void maybeRegisterSignalStrengthCallbackLocked(
+ @NonNull NetworkCapabilities capabilities) {
+ if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+ return;
+ }
+ TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+ final Set<Integer> subscriptionIds = capabilities.getSubscriptionIds();
+ for (int subId : subscriptionIds) {
+ if (mSignalStrengths.indexOfKey(subId) >= 0) {
+ continue;
+ }
+ TelephonyManager idTm = telephonyManager.createForSubscriptionId(subId);
+ CellSignalStrengthCallback callback = new CellSignalStrengthCallback();
+ idTm.registerTelephonyCallback(
+ JobSchedulerBackgroundThread.getExecutor(), callback);
+ mSignalStrengths.put(subId, callback);
+
+ final SignalStrength signalStrength = idTm.getSignalStrength();
+ if (signalStrength != null) {
+ callback.signalStrength = signalStrength.getLevel();
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void maybeUnregisterSignalStrengthCallbackLocked(
+ @NonNull NetworkCapabilities capabilities) {
+ if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+ return;
+ }
+ ArraySet<Integer> activeIds = new ArraySet<>();
+ for (int i = 0, size = mAvailableNetworks.size(); i < size; ++i) {
+ NetworkCapabilities nc = mAvailableNetworks.valueAt(i);
+ if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+ activeIds.addAll(nc.getSubscriptionIds());
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Active subscription IDs: " + activeIds);
+ }
+ TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+ Set<Integer> subscriptionIds = capabilities.getSubscriptionIds();
+ for (int subId : subscriptionIds) {
+ if (activeIds.contains(subId)) {
+ continue;
+ }
+ TelephonyManager idTm = telephonyManager.createForSubscriptionId(subId);
+ CellSignalStrengthCallback callback = mSignalStrengths.removeReturnOld(subId);
+ if (callback != null) {
+ idTm.unregisterTelephonyCallback(callback);
+ } else {
+ Slog.wtf(TAG, "Callback for sub " + subId + " didn't exist?!?!");
+ }
+ }
+ }
};
private class CcHandler extends Handler {
@@ -1127,6 +1295,13 @@
maybeAdjustRegisteredCallbacksLocked();
}
break;
+
+ case MSG_UPDATE_ALL_TRACKED_JOBS:
+ synchronized (mLock) {
+ final boolean allowThrottle = msg.arg1 == 1;
+ updateAllTrackedJobsLocked(allowThrottle);
+ }
+ break;
}
}
}
@@ -1268,6 +1443,33 @@
}
}
+ private class CellSignalStrengthCallback extends TelephonyCallback
+ implements TelephonyCallback.SignalStrengthsListener {
+ @GuardedBy("mLock")
+ public int signalStrength = CellSignalStrength.SIGNAL_STRENGTH_GREAT;
+
+ @Override
+ public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength) {
+ synchronized (mLock) {
+ final int newSignalStrength = signalStrength.getLevel();
+ if (DEBUG) {
+ Slog.d(TAG, "Signal strength changing from "
+ + this.signalStrength + " to " + newSignalStrength);
+ for (CellSignalStrength css : signalStrength.getCellSignalStrengths()) {
+ Slog.d(TAG, "CSS: " + css.getLevel() + " " + css);
+ }
+ }
+ if (this.signalStrength == newSignalStrength) {
+ // This happens a lot.
+ return;
+ }
+ this.signalStrength = newSignalStrength;
+ // Update job bookkeeping out of band to avoid blocking callback progress.
+ mHandler.obtainMessage(MSG_UPDATE_ALL_TRACKED_JOBS, 1, 0).sendToTarget();
+ }
+ }
+ }
+
@GuardedBy("mLock")
@Override
public void dumpControllerStateLocked(IndentingPrintWriter pw,
@@ -1299,6 +1501,20 @@
}
pw.println();
+ if (mSignalStrengths.size() > 0) {
+ pw.println("Subscription ID signal strengths:");
+ pw.increaseIndent();
+ for (int i = 0; i < mSignalStrengths.size(); ++i) {
+ pw.print(mSignalStrengths.keyAt(i));
+ pw.print(": ");
+ pw.println(mSignalStrengths.valueAt(i).signalStrength);
+ }
+ pw.decreaseIndent();
+ } else {
+ pw.println("No cached signal strengths");
+ }
+ pw.println();
+
pw.println("Current default network callbacks:");
pw.increaseIndent();
for (int i = 0; i < mCurrentDefaultNetworkCallbacks.size(); i++) {
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 0456a9b..acbb1c1 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
@@ -1139,11 +1139,12 @@
*/
public float getFractionRunTime() {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- if (earliestRunTimeElapsedMillis == 0 && latestRunTimeElapsedMillis == Long.MAX_VALUE) {
+ if (earliestRunTimeElapsedMillis == NO_EARLIEST_RUNTIME
+ && latestRunTimeElapsedMillis == NO_LATEST_RUNTIME) {
return 1;
- } else if (earliestRunTimeElapsedMillis == 0) {
+ } else if (earliestRunTimeElapsedMillis == NO_EARLIEST_RUNTIME) {
return now >= latestRunTimeElapsedMillis ? 1 : 0;
- } else if (latestRunTimeElapsedMillis == Long.MAX_VALUE) {
+ } else if (latestRunTimeElapsedMillis == NO_LATEST_RUNTIME) {
return now >= earliestRunTimeElapsedMillis ? 1 : 0;
} else {
if (now <= earliestRunTimeElapsedMillis) {
diff --git a/core/api/current.txt b/core/api/current.txt
index 0cac17d..77f869a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -112,6 +112,7 @@
field public static final String MANAGE_ONGOING_CALLS = "android.permission.MANAGE_ONGOING_CALLS";
field public static final String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS";
field public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN";
+ field public static final String MANAGE_WIFI_INTERFACES = "android.permission.MANAGE_WIFI_INTERFACES";
field public static final String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
field public static final String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL";
field public static final String MODIFY_AUDIO_SETTINGS = "android.permission.MODIFY_AUDIO_SETTINGS";
@@ -342,6 +343,7 @@
field public static final int allowSingleTap = 16843353; // 0x1010259
field public static final int allowTaskReparenting = 16843268; // 0x1010204
field public static final int allowUndo = 16843999; // 0x10104df
+ field public static final int allowUntrustedActivityEmbedding;
field public static final int alpha = 16843551; // 0x101031f
field public static final int alphabeticModifiers = 16844110; // 0x101054e
field public static final int alphabeticShortcut = 16843235; // 0x10101e3
@@ -906,6 +908,7 @@
field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
field public static final int keycode = 16842949; // 0x10100c5
field public static final int killAfterRestore = 16843420; // 0x101029c
+ field public static final int knownActivityEmbeddingCerts;
field public static final int knownCerts = 16844330; // 0x101062a
field public static final int lStar = 16844359; // 0x1010647
field public static final int label = 16842753; // 0x1010001
@@ -3068,6 +3071,7 @@
method @Nullable public final android.accessibilityservice.InputMethod getInputMethod();
method @NonNull public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
+ method @Nullable public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow(int);
method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
method @NonNull public final android.accessibilityservice.AccessibilityService.SoftKeyboardController getSoftKeyboardController();
method @NonNull public final java.util.List<android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction> getSystemActions();
@@ -10967,6 +10971,7 @@
ctor public ActivityInfo(android.content.pm.ActivityInfo);
method public int describeContents();
method public void dump(android.util.Printer, String);
+ method @NonNull public java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts();
method public final int getThemeResource();
field public static final int COLOR_MODE_DEFAULT = 0; // 0x0
field public static final int COLOR_MODE_HDR = 2; // 0x2
@@ -10994,6 +10999,7 @@
field public static final int DOCUMENT_LAUNCH_NEVER = 3; // 0x3
field public static final int DOCUMENT_LAUNCH_NONE = 0; // 0x0
field public static final int FLAG_ALLOW_TASK_REPARENTING = 64; // 0x40
+ field public static final int FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING = 268435456; // 0x10000000
field public static final int FLAG_ALWAYS_RETAIN_TASK_STATE = 8; // 0x8
field public static final int FLAG_AUTO_REMOVE_FROM_RECENTS = 8192; // 0x2000
field public static final int FLAG_CLEAR_TASK_ON_LAUNCH = 4; // 0x4
@@ -11083,6 +11089,7 @@
method public void dump(android.util.Printer, String);
method public static CharSequence getCategoryTitle(android.content.Context, int);
method public int getGwpAsanMode();
+ method @NonNull public java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts();
method public int getMemtagMode();
method public int getNativeHeapZeroInitialized();
method public int getRequestRawExternalStorageAccess();
@@ -18198,6 +18205,7 @@
method public void enableSurfaceSharing();
method public int getDynamicRangeProfile();
method public int getMaxSharedSurfaceCount();
+ method public int getMirrorMode();
method public int getStreamUseCase();
method @Nullable public android.view.Surface getSurface();
method public int getSurfaceGroupId();
@@ -18206,11 +18214,16 @@
method public void removeSensorPixelModeUsed(int);
method public void removeSurface(@NonNull android.view.Surface);
method public void setDynamicRangeProfile(int);
+ method public void setMirrorMode(int);
method public void setPhysicalCameraId(@Nullable String);
method public void setStreamUseCase(int);
method public void setTimestampBase(int);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR;
+ field public static final int MIRROR_MODE_AUTO = 0; // 0x0
+ field public static final int MIRROR_MODE_H = 2; // 0x2
+ field public static final int MIRROR_MODE_NONE = 1; // 0x1
+ field public static final int MIRROR_MODE_V = 3; // 0x3
field public static final int SURFACE_GROUP_ID_NONE = -1; // 0xffffffff
field public static final int TIMESTAMP_BASE_CHOREOGRAPHER_SYNCED = 4; // 0x4
field public static final int TIMESTAMP_BASE_DEFAULT = 0; // 0x0
@@ -26747,6 +26760,23 @@
method @Deprecated public void startProvisionedVpnProfile();
method @NonNull public String startProvisionedVpnProfileSession();
method public void stopProvisionedVpnProfile();
+ field public static final String ACTION_VPN_MANAGER_EVENT = "android.net.action.VPN_MANAGER_EVENT";
+ field public static final String CATEGORY_EVENT_DEACTIVATED_BY_USER = "android.net.category.EVENT_DEACTIVATED_BY_USER";
+ field public static final String CATEGORY_EVENT_IKE_ERROR = "android.net.category.EVENT_IKE_ERROR";
+ field public static final String CATEGORY_EVENT_NETWORK_ERROR = "android.net.category.EVENT_NETWORK_ERROR";
+ field public static final int ERROR_CLASS_NOT_RECOVERABLE = 1; // 0x1
+ field public static final int ERROR_CLASS_RECOVERABLE = 2; // 0x2
+ field public static final int ERROR_CODE_NETWORK_IO = 3; // 0x3
+ field public static final int ERROR_CODE_NETWORK_LOST = 2; // 0x2
+ field public static final int ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT = 1; // 0x1
+ field public static final int ERROR_CODE_NETWORK_UNKNOWN_HOST = 0; // 0x0
+ field public static final String EXTRA_ERROR_CLASS = "android.net.extra.ERROR_CLASS";
+ field public static final String EXTRA_ERROR_CODE = "android.net.extra.ERROR_CODE";
+ field public static final String EXTRA_SESSION_KEY = "android.net.extra.SESSION_KEY";
+ field public static final String EXTRA_TIMESTAMP = "android.net.extra.TIMESTAMP";
+ field public static final String EXTRA_UNDERLYING_LINK_PROPERTIES = "android.net.extra.UNDERLYING_LINK_PROPERTIES";
+ field public static final String EXTRA_UNDERLYING_NETWORK = "android.net.extra.UNDERLYING_NETWORK";
+ field public static final String EXTRA_UNDERLYING_NETWORK_CAPABILITIES = "android.net.extra.UNDERLYING_NETWORK_CAPABILITIES";
}
public class VpnService extends android.app.Service {
@@ -51603,6 +51633,7 @@
method @Deprecated public void getBoundsInParent(android.graphics.Rect);
method public void getBoundsInScreen(android.graphics.Rect);
method public android.view.accessibility.AccessibilityNodeInfo getChild(int);
+ method @Nullable public android.view.accessibility.AccessibilityNodeInfo getChild(int, int);
method public int getChildCount();
method public CharSequence getClassName();
method public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo getCollectionInfo();
@@ -51622,6 +51653,7 @@
method public CharSequence getPackageName();
method @Nullable public CharSequence getPaneTitle();
method public android.view.accessibility.AccessibilityNodeInfo getParent();
+ method @Nullable public android.view.accessibility.AccessibilityNodeInfo getParent(int);
method public android.view.accessibility.AccessibilityNodeInfo.RangeInfo getRangeInfo();
method @Nullable public CharSequence getStateDescription();
method public CharSequence getText();
@@ -51772,8 +51804,15 @@
field public static final int EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_MAX_LENGTH = 20000; // 0x4e20
field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX";
field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY";
+ field public static final int FLAG_PREFETCH_ANCESTORS = 1; // 0x1
+ field public static final int FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST = 16; // 0x10
+ field public static final int FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST = 8; // 0x8
+ field public static final int FLAG_PREFETCH_DESCENDANTS_HYBRID = 4; // 0x4
+ field public static final int FLAG_PREFETCH_SIBLINGS = 2; // 0x2
+ field public static final int FLAG_PREFETCH_UNINTERRUPTIBLE = 32; // 0x20
field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2
field public static final int FOCUS_INPUT = 1; // 0x1
+ field public static final int MAX_NUMBER_OF_PREFETCHED_NODES = 50; // 0x32
field public static final int MOVEMENT_GRANULARITY_CHARACTER = 1; // 0x1
field public static final int MOVEMENT_GRANULARITY_LINE = 4; // 0x4
field public static final int MOVEMENT_GRANULARITY_PAGE = 16; // 0x10
@@ -51938,6 +51977,7 @@
method public int getScrollX();
method public int getScrollY();
method @Nullable public android.view.accessibility.AccessibilityNodeInfo getSource();
+ method @Nullable public android.view.accessibility.AccessibilityNodeInfo getSource(int);
method @NonNull public java.util.List<java.lang.CharSequence> getText();
method public int getToIndex();
method public int getWindowId();
@@ -51995,6 +52035,7 @@
method public android.view.accessibility.AccessibilityWindowInfo getParent();
method public void getRegionInScreen(@NonNull android.graphics.Region);
method public android.view.accessibility.AccessibilityNodeInfo getRoot();
+ method @Nullable public android.view.accessibility.AccessibilityNodeInfo getRoot(int);
method @Nullable public CharSequence getTitle();
method public int getType();
method public boolean isAccessibilityFocused();
@@ -52379,6 +52420,8 @@
method public void requestAutofill(@NonNull android.view.View, int, @NonNull android.graphics.Rect);
method public void setAutofillRequestCallback(@NonNull java.util.concurrent.Executor, @NonNull android.view.autofill.AutofillRequestCallback);
method public void setUserData(@Nullable android.service.autofill.UserData);
+ method public boolean showAutofillDialog(@NonNull android.view.View);
+ method public boolean showAutofillDialog(@NonNull android.view.View, int);
method public void unregisterCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
field public static final String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE";
field public static final String EXTRA_AUTHENTICATION_RESULT = "android.view.autofill.extra.AUTHENTICATION_RESULT";
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 5df593d..24b4f89 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -250,6 +250,22 @@
package android.net {
+ public class EthernetManager {
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void addInterfaceStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.EthernetManager.InterfaceStateListener);
+ method public void removeInterfaceStateListener(@NonNull android.net.EthernetManager.InterfaceStateListener);
+ method public void setIncludeTestInterfaces(boolean);
+ field public static final int ROLE_CLIENT = 1; // 0x1
+ field public static final int ROLE_NONE = 0; // 0x0
+ field public static final int ROLE_SERVER = 2; // 0x2
+ field public static final int STATE_ABSENT = 0; // 0x0
+ field public static final int STATE_LINK_DOWN = 1; // 0x1
+ field public static final int STATE_LINK_UP = 2; // 0x2
+ }
+
+ public static interface EthernetManager.InterfaceStateListener {
+ method public void onInterfaceStateChanged(@NonNull String, int, int, @Nullable android.net.IpConfiguration);
+ }
+
public class LocalSocket implements java.io.Closeable {
ctor public LocalSocket(@NonNull java.io.FileDescriptor);
}
@@ -466,6 +482,25 @@
method public static int logToRadioBuffer(int, @Nullable String, @Nullable String);
}
+ public final class Slog {
+ method public static int d(@Nullable String, @NonNull String);
+ method public static int d(@Nullable String, @NonNull String, @Nullable Throwable);
+ method public static int e(@Nullable String, @NonNull String);
+ method public static int e(@Nullable String, @NonNull String, @Nullable Throwable);
+ method public static int i(@Nullable String, @NonNull String);
+ method public static int i(@Nullable String, @NonNull String, @Nullable Throwable);
+ method public static int v(@Nullable String, @NonNull String);
+ method public static int v(@Nullable String, @NonNull String, @Nullable Throwable);
+ method public static int w(@Nullable String, @NonNull String);
+ method public static int w(@Nullable String, @NonNull String, @Nullable Throwable);
+ method public static int w(@Nullable String, @Nullable Throwable);
+ method public static int wtf(@Nullable String, @NonNull String);
+ method public static int wtf(@Nullable String, @Nullable Throwable);
+ method public static int wtf(@Nullable String, @NonNull String, @Nullable Throwable);
+ method public static void wtfQuiet(@Nullable String, @NonNull String);
+ method public static int wtfStack(@Nullable String, @NonNull String);
+ }
+
public class SystemConfigFileCommitEventLogger {
ctor public SystemConfigFileCommitEventLogger(@NonNull String);
method public void setStartTime(long);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b077cd7..5e5f387 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -851,6 +851,10 @@
field public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; // 0xb
}
+ public static class Notification.MediaStyle extends android.app.Notification.Style {
+ method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public android.app.Notification.MediaStyle setRemotePlaybackInfo(@NonNull CharSequence, @DrawableRes int, @Nullable android.app.PendingIntent);
+ }
+
public static final class Notification.TvExtender implements android.app.Notification.Extender {
ctor public Notification.TvExtender();
ctor public Notification.TvExtender(android.app.Notification);
@@ -914,7 +918,7 @@
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public int getNavBarModeOverride();
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean);
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setNavBarModeOverride(int);
- method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferReceiverDisplay(int, @NonNull android.media.MediaRoute2Info);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferReceiverDisplay(int, @NonNull android.media.MediaRoute2Info, @Nullable android.graphics.drawable.Icon, @Nullable CharSequence);
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferSenderDisplay(int, @NonNull android.media.MediaRoute2Info, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
field public static final int MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER = 0; // 0x0
field public static final int MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER = 1; // 0x1
@@ -3027,6 +3031,7 @@
}
public static final class IntegrityFormula.Application {
+ method @NonNull public static android.content.integrity.IntegrityFormula certificateLineageContains(@NonNull String);
method @NonNull public static android.content.integrity.IntegrityFormula certificatesContain(@NonNull String);
method @NonNull public static android.content.integrity.IntegrityFormula isPreInstalled();
method @NonNull public static android.content.integrity.IntegrityFormula packageNameEquals(@NonNull String);
@@ -3321,6 +3326,7 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, @NonNull java.util.List<java.lang.String>);
field public static final String ACTION_REQUEST_PERMISSIONS = "android.content.pm.action.REQUEST_PERMISSIONS";
field public static final String ACTION_REQUEST_PERMISSIONS_FOR_OTHER = "android.content.pm.action.REQUEST_PERMISSIONS_FOR_OTHER";
+ field public static final String EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES";
field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio";
@@ -5170,6 +5176,15 @@
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.Keyphrase> CREATOR;
}
+ public static final class SoundTrigger.KeyphraseRecognitionExtra implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getCoarseConfidenceLevel();
+ method public int getKeyphraseId();
+ method public int getRecognitionModes();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra> CREATOR;
+ }
+
public static final class SoundTrigger.KeyphraseSoundModel extends android.hardware.soundtrigger.SoundTrigger.SoundModel implements android.os.Parcelable {
ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[], int);
ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[]);
@@ -6199,6 +6214,10 @@
method @NonNull public android.media.HwAudioSource.Builder setAudioDeviceInfo(@NonNull android.media.AudioDeviceInfo);
}
+ public final class MediaCodec {
+ method @NonNull @RequiresPermission("android.permission.MEDIA_RESOURCE_OVERRIDE_PID") public static android.media.MediaCodec createByCodecNameForClient(@NonNull String, int, int) throws java.io.IOException;
+ }
+
public class MediaPlayer implements android.media.AudioRouting android.media.VolumeAutomation {
method @RequiresPermission(android.Manifest.permission.BIND_IMS_SERVICE) public void setOnRtpRxNoticeListener(@NonNull android.content.Context, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaPlayer.OnRtpRxNoticeListener);
}
@@ -8895,7 +8914,7 @@
method @Nullable public android.net.wifi.nl80211.DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String);
method @NonNull public java.util.List<android.net.wifi.nl80211.NativeScanResult> getScanResults(@NonNull String, int);
method @Nullable public android.net.wifi.nl80211.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String);
- method public boolean notifyCountryCodeChanged();
+ method public void notifyCountryCodeChanged(@Nullable String);
method @Nullable public static android.net.wifi.nl80211.WifiNl80211Manager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]);
method @Deprecated public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SoftApCallback);
method public boolean registerCountryCodeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangedListener);
@@ -11130,7 +11149,8 @@
method public int encodeSmdxSubjectAndReasonCode(@Nullable String, @Nullable String);
method @CallSuper public android.os.IBinder onBind(android.content.Intent);
method public abstract int onDeleteSubscription(int, String);
- method public android.service.euicc.DownloadSubscriptionResult onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean, @Nullable android.os.Bundle);
+ method @Deprecated public android.service.euicc.DownloadSubscriptionResult onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean, @Nullable android.os.Bundle);
+ method @NonNull public android.service.euicc.DownloadSubscriptionResult onDownloadSubscription(int, int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean, @NonNull android.os.Bundle);
method @Deprecated public int onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean);
method @Deprecated public abstract int onEraseSubscriptions(int);
method public int onEraseSubscriptions(int, @android.telephony.euicc.EuiccCardManager.ResetOption int);
@@ -11749,8 +11769,13 @@
public static class AlwaysOnHotwordDetector.EventPayload {
method @Nullable public android.os.ParcelFileDescriptor getAudioStream();
method @Nullable public android.media.AudioFormat getCaptureAudioFormat();
+ method @Nullable public byte[] getData();
+ method public int getDataFormat();
method @Nullable public android.service.voice.HotwordDetectedResult getHotwordDetectedResult();
- method @Nullable public byte[] getTriggerAudio();
+ method @NonNull public java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra> getKeyphraseRecognitionExtras();
+ method @Deprecated @Nullable public byte[] getTriggerAudio();
+ field public static final int DATA_FORMAT_RAW = 0; // 0x0
+ field public static final int DATA_FORMAT_TRIGGER_AUDIO = 1; // 0x1
}
public static final class AlwaysOnHotwordDetector.ModelParamRange {
@@ -11821,6 +11846,7 @@
}
public interface HotwordDetector {
+ method public default void destroy();
method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition();
method public boolean startRecognition(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle);
method public boolean stopRecognition();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index bcc7e4a..d683190 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -300,6 +300,9 @@
public class Notification implements android.os.Parcelable {
method public boolean shouldShowForegroundImmediately();
+ field public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice";
+ field public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon";
+ field public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent";
}
public final class NotificationChannel implements android.os.Parcelable {
@@ -1276,6 +1279,10 @@
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.KeyphraseMetadata> CREATOR;
}
+ public static final class SoundTrigger.KeyphraseRecognitionExtra implements android.os.Parcelable {
+ ctor public SoundTrigger.KeyphraseRecognitionExtra(int, int, int);
+ }
+
public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable {
ctor public SoundTrigger.ModelParamRange(int, int);
}
@@ -1607,10 +1614,6 @@
package android.net {
- public class EthernetManager {
- method public void setIncludeTestInterfaces(boolean);
- }
-
public class NetworkPolicyManager {
method public boolean getRestrictBackground();
method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public boolean isUidNetworkingBlocked(int, boolean);
@@ -1753,6 +1756,7 @@
public class Process {
method public static final int getThreadScheduler(int) throws java.lang.IllegalArgumentException;
+ method public static final int toSupplementalUid(int);
field public static final int FIRST_APP_ZYGOTE_ISOLATED_UID = 90000; // 0x15f90
field public static final int FIRST_ISOLATED_UID = 99000; // 0x182b8
field public static final int LAST_APP_ZYGOTE_ISOLATED_UID = 98999; // 0x182b7
@@ -2064,7 +2068,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData();
method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData(boolean);
method @NonNull public android.content.AttributionSource registerAttributionSource(@NonNull android.content.AttributionSource);
- method public void revokePostNotificationPermissionWithoutKillForTest(@NonNull String, int);
+ method @RequiresPermission(android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL) public void revokePostNotificationPermissionWithoutKillForTest(@NonNull String, int);
}
}
@@ -2401,6 +2405,19 @@
method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public void triggerHardwareRecognitionEventForTest(int, int, boolean, int, int, int, boolean, @NonNull android.media.AudioFormat, @Nullable byte[]);
}
+ public static final class AlwaysOnHotwordDetector.EventPayload.Builder {
+ ctor public AlwaysOnHotwordDetector.EventPayload.Builder();
+ method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload build();
+ method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setAudioStream(@NonNull android.os.ParcelFileDescriptor);
+ method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setCaptureAudioFormat(@NonNull android.media.AudioFormat);
+ method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setCaptureAvailable(boolean);
+ method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setCaptureSession(int);
+ method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setData(@NonNull byte[]);
+ method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setDataFormat(int);
+ method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setHotwordDetectedResult(@NonNull android.service.voice.HotwordDetectedResult);
+ method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setKeyphraseRecognitionExtras(@NonNull java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
+ }
+
public final class VisibleActivityInfo implements android.os.Parcelable {
ctor public VisibleActivityInfo(int, @NonNull android.os.IBinder);
}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index cf3ca20..c82f5f6 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -1008,14 +1008,6 @@
* is currently touching or the window with input focus, if the user is not
* touching any window. It could be from any logical display.
* <p>
- * The currently active window is defined as the window that most recently fired one
- * of the following events:
- * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED},
- * {@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER},
- * {@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}.
- * In other words, the last window shown that also has input focus.
- * </p>
- * <p>
* <strong>Note:</strong> In order to access the root node your service has
* to declare the capability to retrieve window content by setting the
* {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
@@ -1023,10 +1015,29 @@
* </p>
*
* @return The root node if this service can retrieve window content.
+ * @see AccessibilityWindowInfo#isActive() for more explanation about the active window.
*/
public AccessibilityNodeInfo getRootInActiveWindow() {
+ return getRootInActiveWindow(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID);
+ }
+
+ /**
+ * Gets the root node in the currently active window if this service
+ * can retrieve window content. The active window is the one that the user
+ * is currently touching or the window with input focus, if the user is not
+ * touching any window. It could be from any logical display.
+ *
+ * @param prefetchingStrategy the prefetching strategy.
+ * @return The root node if this service can retrieve window content.
+ *
+ * @see #getRootInActiveWindow()
+ * @see AccessibilityNodeInfo#getParent(int) for a description of prefetching.
+ */
+ @Nullable
+ public AccessibilityNodeInfo getRootInActiveWindow(
+ @AccessibilityNodeInfo.PrefetchingStrategy int prefetchingStrategy) {
return AccessibilityInteractionClient.getInstance(this).getRootInActiveWindow(
- mConnectionId);
+ mConnectionId, prefetchingStrategy);
}
/**
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a31aa28..983dde3 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -47,7 +47,9 @@
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ActivityNotFoundException;
+import android.content.ComponentCallbacks;
import android.content.ComponentCallbacks2;
+import android.content.ComponentCallbacksController;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -982,6 +984,8 @@
@Nullable
private DumpableContainerImpl mDumpableContainer;
+ private ComponentCallbacksController mCallbacksController;
+
private final WindowControllerCallback mWindowControllerCallback =
new WindowControllerCallback() {
/**
@@ -1323,6 +1327,28 @@
}
}
+ @Override
+ public void registerComponentCallbacks(ComponentCallbacks callback) {
+ if (CompatChanges.isChangeEnabled(OVERRIDABLE_COMPONENT_CALLBACKS)
+ && mCallbacksController == null) {
+ mCallbacksController = new ComponentCallbacksController();
+ }
+ if (mCallbacksController != null) {
+ mCallbacksController.registerCallbacks(callback);
+ } else {
+ super.registerComponentCallbacks(callback);
+ }
+ }
+
+ @Override
+ public void unregisterComponentCallbacks(ComponentCallbacks callback) {
+ if (mCallbacksController != null) {
+ mCallbacksController.unregisterCallbacks(callback);
+ } else {
+ super.unregisterComponentCallbacks(callback);
+ }
+ }
+
private void dispatchActivityPreCreated(@Nullable Bundle savedInstanceState) {
getApplication().dispatchActivityPreCreated(this, savedInstanceState);
Object[] callbacks = collectActivityLifecycleCallbacks();
@@ -2668,9 +2694,12 @@
if (mUiTranslationController != null) {
mUiTranslationController.onActivityDestroyed();
}
-
if (mDefaultBackCallback != null) {
getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mDefaultBackCallback);
+ mDefaultBackCallback = null;
+ }
+ if (mCallbacksController != null) {
+ mCallbacksController.clearCallbacks();
}
}
@@ -2991,6 +3020,9 @@
}
dispatchActivityConfigurationChanged();
+ if (mCallbacksController != null) {
+ mCallbacksController.dispatchConfigurationChanged(newConfig);
+ }
}
/**
@@ -3162,12 +3194,18 @@
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onLowMemory " + this);
mCalled = true;
mFragments.dispatchLowMemory();
+ if (mCallbacksController != null) {
+ mCallbacksController.dispatchLowMemory();
+ }
}
public void onTrimMemory(int level) {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onTrimMemory " + this + ": " + level);
mCalled = true;
mFragments.dispatchTrimMemory(level);
+ if (mCallbacksController != null) {
+ mCallbacksController.dispatchTrimMemory(level);
+ }
}
/**
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index cce7dd3..a58ceaa 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -215,6 +215,14 @@
public abstract boolean isSystemReady();
/**
+ * Returns package name given pid.
+ *
+ * @param pid The pid we are searching package name for.
+ */
+ @Nullable
+ public abstract String getPackageNameByPid(int pid);
+
+ /**
* Sets if the given pid has an overlay UI or not.
*
* @param pid The pid we are setting overlay UI for.
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 2084775..aa6c184 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -465,6 +465,7 @@
}
};
getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback);
+ mDefaultBackCallback = null;
}
}
diff --git a/core/java/android/app/DialogFragment.java b/core/java/android/app/DialogFragment.java
index 9fea3f7..cadbf23 100644
--- a/core/java/android/app/DialogFragment.java
+++ b/core/java/android/app/DialogFragment.java
@@ -140,7 +140,7 @@
* embed}
*
* @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- * {@link android.support.v4.app.DialogFragment} for consistent behavior across all devices
+ * {@link androidx.fragment.app.DialogFragment} for consistent behavior across all devices
* and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
*/
@Deprecated
diff --git a/core/java/android/app/FragmentContainer.java b/core/java/android/app/FragmentContainer.java
index 536c866..ff9dbcb 100644
--- a/core/java/android/app/FragmentContainer.java
+++ b/core/java/android/app/FragmentContainer.java
@@ -26,7 +26,7 @@
* Callbacks to a {@link Fragment}'s container.
*
* @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- * {@link android.support.v4.app.FragmentContainer}.
+ * {@link androidx.fragment.app.FragmentContainer}.
*/
@Deprecated
public abstract class FragmentContainer {
diff --git a/core/java/android/app/FragmentController.java b/core/java/android/app/FragmentController.java
index 150b7a5..3c8d760 100644
--- a/core/java/android/app/FragmentController.java
+++ b/core/java/android/app/FragmentController.java
@@ -41,7 +41,7 @@
* The methods provided by {@link FragmentController} are for that purpose.
*
* @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- * {@link android.support.v4.app.FragmentController}
+ * {@link androidx.fragment.app.FragmentController}
*/
@Deprecated
public class FragmentController {
diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java
index 9e887b8..6cfdc53 100644
--- a/core/java/android/app/FragmentHostCallback.java
+++ b/core/java/android/app/FragmentHostCallback.java
@@ -40,7 +40,7 @@
* applicable to the host.
*
* @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- * {@link android.support.v4.app.FragmentHostCallback}
+ * {@link androidx.fragment.app.FragmentHostCallback}
*/
@Deprecated
public abstract class FragmentHostCallback<E> extends FragmentContainer {
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 5435558..f8f846d 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -72,12 +72,12 @@
* While the FragmentManager API was introduced in
* {@link android.os.Build.VERSION_CODES#HONEYCOMB}, a version of the API
* at is also available for use on older platforms through
- * {@link android.support.v4.app.FragmentActivity}. See the blog post
+ * {@link androidx.fragment.app.FragmentActivity}. See the blog post
* <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html">
* Fragments For All</a> for more details.
*
* @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- * {@link android.support.v4.app.FragmentManager} for consistent behavior across all devices
+ * {@link androidx.fragment.app.FragmentManager} for consistent behavior across all devices
* and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
*/
@Deprecated
@@ -94,7 +94,7 @@
* will be persisted across activity instances.
*
* @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
- * Support Library</a> {@link android.support.v4.app.FragmentManager.BackStackEntry}
+ * Support Library</a> {@link androidx.fragment.app.FragmentManager.BackStackEntry}
*/
@Deprecated
public interface BackStackEntry {
@@ -142,7 +142,7 @@
*
* @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
* Support Library</a>
- * {@link android.support.v4.app.FragmentManager.OnBackStackChangedListener}
+ * {@link androidx.fragment.app.FragmentManager.OnBackStackChangedListener}
*/
@Deprecated
public interface OnBackStackChangedListener {
@@ -446,7 +446,7 @@
*
* @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
* Support Library</a>
- * {@link android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks}
+ * {@link androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks}
*/
@Deprecated
public abstract static class FragmentLifecycleCallbacks {
diff --git a/core/java/android/app/FragmentManagerNonConfig.java b/core/java/android/app/FragmentManagerNonConfig.java
index 326438a..ae7fd64 100644
--- a/core/java/android/app/FragmentManagerNonConfig.java
+++ b/core/java/android/app/FragmentManagerNonConfig.java
@@ -29,7 +29,7 @@
* {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</p>
*
* @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- * {@link android.support.v4.app.FragmentManagerNonConfig}
+ * {@link androidx.fragment.app.FragmentManagerNonConfig}
*/
@Deprecated
public class FragmentManagerNonConfig {
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index 713a559..34c4928 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -23,7 +23,7 @@
* </div>
*
* @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- * {@link android.support.v4.app.FragmentTransaction}
+ * {@link androidx.fragment.app.FragmentTransaction}
*/
@Deprecated
public abstract class FragmentTransaction {
diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java
index 5de0d69..2e83308 100644
--- a/core/java/android/app/IntentService.java
+++ b/core/java/android/app/IntentService.java
@@ -52,7 +52,7 @@
* guide.</p>
* </div>
*
- * @see android.support.v4.app.JobIntentService
+ * @see androidx.core.app.JobIntentService
*
* @deprecated IntentService is subject to all the
* <a href="{@docRoot}about/versions/oreo/background.html">background execution limits</a>
diff --git a/core/java/android/app/ListFragment.java b/core/java/android/app/ListFragment.java
index 7790f70..b6d80ca 100644
--- a/core/java/android/app/ListFragment.java
+++ b/core/java/android/app/ListFragment.java
@@ -146,7 +146,7 @@
* @see android.widget.ListView
*
* @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- * {@link android.support.v4.app.ListFragment} for consistent behavior across all devices
+ * {@link androidx.fragment.app.ListFragment} for consistent behavior across all devices
* and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
*/
@Deprecated
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index 86d0fd62..e2de716 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -37,7 +37,7 @@
* While the LoaderManager API was introduced in
* {@link android.os.Build.VERSION_CODES#HONEYCOMB}, a version of the API
* at is also available for use on older platforms through
- * {@link android.support.v4.app.FragmentActivity}. See the blog post
+ * {@link androidx.fragment.app.FragmentActivity}. See the blog post
* <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html">
* Fragments For All</a> for more details.
*
@@ -56,7 +56,7 @@
* </div>
*
* @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- * {@link android.support.v4.app.LoaderManager}
+ * {@link androidx.loader.app.LoaderManager}
*/
@Deprecated
public abstract class LoaderManager {
@@ -64,7 +64,7 @@
* Callback interface for a client to interact with the manager.
*
* @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
- * Support Library</a> {@link android.support.v4.app.LoaderManager.LoaderCallbacks}
+ * Support Library</a> {@link androidx.loader.app.LoaderManager.LoaderCallbacks}
*/
@Deprecated
public interface LoaderCallbacks<D> {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 6a14772..9dd206e 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1330,6 +1330,32 @@
public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
/**
+ * {@link #extras} key: A {@code CharSequence} name of a remote device used for a media session
+ * associated with a {@link Notification.MediaStyle} notification. This will show in the media
+ * controls output switcher instead of the local device name.
+ * @hide
+ */
+ @TestApi
+ public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice";
+
+ /**
+ * {@link #extras} key: A {@code int} resource ID for an icon that should show in the output
+ * switcher of the media controls for a {@link Notification.MediaStyle} notification.
+ * @hide
+ */
+ @TestApi
+ public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon";
+
+ /**
+ * {@link #extras} key: A {@code PendingIntent} that will replace the default action for the
+ * media controls output switcher chip, associated with a {@link Notification.MediaStyle}
+ * notification. This should launch an activity.
+ * @hide
+ */
+ @TestApi
+ public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent";
+
+ /**
* {@link #extras} key: the indices of actions to be shown in the compact view,
* as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}.
*/
@@ -3719,7 +3745,7 @@
* Provides a convenient way to set the various fields of a {@link Notification} and generate
* content views using the platform's notification layout template. If your app supports
* versions of Android as old as API level 4, you can instead use
- * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder},
+ * {@link androidx.core.app.NotificationCompat.Builder NotificationCompat.Builder},
* available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support
* library</a>.
*
@@ -8943,6 +8969,9 @@
private int[] mActionsToShowInCompact = null;
private MediaSession.Token mToken;
+ private CharSequence mDeviceName;
+ private int mDeviceIcon;
+ private PendingIntent mDeviceIntent;
public MediaStyle() {
}
@@ -8976,6 +9005,32 @@
}
/**
+ * For media notifications associated with playback on a remote device, provide device
+ * information that will replace the default values for the output switcher chip on the
+ * media control, as well as an intent to use when the output switcher chip is tapped,
+ * on devices where this is supported.
+ *
+ * @param deviceName The name of the remote device to display
+ * @param iconResource Icon resource representing the device
+ * @param chipIntent PendingIntent to send when the output switcher is tapped. May be
+ * {@code null}, in which case the output switcher will be disabled.
+ * This intent should open an Activity or it will be ignored.
+ * @return MediaStyle
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+ @NonNull
+ public MediaStyle setRemotePlaybackInfo(@NonNull CharSequence deviceName,
+ @DrawableRes int iconResource, @Nullable PendingIntent chipIntent) {
+ mDeviceName = deviceName;
+ mDeviceIcon = iconResource;
+ mDeviceIntent = chipIntent;
+ return this;
+ }
+
+ /**
* @hide
*/
@Override
@@ -9023,6 +9078,15 @@
if (mActionsToShowInCompact != null) {
extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact);
}
+ if (mDeviceName != null) {
+ extras.putCharSequence(EXTRA_MEDIA_REMOTE_DEVICE, mDeviceName);
+ }
+ if (mDeviceIcon > 0) {
+ extras.putInt(EXTRA_MEDIA_REMOTE_ICON, mDeviceIcon);
+ }
+ if (mDeviceIntent != null) {
+ extras.putParcelable(EXTRA_MEDIA_REMOTE_INTENT, mDeviceIntent);
+ }
}
/**
@@ -9038,6 +9102,15 @@
if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) {
mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS);
}
+ if (extras.containsKey(EXTRA_MEDIA_REMOTE_DEVICE)) {
+ mDeviceName = extras.getCharSequence(EXTRA_MEDIA_REMOTE_DEVICE);
+ }
+ if (extras.containsKey(EXTRA_MEDIA_REMOTE_ICON)) {
+ mDeviceIcon = extras.getInt(EXTRA_MEDIA_REMOTE_ICON);
+ }
+ if (extras.containsKey(EXTRA_MEDIA_REMOTE_INTENT)) {
+ mDeviceIntent = extras.getParcelable(EXTRA_MEDIA_REMOTE_INTENT);
+ }
}
/**
diff --git a/core/java/android/app/NotificationHistory.java b/core/java/android/app/NotificationHistory.java
index b40fbee..eadb7e3 100644
--- a/core/java/android/app/NotificationHistory.java
+++ b/core/java/android/app/NotificationHistory.java
@@ -610,6 +610,7 @@
// Data can be too large for a transact. Write the data as a Blob, which will be written to
// ashmem if too large.
dest.writeBlob(data.marshall());
+ data.recycle();
}
public static final @NonNull Creator<NotificationHistory> CREATOR
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index da1ba52..ea12942 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -1012,6 +1012,8 @@
*
* @param displayState the new state for media tap-to-transfer.
* @param routeInfo the media route information for the media being transferred.
+ * @param appIcon the icon of the app playing the media.
+ * @param appName the name of the app playing the media.
*
* @hide
*/
@@ -1019,11 +1021,13 @@
@RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void updateMediaTapToTransferReceiverDisplay(
@MediaTransferReceiverState int displayState,
- @NonNull MediaRoute2Info routeInfo) {
+ @NonNull MediaRoute2Info routeInfo,
+ @Nullable Icon appIcon,
+ @Nullable CharSequence appName) {
Objects.requireNonNull(routeInfo);
IStatusBarService svc = getService();
try {
- svc.updateMediaTapToTransferReceiverDisplay(displayState, routeInfo);
+ svc.updateMediaTapToTransferReceiverDisplay(displayState, routeInfo, appIcon, appName);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 2af8905..5e521b0 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -733,7 +733,8 @@
}
// Calling out without a lock held.
return AccessibilityInteractionClient.getInstance()
- .getRootInActiveWindow(connectionId);
+ .getRootInActiveWindow(connectionId,
+ AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID);
}
/**
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 73a9e5a..cfaffb1 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -517,7 +517,7 @@
* (and potentially an Activity lifecycle event) being applied to all running apps.
* Developers interested in an app-local implementation of night mode should consider using
* {@link #setApplicationNightMode(int)} to set and persist the -night qualifier locally or
- * {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)} for the
+ * {@link androidx.appcompat.app.AppCompatDelegate#setDefaultNightMode(int)} for the
* backward compatible implementation.
*
* @param mode the night mode to set
@@ -595,7 +595,7 @@
* user clears the data for the application, or this application is uninstalled.
* <p>
* Developers interested in a non-persistent app-local implementation of night mode should
- * consider using {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)}
+ * consider using {@link androidx.appcompat.app.AppCompatDelegate#setDefaultNightMode(int)}
* to manage the -night qualifier locally.
*
* @param mode the night mode to set
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index e52ae51..fde9b96 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9713,9 +9713,9 @@
* service. When zero or more packages have been added, accessibility services that are not in
* the list and not part of the system can not be enabled by the user.
* <p>
- * Calling with a null value for the list disables the restriction so that all services can be
- * used, calling with an empty list only allows the built-in system services. Any non-system
- * accessibility service that's currently enabled must be included in the list.
+ * Calling with a {@code null} value for the list disables the restriction so that all services
+ * can be used, calling with an empty list only allows the built-in system services. Any
+ * non-system accessibility service that's currently enabled must be included in the list.
* <p>
* System accessibility services are always available to the user and this method can't
* disable them.
@@ -9741,8 +9741,8 @@
/**
* Returns the list of permitted accessibility services set by this device or profile owner.
* <p>
- * An empty list means no accessibility services except system services are allowed. Null means
- * all accessibility services are allowed.
+ * An empty list means no accessibility services except system services are allowed.
+ * {@code null} means all accessibility services are allowed.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @return List of accessiblity service package names.
@@ -9787,7 +9787,7 @@
* Returns the list of accessibility services permitted by the device or profiles
* owners of this user.
*
- * <p>Null means all accessibility services are allowed, if a non-null list is returned
+ * <p>{@code null} means all accessibility services are allowed, if a non-null list is returned
* it will contain the intersection of the permitted lists for any device or profile
* owners that apply to this user. It will also include any system accessibility services.
*
@@ -9933,6 +9933,8 @@
*
* @return List of input method package names.
* @hide
+ *
+ * @see #setPermittedAccessibilityServices(ComponentName, List)
*/
@SystemApi
@RequiresPermission(anyOf = {
@@ -9953,29 +9955,30 @@
/**
* Returns the list of input methods permitted.
*
- * <p>When this method returns empty list means all input methods are allowed, if a non-empty
- * list is returned it will contain the intersection of the permitted lists for any device or
- * profile owners that apply to this user. It will also include any system input methods.
+ * <p>{@code null} means all input methods are allowed, if a non-null list is returned
+ * it will contain the intersection of the permitted lists for any device or profile
+ * owners that apply to this user. It will also include any system input methods.
*
* @return List of input method package names.
* @hide
+ *
+ * @see #setPermittedAccessibilityServices(ComponentName, List)
*/
@UserHandleAware
@RequiresPermission(allOf = {
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
android.Manifest.permission.MANAGE_USERS
}, conditional = true)
- public @NonNull List<String> getPermittedInputMethods() {
+ public @Nullable List<String> getPermittedInputMethods() {
throwIfParentInstance("getPermittedInputMethods");
- List<String> result = null;
if (mService != null) {
try {
- result = mService.getPermittedInputMethodsAsUser(myUserId());
+ return mService.getPermittedInputMethodsAsUser(myUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
- return result != null ? result : Collections.emptyList();
+ return null;
}
/**
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 36802eabe..1568500 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -780,9 +780,9 @@
}
/**
- * Notify the system that the given self-managed association has just 'appeared'.
+ * Notify the system that the given self-managed association has just appeared.
* This causes the system to bind to the companion app to keep it running until the association
- * is reported as 'disappeared'
+ * is reported as disappeared
*
* <p>This API is only available for the companion apps that manage the connectivity by
* themselves.</p>
@@ -803,7 +803,7 @@
}
/**
- * Notify the system that the given self-managed association has just 'disappeared'.
+ * Notify the system that the given self-managed association has just disappeared.
* This causes the system to unbind to the companion app.
*
* <p>This API is only available for the companion apps that manage the connectivity by
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index cb96ebe..9e1bf4b 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -77,10 +77,11 @@
* {@link #onDeviceAppeared(AssociationInfo)} and {@link #onDeviceDisappeared(AssociationInfo)}
* only to one "primary" services.
* Applications that declare multiple {@link CompanionDeviceService}-s should indicate the "primary"
- * service using "android.companion.primary" tag.
+ * service using "android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE" service level
+ * property.
* <pre>{@code
- * <meta-data
- * android:name="android.companion.primary"
+ * <property
+ * android:name="android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE"
* android:value="true" />
* }</pre>
*
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index fc0efb1..fdff27f 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -265,8 +265,8 @@
*
* @param display the display that the events inputted through this device should target
* @param inputDeviceName the name to call this input device
- * @param vendorId the vendor id
- * @param productId the product id
+ * @param vendorId the vendor id, as defined by uinput's uinput_setup struct (PCI vendor id)
+ * @param productId the product id, as defined by uinput's uinput_setup struct
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
@@ -291,8 +291,8 @@
*
* @param display the display that the events inputted through this device should target
* @param inputDeviceName the name to call this input device
- * @param vendorId the vendor id
- * @param productId the product id
+ * @param vendorId the vendor id, as defined by uinput's uinput_setup struct (PCI vendor id)
+ * @param productId the product id, as defined by uinput's uinput_setup struct
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
@@ -317,8 +317,8 @@
*
* @param display the display that the events inputted through this device should target
* @param inputDeviceName the name to call this input device
- * @param vendorId the vendor id
- * @param productId the product id
+ * @param vendorId the vendor id, as defined by uinput's uinput_setup struct (PCI vendor id)
+ * @param productId the product id, as defined by uinput's uinput_setup struct
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java
index 14c3387..3e544b2 100644
--- a/core/java/android/content/AsyncTaskLoader.java
+++ b/core/java/android/content/AsyncTaskLoader.java
@@ -52,7 +52,7 @@
* @param <D> the data type to be loaded.
*
* @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- * {@link android.support.v4.content.AsyncTaskLoader}
+ * {@link androidx.loader.content.AsyncTaskLoader}
*/
@Deprecated
public abstract class AsyncTaskLoader<D> extends Loader<D> {
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index d46a0c6..2a19d37 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -376,7 +376,7 @@
* to run, allowing them to execute for 30 seconds or even a bit more. This is something that
* receivers should rarely take advantage of (long work should be punted to another system
* facility such as {@link android.app.job.JobScheduler}, {@link android.app.Service}, or
- * see especially {@link android.support.v4.app.JobIntentService}), but can be useful in
+ * see especially {@link androidx.core.app.JobIntentService}), but can be useful in
* certain rare cases where it is necessary to do some work as soon as the broadcast is
* delivered. Keep in mind that the work you do here will block further broadcasts until
* it completes, so taking advantage of this at all excessively can be counter-productive
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2074125..957cb24c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -36,6 +36,7 @@
import android.annotation.TestApi;
import android.annotation.UiContext;
import android.annotation.UserIdInt;
+import android.app.Activity;
import android.app.ActivityManager;
import android.app.BroadcastOptions;
import android.app.GameManager;
@@ -45,6 +46,8 @@
import android.app.ambientcontext.AmbientContextManager;
import android.app.people.PeopleManager;
import android.app.time.TimeManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -85,6 +88,7 @@
import android.view.textclassifier.TextClassificationManager;
import android.window.WindowContext;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.compat.IPlatformCompatNative;
@@ -111,6 +115,19 @@
* broadcasting and receiving intents, etc.
*/
public abstract class Context {
+ /**
+ * After {@link Build.VERSION_CODES#TIRAMISU},
+ * {@link #registerComponentCallbacks(ComponentCallbacks)} will add a {@link ComponentCallbacks}
+ * to {@link Activity} or {@link ContextWrapper#getBaseContext()} instead of always adding to
+ * {@link #getApplicationContext()}.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ @VisibleForTesting
+ public static final long OVERRIDABLE_COMPONENT_CALLBACKS = 193247900L;
+
/** @hide */
@IntDef(flag = true, prefix = { "MODE_" }, value = {
MODE_PRIVATE,
@@ -163,7 +180,7 @@
* {@link BroadcastReceiver}, and {@link android.app.Service}.
* There are no guarantees that this access mode will remain on
* a file, such as when it goes through a backup and restore.
- * @see android.support.v4.content.FileProvider
+ * @see androidx.core.content.FileProvider
* @see Intent#FLAG_GRANT_WRITE_URI_PERMISSION
*/
@Deprecated
@@ -183,7 +200,7 @@
* {@link BroadcastReceiver}, and {@link android.app.Service}.
* There are no guarantees that this access mode will remain on
* a file, such as when it goes through a backup and restore.
- * @see android.support.v4.content.FileProvider
+ * @see androidx.core.content.FileProvider
* @see Intent#FLAG_GRANT_WRITE_URI_PERMISSION
*/
@Deprecated
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 98ced6d..9adf173 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -25,8 +25,6 @@
import android.app.IApplicationThread;
import android.app.IServiceConnection;
import android.app.compat.CompatChanges;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -74,16 +72,6 @@
Context mBase;
/**
- * After {@link Build.VERSION_CODES#TIRAMISU},
- * {@link #registerComponentCallbacks(ComponentCallbacks)} will delegate to
- * {@link #getBaseContext()} instead of {@link #getApplicationContext()}.
- */
- @ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
- @VisibleForTesting
- static final long COMPONENT_CALLBACK_ON_WRAPPER = 193247900L;
-
- /**
* A list to store {@link ComponentCallbacks} which
* passes to {@link #registerComponentCallbacks(ComponentCallbacks)} before
* {@link #attachBaseContext(Context)}.
@@ -1355,7 +1343,7 @@
public void registerComponentCallbacks(ComponentCallbacks callback) {
if (mBase != null) {
mBase.registerComponentCallbacks(callback);
- } else if (!CompatChanges.isChangeEnabled(COMPONENT_CALLBACK_ON_WRAPPER)) {
+ } else if (!CompatChanges.isChangeEnabled(OVERRIDABLE_COMPONENT_CALLBACKS)) {
super.registerComponentCallbacks(callback);
synchronized (mLock) {
// Also register ComponentCallbacks to ContextWrapper, so we can find the correct
@@ -1397,7 +1385,7 @@
mCallbacksRegisteredToSuper.remove(callback);
} else if (mBase != null) {
mBase.unregisterComponentCallbacks(callback);
- } else if (CompatChanges.isChangeEnabled(COMPONENT_CALLBACK_ON_WRAPPER)) {
+ } else if (CompatChanges.isChangeEnabled(OVERRIDABLE_COMPONENT_CALLBACKS)) {
// Throw exception for Application that is targeting S-v2+
throw new IllegalStateException("ComponentCallbacks must be unregistered after "
+ "this ContextWrapper is attached to a base Context.");
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index fda646c..cfb0f95 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -42,7 +42,7 @@
* and {@link #setProjection(String[])}.
*
* @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- * {@link android.support.v4.content.CursorLoader}
+ * {@link androidx.loader.content.CursorLoader}
*/
@Deprecated
public class CursorLoader extends AsyncTaskLoader<Cursor> {
diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java
index b0555d4..afd495b 100644
--- a/core/java/android/content/Loader.java
+++ b/core/java/android/content/Loader.java
@@ -50,7 +50,7 @@
* @param <D> The result returned when the load is complete
*
* @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- * {@link android.support.v4.content.Loader}
+ * {@link androidx.loader.content.Loader}
*/
@Deprecated
public class Loader<D> {
@@ -71,7 +71,7 @@
* it is used for you by {@link CursorLoader} to take care of executing
* an update when the cursor's backing data changes.
*
- * @deprecated Use {@link android.support.v4.content.Loader.ForceLoadContentObserver}
+ * @deprecated Use {@link androidx.loader.content.Loader.ForceLoadContentObserver}
*/
@Deprecated
public final class ForceLoadContentObserver extends ContentObserver {
@@ -98,7 +98,7 @@
* be reported to its client. This interface should only be used if a
* Loader is not being used in conjunction with LoaderManager.
*
- * @deprecated Use {@link android.support.v4.content.Loader.OnLoadCompleteListener}
+ * @deprecated Use {@link androidx.loader.content.Loader.OnLoadCompleteListener}
*/
@Deprecated
public interface OnLoadCompleteListener<D> {
@@ -119,7 +119,7 @@
* can schedule the next Loader. This interface should only be used if a
* Loader is not being used in conjunction with LoaderManager.
*
- * @deprecated Use {@link android.support.v4.content.Loader.OnLoadCanceledListener}
+ * @deprecated Use {@link androidx.loader.content.Loader.OnLoadCanceledListener}
*/
@Deprecated
public interface OnLoadCanceledListener<D> {
diff --git a/core/java/android/content/integrity/AppInstallMetadata.java b/core/java/android/content/integrity/AppInstallMetadata.java
index 4f38fae..9874890 100644
--- a/core/java/android/content/integrity/AppInstallMetadata.java
+++ b/core/java/android/content/integrity/AppInstallMetadata.java
@@ -37,6 +37,8 @@
private final String mPackageName;
// Raw string encoding for the SHA-256 hash of the certificate of the app.
private final List<String> mAppCertificates;
+ // Raw string encoding for the SHA-256 hash of the certificate lineage/history of the app.
+ private final List<String> mAppCertificateLineage;
private final String mInstallerName;
// Raw string encoding for the SHA-256 hash of the certificate of the installer.
private final List<String> mInstallerCertificates;
@@ -52,6 +54,7 @@
private AppInstallMetadata(Builder builder) {
this.mPackageName = builder.mPackageName;
this.mAppCertificates = builder.mAppCertificates;
+ this.mAppCertificateLineage = builder.mAppCertificateLineage;
this.mInstallerName = builder.mInstallerName;
this.mInstallerCertificates = builder.mInstallerCertificates;
this.mVersionCode = builder.mVersionCode;
@@ -74,6 +77,11 @@
}
@NonNull
+ public List<String> getAppCertificateLineage() {
+ return mAppCertificateLineage;
+ }
+
+ @NonNull
public String getInstallerName() {
return mInstallerName;
}
@@ -126,6 +134,7 @@
+ " %b, StampVerified = %b, StampTrusted = %b, StampCert = %s }",
mPackageName,
mAppCertificates,
+ mAppCertificateLineage,
mInstallerName == null ? "null" : mInstallerName,
mInstallerCertificates == null ? "null" : mInstallerCertificates,
mVersionCode,
@@ -140,6 +149,7 @@
public static final class Builder {
private String mPackageName;
private List<String> mAppCertificates;
+ private List<String> mAppCertificateLineage;
private String mInstallerName;
private List<String> mInstallerCertificates;
private long mVersionCode;
@@ -192,6 +202,20 @@
}
/**
+ * Set the list of (old and new) certificates used for signing the app to be installed.
+ *
+ * <p>It is represented as the raw string encoding for the SHA-256 hash of the certificate
+ * lineage/history of the app.
+ *
+ * @see AppInstallMetadata#getAppCertificateLineage()
+ */
+ @NonNull
+ public Builder setAppCertificateLineage(@NonNull List<String> appCertificateLineage) {
+ this.mAppCertificateLineage = Objects.requireNonNull(appCertificateLineage);
+ return this;
+ }
+
+ /**
* Set name of the installer installing the app.
*
* @see AppInstallMetadata#getInstallerName()
@@ -294,6 +318,7 @@
public AppInstallMetadata build() {
Objects.requireNonNull(mPackageName);
Objects.requireNonNull(mAppCertificates);
+ Objects.requireNonNull(mAppCertificateLineage);
return new AppInstallMetadata(this);
}
}
diff --git a/core/java/android/content/integrity/AtomicFormula.java b/core/java/android/content/integrity/AtomicFormula.java
index e359800..f888813 100644
--- a/core/java/android/content/integrity/AtomicFormula.java
+++ b/core/java/android/content/integrity/AtomicFormula.java
@@ -56,6 +56,7 @@
PRE_INSTALLED,
STAMP_TRUSTED,
STAMP_CERTIFICATE_HASH,
+ APP_CERTIFICATE_LINEAGE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Key {}
@@ -122,6 +123,13 @@
*/
public static final int STAMP_CERTIFICATE_HASH = 7;
+ /**
+ * SHA-256 of a certificate in the signing lineage of the app.
+ *
+ * <p>Can only be used in {@link StringAtomicFormula}.
+ */
+ public static final int APP_CERTIFICATE_LINEAGE = 8;
+
public static final int EQ = 0;
public static final int GT = 1;
public static final int GTE = 2;
@@ -225,6 +233,11 @@
}
@Override
+ public boolean isAppCertificateLineageFormula() {
+ return false;
+ }
+
+ @Override
public boolean isInstallerFormula() {
return false;
}
@@ -314,7 +327,8 @@
|| key == APP_CERTIFICATE
|| key == INSTALLER_CERTIFICATE
|| key == INSTALLER_NAME
- || key == STAMP_CERTIFICATE_HASH,
+ || key == STAMP_CERTIFICATE_HASH
+ || key == APP_CERTIFICATE_LINEAGE,
"Key %s cannot be used with StringAtomicFormula", keyToString(key));
mValue = null;
mIsHashedValue = null;
@@ -335,7 +349,8 @@
|| key == APP_CERTIFICATE
|| key == INSTALLER_CERTIFICATE
|| key == INSTALLER_NAME
- || key == STAMP_CERTIFICATE_HASH,
+ || key == STAMP_CERTIFICATE_HASH
+ || key == APP_CERTIFICATE_LINEAGE,
"Key %s cannot be used with StringAtomicFormula", keyToString(key));
mValue = value;
mIsHashedValue = isHashed;
@@ -348,8 +363,9 @@
* <p>The value will be automatically hashed with SHA256 and the hex digest will be computed
* when the key is PACKAGE_NAME or INSTALLER_NAME and the value is more than 32 characters.
*
- * <p>The APP_CERTIFICATES, INSTALLER_CERTIFICATES, and STAMP_CERTIFICATE_HASH are always
- * delivered in hashed form. So the isHashedValue is set to true by default.
+ * <p>The APP_CERTIFICATES, INSTALLER_CERTIFICATES, STAMP_CERTIFICATE_HASH and
+ * APP_CERTIFICATE_LINEAGE are always delivered in hashed form. So the isHashedValue is set
+ * to true by default.
*
* @throws IllegalArgumentException if {@code key} cannot be used with string value.
*/
@@ -360,13 +376,15 @@
|| key == APP_CERTIFICATE
|| key == INSTALLER_CERTIFICATE
|| key == INSTALLER_NAME
- || key == STAMP_CERTIFICATE_HASH,
+ || key == STAMP_CERTIFICATE_HASH
+ || key == APP_CERTIFICATE_LINEAGE,
"Key %s cannot be used with StringAtomicFormula", keyToString(key));
mValue = hashValue(key, value);
mIsHashedValue =
(key == APP_CERTIFICATE
|| key == INSTALLER_CERTIFICATE
- || key == STAMP_CERTIFICATE_HASH)
+ || key == STAMP_CERTIFICATE_HASH
+ || key == APP_CERTIFICATE_LINEAGE)
|| !mValue.equals(value);
}
@@ -409,6 +427,11 @@
}
@Override
+ public boolean isAppCertificateLineageFormula() {
+ return getKey() == APP_CERTIFICATE_LINEAGE;
+ }
+
+ @Override
public boolean isInstallerFormula() {
return getKey() == INSTALLER_NAME || getKey() == INSTALLER_CERTIFICATE;
}
@@ -474,6 +497,8 @@
return Collections.singletonList(appInstallMetadata.getInstallerName());
case AtomicFormula.STAMP_CERTIFICATE_HASH:
return Collections.singletonList(appInstallMetadata.getStampCertificateHash());
+ case AtomicFormula.APP_CERTIFICATE_LINEAGE:
+ return appInstallMetadata.getAppCertificateLineage();
default:
throw new IllegalStateException(
"Unexpected key in StringAtomicFormula: " + key);
@@ -577,6 +602,11 @@
}
@Override
+ public boolean isAppCertificateLineageFormula() {
+ return false;
+ }
+
+ @Override
public boolean isInstallerFormula() {
return false;
}
@@ -660,6 +690,8 @@
return "STAMP_TRUSTED";
case STAMP_CERTIFICATE_HASH:
return "STAMP_CERTIFICATE_HASH";
+ case APP_CERTIFICATE_LINEAGE:
+ return "APP_CERTIFICATE_LINEAGE";
default:
throw new IllegalArgumentException("Unknown key " + key);
}
@@ -686,6 +718,7 @@
|| key == INSTALLER_CERTIFICATE
|| key == PRE_INSTALLED
|| key == STAMP_TRUSTED
- || key == STAMP_CERTIFICATE_HASH;
+ || key == STAMP_CERTIFICATE_HASH
+ || key == APP_CERTIFICATE_LINEAGE;
}
}
diff --git a/core/java/android/content/integrity/CompoundFormula.java b/core/java/android/content/integrity/CompoundFormula.java
index 1ffabd0..23ee1d9 100644
--- a/core/java/android/content/integrity/CompoundFormula.java
+++ b/core/java/android/content/integrity/CompoundFormula.java
@@ -137,6 +137,11 @@
}
@Override
+ public boolean isAppCertificateLineageFormula() {
+ return getFormulas().stream().anyMatch(formula -> formula.isAppCertificateLineageFormula());
+ }
+
+ @Override
public boolean isInstallerFormula() {
return getFormulas().stream().anyMatch(formula -> formula.isInstallerFormula());
}
diff --git a/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java
index 9d37299..5bcbef6 100644
--- a/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java
+++ b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java
@@ -73,6 +73,11 @@
}
@Override
+ public boolean isAppCertificateLineageFormula() {
+ return false;
+ }
+
+ @Override
public boolean isInstallerFormula() {
return true;
}
diff --git a/core/java/android/content/integrity/IntegrityFormula.java b/core/java/android/content/integrity/IntegrityFormula.java
index d965ef5..9e2a332 100644
--- a/core/java/android/content/integrity/IntegrityFormula.java
+++ b/core/java/android/content/integrity/IntegrityFormula.java
@@ -49,14 +49,23 @@
}
/**
- * Returns an integrity formula that checks if the app certificates contain {@code
- * appCertificate}.
+ * Returns an integrity formula that checks if the app certificates contain the string
+ * provided by the appCertificate parameter.
*/
@NonNull
public static IntegrityFormula certificatesContain(@NonNull String appCertificate) {
return new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE, appCertificate);
}
+ /**
+ * Returns an integrity formula that checks if the app certificate lineage contains the
+ * string provided by the appCertificate parameter.
+ */
+ @NonNull
+ public static IntegrityFormula certificateLineageContains(@NonNull String appCertificate) {
+ return new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE_LINEAGE, appCertificate);
+ }
+
/** Returns an integrity formula that checks the equality to a version code. */
@NonNull
public static IntegrityFormula versionCodeEquals(@NonNull long versionCode) {
@@ -187,6 +196,14 @@
public abstract boolean isAppCertificateFormula();
/**
+ * Returns true when the formula (or one of its atomic formulas) has app certificate lineage as
+ * key.
+ *
+ * @hide
+ */
+ public abstract boolean isAppCertificateLineageFormula();
+
+ /**
* Returns true when the formula (or one of its atomic formulas) has installer package name or
* installer certificate as key.
*
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 8bea006..0673b3a 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -18,6 +18,8 @@
import android.annotation.FloatRange;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.Activity;
import android.app.compat.CompatChanges;
@@ -35,10 +37,16 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
+import android.util.ArraySet;
import android.util.Printer;
+import com.android.internal.util.Parcelling;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Set;
/**
* Information you can retrieve about a particular application
@@ -48,6 +56,9 @@
*/
public class ActivityInfo extends ComponentInfo implements Parcelable {
+ private static final Parcelling.BuiltIn.ForStringSet sForStringSet =
+ Parcelling.Cache.getOrCreate(Parcelling.BuiltIn.ForStringSet.class);
+
// NOTE: When adding new data members be sure to update the copy-constructor, Parcel
// constructor, and writeToParcel.
@@ -525,6 +536,13 @@
public static final int FLAG_PREFER_MINIMAL_POST_PROCESSING = 0x2000000;
/**
+ * Bit in {@link #flags}: If set, indicates that the activity can be embedded by untrusted
+ * hosts. In this case the interactions with and visibility of the embedded activity may be
+ * limited. Set from the {@link android.R.attr#allowUntrustedActivityEmbedding} attribute.
+ */
+ public static final int FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING = 0x10000000;
+
+ /**
* @hide Bit in {@link #flags}: If set, this component will only be seen
* by the system user. Only works with broadcast receivers. Set from the
* android.R.attr#systemUserOnly attribute.
@@ -561,7 +579,8 @@
* {@link #FLAG_STATE_NOT_NEEDED}, {@link #FLAG_EXCLUDE_FROM_RECENTS},
* {@link #FLAG_ALLOW_TASK_REPARENTING}, {@link #FLAG_NO_HISTORY},
* {@link #FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS},
- * {@link #FLAG_HARDWARE_ACCELERATED}, {@link #FLAG_SINGLE_USER}.
+ * {@link #FLAG_HARDWARE_ACCELERATED}, {@link #FLAG_SINGLE_USER},
+ * {@link #FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING}.
*/
public int flags;
@@ -1080,6 +1099,13 @@
private static final long CHECK_MIN_WIDTH_HEIGHT_FOR_MULTI_WINDOW = 197654537L;
/**
+ * Optional set of a certificates identifying apps that are allowed to embed this activity. From
+ * the "knownActivityEmbeddingCerts" attribute.
+ */
+ @Nullable
+ private Set<String> mKnownActivityEmbeddingCerts;
+
+ /**
* Convert Java change bits to native.
*
* @hide
@@ -1227,6 +1253,7 @@
launchMode = orig.launchMode;
documentLaunchMode = orig.documentLaunchMode;
permission = orig.permission;
+ mKnownActivityEmbeddingCerts = orig.mKnownActivityEmbeddingCerts;
taskAffinity = orig.taskAffinity;
targetActivity = orig.targetActivity;
flags = orig.flags;
@@ -1442,6 +1469,31 @@
return mMinAspectRatio;
}
+ /**
+ * Gets the trusted host certificate digests of apps that are allowed to embed this activity.
+ * The digests are computed using the SHA-256 digest algorithm.
+ * @see android.R.attr#knownActivityEmbeddingCerts
+ */
+ @NonNull
+ public Set<String> getKnownActivityEmbeddingCerts() {
+ return mKnownActivityEmbeddingCerts == null ? Collections.emptySet()
+ : mKnownActivityEmbeddingCerts;
+ }
+
+ /**
+ * Sets the trusted host certificates of apps that are allowed to embed this activity.
+ * @see #getKnownActivityEmbeddingCerts()
+ * @hide
+ */
+ public void setKnownActivityEmbeddingCerts(@NonNull Set<String> knownActivityEmbeddingCerts) {
+ // Convert the provided digest to upper case for consistent Set membership
+ // checks when verifying the signing certificate digests of requesting apps.
+ mKnownActivityEmbeddingCerts = new ArraySet<>();
+ for (String knownCert : knownActivityEmbeddingCerts) {
+ mKnownActivityEmbeddingCerts.add(knownCert.toUpperCase(Locale.US));
+ }
+ }
+
private boolean isChangeEnabled(long changeId) {
return CompatChanges.isChangeEnabled(changeId, applicationInfo.packageName,
UserHandle.getUserHandleForUid(applicationInfo.uid));
@@ -1573,6 +1625,9 @@
if (supportsSizeChanges) {
pw.println(prefix + "supportsSizeChanges=true");
}
+ if (mKnownActivityEmbeddingCerts != null) {
+ pw.println(prefix + "knownActivityEmbeddingCerts=" + mKnownActivityEmbeddingCerts);
+ }
super.dumpBack(pw, prefix, dumpFlags);
}
@@ -1618,6 +1673,7 @@
dest.writeFloat(mMaxAspectRatio);
dest.writeFloat(mMinAspectRatio);
dest.writeBoolean(supportsSizeChanges);
+ sForStringSet.parcel(mKnownActivityEmbeddingCerts, dest, flags);
}
/**
@@ -1739,6 +1795,10 @@
mMaxAspectRatio = source.readFloat();
mMinAspectRatio = source.readFloat();
supportsSizeChanges = source.readBoolean();
+ mKnownActivityEmbeddingCerts = sForStringSet.unparcel(source);
+ if (mKnownActivityEmbeddingCerts.isEmpty()) {
+ mKnownActivityEmbeddingCerts = null;
+ }
}
/**
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 567f649..2528e16 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -35,6 +35,7 @@
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Printer;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
@@ -52,7 +53,9 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Locale;
import java.util.Objects;
+import java.util.Set;
import java.util.UUID;
/**
@@ -62,6 +65,8 @@
*/
public class ApplicationInfo extends PackageItemInfo implements Parcelable {
private static ForBoolean sForBoolean = Parcelling.Cache.getOrCreate(ForBoolean.class);
+ private static final Parcelling.BuiltIn.ForStringSet sForStringSet =
+ Parcelling.Cache.getOrCreate(Parcelling.BuiltIn.ForStringSet.class);
/**
* Default task affinity of all activities in this application. See
@@ -1550,6 +1555,13 @@
*/
private int localeConfigRes;
+ /**
+ * Optional set of a certificates identifying apps that are allowed to embed activities of this
+ * application. From the "knownActivityEmbeddingCerts" attribute.
+ */
+ @Nullable
+ private Set<String> mKnownActivityEmbeddingCerts;
+
public void dump(Printer pw, String prefix) {
dump(pw, prefix, DUMP_FLAG_ALL);
}
@@ -1673,6 +1685,9 @@
}
}
pw.println(prefix + "createTimestamp=" + createTimestamp);
+ if (mKnownActivityEmbeddingCerts != null) {
+ pw.println(prefix + "knownActivityEmbeddingCerts=" + mKnownActivityEmbeddingCerts);
+ }
super.dumpBack(pw, prefix);
}
@@ -1787,6 +1802,11 @@
}
proto.end(detailToken);
}
+ if (!ArrayUtils.isEmpty(mKnownActivityEmbeddingCerts)) {
+ for (String knownCert : mKnownActivityEmbeddingCerts) {
+ proto.write(ApplicationInfoProto.KNOWN_ACTIVITY_EMBEDDING_CERTS, knownCert);
+ }
+ }
proto.end(token);
}
@@ -1837,6 +1857,7 @@
super(orig);
taskAffinity = orig.taskAffinity;
permission = orig.permission;
+ mKnownActivityEmbeddingCerts = orig.mKnownActivityEmbeddingCerts;
processName = orig.processName;
className = orig.className;
theme = orig.theme;
@@ -2006,6 +2027,7 @@
}
}
dest.writeInt(localeConfigRes);
+ sForStringSet.parcel(mKnownActivityEmbeddingCerts, dest, flags);
}
public static final @android.annotation.NonNull Parcelable.Creator<ApplicationInfo> CREATOR
@@ -2102,6 +2124,10 @@
}
}
localeConfigRes = source.readInt();
+ mKnownActivityEmbeddingCerts = sForStringSet.unparcel(source);
+ if (mKnownActivityEmbeddingCerts.isEmpty()) {
+ mKnownActivityEmbeddingCerts = null;
+ }
}
/**
@@ -2658,7 +2684,6 @@
return localeConfigRes;
}
-
/**
* List of all shared libraries this application is linked against. This
* list will only be set if the {@link PackageManager#GET_SHARED_LIBRARY_FILES
@@ -2675,4 +2700,29 @@
return sharedLibraryInfos;
}
+ /**
+ * Gets the trusted host certificate digests of apps that are allowed to embed activities of
+ * this application. The digests are computed using the SHA-256 digest algorithm.
+ * @see android.R.attr#knownActivityEmbeddingCerts
+ */
+ @NonNull
+ public Set<String> getKnownActivityEmbeddingCerts() {
+ return mKnownActivityEmbeddingCerts == null ? Collections.emptySet()
+ : mKnownActivityEmbeddingCerts;
+ }
+
+ /**
+ * Sets the trusted host certificates of apps that are allowed to embed activities of this
+ * application.
+ * @see #getKnownActivityEmbeddingCerts()
+ * @hide
+ */
+ public void setKnownActivityEmbeddingCerts(@NonNull Set<String> knownActivityEmbeddingCerts) {
+ // Convert the provided digest to upper case for consistent Set membership
+ // checks when verifying the signing certificate digests of requesting apps.
+ mKnownActivityEmbeddingCerts = new ArraySet<>();
+ for (String knownCert : knownActivityEmbeddingCerts) {
+ mKnownActivityEmbeddingCerts.add(knownCert.toUpperCase(Locale.US));
+ }
+ }
}
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index cb8988e..08cfbf7 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -58,7 +58,7 @@
void startActivityAsUser(in IApplicationThread caller, String callingPackage,
String callingFeatureId, in ComponentName component, in Rect sourceBounds,
in Bundle opts, in UserHandle user);
- PendingIntent getActivityLaunchIntent(in ComponentName component, in Bundle opts,
+ PendingIntent getActivityLaunchIntent(String callingPackage, in ComponentName component,
in UserHandle user);
void showAppDetailsAsUser(in IApplicationThread caller, String callingPackage,
String callingFeatureId, in ComponentName component, in Rect sourceBounds,
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index e35c2f4..301d1bbc 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -749,24 +749,29 @@
}
/**
- * Returns a PendingIntent that would start the same activity started from
- * {@link #startMainActivity(ComponentName, UserHandle, Rect, Bundle)}.
+ * Returns a mutable PendingIntent that would start the same activity started from
+ * {@link #startMainActivity(ComponentName, UserHandle, Rect, Bundle)}. The caller needs to
+ * take care in ensuring that the mutable intent returned is not passed to untrusted parties.
*
* @param component The ComponentName of the activity to launch
* @param startActivityOptions This parameter is no longer supported
* @param user The UserHandle of the profile
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS)
@Nullable
public PendingIntent getMainActivityLaunchIntent(@NonNull ComponentName component,
@Nullable Bundle startActivityOptions, @NonNull UserHandle user) {
+ if (mContext.checkSelfPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS)
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.w(TAG, "Only allowed for recents.");
+ }
logErrorForInvalidProfileAccess(user);
if (DEBUG) {
Log.i(TAG, "GetMainActivityLaunchIntent " + component + " " + user);
}
try {
- // due to b/209607104, startActivityOptions will be ignored
- return mService.getActivityLaunchIntent(component, null /* opts */, user);
+ return mService.getActivityLaunchIntent(mContext.getPackageName(), component, user);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ce549c3..4279d07 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4342,6 +4342,21 @@
= "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
/**
+ * Indicates that the package requesting permissions has legacy access for some permissions,
+ * or had it, but it was recently revoked. These request dialogs may show different text,
+ * indicating that the app is requesting continued access to a permission. Will be cleared
+ * from any permission request intent, if set by a non-system server app.
+ * <p>
+ * <strong>Type:</strong> String[]
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES
+ = "android.content.pm.extra.REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES";
+
+ /**
* String extra for {@link PackageInstallObserver} in the 'extras' Bundle in case of
* {@link #INSTALL_FAILED_DUPLICATE_PERMISSION}. This extra names the package which provides
* the existing definition for the permission.
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 7dbfd08..8e8aaf1 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -532,7 +532,7 @@
* app.
*
* <p><b>Note:</b> See also the support library counterpart
- * {@link android.support.v4.content.pm.ShortcutManagerCompat#isRequestPinShortcutSupported(
+ * {@link androidx.core.content.pm.ShortcutManagerCompat#isRequestPinShortcutSupported(
* Context)}, which supports Android versions lower than {@link VERSION_CODES#O} using the
* legacy private intent {@code com.android.launcher.action.INSTALL_SHORTCUT}.
*
@@ -561,7 +561,7 @@
* previous requests.
*
* <p><b>Note:</b> See also the support library counterpart
- * {@link android.support.v4.content.pm.ShortcutManagerCompat#requestPinShortcut(
+ * {@link androidx.core.content.pm.ShortcutManagerCompat#requestPinShortcut(
* Context, ShortcutInfoCompat, IntentSender)},
* which supports Android versions lower than {@link VERSION_CODES#O} using the
* legacy private intent {@code com.android.launcher.action.INSTALL_SHORTCUT}.
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index 515a009..ac1bcf3 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -131,7 +131,8 @@
/** Usage: The buffer will be written to by the GPU */
public static final long USAGE_GPU_COLOR_OUTPUT = 1 << 9;
/**
- * The buffer will be used as a composer HAL overlay layer.
+ * The buffer will be used as a hardware composer overlay layer. That is, it will be displayed
+ * using the system compositor via {@link SurfaceControl}
*
* This flag is currently only needed when using
* {@link android.view.SurfaceControl.Transaction#setBuffer(SurfaceControl, HardwareBuffer)}
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index f67a5b4..8093764 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -264,6 +264,44 @@
public @interface StreamUseCase {};
/**
+ * Automatic mirroring based on camera facing
+ *
+ * <p>This is the default mirroring mode for the camera device. With this mode,
+ * the camera output is mirrored horizontally for front-facing cameras. There is
+ * no mirroring for rear-facing and external cameras.</p>
+ */
+ public static final int MIRROR_MODE_AUTO = 0;
+
+ /**
+ * No mirror transform is applied
+ *
+ * <p>No mirroring is applied to the camera output regardless of the camera facing.</p>
+ */
+ public static final int MIRROR_MODE_NONE = 1;
+
+ /**
+ * Camera output is mirrored horizontally
+ *
+ * <p>The camera output is mirrored horizontally, the same behavior as in AUTO mode for
+ * front facing camera.</p>
+ */
+ public static final int MIRROR_MODE_H = 2;
+
+ /**
+ * Camera output is mirrored vertically
+ */
+ public static final int MIRROR_MODE_V = 3;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"MIRROR_MODE_"}, value =
+ {MIRROR_MODE_AUTO,
+ MIRROR_MODE_NONE,
+ MIRROR_MODE_H,
+ MIRROR_MODE_V})
+ public @interface MirrorMode {};
+
+ /**
* Create a new {@link OutputConfiguration} instance with a {@link Surface}.
*
* @param surface
@@ -461,6 +499,7 @@
mDynamicRangeProfile = DynamicRangeProfiles.STANDARD;
mStreamUseCase = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT;
mTimestampBase = TIMESTAMP_BASE_DEFAULT;
+ mMirrorMode = MIRROR_MODE_AUTO;
}
/**
@@ -945,6 +984,42 @@
}
/**
+ * Set the mirroring mode for this output target
+ *
+ * <p>If this function is not called, the mirroring mode for this output is
+ * {@link #MIRROR_MODE_AUTO}, with which the camera API will mirror the output images
+ * horizontally for front facing camera.</p>
+ *
+ * <p>For efficiency, the mirror effect is applied as a transform flag, so it is only effective
+ * in some outputs. It works automatically for SurfaceView and TextureView outputs. For manual
+ * use of SurfaceTexture, it is reflected in the value of
+ * {@link android.graphics.SurfaceTexture#getTransformMatrix}. For other end points, such as
+ * ImageReader, MediaRecorder, or MediaCodec, the mirror mode has no effect. If mirroring is
+ * needed for such outputs, the application needs to mirror the image buffers itself before
+ * passing them onward.</p>
+ */
+ public void setMirrorMode(@MirrorMode int mirrorMode) {
+ // Verify that the value is in range
+ if (mirrorMode < MIRROR_MODE_AUTO ||
+ mirrorMode > MIRROR_MODE_V) {
+ throw new IllegalArgumentException("Not a valid mirror mode " + mirrorMode);
+ }
+ mMirrorMode = mirrorMode;
+ }
+
+ /**
+ * Get the current mirroring mode
+ *
+ * <p>If no {@link #setMirrorMode} is called first, this function returns
+ * {@link #MIRROR_MODE_AUTO}.</p>
+ *
+ * @return The currently set mirroring mode
+ */
+ public @MirrorMode int getMirrorMode() {
+ return mMirrorMode;
+ }
+
+ /**
* Create a new {@link OutputConfiguration} instance with another {@link OutputConfiguration}
* instance.
*
@@ -973,6 +1048,7 @@
this.mDynamicRangeProfile = other.mDynamicRangeProfile;
this.mStreamUseCase = other.mStreamUseCase;
this.mTimestampBase = other.mTimestampBase;
+ this.mMirrorMode = other.mMirrorMode;
}
/**
@@ -998,6 +1074,8 @@
DynamicRangeProfiles.checkProfileValue(dynamicRangeProfile);
int timestampBase = source.readInt();
+ int mirrorMode = source.readInt();
+
mSurfaceGroupId = surfaceSetId;
mRotation = rotation;
mSurfaces = surfaces;
@@ -1023,6 +1101,7 @@
mDynamicRangeProfile = dynamicRangeProfile;
mStreamUseCase = streamUseCase;
mTimestampBase = timestampBase;
+ mMirrorMode = mirrorMode;
}
/**
@@ -1141,6 +1220,7 @@
dest.writeInt(mDynamicRangeProfile);
dest.writeInt(mStreamUseCase);
dest.writeInt(mTimestampBase);
+ dest.writeInt(mMirrorMode);
}
/**
@@ -1173,7 +1253,8 @@
!Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId) ||
mIsMultiResolution != other.mIsMultiResolution ||
mStreamUseCase != other.mStreamUseCase ||
- mTimestampBase != other.mTimestampBase)
+ mTimestampBase != other.mTimestampBase ||
+ mMirrorMode != other.mMirrorMode)
return false;
if (mSensorPixelModesUsed.size() != other.mSensorPixelModesUsed.size()) {
return false;
@@ -1211,7 +1292,7 @@
mSurfaceGroupId, mSurfaceType, mIsShared ? 1 : 0,
mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(),
mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(),
- mDynamicRangeProfile, mStreamUseCase, mTimestampBase);
+ mDynamicRangeProfile, mStreamUseCase, mTimestampBase, mMirrorMode);
}
return HashCodeHelpers.hashCode(
@@ -1220,7 +1301,8 @@
mConfiguredDataspace, mSurfaceGroupId, mIsShared ? 1 : 0,
mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(),
mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(),
- mDynamicRangeProfile, mStreamUseCase, mTimestampBase);
+ mDynamicRangeProfile, mStreamUseCase, mTimestampBase,
+ mMirrorMode);
}
private static final String TAG = "OutputConfiguration";
@@ -1258,4 +1340,6 @@
private int mStreamUseCase;
// Timestamp base
private int mTimestampBase;
+ // Mirroring mode
+ private int mMirrorMode;
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index eefa1d3..623f38e 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1214,17 +1214,6 @@
}
/**
- * Returns whether the specified display supports DISPLAY_DECORATION.
- *
- * @param displayId The display to query support.
- *
- * @hide
- */
- public boolean getDisplayDecorationSupport(int displayId) {
- return mGlobal.getDisplayDecorationSupport(displayId);
- }
-
- /**
* Returns the user preference for "Match content frame rate".
* <p>
* Never: Even if the app requests it, the device will never try to match its output to the
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index af8ec27..7e7a648 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -32,6 +32,7 @@
import android.graphics.ColorSpace;
import android.graphics.Point;
import android.hardware.display.DisplayManager.DisplayListener;
+import android.hardware.graphics.common.DisplayDecorationSupport;
import android.media.projection.IMediaProjection;
import android.media.projection.MediaProjection;
import android.os.Handler;
@@ -812,13 +813,13 @@
}
/**
- * Report whether the display supports DISPLAY_DECORATION.
+ * Report whether/how the display supports DISPLAY_DECORATION.
*
* @param displayId The display whose support is being queried.
*
* @hide
*/
- public boolean getDisplayDecorationSupport(int displayId) {
+ public DisplayDecorationSupport getDisplayDecorationSupport(int displayId) {
try {
return mDm.getDisplayDecorationSupport(displayId);
} catch (RemoteException ex) {
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index b3af52b..ddd18f4 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -22,6 +22,7 @@
import android.hardware.display.BrightnessConfiguration;
import android.hardware.display.BrightnessInfo;
import android.hardware.display.Curve;
+import android.hardware.graphics.common.DisplayDecorationSupport;
import android.hardware.display.IDisplayManagerCallback;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
@@ -183,5 +184,5 @@
int getRefreshRateSwitchingType();
// Query for DISPLAY_DECORATION support.
- boolean getDisplayDecorationSupport(int displayId);
+ DisplayDecorationSupport getDisplayDecorationSupport(int displayId);
}
diff --git a/core/java/android/hardware/input/VirtualKeyEvent.java b/core/java/android/hardware/input/VirtualKeyEvent.java
index d875156..26d74df 100644
--- a/core/java/android/hardware/input/VirtualKeyEvent.java
+++ b/core/java/android/hardware/input/VirtualKeyEvent.java
@@ -114,9 +114,56 @@
}
/**
- * Sets the Android key code of the event. The set of allowed characters include digits 0-9,
- * characters A-Z, and standard punctuation, as well as numpad keys, function keys F1-F12,
- * and meta keys (caps lock, shift, etc.).
+ * Sets the Android key code of the event. The set of allowed keys include digits
+ * {@link android.view.KeyEvent#KEYCODE_0} through
+ * {@link android.view.KeyEvent#KEYCODE_9}, characters
+ * {@link android.view.KeyEvent#KEYCODE_A} through
+ * {@link android.view.KeyEvent#KEYCODE_Z}, function keys
+ * {@link android.view.KeyEvent#KEYCODE_F1} through
+ * {@link android.view.KeyEvent#KEYCODE_F12}, numpad keys
+ * {@link android.view.KeyEvent#KEYCODE_NUMPAD_0} through
+ * {@link android.view.KeyEvent#KEYCODE_NUMPAD_RIGHT_PAREN},
+ * and these additional keys:
+ * {@link android.view.KeyEvent#KEYCODE_GRAVE}
+ * {@link android.view.KeyEvent#KEYCODE_MINUS}
+ * {@link android.view.KeyEvent#KEYCODE_EQUALS}
+ * {@link android.view.KeyEvent#KEYCODE_LEFT_BRACKET}
+ * {@link android.view.KeyEvent#KEYCODE_RIGHT_BRACKET}
+ * {@link android.view.KeyEvent#KEYCODE_BACKSLASH}
+ * {@link android.view.KeyEvent#KEYCODE_SEMICOLON}
+ * {@link android.view.KeyEvent#KEYCODE_APOSTROPHE}
+ * {@link android.view.KeyEvent#KEYCODE_COMMA}
+ * {@link android.view.KeyEvent#KEYCODE_PERIOD}
+ * {@link android.view.KeyEvent#KEYCODE_SLASH}
+ * {@link android.view.KeyEvent#KEYCODE_ALT_LEFT}
+ * {@link android.view.KeyEvent#KEYCODE_ALT_RIGHT}
+ * {@link android.view.KeyEvent#KEYCODE_CTRL_LEFT}
+ * {@link android.view.KeyEvent#KEYCODE_CTRL_RIGHT}
+ * {@link android.view.KeyEvent#KEYCODE_SHIFT_LEFT}
+ * {@link android.view.KeyEvent#KEYCODE_SHIFT_RIGHT}
+ * {@link android.view.KeyEvent#KEYCODE_META_LEFT}
+ * {@link android.view.KeyEvent#KEYCODE_META_RIGHT}
+ * {@link android.view.KeyEvent#KEYCODE_CAPS_LOCK}
+ * {@link android.view.KeyEvent#KEYCODE_SCROLL_LOCK}
+ * {@link android.view.KeyEvent#KEYCODE_NUM_LOCK}
+ * {@link android.view.KeyEvent#KEYCODE_ENTER}
+ * {@link android.view.KeyEvent#KEYCODE_TAB}
+ * {@link android.view.KeyEvent#KEYCODE_SPACE}
+ * {@link android.view.KeyEvent#KEYCODE_DPAD_DOWN}
+ * {@link android.view.KeyEvent#KEYCODE_DPAD_UP}
+ * {@link android.view.KeyEvent#KEYCODE_DPAD_LEFT}
+ * {@link android.view.KeyEvent#KEYCODE_DPAD_RIGHT}
+ * {@link android.view.KeyEvent#KEYCODE_MOVE_END}
+ * {@link android.view.KeyEvent#KEYCODE_MOVE_HOME}
+ * {@link android.view.KeyEvent#KEYCODE_PAGE_DOWN}
+ * {@link android.view.KeyEvent#KEYCODE_PAGE_UP}
+ * {@link android.view.KeyEvent#KEYCODE_DEL}
+ * {@link android.view.KeyEvent#KEYCODE_FORWARD_DEL}
+ * {@link android.view.KeyEvent#KEYCODE_INSERT}
+ * {@link android.view.KeyEvent#KEYCODE_ESCAPE}
+ * {@link android.view.KeyEvent#KEYCODE_BREAK}
+ * {@link android.view.KeyEvent#KEYCODE_BACK}
+ * {@link android.view.KeyEvent#KEYCODE_FORWARD}
*
* @return this builder, to allow for chaining of calls
*/
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index b0439d0..c363909 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -61,6 +61,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Locale;
import java.util.UUID;
@@ -1576,31 +1577,58 @@
}
/**
- * Additional data conveyed by a {@link KeyphraseRecognitionEvent}
- * for a key phrase detection.
- *
- * @hide
+ * Additional data conveyed by a {@link KeyphraseRecognitionEvent}
+ * for a key phrase detection.
*/
- public static class KeyphraseRecognitionExtra implements Parcelable {
- /** The keyphrase ID */
+ public static final class KeyphraseRecognitionExtra implements Parcelable {
+ /**
+ * The keyphrase ID
+ *
+ * @hide
+ */
@UnsupportedAppUsage
public final int id;
- /** Recognition modes matched for this event */
+ /**
+ * Recognition modes matched for this event
+ *
+ * @hide
+ */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public final int recognitionModes;
- /** Confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER when user identification
- * is not performed */
+ /**
+ * Confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER when user identification
+ * is not performed
+ *
+ * @hide
+ */
@UnsupportedAppUsage
public final int coarseConfidenceLevel;
- /** Confidence levels for all users recognized (KeyphraseRecognitionEvent) or to
- * be recognized (RecognitionConfig) */
+ /**
+ * Confidence levels for all users recognized (KeyphraseRecognitionEvent) or to
+ * be recognized (RecognitionConfig)
+ *
+ * @hide
+ */
@UnsupportedAppUsage
@NonNull
public final ConfidenceLevel[] confidenceLevels;
+
+ /**
+ * @hide
+ */
+ @TestApi
+ public KeyphraseRecognitionExtra(int id, @RecognitionModes int recognitionModes,
+ int coarseConfidenceLevel) {
+ this(id, recognitionModes, coarseConfidenceLevel, new ConfidenceLevel[0]);
+ }
+
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public KeyphraseRecognitionExtra(int id, int recognitionModes, int coarseConfidenceLevel,
@Nullable ConfidenceLevel[] confidenceLevels) {
@@ -1611,7 +1639,47 @@
confidenceLevels != null ? confidenceLevels : new ConfidenceLevel[0];
}
- public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseRecognitionExtra> CREATOR
+ /**
+ * The keyphrase ID associated with this class' additional data
+ */
+ public int getKeyphraseId() {
+ return id;
+ }
+
+ /**
+ * Recognition modes matched for this event
+ */
+ @RecognitionModes
+ public int getRecognitionModes() {
+ return recognitionModes;
+ }
+
+ /**
+ * Confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER when user identification
+ * is not performed
+ *
+ * <p>The confidence level is expressed in percent (0% -100%).
+ */
+ public int getCoarseConfidenceLevel() {
+ return coarseConfidenceLevel;
+ }
+
+ /**
+ * Detected confidence level for users defined in a keyphrase.
+ *
+ * <p>The confidence level is expressed in percent (0% -100%).
+ *
+ * <p>The user ID is derived from the system ID
+ * {@link android.os.UserHandle#getIdentifier()}.
+ *
+ * @hide
+ */
+ @NonNull
+ public Collection<ConfidenceLevel> getConfidenceLevels() {
+ return Arrays.asList(confidenceLevels);
+ }
+
+ public static final @NonNull Parcelable.Creator<KeyphraseRecognitionExtra> CREATOR
= new Parcelable.Creator<KeyphraseRecognitionExtra>() {
public KeyphraseRecognitionExtra createFromParcel(Parcel in) {
return KeyphraseRecognitionExtra.fromParcel(in);
@@ -1632,7 +1700,7 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(id);
dest.writeInt(recognitionModes);
dest.writeInt(coarseConfidenceLevel);
@@ -1657,21 +1725,28 @@
@Override
public boolean equals(@Nullable Object obj) {
- if (this == obj)
+ if (this == obj) {
return true;
- if (obj == null)
+ }
+ if (obj == null) {
return false;
- if (getClass() != obj.getClass())
+ }
+ if (getClass() != obj.getClass()) {
return false;
+ }
KeyphraseRecognitionExtra other = (KeyphraseRecognitionExtra) obj;
- if (!Arrays.equals(confidenceLevels, other.confidenceLevels))
+ if (!Arrays.equals(confidenceLevels, other.confidenceLevels)) {
return false;
- if (id != other.id)
+ }
+ if (id != other.id) {
return false;
- if (recognitionModes != other.recognitionModes)
+ }
+ if (recognitionModes != other.recognitionModes) {
return false;
- if (coarseConfidenceLevel != other.coarseConfidenceLevel)
+ }
+ if (coarseConfidenceLevel != other.coarseConfidenceLevel) {
return false;
+ }
return true;
}
@@ -1715,7 +1790,7 @@
keyphraseExtras != null ? keyphraseExtras : new KeyphraseRecognitionExtra[0];
}
- public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseRecognitionEvent> CREATOR
+ public static final @NonNull Parcelable.Creator<KeyphraseRecognitionEvent> CREATOR
= new Parcelable.Creator<KeyphraseRecognitionEvent>() {
public KeyphraseRecognitionEvent createFromParcel(Parcel in) {
return KeyphraseRecognitionEvent.fromParcelForKeyphrase(in);
diff --git a/core/java/android/inputmethodservice/InkWindow.java b/core/java/android/inputmethodservice/InkWindow.java
index 83b053a..499634a 100644
--- a/core/java/android/inputmethodservice/InkWindow.java
+++ b/core/java/android/inputmethodservice/InkWindow.java
@@ -102,11 +102,4 @@
lp.token = token;
setAttributes(lp);
}
-
- /**
- * Returns {@code true} if Window was created and added to WM.
- */
- boolean isInitialized() {
- return mIsViewAdded;
- }
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index b6e1b1f..adf2759 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -578,6 +578,7 @@
private boolean mImeSurfaceScheduledForRemoval;
private ImsConfigurationTracker mConfigTracker = new ImsConfigurationTracker();
private boolean mDestroyed;
+ private boolean mOnPreparedStylusHwCalled;
/** Stylus handwriting Ink window. */
private InkWindow mInkWindow;
@@ -919,9 +920,10 @@
Log.d(TAG, "Input should have started before starting Stylus handwriting.");
return;
}
- if (!mInkWindow.isInitialized()) {
+ if (!mOnPreparedStylusHwCalled) {
// prepare hasn't been called by Stylus HOVER.
onPrepareStylusHandwriting();
+ mOnPreparedStylusHwCalled = true;
}
if (onStartStylusHandwriting()) {
mPrivOps.onStylusHandwritingReady(requestId);
@@ -976,6 +978,7 @@
public void initInkWindow() {
mInkWindow.initOnly();
onPrepareStylusHandwriting();
+ mOnPreparedStylusHwCalled = true;
}
/**
@@ -2354,7 +2357,7 @@
/**
* Called to prepare stylus handwriting.
- * The system calls this before the first {@link #onStartStylusHandwriting} request.
+ * The system calls this before the {@link #onStartStylusHandwriting} request.
*
* <p>Note: The system tries to call this as early as possible, when it detects that
* handwriting stylus input is imminent. However, that a subsequent call to
@@ -2438,6 +2441,7 @@
mInkWindow.hide(false /* remove */);
mPrivOps.finishStylusHandwriting(requestId);
+ mOnPreparedStylusHwCalled = false;
onFinishStylusHandwriting();
}
diff --git a/core/java/android/inputmethodservice/Keyboard.java b/core/java/android/inputmethodservice/Keyboard.java
index c85fcd9..59c9c50 100644
--- a/core/java/android/inputmethodservice/Keyboard.java
+++ b/core/java/android/inputmethodservice/Keyboard.java
@@ -217,6 +217,7 @@
rowEdgeFlags = a.getInt(com.android.internal.R.styleable.Keyboard_Row_rowEdgeFlags, 0);
mode = a.getResourceId(com.android.internal.R.styleable.Keyboard_Row_keyboardMode,
0);
+ a.recycle();
}
}
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index b780b21..af63dd3 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -326,9 +326,11 @@
}
}
+ a.recycle();
a = mContext.obtainStyledAttributes(
com.android.internal.R.styleable.Theme);
mBackgroundDimAmount = a.getFloat(android.R.styleable.Theme_backgroundDimAmount, 0.5f);
+ a.recycle();
mPreviewPopup = new PopupWindow(context);
if (previewLayout != 0) {
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index d8f098e..18ec8f5 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -167,6 +167,8 @@
public static final String FIREWALL_CHAIN_NAME_POWERSAVE = "powersave";
/** @hide */
public static final String FIREWALL_CHAIN_NAME_RESTRICTED = "restricted";
+ /** @hide */
+ public static final String FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY = "low_power_standby";
private static final boolean ALLOW_PLATFORM_APP_POLICY = true;
@@ -174,6 +176,9 @@
public static final int FOREGROUND_THRESHOLD_STATE =
ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+ /** @hide */
+ public static final int TOP_THRESHOLD_STATE = ActivityManager.PROCESS_STATE_BOUND_TOP;
+
/**
* {@link Intent} extra that indicates which {@link NetworkTemplate} rule it
* applies to.
@@ -245,6 +250,20 @@
*/
public static final int ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS = 1 << 4;
/**
+ * Flag to indicate that app is exempt from certain network restrictions because of it being
+ * in the bound top or top procstate.
+ *
+ * @hide
+ */
+ public static final int ALLOWED_REASON_TOP = 1 << 5;
+ /**
+ * Flag to indicate that app is exempt from low power standby restrictions because of it being
+ * allowlisted.
+ *
+ * @hide
+ */
+ public static final int ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST = 1 << 6;
+ /**
* Flag to indicate that app is exempt from certain metered network restrictions because user
* explicitly exempted it.
*
@@ -750,6 +769,14 @@
|| (capability & ActivityManager.PROCESS_CAPABILITY_NETWORK) != 0;
}
+ /** @hide */
+ public static boolean isProcStateAllowedWhileInLowPowerStandby(@Nullable UidState uidState) {
+ if (uidState == null) {
+ return false;
+ }
+ return uidState.procState <= TOP_THRESHOLD_STATE;
+ }
+
/**
* Returns true if {@param procState} is considered foreground and as such will be allowed
* to access network when the device is in data saver mode. Otherwise, false.
diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
index 5aad997..779d931 100644
--- a/core/java/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -24,6 +24,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.UserIdInt;
import android.app.Activity;
@@ -52,7 +53,7 @@
* app (unlike VpnService).
*
* <p>VPN apps using supported protocols should preferentially use this API over the {@link
- * VpnService} API for ease-of-development and reduced maintainance burden. This also give the user
+ * VpnService} API for ease-of-development and reduced maintenance burden. This also give the user
* the guarantee that VPN network traffic is not subjected to on-device packet interception.
*
* @see Ikev2VpnProfile
@@ -97,130 +98,173 @@
public static final String NOTIFICATION_CHANNEL_VPN = "VPN";
/**
- * Action sent in the intent when an error occurred.
+ * Action sent in {@link android.content.Intent}s to VpnManager clients when an event occurred.
*
- * @hide
+ * This action will have a category of either {@link #CATEGORY_EVENT_IKE_ERROR},
+ * {@link #CATEGORY_EVENT_NETWORK_ERROR}, or {@link #CATEGORY_EVENT_DEACTIVATED_BY_USER},
+ * that the app can use to filter events it's interested in reacting to.
+ *
+ * It will also contain the following extras :
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_KEY}, a {@code String} for the session key, as returned by
+ * {@link #startProvisionedVpnProfileSession}.
+ * <li>{@link #EXTRA_TIMESTAMP}, a long for the timestamp at which the error occurred,
+ * in milliseconds since the epoch, as returned by
+ * {@link java.lang.System#currentTimeMillis}.
+ * <li>{@link #EXTRA_UNDERLYING_NETWORK}, a {@link Network} containing the underlying
+ * network at the time the error occurred, or null if none. Note that this network
+ * may have disconnected already.
+ * <li>{@link #EXTRA_UNDERLYING_NETWORK_CAPABILITIES}, a {@link NetworkCapabilities} for
+ * the underlying network at the time the error occurred.
+ * <li>{@link #EXTRA_UNDERLYING_LINK_PROPERTIES}, a {@link LinkProperties} for the underlying
+ * network at the time the error occurred.
+ * </ul>
+ * When this event is an error, either {@link #CATEGORY_EVENT_IKE_ERROR} or
+ * {@link #CATEGORY_EVENT_NETWORK_ERROR}, the following extras will be populated :
+ * <ul>
+ * <li>{@link #EXTRA_ERROR_CLASS}, an {@code int} for the class of error, either
+ * {@link #ERROR_CLASS_RECOVERABLE} or {@link #ERROR_CLASS_NOT_RECOVERABLE}.
+ * <li>{@link #EXTRA_ERROR_CODE}, an {@code int} error code specific to the error. See
+ * {@link #EXTRA_ERROR_CODE} for details.
+ * </ul>
*/
- public static final String ACTION_VPN_MANAGER_ERROR = "android.net.action.VPN_MANAGER_ERROR";
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String ACTION_VPN_MANAGER_EVENT = "android.net.action.VPN_MANAGER_EVENT";
/**
- * An IKE protocol error. Codes are the codes from IkeProtocolException, RFC 7296.
+ * An IKE protocol error occurred.
*
- * @hide
+ * Codes (in {@link #EXTRA_ERROR_CODE}) are the codes from
+ * {@link android.net.ipsec.ike.exceptions.IkeProtocolException}, as defined by IANA in
+ * "IKEv2 Notify Message Types - Error Types".
*/
- public static final String CATEGORY_ERROR_IKE = "android.net.category.ERROR_IKE";
+ @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_EVENT_IKE_ERROR = "android.net.category.EVENT_IKE_ERROR";
/**
- * User deactivated the VPN, either by turning it off or selecting a different VPN provider.
- * The error code is always 0.
+ * A network error occurred.
*
- * @hide
+ * Error codes (in {@link #EXTRA_ERROR_CODE}) are ERROR_CODE_NETWORK_*.
*/
- public static final String CATEGORY_ERROR_USER_DEACTIVATED =
- "android.net.category.ERROR_USER_DEACTIVATED";
+ @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_EVENT_NETWORK_ERROR =
+ "android.net.category.EVENT_NETWORK_ERROR";
/**
- * Network error. Error codes are ERROR_CODE_NETWORK_*.
+ * The user deactivated the VPN.
*
- * @hide
+ * This can happen either when the user turns the VPN off explicitly, or when they select
+ * a different VPN provider.
*/
- public static final String CATEGORY_ERROR_NETWORK = "android.net.category.ERROR_NETWORK";
+ @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_EVENT_DEACTIVATED_BY_USER =
+ "android.net.category.EVENT_DEACTIVATED_BY_USER";
/**
- * The key of the session that experienced this error, as returned by
- * startProvisionedVpnProfileSession.
+ * The key of the session that experienced this event, as a {@code String}.
*
- * @hide
+ * This is the same key that was returned by {@link #startProvisionedVpnProfileSession}.
*/
public static final String EXTRA_SESSION_KEY = "android.net.extra.SESSION_KEY";
/**
- * Extra for the Network object that was the underlying network at the time of the failure, or
- * null if none.
+ * The network that was underlying the VPN when the event occurred, as a {@link Network}.
*
- * @hide
+ * This extra will be null if there was no underlying network at the time of the event.
*/
public static final String EXTRA_UNDERLYING_NETWORK = "android.net.extra.UNDERLYING_NETWORK";
/**
- * The NetworkCapabilities of the underlying network.
+ * The {@link NetworkCapabilities} of the underlying network when the event occurred.
*
- * @hide
+ * This extra will be null if there was no underlying network at the time of the event.
*/
public static final String EXTRA_UNDERLYING_NETWORK_CAPABILITIES =
"android.net.extra.UNDERLYING_NETWORK_CAPABILITIES";
/**
- * The LinkProperties of the underlying network.
+ * The {@link LinkProperties} of the underlying network when the event occurred.
*
- * @hide
+ * This extra will be null if there was no underlying network at the time of the event.
*/
public static final String EXTRA_UNDERLYING_LINK_PROPERTIES =
"android.net.extra.UNDERLYING_LINK_PROPERTIES";
/**
- * A long timestamp with SystemClock.elapsedRealtime base for when the event happened.
+ * A {@code long} timestamp containing the time at which the event occurred.
*
- * @hide
+ * This is a number of milliseconds since the epoch, suitable to be compared with
+ * {@link java.lang.System#currentTimeMillis}.
*/
public static final String EXTRA_TIMESTAMP = "android.net.extra.TIMESTAMP";
/**
- * Extra for the error type. This is ERROR_NOT_RECOVERABLE or ERROR_RECOVERABLE.
+ * Extra for the error class, as an {@code int}.
*
- * @hide
+ * This is always either {@link #ERROR_CLASS_NOT_RECOVERABLE} or
+ * {@link #ERROR_CLASS_RECOVERABLE}. This extra is only populated for error categories.
*/
- public static final String EXTRA_ERROR_TYPE = "android.net.extra.ERROR_TYPE";
+ public static final String EXTRA_ERROR_CLASS = "android.net.extra.ERROR_CLASS";
/**
- * Extra for the error code. The value will be 0 for CATEGORY_ERROR_USER_DEACTIVATED, one of
- * ERROR_CODE_NETWORK_* for ERROR_CATEGORY_NETWORK or one of values defined in
- * IkeProtocolException#ErrorType for CATEGORY_ERROR_IKE.
+ * Extra for an error code, as an {@code int}.
*
- * @hide
+ * <ul>
+ * <li>For {@link #CATEGORY_EVENT_NETWORK_ERROR}, this is one of the
+ * {@code ERROR_CODE_NETWORK_*} constants.
+ * <li>For {@link #CATEGORY_EVENT_IKE_ERROR}, this is one of values defined in
+ * {@link android.net.ipsec.ike.exceptions.IkeProtocolException}.ERROR_TYPE_*.
+ * </ul>
+ * For non-error categories, this extra is not populated.
*/
public static final String EXTRA_ERROR_CODE = "android.net.extra.ERROR_CODE";
/**
- * This error is fatal, e.g. the VPN was disabled or configuration error. The stack will not
- * retry connection.
+ * {@link #EXTRA_ERROR_CLASS} coding for a non-recoverable error.
*
- * @hide
+ * This error is fatal, e.g. configuration error. The stack will not retry connection.
*/
- public static final int ERROR_NOT_RECOVERABLE = 1;
+ public static final int ERROR_CLASS_NOT_RECOVERABLE = 1;
/**
+ * {@link #EXTRA_ERROR_CLASS} coding for a recoverable error.
+ *
* The stack experienced an error but will retry with exponential backoff, e.g. network timeout.
- *
- * @hide
*/
- public static final int ERROR_RECOVERABLE = 2;
+ public static final int ERROR_CLASS_RECOVERABLE = 2;
/**
- * An error code to indicate that there was an UnknownHostException.
+ * An {@link #EXTRA_ERROR_CODE} for {@link #CATEGORY_EVENT_NETWORK_ERROR} to indicate that the
+ * network host isn't known.
*
- * @hide
+ * This happens when domain name resolution could not resolve an IP address for the
+ * specified host. {@see java.net.UnknownHostException}
*/
public static final int ERROR_CODE_NETWORK_UNKNOWN_HOST = 0;
/**
- * An error code to indicate that there is a SocketTimeoutException.
+ * An {@link #EXTRA_ERROR_CODE} for {@link #CATEGORY_EVENT_NETWORK_ERROR} indicating a timeout.
*
- * @hide
+ * For Ikev2 VPNs, this happens typically after a retransmission failure.
+ * {@see android.net.ipsec.ike.exceptions.IkeTimeoutException}
*/
- public static final int ERROR_CODE_NETWORK_TIMEOUT = 1;
+ public static final int ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT = 1;
/**
- * An error code to indicate the connection was reset. (e.g. SocketException)
+ * An {@link #EXTRA_ERROR_CODE} for {@link #CATEGORY_EVENT_NETWORK_ERROR} indicating that
+ * network connectivity was lost.
*
- * @hide
+ * The most common reason for this error is that the underlying network was disconnected,
+ * {@see android.net.ipsec.ike.exceptions.IkeNetworkLostException}.
*/
- public static final int ERROR_CODE_NETWORK_RESET = 2;
+ public static final int ERROR_CODE_NETWORK_LOST = 2;
/**
- * An error code to indicate that there is an IOException.
+ * An {@link #EXTRA_ERROR_CODE} for {@link #CATEGORY_EVENT_NETWORK_ERROR} indicating an
+ * input/output error.
*
- * @hide
+ * This code happens when reading or writing to sockets on the underlying networks was
+ * terminated by an I/O error. {@see IOException}.
*/
public static final int ERROR_CODE_NETWORK_IO = 3;
diff --git a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
index 9772bde..2dd3aaa1 100644
--- a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
+++ b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
@@ -24,6 +24,7 @@
import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
import static android.net.NetworkStats.SET_DEFAULT;
import static android.net.NetworkStats.TAG_NONE;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import android.annotation.NonNull;
import android.annotation.SystemApi;
@@ -108,6 +109,7 @@
static final int VERSION_ADD_METERED = 4;
static final int VERSION_ADD_DEFAULT_NETWORK = 5;
static final int VERSION_ADD_OEM_MANAGED_NETWORK = 6;
+ static final int VERSION_ADD_SUB_ID = 7;
}
/**
@@ -448,6 +450,13 @@
oemNetCapabilities = NetworkTemplate.OEM_MANAGED_NO;
}
+ final int subId;
+ if (version >= IdentitySetVersion.VERSION_ADD_SUB_ID) {
+ subId = in.readInt();
+ } else {
+ subId = INVALID_SUBSCRIPTION_ID;
+ }
+
// Legacy files might contain TYPE_MOBILE_* types which were deprecated in later
// releases. For backward compatibility, record them as TYPE_MOBILE instead.
final int collapsedLegacyType = getCollapsedLegacyType(type);
@@ -457,7 +466,8 @@
.setWifiNetworkKey(networkId)
.setRoaming(roaming).setMetered(metered)
.setDefaultNetwork(defaultNetwork)
- .setOemManaged(oemNetCapabilities);
+ .setOemManaged(oemNetCapabilities)
+ .setSubId(subId);
if (type == TYPE_MOBILE && ratType != NetworkTemplate.NETWORK_TYPE_ALL) {
builder.setRatType(ratType);
}
@@ -501,10 +511,10 @@
* This is copied from {@code NetworkStatsCollection#readLegacyUid}.
* See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}.
*
- * @param taggedData whether to read tagged data. For legacy uid files, the tagged
- * data was stored in the same binary file with non-tagged data.
- * But in later releases, these data should be kept in different
- * recorders.
+ * @param taggedData whether to read only tagged data (true) or only non-tagged data
+ * (false). For legacy uid files, the tagged data was stored in
+ * the same binary file with non-tagged data. But in later releases,
+ * these data should be kept in different recorders.
* @hide
*/
@VisibleForTesting
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 9970641..1d39089 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -401,7 +401,12 @@
/**
* All known codenames starting from {@link VERSION_CODES.Q}.
*
- * <p>This includes in development codenames as well.
+ * <p>This includes in development codenames as well, i.e. if {@link #CODENAME} is not "REL"
+ * then the value of that is present in this set.
+ *
+ * <p>If a particular string is not present in this set, then it is either not a codename
+ * or a codename for a future release. For example, during Android R development, "Tiramisu"
+ * was not a known codename.
*
* @hide
*/
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 0257408..3d12941 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -1333,7 +1333,7 @@
final Context context = AppGlobals.getInitialApplication();
final int uid = context.getApplicationInfo().uid;
// Isolated processes and Instant apps are never allowed to be in scoped storage
- if (Process.isIsolated(uid)) {
+ if (Process.isIsolated(uid) || Process.isSupplemental(uid)) {
return false;
}
diff --git a/core/java/android/os/FileUriExposedException.java b/core/java/android/os/FileUriExposedException.java
index e47abe2..a3af24d 100644
--- a/core/java/android/os/FileUriExposedException.java
+++ b/core/java/android/os/FileUriExposedException.java
@@ -35,7 +35,7 @@
* or higher. Applications targeting earlier SDK versions are allowed to share
* {@code file://} {@link android.net.Uri}, but it's strongly discouraged.
*
- * @see android.support.v4.content.FileProvider
+ * @see androidx.core.content.FileProvider
* @see Intent#FLAG_GRANT_READ_URI_PERMISSION
*/
public class FileUriExposedException extends RuntimeException {
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 2fe0622..17b5ec5 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -929,6 +929,7 @@
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
+ @TestApi
public static final int toSupplementalUid(int uid) {
return uid + (FIRST_SUPPLEMENTAL_UID - FIRST_APPLICATION_UID);
}
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 70aaa5e..412a33a 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -942,7 +942,7 @@
* <p>Instead, apps should use {@code content://} Uris so the platform can extend
* temporary permission for the receiving app to access the resource.
*
- * @see android.support.v4.content.FileProvider
+ * @see androidx.core.content.FileProvider
* @see Intent#FLAG_GRANT_READ_URI_PERMISSION
*/
public @NonNull Builder detectFileUriExposure() {
diff --git a/core/java/android/os/logcat/ILogcatManagerService.aidl b/core/java/android/os/logcat/ILogcatManagerService.aidl
index 68b5679..02db274 100644
--- a/core/java/android/os/logcat/ILogcatManagerService.aidl
+++ b/core/java/android/os/logcat/ILogcatManagerService.aidl
@@ -22,5 +22,7 @@
interface ILogcatManagerService {
void startThread(in int uid, in int gid, in int pid, in int fd);
void finishThread(in int uid, in int gid, in int pid, in int fd);
+ void approve(in int uid, in int gid, in int pid, in int fd);
+ void decline(in int uid, in int gid, in int pid, in int fd);
}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 12fa0dd..fc7ac11 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -16,6 +16,12 @@
package android.permission;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.os.Build.VERSION_CODES.S;
import android.Manifest;
@@ -107,6 +113,16 @@
public static final int PERMISSION_HARD_DENIED = 2;
/**
+ * The set of flags that indicate that a permission state has been explicitly set
+ *
+ * @hide
+ */
+ public static final int EXPLICIT_SET_FLAGS = FLAG_PERMISSION_USER_SET
+ | FLAG_PERMISSION_USER_FIXED | FLAG_PERMISSION_POLICY_FIXED
+ | FLAG_PERMISSION_SYSTEM_FIXED | FLAG_PERMISSION_GRANTED_BY_DEFAULT
+ | FLAG_PERMISSION_GRANTED_BY_ROLE;
+
+ /**
* Activity action: Launch UI to review permission decisions.
* <p>
* <strong>Important:</strong>You must protect the activity that handles this action with the
@@ -1447,6 +1463,7 @@
* @hide
*/
@TestApi
+ @RequiresPermission(Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL)
public void revokePostNotificationPermissionWithoutKillForTest(@NonNull String packageName,
int userId) {
try {
diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java
index 22399f5..bbee48b 100644
--- a/core/java/android/preference/PreferenceFragment.java
+++ b/core/java/android/preference/PreferenceFragment.java
@@ -142,7 +142,7 @@
* switch to a new fragment.
*
* @deprecated Use {@link
- * android.support.v7.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback}
+ * androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback}
*/
@Deprecated
public interface OnPreferenceStartFragmentCallback {
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index bb1f393..3459172 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -31,6 +31,11 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
@@ -39,9 +44,11 @@
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.util.AttributeSet;
import android.util.Log;
import android.util.MathUtils;
import android.util.Slog;
+import android.util.Xml;
import android.view.ActionMode;
import android.view.Display;
import android.view.KeyEvent;
@@ -57,9 +64,14 @@
import android.view.WindowManager.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
+import com.android.internal.R;
import com.android.internal.util.DumpUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.FileDescriptor;
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.function.Consumer;
@@ -159,8 +171,9 @@
* </pre>
*/
public class DreamService extends Service implements Window.Callback {
- private final String mTag =
- DreamService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]";
+ private static final String TAG = DreamService.class.getSimpleName();
+ private final String mTag = TAG + "[" + getClass().getSimpleName() + "]";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
/**
* The name of the dream manager service.
@@ -191,6 +204,11 @@
public static final String DREAM_META_DATA = "android.service.dream";
/**
+ * Name of the root tag under which a Dream defines its metadata in an XML file.
+ */
+ private static final String DREAM_META_DATA_ROOT_TAG = "dream";
+
+ /**
* Extra containing a boolean for whether to show complications on the overlay.
* @hide
*/
@@ -239,13 +257,16 @@
mRequests = new ArrayDeque<>();
}
- public void bind(Context context, @Nullable ComponentName overlayService) {
+ public void bind(Context context, @Nullable ComponentName overlayService,
+ ComponentName dreamService) {
if (overlayService == null) {
return;
}
final Intent overlayIntent = new Intent();
overlayIntent.setComponent(overlayService);
+ overlayIntent.putExtra(EXTRA_SHOW_COMPLICATIONS,
+ fetchShouldShowComplications(context, dreamService));
context.bindService(overlayIntent,
this, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE);
@@ -967,7 +988,8 @@
// Connect to the overlay service if present.
if (!mWindowless) {
- mOverlayConnection.bind(this, intent.getParcelableExtra(EXTRA_DREAM_OVERLAY_COMPONENT));
+ mOverlayConnection.bind(this, intent.getParcelableExtra(EXTRA_DREAM_OVERLAY_COMPONENT),
+ new ComponentName(this, getClass()));
}
return mDreamServiceWrapper;
@@ -1081,6 +1103,86 @@
// end public api
/**
+ * Parses and returns metadata of the dream service indicated by the service info. Returns null
+ * if metadata cannot be found.
+ *
+ * Note that {@link ServiceInfo} must be fetched with {@link PackageManager#GET_META_DATA} flag.
+ *
+ * @hide
+ */
+ @Nullable
+ public static DreamMetadata getDreamMetadata(Context context, ServiceInfo serviceInfo) {
+ final PackageManager pm = context.getPackageManager();
+
+ final TypedArray rawMetadata = readMetadata(pm, serviceInfo);
+ if (rawMetadata == null) return null;
+
+ final DreamMetadata metadata = new DreamMetadata(
+ convertToComponentName(rawMetadata.getString(
+ com.android.internal.R.styleable.Dream_settingsActivity), serviceInfo),
+ rawMetadata.getDrawable(
+ com.android.internal.R.styleable.Dream_previewImage),
+ rawMetadata.getBoolean(R.styleable.Dream_showClockAndComplications,
+ DEFAULT_SHOW_COMPLICATIONS));
+ rawMetadata.recycle();
+ return metadata;
+ }
+
+ /**
+ * Returns the raw XML metadata fetched from the {@link ServiceInfo}.
+ *
+ * Returns <code>null</code> if the {@link ServiceInfo} doesn't contain valid dream metadata.
+ */
+ @Nullable
+ private static TypedArray readMetadata(PackageManager pm, ServiceInfo serviceInfo) {
+ if (serviceInfo == null || serviceInfo.metaData == null) {
+ return null;
+ }
+
+ try (XmlResourceParser parser =
+ serviceInfo.loadXmlMetaData(pm, DreamService.DREAM_META_DATA)) {
+ if (parser == null) {
+ if (DEBUG) Log.w(TAG, "No " + DreamService.DREAM_META_DATA + " metadata");
+ return null;
+ }
+
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+ while (true) {
+ final int type = parser.next();
+ if (type == XmlPullParser.END_DOCUMENT || type == XmlPullParser.START_TAG) {
+ break;
+ }
+ }
+
+ if (!parser.getName().equals(DREAM_META_DATA_ROOT_TAG)) {
+ if (DEBUG) {
+ Log.w(TAG, "Metadata does not start with " + DREAM_META_DATA_ROOT_TAG + " tag");
+ }
+ return null;
+ }
+
+ return pm.getResourcesForApplication(serviceInfo.applicationInfo).obtainAttributes(
+ attrs, com.android.internal.R.styleable.Dream);
+ } catch (PackageManager.NameNotFoundException | IOException | XmlPullParserException e) {
+ if (DEBUG) Log.e(TAG, "Error parsing: " + serviceInfo.packageName, e);
+ return null;
+ }
+ }
+
+ private static ComponentName convertToComponentName(String flattenedString,
+ ServiceInfo serviceInfo) {
+ if (flattenedString == null) {
+ return null;
+ }
+
+ if (!flattenedString.contains("/")) {
+ return new ComponentName(serviceInfo.packageName, flattenedString);
+ }
+
+ return ComponentName.unflattenFromString(flattenedString);
+ }
+
+ /**
* Called by DreamController.stopDream() when the Dream is about to be unbound and destroyed.
*
* Must run on mHandler.
@@ -1242,6 +1344,30 @@
return (oldFlags&~mask) | (flags&mask);
}
+ /**
+ * Fetches metadata of the dream indicated by the {@link ComponentName}, and returns whether
+ * the dream should show complications on the overlay. If not defined, returns
+ * {@link DreamService#DEFAULT_SHOW_COMPLICATIONS}.
+ */
+ private static boolean fetchShouldShowComplications(Context context,
+ ComponentName componentName) {
+ final PackageManager pm = context.getPackageManager();
+
+ try {
+ final ServiceInfo si = pm.getServiceInfo(componentName,
+ PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
+ final DreamMetadata metadata = getDreamMetadata(context, si);
+
+ if (metadata != null) {
+ return metadata.showComplications;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ if (DEBUG) Log.w(TAG, "cannot find component " + componentName.flattenToShortString());
+ }
+
+ return DEFAULT_SHOW_COMPLICATIONS;
+ }
+
@Override
protected void dump(final FileDescriptor fd, PrintWriter pw, final String[] args) {
DumpUtils.dumpAsync(mHandler, (pw1, prefix) -> dumpOnHandler(fd, pw1, args), pw, "", 1000);
@@ -1302,4 +1428,27 @@
onWindowCreated(a.getWindow());
}
}
+
+ /**
+ * Represents metadata defined in {@link android.R.styleable#Dream <dream>}.
+ *
+ * @hide
+ */
+ public static final class DreamMetadata {
+ @Nullable
+ public final ComponentName settingsActivity;
+
+ @Nullable
+ public final Drawable previewImage;
+
+ @NonNull
+ public final boolean showComplications;
+
+ DreamMetadata(ComponentName settingsActivity, Drawable previewImage,
+ boolean showComplications) {
+ this.settingsActivity = settingsActivity;
+ this.previewImage = previewImage;
+ this.showComplications = showComplications;
+ }
+ }
}
diff --git a/core/java/android/service/voice/AbstractHotwordDetector.java b/core/java/android/service/voice/AbstractHotwordDetector.java
index 1922607..01d5638 100644
--- a/core/java/android/service/voice/AbstractHotwordDetector.java
+++ b/core/java/android/service/voice/AbstractHotwordDetector.java
@@ -18,6 +18,7 @@
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
@@ -34,6 +35,9 @@
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVoiceInteractionManagerService;
+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();
@@ -45,6 +49,8 @@
private final Handler mHandler;
private final HotwordDetector.Callback mCallback;
private final int mDetectorType;
+ private Consumer<AbstractHotwordDetector> mOnDestroyListener;
+ private final AtomicBoolean mIsDetectorActive;
AbstractHotwordDetector(
IVoiceInteractionManagerService managerService,
@@ -55,6 +61,7 @@
mHandler = new Handler(Looper.getMainLooper());
mCallback = callback;
mDetectorType = detectorType;
+ mIsDetectorActive = new AtomicBoolean(true);
}
/**
@@ -70,6 +77,7 @@
if (DEBUG) {
Slog.i(TAG, "#recognizeHotword");
}
+ throwIfDetectorIsNoLongerActive();
// TODO: consider closing existing session.
@@ -106,6 +114,7 @@
if (DEBUG) {
Slog.d(TAG, "updateState()");
}
+ throwIfDetectorIsNoLongerActive();
synchronized (mLock) {
updateStateLocked(options, sharedMemory, null /* callback */, mDetectorType);
}
@@ -126,6 +135,35 @@
}
}
+ void registerOnDestroyListener(Consumer<AbstractHotwordDetector> onDestroyListener) {
+ synchronized (mLock) {
+ if (mOnDestroyListener != null) {
+ throw new IllegalStateException("only one destroy listener can be registered");
+ }
+ mOnDestroyListener = onDestroyListener;
+ }
+ }
+
+ @CallSuper
+ @Override
+ public void destroy() {
+ if (!mIsDetectorActive.get()) {
+ return;
+ }
+ mIsDetectorActive.set(false);
+ synchronized (mLock) {
+ mOnDestroyListener.accept(this);
+ }
+ }
+
+ protected void throwIfDetectorIsNoLongerActive() {
+ if (!mIsDetectorActive.get()) {
+ Slog.e(TAG, "attempting to use a destroyed detector which is no longer active");
+ throw new IllegalStateException(
+ "attempting to use a destroyed detector which is no longer active");
+ }
+ }
+
private static class BinderCallback
extends IMicrophoneHotwordDetectionVoiceInteractionCallback.Stub {
private final Handler mHandler;
@@ -146,7 +184,10 @@
mHandler.sendMessage(obtainMessage(
HotwordDetector.Callback::onDetected,
mCallback,
- new AlwaysOnHotwordDetector.EventPayload(audioFormat, hotwordDetectedResult)));
+ new AlwaysOnHotwordDetector.EventPayload.Builder()
+ .setCaptureAudioFormat(audioFormat)
+ .setHotwordDetectedResult(hotwordDetectedResult)
+ .build()));
}
}
}
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index c9daf52..bec5d1b 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -23,6 +23,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.ActivityThread;
@@ -59,6 +60,9 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import java.util.Locale;
/**
@@ -336,7 +340,39 @@
* Additional payload for {@link Callback#onDetected}.
*/
public static class EventPayload {
- private final boolean mTriggerAvailable;
+
+ /**
+ * Flags for describing the data format provided in the event payload.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"DATA_FORMAT_"}, value = {
+ DATA_FORMAT_RAW,
+ DATA_FORMAT_TRIGGER_AUDIO,
+ })
+ public @interface DataFormat {
+ }
+
+ /**
+ * Data format is not strictly defined by the framework, and the
+ * {@link android.hardware.soundtrigger.SoundTriggerModule} voice engine may populate this
+ * field in any format.
+ */
+ public static final int DATA_FORMAT_RAW = 0;
+
+ /**
+ * Data format is defined as trigger audio.
+ *
+ * <p>When this format is used, {@link #getCaptureAudioFormat()} can be used to understand
+ * further the audio format for reading the data.
+ *
+ * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
+ */
+ public static final int DATA_FORMAT_TRIGGER_AUDIO = 1;
+
+ @DataFormat
+ private final int mDataFormat;
// Indicates if {@code captureSession} can be used to continue capturing more audio
// from the DSP hardware.
private final boolean mCaptureAvailable;
@@ -348,40 +384,24 @@
private final byte[] mData;
private final HotwordDetectedResult mHotwordDetectedResult;
private final ParcelFileDescriptor mAudioStream;
+ private final List<KeyphraseRecognitionExtra> mKephraseExtras;
- EventPayload(boolean triggerAvailable, boolean captureAvailable,
- AudioFormat audioFormat, int captureSession, byte[] data) {
- this(triggerAvailable, captureAvailable, audioFormat, captureSession, data, null,
- null);
- }
-
- EventPayload(boolean triggerAvailable, boolean captureAvailable,
- AudioFormat audioFormat, int captureSession, byte[] data,
- HotwordDetectedResult hotwordDetectedResult) {
- this(triggerAvailable, captureAvailable, audioFormat, captureSession, data,
- hotwordDetectedResult, null);
- }
-
- EventPayload(AudioFormat audioFormat, HotwordDetectedResult hotwordDetectedResult) {
- this(false, false, audioFormat, -1, null, hotwordDetectedResult, null);
- }
-
- EventPayload(AudioFormat audioFormat,
- HotwordDetectedResult hotwordDetectedResult,
- ParcelFileDescriptor audioStream) {
- this(false, false, audioFormat, -1, null, hotwordDetectedResult, audioStream);
- }
-
- private EventPayload(boolean triggerAvailable, boolean captureAvailable,
- AudioFormat audioFormat, int captureSession, byte[] data,
- HotwordDetectedResult hotwordDetectedResult, ParcelFileDescriptor audioStream) {
- mTriggerAvailable = triggerAvailable;
+ private EventPayload(boolean captureAvailable,
+ @Nullable AudioFormat audioFormat,
+ int captureSession,
+ @DataFormat int dataFormat,
+ @Nullable byte[] data,
+ @Nullable HotwordDetectedResult hotwordDetectedResult,
+ @Nullable ParcelFileDescriptor audioStream,
+ @NonNull List<KeyphraseRecognitionExtra> keyphraseExtras) {
mCaptureAvailable = captureAvailable;
mCaptureSession = captureSession;
mAudioFormat = audioFormat;
+ mDataFormat = dataFormat;
mData = data;
mHotwordDetectedResult = hotwordDetectedResult;
mAudioStream = audioStream;
+ mKephraseExtras = keyphraseExtras;
}
/**
@@ -400,10 +420,12 @@
* {@link #getCaptureAudioFormat()}.
*
* @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
+ * @deprecated Use {@link #getData()} instead.
*/
+ @Deprecated
@Nullable
public byte[] getTriggerAudio() {
- if (mTriggerAvailable) {
+ if (mDataFormat == DATA_FORMAT_TRIGGER_AUDIO) {
return mData;
} else {
return null;
@@ -411,6 +433,36 @@
}
/**
+ * Conveys the format of the additional data that is triggered with the keyphrase event.
+ *
+ * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
+ * @see DataFormat
+ */
+ @DataFormat
+ public int getDataFormat() {
+ return mDataFormat;
+ }
+
+ /**
+ * Gets additional raw data that is triggered with the keyphrase event.
+ *
+ * <p>A {@link android.hardware.soundtrigger.SoundTriggerModule} may populate this
+ * field with opaque data for use by system applications who know about voice
+ * engine internals. Data may be null if the field is not populated by the
+ * {@link android.hardware.soundtrigger.SoundTriggerModule}.
+ *
+ * <p>If {@link #getDataFormat()} is {@link #DATA_FORMAT_TRIGGER_AUDIO}, then the
+ * entirety of this buffer is expected to be of the format from
+ * {@link #getCaptureAudioFormat()}.
+ *
+ * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
+ */
+ @Nullable
+ public byte[] getData() {
+ return mData;
+ }
+
+ /**
* Gets the session ID to start a capture from the DSP.
* This may be null if streaming capture isn't possible.
* If non-null, the format of the audio that can be captured can be
@@ -464,6 +516,166 @@
public ParcelFileDescriptor getAudioStream() {
return mAudioStream;
}
+
+ /**
+ * Returns the keyphrases recognized by the voice engine with additional confidence
+ * information
+ *
+ * @return List of keyphrase extras describing additional data for each keyphrase the voice
+ * engine triggered on for this event. The ordering of the list is preserved based on what
+ * the ordering provided by {@link android.hardware.soundtrigger.SoundTriggerModule}.
+ */
+ @NonNull
+ public List<KeyphraseRecognitionExtra> getKeyphraseRecognitionExtras() {
+ return mKephraseExtras;
+ }
+
+ /**
+ * Builder class for {@link EventPayload} objects
+ *
+ * @hide
+ */
+ @TestApi
+ public static final class Builder {
+ private boolean mCaptureAvailable = false;
+ private int mCaptureSession = -1;
+ private AudioFormat mAudioFormat = null;
+ @DataFormat
+ private int mDataFormat = DATA_FORMAT_RAW;
+ private byte[] mData = null;
+ private HotwordDetectedResult mHotwordDetectedResult = null;
+ private ParcelFileDescriptor mAudioStream = null;
+ private List<KeyphraseRecognitionExtra> mKeyphraseExtras = Collections.emptyList();
+
+ public Builder() {}
+
+ Builder(SoundTrigger.KeyphraseRecognitionEvent keyphraseRecognitionEvent) {
+ setCaptureAvailable(keyphraseRecognitionEvent.isCaptureAvailable());
+ setCaptureSession(keyphraseRecognitionEvent.getCaptureSession());
+ if (keyphraseRecognitionEvent.getCaptureFormat() != null) {
+ setCaptureAudioFormat(keyphraseRecognitionEvent.getCaptureFormat());
+ }
+ setDataFormat((keyphraseRecognitionEvent.triggerInData) ? DATA_FORMAT_TRIGGER_AUDIO
+ : DATA_FORMAT_RAW);
+ if (keyphraseRecognitionEvent.getData() != null) {
+ setData(keyphraseRecognitionEvent.getData());
+ }
+ if (keyphraseRecognitionEvent.keyphraseExtras != null) {
+ setKeyphraseRecognitionExtras(
+ Arrays.asList(keyphraseRecognitionEvent.keyphraseExtras));
+ }
+ }
+
+ /**
+ * Indicates if {@code captureSession} can be used to continue capturing more audio from
+ * the DSP hardware.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
+ public Builder setCaptureAvailable(boolean captureAvailable) {
+ mCaptureAvailable = captureAvailable;
+ return this;
+ }
+
+ /**
+ * Sets the session ID to start a capture from the DSP.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
+ public Builder setCaptureSession(int captureSession) {
+ mCaptureSession = captureSession;
+ return this;
+ }
+
+ /**
+ * Sets the format of the audio obtained using {@link #getTriggerAudio()}.
+ */
+ @NonNull
+ public Builder setCaptureAudioFormat(@NonNull AudioFormat audioFormat) {
+ mAudioFormat = audioFormat;
+ return this;
+ }
+
+ /**
+ * Conveys the format of the additional data that is triggered with the keyphrase event.
+ *
+ * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
+ * @see DataFormat
+ */
+ @NonNull
+ public Builder setDataFormat(@DataFormat int dataFormat) {
+ mDataFormat = dataFormat;
+ return this;
+ }
+
+ /**
+ * Sets additional raw data that is triggered with the keyphrase event.
+ *
+ * <p>A {@link android.hardware.soundtrigger.SoundTriggerModule} may populate this
+ * field with opaque data for use by system applications who know about voice
+ * engine internals. Data may be null if the field is not populated by the
+ * {@link android.hardware.soundtrigger.SoundTriggerModule}.
+ *
+ * <p>If {@link #getDataFormat()} is {@link #DATA_FORMAT_TRIGGER_AUDIO}, then the
+ * entirety of this
+ * buffer is expected to be of the format from {@link #getCaptureAudioFormat()}.
+ *
+ * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
+ */
+ @NonNull
+ public Builder setData(@NonNull byte[] data) {
+ mData = data;
+ return this;
+ }
+
+ /**
+ * Sets {@link HotwordDetectedResult} associated with the hotword event, passed from
+ * {@link HotwordDetectionService}.
+ */
+ @NonNull
+ public Builder setHotwordDetectedResult(
+ @NonNull HotwordDetectedResult hotwordDetectedResult) {
+ mHotwordDetectedResult = hotwordDetectedResult;
+ return this;
+ }
+
+ /**
+ * Sets a stream with bytes corresponding to the open audio stream with hotword data.
+ *
+ * <p>This data represents an audio stream in the format returned by
+ * {@link #getCaptureAudioFormat}.
+ *
+ * <p>Clients are expected to start consuming the stream within 1 second of receiving
+ * the
+ * event.
+ */
+ @NonNull
+ public Builder setAudioStream(@NonNull ParcelFileDescriptor audioStream) {
+ mAudioStream = audioStream;
+ return this;
+ }
+
+ /**
+ * Sets the keyphrases recognized by the voice engine with additional confidence
+ * information
+ */
+ @NonNull
+ public Builder setKeyphraseRecognitionExtras(
+ @NonNull List<KeyphraseRecognitionExtra> keyphraseRecognitionExtras) {
+ mKeyphraseExtras = keyphraseRecognitionExtras;
+ return this;
+ }
+
+ /**
+ * Builds an {@link EventPayload} instance
+ */
+ @NonNull
+ public EventPayload build() {
+ return new EventPayload(mCaptureAvailable, mAudioFormat, mCaptureSession,
+ mDataFormat, mData, mHotwordDetectedResult, mAudioStream,
+ mKeyphraseExtras);
+ }
+ }
}
/**
@@ -993,11 +1205,14 @@
/**
* Invalidates this hotword detector so that any future calls to this result
* in an IllegalStateException.
- *
- * @hide
*/
- void invalidate() {
+ @Override
+ public void destroy() {
synchronized (mLock) {
+ if (mAvailability == STATE_KEYPHRASE_ENROLLED) {
+ stopRecognition();
+ }
+
mAvailability = STATE_INVALID;
notifyStateChangedLocked();
@@ -1009,6 +1224,7 @@
}
}
}
+ super.destroy();
}
/**
@@ -1171,8 +1387,9 @@
Slog.i(TAG, "onDetected");
}
Message.obtain(mHandler, MSG_HOTWORD_DETECTED,
- new EventPayload(event.triggerInData, event.captureAvailable,
- event.captureFormat, event.captureSession, event.data, result))
+ new EventPayload.Builder(event)
+ .setHotwordDetectedResult(result)
+ .build())
.sendToTarget();
}
@Override
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index e3bb589..dfe0f54 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -140,9 +140,7 @@
Log.d(TAG, "#detectFromDspSource");
}
HotwordDetectionService.this.onDetect(
- new AlwaysOnHotwordDetector.EventPayload(
- event.triggerInData, event.captureAvailable,
- event.captureFormat, event.captureSession, event.data),
+ new AlwaysOnHotwordDetector.EventPayload.Builder(event).build(),
timeoutMillis,
new Callback(callback));
}
diff --git a/core/java/android/service/voice/HotwordDetector.java b/core/java/android/service/voice/HotwordDetector.java
index 969ec22..96fd8bb 100644
--- a/core/java/android/service/voice/HotwordDetector.java
+++ b/core/java/android/service/voice/HotwordDetector.java
@@ -119,6 +119,17 @@
void updateState(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory);
/**
+ * Invalidates this hotword detector so that any future calls to this result
+ * in an {@link IllegalStateException}.
+ *
+ * <p>If there are no other {@link HotwordDetector} instances linked to the
+ * {@link HotwordDetectionService}, the service will be shutdown.
+ */
+ default void destroy() {
+ throw new UnsupportedOperationException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* @hide
*/
static String detectorTypeToString(int detectorType) {
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index 512a654..2d662ea 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -77,7 +77,7 @@
if (DEBUG) {
Slog.i(TAG, "#startRecognition");
}
-
+ throwIfDetectorIsNoLongerActive();
maybeCloseExistingSession();
try {
@@ -100,6 +100,7 @@
if (DEBUG) {
Slog.i(TAG, "#stopRecognition");
}
+ throwIfDetectorIsNoLongerActive();
try {
mManagerService.stopListeningFromMic();
@@ -110,6 +111,19 @@
return true;
}
+ @Override
+ public void destroy() {
+ stopRecognition();
+ maybeCloseExistingSession();
+
+ try {
+ mManagerService.shutdownHotwordDetectionService();
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ super.destroy();
+ }
+
private void maybeCloseExistingSession() {
// TODO: needs to be synchronized.
// TODO: implement this
@@ -135,8 +149,11 @@
mHandler.sendMessage(obtainMessage(
HotwordDetector.Callback::onDetected,
mCallback,
- new AlwaysOnHotwordDetector.EventPayload(
- audioFormat, hotwordDetectedResult, audioStream)));
+ new AlwaysOnHotwordDetector.EventPayload.Builder()
+ .setCaptureAudioFormat(audioFormat)
+ .setAudioStream(audioStream)
+ .setHotwordDetectedResult(hotwordDetectedResult)
+ .build()));
}
}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index f52c9ff..bf0cfbe 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -271,7 +271,7 @@
// It's still guaranteed to have been stopped.
// This helps with cases where the voice interaction implementation is changed
// by the user.
- safelyShutdownHotwordDetector();
+ safelyShutdownAllHotwordDetectors();
}
/**
@@ -380,11 +380,13 @@
}
synchronized (mLock) {
// Allow only one concurrent recognition via the APIs.
- safelyShutdownHotwordDetector();
+ safelyShutdownAllHotwordDetectors();
mHotwordDetector = new AlwaysOnHotwordDetector(keyphrase, locale, callback,
mKeyphraseEnrollmentInfo, mSystemService,
getApplicationContext().getApplicationInfo().targetSdkVersion,
supportHotwordDetectionService, options, sharedMemory);
+ mHotwordDetector.registerOnDestroyListener((detector) -> onDspHotwordDetectorDestroyed(
+ (AlwaysOnHotwordDetector) detector));
}
return mHotwordDetector;
}
@@ -433,10 +435,13 @@
}
synchronized (mLock) {
// Allow only one concurrent recognition via the APIs.
- safelyShutdownHotwordDetector();
+ safelyShutdownAllHotwordDetectors();
mSoftwareHotwordDetector =
new SoftwareHotwordDetector(
mSystemService, null, options, sharedMemory, callback);
+ mSoftwareHotwordDetector.registerOnDestroyListener(
+ (detector) -> onMicrophoneHotwordDetectorDestroyed(
+ (SoftwareHotwordDetector) detector));
}
return mSoftwareHotwordDetector;
}
@@ -482,51 +487,36 @@
return mKeyphraseEnrollmentInfo.getKeyphraseMetadata(keyphrase, locale) != null;
}
- private void safelyShutdownHotwordDetector() {
+ private void safelyShutdownAllHotwordDetectors() {
synchronized (mLock) {
- shutdownDspHotwordDetectorLocked();
- shutdownMicrophoneHotwordDetectorLocked();
+ if (mHotwordDetector != null) {
+ try {
+ mHotwordDetector.destroy();
+ } catch (Exception ex) {
+ Log.i(TAG, "exception destroying AlwaysOnHotwordDetector", ex);
+ }
+ }
+
+ if (mSoftwareHotwordDetector != null) {
+ try {
+ mSoftwareHotwordDetector.destroy();
+ } catch (Exception ex) {
+ Log.i(TAG, "exception destroying SoftwareHotwordDetector", ex);
+ }
+ }
}
}
- private void shutdownDspHotwordDetectorLocked() {
- if (mHotwordDetector == null) {
- return;
+ private void onDspHotwordDetectorDestroyed(@NonNull AlwaysOnHotwordDetector detector) {
+ synchronized (mLock) {
+ mHotwordDetector = null;
}
-
- try {
- mHotwordDetector.stopRecognition();
- } catch (Exception ex) {
- // Ignore.
- }
-
- try {
- mHotwordDetector.invalidate();
- } catch (Exception ex) {
- // Ignore.
- }
-
- mHotwordDetector = null;
}
- private void shutdownMicrophoneHotwordDetectorLocked() {
- if (mSoftwareHotwordDetector == null) {
- return;
+ private void onMicrophoneHotwordDetectorDestroyed(@NonNull SoftwareHotwordDetector detector) {
+ synchronized (mLock) {
+ mSoftwareHotwordDetector = null;
}
-
- try {
- mSoftwareHotwordDetector.stopRecognition();
- } catch (Exception ex) {
- // Ignore.
- }
-
- try {
- mSystemService.shutdownHotwordDetectionService();
- } catch (Exception ex) {
- // Ignore.
- }
-
- mSoftwareHotwordDetector = null;
}
/**
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index c91851a..a2938a8 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -26,6 +26,8 @@
import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.FloatRange;
import android.annotation.NonNull;
@@ -890,8 +892,6 @@
* @param dimAmount Float amount between [0.0, 1.0] to dim the wallpaper.
*/
private void updateWallpaperDimming(float dimAmount) {
- mPreviousWallpaperDimAmount = mWallpaperDimAmount;
-
// Custom dim amount cannot be less than the default dim amount.
mWallpaperDimAmount = Math.max(mDefaultDimAmount, dimAmount);
// If dim amount is 0f (additional dimming is removed), then the wallpaper should dim
@@ -909,15 +909,15 @@
SurfaceControl.Transaction surfaceControlTransaction = new SurfaceControl.Transaction();
// TODO: apply the dimming to preview as well once surface transparency works in
// preview mode.
- if (!isPreview() && mShouldDim) {
+ if ((!isPreview() && mShouldDim)
+ || mPreviousWallpaperDimAmount != mWallpaperDimAmount) {
Log.v(TAG, "Setting wallpaper dimming: " + mWallpaperDimAmount);
// Animate dimming to gradually change the wallpaper alpha from the previous
// dim amount to the new amount only if the dim amount changed.
ValueAnimator animator = ValueAnimator.ofFloat(
mPreviousWallpaperDimAmount, mWallpaperDimAmount);
- animator.setDuration(mPreviousWallpaperDimAmount == mWallpaperDimAmount
- ? 0 : DIMMING_ANIMATION_DURATION_MS);
+ animator.setDuration(DIMMING_ANIMATION_DURATION_MS);
animator.addUpdateListener((ValueAnimator va) -> {
final float dimValue = (float) va.getAnimatedValue();
if (mBbqSurfaceControl != null) {
@@ -925,11 +925,19 @@
.setAlpha(mBbqSurfaceControl, 1 - dimValue).apply();
}
});
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ updateSurface(false, false, true);
+ }
+ });
animator.start();
} else {
Log.v(TAG, "Setting wallpaper dimming: " + 0);
surfaceControlTransaction.setAlpha(mBbqSurfaceControl, 1.0f).apply();
}
+
+ mPreviousWallpaperDimAmount = mWallpaperDimAmount;
}
/**
@@ -1332,6 +1340,7 @@
resetWindowPages();
mSession.finishDrawing(mWindow, null /* postDrawTransaction */);
processLocalColors(mPendingXOffset, mPendingXOffsetStep);
+ notifyColorsChanged();
}
reposition();
reportEngineShown(shouldWaitForEngineShown());
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index 502558e..e075c05 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -496,6 +496,17 @@
Objects.requireNonNull(recognizerIntent, "intent must not be null");
Objects.requireNonNull(supportListener, "listener must not be null");
+ if (DBG) {
+ Slog.i(TAG, "#checkRecognitionSupport called");
+ if (mService == null) {
+ Slog.i(TAG, "Connection is not established yet");
+ }
+ }
+
+ if (mService == null) {
+ // First time connection: first establish a connection, then dispatch.
+ connectToSystemService();
+ }
putMessage(Message.obtain(mHandler, MSG_CHECK_RECOGNITION_SUPPORT,
Pair.create(recognizerIntent, supportListener)));
}
@@ -510,7 +521,17 @@
*/
public void triggerModelDownload(@NonNull Intent recognizerIntent) {
Objects.requireNonNull(recognizerIntent, "intent must not be null");
- putMessage(Message.obtain(mHandler, MSG_TRIGGER_MODEL_DOWNLOAD));
+ if (DBG) {
+ Slog.i(TAG, "#triggerModelDownload called");
+ if (mService == null) {
+ Slog.i(TAG, "Connection is not established yet");
+ }
+ }
+ if (mService == null) {
+ // First time connection: first establish a connection, then dispatch.
+ connectToSystemService();
+ }
+ putMessage(Message.obtain(mHandler, MSG_TRIGGER_MODEL_DOWNLOAD, recognizerIntent));
}
/**
@@ -625,7 +646,6 @@
}
try {
mService.triggerModelDownload(recognizerIntent);
- if (DBG) Log.d(TAG, "service download support command succeeded");
} catch (final RemoteException e) {
Log.e(TAG, "downloadModel() failed", e);
mListener.onError(ERROR_CLIENT);
@@ -705,6 +725,9 @@
}
private synchronized boolean maybeInitializeManagerService() {
+ if (DBG) {
+ Log.i(TAG, "#maybeInitializeManagerService found = " + mManagerService);
+ }
if (mManagerService != null) {
return true;
}
@@ -712,8 +735,13 @@
mManagerService = IRecognitionServiceManager.Stub.asInterface(
ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE));
- if (mManagerService == null && mListener != null) {
- mListener.onError(ERROR_CLIENT);
+ if (DBG) {
+ Log.i(TAG, "#maybeInitializeManagerService instantiated =" + mManagerService);
+ }
+ if (mManagerService == null) {
+ if (mListener != null) {
+ mListener.onError(ERROR_CLIENT);
+ }
return false;
}
return true;
diff --git a/core/java/android/text/BidiFormatter.java b/core/java/android/text/BidiFormatter.java
index 422d347..dfa172d 100644
--- a/core/java/android/text/BidiFormatter.java
+++ b/core/java/android/text/BidiFormatter.java
@@ -31,7 +31,7 @@
* directionality of the text can be either estimated or passed in when known.
*
* <p>To support versions lower than {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
- * you can use the support library's {@link android.support.v4.text.BidiFormatter} class.
+ * you can use the support library's {@link androidx.core.text.BidiFormatter} class.
*
* <p>These APIs provides the following functionality:
* <p>
diff --git a/core/java/android/text/TextDirectionHeuristics.java b/core/java/android/text/TextDirectionHeuristics.java
index 354c15f..85260f4 100644
--- a/core/java/android/text/TextDirectionHeuristics.java
+++ b/core/java/android/text/TextDirectionHeuristics.java
@@ -28,7 +28,7 @@
* provided in the {@link android.view.View} class for {@link android.view.View#setTextDirection
* setTextDirection()}, such as {@link android.view.View#TEXT_DIRECTION_RTL}.
* <p>To support versions lower than {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
- * you can use the support library's {@link android.support.v4.text.TextDirectionHeuristicsCompat}
+ * you can use the support library's {@link androidx.core.text.TextDirectionHeuristicsCompat}
* class.
*
*/
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index 4a48832..ad044af 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -217,6 +217,7 @@
com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0);
mMisspelledUnderlineColor = typedArray.getColor(
com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK);
+ typedArray.recycle();
defStyleAttr = com.android.internal.R.attr.textAppearanceGrammarErrorSuggestion;
typedArray = context.obtainStyledAttributes(
@@ -225,6 +226,7 @@
com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0);
mGrammarErrorUnderlineColor = typedArray.getColor(
com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK);
+ typedArray.recycle();
defStyleAttr = com.android.internal.R.attr.textAppearanceEasyCorrectSuggestion;
typedArray = context.obtainStyledAttributes(
@@ -233,6 +235,7 @@
com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0);
mEasyCorrectUnderlineColor = typedArray.getColor(
com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK);
+ typedArray.recycle();
defStyleAttr = com.android.internal.R.attr.textAppearanceAutoCorrectionSuggestion;
typedArray = context.obtainStyledAttributes(
@@ -241,6 +244,7 @@
com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0);
mAutoCorrectionUnderlineColor = typedArray.getColor(
com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK);
+ typedArray.recycle();
}
public SuggestionSpan(Parcel src) {
diff --git a/core/java/android/tracing/OWNERS b/core/java/android/tracing/OWNERS
index f5de4eb..7d1b48b 100644
--- a/core/java/android/tracing/OWNERS
+++ b/core/java/android/tracing/OWNERS
@@ -1,2 +1,2 @@
-cfijalkovich@google.com
carmenjackson@google.com
+include platform/external/perfetto:/OWNERS
diff --git a/core/java/android/util/RotationUtils.java b/core/java/android/util/RotationUtils.java
index 0ac2c9c..cebdbf6 100644
--- a/core/java/android/util/RotationUtils.java
+++ b/core/java/android/util/RotationUtils.java
@@ -24,8 +24,10 @@
import android.annotation.Dimension;
import android.graphics.Insets;
import android.graphics.Matrix;
+import android.graphics.Point;
import android.graphics.Rect;
import android.view.Surface.Rotation;
+import android.view.SurfaceControl;
/**
* A class containing utility methods related to rotation.
@@ -121,13 +123,64 @@
/** @return the rotation needed to rotate from oldRotation to newRotation. */
@Rotation
- public static int deltaRotation(int oldRotation, int newRotation) {
+ public static int deltaRotation(@Rotation int oldRotation, @Rotation int newRotation) {
int delta = newRotation - oldRotation;
if (delta < 0) delta += 4;
return delta;
}
/**
+ * Rotates a surface CCW around the origin (eg. a 90-degree rotation will result in the
+ * bottom-left being at the origin). Use {@link #rotatePoint} to transform the top-left
+ * corner appropriately.
+ */
+ public static void rotateSurface(SurfaceControl.Transaction t, SurfaceControl sc,
+ @Rotation int rotation) {
+ // Note: the matrix values look inverted, but they aren't because our coordinate-space
+ // is actually left-handed.
+ // Note: setMatrix expects values in column-major order.
+ switch (rotation) {
+ case ROTATION_0:
+ t.setMatrix(sc, 1.f, 0.f, 0.f, 1.f);
+ break;
+ case ROTATION_90:
+ t.setMatrix(sc, 0.f, -1.f, 1.f, 0.f);
+ break;
+ case ROTATION_180:
+ t.setMatrix(sc, -1.f, 0.f, 0.f, -1.f);
+ break;
+ case ROTATION_270:
+ t.setMatrix(sc, 0.f, 1.f, -1.f, 0.f);
+ break;
+ }
+ }
+
+ /**
+ * Rotates a point CCW within a rectangle of size parentW x parentH with top/left at the
+ * origin as if the point is stuck to the rectangle. The rectangle is transformed such that
+ * it's top/left remains at the origin after the rotation.
+ */
+ public static void rotatePoint(Point inOutPoint, @Rotation int rotation,
+ int parentW, int parentH) {
+ int origX = inOutPoint.x;
+ switch (rotation) {
+ case ROTATION_0:
+ return;
+ case ROTATION_90:
+ inOutPoint.x = inOutPoint.y;
+ inOutPoint.y = parentW - origX;
+ return;
+ case ROTATION_180:
+ inOutPoint.x = parentW - inOutPoint.x;
+ inOutPoint.y = parentH - inOutPoint.y;
+ return;
+ case ROTATION_270:
+ inOutPoint.x = parentH - inOutPoint.y;
+ inOutPoint.y = origX;
+ }
+ }
+
+ /**
* Sets a matrix such that given a rotation, it transforms physical display
* coordinates to that rotation's logical coordinates.
*
diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java
index 117d75e..3aeecca 100644
--- a/core/java/android/util/Slog.java
+++ b/core/java/android/util/Slog.java
@@ -16,6 +16,9 @@
package android.util;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
@@ -24,118 +27,265 @@
*
* <p>Should be used by system components. Use {@code adb logcat --buffer=system} to fetch the logs.
*
+ * @see Log
* @hide
*/
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public final class Slog {
private Slog() {
}
+ /**
+ * Logs {@code msg} at {@link Log#VERBOSE} level.
+ *
+ * @param tag identifies the source of a log message. It usually represents system service,
+ * e.g. {@code PackageManager}.
+ * @param msg the message to log.
+ *
+ * @see Log#v(String, String)
+ */
@UnsupportedAppUsage
- public static int v(String tag, String msg) {
+ public static int v(@Nullable String tag, @NonNull String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.VERBOSE, tag, msg);
}
- public static int v(String tag, String msg, Throwable tr) {
+ /**
+ * Logs {@code msg} at {@link Log#VERBOSE} level, attaching stack trace of the {@code tr} to
+ * the end of the log statement.
+ *
+ * @param tag identifies the source of a log message. It usually represents system service,
+ * e.g. {@code PackageManager}.
+ * @param msg the message to log.
+ * @param tr an exception to log.
+ *
+ * @see Log#v(String, String, Throwable)
+ */
+ public static int v(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.VERBOSE, tag,
msg + '\n' + Log.getStackTraceString(tr));
}
+ /**
+ * Logs {@code msg} at {@link Log#DEBUG} level.
+ *
+ * @param tag identifies the source of a log message. It usually represents system service,
+ * e.g. {@code PackageManager}.
+ * @param msg the message to log.
+ *
+ * @see Log#d(String, String)
+ */
@UnsupportedAppUsage
- public static int d(String tag, String msg) {
+ public static int d(@Nullable String tag, @NonNull String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.DEBUG, tag, msg);
}
+ /**
+ * Logs {@code msg} at {@link Log#DEBUG} level, attaching stack trace of the {@code tr} to
+ * the end of the log statement.
+ *
+ * @param tag identifies the source of a log message. It usually represents system service,
+ * e.g. {@code PackageManager}.
+ * @param msg the message to log.
+ * @param tr an exception to log.
+ *
+ * @see Log#d(String, String, Throwable)
+ */
@UnsupportedAppUsage
- public static int d(String tag, String msg, Throwable tr) {
+ public static int d(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.DEBUG, tag,
msg + '\n' + Log.getStackTraceString(tr));
}
+ /**
+ * Logs {@code msg} at {@link Log#INFO} level.
+ *
+ * @param tag identifies the source of a log message. It usually represents system service,
+ * e.g. {@code PackageManager}.
+ * @param msg the message to log.
+ *
+ * @see Log#i(String, String)
+ */
@UnsupportedAppUsage
- public static int i(String tag, String msg) {
+ public static int i(@Nullable String tag, @NonNull String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.INFO, tag, msg);
}
- public static int i(String tag, String msg, Throwable tr) {
+ /**
+ * Logs {@code msg} at {@link Log#INFO} level, attaching stack trace of the {@code tr} to
+ * the end of the log statement.
+ *
+ * @param tag identifies the source of a log message. It usually represents system service,
+ * e.g. {@code PackageManager}.
+ * @param msg the message to log.
+ * @param tr an exception to log.
+ *
+ * @see Log#i(String, String, Throwable)
+ */
+ public static int i(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.INFO, tag,
msg + '\n' + Log.getStackTraceString(tr));
}
+ /**
+ * Logs {@code msg} at {@link Log#WARN} level.
+ *
+ * @param tag identifies the source of a log message. It usually represents system service,
+ * e.g. {@code PackageManager}.
+ * @param msg the message to log.
+ *
+ * @see Log#w(String, String)
+ */
@UnsupportedAppUsage
- public static int w(String tag, String msg) {
+ public static int w(@Nullable String tag, @NonNull String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag, msg);
}
+ /**
+ * Logs {@code msg} at {@link Log#WARN} level, attaching stack trace of the {@code tr} to
+ * the end of the log statement.
+ *
+ * @param tag identifies the source of a log message. It usually represents system service,
+ * e.g. {@code PackageManager}.
+ * @param msg the message to log.
+ * @param tr an exception to log.
+ *
+ * @see Log#w(String, String, Throwable)
+ */
@UnsupportedAppUsage
- public static int w(String tag, String msg, Throwable tr) {
+ public static int w(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag,
msg + '\n' + Log.getStackTraceString(tr));
}
- public static int w(String tag, Throwable tr) {
+ /**
+ * Logs stack trace of {@code tr} at {@link Log#WARN} level.
+ *
+ * @param tag identifies the source of a log message. It usually represents system service,
+ * e.g. {@code PackageManager}.
+ * @param tr an exception to log.
+ *
+ * @see Log#w(String, Throwable)
+ */
+ public static int w(@Nullable String tag, @Nullable Throwable tr) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag, Log.getStackTraceString(tr));
}
+ /**
+ * Logs {@code msg} at {@link Log#ERROR} level.
+ *
+ * @param tag identifies the source of a log message. It usually represents system service,
+ * e.g. {@code PackageManager}.
+ * @param msg the message to log.
+ *
+ * @see Log#e(String, String)
+ */
@UnsupportedAppUsage
- public static int e(String tag, String msg) {
+ public static int e(@Nullable String tag, @NonNull String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.ERROR, tag, msg);
}
+ /**
+ * Logs {@code msg} at {@link Log#ERROR} level, attaching stack trace of the {@code tr} to
+ * the end of the log statement.
+ *
+ * @param tag identifies the source of a log message. It usually represents system service,
+ * e.g. {@code PackageManager}.
+ * @param msg the message to log.
+ * @param tr an exception to log.
+ *
+ * @see Log#e(String, String, Throwable)
+ */
@UnsupportedAppUsage
- public static int e(String tag, String msg, Throwable tr) {
+ public static int e(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.ERROR, tag,
msg + '\n' + Log.getStackTraceString(tr));
}
/**
- * Like {@link Log#wtf(String, String)}, but will never cause the caller to crash, and
- * will always be handled asynchronously. Primarily for use by coding running within
- * the system process.
+ * Logs a condition that should never happen.
+ *
+ * <p>
+ * Similar to {@link Log#wtf(String, String)}, but will never cause the caller to crash, and
+ * will always be handled asynchronously. Primarily to be used by the system server.
+ *
+ * @param tag identifies the source of a log message. It usually represents system service,
+ * e.g. {@code PackageManager}.
+ * @param msg the message to log.
+ *
+ * @see Log#wtf(String, String)
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static int wtf(String tag, String msg) {
+ public static int wtf(@Nullable String tag, @NonNull String msg) {
return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, false, true);
}
/**
- * Like {@link #wtf(String, String)}, but does not output anything to the log.
+ * Similar to {@link #wtf(String, String)}, but does not output anything to the log.
*/
- public static void wtfQuiet(String tag, String msg) {
+ public static void wtfQuiet(@Nullable String tag, @NonNull String msg) {
Log.wtfQuiet(Log.LOG_ID_SYSTEM, tag, msg, true);
}
/**
- * Like {@link Log#wtfStack(String, String)}, but will never cause the caller to crash, and
- * will always be handled asynchronously. Primarily for use by coding running within
- * the system process.
+ * Logs a condition that should never happen, attaching the full call stack to the log.
+ *
+ * <p>
+ * Similar to {@link Log#wtfStack(String, String)}, but will never cause the caller to crash,
+ * and will always be handled asynchronously. Primarily to be used by the system server.
+ *
+ * @param tag identifies the source of a log message. It usually represents system service,
+ * e.g. {@code PackageManager}.
+ * @param msg the message to log.
+ *
+ * @see Log#wtfStack(String, String)
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- public static int wtfStack(String tag, String msg) {
+ public static int wtfStack(@Nullable String tag, @NonNull String msg) {
return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, true, true);
}
/**
- * Like {@link Log#wtf(String, Throwable)}, but will never cause the caller to crash,
- * and will always be handled asynchronously. Primarily for use by coding running within
- * the system process.
+ * Logs a condition that should never happen, attaching stack trace of the {@code tr} to the
+ * end of the log statement.
+ *
+ * <p>
+ * Similar to {@link Log#wtf(String, Throwable)}, but will never cause the caller to crash,
+ * and will always be handled asynchronously. Primarily to be used by the system server.
+ *
+ * @param tag identifies the source of a log message. It usually represents system service,
+ * e.g. {@code PackageManager}.
+ * @param tr an exception to log.
+ *
+ * @see Log#wtf(String, Throwable)
*/
- public static int wtf(String tag, Throwable tr) {
+ public static int wtf(@Nullable String tag, @Nullable Throwable tr) {
return Log.wtf(Log.LOG_ID_SYSTEM, tag, tr.getMessage(), tr, false, true);
}
/**
- * Like {@link Log#wtf(String, String, Throwable)}, but will never cause the caller to crash,
- * and will always be handled asynchronously. Primarily for use by coding running within
- * the system process.
+ * Logs a condition that should never happen, attaching stack trace of the {@code tr} to the
+ * end of the log statement.
+ *
+ * <p>
+ * Similar to {@link Log#wtf(String, String, Throwable)}, but will never cause the caller to
+ * crash, and will always be handled asynchronously. Primarily to be used by the system server.
+ *
+ * @param tag identifies the source of a log message. It usually represents system service,
+ * e.g. {@code PackageManager}.
+ * @param msg the message to log.
+ * @param tr an exception to log.
+ *
+ * @see Log#wtf(String, String, Throwable)
*/
@UnsupportedAppUsage
- public static int wtf(String tag, String msg, Throwable tr) {
+ public static int wtf(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, tr, false, true);
}
+ /** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static int println(int priority, String tag, String msg) {
+ public static int println(@Log.Level int priority, @Nullable String tag, @NonNull String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, priority, tag, msg);
}
}
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index de56d3a..43c07c8 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -52,9 +52,10 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
+import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -348,6 +349,11 @@
View requestedView = null;
AccessibilityNodeInfo requestedNode = null;
+ boolean interruptPrefetch =
+ ((flags & AccessibilityNodeInfo.FLAG_PREFETCH_UNINTERRUPTIBLE) == 0);
+
+ ArrayList<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
+ infos.clear();
try {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
@@ -357,27 +363,46 @@
if (requestedView != null && isShown(requestedView)) {
requestedNode = populateAccessibilityNodeInfoForView(
requestedView, arguments, virtualDescendantId);
+ mPrefetcher.mInterruptPrefetch = interruptPrefetch;
+ mPrefetcher.mFetchFlags = flags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
+
+ if (!interruptPrefetch) {
+ infos.add(requestedNode);
+ mPrefetcher.prefetchAccessibilityNodeInfos(requestedView,
+ requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode),
+ infos);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ }
}
} finally {
- updateInfoForViewportAndReturnFindNodeResult(
- requestedNode == null ? null : AccessibilityNodeInfo.obtain(requestedNode),
- callback, interactionId, spec, interactiveRegion);
+ if (!interruptPrefetch) {
+ // Return found node and prefetched nodes in one IPC.
+ updateInfosForViewportAndReturnFindNodeResult(infos, callback, interactionId, spec,
+ interactiveRegion);
+
+ final SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest =
+ getSatisfiedRequestInPrefetch(requestedNode == null ? null : requestedNode,
+ infos, flags);
+ if (satisfiedRequest != null) {
+ returnFindNodeResult(satisfiedRequest);
+ }
+ return;
+ } else {
+ // Return found node.
+ updateInfoForViewportAndReturnFindNodeResult(
+ requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode),
+ callback, interactionId, spec, interactiveRegion);
+ }
}
- ArrayList<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
- infos.clear();
mPrefetcher.prefetchAccessibilityNodeInfos(requestedView,
- requestedNode == null ? null : AccessibilityNodeInfo.obtain(requestedNode),
- virtualDescendantId, flags, infos);
+ requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode), infos);
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
updateInfosForViewPort(infos, spec, interactiveRegion);
final SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest =
- getSatisfiedRequestInPrefetch(
- requestedNode == null ? null : requestedNode, infos, flags);
+ getSatisfiedRequestInPrefetch(requestedNode == null ? null : requestedNode, infos,
+ flags);
- if (satisfiedRequest != null && satisfiedRequest.mSatisfiedRequestNode != requestedNode) {
- infos.remove(satisfiedRequest.mSatisfiedRequestNode);
- }
-
+ // Return prefetch result separately.
returnPrefetchResult(interactionId, infos, callback);
if (satisfiedRequest != null) {
@@ -1077,6 +1102,11 @@
}
}
mPendingFindNodeByIdMessages.clear();
+ // Remove node from prefetched infos.
+ if (satisfiedRequest != null && satisfiedRequest.mSatisfiedRequestNode
+ != requestedNode) {
+ infos.remove(satisfiedRequest.mSatisfiedRequestNode);
+ }
return satisfiedRequest;
}
}
@@ -1149,45 +1179,76 @@
*/
private class AccessibilityNodePrefetcher {
- private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
-
private final ArrayList<View> mTempViewList = new ArrayList<View>();
+ private boolean mInterruptPrefetch;
+ private int mFetchFlags;
public void prefetchAccessibilityNodeInfos(View view, AccessibilityNodeInfo root,
- int virtualViewId, int fetchFlags, List<AccessibilityNodeInfo> outInfos) {
+ List<AccessibilityNodeInfo> outInfos) {
if (root == null) {
return;
}
AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
+ final boolean prefetchPredecessors =
+ isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS);
if (provider == null) {
- if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+ if (prefetchPredecessors) {
prefetchPredecessorsOfRealNode(view, outInfos);
}
- if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
- prefetchSiblingsOfRealNode(view, outInfos);
+ if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS)) {
+ prefetchSiblingsOfRealNode(view, outInfos, prefetchPredecessors);
}
- if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+ if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID)) {
prefetchDescendantsOfRealNode(view, outInfos);
}
} else {
- if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+ if (prefetchPredecessors) {
prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
}
- if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
- prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
+ if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS)) {
+ prefetchSiblingsOfVirtualNode(root, view, provider, outInfos,
+ prefetchPredecessors);
}
- if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+ if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID)) {
prefetchDescendantsOfVirtualNode(root, provider, outInfos);
}
}
+ if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST)
+ || isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST)) {
+ if (shouldStopPrefetching(outInfos)) {
+ return;
+ }
+ PrefetchDeque<DequeNode> deque = new PrefetchDeque<>(
+ mFetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_MASK,
+ outInfos);
+ addChildrenOfRoot(view, root, provider, deque);
+ deque.performTraversalAndPrefetch();
+ }
if (ENFORCE_NODE_TREE_CONSISTENT) {
enforceNodeTreeConsistent(root, outInfos);
}
}
- private boolean shouldStopPrefetching(List prefetchededInfos) {
- return mHandler.hasUserInteractiveMessagesWaiting()
- || prefetchededInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE;
+ private void addChildrenOfRoot(View root, AccessibilityNodeInfo rootInfo,
+ AccessibilityNodeProvider rootProvider, PrefetchDeque deque) {
+ DequeNode rootDequeNode;
+ if (rootProvider == null) {
+ rootDequeNode = new ViewNode(root);
+ } else {
+ rootDequeNode = new VirtualNode(
+ AccessibilityNodeProvider.HOST_VIEW_ID, rootProvider);
+ }
+ rootDequeNode.addChildren(rootInfo, deque);
+ }
+
+ private boolean isFlagSet(@AccessibilityNodeInfo.PrefetchingStrategy int strategy) {
+ return (mFetchFlags & strategy) != 0;
+ }
+
+ public boolean shouldStopPrefetching(List prefetchedInfos) {
+ return ((mHandler.hasUserInteractiveMessagesWaiting() && mInterruptPrefetch)
+ || prefetchedInfos.size()
+ >= AccessibilityNodeInfo.MAX_NUMBER_OF_PREFETCHED_NODES);
}
private void enforceNodeTreeConsistent(
@@ -1283,7 +1344,7 @@
}
private void prefetchSiblingsOfRealNode(View current,
- List<AccessibilityNodeInfo> outInfos) {
+ List<AccessibilityNodeInfo> outInfos, boolean predecessorsPrefetched) {
if (shouldStopPrefetching(outInfos)) {
return;
}
@@ -1293,6 +1354,13 @@
ArrayList<View> children = mTempViewList;
children.clear();
try {
+ if (!predecessorsPrefetched) {
+ AccessibilityNodeInfo parentInfo =
+ ((ViewGroup) parent).createAccessibilityNodeInfo();
+ if (parentInfo != null) {
+ outInfos.add(parentInfo);
+ }
+ }
parentGroup.addChildrenForAccessibility(children);
final int childCount = children.size();
for (int i = 0; i < childCount; i++) {
@@ -1304,7 +1372,7 @@
&& isShown(child)) {
AccessibilityNodeInfo info = null;
AccessibilityNodeProvider provider =
- child.getAccessibilityNodeProvider();
+ child.getAccessibilityNodeProvider();
if (provider == null) {
info = child.createAccessibilityNodeInfo();
} else {
@@ -1327,8 +1395,8 @@
if (shouldStopPrefetching(outInfos) || !(root instanceof ViewGroup)) {
return;
}
- HashMap<View, AccessibilityNodeInfo> addedChildren =
- new HashMap<View, AccessibilityNodeInfo>();
+ LinkedHashMap<View, AccessibilityNodeInfo> addedChildren =
+ new LinkedHashMap<View, AccessibilityNodeInfo>();
ArrayList<View> children = mTempViewList;
children.clear();
try {
@@ -1414,17 +1482,21 @@
}
private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
- AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
+ AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos,
+ boolean predecessorsPrefetched) {
final long parentNodeId = current.getParentNodeId();
final int parentAccessibilityViewId =
- AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
+ AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
final int parentVirtualDescendantId =
- AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
+ AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
if (parentVirtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID
|| parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
final AccessibilityNodeInfo parent =
provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
if (parent != null) {
+ if (!predecessorsPrefetched) {
+ outInfos.add(parent);
+ }
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
if (shouldStopPrefetching(outInfos)) {
@@ -1443,7 +1515,7 @@
}
}
} else {
- prefetchSiblingsOfRealNode(providerHost, outInfos);
+ prefetchSiblingsOfRealNode(providerHost, outInfos, predecessorsPrefetched);
}
}
@@ -1626,4 +1698,159 @@
mSatisfiedRequestInteractionId = satisfiedRequestInteractionId;
}
}
+
+ private class PrefetchDeque<E extends DequeNode>
+ extends ArrayDeque<E> {
+ int mStrategy;
+ List<AccessibilityNodeInfo> mPrefetchOutput;
+
+ PrefetchDeque(int strategy, List<AccessibilityNodeInfo> output) {
+ mStrategy = strategy;
+ mPrefetchOutput = output;
+ }
+
+ /** Performs depth-first or breadth-first traversal.
+ *
+ * For depth-first search, we iterate through the children in backwards order and push them
+ * to the stack before taking from the head. For breadth-first search, we iterate through
+ * the children in order and push them to the stack before taking from the tail.
+ *
+ * Depth-first search: 0 has children 0, 1, 2, 4. 1 has children 5 and 6.
+ * Head Tail
+ * 1 2 3 4 -> pop: 1 -> 5 6 2 3 4
+ *
+ * Breadth-first search
+ * Head Tail
+ * 4 3 2 1 -> remove last: 1 -> 6 5 3 2
+ *
+ **/
+ void performTraversalAndPrefetch() {
+ try {
+ while (!isEmpty()) {
+ E child = getNext();
+ AccessibilityNodeInfo childInfo = child.getA11yNodeInfo();
+ if (childInfo != null) {
+ mPrefetchOutput.add(childInfo);
+ }
+ if (mPrefetcher.shouldStopPrefetching(mPrefetchOutput)) {
+ return;
+ }
+ // Add children to deque.
+ child.addChildren(childInfo, this);
+ }
+ } finally {
+ clear();
+ }
+ }
+
+ E getNext() {
+ if (isStack()) {
+ return pop();
+ }
+ return removeLast();
+ }
+
+ boolean isStack() {
+ return (mStrategy & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST) != 0;
+ }
+ }
+
+ interface DequeNode {
+ AccessibilityNodeInfo getA11yNodeInfo();
+ void addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque);
+ }
+
+ private class ViewNode implements DequeNode {
+ View mView;
+ private final ArrayList<View> mTempViewList = new ArrayList<>();
+
+ ViewNode(View view) {
+ mView = view;
+ }
+
+ @Override
+ public AccessibilityNodeInfo getA11yNodeInfo() {
+ if (mView == null) {
+ return null;
+ }
+ return mView.createAccessibilityNodeInfo();
+ }
+
+ @Override
+ public void addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque) {
+ if (mView == null) {
+ return;
+ }
+ if (!(mView instanceof ViewGroup)) {
+ return;
+ }
+ ArrayList<View> children = mTempViewList;
+ children.clear();
+ try {
+ mView.addChildrenForAccessibility(children);
+ final int childCount = children.size();
+
+ if (deque.isStack()) {
+ for (int i = childCount - 1; i >= 0; i--) {
+ addChild(deque, children.get(i));
+ }
+ } else {
+ for (int i = 0; i < childCount; i++) {
+ addChild(deque, children.get(i));
+ }
+ }
+ } finally {
+ children.clear();
+ }
+ }
+
+ private void addChild(ArrayDeque deque, View child) {
+ if (isShown(child)) {
+ AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
+ if (provider == null) {
+ deque.push(new ViewNode(child));
+ } else {
+ deque.push(new VirtualNode(AccessibilityNodeProvider.HOST_VIEW_ID,
+ provider));
+ }
+ }
+ }
+ }
+
+ private class VirtualNode implements DequeNode {
+ long mInfoId;
+ AccessibilityNodeProvider mProvider;
+
+ VirtualNode(long id, AccessibilityNodeProvider provider) {
+ mInfoId = id;
+ mProvider = provider;
+ }
+ @Override
+ public AccessibilityNodeInfo getA11yNodeInfo() {
+ if (mProvider == null) {
+ return null;
+ }
+ return mProvider.createAccessibilityNodeInfo(
+ AccessibilityNodeInfo.getVirtualDescendantId(mInfoId));
+ }
+
+ @Override
+ public void addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque) {
+ if (virtualRoot == null) {
+ return;
+ }
+ final int childCount = virtualRoot.getChildCount();
+ if (deque.isStack()) {
+ for (int i = childCount - 1; i >= 0; i--) {
+ final long childNodeId = virtualRoot.getChildId(i);
+ deque.push(new VirtualNode(childNodeId, mProvider));
+ }
+ } else {
+ for (int i = 0; i < childCount; i++) {
+ final long childNodeId = virtualRoot.getChildId(i);
+ deque.push(new VirtualNode(childNodeId, mProvider));
+ }
+ }
+ }
+ }
}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 246a8c9..7d8e998 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -42,6 +42,7 @@
import android.hardware.display.DeviceProductInfo;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.graphics.common.DisplayDecorationSupport;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -1856,6 +1857,19 @@
}
/**
+ * Returns whether/how the specified display supports DISPLAY_DECORATION.
+ *
+ * Composition.DISPLAY_DECORATION is a special layer type which is used to
+ * render the screen decorations (i.e. antialiased rounded corners and
+ * cutouts) while taking advantage of specific hardware.
+ *
+ * @hide
+ */
+ public DisplayDecorationSupport getDisplayDecorationSupport() {
+ return mGlobal.getDisplayDecorationSupport(mDisplayId);
+ }
+
+ /**
* A mode supported by a given display.
*
* @see Display#getSupportedModes()
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index c87c13d..c1413be 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -24,6 +24,9 @@
import com.android.internal.annotations.VisibleForTesting;
import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
/**
* Initiates handwriting mode once it detects stylus movement in handwritable areas.
@@ -58,6 +61,7 @@
private final long mTapTimeoutInMillis;
private State mState = new State();
+ private final HandwritingAreaTracker mHandwritingAreasTracker = new HandwritingAreaTracker();
/**
* Helper method to reset the internal state of this class.
@@ -83,8 +87,8 @@
private final InputMethodManager mImm;
@VisibleForTesting
- public HandwritingInitiator(ViewConfiguration viewConfiguration,
- InputMethodManager inputMethodManager) {
+ public HandwritingInitiator(@NonNull ViewConfiguration viewConfiguration,
+ @NonNull InputMethodManager inputMethodManager) {
mTouchSlop = viewConfiguration.getScaledTouchSlop();
mTapTimeoutInMillis = ViewConfiguration.getTapTimeout();
mImm = inputMethodManager;
@@ -98,7 +102,7 @@
* @param motionEvent the stylus MotionEvent.
*/
@VisibleForTesting
- public void onTouchEvent(MotionEvent motionEvent) {
+ public void onTouchEvent(@NonNull MotionEvent motionEvent) {
final int maskedAction = motionEvent.getActionMasked();
switch (maskedAction) {
case MotionEvent.ACTION_DOWN:
@@ -151,11 +155,20 @@
final float y = motionEvent.getY(pointerIndex);
if (largerThanTouchSlop(x, y, mState.mStylusDownX, mState.mStylusDownY)) {
mState.mExceedTouchSlop = true;
- tryStartHandwriting();
+ View candidateView =
+ findBestCandidateView(mState.mStylusDownX, mState.mStylusDownY);
+ if (candidateView != null) {
+ if (candidateView == getConnectedView()) {
+ startHandwriting(candidateView);
+ } else {
+ candidateView.requestFocus();
+ }
+ }
}
}
}
+ @Nullable
private View getConnectedView() {
if (mConnectedView == null) return null;
return mConnectedView.get();
@@ -178,13 +191,16 @@
clearConnectedView();
return;
}
+
final View connectedView = getConnectedView();
if (connectedView == view) {
++mConnectionCount;
} else {
mConnectedView = new WeakReference<>(view);
mConnectionCount = 1;
- tryStartHandwriting();
+ if (mState.mShouldInitHandwriting) {
+ tryStartHandwriting();
+ }
}
}
@@ -233,17 +249,10 @@
return;
}
- final ViewParent viewParent = connectedView.getParent();
- // Do a final check before startHandwriting.
- if (viewParent != null && connectedView.isAttachedToWindow()) {
- final Rect editorBounds =
- new Rect(0, 0, connectedView.getWidth(), connectedView.getHeight());
- if (viewParent.getChildVisibleRect(connectedView, editorBounds, null)) {
- final int roundedInitX = Math.round(mState.mStylusDownX);
- final int roundedInitY = Math.round(mState.mStylusDownY);
- if (editorBounds.contains(roundedInitX, roundedInitY)) {
- startHandwriting(connectedView);
- }
+ Rect handwritingArea = getViewHandwritingArea(connectedView);
+ if (handwritingArea != null) {
+ if (contains(handwritingArea, mState.mStylusDownX, mState.mStylusDownY)) {
+ startHandwriting(connectedView);
}
}
reset();
@@ -251,10 +260,79 @@
/** For test only. */
@VisibleForTesting
- public void startHandwriting(View view) {
+ public void startHandwriting(@NonNull View view) {
mImm.startStylusHandwriting(view);
}
+ /**
+ * Notify that the handwriting area for the given view might be updated.
+ * @param view the view whose handwriting area might be updated.
+ */
+ public void updateHandwritingAreasForView(@NonNull View view) {
+ mHandwritingAreasTracker.updateHandwritingAreaForView(view);
+ }
+
+ /**
+ * Given the location of the stylus event, return the best candidate view to initialize
+ * handwriting mode.
+ *
+ * @param x the x coordinates of the stylus event, in the coordinates of the window.
+ * @param y the y coordinates of the stylus event, in the coordinates of the window.
+ */
+ @Nullable
+ private View findBestCandidateView(float x, float y) {
+ // If the connectedView is not null and do not set any handwriting area, it will check
+ // whether the connectedView's boundary contains the initial stylus position. If true,
+ // directly return the connectedView.
+ final View connectedView = getConnectedView();
+ if (connectedView != null && connectedView.isAutoHandwritingEnabled()) {
+ final Rect handwritingArea = getViewHandwritingArea(connectedView);
+ if (handwritingArea != null && contains(handwritingArea, x, y)) {
+ return connectedView;
+ }
+ }
+
+ // Check the registered handwriting areas.
+ final List<HandwritableViewInfo> handwritableViewInfos =
+ mHandwritingAreasTracker.computeViewInfos();
+ for (HandwritableViewInfo viewInfo : handwritableViewInfos) {
+ final View view = viewInfo.getView();
+ if (!view.isAutoHandwritingEnabled()) continue;
+ final Rect rect = viewInfo.getHandwritingArea();
+ if (rect != null && contains(rect, x, y)) {
+ return viewInfo.getView();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return the handwriting area of the given view, represented in the window's coordinate.
+ * If the view didn't set any handwriting area, it will return the view's boundary.
+ * It will return null if the view or its handwriting area is not visible.
+ */
+ @Nullable
+ private static Rect getViewHandwritingArea(@NonNull View view) {
+ final ViewParent viewParent = view.getParent();
+ if (viewParent != null && view.isAttachedToWindow() && view.isAggregatedVisible()) {
+ Rect handwritingArea = view.getHandwritingArea();
+ if (handwritingArea == null) {
+ handwritingArea = new Rect(0, 0, view.getWidth(), view.getHeight());
+ }
+ if (viewParent.getChildVisibleRect(view, handwritingArea, null)) {
+ return handwritingArea;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return true if the (x, y) is inside by the given {@link Rect}.
+ */
+ private boolean contains(@NonNull Rect rect, float x, float y) {
+ return x >= rect.left && x < rect.right && y >= rect.top && y < rect.bottom;
+ }
+
private boolean largerThanTouchSlop(float x1, float y1, float x2, float y2) {
float dx = x1 - x2;
float dy = y1 - y2;
@@ -291,4 +369,134 @@
private float mStylusDownX = Float.NaN;
private float mStylusDownY = Float.NaN;
}
+
+ /** The helper method to check if the given view is still active for handwriting. */
+ private static boolean isViewActive(@Nullable View view) {
+ return view != null && view.isAttachedToWindow() && view.isAggregatedVisible()
+ && view.isAutoHandwritingEnabled();
+ }
+
+ /**
+ * A class used to track the handwriting areas set by the Views.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static class HandwritingAreaTracker {
+ private final List<HandwritableViewInfo> mHandwritableViewInfos;
+
+ public HandwritingAreaTracker() {
+ mHandwritableViewInfos = new ArrayList<>();
+ }
+
+ /**
+ * Notify this tracker that the handwriting area of the given view has been updated.
+ * This method does three things:
+ * a) iterate over the all the tracked ViewInfos and remove those already invalid ones.
+ * b) mark the given view's ViewInfo to be dirty. So that next time when
+ * {@link #computeViewInfos} is called, this view's handwriting area will be recomputed.
+ * c) If no the given view is not in the tracked ViewInfo list, a new ViewInfo object will
+ * be created and added to the list.
+ *
+ * @param view the view whose handwriting area is updated.
+ */
+ public void updateHandwritingAreaForView(@NonNull View view) {
+ Iterator<HandwritableViewInfo> iterator = mHandwritableViewInfos.iterator();
+ boolean found = false;
+ while (iterator.hasNext()) {
+ final HandwritableViewInfo handwritableViewInfo = iterator.next();
+ final View curView = handwritableViewInfo.getView();
+ if (!isViewActive(curView)) {
+ iterator.remove();
+ }
+ if (curView == view) {
+ found = true;
+ handwritableViewInfo.mIsDirty = true;
+ }
+ }
+ if (!found && isViewActive(view)) {
+ // The given view is not tracked. Create a new HandwritableViewInfo for it and add
+ // to the list.
+ mHandwritableViewInfos.add(new HandwritableViewInfo(view));
+ }
+ }
+
+ /**
+ * Update the handwriting areas and return a list of ViewInfos containing the view
+ * reference and its handwriting area.
+ */
+ @NonNull
+ public List<HandwritableViewInfo> computeViewInfos() {
+ mHandwritableViewInfos.removeIf(viewInfo -> !viewInfo.update());
+ return mHandwritableViewInfos;
+ }
+ }
+
+ /**
+ * A class that reference to a View and its handwriting area(in the ViewRoot's coordinate.)
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static class HandwritableViewInfo {
+ final WeakReference<View> mViewRef;
+ Rect mHandwritingArea = null;
+ @VisibleForTesting
+ public boolean mIsDirty = true;
+
+ @VisibleForTesting
+ public HandwritableViewInfo(@NonNull View view) {
+ mViewRef = new WeakReference<>(view);
+ }
+
+ /** Return the tracked view. */
+ @Nullable
+ public View getView() {
+ return mViewRef.get();
+ }
+
+ /**
+ * Return the tracked handwriting area, represented in the ViewRoot's coordinates.
+ * Notice, the caller should not modify the returned Rect.
+ */
+ @Nullable
+ public Rect getHandwritingArea() {
+ return mHandwritingArea;
+ }
+
+ /**
+ * Update the handwriting area in this ViewInfo.
+ *
+ * @return true if this ViewInfo is still valid. Or false if this ViewInfo has become
+ * invalid due to either view is no longer visible, or the handwriting area set by the
+ * view is removed. {@link HandwritingAreaTracker} no longer need to keep track of this
+ * HandwritableViewInfo this method returns false.
+ */
+ public boolean update() {
+ final View view = getView();
+ if (!isViewActive(view)) {
+ return false;
+ }
+
+ if (!mIsDirty) {
+ return true;
+ }
+ final Rect localRect = view.getHandwritingArea();
+ if (localRect == null) {
+ return false;
+ }
+
+ ViewParent parent = view.getParent();
+ if (parent != null) {
+ final Rect newRect = new Rect(localRect);
+ if (parent.getChildVisibleRect(view, newRect, null /* offset */)) {
+ mHandwritingArea = newRect;
+ } else {
+ mHandwritingArea = null;
+ }
+ }
+ mIsDirty = false;
+ return true;
+ }
+ }
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 32054b1..1ed35f7 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -298,7 +298,7 @@
*/
void grantInputChannel(int displayId, in SurfaceControl surface, in IWindow window,
in IBinder hostInputToken, int flags, int privateFlags, int type,
- in IBinder focusGrantToken, out InputChannel outInputChannel);
+ in IBinder focusGrantToken, String inputHandleName, out InputChannel outInputChannel);
/**
* Update the flags on an input channel associated with a particular surface.
diff --git a/core/java/android/view/OnBackInvokedCallback.java b/core/java/android/view/OnBackInvokedCallback.java
index b5cd89c..37f858a 100644
--- a/core/java/android/view/OnBackInvokedCallback.java
+++ b/core/java/android/view/OnBackInvokedCallback.java
@@ -18,6 +18,7 @@
import android.app.Activity;
import android.app.Dialog;
+import android.window.BackEvent;
/**
* Interface for applications to register back invocation callbacks. This allows the client
@@ -46,14 +47,12 @@
/**
* Called on back gesture progress.
*
- * @param touchX Absolute X location of the touch point.
- * @param touchY Absolute Y location of the touch point.
- * @param progress Value between 0 and 1 on how far along the back gesture is.
+ * @param backEvent An {@link android.window.BackEvent} object describing the progress event.
*
+ * @see android.window.BackEvent
* @hide
*/
- // TODO(b/210539672): combine back progress params into BackEvent.
- default void onBackProgressed(int touchX, int touchY, float progress) { };
+ default void onBackProgressed(BackEvent backEvent) { };
/**
* Called when a back gesture or back button press has been cancelled.
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index d3061e2..ce54968 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -49,6 +49,7 @@
import android.hardware.display.DeviceProductInfo;
import android.hardware.display.DisplayedContentSample;
import android.hardware.display.DisplayedContentSamplingAttributes;
+import android.hardware.graphics.common.DisplayDecorationSupport;
import android.os.Build;
import android.os.IBinder;
import android.os.Parcel;
@@ -235,7 +236,8 @@
float shadowRadius);
private static native void nativeSetGlobalShadowSettings(@Size(4) float[] ambientColor,
@Size(4) float[] spotColor, float lightPosY, float lightPosZ, float lightRadius);
- private static native boolean nativeGetDisplayDecorationSupport(IBinder displayToken);
+ private static native DisplayDecorationSupport nativeGetDisplayDecorationSupport(
+ IBinder displayToken);
private static native void nativeSetFrameRate(long transactionObj, long nativeObject,
float frameRate, int compatibility, int changeFrameRateStrategy);
@@ -2694,16 +2696,18 @@
}
/**
- * Returns whether a display supports DISPLAY_DECORATION.
+ * Returns whether/how a display supports DISPLAY_DECORATION.
*
* @param displayToken
* The token for the display.
*
- * @return Whether the display supports DISPLAY_DECORATION.
+ * @return A class describing how the display supports DISPLAY_DECORATION or null if it does
+ * not.
*
+ * TODO (b/218524164): Move this out of SurfaceControl.
* @hide
*/
- public static boolean getDisplayDecorationSupport(IBinder displayToken) {
+ public static DisplayDecorationSupport getDisplayDecorationSupport(IBinder displayToken) {
return nativeGetDisplayDecorationSupport(displayToken);
}
@@ -3696,21 +3700,32 @@
/**
* Sets the buffer transform that should be applied to the current buffer.
*
+ * This can be used in combination with
+ * {@link AttachedSurfaceControl#addOnBufferTransformHintChangedListener(AttachedSurfaceControl.OnBufferTransformHintChangedListener)}
+ * to pre-rotate the buffer for the current display orientation. This can
+ * improve the performance of displaying the associated buffer.
+ *
* @param sc The SurfaceControl to update
* @param transform The transform to apply to the buffer.
* @return this
*/
public @NonNull Transaction setBufferTransform(@NonNull SurfaceControl sc,
- /* TODO: Mark the intdef */ int transform) {
+ @SurfaceControl.BufferTransform int transform) {
checkPreconditions(sc);
nativeSetBufferTransform(mNativeObject, sc.mNativeObject, transform);
return this;
}
/**
- * Updates the region for the content on this surface updated in this transaction.
+ * Updates the region for the content on this surface updated in this transaction. The
+ * damage region is the area of the buffer that has changed since the previously
+ * sent buffer. This can be used to reduce the amount of recomposition that needs
+ * to happen when only a small region of the buffer is being updated, such as for
+ * a small blinking cursor or a loading indicator.
*
- * If unspecified, the complete surface is assumed to be damaged.
+ * @param sc The SurfaceControl on which to set the damage region
+ * @param region The region to set. If null, the entire buffer is assumed dirty. This is
+ * equivalent to not setting a damage region at all.
*/
public @NonNull Transaction setDamageRegion(@NonNull SurfaceControl sc,
@Nullable Region region) {
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 7e0d887..2edfda5 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -27,8 +27,10 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
-import android.view.accessibility.IAccessibilityEmbeddedConnection;
+import android.util.Log;
import android.view.InsetsState;
+import android.view.WindowManagerGlobal;
+import android.view.accessibility.IAccessibilityEmbeddedConnection;
import java.util.Objects;
@@ -43,11 +45,13 @@
* {@link SurfaceView#setChildSurfacePackage}.
*/
public class SurfaceControlViewHost {
+ private final static String TAG = "SurfaceControlViewHost";
private final ViewRootImpl mViewRoot;
private WindowlessWindowManager mWm;
private SurfaceControl mSurfaceControl;
private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
+ private boolean mReleased = false;
private final class ISurfaceControlViewHostImpl extends ISurfaceControlViewHost.Stub {
@Override
@@ -268,6 +272,8 @@
@NonNull WindowlessWindowManager wwm, boolean useSfChoreographer) {
mWm = wwm;
mViewRoot = new ViewRootImpl(c, d, mWm, useSfChoreographer);
+ WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
+
mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection();
}
@@ -292,7 +298,10 @@
.build();
mWm = new WindowlessWindowManager(context.getResources().getConfiguration(),
mSurfaceControl, hostToken);
+
mViewRoot = new ViewRootImpl(context, display, mWm);
+ WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
+
mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection();
}
@@ -301,12 +310,15 @@
*/
@Override
protected void finalize() throws Throwable {
- // We aren't on the UI thread here so we need to pass false to
- // doDie
+ if (mReleased) {
+ return;
+ }
+ Log.e(TAG, "SurfaceControlViewHost finalized without being released: " + this);
+ // We aren't on the UI thread here so we need to pass false to doDie
mViewRoot.die(false /* immediate */);
+ WindowManagerGlobal.getInstance().removeWindowlessRoot(mViewRoot);
}
-
/**
* Return a SurfacePackage for the root SurfaceControl of the embedded hierarchy.
* Rather than be directly reparented using {@link SurfaceControl.Transaction} this
@@ -413,5 +425,14 @@
public void release() {
// ViewRoot will release mSurfaceControl for us.
mViewRoot.die(true /* immediate */);
+ WindowManagerGlobal.getInstance().removeWindowlessRoot(mViewRoot);
+ mReleased = true;
+ }
+
+ /**
+ * @hide
+ */
+ public IBinder getFocusGrantToken() {
+ return mWm.getFocusGrantToken();
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f74b599..179f6ee 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4745,9 +4745,11 @@
private List<Rect> mSystemGestureExclusionRects = null;
private List<Rect> mKeepClearRects = null;
private boolean mPreferKeepClear = false;
+ private Rect mHandwritingArea = null;
/**
- * Used to track {@link #mSystemGestureExclusionRects} and {@link #mKeepClearRects}
+ * Used to track {@link #mSystemGestureExclusionRects}, {@link #mKeepClearRects} and
+ * {@link #mHandwritingArea}.
*/
public RenderNode.PositionUpdateListener mPositionUpdateListener;
private Runnable mPositionChangedUpdate;
@@ -11710,7 +11712,8 @@
private void updatePositionUpdateListener() {
final ListenerInfo info = getListenerInfo();
if (getSystemGestureExclusionRects().isEmpty()
- && collectPreferKeepClearRects().isEmpty()) {
+ && collectPreferKeepClearRects().isEmpty()
+ && (info.mHandwritingArea == null || !isAutoHandwritingEnabled())) {
if (info.mPositionUpdateListener != null) {
mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener);
info.mPositionChangedUpdate = null;
@@ -11720,6 +11723,7 @@
info.mPositionChangedUpdate = () -> {
updateSystemGestureExclusionRects();
updateKeepClearRects();
+ updateHandwritingArea();
};
info.mPositionUpdateListener = new RenderNode.PositionUpdateListener() {
@Override
@@ -11876,6 +11880,51 @@
}
/**
+ * Set a list of handwriting areas in this view. If there is any stylus {@link MotionEvent}
+ * occurs within those areas, it will trigger stylus handwriting mode. This can be disabled by
+ * disabling the auto handwriting initiation by calling
+ * {@link #setAutoHandwritingEnabled(boolean)} with false.
+ *
+ * @attr rects a list of handwriting area in the view's local coordiniates.
+ *
+ * @see android.view.inputmethod.InputMethodManager#startStylusHandwriting(View)
+ * @see #setAutoHandwritingEnabled(boolean)
+ *
+ * @hide
+ */
+ public void setHandwritingArea(@Nullable Rect rect) {
+ final ListenerInfo info = getListenerInfo();
+ info.mHandwritingArea = rect;
+ updatePositionUpdateListener();
+ postUpdate(this::updateHandwritingArea);
+ }
+
+ /**
+ * Return the handwriting areas set on this view, in its local coordinates.
+ * Notice: the caller of this method should not modify the Rect returned.
+ * @see #setHandwritingArea(Rect)
+ *
+ * @hide
+ */
+ @Nullable
+ public Rect getHandwritingArea() {
+ final ListenerInfo info = mListenerInfo;
+ if (info != null) {
+ return info.mHandwritingArea;
+ }
+ return null;
+ }
+
+ void updateHandwritingArea() {
+ // If autoHandwritingArea is not enabled, do nothing.
+ if (!isAutoHandwritingEnabled()) return;
+ final AttachInfo ai = mAttachInfo;
+ if (ai != null) {
+ ai.mViewRootImpl.getHandwritingInitiator().updateHandwritingAreasForView(this);
+ }
+ }
+
+ /**
* Compute the view's coordinate within the surface.
*
* <p>Computes the coordinates of this view in its surface. The argument
@@ -31181,6 +31230,8 @@
} else {
mPrivateFlags4 &= ~PFLAG4_AUTO_HANDWRITING_ENABLED;
}
+ updatePositionUpdateListener();
+ postUpdate(this::updateHandwritingArea);
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 39fc2c0..8236fbb 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -583,6 +583,12 @@
boolean mForceNextWindowRelayout;
CountDownLatch mWindowDrawCountDown;
+ // Whether we have used applyTransactionOnDraw to schedule an RT
+ // frame callback consuming a passed in transaction. In this case
+ // we also need to schedule a commit callback so we can observe
+ // if the draw was skipped, and the BBQ pending transactions.
+ boolean mHasPendingTransactions;
+
boolean mIsDrawing;
int mLastSystemUiVisibility;
int mClientWindowLayoutFlags;
@@ -1788,10 +1794,16 @@
}
void pokeDrawLockIfNeeded() {
- final int displayState = mAttachInfo.mDisplayState;
- if (mView != null && mAdded && mTraversalScheduled
- && (displayState == Display.STATE_DOZE
- || displayState == Display.STATE_DOZE_SUSPEND)) {
+ if (!Display.isDozeState(mAttachInfo.mDisplayState)) {
+ // Only need to acquire wake lock for DOZE state.
+ return;
+ }
+ if (mWindowAttributes.type != WindowManager.LayoutParams.TYPE_BASE_APPLICATION) {
+ // Non-activity windows should be responsible to hold wake lock by themself, because
+ // usually they are system windows.
+ return;
+ }
+ if (mAdded && mTraversalScheduled && mAttachInfo.mHasWindowFocus) {
try {
mWindowSession.pokeDrawLock(mWindow);
} catch (RemoteException ex) {
@@ -4184,6 +4196,9 @@
mRtLastAttemptedDrawFrameNum);
tmpTransaction.merge(pendingTransactions);
}
+ if (!useBlastSync && !reportNextDraw) {
+ tmpTransaction.apply();
+ }
// Post at front of queue so the buffer can be processed immediately and allow RT
// to continue processing new buffers. If RT tries to process buffers before the sync
@@ -4214,7 +4229,7 @@
final boolean hasBlurUpdates = mBlurRegionAggregator.hasUpdates();
final boolean needsCallbackForBlur = hasBlurUpdates || mBlurRegionAggregator.hasRegions();
- if (!useBlastSync && !needsCallbackForBlur && !reportNextDraw) {
+ if (!useBlastSync && !needsCallbackForBlur && !reportNextDraw && !mHasPendingTransactions) {
return null;
}
@@ -4226,11 +4241,14 @@
+ " nextDrawUseBlastSync=" + useBlastSync
+ " reportNextDraw=" + reportNextDraw
+ " hasBlurUpdates=" + hasBlurUpdates
- + " hasBlastSyncConsumer=" + (blastSyncConsumer != null));
+ + " hasBlastSyncConsumer=" + (blastSyncConsumer != null)
+ + " mHasPendingTransactions=" + mHasPendingTransactions);
}
final BackgroundBlurDrawable.BlurRegion[] blurRegionsForFrame =
needsCallbackForBlur ? mBlurRegionAggregator.getBlurRegionsCopyForRT() : null;
+ final boolean hasPendingTransactions = mHasPendingTransactions;
+ mHasPendingTransactions = false;
// The callback will run on the render thread.
return new FrameDrawingCallback() {
@@ -4257,7 +4275,7 @@
return null;
}
- if (!useBlastSync && !reportNextDraw) {
+ if (!useBlastSync && !reportNextDraw && !hasPendingTransactions) {
return null;
}
@@ -10700,7 +10718,10 @@
if (mRemoved || !isHardwareEnabled()) {
t.apply();
} else {
- registerRtFrameCallback(frame -> mergeWithNextTransaction(t, frame));
+ mHasPendingTransactions = true;
+ registerRtFrameCallback(frame -> {
+ mergeWithNextTransaction(t, frame);
+ });
}
return true;
}
@@ -10841,6 +10862,7 @@
private void unregisterCompatOnBackInvokedCallback() {
if (mCompatOnBackInvokedCallback != null) {
mOnBackInvokedDispatcher.unregisterOnBackInvokedCallback(mCompatOnBackInvokedCallback);
+ mCompatOnBackInvokedCallback = null;
}
}
@@ -10854,4 +10876,8 @@
mLastGivenInsets.reset();
requestLayout();
}
+
+ IWindowSession getWindowSession() {
+ return mWindowSession;
+ }
}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index c92a3a0..93cb0dd7 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -159,6 +159,8 @@
new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();
+ private final ArrayList<ViewRootImpl> mWindowlessRoots = new ArrayList<ViewRootImpl>();
+
private Runnable mSystemPropertyUpdater;
private WindowManagerGlobal() {
@@ -387,7 +389,25 @@
}
}
- root = new ViewRootImpl(view.getContext(), display);
+ IWindowSession windowlessSession = null;
+ // If there is a parent set, but we can't find it, it may be coming
+ // from a SurfaceControlViewHost hierarchy.
+ if (wparams.token != null && panelParentView == null) {
+ for (int i = 0; i < mWindowlessRoots.size(); i++) {
+ ViewRootImpl maybeParent = mWindowlessRoots.get(i);
+ if (maybeParent.getWindowToken() == wparams.token) {
+ windowlessSession = maybeParent.getWindowSession();
+ break;
+ }
+ }
+ }
+
+ if (windowlessSession == null) {
+ root = new ViewRootImpl(view.getContext(), display);
+ } else {
+ root = new ViewRootImpl(view.getContext(), display,
+ windowlessSession);
+ }
view.setLayoutParams(wparams);
@@ -720,6 +740,20 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /** @hide */
+ public void addWindowlessRoot(ViewRootImpl impl) {
+ synchronized (mLock) {
+ mWindowlessRoots.add(impl);
+ }
+ }
+
+ /** @hide */
+ public void removeWindowlessRoot(ViewRootImpl impl) {
+ synchronized (mLock) {
+ mWindowlessRoots.remove(impl);
+ }
+ }
}
final class WindowLeaked extends AndroidRuntimeException {
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 56f0915..2122152 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -160,14 +160,15 @@
if (((attrs.inputFeatures &
WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0)) {
try {
- if(mRealWm instanceof IWindowSession.Stub) {
+ if (mRealWm instanceof IWindowSession.Stub) {
mRealWm.grantInputChannel(displayId,
- new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"),
- window, mHostInputToken, attrs.flags, attrs.privateFlags, attrs.type,
- mFocusGrantToken, outInputChannel);
+ new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"),
+ window, mHostInputToken, attrs.flags, attrs.privateFlags, attrs.type,
+ mFocusGrantToken, attrs.getTitle().toString(), outInputChannel);
} else {
mRealWm.grantInputChannel(displayId, sc, window, mHostInputToken, attrs.flags,
- attrs.privateFlags, attrs.type, mFocusGrantToken, outInputChannel);
+ attrs.privateFlags, attrs.type, mFocusGrantToken,
+ attrs.getTitle().toString(), outInputChannel);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to grant input to surface: ", e);
@@ -485,7 +486,7 @@
@Override
public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window,
IBinder hostInputToken, int flags, int privateFlags, int type, IBinder focusGrantToken,
- InputChannel outInputChannel) {
+ String inputHandleName, InputChannel outInputChannel) {
}
@Override
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 82e823f..07b7a18 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -19,6 +19,8 @@
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CLIENT;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK;
import static android.os.Build.VERSION_CODES.S;
+import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_MASK;
+import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.annotation.NonNull;
@@ -301,10 +303,11 @@
* @param connectionId The id of a connection for interacting with the system.
* @return The root {@link AccessibilityNodeInfo} if found, null otherwise.
*/
- public AccessibilityNodeInfo getRootInActiveWindow(int connectionId) {
+ public AccessibilityNodeInfo getRootInActiveWindow(int connectionId,
+ @AccessibilityNodeInfo.PrefetchingStrategy int strategy) {
return findAccessibilityNodeInfoByAccessibilityId(connectionId,
AccessibilityWindowInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID,
- false, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS, null);
+ false, strategy, null);
}
/**
@@ -529,11 +532,6 @@
public @Nullable AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
int connectionId, int accessibilityWindowId, long accessibilityNodeId,
boolean bypassCache, int prefetchFlags, Bundle arguments) {
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0
- && (prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) == 0) {
- throw new IllegalArgumentException("FLAG_PREFETCH_SIBLINGS"
- + " requires FLAG_PREFETCH_PREDECESSORS");
- }
try {
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
@@ -560,7 +558,7 @@
}
if (!cache.isEnabled()) {
// Skip prefetching if cache is disabled.
- prefetchFlags &= ~AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
+ prefetchFlags &= ~FLAG_PREFETCH_MASK;
}
if (DEBUG) {
Log.i(LOG_TAG, "Node cache miss for "
@@ -573,12 +571,18 @@
}
} else {
// No need to prefech nodes in bypass cache case.
- prefetchFlags &= ~AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
+ prefetchFlags &= ~FLAG_PREFETCH_MASK;
}
// Skip prefetching if window is scrolling.
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0
+ if ((prefetchFlags & FLAG_PREFETCH_MASK) != 0
&& isWindowScrolling(accessibilityWindowId)) {
- prefetchFlags &= ~AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
+ prefetchFlags &= ~FLAG_PREFETCH_MASK;
+ }
+
+ final int descendantPrefetchFlags = prefetchFlags & FLAG_PREFETCH_DESCENDANTS_MASK;
+ if ((descendantPrefetchFlags & (descendantPrefetchFlags - 1)) != 0) {
+ throw new IllegalArgumentException("There can be no more than one descendant"
+ + " prefetching strategy");
}
final int interactionId = mInteractionIdCounter.getAndIncrement();
if (shouldTraceClient()) {
@@ -599,21 +603,41 @@
Binder.restoreCallingIdentity(identityToken);
}
if (packageNames != null) {
- AccessibilityNodeInfo info =
- getFindAccessibilityNodeInfoResultAndClear(interactionId);
- if (shouldTraceCallback()) {
- logTraceCallback(connection, "findAccessibilityNodeInfoByAccessibilityId",
- "InteractionId:" + interactionId + ";connectionId="
- + connectionId + ";Result: " + info);
+ if ((prefetchFlags
+ & AccessibilityNodeInfo.FLAG_PREFETCH_UNINTERRUPTIBLE) != 0) {
+ List<AccessibilityNodeInfo> infos =
+ getFindAccessibilityNodeInfosResultAndClear(
+ interactionId);
+ if (shouldTraceCallback()) {
+ logTraceCallback(connection,
+ "findAccessibilityNodeInfoByAccessibilityId",
+ "InteractionId:" + interactionId + ";connectionId="
+ + connectionId + ";Result: " + infos);
+ }
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId,
+ bypassCache, packageNames);
+ if (infos != null && !infos.isEmpty()) {
+ return infos.get(0);
+ }
+ } else {
+ AccessibilityNodeInfo info =
+ getFindAccessibilityNodeInfoResultAndClear(interactionId);
+ if (shouldTraceCallback()) {
+ logTraceCallback(connection,
+ "findAccessibilityNodeInfoByAccessibilityId",
+ "InteractionId:" + interactionId + ";connectionId="
+ + connectionId + ";Result: " + info);
+ }
+ if ((prefetchFlags & FLAG_PREFETCH_MASK) != 0
+ && info != null) {
+ setInteractionWaitingForPrefetchResult(interactionId, connectionId,
+ packageNames);
+ }
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId,
+ bypassCache, packageNames);
+ return info;
}
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0
- && info != null) {
- setInteractionWaitingForPrefetchResult(interactionId, connectionId,
- packageNames);
- }
- finalizeAndCacheAccessibilityNodeInfo(info, connectionId,
- bypassCache, packageNames);
- return info;
+
}
} else {
if (DEBUG) {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index a31cacf..aeef76c 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -23,8 +23,10 @@
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
@@ -62,6 +64,8 @@
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -128,23 +132,99 @@
public static final long LEASHED_NODE_ID = makeNodeId(LEASHED_ITEM_ID,
AccessibilityNodeProvider.HOST_VIEW_ID);
- /** @hide */
- public static final int FLAG_PREFETCH_PREDECESSORS = 0x00000001;
+ /**
+ * Prefetching strategy that prefetches the ancestors of the requested node.
+ * <p> Ancestors will be prefetched before siblings and descendants.
+ *
+ * @see #getChild(int, int)
+ * @see #getParent(int)
+ * @see AccessibilityWindowInfo#getRoot(int)
+ * @see AccessibilityService#getRootInActiveWindow(int)
+ * @see AccessibilityEvent#getSource(int)
+ */
+ public static final int FLAG_PREFETCH_ANCESTORS = 0x00000001;
- /** @hide */
+ /**
+ * Prefetching strategy that prefetches the siblings of the requested node.
+ * <p> To avoid disconnected trees, this flag will also prefetch the parent. Siblings will be
+ * prefetched before descendants.
+ *
+ * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
+ */
public static final int FLAG_PREFETCH_SIBLINGS = 0x00000002;
- /** @hide */
- public static final int FLAG_PREFETCH_DESCENDANTS = 0x00000004;
+ /**
+ * Prefetching strategy that prefetches the descendants in a hybrid depth first and breadth
+ * first approach.
+ * <p> The children of the root node is prefetched before recursing on the children. This
+ * must not be combined with {@link #FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST} or
+ * {@link #FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST} or this will trigger an
+ * IllegalArgumentException.
+ *
+ * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
+ */
+ public static final int FLAG_PREFETCH_DESCENDANTS_HYBRID = 0x00000004;
+
+ /**
+ * Prefetching strategy that prefetches the descendants of the requested node depth-first.
+ * <p> This must not be combined with {@link #FLAG_PREFETCH_DESCENDANTS_HYBRID} or
+ * {@link #FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST} or this will trigger an
+ * IllegalArgumentException.
+ *
+ * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
+ */
+ public static final int FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST = 0x00000008;
+
+ /**
+ * Prefetching strategy that prefetches the descendants of the requested node breadth-first.
+ * <p> This must not be combined with {@link #FLAG_PREFETCH_DESCENDANTS_HYBRID} or
+ * {@link #FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST} or this will trigger an
+ * IllegalArgumentException.
+ *
+ * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
+ */
+ public static final int FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST = 0x00000010;
+
+ /**
+ * Prefetching flag that specifies prefetching should not be interrupted by a request to
+ * retrieve a node or perform an action on a node.
+ *
+ * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
+ */
+ public static final int FLAG_PREFETCH_UNINTERRUPTIBLE = 0x00000020;
/** @hide */
- public static final int FLAG_PREFETCH_MASK = 0x00000007;
+ public static final int FLAG_PREFETCH_MASK = 0x0000003f;
/** @hide */
- public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000008;
+ public static final int FLAG_PREFETCH_DESCENDANTS_MASK = 0x0000001C;
+
+ /**
+ * Maximum batch size of prefetched nodes for a request.
+ */
+ @SuppressLint("MinMaxConstant")
+ public static final int MAX_NUMBER_OF_PREFETCHED_NODES = 50;
/** @hide */
- public static final int FLAG_REPORT_VIEW_IDS = 0x00000010;
+ @IntDef(flag = true, prefix = { "FLAG_PREFETCH" }, value = {
+ FLAG_PREFETCH_ANCESTORS,
+ FLAG_PREFETCH_SIBLINGS,
+ FLAG_PREFETCH_DESCENDANTS_HYBRID,
+ FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST,
+ FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST,
+ FLAG_PREFETCH_UNINTERRUPTIBLE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PrefetchingStrategy {}
+
+ /** @hide */
+ public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000080;
+
+ /** @hide */
+ public static final int FLAG_REPORT_VIEW_IDS = 0x00000100;
+
+ /** @hide */
+ public static final int FLAG_REPORT_MASK = 0x00000180;
// Actions.
@@ -1079,11 +1159,6 @@
/**
* Get the child at given index.
- * <p>
- * <strong>Note:</strong> It is a client responsibility to recycle the
- * received info by calling {@link AccessibilityNodeInfo#recycle()}
- * to avoid creating of multiple instances.
- * </p>
*
* @param index The child index.
* @return The child node.
@@ -1092,6 +1167,23 @@
*
*/
public AccessibilityNodeInfo getChild(int index) {
+ return getChild(index, FLAG_PREFETCH_DESCENDANTS_HYBRID);
+ }
+
+
+ /**
+ * Get the child at given index.
+ *
+ * @param index The child index.
+ * @param prefetchingStrategy the prefetching strategy.
+ * @return The child node.
+ *
+ * @throws IllegalStateException If called outside of an AccessibilityService.
+ *
+ * @see AccessibilityNodeInfo#getParent(int) for a description of prefetching.
+ */
+ @Nullable
+ public AccessibilityNodeInfo getChild(int index, @PrefetchingStrategy int prefetchingStrategy) {
enforceSealed();
if (mChildNodeIds == null) {
return null;
@@ -1103,11 +1195,11 @@
final AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
if (mLeashedChild != null && childId == LEASHED_NODE_ID) {
return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mLeashedChild,
- ROOT_NODE_ID, false, FLAG_PREFETCH_DESCENDANTS, null);
+ ROOT_NODE_ID, false, prefetchingStrategy, null);
}
return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId,
- childId, false, FLAG_PREFETCH_DESCENDANTS, null);
+ childId, false, prefetchingStrategy, null);
}
/**
@@ -1816,23 +1908,56 @@
/**
* Gets the parent.
- * <p>
- * <strong>Note:</strong> It is a client responsibility to recycle the
- * received info by calling {@link AccessibilityNodeInfo#recycle()}
- * to avoid creating of multiple instances.
- * </p>
*
* @return The parent.
*/
public AccessibilityNodeInfo getParent() {
enforceSealed();
if (mLeashedParent != null && mLeashedParentNodeId != UNDEFINED_NODE_ID) {
- return getNodeForAccessibilityId(mConnectionId, mLeashedParent, mLeashedParentNodeId);
+ return getNodeForAccessibilityId(mConnectionId, mLeashedParent, mLeashedParentNodeId,
+ FLAG_PREFETCH_ANCESTORS | FLAG_PREFETCH_SIBLINGS);
}
return getNodeForAccessibilityId(mConnectionId, mWindowId, mParentNodeId);
}
/**
+ * Gets the parent.
+ *
+ * <p>
+ * Use {@code prefetchingStrategy} to determine the types of
+ * nodes prefetched from the app if the requested node is not in the cache and must be retrieved
+ * by the app. The default strategy for {@link #getParent()} is a combination of ancestor and
+ * sibling strategies. The app will prefetch until all nodes fulfilling the strategies are
+ * fetched, another node request is sent, or the maximum prefetch batch size of
+ * {@link #MAX_NUMBER_OF_PREFETCHED_NODES} nodes is reached. To prevent interruption by another
+ * request and to force prefetching of the max batch size, use
+ * {@link AccessibilityNodeInfo#FLAG_PREFETCH_UNINTERRUPTIBLE}.
+ * </p>
+ *
+ * @param prefetchingStrategy the prefetching strategy.
+ * @return The parent.
+ *
+ * @throws IllegalStateException If called outside of an AccessibilityService.
+ *
+ * @see #FLAG_PREFETCH_ANCESTORS
+ * @see #FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST
+ * @see #FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST
+ * @see #FLAG_PREFETCH_DESCENDANTS_HYBRID
+ * @see #FLAG_PREFETCH_SIBLINGS
+ * @see #FLAG_PREFETCH_UNINTERRUPTIBLE
+ */
+ @Nullable
+ public AccessibilityNodeInfo getParent(@PrefetchingStrategy int prefetchingStrategy) {
+ enforceSealed();
+ if (mLeashedParent != null && mLeashedParentNodeId != UNDEFINED_NODE_ID) {
+ return getNodeForAccessibilityId(mConnectionId, mLeashedParent, mLeashedParentNodeId,
+ prefetchingStrategy);
+ }
+ return getNodeForAccessibilityId(mConnectionId, mWindowId, mParentNodeId,
+ prefetchingStrategy);
+ }
+
+ /**
* @return The parent node id.
*
* @hide
@@ -4507,17 +4632,31 @@
private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId,
int windowId, long accessibilityId) {
+ return getNodeForAccessibilityId(connectionId, windowId, accessibilityId,
+ FLAG_PREFETCH_ANCESTORS
+ | FLAG_PREFETCH_DESCENDANTS_HYBRID | FLAG_PREFETCH_SIBLINGS);
+ }
+
+ private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId,
+ int windowId, long accessibilityId, @PrefetchingStrategy int prefetchingStrategy) {
if (!canPerformRequestOverConnection(connectionId, windowId, accessibilityId)) {
return null;
}
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
return client.findAccessibilityNodeInfoByAccessibilityId(connectionId,
- windowId, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS
- | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
+ windowId, accessibilityId, false, prefetchingStrategy, null);
}
private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId,
IBinder leashToken, long accessibilityId) {
+ return getNodeForAccessibilityId(connectionId, leashToken, accessibilityId,
+ FLAG_PREFETCH_ANCESTORS
+ | FLAG_PREFETCH_DESCENDANTS_HYBRID | FLAG_PREFETCH_SIBLINGS);
+ }
+
+ private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId,
+ IBinder leashToken, long accessibilityId,
+ @PrefetchingStrategy int prefetchingStrategy) {
if (!((leashToken != null)
&& (getAccessibilityViewId(accessibilityId) != UNDEFINED_ITEM_ID)
&& (connectionId != UNDEFINED_CONNECTION_ID))) {
@@ -4525,8 +4664,7 @@
}
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
return client.findAccessibilityNodeInfoByAccessibilityId(connectionId,
- leashToken, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS
- | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
+ leashToken, accessibilityId, false, prefetchingStrategy, null);
}
/** @hide */
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index cf2ea15..036316e 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -74,10 +74,9 @@
private static final int PROPERTY_IMPORTANT_FOR_ACCESSIBILITY = 0x00000200;
private static final int GET_SOURCE_PREFETCH_FLAGS =
- AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS
- | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS
- | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS;
-
+ AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS
+ | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS
+ | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID;
@UnsupportedAppUsage
boolean mSealed;
@@ -185,16 +184,30 @@
* @return The info of the source.
*/
public @Nullable AccessibilityNodeInfo getSource() {
+ return getSource(GET_SOURCE_PREFETCH_FLAGS);
+ }
+
+ /**
+ * Gets the {@link AccessibilityNodeInfo} of the event source.
+ *
+ * @param prefetchingStrategy the prefetching strategy.
+ * @return The info of the source.
+ *
+ * @see AccessibilityNodeInfo#getParent(int) for a description of prefetching.
+ */
+ @Nullable
+ public AccessibilityNodeInfo getSource(
+ @AccessibilityNodeInfo.PrefetchingStrategy int prefetchingStrategy) {
enforceSealed();
if ((mConnectionId == UNDEFINED)
|| (mSourceWindowId == AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
|| (AccessibilityNodeInfo.getAccessibilityViewId(mSourceNodeId)
- == AccessibilityNodeInfo.UNDEFINED_ITEM_ID)) {
+ == AccessibilityNodeInfo.UNDEFINED_ITEM_ID)) {
return null;
}
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mSourceWindowId,
- mSourceNodeId, false, GET_SOURCE_PREFETCH_FLAGS, null);
+ mSourceNodeId, false, prefetchingStrategy, null);
}
/**
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index 540f5dc..f155bad 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -219,13 +219,27 @@
* @return The root node.
*/
public AccessibilityNodeInfo getRoot() {
+ return getRoot(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID);
+ }
+
+ /**
+ * Gets the root node in the window's hierarchy.
+ *
+ * @param prefetchingStrategy the prefetching strategy.
+ * @return The root node.
+ *
+ * @see AccessibilityNodeInfo#getParent(int) for a description of prefetching.
+ */
+ @Nullable
+ public AccessibilityNodeInfo getRoot(
+ @AccessibilityNodeInfo.PrefetchingStrategy int prefetchingStrategy) {
if (mConnectionId == UNDEFINED_WINDOW_ID) {
return null;
}
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
mId, AccessibilityNodeInfo.ROOT_NODE_ID,
- true, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS, null);
+ true, prefetchingStrategy, null);
}
/**
@@ -382,6 +396,14 @@
* Gets if this window is active. An active window is the one
* the user is currently touching or the window has input focus
* and the user is not touching any window.
+ * <p>
+ * This is defined as the window that most recently fired one
+ * of the following events:
+ * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED},
+ * {@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER},
+ * {@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}.
+ * In other words, the last window shown that also has input focus.
+ * </p>
*
* @return Whether this is the active window.
*/
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 60ccf67..b7994db 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -3100,11 +3100,51 @@
}
/**
- * Checks the id of autofill whether supported the fill dialog.
+ * If autofill suggestions for a dialog-style UI are available for {@code view}, shows a dialog
+ * allowing the user to select a suggestion and returns {@code true}.
+ * <p>
+ * The dialog may not be available if the autofill service does not support it, or if the
+ * autofill request has not returned a response yet.
+ * <p>
+ * It is recommended to call this method the first time a user focuses on an autofill-able form,
+ * and to avoid showing the input method if the dialog is shown. If this method returns
+ * {@code false}, you should then instead show the input method (assuming that is how the
+ * view normally handles the focus event). If the user re-focuses on the view, you should not
+ * call this method again so as to not disrupt usage of the input method.
*
- * @hide
+ * @param view the view for which to show autofill suggestions. This is typically a view
+ * receiving a focus event. The autofill suggestions shown will include content for
+ * related views as well.
+ * @return {@code true} if the autofill dialog is being shown
*/
- public boolean isShowFillDialog(AutofillId id) {
+ // TODO(b/210926084): Consider whether to include the one-time show logic within this method.
+ public boolean showAutofillDialog(@NonNull View view) {
+ Objects.requireNonNull(view);
+ if (shouldShowAutofillDialog(view.getAutofillId())) {
+ // If the id matches a trigger id, this will trigger the fill dialog.
+ notifyViewEntered(view);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Like {@link #showAutofillDialog(View)} but for virtual views.
+ *
+ * @param virtualId id identifying the virtual child inside the parent view.
+ */
+ // TODO(b/210926084): Consider whether to include the one-time show logic within this method.
+ public boolean showAutofillDialog(@NonNull View view, int virtualId) {
+ Objects.requireNonNull(view);
+ if (shouldShowAutofillDialog(getAutofillId(view, virtualId))) {
+ // If the id matches a trigger id, this will trigger the fill dialog.
+ notifyViewEntered(view, virtualId, /* bounds= */ null, /* flags= */ 0);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean shouldShowAutofillDialog(AutofillId id) {
if (!hasFillDialogUiFeature() || mFillDialogTriggerIds == null) {
return false;
}
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 7e6e6fd..9a70667 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -278,6 +278,7 @@
.InputMethod_Subtype_subtypeId, 0 /* use Arrays.hashCode */))
.setIsAsciiCapable(a.getBoolean(com.android.internal.R.styleable
.InputMethod_Subtype_isAsciiCapable, false)).build();
+ a.recycle();
if (!subtype.isAuxiliary()) {
isAuxIme = false;
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index c713a54..2359d8d 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1764,7 +1764,7 @@
}
/**
- * This method is still kept for a while until android.support.v7.widget.SearchView ver. 26.0
+ * This method is still kept for a while until androidx.appcompat.widget.SearchView ver. 26.0
* is publicly released because previous implementations of that class had relied on this method
* via reflection.
*
@@ -1777,7 +1777,7 @@
synchronized (mH) {
try {
Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
- + " removed soon. If you are using android.support.v7.widget.SearchView,"
+ + " removed soon. If you are using androidx.appcompat.widget.SearchView,"
+ " please update to version 26.0 or newer version.");
if (mCurRootView == null || mCurRootView.getView() == null) {
Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()");
diff --git a/core/java/android/view/textservice/SpellCheckerInfo.java b/core/java/android/view/textservice/SpellCheckerInfo.java
index 13d44da..edcbce9 100644
--- a/core/java/android/view/textservice/SpellCheckerInfo.java
+++ b/core/java/android/view/textservice/SpellCheckerInfo.java
@@ -124,6 +124,7 @@
.SpellChecker_Subtype_subtypeExtraValue),
a.getInt(com.android.internal.R.styleable
.SpellChecker_Subtype_subtypeId, 0));
+ a.recycle();
mSubtypes.add(subtype);
}
}
diff --git a/core/java/android/widget/ArrayAdapter.java b/core/java/android/widget/ArrayAdapter.java
index de9f76d..f554f89 100644
--- a/core/java/android/widget/ArrayAdapter.java
+++ b/core/java/android/widget/ArrayAdapter.java
@@ -61,7 +61,7 @@
* </p>
* <p class="note"><strong>Note:</strong>
* If you are considering using array adapter with a ListView, consider using
- * {@link android.support.v7.widget.RecyclerView} instead.
+ * {@link androidx.recyclerview.widget.RecyclerView} instead.
* RecyclerView offers similar features with better performance and more flexibility than
* ListView provides.
* See the
diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java
index ec07209..1bde235 100755
--- a/core/java/android/widget/DatePickerCalendarDelegate.java
+++ b/core/java/android/widget/DatePickerCalendarDelegate.java
@@ -209,6 +209,7 @@
// Generate a non-activated color using the disabled alpha.
final TypedArray ta = mContext.obtainStyledAttributes(ATTRS_DISABLED_ALPHA);
final float disabledAlpha = ta.getFloat(0, 0.30f);
+ ta.recycle();
defaultColor = multiplyAlphaComponent(activatedColor, disabledAlpha);
}
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index 2c61280..9c0900b 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -17,6 +17,7 @@
package android.widget;
import android.content.Context;
+import android.graphics.Rect;
import android.text.Editable;
import android.text.Selection;
import android.text.Spannable;
@@ -173,6 +174,12 @@
return EditText.class.getName();
}
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ setHandwritingArea(new Rect(0, 0, w, h));
+ }
+
/** @hide */
@Override
protected boolean supportsAutoSizeText() {
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index d969a88..b2bc0764 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -57,7 +57,7 @@
* @attr ref android.R.styleable#Gallery_gravity
*
* @deprecated This widget is no longer supported. Other horizontally scrolling
- * widgets include {@link HorizontalScrollView} and {@link android.support.v4.view.ViewPager}
+ * widgets include {@link HorizontalScrollView} and {@link androidx.viewpager.widget.ViewPager}
* from the support library.
*/
@Deprecated
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 3ad7b46..15cd17b 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -63,12 +63,12 @@
* <p>Scroll view supports vertical scrolling only. For horizontal scrolling,
* use {@link HorizontalScrollView} instead.</p>
*
- * <p>Never add a {@link android.support.v7.widget.RecyclerView} or {@link ListView} to
+ * <p>Never add a {@link androidx.recyclerview.widget.RecyclerView} or {@link ListView} to
* a scroll view. Doing so results in poor user interface performance and a poor user
* experience.</p>
*
* <p class="note">
- * For vertical scrolling, consider {@link android.support.v4.widget.NestedScrollView}
+ * For vertical scrolling, consider {@link androidx.core.widget.NestedScrollView}
* instead of scroll view which offers greater user interface flexibility and
* support for the material design scrolling patterns.</p>
*
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index d3600ef..872e65a 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -72,7 +72,7 @@
* {@link #setSwitchTextAppearance(android.content.Context, int) switchTextAppearance} and
* the related setSwitchTypeface() methods control that of the thumb.
*
- * <p>{@link android.support.v7.widget.SwitchCompat} is a version of
+ * <p>{@link androidx.recyclerview.widget.RecyclerView} is a version of
* the Switch widget which runs on devices back to API 7.</p>
*
* <p>See the <a href="{@docRoot}guide/topics/ui/controls/togglebutton.html">Toggle Buttons</a>
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index c0c7641..3dfb4a5 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5081,7 +5081,7 @@
*
* @param color A color value in the form 0xAARRGGBB.
* Do not pass a resource ID. To get a color value from a resource ID, call
- * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}.
+ * {@link androidx.core.content.ContextCompat#getColor(Context, int) getColor}.
*
* @see #setTextColor(ColorStateList)
* @see #getTextColors()
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index dc9a585..a453c28 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -373,6 +373,7 @@
// Generate a non-activated color using the disabled alpha.
final TypedArray ta = mContext.obtainStyledAttributes(ATTRS_DISABLED_ALPHA);
final float disabledAlpha = ta.getFloat(0, 0.30f);
+ ta.recycle();
defaultColor = multiplyAlphaComponent(activatedColor, disabledAlpha);
}
diff --git a/core/java/android/window/BackEvent.aidl b/core/java/android/window/BackEvent.aidl
new file mode 100644
index 0000000..821f1fa
--- /dev/null
+++ b/core/java/android/window/BackEvent.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+/**
+ * @hide
+ */
+parcelable BackEvent;
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
new file mode 100644
index 0000000..14985c9
--- /dev/null
+++ b/core/java/android/window/BackEvent.java
@@ -0,0 +1,159 @@
+/*
+ * 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.Parcel;
+import android.os.Parcelable;
+import android.view.RemoteAnimationTarget;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Represents an event that is sent out by the system during back navigation gesture.
+ * Holds information about the touch event, swipe direction and overall progress of the gesture
+ * interaction.
+ *
+ * @hide
+ */
+public class BackEvent implements Parcelable {
+ /** Indicates that the edge swipe starts from the left edge of the screen */
+ public static final int EDGE_LEFT = 0;
+ /** Indicates that the edge swipe starts from the right edge of the screen */
+ public static final int EDGE_RIGHT = 1;
+
+ @IntDef({
+ EDGE_LEFT,
+ EDGE_RIGHT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SwipeEdge{}
+
+ private final int mTouchX;
+ private final int mTouchY;
+ private final float mProgress;
+
+ @SwipeEdge
+ private final int mSwipeEdge;
+ @Nullable
+ private final RemoteAnimationTarget mDepartingAnimationTarget;
+
+ /**
+ * Creates a new {@link BackEvent} instance.
+ *
+ * @param touchX Absolute X location of the touch point.
+ * @param touchY Absolute Y location of the touch point.
+ * @param progress Value between 0 and 1 on how far along the back gesture is.
+ * @param swipeEdge Indicates which edge the swipe starts from.
+ * @param departingAnimationTarget The remote animation target of the departing application
+ * window.
+ */
+ public BackEvent(int touchX, int touchY, float progress, @SwipeEdge int swipeEdge,
+ @Nullable RemoteAnimationTarget departingAnimationTarget) {
+ mTouchX = touchX;
+ mTouchY = touchY;
+ mProgress = progress;
+ mSwipeEdge = swipeEdge;
+ mDepartingAnimationTarget = departingAnimationTarget;
+ }
+
+ private BackEvent(@NonNull Parcel in) {
+ mTouchX = in.readInt();
+ mTouchY = in.readInt();
+ mProgress = in.readFloat();
+ mSwipeEdge = in.readInt();
+ mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
+ }
+
+ public static final Creator<BackEvent> CREATOR = new Creator<BackEvent>() {
+ @Override
+ public BackEvent createFromParcel(Parcel in) {
+ return new BackEvent(in);
+ }
+
+ @Override
+ public BackEvent[] newArray(int size) {
+ return new BackEvent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mTouchX);
+ dest.writeInt(mTouchY);
+ dest.writeFloat(mProgress);
+ dest.writeInt(mSwipeEdge);
+ dest.writeTypedObject(mDepartingAnimationTarget, flags);
+ }
+
+ /**
+ * Returns a value between 0 and 1 on how far along the back gesture is.
+ */
+ public float getProgress() {
+ return mProgress;
+ }
+
+ /**
+ * Returns the absolute X location of the touch point.
+ */
+ public int getTouchX() {
+ return mTouchX;
+ }
+
+ /**
+ * Returns the absolute Y location of the touch point.
+ */
+ public int getTouchY() {
+ return mTouchY;
+ }
+
+ /**
+ * Returns the screen edge that the swipe starts from.
+ */
+ public int getSwipeEdge() {
+ return mSwipeEdge;
+ }
+
+ /**
+ * Returns the {@link RemoteAnimationTarget} of the top departing application window,
+ * or {@code null} if the top window should not be moved for the current type of back
+ * destination.
+ */
+ @Nullable
+ public RemoteAnimationTarget getDepartingAnimationTarget() {
+ return mDepartingAnimationTarget;
+ }
+
+ @Override
+ public String toString() {
+ return "BackEvent{"
+ + "mTouchX=" + mTouchX
+ + ", mTouchY=" + mTouchY
+ + ", mProgress=" + mProgress
+ + ", mSwipeEdge" + mSwipeEdge
+ + ", mDepartingAnimationTarget" + mDepartingAnimationTarget
+ + "}";
+ }
+}
diff --git a/core/java/android/window/IOnBackInvokedCallback.aidl b/core/java/android/window/IOnBackInvokedCallback.aidl
index a42863c..47796de 100644
--- a/core/java/android/window/IOnBackInvokedCallback.aidl
+++ b/core/java/android/window/IOnBackInvokedCallback.aidl
@@ -17,6 +17,8 @@
package android.window;
+import android.window.BackEvent;
+
/**
* Interface that wraps a {@link OnBackInvokedCallback} object, to be stored in window manager
* and called from back handling process when back is invoked.
@@ -38,7 +40,7 @@
* @param touchY Absolute Y location of the touch point.
* @param progress Value between 0 and 1 on how far along the back gesture is.
*/
- void onBackProgressed(int touchX, int touchY, float progress);
+ void onBackProgressed(in BackEvent backEvent);
/**
* Called when a back gesture or back button press has been cancelled.
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index d37d3b4..03de479 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -55,10 +55,6 @@
private static final boolean IS_BACK_PREDICTABILITY_ENABLED = SystemProperties
.getInt(BACK_PREDICTABILITY_PROP, 0) > 0;
- /** The currently most prioritized callback. */
- @Nullable
- private OnBackInvokedCallbackWrapper mTopCallback;
-
/** Convenience hashmap to quickly decide if a callback has been added. */
private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>();
/** Holds all callbacks by priorities. */
@@ -72,8 +68,8 @@
public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window) {
mWindowSession = windowSession;
mWindow = window;
- if (mTopCallback != null) {
- setTopOnBackInvokedCallback(mTopCallback);
+ if (!mAllCallbacks.isEmpty()) {
+ setTopOnBackInvokedCallback(getTopCallback());
}
}
@@ -81,6 +77,7 @@
public void detachFromWindow() {
mWindow = null;
mWindowSession = null;
+ clear();
}
// TODO: Take an Executor for the callback to run on.
@@ -110,11 +107,13 @@
mOnBackInvokedCallbacks.get(prevPriority).remove(callback);
}
+ OnBackInvokedCallback previousTopCallback = getTopCallback();
callbacks.add(callback);
mAllCallbacks.put(callback, priority);
- if (mTopCallback == null || (mTopCallback.getCallback() != callback
- && mAllCallbacks.get(mTopCallback.getCallback()) <= priority)) {
- setTopOnBackInvokedCallback(new OnBackInvokedCallbackWrapper(callback, priority));
+ if (previousTopCallback == null
+ || (previousTopCallback != callback
+ && mAllCallbacks.get(previousTopCallback) <= priority)) {
+ setTopOnBackInvokedCallback(callback);
}
}
@@ -126,11 +125,17 @@
}
return;
}
+ OnBackInvokedCallback previousTopCallback = getTopCallback();
Integer priority = mAllCallbacks.get(callback);
- mOnBackInvokedCallbacks.get(priority).remove(callback);
+ ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
+ callbacks.remove(callback);
+ if (callbacks.isEmpty()) {
+ mOnBackInvokedCallbacks.remove(priority);
+ }
mAllCallbacks.remove(callback);
- if (mTopCallback != null && mTopCallback.getCallback() == callback) {
- findAndSetTopOnBackInvokedCallback();
+ // Re-populate the top callback to WM if the removed callback was previously the top one.
+ if (previousTopCallback == callback) {
+ setTopOnBackInvokedCallback(getTopCallback());
}
}
@@ -141,41 +146,26 @@
/** Clears all registered callbacks on the instance. */
public void clear() {
+ if (!mAllCallbacks.isEmpty()) {
+ // Clear binder references in WM.
+ setTopOnBackInvokedCallback(null);
+ }
mAllCallbacks.clear();
- mTopCallback = null;
mOnBackInvokedCallbacks.clear();
}
- /**
- * Iterates through all callbacks to find the most prioritized one and pushes it to
- * window manager.
- */
- private void findAndSetTopOnBackInvokedCallback() {
- if (mAllCallbacks.isEmpty()) {
- setTopOnBackInvokedCallback(null);
- return;
- }
-
- for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) {
- ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
- if (!callbacks.isEmpty()) {
- OnBackInvokedCallbackWrapper callback = new OnBackInvokedCallbackWrapper(
- callbacks.get(callbacks.size() - 1), priority);
- setTopOnBackInvokedCallback(callback);
- return;
- }
- }
- setTopOnBackInvokedCallback(null);
- }
-
- // Pushes the top priority callback to window manager.
- private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallbackWrapper callback) {
- mTopCallback = callback;
+ private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) {
if (mWindowSession == null || mWindow == null) {
return;
}
try {
- mWindowSession.setOnBackInvokedCallback(mWindow, mTopCallback);
+ if (callback == null) {
+ mWindowSession.setOnBackInvokedCallback(mWindow, null);
+ } else {
+ int priority = mAllCallbacks.get(callback);
+ mWindowSession.setOnBackInvokedCallback(
+ mWindow, new OnBackInvokedCallbackWrapper(callback, priority));
+ }
} catch (RemoteException e) {
Log.e(TAG, "Failed to set OnBackInvokedCallback to WM. Error: " + e);
}
@@ -202,9 +192,9 @@
}
@Override
- public void onBackProgressed(int touchX, int touchY, float progress)
+ public void onBackProgressed(BackEvent backEvent)
throws RemoteException {
- Handler.getMain().post(() -> mCallback.onBackProgressed(touchX, touchY, progress));
+ Handler.getMain().post(() -> mCallback.onBackProgressed(backEvent));
}
@Override
@@ -220,7 +210,16 @@
@Override
public OnBackInvokedCallback getTopCallback() {
- return mTopCallback == null ? null : mTopCallback.getCallback();
+ if (mAllCallbacks.isEmpty()) {
+ return null;
+ }
+ for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) {
+ ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
+ if (!callbacks.isEmpty()) {
+ return callbacks.get(callbacks.size() - 1);
+ }
+ }
+ return null;
}
/**
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 4ae6bf7..150eb65 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -217,6 +217,12 @@
private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
+ private static final String[] QUERY_FILE_INFO_PROJECTION = {
+ OpenableColumns.DISPLAY_NAME,
+ Downloads.Impl.COLUMN_TITLE,
+ DocumentsContract.Document.COLUMN_FLAGS
+ };
+
private static final String PLURALS_COUNT = "count";
private static final String PLURALS_FILE_NAME = "file_name";
@@ -1474,15 +1480,15 @@
* and to avoid mocking Android core classes.
*/
@VisibleForTesting
- public Cursor queryResolver(ContentResolver resolver, Uri uri) {
- return resolver.query(uri, null, null, null, null);
+ public Cursor queryResolver(ContentResolver resolver, String[] projection, Uri uri) {
+ return resolver.query(uri, projection, null, null, null);
}
private FileInfo extractFileInfo(Uri uri, ContentResolver resolver) {
String fileName = null;
boolean hasThumbnail = false;
- try (Cursor cursor = queryResolver(resolver, uri)) {
+ try (Cursor cursor = queryResolver(resolver, QUERY_FILE_INFO_PROJECTION, uri)) {
if (cursor != null && cursor.getCount() > 0) {
int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
int titleIndex = cursor.getColumnIndex(Downloads.Impl.COLUMN_TITLE);
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 51eb429..46f54ce 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -308,5 +308,7 @@
/** Notifies System UI about an update to the media tap-to-transfer receiver state. */
void updateMediaTapToTransferReceiverDisplay(
int displayState,
- in MediaRoute2Info routeInfo);
+ in MediaRoute2Info routeInfo,
+ in Icon appIcon,
+ in CharSequence appName);
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 0c45e5b..6c17df1 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -208,5 +208,7 @@
/** Notifies System UI about an update to the media tap-to-transfer receiver state. */
void updateMediaTapToTransferReceiverDisplay(
int displayState,
- in MediaRoute2Info routeInfo);
+ in MediaRoute2Info routeInfo,
+ in Icon appIcon,
+ in CharSequence appName);
}
diff --git a/core/java/com/android/internal/util/ContrastColorUtil.java b/core/java/com/android/internal/util/ContrastColorUtil.java
index 7a712e5..ced2722 100644
--- a/core/java/com/android/internal/util/ContrastColorUtil.java
+++ b/core/java/com/android/internal/util/ContrastColorUtil.java
@@ -627,7 +627,7 @@
}
/**
- * Framework copy of functions needed from android.support.v4.graphics.ColorUtils.
+ * Framework copy of functions needed from androidx.core.graphics.ColorUtils.
*/
private static class ColorUtilsFromCompat {
private static final double XYZ_WHITE_REFERENCE_X = 95.047;
diff --git a/core/java/com/android/internal/widget/GridLayoutManager.java b/core/java/com/android/internal/widget/GridLayoutManager.java
index 09e6a99..3873e3b 100644
--- a/core/java/com/android/internal/widget/GridLayoutManager.java
+++ b/core/java/com/android/internal/widget/GridLayoutManager.java
@@ -29,7 +29,7 @@
/**
* Note: This GridLayoutManager widget may lack of latest fix because it is ported from
- * oc-dr1-release version of android.support.v7.widget.GridLayoutManager due to compatibility
+ * oc-dr1-release version of androidx.gridlayout.widget.GridLayoutManager due to compatibility
* concern with other internal widgets, like {@link RecyclerView} and {@link LinearLayoutManager},
* and is merely used for {@link com.android.internal.app.ChooserActivity}.
*
diff --git a/core/java/com/android/internal/widget/PagerAdapter.java b/core/java/com/android/internal/widget/PagerAdapter.java
index 910a720..c595f5c 100644
--- a/core/java/com/android/internal/widget/PagerAdapter.java
+++ b/core/java/com/android/internal/widget/PagerAdapter.java
@@ -24,10 +24,10 @@
/**
* Base class providing the adapter to populate pages inside of
- * a {@link android.support.v4.view.ViewPager}. You will most likely want to use a more
+ * a {@link androidx.viewpager.view.ViewPager}. You will most likely want to use a more
* specific implementation of this, such as
- * {@link android.support.v4.app.FragmentPagerAdapter} or
- * {@link android.support.v4.app.FragmentStatePagerAdapter}.
+ * {@link androidx.fragment.app.FragmentPagerAdapter} or
+ * {@link androidx.fragment.app.FragmentStatePagerAdapter}.
*
* <p>When you implement a PagerAdapter, you must override the following methods
* at minimum:</p>
diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java
index be15a9b..e27557a 100644
--- a/core/java/com/android/internal/widget/RecyclerView.java
+++ b/core/java/com/android/internal/widget/RecyclerView.java
@@ -1299,7 +1299,7 @@
* Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views.
* This can be useful if you have multiple RecyclerViews with adapters that use the same
* view types, for example if you have several data sets with the same kinds of item views
- * displayed by a {@link android.support.v4.view.ViewPager ViewPager}.
+ * displayed by a {@link androidx.viewpager.view.ViewPager ViewPager}.
*
* @param pool Pool to set. If this parameter is null a new pool will be created and used.
*/
@@ -9764,13 +9764,13 @@
* Some general properties that a LayoutManager may want to use.
*/
public static class Properties {
- /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation */
+ /** @attr ref androidx.recyclerview.R.styleable#RecyclerView_android_orientation */
public int orientation;
- /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_spanCount */
+ /** @attr ref androidx.recyclerview.R.styleable#RecyclerView_spanCount */
public int spanCount;
- /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout */
+ /** @attr ref androidx.recyclerview.R.styleable#RecyclerView_reverseLayout */
public boolean reverseLayout;
- /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd */
+ /** @attr ref androidx.recyclerview.R.styleable#RecyclerView_stackFromEnd */
public boolean stackFromEnd;
}
}
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 24c0d2a..2a4f812 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -67,6 +67,7 @@
### Graphics ###
per-file android_graphics_* = file:/graphics/java/android/graphics/OWNERS
per-file android_hardware_HardwareBuffer.cpp = file:/graphics/java/android/graphics/OWNERS
+per-file android_hardware_SyncFence.cpp = file:/graphics/java/android/graphics/OWNERS
### Text ###
per-file android_text_* = file:/core/java/android/text/OWNERS
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 2488b57..336161c 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -245,6 +245,13 @@
jmethodID onTransactionCommitted;
} gTransactionCommittedListenerClassInfo;
+static struct {
+ jclass clazz;
+ jmethodID ctor;
+ jfieldID format;
+ jfieldID alphaInterpretation;
+} gDisplayDecorationSupportInfo;
+
class JNamedColorSpace {
public:
// ColorSpace.Named.SRGB.ordinal() = 0;
@@ -1792,13 +1799,29 @@
client->setGlobalShadowSettings(ambientColor, spotColor, lightPosY, lightPosZ, lightRadius);
}
-static jboolean nativeGetDisplayDecorationSupport(JNIEnv* env, jclass clazz,
- jobject displayTokenObject) {
+static jobject nativeGetDisplayDecorationSupport(JNIEnv* env, jclass clazz,
+ jobject displayTokenObject) {
sp<IBinder> displayToken(ibinderForJavaObject(env, displayTokenObject));
if (displayToken == nullptr) {
- return JNI_FALSE;
+ return nullptr;
}
- return static_cast<jboolean>(SurfaceComposerClient::getDisplayDecorationSupport(displayToken));
+ const auto support = SurfaceComposerClient::getDisplayDecorationSupport(displayToken);
+ if (!support) {
+ return nullptr;
+ }
+
+ jobject jDisplayDecorationSupport =
+ env->NewObject(gDisplayDecorationSupportInfo.clazz, gDisplayDecorationSupportInfo.ctor);
+ if (jDisplayDecorationSupport == nullptr) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", nullptr);
+ return nullptr;
+ }
+
+ env->SetIntField(jDisplayDecorationSupport, gDisplayDecorationSupportInfo.format,
+ static_cast<jint>(support.value().format));
+ env->SetIntField(jDisplayDecorationSupport, gDisplayDecorationSupportInfo.alphaInterpretation,
+ static_cast<jint>(support.value().alphaInterpretation));
+ return jDisplayDecorationSupport;
}
static jlong nativeGetHandle(JNIEnv* env, jclass clazz, jlong nativeObject) {
@@ -2131,7 +2154,8 @@
(void*)nativeMirrorSurface },
{"nativeSetGlobalShadowSettings", "([F[FFFF)V",
(void*)nativeSetGlobalShadowSettings },
- {"nativeGetDisplayDecorationSupport", "(Landroid/os/IBinder;)Z",
+ {"nativeGetDisplayDecorationSupport",
+ "(Landroid/os/IBinder;)Landroid/hardware/graphics/common/DisplayDecorationSupport;",
(void*)nativeGetDisplayDecorationSupport},
{"nativeGetHandle", "(J)J",
(void*)nativeGetHandle },
@@ -2390,6 +2414,17 @@
gTransactionCommittedListenerClassInfo.onTransactionCommitted =
GetMethodIDOrDie(env, transactionCommittedListenerClazz, "onTransactionCommitted",
"()V");
+
+ jclass displayDecorationSupportClazz =
+ FindClassOrDie(env, "android/hardware/graphics/common/DisplayDecorationSupport");
+ gDisplayDecorationSupportInfo.clazz = MakeGlobalRefOrDie(env, displayDecorationSupportClazz);
+ gDisplayDecorationSupportInfo.ctor =
+ GetMethodIDOrDie(env, displayDecorationSupportClazz, "<init>", "()V");
+ gDisplayDecorationSupportInfo.format =
+ GetFieldIDOrDie(env, displayDecorationSupportClazz, "format", "I");
+ gDisplayDecorationSupportInfo.alphaInterpretation =
+ GetFieldIDOrDie(env, displayDecorationSupportClazz, "alphaInterpretation", "I");
+
return err;
}
diff --git a/core/proto/android/content/package_item_info.proto b/core/proto/android/content/package_item_info.proto
index 5c6116a..279a5d0 100644
--- a/core/proto/android/content/package_item_info.proto
+++ b/core/proto/android/content/package_item_info.proto
@@ -115,4 +115,5 @@
}
optional Detail detail = 17;
repeated string overlay_paths = 18;
+ repeated string known_activity_embedding_certs = 19;
}
diff --git a/core/proto/android/internal/binder_latency.proto b/core/proto/android/internal/binder_latency.proto
index 8b11f5b..edd9b71 100644
--- a/core/proto/android/internal/binder_latency.proto
+++ b/core/proto/android/internal/binder_latency.proto
@@ -35,6 +35,7 @@
SYSTEM_SERVER = 1;
TELEPHONY = 2;
BLUETOOTH = 3;
+ WIFI = 4;
}
enum ServiceClassName {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 19c1325..0c41f31 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1905,6 +1905,13 @@
<permission android:name="android.permission.MANAGE_WIFI_AUTO_JOIN"
android:protectionLevel="signature|privileged" />
+ <!-- Allows applications to get notified when a Wi-Fi interface request cannot
+ be satisfied without tearing down one or more other interfaces, and provide a decision
+ whether to approve the request or reject it.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MANAGE_WIFI_INTERFACES"
+ android:protectionLevel="signature|privileged" />
+
<!-- @SystemApi @hide Allows apps to create and manage IPsec tunnels.
<p>Only granted to applications that are currently bound by the
system for creating and managing IPsec-based interfaces.
@@ -6629,6 +6636,14 @@
android:exported="false">
</activity>
+ <activity android:name="com.android.server.logcat.LogAccessConfirmationActivity"
+ android:theme="@style/Theme.Dialog.Confirmation"
+ android:excludeFromRecents="true"
+ android:process=":ui"
+ android:label="@string/log_access_confirmation_title"
+ android:exported="false">
+ </activity>
+
<activity android:name="com.android.server.notification.NASLearnMoreActivity"
android:theme="@style/Theme.Dialog.Confirmation"
android:excludeFromRecents="true"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index cb40e86..6dc975b 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1048,6 +1048,24 @@
<p>The default value of this attribute is <code>false</code>. -->
<attr name="allowEmbedded" format="boolean" />
+ <!-- A reference to an array resource containing the signing certificate digests, one of which a
+ client is required to be signed with in order to embed the activity. If the client is not
+ signed with one of the certificates in the set, and the activity does not allow embedding
+ by untrusted hosts via {@link android.R.attr#allowUntrustedActivityEmbedding} flag, the
+ embedding request will fail.
+ <p>The digest should be computed over the DER encoding of the trusted certificate using the
+ SHA-256 digest algorithm.
+ <p>If only a single signer is declared this can also be a string resource, or the digest
+ can be declared inline as the value for this attribute.
+ <p>If the attribute is declared both on the application and the activity level, the value
+ on the activity level takes precedence. -->
+ <attr name="knownActivityEmbeddingCerts" format="reference|string" />
+
+ <!-- Indicate that the activity can be embedded by untrusted hosts. In this case the
+ interactions and visibility of the embedded activity may be limited.
+ <p>The default value of this attribute is <code>false</code>. -->
+ <attr name="allowUntrustedActivityEmbedding" format="boolean" />
+
<!-- Specifies whether this {@link android.app.Activity} should be shown on
top of the lock screen whenever the lockscreen is up and this activity has another
activity behind it with the {@link android.R.attr#showWhenLocked} attribute set. That
@@ -2011,6 +2029,7 @@
when the application's user data is cleared. The default value is false.
-->
<attr name="resetEnabledSettingsOnAppDataCleared" format="boolean" />
+ <attr name="knownActivityEmbeddingCerts" />
</declare-styleable>
<!-- An attribution is a logical part of an app and is identified by a tag.
@@ -3033,6 +3052,8 @@
<!-- Indicates whether the activity can be displayed on a remote device which may or
may not be running Android. -->
<attr name="canDisplayOnRemoteDevices" format="boolean"/>
+ <attr name="allowUntrustedActivityEmbedding" />
+ <attr name="knownActivityEmbeddingCerts" />
</declare-styleable>
<!-- The <code>activity-alias</code> tag declares a new
@@ -3073,6 +3094,8 @@
<attr name="exported" />
<attr name="parentActivityName" />
<attr name="attributionTags" />
+ <attr name="allowUntrustedActivityEmbedding" />
+ <attr name="knownActivityEmbeddingCerts" />
</declare-styleable>
<!-- The <code>meta-data</code> tag is used to attach additional
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 775527d..6eb19e1 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4276,6 +4276,9 @@
<!-- URI for in call notification sound -->
<string translatable="false" name="config_inCallNotificationSound">/product/media/audio/ui/InCallNotification.ogg</string>
+ <!-- URI for camera shutter sound -->
+ <string translatable="false" name="config_cameraShutterSound">/product/media/audio/ui/camera_click.ogg</string>
+
<!-- URI for default ringtone sound file to be used for silent ringer vibration -->
<string translatable="false" name="config_defaultRingtoneVibrationSound"></string>
@@ -5697,4 +5700,75 @@
-->
<string-array name="config_dockExtconStateMapping">
</string-array>
+
+ <!-- Whether or not the monitoring on the apps' background battery drain is enabled -->
+ <bool name="config_bg_current_drain_monitor_enabled">true</bool>
+
+ <!-- The threshold of the background current drain (in percentage) to the restricted
+ standby bucket.
+ -->
+ <array name="config_bg_current_drain_threshold_to_restricted_bucket">
+ <item>2.0</item> <!-- regular device -->
+ <item>4.0</item> <!-- low ram device -->
+ </array>
+
+ <!-- The threshold of the background current drain (in percentage) to the background
+ restricted level.
+ -->
+ <array name="config_bg_current_drain_threshold_to_bg_restricted">
+ <item>4.0</item> <!-- regular device -->
+ <item>8.0</item> <!-- low ram device -->
+ </array>
+
+ <!-- The background current drain monitoring window size. -->
+ <integer name="config_bg_current_drain_window">86400</integer>
+
+ <!-- The types of battery drain we're checking on each app; if the sum of the battery drain
+ exceeds the threshold, it'll be moved to restricted standby bucket. The value must be
+ one of or combination of the definitions in AppBatteryPolicy.
+ -->
+ <integer name="config_bg_current_drain_types_to_restricted_bucket">4</integer>
+
+ <!-- The types of battery drain we're checking on each app; if the sum of the battery drain
+ exceeds the threshold, it'll be moved to background restricted level. The value must be
+ one of or combination of the definitions in AppBatteryPolicy.
+ -->
+ <integer name="config_bg_current_drain_types_to_bg_restricted">12</integer>
+
+ <!-- The power usage components we're monitoring. Must one of the definition in BatteryConsumer.
+ -->
+ <integer name="config_bg_current_drain_power_components">-1</integer>
+
+ <!-- Whether or not enable the different threshold based on the durations of
+ certain event type.
+ -->
+ <bool name="config_bg_current_drain_event_duration_based_threshold_enabled">false</bool>
+
+ <!-- The threshold of the background current drain (in percentage) to the restricted
+ standby bucket for legitimate case with higher background current drain.
+ -->
+ <array name="config_bg_current_drain_high_threshold_to_restricted_bucket">
+ <item>30.0</item> <!-- regular device -->
+ <item>60.0</item> <!-- low ram device -->
+ </array>
+
+ <!-- The threshold of the background current drain (in percentage) to the background
+ restricted level for legitimate case with higher background current drain.
+ -->
+ <array name="config_bg_current_drain_high_threshold_to_bg_restricted">
+ <item>20.0</item> <!-- regular device -->
+ <item>40.0</item> <!-- low ram device -->
+ </array>
+
+ <!-- The threshold of minimal time of hosting a foreground service with type "mediaPlayback"
+ or a media session, over the given window, so it'd subject towards the higher background
+ current drain threshold.
+ -->
+ <integer name="config_bg_current_drain_media_playback_min_duration">1800</integer>
+
+ <!-- The threshold of minimal time of hosting a foreground service with type "location"
+ over the given window, so it'd subject towards the higher background
+ current drain threshold.
+ -->
+ <integer name="config_bg_current_drain_location_min_duration">1800</integer>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index cc63fd6..3d80ca8 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3276,6 +3276,8 @@
<public name="toExtendBottom" />
<public name="tileService" />
<public name="windowSplashScreenBehavior" />
+ <public name="allowUntrustedActivityEmbedding" />
+ <public name="knownActivityEmbeddingCerts" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01de0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6297ed9..e41aa45 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5719,6 +5719,20 @@
<!-- Title for the harmful app warning dialog. [CHAR LIMIT=40] -->
<string name="harmful_app_warning_title">Harmful app detected</string>
+ <!-- Title for the log access confirmation dialog. [CHAR LIMIT=40] -->
+ <string name="log_access_confirmation_title">System log access request</string>
+ <!-- Label for the allow button on the log access confirmation dialog. [CHAR LIMIT=20] -->
+ <string name="log_access_confirmation_allow">Only this time</string>
+ <!-- Label for the deny button on the log access confirmation dialog. [CHAR LIMIT=20] -->
+ <string name="log_access_confirmation_deny">Don\u2019t allow</string>
+
+ <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
+ <string name="log_access_confirmation_body"><xliff:g id="log_access_app_name" example="Example App">%s</xliff:g> requests system logs for functional debugging.
+ These logs might contain information that apps and services on your device have written.</string>
+
+ <!-- Privacy notice do not show [CHAR LIMIT=20] -->
+ <string name="log_access_do_not_show_again">Don\u2019t show again</string>
+
<!-- Text describing a permission request for one app to show another app's
slices [CHAR LIMIT=NONE] -->
<string name="slices_permission_request"><xliff:g id="app" example="Example App">%1$s</xliff:g> wants to show <xliff:g id="app_2" example="Other Example App">%2$s</xliff:g> slices</string>
@@ -6266,4 +6280,8 @@
</string>
<!-- Action label of notification for user to check background apps. [CHAR LIMIT=NONE] -->
<string name="notification_action_check_bg_apps">Check active apps</string>
+
+ <!-- Strings for VirtualDeviceManager -->
+ <!-- Error message indicating the camera cannot be accessed when running on a virtual device. [CHAR LIMIT=NONE] -->
+ <string name="vdm_camera_access_denied">Cannot access camera from this device</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d731180..c63e543 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3749,6 +3749,7 @@
<java-symbol type="bool" name="config_handleVolumeAliasesUsingVolumeGroups" />
<java-symbol type="dimen" name="config_inCallNotificationVolume" />
<java-symbol type="string" name="config_inCallNotificationSound" />
+ <java-symbol type="string" name="config_cameraShutterSound" />
<java-symbol type="integer" name="config_autoGroupAtCount" />
<java-symbol type="bool" name="config_dozeAlwaysOnDisplayAvailable" />
<java-symbol type="bool" name="config_dozeAlwaysOnEnabled" />
@@ -3868,6 +3869,11 @@
<java-symbol type="string" name="harmful_app_warning_title" />
<java-symbol type="layout" name="harmful_app_warning_dialog" />
+ <java-symbol type="string" name="log_access_confirmation_allow" />
+ <java-symbol type="string" name="log_access_confirmation_deny" />
+ <java-symbol type="string" name="log_access_confirmation_title" />
+ <java-symbol type="string" name="log_access_confirmation_body" />
+
<java-symbol type="string" name="config_defaultAssistantAccessComponent" />
<java-symbol type="string" name="slices_permission_request" />
@@ -4724,5 +4730,21 @@
<java-symbol type="bool" name="config_lowPowerStandbyEnabledByDefault" />
<java-symbol type="integer" name="config_lowPowerStandbyNonInteractiveTimeout" />
+ <!-- For VirtualDeviceManager -->
+ <java-symbol type="string" name="vdm_camera_access_denied" />
+
<java-symbol type="color" name="camera_privacy_light"/>
+
+ <java-symbol type="bool" name="config_bg_current_drain_monitor_enabled" />
+ <java-symbol type="array" name="config_bg_current_drain_threshold_to_restricted_bucket" />
+ <java-symbol type="array" name="config_bg_current_drain_threshold_to_bg_restricted" />
+ <java-symbol type="integer" name="config_bg_current_drain_window" />
+ <java-symbol type="integer" name="config_bg_current_drain_types_to_restricted_bucket" />
+ <java-symbol type="integer" name="config_bg_current_drain_types_to_bg_restricted" />
+ <java-symbol type="integer" name="config_bg_current_drain_power_components" />
+ <java-symbol type="bool" name="config_bg_current_drain_event_duration_based_threshold_enabled" />
+ <java-symbol type="array" name="config_bg_current_drain_high_threshold_to_restricted_bucket" />
+ <java-symbol type="array" name="config_bg_current_drain_high_threshold_to_bg_restricted" />
+ <java-symbol type="integer" name="config_bg_current_drain_media_playback_min_duration" />
+ <java-symbol type="integer" name="config_bg_current_drain_location_min_duration" />
</resources>
diff --git a/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java b/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java
new file mode 100644
index 0000000..c1b6666
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.app.activity;
+
+import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
+import static android.content.Context.OVERRIDABLE_COMPONENT_CALLBACKS;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+
+import android.app.Activity;
+import android.app.WindowConfiguration;
+import android.app.activity.ActivityThreadTest.TestActivity;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.ComponentCallbacks;
+import android.content.TestComponentCallbacks2;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for verifying {@link Activity#registerComponentCallbacks(ComponentCallbacks)} behavior.
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:android.app.activity.RegisterComponentCallbacksTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class RegisterComponentCallbacksTest {
+ @Rule
+ public ActivityScenarioRule rule = new ActivityScenarioRule<>(TestActivity.class);
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+ @Test
+ public void testRegisterComponentCallbacks() {
+ final ActivityScenario scenario = rule.getScenario();
+ final TestComponentCallbacks2 callbacks = new TestComponentCallbacks2();
+ final Configuration config = new Configuration();
+ config.fontScale = 1.2f;
+ config.windowConfiguration.setWindowingMode(
+ WindowConfiguration.WINDOWING_MODE_FREEFORM);
+ config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
+ final int trimMemoryLevel = TRIM_MEMORY_RUNNING_LOW;
+
+ scenario.onActivity(activity -> {
+ // It should be no-op to unregister a ComponentCallbacks without registration.
+ activity.unregisterComponentCallbacks(callbacks);
+
+ activity.registerComponentCallbacks(callbacks);
+ // Verify #onConfigurationChanged
+ activity.onConfigurationChanged(config);
+ assertThat(callbacks.mConfiguration).isEqualTo(config);
+ // Verify #onTrimMemory
+ activity.onTrimMemory(trimMemoryLevel);
+ assertThat(callbacks.mLevel).isEqualTo(trimMemoryLevel);
+ // verify #onLowMemory
+ activity.onLowMemory();
+ assertThat(callbacks.mLowMemoryCalled).isTrue();
+
+ activity.unregisterComponentCallbacks(callbacks);
+ });
+ }
+
+ @DisableCompatChanges(OVERRIDABLE_COMPONENT_CALLBACKS)
+ @Test
+ public void testRegisterComponentCallbacksBeforeT() {
+ final ActivityScenario scenario = rule.getScenario();
+ final TestComponentCallbacks2 callbacks = new TestComponentCallbacks2();
+ final Configuration config = new Configuration();
+ config.fontScale = 1.2f;
+ config.windowConfiguration.setWindowingMode(
+ WindowConfiguration.WINDOWING_MODE_FREEFORM);
+ config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
+ final int trimMemoryLevel = TRIM_MEMORY_RUNNING_LOW;
+
+ scenario.onActivity(activity -> {
+ // It should be no-op to unregister a ComponentCallbacks without registration.
+ activity.unregisterComponentCallbacks(callbacks);
+
+ activity.registerComponentCallbacks(callbacks);
+ // Verify #onConfigurationChanged
+ activity.onConfigurationChanged(config);
+ assertWithMessage("The ComponentCallbacks must be added to #getApplicationContext "
+ + "before T.").that(callbacks.mConfiguration).isNull();
+ // Verify #onTrimMemory
+ activity.onTrimMemory(trimMemoryLevel);
+ assertWithMessage("The ComponentCallbacks must be added to #getApplicationContext "
+ + "before T.").that(callbacks.mLevel).isEqualTo(0);
+ // verify #onLowMemory
+ activity.onLowMemory();
+ assertWithMessage("The ComponentCallbacks must be added to #getApplicationContext "
+ + "before T.").that(callbacks.mLowMemoryCalled).isFalse();
+
+ activity.unregisterComponentCallbacks(callbacks);
+ });
+ }
+}
diff --git a/core/tests/coretests/src/android/content/ContextWrapperTest.java b/core/tests/coretests/src/android/content/ContextWrapperTest.java
index ecaf1f4..4957702 100644
--- a/core/tests/coretests/src/android/content/ContextWrapperTest.java
+++ b/core/tests/coretests/src/android/content/ContextWrapperTest.java
@@ -16,7 +16,7 @@
package android.content;
-import static android.content.ContextWrapper.COMPONENT_CALLBACK_ON_WRAPPER;
+import static android.content.Context.OVERRIDABLE_COMPONENT_CALLBACKS;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -61,7 +61,7 @@
* register {@link ComponentCallbacks} to {@link ContextWrapper#getApplicationContext} before
* {@link ContextWrapper#attachBaseContext(Context)}.
*/
- @DisableCompatChanges(COMPONENT_CALLBACK_ON_WRAPPER)
+ @DisableCompatChanges(OVERRIDABLE_COMPONENT_CALLBACKS)
@Test
public void testRegisterComponentCallbacksWithoutBaseContextBeforeT() {
final ContextWrapper wrapper = new TestContextWrapper(null /* base */);
diff --git a/core/tests/coretests/src/android/content/TestComponentCallbacks2.java b/core/tests/coretests/src/android/content/TestComponentCallbacks2.java
index 6ae7fc4..5c8787a 100644
--- a/core/tests/coretests/src/android/content/TestComponentCallbacks2.java
+++ b/core/tests/coretests/src/android/content/TestComponentCallbacks2.java
@@ -20,10 +20,10 @@
import androidx.annotation.NonNull;
-class TestComponentCallbacks2 implements ComponentCallbacks2 {
- android.content.res.Configuration mConfiguration;
- boolean mLowMemoryCalled;
- int mLevel;
+public class TestComponentCallbacks2 implements ComponentCallbacks2 {
+ public Configuration mConfiguration = null;
+ public boolean mLowMemoryCalled = false;
+ public int mLevel = 0;
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
diff --git a/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java b/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java
index 7af96c3..36072c3 100644
--- a/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java
+++ b/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java
@@ -104,6 +104,17 @@
}
@Test
+ public void testValidAtomicFormula_stringValue_appCertificateLineageIsNotAutoHashed() {
+ String appCert = "cert";
+ StringAtomicFormula stringAtomicFormula =
+ new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE_LINEAGE, appCert);
+
+ assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.APP_CERTIFICATE_LINEAGE);
+ assertThat(stringAtomicFormula.getValue()).matches(appCert);
+ assertThat(stringAtomicFormula.getIsHashedValue()).isTrue();
+ }
+
+ @Test
public void testValidAtomicFormula_stringValue_installerCertificateIsNotAutoHashed() {
String installerCert = "cert";
StringAtomicFormula stringAtomicFormula =
@@ -285,6 +296,34 @@
}
@Test
+ public void testFormulaMatches_string_multipleAppCertificateLineage_true() {
+ StringAtomicFormula stringAtomicFormula =
+ new StringAtomicFormula(
+ AtomicFormula.APP_CERTIFICATE_LINEAGE, "cert", /* isHashedValue= */ true);
+ AppInstallMetadata appInstallMetadata =
+ getAppInstallMetadataBuilder()
+ .setPackageName("com.test.app")
+ .setAppCertificateLineage(Arrays.asList("test-cert", "cert"))
+ .build();
+
+ assertThat(stringAtomicFormula.matches(appInstallMetadata)).isTrue();
+ }
+
+ @Test
+ public void testFormulaMatches_string_multipleAppCertificateLineage_false() {
+ StringAtomicFormula stringAtomicFormula =
+ new StringAtomicFormula(
+ AtomicFormula.APP_CERTIFICATE_LINEAGE, "cert", /* isHashedValue= */ true);
+ AppInstallMetadata appInstallMetadata =
+ getAppInstallMetadataBuilder()
+ .setPackageName("com.test.app")
+ .setAppCertificateLineage(Arrays.asList("test-cert", "another-cert"))
+ .build();
+
+ assertThat(stringAtomicFormula.matches(appInstallMetadata)).isFalse();
+ }
+
+ @Test
public void testFormulaMatches_string_multipleInstallerCertificates_true() {
StringAtomicFormula stringAtomicFormula =
new StringAtomicFormula(
@@ -324,6 +363,15 @@
}
@Test
+ public void testIsAppCertificateLineageFormula_string_true() {
+ StringAtomicFormula stringAtomicFormula =
+ new StringAtomicFormula(
+ AtomicFormula.APP_CERTIFICATE_LINEAGE, "cert", /* isHashedValue= */false);
+
+ assertThat(stringAtomicFormula.isAppCertificateLineageFormula()).isTrue();
+ }
+
+ @Test
public void testIsAppCertificateFormula_string_false() {
StringAtomicFormula stringAtomicFormula =
new StringAtomicFormula(
@@ -334,6 +382,16 @@
}
@Test
+ public void testIsAppCertificateLineageFormula_string_false() {
+ StringAtomicFormula stringAtomicFormula =
+ new StringAtomicFormula(
+ AtomicFormula.PACKAGE_NAME, "com.test.app", /* isHashedValue= */
+ false);
+
+ assertThat(stringAtomicFormula.isAppCertificateLineageFormula()).isFalse();
+ }
+
+ @Test
public void testIsInstallerFormula_string_false() {
StringAtomicFormula stringAtomicFormula =
new StringAtomicFormula(
@@ -442,6 +500,15 @@
}
@Test
+ public void testIsAppCertificateLineageFormula_long_false() {
+ LongAtomicFormula longAtomicFormula =
+ new AtomicFormula.LongAtomicFormula(
+ AtomicFormula.VERSION_CODE, AtomicFormula.GTE, 1);
+
+ assertThat(longAtomicFormula.isAppCertificateLineageFormula()).isFalse();
+ }
+
+ @Test
public void testIsInstallerFormula_long_false() {
LongAtomicFormula longAtomicFormula =
new LongAtomicFormula(
@@ -479,6 +546,14 @@
}
@Test
+ public void testIsAppCertificateLineageFormula_bool_false() {
+ BooleanAtomicFormula boolFormula =
+ new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true);
+
+ assertThat(boolFormula.isAppCertificateLineageFormula()).isFalse();
+ }
+
+ @Test
public void testIsInstallerFormula_bool_false() {
BooleanAtomicFormula boolFormula =
new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true);
@@ -491,6 +566,7 @@
return new AppInstallMetadata.Builder()
.setPackageName("abc")
.setAppCertificates(Collections.singletonList("abc"))
+ .setAppCertificateLineage(Collections.singletonList("abc"))
.setInstallerCertificates(Collections.singletonList("abc"))
.setInstallerName("abc")
.setVersionCode(-1)
diff --git a/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java b/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java
index 593e70e..a202efb 100644
--- a/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java
+++ b/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java
@@ -249,6 +249,28 @@
}
@Test
+ public void testIsAppCertificateLineageFormula_false() {
+ CompoundFormula compoundFormula =
+ new CompoundFormula(
+ CompoundFormula.AND, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2));
+
+ assertThat(compoundFormula.isAppCertificateLineageFormula()).isFalse();
+ }
+
+ @Test
+ public void testIsAppCertificateLineageFormula_true() {
+ AtomicFormula appCertFormula =
+ new AtomicFormula.StringAtomicFormula(AtomicFormula.APP_CERTIFICATE_LINEAGE,
+ "app.cert", /* isHashed= */false);
+ CompoundFormula compoundFormula =
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2, appCertFormula));
+
+ assertThat(compoundFormula.isAppCertificateLineageFormula()).isTrue();
+ }
+
+ @Test
public void testIsInstallerFormula_false() {
CompoundFormula compoundFormula =
new CompoundFormula(
@@ -288,6 +310,7 @@
return new AppInstallMetadata.Builder()
.setPackageName("abc")
.setAppCertificates(Collections.singletonList("abc"))
+ .setAppCertificateLineage(Collections.singletonList("abc"))
.setInstallerCertificates(Collections.singletonList("abc"))
.setInstallerName("abc")
.setVersionCode(-1)
diff --git a/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java
index 70712e4..54acb1e 100644
--- a/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java
+++ b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java
@@ -32,8 +32,8 @@
@RunWith(JUnit4.class)
public class InstallerAllowedByManifestFormulaTest {
- private static final InstallerAllowedByManifestFormula
- FORMULA = new InstallerAllowedByManifestFormula();
+ private static final InstallerAllowedByManifestFormula FORMULA =
+ new InstallerAllowedByManifestFormula();
@Test
public void testFormulaMatches_installerAndCertBothInManifest() {
@@ -115,6 +115,7 @@
return new AppInstallMetadata.Builder()
.setPackageName("abc")
.setAppCertificates(Collections.emptyList())
+ .setAppCertificateLineage(Collections.emptyList())
.setInstallerCertificates(Collections.emptyList())
.setInstallerName("abc")
.setVersionCode(-1)
diff --git a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java
index 7e4c138..9058a71 100644
--- a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java
+++ b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java
@@ -54,6 +54,20 @@
}
@Test
+ public void createEqualsFormula_appCertificateLineage() {
+ String appCertificate = "com.test.app";
+ IntegrityFormula formula =
+ IntegrityFormula.Application.certificateLineageContains(appCertificate);
+
+ AtomicFormula.StringAtomicFormula stringAtomicFormula =
+ (AtomicFormula.StringAtomicFormula) formula;
+
+ assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.APP_CERTIFICATE_LINEAGE);
+ assertThat(stringAtomicFormula.getValue()).matches(appCertificate);
+ assertThat(stringAtomicFormula.getIsHashedValue()).isTrue();
+ }
+
+ @Test
public void createEqualsFormula_installerName() {
String installerName = "com.test.app";
IntegrityFormula formula = IntegrityFormula.Installer.packageNameEquals(installerName);
@@ -138,8 +152,10 @@
IntegrityFormula formula1 = IntegrityFormula.Application.packageNameEquals(packageName);
IntegrityFormula formula2 =
IntegrityFormula.Application.certificatesContain(certificateName);
+ IntegrityFormula formula3 =
+ IntegrityFormula.Application.certificateLineageContains(certificateName);
- IntegrityFormula compoundFormula = IntegrityFormula.all(formula1, formula2);
+ IntegrityFormula compoundFormula = IntegrityFormula.all(formula1, formula2, formula3);
assertThat(compoundFormula.getTag()).isEqualTo(COMPOUND_FORMULA_TAG);
}
@@ -151,8 +167,10 @@
IntegrityFormula formula1 = IntegrityFormula.Application.packageNameEquals(packageName);
IntegrityFormula formula2 =
IntegrityFormula.Application.certificatesContain(certificateName);
+ IntegrityFormula formula3 =
+ IntegrityFormula.Application.certificateLineageContains(certificateName);
- IntegrityFormula compoundFormula = IntegrityFormula.any(formula1, formula2);
+ IntegrityFormula compoundFormula = IntegrityFormula.any(formula1, formula2, formula3);
assertThat(compoundFormula.getTag()).isEqualTo(COMPOUND_FORMULA_TAG);
}
diff --git a/core/tests/coretests/src/android/util/RotationUtilsTest.java b/core/tests/coretests/src/android/util/RotationUtilsTest.java
index 5dbe03e..826eb30 100644
--- a/core/tests/coretests/src/android/util/RotationUtilsTest.java
+++ b/core/tests/coretests/src/android/util/RotationUtilsTest.java
@@ -17,12 +17,14 @@
package android.util;
import static android.util.RotationUtils.rotateBounds;
+import static android.util.RotationUtils.rotatePoint;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static org.junit.Assert.assertEquals;
+import android.graphics.Point;
import android.graphics.Rect;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -58,4 +60,23 @@
rotateBounds(testResult, testParent, ROTATION_270);
assertEquals(new Rect(520, 40, 580, 120), testResult);
}
+
+ @Test
+ public void testRotatePoint() {
+ int parentW = 1000;
+ int parentH = 600;
+ Point testPt = new Point(60, 40);
+
+ Point testResult = new Point(testPt);
+ rotatePoint(testResult, ROTATION_90, parentW, parentH);
+ assertEquals(new Point(40, 940), testResult);
+
+ testResult.set(testPt.x, testPt.y);
+ rotatePoint(testResult, ROTATION_180, parentW, parentH);
+ assertEquals(new Point(940, 560), testResult);
+
+ testResult.set(testPt.x, testPt.y);
+ rotatePoint(testResult, ROTATION_270, parentW, parentH);
+ assertEquals(new Point(560, 60), testResult);
+ }
}
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritableViewInfoTest.java b/core/tests/coretests/src/android/view/stylus/HandwritableViewInfoTest.java
new file mode 100644
index 0000000..4bea54b
--- /dev/null
+++ b/core/tests/coretests/src/android/view/stylus/HandwritableViewInfoTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.view.stylus;
+
+import static android.view.stylus.HandwritingTestUtil.createView;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.view.HandwritingInitiator;
+import android.view.View;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class HandwritableViewInfoTest {
+
+ @Test
+ public void constructorTest() {
+ final Rect rect = new Rect(1, 2, 3, 4);
+ final View view = createView(rect);
+ final HandwritingInitiator.HandwritableViewInfo handwritableViewInfo =
+ new HandwritingInitiator.HandwritableViewInfo(view);
+
+ assertThat(handwritableViewInfo.getView()).isEqualTo(view);
+ // It's labeled dirty by default.
+ assertTrue(handwritableViewInfo.mIsDirty);
+ }
+
+ @Test
+ public void update() {
+ final Rect rect = new Rect(1, 2, 3, 4);
+ final View view = createView(rect);
+ final HandwritingInitiator.HandwritableViewInfo handwritableViewInfo =
+ new HandwritingInitiator.HandwritableViewInfo(view);
+
+ assertThat(handwritableViewInfo.getView()).isEqualTo(view);
+
+ final boolean isViewInfoValid = handwritableViewInfo.update();
+
+ assertTrue(isViewInfoValid);
+ assertThat(handwritableViewInfo.getHandwritingArea()).isEqualTo(rect);
+ assertFalse(handwritableViewInfo.mIsDirty);
+ }
+
+ @Test
+ public void update_viewDisableAutoHandwriting() {
+ final Rect rect = new Rect(1, 2, 3, 4);
+ final View view = HandwritingTestUtil.createView(rect, false /* autoHandwritingEnabled */);
+ final HandwritingInitiator.HandwritableViewInfo handwritableViewInfo =
+ new HandwritingInitiator.HandwritableViewInfo(view);
+
+ assertThat(handwritableViewInfo.getView()).isEqualTo(view);
+
+ final boolean isViewInfoValid = handwritableViewInfo.update();
+
+ // Return false because the view disabled autoHandwriting.
+ assertFalse(isViewInfoValid);
+ // The view disabled the autoHandwriting, and it won't update the handwriting area.
+ assertThat(handwritableViewInfo.getHandwritingArea()).isNull();
+ }
+
+}
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingAreaTrackerTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingAreaTrackerTest.java
new file mode 100644
index 0000000..db4707a
--- /dev/null
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingAreaTrackerTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.view.stylus;
+
+import static android.view.stylus.HandwritingTestUtil.createView;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.view.HandwritingInitiator;
+import android.view.View;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+
+/**
+ * Tests for {@link HandwritingInitiator.HandwritingAreaTracker}
+ *
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:android.view.stylus.HandwritingAreaTrackerTest
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class HandwritingAreaTrackerTest {
+ HandwritingInitiator.HandwritingAreaTracker mHandwritingAreaTracker;
+ Context mContext;
+
+ @Before
+ public void setup() {
+ final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mContext = mInstrumentation.getTargetContext();
+ mHandwritingAreaTracker = new HandwritingInitiator.HandwritingAreaTracker();
+ }
+
+ @Test
+ public void updateHandwritingAreaForView_singleView() {
+ Rect rect = new Rect(0, 0, 100, 100);
+ View view = createView(rect);
+ mHandwritingAreaTracker.updateHandwritingAreaForView(view);
+
+ List<HandwritingInitiator.HandwritableViewInfo> viewInfos =
+ mHandwritingAreaTracker.computeViewInfos();
+
+ assertThat(viewInfos.size()).isEqualTo(1);
+ assertThat(viewInfos.get(0).getHandwritingArea()).isEqualTo(rect);
+ assertThat(viewInfos.get(0).getView()).isEqualTo(view);
+ }
+
+ @Test
+ public void updateHandwritingAreaForView_multipleViews() {
+ Rect rect1 = new Rect(0, 0, 100, 100);
+ Rect rect2 = new Rect(100, 100, 200, 200);
+
+ View view1 = createView(rect1);
+ View view2 = createView(rect2);
+ mHandwritingAreaTracker.updateHandwritingAreaForView(view1);
+ mHandwritingAreaTracker.updateHandwritingAreaForView(view2);
+
+ List<HandwritingInitiator.HandwritableViewInfo> viewInfos =
+ mHandwritingAreaTracker.computeViewInfos();
+
+ assertThat(viewInfos.size()).isEqualTo(2);
+ assertThat(viewInfos.get(0).getView()).isEqualTo(view1);
+ assertThat(viewInfos.get(0).getHandwritingArea()).isEqualTo(rect1);
+
+ assertThat(viewInfos.get(1).getView()).isEqualTo(view2);
+ assertThat(viewInfos.get(1).getHandwritingArea()).isEqualTo(rect2);
+ }
+
+ @Test
+ public void updateHandwritingAreaForView_afterDisableAutoHandwriting() {
+ Rect rect1 = new Rect(0, 0, 100, 100);
+ Rect rect2 = new Rect(100, 100, 200, 200);
+
+ View view1 = createView(rect1);
+ View view2 = createView(rect2);
+ mHandwritingAreaTracker.updateHandwritingAreaForView(view1);
+ mHandwritingAreaTracker.updateHandwritingAreaForView(view2);
+
+ // There should be 2 views tracked.
+ assertThat(mHandwritingAreaTracker.computeViewInfos().size()).isEqualTo(2);
+
+ // Disable autoHandwriting for view1 and update handwriting area.
+ view1.setAutoHandwritingEnabled(false);
+ mHandwritingAreaTracker.updateHandwritingAreaForView(view1);
+
+ List<HandwritingInitiator.HandwritableViewInfo> viewInfos =
+ mHandwritingAreaTracker.computeViewInfos();
+ // The view1 has disabled the autoHandwriting, it's not tracked anymore.
+ assertThat(viewInfos.size()).isEqualTo(1);
+
+ // view2 is still tracked.
+ assertThat(viewInfos.get(0).getView()).isEqualTo(view2);
+ assertThat(viewInfos.get(0).getHandwritingArea()).isEqualTo(rect2);
+ }
+
+ @Test
+ public void updateHandwritingAreaForView_removesInactiveView() {
+ Rect rect1 = new Rect(0, 0, 100, 100);
+ Rect rect2 = new Rect(100, 100, 200, 200);
+
+ View view1 = createView(rect1);
+ View view2 = createView(rect2);
+ mHandwritingAreaTracker.updateHandwritingAreaForView(view1);
+ mHandwritingAreaTracker.updateHandwritingAreaForView(view2);
+
+ // There should be 2 viewInfos tracked.
+ assertThat(mHandwritingAreaTracker.computeViewInfos().size()).isEqualTo(2);
+
+ // Disable autoHandwriting for view1, but update handwriting area for view2.
+ view1.setAutoHandwritingEnabled(false);
+ mHandwritingAreaTracker.updateHandwritingAreaForView(view2);
+
+ List<HandwritingInitiator.HandwritableViewInfo> viewInfos =
+ mHandwritingAreaTracker.computeViewInfos();
+ // The view1 has disabled the autoHandwriting, it's not tracked anymore.
+ assertThat(viewInfos.size()).isEqualTo(1);
+
+ // view2 is still tracked.
+ assertThat(viewInfos.get(0).getView()).isEqualTo(view2);
+ assertThat(viewInfos.get(0).getHandwritingArea()).isEqualTo(rect2);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index e11fe17..1ae9649 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -19,6 +19,7 @@
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
+import static android.view.stylus.HandwritingTestUtil.createView;
import static com.google.common.truth.Truth.assertThat;
@@ -38,7 +39,6 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
-import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -78,7 +78,8 @@
mHandwritingInitiator =
spy(new HandwritingInitiator(viewConfiguration, inputMethodManager));
- mTestView = createMockView(sHwArea, true);
+ mTestView = createView(sHwArea, true);
+ mHandwritingInitiator.updateHandwritingAreasForView(mTestView);
}
@Test
@@ -195,8 +196,25 @@
}
@Test
+ public void onTouchEvent_focusView_stylusMoveOnce_withinHWArea() {
+ final int x1 = (sHwArea.left + sHwArea.right) / 2;
+ final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
+ MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
+ mHandwritingInitiator.onTouchEvent(stylusEvent1);
+
+ final int x2 = x1 + TOUCH_SLOP * 2;
+ final int y2 = y1;
+
+ MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
+ mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+ // HandwritingInitiator will request focus for the registered view.
+ verify(mTestView, times(1)).requestFocus();
+ }
+
+ @Test
public void autoHandwriting_whenDisabled_wontStartHW() {
- View mockView = createMockView(sHwArea, false);
+ View mockView = createView(sHwArea, false);
mHandwritingInitiator.onInputConnectionCreated(mockView);
final int x1 = (sHwArea.left + sHwArea.right) / 2;
final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
@@ -273,25 +291,4 @@
1 /* yPrecision */, 0 /* deviceId */, 0 /* edgeFlags */,
InputDevice.SOURCE_TOUCHSCREEN, 0 /* flags */);
}
-
- private View createMockView(Rect viewBound, boolean autoHandwritingEnabled) {
- // mock a parent so that HandwritingInitiator can get
- ViewGroup parent = new ViewGroup(mContext) {
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- // We don't layout this view.
- }
- @Override
- public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
- r.set(viewBound);
- return true;
- }
- };
-
- View mockView = mock(View.class);
- when(mockView.isAttachedToWindow()).thenReturn(true);
- when(mockView.isAutoHandwritingEnabled()).thenReturn(autoHandwritingEnabled);
- parent.addView(mockView);
- return mockView;
- }
}
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingTestUtil.java b/core/tests/coretests/src/android/view/stylus/HandwritingTestUtil.java
new file mode 100644
index 0000000..6daf880
--- /dev/null
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingTestUtil.java
@@ -0,0 +1,59 @@
+/*
+ * 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.view.stylus;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+public class HandwritingTestUtil {
+ public static View createView(Rect handwritingArea) {
+ return createView(handwritingArea, true);
+ }
+
+ public static View createView(Rect handwritingArea, boolean autoHandwritingEnabled) {
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ final Context context = instrumentation.getTargetContext();
+ // mock a parent so that HandwritingInitiator can get
+ final ViewGroup parent = new ViewGroup(context) {
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ // We don't layout this view.
+ }
+ @Override
+ public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
+ r.set(handwritingArea);
+ return true;
+ }
+ };
+
+ View view = spy(new View(context));
+ when(view.isAttachedToWindow()).thenReturn(true);
+ when(view.isAggregatedVisible()).thenReturn(true);
+ when(view.getHandwritingArea()).thenReturn(handwritingArea);
+ view.setAutoHandwritingEnabled(autoHandwritingEnabled);
+ parent.addView(view);
+ return view;
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 7f85982..139bc36 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -192,7 +192,7 @@
}
@Override
- public Cursor queryResolver(ContentResolver resolver, Uri uri) {
+ public Cursor queryResolver(ContentResolver resolver, String[] projection, Uri uri) {
if (sOverrides.resolverCursor != null) {
return sOverrides.resolverCursor;
}
@@ -201,7 +201,7 @@
throw new SecurityException("Test exception handling");
}
- return super.queryResolver(resolver, uri);
+ return super.queryResolver(resolver, projection, uri);
}
@Override
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 2d91389..ac5daf0 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -508,6 +508,8 @@
<permission name="android.permission.MANAGE_APP_HIBERNATION"/>
<!-- Permission required for CTS test - ResourceObserverNativeTest -->
<permission name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" />
+ <!-- Permission required for CTS test - MediaCodecResourceTest -->
+ <permission name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" />
<!-- Permission required for CTS test - CtsAlarmManagerTestCases -->
<permission name="android.permission.SCHEDULE_PRIORITIZED_ALARM" />
<!-- Permission required for CTS test - SystemMediaRouter2Test -->
diff --git a/graphics/java/android/graphics/PixelFormat.java b/graphics/java/android/graphics/PixelFormat.java
index dde757b..3ec5b9c 100644
--- a/graphics/java/android/graphics/PixelFormat.java
+++ b/graphics/java/android/graphics/PixelFormat.java
@@ -29,7 +29,7 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef({RGBA_8888, RGBX_8888, RGBA_F16, RGBA_1010102, RGB_888, RGB_565})
+ @IntDef({RGBA_8888, RGBX_8888, RGBA_F16, RGBA_1010102, RGB_888, RGB_565, R_8})
public @interface Format { }
// NOTE: these constants must match the values from graphics/common/x.x/types.hal
@@ -93,6 +93,9 @@
/** @hide */
public static final int HSV_888 = 0x37;
+ /** @hide */
+ public static final int R_8 = 0x38;
+
/**
* @deprecated use {@link android.graphics.ImageFormat#JPEG
* ImageFormat.JPEG} instead.
@@ -142,6 +145,10 @@
info.bitsPerPixel = 64;
info.bytesPerPixel = 8;
break;
+ case R_8:
+ info.bitsPerPixel = 8;
+ info.bytesPerPixel = 1;
+ break;
default:
throw new IllegalArgumentException("unknown pixel format " + format);
}
@@ -235,6 +242,8 @@
return "HSV_888";
case JPEG:
return "JPEG";
+ case R_8:
+ return "R_8";
default:
return Integer.toString(format);
}
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 8f368c2..d35ecbd 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -924,6 +924,7 @@
* Checks if an activity is embedded and its presentation is customized by a
* {@link android.window.TaskFragmentOrganizer} to only occupy a portion of Task bounds.
*/
+ @Override
public boolean isActivityEmbedded(@NonNull Activity activity) {
return mPresenter.isActivityEmbedded(activity.getActivityToken());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index 9a6df23..4c505f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.back;
import android.view.MotionEvent;
+import android.window.BackEvent;
import com.android.wm.shell.common.annotations.ExternalThread;
@@ -29,7 +30,7 @@
/**
* Called when a {@link MotionEvent} is generated by a back gesture.
*/
- void onBackMotion(MotionEvent event);
+ void onBackMotion(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge);
/**
* Sets whether the back gesture is past the trigger threshold or not.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index a5140c3..32ac43d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -36,6 +36,7 @@
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceControl;
+import android.window.BackEvent;
import android.window.BackNavigationInfo;
import android.window.IOnBackInvokedCallback;
@@ -127,8 +128,8 @@
}
@Override
- public void onBackMotion(MotionEvent event) {
- mShellExecutor.execute(() -> onMotionEvent(event));
+ public void onBackMotion(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge) {
+ mShellExecutor.execute(() -> onMotionEvent(event, swipeEdge));
}
@Override
@@ -183,12 +184,12 @@
* Called when a new motion event needs to be transferred to this
* {@link BackAnimationController}
*/
- public void onMotionEvent(MotionEvent event) {
+ public void onMotionEvent(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge) {
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
initAnimation(event);
} else if (action == MotionEvent.ACTION_MOVE) {
- onMove(event);
+ onMove(event, swipeEdge);
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
onGestureFinished();
}
@@ -264,7 +265,7 @@
mTransaction.setVisibility(screenshotSurface, true);
}
- private void onMove(MotionEvent event) {
+ private void onMove(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge) {
if (!mBackGestureStarted || mBackNavigationInfo == null) {
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 1039e2a..51067a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -191,6 +191,19 @@
return null;
}
+ /**
+ * Gets a token associated with the view that can be used to grant the view focus.
+ */
+ public IBinder getFocusGrantToken(View view) {
+ SurfaceControlViewHost root = mViewRoots.get(view);
+ if (root == null) {
+ Slog.e(TAG, "Couldn't get focus grant token since view does not exist in "
+ + "SystemWindow:" + view);
+ return null;
+ }
+ return root.getFocusGrantToken();
+ }
+
private class PerDisplay {
final int mDisplayId;
private final SparseArray<SysUiWindowManager> mWwms = new SparseArray<>();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
index bdf9d51..7014fcc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
@@ -140,11 +140,8 @@
/**
* Whether the layout is eligible to be shown according to the internal state of the subclass.
- * Returns true by default if subclass doesn't override this method.
*/
- protected boolean eligibleToShowLayout() {
- return true;
- }
+ protected abstract boolean eligibleToShowLayout();
@Override
public void setConfiguration(Configuration configuration) {
@@ -214,8 +211,7 @@
boolean layoutDirectionUpdated =
mTaskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection();
if (boundsUpdated || layoutDirectionUpdated) {
- // Reposition the UI surfaces.
- updateSurfacePosition();
+ updateSurface();
}
if (layout != null && layoutDirectionUpdated) {
@@ -226,7 +222,6 @@
return true;
}
-
/**
* Updates the visibility of the layout.
*
@@ -253,8 +248,7 @@
displayLayout.getStableBounds(curStableBounds);
mDisplayLayout = displayLayout;
if (!prevStableBounds.equals(curStableBounds)) {
- // Stable bounds changed, update UI surface positions.
- updateSurfacePosition();
+ updateSurface();
mStableBounds.set(curStableBounds);
}
}
@@ -304,6 +298,14 @@
}
/**
+ * Updates the surface following a change in the task bounds, display layout stable bounds,
+ * or the layout direction.
+ */
+ protected void updateSurface() {
+ updateSurfacePosition();
+ }
+
+ /**
* Updates the position of the surface with respect to the task bounds and display layout
* stable bounds.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
index b22b829..bc1d19b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
@@ -36,8 +36,6 @@
// The alpha of a background is a number between 0 (fully transparent) to 255 (fully opaque).
// 204 is simply 255 * 0.8.
static final int BACKGROUND_DIM_ALPHA = 204;
-
- private LetterboxEduWindowManager mWindowManager;
private View mDialogContainer;
private Drawable mBackgroundDim;
@@ -58,10 +56,6 @@
super(context, attrs, defStyleAttr, defStyleRes);
}
- void inject(LetterboxEduWindowManager windowManager) {
- mWindowManager = windowManager;
- }
-
View getDialogContainer() {
return mDialogContainer;
}
@@ -70,13 +64,6 @@
return mBackgroundDim;
}
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- // Need to relayout after visibility changes since they affect size.
- mWindowManager.relayout();
- }
-
/**
* Register a callback for the dismiss button and background dim.
*
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
index d5aefb1..c461ebc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
@@ -103,7 +103,6 @@
protected View createLayout() {
setSeenLetterboxEducation();
mLayout = inflateLayout();
- mLayout.inject(this);
mAnimationController.startEnterAnimation(mLayout, /* endCallback= */
this::setDismissOnClickListener);
@@ -145,15 +144,22 @@
}
@Override
+ protected void updateSurface() {
+ // We need to relayout because the layout dimensions depend on the task bounds.
+ relayout();
+ }
+
+ @Override
protected void updateSurfacePosition(Rect taskBounds, Rect stableBounds) {
- updateSurfacePosition(/* positionX= */ taskBounds.left, /* positionY= */ taskBounds.top);
+ // Nothing to do, since the position of the surface is fixed to the top left corner (0,0)
+ // of the task (parent surface), which is the default position of a surface.
}
@Override
protected WindowManager.LayoutParams getWindowLayoutParams() {
final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds();
- return getWindowLayoutParams(/* width= */ taskBounds.right - taskBounds.left,
- /* height= */ taskBounds.bottom - taskBounds.top);
+ return getWindowLayoutParams(/* width= */ taskBounds.width(), /* height= */
+ taskBounds.height());
}
private boolean getHasSeenLetterboxEducation() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 5ebdceb..e8bae0f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -45,10 +45,12 @@
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipDescription;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.LauncherApps;
+import android.content.pm.ResolveInfo;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Bundle;
@@ -62,9 +64,11 @@
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.InstanceId;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.lang.annotation.Retention;
@@ -106,12 +110,19 @@
*/
void start(DisplayLayout displayLayout, ClipData data, InstanceId loggerSessionId) {
mLoggerSessionId = loggerSessionId;
- mSession = new DragSession(mContext, mActivityTaskManager, displayLayout, data);
+ mSession = new DragSession(mActivityTaskManager, displayLayout, data);
// TODO(b/169894807): Also update the session data with task stack changes
mSession.update();
}
/**
+ * Returns the last running task.
+ */
+ ActivityManager.RunningTaskInfo getLatestRunningTask() {
+ return mSession.runningTaskInfo;
+ }
+
+ /**
* Returns the target's regions based on the current state of the device and display.
*/
@NonNull
@@ -248,32 +259,68 @@
final UserHandle user = intent.getParcelableExtra(EXTRA_USER);
mStarter.startShortcut(packageName, id, position, opts, user);
} else {
- mStarter.startIntent(intent.getParcelableExtra(EXTRA_PENDING_INTENT),
- null, position, opts);
+ final PendingIntent launchIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
+ mStarter.startIntent(launchIntent, getStartIntentFillInIntent(launchIntent, position),
+ position, opts);
}
}
/**
+ * Returns the fill-in intent to use when starting an app from a drop.
+ */
+ @VisibleForTesting
+ Intent getStartIntentFillInIntent(PendingIntent launchIntent, @SplitPosition int position) {
+ // Get the drag app
+ final List<ResolveInfo> infos = launchIntent.queryIntentComponents(0 /* flags */);
+ final ComponentName dragIntentActivity = !infos.isEmpty()
+ ? infos.get(0).activityInfo.getComponentName()
+ : null;
+
+ // Get the current app (either fullscreen or the remaining app post-drop if in splitscreen)
+ final boolean inSplitScreen = mSplitScreen != null
+ && mSplitScreen.isSplitScreenVisible();
+ final ComponentName currentActivity;
+ if (!inSplitScreen) {
+ currentActivity = mSession.runningTaskInfo != null
+ ? mSession.runningTaskInfo.baseActivity
+ : null;
+ } else {
+ final int nonReplacedSplitPosition = position == SPLIT_POSITION_TOP_OR_LEFT
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT
+ : SPLIT_POSITION_TOP_OR_LEFT;
+ ActivityManager.RunningTaskInfo nonReplacedTaskInfo =
+ mSplitScreen.getTaskInfo(nonReplacedSplitPosition);
+ currentActivity = nonReplacedTaskInfo.baseActivity;
+ }
+
+ if (currentActivity.equals(dragIntentActivity)) {
+ // Only apply MULTIPLE_TASK if we are dragging the same activity
+ final Intent fillInIntent = new Intent();
+ fillInIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Adding MULTIPLE_TASK");
+ return fillInIntent;
+ }
+ return null;
+ }
+
+ /**
* Per-drag session data.
*/
private static class DragSession {
- private final Context mContext;
private final ActivityTaskManager mActivityTaskManager;
private final ClipData mInitialDragData;
final DisplayLayout displayLayout;
Intent dragData;
- int runningTaskId;
+ ActivityManager.RunningTaskInfo runningTaskInfo;
@WindowConfiguration.WindowingMode
int runningTaskWinMode = WINDOWING_MODE_UNDEFINED;
@WindowConfiguration.ActivityType
int runningTaskActType = ACTIVITY_TYPE_STANDARD;
- boolean runningTaskIsResizeable;
boolean dragItemSupportsSplitscreen;
- DragSession(Context context, ActivityTaskManager activityTaskManager,
+ DragSession(ActivityTaskManager activityTaskManager,
DisplayLayout dispLayout, ClipData data) {
- mContext = context;
mActivityTaskManager = activityTaskManager;
mInitialDragData = data;
displayLayout = dispLayout;
@@ -287,10 +334,9 @@
mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */);
if (!tasks.isEmpty()) {
final ActivityManager.RunningTaskInfo task = tasks.get(0);
+ runningTaskInfo = task;
runningTaskWinMode = task.getWindowingMode();
runningTaskActType = task.getActivityType();
- runningTaskId = task.taskId;
- runningTaskIsResizeable = task.isResizeable;
}
final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 7307ba3..d395f95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -26,7 +26,6 @@
import android.animation.AnimatorListenerAdapter;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
import android.app.StatusBarManager;
import android.content.ClipData;
import android.content.Context;
@@ -35,7 +34,6 @@
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.RemoteException;
import android.view.DragEvent;
import android.view.SurfaceControl;
import android.view.WindowInsets;
@@ -51,7 +49,6 @@
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.util.ArrayList;
-import java.util.List;
/**
* Coordinates the visible drop targets for the current drag.
@@ -166,17 +163,8 @@
boolean alreadyInSplit = mSplitScreenController != null
&& mSplitScreenController.isSplitScreenVisible();
if (!alreadyInSplit) {
- List<ActivityManager.RunningTaskInfo> tasks = null;
- // Figure out the splashscreen info for the existing task.
- try {
- tasks = ActivityTaskManager.getService().getTasks(1,
- false /* filterOnlyVisibleRecents */,
- false /* keepIntentExtra */);
- } catch (RemoteException e) {
- // don't show an icon / will just use the defaults
- }
- if (tasks != null && !tasks.isEmpty()) {
- ActivityManager.RunningTaskInfo taskInfo1 = tasks.get(0);
+ ActivityManager.RunningTaskInfo taskInfo1 = mPolicy.getLatestRunningTask();
+ if (taskInfo1 != null) {
Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo);
int bgColor1 = getResizingBackgroundColor(taskInfo1);
mDropZoneView1.setAppInfo(bgColor1, icon1);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 6ec8f5b..71cff02 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -28,7 +28,6 @@
import android.graphics.RectF;
import android.os.Debug;
import android.os.Handler;
-import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.util.Size;
@@ -126,7 +125,6 @@
private int mMenuState;
private PipMenuView mPipMenuView;
- private IBinder mPipMenuInputToken;
private ActionListener mMediaActionListener = new ActionListener() {
@Override
@@ -206,7 +204,6 @@
mApplier = null;
mSystemWindows.removeView(mPipMenuView);
mPipMenuView = null;
- mPipMenuInputToken = null;
}
/**
@@ -392,7 +389,6 @@
if (mApplier == null) {
mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView);
- mPipMenuInputToken = mPipMenuView.getViewRootImpl().getInputToken();
}
return mApplier != null;
@@ -539,7 +535,8 @@
try {
WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
- mPipMenuInputToken, menuState != MENU_STATE_NONE /* grantFocus */);
+ mSystemWindows.getFocusGrantToken(mPipMenuView),
+ menuState != MENU_STATE_NONE /* grantFocus */);
} catch (RemoteException e) {
Log.e(TAG, "Unable to update focus as menu appears/disappears", e);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 32861b6..f838a0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -143,6 +143,7 @@
mSystemWindows.addView(mPipMenuView,
getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
0, SHELL_ROOT_LAYER_PIP);
+ mPipMenuView.setFocusGrantToken(mSystemWindows.getFocusGrantToken(mPipMenuView));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 0141b6a..84eae9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -28,6 +28,7 @@
import android.app.RemoteAction;
import android.content.Context;
import android.os.Handler;
+import android.os.IBinder;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
@@ -69,6 +70,7 @@
private final ImageView mArrowRight;
private final ImageView mArrowDown;
private final ImageView mArrowLeft;
+ private IBinder mFocusGrantToken = null;
public TvPipMenuView(@NonNull Context context) {
this(context, null);
@@ -108,6 +110,10 @@
mListener = listener;
}
+ void setFocusGrantToken(IBinder token) {
+ mFocusGrantToken = token;
+ }
+
void show(boolean inMoveMode, int gravity) {
if (DEBUG) Log.d(TAG, "show(), inMoveMode: " + inMoveMode);
grantWindowFocus(true);
@@ -162,7 +168,7 @@
try {
WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
- getViewRootImpl().getInputToken(), grantFocus);
+ mFocusGrantToken, grantFocus);
} catch (Exception e) {
Log.e(TAG, "Unable to update focus", e);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 219530b..029d073 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -163,6 +163,7 @@
// and exit, since exit itself can trigger a number of changes that update the stages.
private boolean mShouldUpdateRecents;
private boolean mExitSplitScreenOnHide;
+ private boolean mIsDividerRemoteAnimating;
/** The target stage to dismiss to when unlock after folded. */
@StageType
@@ -389,6 +390,7 @@
RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps,
final IRemoteAnimationFinishedCallback finishedCallback) {
+ mIsDividerRemoteAnimating = true;
RemoteAnimationTarget[] augmentedNonApps =
new RemoteAnimationTarget[nonApps.length + 1];
for (int i = 0; i < nonApps.length; ++i) {
@@ -400,6 +402,7 @@
new IRemoteAnimationFinishedCallback.Stub() {
@Override
public void onAnimationFinished() throws RemoteException {
+ mIsDividerRemoteAnimating = false;
mShouldUpdateRecents = true;
mSyncQueue.queue(evictWct);
mSyncQueue.runInSync(t -> setDividerVisibility(true, t));
@@ -423,6 +426,7 @@
@Override
public void onAnimationCancelled() {
+ mIsDividerRemoteAnimating = false;
mShouldUpdateRecents = true;
mSyncQueue.queue(evictWct);
mSyncQueue.runInSync(t -> setDividerVisibility(true, t));
@@ -467,6 +471,7 @@
// Using legacy transitions, so we can't use blast sync since it conflicts.
mTaskOrganizer.applyTransaction(wct);
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(mSplitLayout, t));
}
/** Start an intent and a task ordered by {@code intentFirst}. */
@@ -1055,7 +1060,7 @@
private void applyDividerVisibility(SurfaceControl.Transaction t) {
final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
- if (dividerLeash == null) return;
+ if (mIsDividerRemoteAnimating || dividerLeash == null) return;
if (mDividerVisible) {
t.show(dividerLeash);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index ddf01a8..34d98ee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -201,6 +201,7 @@
"Display is changing, check if it should be seamless.");
boolean checkedDisplayLayout = false;
boolean hasTask = false;
+ boolean displayExplicitSeamless = false;
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
@@ -209,7 +210,6 @@
// This container isn't rotating, so we can ignore it.
if (change.getEndRotation() == change.getStartRotation()) continue;
-
if ((change.getFlags() & FLAG_IS_DISPLAY) != 0) {
// In the presence of System Alert windows we can not seamlessly rotate.
if ((change.getFlags() & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
@@ -217,6 +217,8 @@
" display has system alert windows, so not seamless.");
return false;
}
+ displayExplicitSeamless =
+ change.getRotationAnimation() == ROTATION_ANIMATION_SEAMLESS;
} else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -268,8 +270,8 @@
}
}
- // ROTATION_ANIMATION_SEAMLESS can only be requested by task.
- if (hasTask) {
+ // ROTATION_ANIMATION_SEAMLESS can only be requested by task or display.
+ if (hasTask || displayExplicitSeamless) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Rotation IS seamless.");
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
index 7f8eaf1..7e95814 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
@@ -16,10 +16,14 @@
package com.android.wm.shell.util;
+import android.graphics.Point;
+import android.util.RotationUtils;
import android.view.SurfaceControl;
/**
- * Utility class that takes care of counter-rotating surfaces during a transition animation.
+ * Utility class that takes care of rotating unchanging child-surfaces to match the parent rotation
+ * during a transition animation. This gives the illusion that the child surfaces haven't rotated
+ * relative to the screen.
*/
public class CounterRotator {
private SurfaceControl mSurface = null;
@@ -33,29 +37,30 @@
* Sets up this rotator.
*
* @param rotateDelta is the forward rotation change (the rotation the display is making).
- * @param displayW (and H) Is the size of the rotating display.
+ * @param parentW (and H) Is the size of the rotating parent after the rotation.
*/
public void setup(SurfaceControl.Transaction t, SurfaceControl parent, int rotateDelta,
- float displayW, float displayH) {
+ float parentW, float parentH) {
if (rotateDelta == 0) return;
- // We want to counter-rotate, so subtract from 4
- rotateDelta = 4 - (rotateDelta + 4) % 4;
mSurface = new SurfaceControl.Builder()
.setName("Transition Unrotate")
.setContainerLayer()
.setParent(parent)
.build();
- // column-major
- if (rotateDelta == 1) {
- t.setMatrix(mSurface, 0, 1, -1, 0);
- t.setPosition(mSurface, displayW, 0);
- } else if (rotateDelta == 2) {
- t.setMatrix(mSurface, -1, 0, 0, -1);
- t.setPosition(mSurface, displayW, displayH);
- } else if (rotateDelta == 3) {
- t.setMatrix(mSurface, 0, -1, 1, 0);
- t.setPosition(mSurface, 0, displayH);
+ // Rotate forward to match the new rotation (rotateDelta is the forward rotation the parent
+ // already took). Child surfaces will be in the old rotation relative to the new parent
+ // rotation, so we need to forward-rotate the child surfaces to match.
+ RotationUtils.rotateSurface(t, mSurface, rotateDelta);
+ final Point tmpPt = new Point(0, 0);
+ // parentW/H are the size in the END rotation, the rotation utilities expect the starting
+ // size. So swap them if necessary
+ if ((rotateDelta % 2) != 0) {
+ final float w = parentW;
+ parentW = parentH;
+ parentH = w;
}
+ RotationUtils.rotatePoint(tmpPt, rotateDelta, (int) parentW, (int) parentH);
+ t.setPosition(mSurface, tmpPt.x, tmpPt.y);
t.show(mSurface);
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
index 0bc6936..278ba9b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -68,9 +68,11 @@
}
teardown {
- notifyManager.setBubblesAllowed(testApp.component.packageName,
+ test {
+ notifyManager.setBubblesAllowed(testApp.component.packageName,
uid, NotificationManager.BUBBLE_PREFERENCE_NONE)
- testApp.exit()
+ testApp.exit()
+ }
}
extraSpec(this)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 5ceb939..467cadc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -71,24 +71,6 @@
}
}
- @FlakyTest
- @Test
- fun runPresubmitAssertion() {
- flickerRule.checkPresubmitAssertions()
- }
-
- @FlakyTest
- @Test
- fun runPostsubmitAssertion() {
- flickerRule.checkPostsubmitAssertions()
- }
-
- @FlakyTest
- @Test
- fun runFlakyAssertion() {
- flickerRule.checkFlakyAssertions()
- }
-
/** {@inheritDoc} */
@FlakyTest(bugId = 206753786)
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index 4de1c03..e00d749 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -78,24 +78,6 @@
}
}
- @FlakyTest
- @Test
- fun runPresubmitAssertion() {
- flickerRule.checkPresubmitAssertions()
- }
-
- @FlakyTest
- @Test
- fun runPostsubmitAssertion() {
- flickerRule.checkPostsubmitAssertions()
- }
-
- @FlakyTest
- @Test
- fun runFlakyAssertion() {
- flickerRule.checkFlakyAssertions()
- }
-
/** {@inheritDoc} */
@FlakyTest(bugId = 206753786)
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
index de0e98c..5214daa0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
@@ -67,24 +67,6 @@
}
}
- @FlakyTest
- @Test
- fun runPresubmitAssertion() {
- flickerRule.checkPresubmitAssertions()
- }
-
- @FlakyTest
- @Test
- fun runPostsubmitAssertion() {
- flickerRule.checkPostsubmitAssertions()
- }
-
- @FlakyTest
- @Test
- fun runFlakyAssertion() {
- flickerRule.checkFlakyAssertions()
- }
-
/** {@inheritDoc} */
@FlakyTest(bugId = 206753786)
@Test
@@ -110,4 +92,4 @@
repetitions = 3)
}
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
index 6080f3a..403dbf9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
@@ -22,7 +22,7 @@
import android.hardware.display.DisplayManager;
import android.testing.TestableContext;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.After;
import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 21ced0d..b11f910 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -30,6 +30,7 @@
import android.testing.AndroidTestingRunner;
import android.view.MotionEvent;
import android.view.SurfaceControl;
+import android.window.BackEvent;
import android.window.BackNavigationInfo;
import androidx.test.filters.SmallTest;
@@ -94,7 +95,9 @@
SurfaceControl screenshotSurface = new SurfaceControl();
HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class);
createNavigationInfo(topWindowLeash, screenshotSurface, hardwareBuffer);
- mController.onMotionEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0));
+ mController.onMotionEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0),
+ BackEvent.EDGE_LEFT);
verify(mTransaction).setBuffer(screenshotSurface, hardwareBuffer);
verify(mTransaction).setVisibility(screenshotSurface, true);
verify(mTransaction).apply();
@@ -106,8 +109,12 @@
SurfaceControl screenshotSurface = new SurfaceControl();
HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class);
createNavigationInfo(topWindowLeash, screenshotSurface, hardwareBuffer);
- mController.onMotionEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0));
- mController.onMotionEvent(MotionEvent.obtain(10, 0, MotionEvent.ACTION_MOVE, 100, 100, 0));
+ mController.onMotionEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0),
+ BackEvent.EDGE_LEFT);
+ mController.onMotionEvent(
+ MotionEvent.obtain(10, 0, MotionEvent.ACTION_MOVE, 100, 100, 0),
+ BackEvent.EDGE_LEFT);
verify(mTransaction).setPosition(topWindowLeash, 100, 100);
verify(mTransaction, atLeastOnce()).apply();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 35e4982..bb6026c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -23,6 +23,8 @@
import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -32,6 +34,7 @@
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
+import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
@@ -50,9 +53,11 @@
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipDescription;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Insets;
@@ -177,6 +182,12 @@
info.configuration.windowConfiguration.setActivityType(actType);
info.configuration.windowConfiguration.setWindowingMode(winMode);
info.isResizeable = true;
+ info.baseActivity = new ComponentName(getInstrumentation().getContext().getPackageName(),
+ ".ActivityWithMode" + winMode);
+ ActivityInfo activityInfo = new ActivityInfo();
+ activityInfo.packageName = info.baseActivity.getPackageName();
+ activityInfo.name = info.baseActivity.getClassName();
+ info.topActivityInfo = activityInfo;
return info;
}
@@ -252,6 +263,62 @@
}
}
+ @Test
+ public void testLaunchMultipleTask_differentActivity() {
+ setRunningTask(mFullscreenAppTask);
+ mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+ Intent fillInIntent = mPolicy.getStartIntentFillInIntent(mock(PendingIntent.class), 0);
+ assertNull(fillInIntent);
+ }
+
+ @Test
+ public void testLaunchMultipleTask_differentActivity_inSplitscreen() {
+ setRunningTask(mFullscreenAppTask);
+ doReturn(true).when(mSplitScreenStarter).isSplitScreenVisible();
+ doReturn(mFullscreenAppTask).when(mSplitScreenStarter).getTaskInfo(anyInt());
+ mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+ Intent fillInIntent = mPolicy.getStartIntentFillInIntent(mock(PendingIntent.class), 0);
+ assertNull(fillInIntent);
+ }
+
+ @Test
+ public void testLaunchMultipleTask_sameActivity() {
+ setRunningTask(mFullscreenAppTask);
+
+ // Replace the mocked drag pending intent and ensure it resolves to the same activity
+ PendingIntent launchIntent = mock(PendingIntent.class);
+ ResolveInfo launchInfo = new ResolveInfo();
+ launchInfo.activityInfo = mFullscreenAppTask.topActivityInfo;
+ doReturn(Collections.singletonList(launchInfo))
+ .when(launchIntent).queryIntentComponents(anyInt());
+ mActivityClipData.getItemAt(0).getIntent().putExtra(ClipDescription.EXTRA_PENDING_INTENT,
+ launchIntent);
+
+ mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+ Intent fillInIntent = mPolicy.getStartIntentFillInIntent(launchIntent, 0);
+ assertTrue((fillInIntent.getFlags() & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0);
+ }
+
+ @Test
+ public void testLaunchMultipleTask_sameActivity_inSplitScreen() {
+ setRunningTask(mFullscreenAppTask);
+
+ // Replace the mocked drag pending intent and ensure it resolves to the same activity
+ PendingIntent launchIntent = mock(PendingIntent.class);
+ ResolveInfo launchInfo = new ResolveInfo();
+ launchInfo.activityInfo = mFullscreenAppTask.topActivityInfo;
+ doReturn(Collections.singletonList(launchInfo))
+ .when(launchIntent).queryIntentComponents(anyInt());
+ mActivityClipData.getItemAt(0).getIntent().putExtra(ClipDescription.EXTRA_PENDING_INTENT,
+ launchIntent);
+
+ doReturn(true).when(mSplitScreenStarter).isSplitScreenVisible();
+ doReturn(mFullscreenAppTask).when(mSplitScreenStarter).getTaskInfo(anyInt());
+ mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+ Intent fillInIntent = mPolicy.getStartIntentFillInIntent(launchIntent, 0);
+ assertTrue((fillInIntent.getFlags() & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0);
+ }
+
private Target filterTargetByType(ArrayList<Target> targets, int type) {
for (Target t : targets) {
if (type == t.type) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
index f10dc16..b976c12 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
@@ -24,8 +24,8 @@
import android.testing.TestableContext;
import android.testing.TestableLooper;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.common.ShellExecutor;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
index 078e2b6..16e9239 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
@@ -45,8 +45,8 @@
import android.window.IWindowContainerToken;
import android.window.WindowContainerToken;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
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 0f4a06f..dbf93b4 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
@@ -591,6 +591,13 @@
.setRotate().build())
.build();
assertFalse(DefaultTransitionHandler.isRotationSeamless(noTask, displays));
+
+ // Seamless if display is explicitly seamless.
+ final TransitionInfo seamlessDisplay = new TransitionInfoBuilder(TRANSIT_CHANGE)
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
+ .setRotate(ROTATION_ANIMATION_SEAMLESS).build())
+ .build();
+ assertTrue(DefaultTransitionHandler.isRotationSeamless(seamlessDisplay, displays));
}
class TransitionInfoBuilder {
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 939b679..4563259 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -16,9 +16,12 @@
package android.media;
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.ImageFormat;
import android.graphics.Rect;
@@ -1934,12 +1937,41 @@
@NonNull
public static MediaCodec createByCodecName(@NonNull String name)
throws IOException {
- return new MediaCodec(
- name, false /* nameIsType */, false /* unused */);
+ return new MediaCodec(name, false /* nameIsType */, false /* encoder */);
}
- private MediaCodec(
- @NonNull String name, boolean nameIsType, boolean encoder) {
+ /**
+ * This is the same as createByCodecName, but allows for instantiating a codec on behalf of a
+ * client process. This is used for system apps or system services that create MediaCodecs on
+ * behalf of other processes and will reclaim resources as necessary from processes with lower
+ * priority than the client process, rather than processes with lower priority than the system
+ * app or system service. Likely to be used with information obtained from
+ * {@link android.media.MediaCodecList}.
+ * @param name
+ * @param clientPid
+ * @param clientUid
+ * @throws IOException if the codec cannot be created.
+ * @throws IllegalArgumentException if name is not valid.
+ * @throws NullPointerException if name is null.
+ * @throws SecurityException if the MEDIA_RESOURCE_OVERRIDE_PID permission is not granted.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MEDIA_RESOURCE_OVERRIDE_PID)
+ public static MediaCodec createByCodecNameForClient(@NonNull String name, int clientPid,
+ int clientUid) throws IOException {
+ return new MediaCodec(name, false /* nameIsType */, false /* encoder */, clientPid,
+ clientUid);
+ }
+
+ private MediaCodec(@NonNull String name, boolean nameIsType, boolean encoder) {
+ this(name, nameIsType, encoder, -1 /* pid */, -1 /* uid */);
+ }
+
+ private MediaCodec(@NonNull String name, boolean nameIsType, boolean encoder, int pid,
+ int uid) {
Looper looper;
if ((looper = Looper.myLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
@@ -1957,7 +1989,7 @@
// save name used at creation
mNameAtCreation = nameIsType ? null : name;
- native_setup(name, nameIsType, encoder);
+ native_setup(name, nameIsType, encoder, pid, uid);
}
private String mNameAtCreation;
@@ -4991,7 +5023,7 @@
private static native final void native_init();
private native final void native_setup(
- @NonNull String name, boolean nameIsType, boolean encoder);
+ @NonNull String name, boolean nameIsType, boolean encoder, int pid, int uid);
private native final void native_finalize();
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index f694482..c8d2d1e 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -202,7 +202,7 @@
JMediaCodec::JMediaCodec(
JNIEnv *env, jobject thiz,
- const char *name, bool nameIsType, bool encoder)
+ const char *name, bool nameIsType, bool encoder, int pid, int uid)
: mClass(NULL),
mObject(NULL) {
jclass clazz = env->GetObjectClass(thiz);
@@ -220,12 +220,12 @@
ANDROID_PRIORITY_VIDEO);
if (nameIsType) {
- mCodec = MediaCodec::CreateByType(mLooper, name, encoder, &mInitStatus);
+ mCodec = MediaCodec::CreateByType(mLooper, name, encoder, &mInitStatus, pid, uid);
if (mCodec == nullptr || mCodec->getName(&mNameAtCreation) != OK) {
mNameAtCreation = "(null)";
}
} else {
- mCodec = MediaCodec::CreateByComponentName(mLooper, name, &mInitStatus);
+ mCodec = MediaCodec::CreateByComponentName(mLooper, name, &mInitStatus, pid, uid);
mNameAtCreation = name;
}
CHECK((mCodec != NULL) != (mInitStatus != OK));
@@ -3136,7 +3136,7 @@
static void android_media_MediaCodec_native_setup(
JNIEnv *env, jobject thiz,
- jstring name, jboolean nameIsType, jboolean encoder) {
+ jstring name, jboolean nameIsType, jboolean encoder, int pid, int uid) {
if (name == NULL) {
jniThrowException(env, "java/lang/NullPointerException", NULL);
return;
@@ -3148,24 +3148,33 @@
return;
}
- sp<JMediaCodec> codec = new JMediaCodec(env, thiz, tmp, nameIsType, encoder);
+ sp<JMediaCodec> codec = new JMediaCodec(env, thiz, tmp, nameIsType, encoder, pid, uid);
const status_t err = codec->initCheck();
if (err == NAME_NOT_FOUND) {
// fail and do not try again.
jniThrowException(env, "java/lang/IllegalArgumentException",
- String8::format("Failed to initialize %s, error %#x", tmp, err));
+ String8::format("Failed to initialize %s, error %#x (NAME_NOT_FOUND)", tmp, err));
env->ReleaseStringUTFChars(name, tmp);
return;
- } if (err == NO_MEMORY) {
+ }
+ if (err == NO_MEMORY) {
throwCodecException(env, err, ACTION_CODE_TRANSIENT,
- String8::format("Failed to initialize %s, error %#x", tmp, err));
+ String8::format("Failed to initialize %s, error %#x (NO_MEMORY)", tmp, err));
env->ReleaseStringUTFChars(name, tmp);
return;
- } else if (err != OK) {
+ }
+ if (err == PERMISSION_DENIED) {
+ jniThrowException(env, "java/lang/SecurityException",
+ String8::format("Failed to initialize %s, error %#x (PERMISSION_DENIED)", tmp,
+ err));
+ env->ReleaseStringUTFChars(name, tmp);
+ return;
+ }
+ if (err != OK) {
// believed possible to try again
jniThrowException(env, "java/io/IOException",
- String8::format("Failed to find matching codec %s, error %#x", tmp, err));
+ String8::format("Failed to find matching codec %s, error %#x (?)", tmp, err));
env->ReleaseStringUTFChars(name, tmp);
return;
}
@@ -3174,7 +3183,7 @@
codec->registerSelf();
- setMediaCodec(env,thiz, codec);
+ setMediaCodec(env, thiz, codec);
}
static void android_media_MediaCodec_native_finalize(
@@ -3478,7 +3487,7 @@
{ "native_init", "()V", (void *)android_media_MediaCodec_native_init },
- { "native_setup", "(Ljava/lang/String;ZZ)V",
+ { "native_setup", "(Ljava/lang/String;ZZII)V",
(void *)android_media_MediaCodec_native_setup },
{ "native_finalize", "()V",
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index ee456c9..616c31b 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -55,7 +55,7 @@
struct JMediaCodec : public AHandler {
JMediaCodec(
JNIEnv *env, jobject thiz,
- const char *name, bool nameIsType, bool encoder);
+ const char *name, bool nameIsType, bool encoder, int pid, int uid);
status_t initCheck() const;
diff --git a/native/android/OWNERS b/native/android/OWNERS
index 02dfd39..cfe9734 100644
--- a/native/android/OWNERS
+++ b/native/android/OWNERS
@@ -1,14 +1,23 @@
-jreck@google.com
+jreck@google.com #{LAST_RESORT_SUGGESTION}
+# General NDK API reviewers
+per-file libandroid.map.txt = danalbert@google.com, etalvala@google.com, michaelwr@google.com
+per-file libandroid.map.txt = jreck@google.com, zyy@google.com
+
+# Networking
per-file libandroid_net.map.txt, net.c = set noparent
per-file libandroid_net.map.txt, net.c = codewiz@google.com, jchalard@google.com, junyulai@google.com
per-file libandroid_net.map.txt, net.c = lorenzo@google.com, reminv@google.com, satk@google.com
+
+# Fonts
per-file system_fonts.cpp = file:/graphics/java/android/graphics/fonts/OWNERS
+# Window manager
per-file native_window_jni.cpp = file:/services/core/java/com/android/server/wm/OWNERS
per-file native_activity.cpp = file:/services/core/java/com/android/server/wm/OWNERS
per-file surface_control.cpp = file:/services/core/java/com/android/server/wm/OWNERS
+# Graphics
per-file choreographer.cpp = file:/graphics/java/android/graphics/OWNERS
per-file hardware_buffer_jni.cpp = file:/graphics/java/android/graphics/OWNERS
per-file native_window_jni.cpp = file:/graphics/java/android/graphics/OWNERS
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index d01a30e..6eff629 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -141,7 +141,7 @@
}
struct ASurfaceControlStats {
- int64_t acquireTime;
+ std::variant<int64_t, sp<Fence>> acquireTimeOrFence;
sp<Fence> previousReleaseFence;
uint64_t frameNumber;
};
@@ -153,7 +153,7 @@
const SurfaceStats& surfaceStats) {
ASurfaceControlStats aSurfaceControlStats;
- aSurfaceControlStats.acquireTime = surfaceStats.acquireTime;
+ aSurfaceControlStats.acquireTimeOrFence = surfaceStats.acquireTimeOrFence;
aSurfaceControlStats.previousReleaseFence = surfaceStats.previousReleaseFence;
aSurfaceControlStats.frameNumber = surfaceStats.eventStats.frameNumber;
@@ -171,7 +171,15 @@
}
int64_t ASurfaceControlStats_getAcquireTime(ASurfaceControlStats* stats) {
- return stats->acquireTime;
+ if (const auto* fence = std::get_if<sp<Fence>>(&stats->acquireTimeOrFence)) {
+ // We got a fence instead of the acquire time due to latch unsignaled.
+ // Ideally the client could just get the acquire time dericly from
+ // the fence instead of calling this function which needs to block.
+ (*fence)->waitForever("ASurfaceControlStats_getAcquireTime");
+ return (*fence)->getSignalTime();
+ }
+
+ return std::get<int64_t>(stats->acquireTimeOrFence);
}
uint64_t ASurfaceControlStats_getFrameNumber(ASurfaceControlStats* stats) {
@@ -250,7 +258,7 @@
aSurfaceControlStats == aSurfaceTransactionStats->aSurfaceControlStats.end(),
"ASurfaceControl not found");
- return aSurfaceControlStats->second.acquireTime;
+ return ASurfaceControlStats_getAcquireTime(&aSurfaceControlStats->second);
}
int ASurfaceTransactionStats_getPreviousReleaseFenceFd(
@@ -294,9 +302,10 @@
auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats;
- for (const auto& [surfaceControl, latchTime, acquireTime, presentFence, previousReleaseFence, transformHint, frameEvents] : surfaceControlStats) {
+ for (const auto& [surfaceControl, latchTime, acquireTimeOrFence, presentFence,
+ previousReleaseFence, transformHint, frameEvents] : surfaceControlStats) {
ASurfaceControl* aSurfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
- aSurfaceControlStats[aSurfaceControl].acquireTime = acquireTime;
+ aSurfaceControlStats[aSurfaceControl].acquireTimeOrFence = acquireTimeOrFence;
aSurfaceControlStats[aSurfaceControl].previousReleaseFence = previousReleaseFence;
}
@@ -643,13 +652,12 @@
aSurfaceTransactionStats.transactionCompleted = false;
auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats;
- for (const auto&
- [surfaceControl, latchTime, acquireTime, presentFence,
- previousReleaseFence, transformHint,
- frameEvents] : surfaceControlStats) {
+ for (const auto& [surfaceControl, latchTime, acquireTimeOrFence, presentFence,
+ previousReleaseFence, transformHint, frameEvents] :
+ surfaceControlStats) {
ASurfaceControl* aSurfaceControl =
reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
- aSurfaceControlStats[aSurfaceControl].acquireTime = acquireTime;
+ aSurfaceControlStats[aSurfaceControl].acquireTimeOrFence = acquireTimeOrFence;
}
(*func)(callback_context, &aSurfaceTransactionStats);
diff --git a/omapi/aidl/vts/functional/config/Android.bp b/omapi/aidl/vts/functional/config/Android.bp
new file mode 100644
index 0000000..7c08257
--- /dev/null
+++ b/omapi/aidl/vts/functional/config/Android.bp
@@ -0,0 +1,26 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+xsd_config {
+ name: "omapi_uuid_map_config",
+ srcs: ["omapi_uuid_map_config.xsd"],
+ api_dir: "schema",
+ package_name: "omapi.uuid.map.config",
+}
diff --git a/omapi/aidl/vts/functional/config/omapi_uuid_map_config.xsd b/omapi/aidl/vts/functional/config/omapi_uuid_map_config.xsd
new file mode 100644
index 0000000..ffeb7a0
--- /dev/null
+++ b/omapi/aidl/vts/functional/config/omapi_uuid_map_config.xsd
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+-->
+<xs:schema version="2.0"
+ attributeFormDefault="unqualified"
+ elementFormDefault="qualified"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:element name="ref_do">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="uuid_ref_do" maxOccurs="unbounded" minOccurs="0">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="uids">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element type="xs:short" name="uid" maxOccurs="unbounded" minOccurs="0"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element type="xs:string" name="uuid"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+</xs:schema>
diff --git a/omapi/aidl/vts/functional/config/schema/current.txt b/omapi/aidl/vts/functional/config/schema/current.txt
new file mode 100644
index 0000000..c2e930b
--- /dev/null
+++ b/omapi/aidl/vts/functional/config/schema/current.txt
@@ -0,0 +1,30 @@
+// Signature format: 2.0
+package omapi.uuid.map.config {
+
+ public class RefDo {
+ ctor public RefDo();
+ method public java.util.List<omapi.uuid.map.config.RefDo.UuidRefDo> getUuid_ref_do();
+ }
+
+ public static class RefDo.UuidRefDo {
+ ctor public RefDo.UuidRefDo();
+ method public omapi.uuid.map.config.RefDo.UuidRefDo.Uids getUids();
+ method public String getUuid();
+ method public void setUids(omapi.uuid.map.config.RefDo.UuidRefDo.Uids);
+ method public void setUuid(String);
+ }
+
+ public static class RefDo.UuidRefDo.Uids {
+ ctor public RefDo.UuidRefDo.Uids();
+ method public java.util.List<java.lang.Short> getUid();
+ }
+
+ public class XmlParser {
+ ctor public XmlParser();
+ method public static omapi.uuid.map.config.RefDo read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ }
+
+}
+
diff --git a/omapi/aidl/vts/functional/config/schema/last_current.txt b/omapi/aidl/vts/functional/config/schema/last_current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/omapi/aidl/vts/functional/config/schema/last_current.txt
diff --git a/omapi/aidl/vts/functional/config/schema/last_removed.txt b/omapi/aidl/vts/functional/config/schema/last_removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/omapi/aidl/vts/functional/config/schema/last_removed.txt
diff --git a/omapi/aidl/vts/functional/config/schema/removed.txt b/omapi/aidl/vts/functional/config/schema/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/omapi/aidl/vts/functional/config/schema/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/omapi/aidl/vts/functional/omapi/Android.bp b/omapi/aidl/vts/functional/omapi/Android.bp
index c3ab8d1..c41479f 100644
--- a/omapi/aidl/vts/functional/omapi/Android.bp
+++ b/omapi/aidl/vts/functional/omapi/Android.bp
@@ -39,6 +39,11 @@
static_libs: [
"VtsHalHidlTargetTestBase",
"android.se.omapi-V1-ndk",
+ "android.hardware.audio.common.test.utility",
+ "libxml2",
+ ],
+ data: [
+ ":omapi_uuid_map_config",
],
cflags: [
"-O0",
@@ -51,4 +56,5 @@
"general-tests",
"vts",
],
+ test_config: "VtsHalOmapiSeServiceV1_TargetTest.xml",
}
diff --git a/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp b/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp
index 319cb7e..5303651 100644
--- a/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp
+++ b/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp
@@ -32,6 +32,7 @@
#include <hidl/GtestPrinter.h>
#include <hidl/ServiceManagement.h>
#include <utils/String16.h>
+#include "utility/ValidateXml.h"
using namespace std;
using namespace ::testing;
@@ -176,6 +177,25 @@
return (deviceSupportsFeature(FEATURE_SE_OMAPI_ESE.c_str()));
}
+ std::optional<std::string> getUuidMappingFile() {
+ char value[PROPERTY_VALUE_MAX] = {0};
+ int len = property_get("ro.boot.product.hardware.sku", value, "config");
+ std::string uuidMappingConfigFile = UUID_MAPPING_CONFIG_PREFIX
+ + std::string(value, len)
+ + UUID_MAPPING_CONFIG_EXT;
+ std::string uuidMapConfigPath;
+ // Search in predefined folders
+ for (auto path : UUID_MAPPING_CONFIG_PATHS) {
+ uuidMapConfigPath = path + uuidMappingConfigFile;
+ auto confFile = fopen(uuidMapConfigPath.c_str(), "r");
+ if (confFile) {
+ fclose(confFile);
+ return uuidMapConfigPath;
+ }
+ }
+ return std::optional<std::string>();
+ }
+
void SetUp() override {
LOG(INFO) << "get OMAPI service with name:" << GetParam();
::ndk::SpAIBinder ks2Binder(AServiceManager_getService(GetParam().c_str()));
@@ -300,6 +320,10 @@
std::map<std::string, std::shared_ptr<aidl::android::se::omapi::ISecureElementReader>>
mVSReaders = {};
+
+ std::string UUID_MAPPING_CONFIG_PREFIX = "hal_uuid_map_";
+ std::string UUID_MAPPING_CONFIG_EXT = ".xml";
+ std::string UUID_MAPPING_CONFIG_PATHS[3] = {"/odm/etc/", "/vendor/etc/", "/etc/"};
};
/** Tests getReaders API */
@@ -600,6 +624,14 @@
}
}
+TEST_P(OMAPISEServiceHalTest, TestUuidMappingConfig) {
+ constexpr const char* xsd = "/data/local/tmp/omapi_uuid_map_config.xsd";
+ auto uuidMappingFile = getUuidMappingFile();
+ ASSERT_TRUE(uuidMappingFile.has_value()) << "Unable to determine UUID mapping config file path";
+ LOG(INFO) << "UUID Mapping config file: " << uuidMappingFile.value();
+ EXPECT_VALID_XML(uuidMappingFile->c_str(), xsd);
+}
+
INSTANTIATE_TEST_SUITE_P(PerInstance, OMAPISEServiceHalTest,
testing::ValuesIn(::android::getAidlHalInstanceNames(
aidl::android::se::omapi::ISecureElementService::descriptor)),
diff --git a/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.xml b/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.xml
new file mode 100644
index 0000000..3ee0414
--- /dev/null
+++ b/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs VtsHalOmapiSeServiceV1_TargetTest.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-native" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push"
+ value="omapi_uuid_map_config.xsd->/data/local/tmp/omapi_uuid_map_config.xsd" />
+ <option name="push"
+ value="VtsHalOmapiSeServiceV1_TargetTest->/data/local/tmp/VtsHalOmapiSeServiceV1_TargetTest" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="VtsHalOmapiSeServiceV1_TargetTest" />
+ </test>
+</configuration>
diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
index 1f67f6d..1a955c4 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
@@ -16,14 +16,16 @@
package android.net;
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
-import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -33,13 +35,15 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
/**
- * A class representing the IP configuration of the Ethernet network.
+ * A class that manages and configures Ethernet interfaces.
*
* @hide
*/
@@ -54,11 +58,13 @@
private final IEthernetServiceListener.Stub mServiceListener =
new IEthernetServiceListener.Stub() {
@Override
- public void onAvailabilityChanged(String iface, boolean isAvailable) {
+ public void onInterfaceStateChanged(String iface, int state, int role,
+ IpConfiguration configuration) {
synchronized (mListeners) {
for (ListenerInfo li : mListeners) {
li.executor.execute(() ->
- li.listener.onAvailabilityChanged(iface, isAvailable));
+ li.listener.onInterfaceStateChanged(iface, state, role,
+ configuration));
}
}
}
@@ -68,19 +74,93 @@
@NonNull
public final Executor executor;
@NonNull
- public final Listener listener;
+ public final InterfaceStateListener listener;
- private ListenerInfo(@NonNull Executor executor, @NonNull Listener listener) {
+ private ListenerInfo(@NonNull Executor executor, @NonNull InterfaceStateListener listener) {
this.executor = executor;
this.listener = listener;
}
}
/**
- * A listener interface to receive notification on changes in Ethernet.
+ * The interface is absent.
* @hide
*/
- public interface Listener {
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int STATE_ABSENT = 0;
+
+ /**
+ * The interface is present but link is down.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int STATE_LINK_DOWN = 1;
+
+ /**
+ * The interface is present and link is up.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int STATE_LINK_UP = 2;
+
+ /** @hide */
+ @IntDef(prefix = "STATE_", value = {STATE_ABSENT, STATE_LINK_DOWN, STATE_LINK_UP})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface InterfaceState {}
+
+ /**
+ * The interface currently does not have any specific role.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int ROLE_NONE = 0;
+
+ /**
+ * The interface is in client mode (e.g., connected to the Internet).
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int ROLE_CLIENT = 1;
+
+ /**
+ * Ethernet interface is in server mode (e.g., providing Internet access to tethered devices).
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int ROLE_SERVER = 2;
+
+ /** @hide */
+ @IntDef(prefix = "ROLE_", value = {ROLE_NONE, ROLE_CLIENT, ROLE_SERVER})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Role {}
+
+ /**
+ * A listener that receives notifications about the state of Ethernet interfaces on the system.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public interface InterfaceStateListener {
+ /**
+ * Called when an Ethernet interface changes state.
+ *
+ * @param iface the name of the interface.
+ * @param state the current state of the interface, or {@link #STATE_ABSENT} if the
+ * interface was removed.
+ * @param role whether the interface is in the client mode or server mode.
+ * @param configuration the current IP configuration of the interface.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ void onInterfaceStateChanged(@NonNull String iface, @InterfaceState int state,
+ @Role int role, @Nullable IpConfiguration configuration);
+ }
+
+ /**
+ * A listener interface to receive notification on changes in Ethernet.
+ * This has never been a supported API. Use {@link InterfaceStateListener} instead.
+ * @hide
+ */
+ public interface Listener extends InterfaceStateListener {
/**
* Called when Ethernet port's availability is changed.
* @param iface Ethernet interface name
@@ -89,6 +169,13 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
void onAvailabilityChanged(String iface, boolean isAvailable);
+
+ /** Default implementation for backwards compatibility. Only calls the legacy listener. */
+ default void onInterfaceStateChanged(@NonNull String iface, @InterfaceState int state,
+ @Role int role, @Nullable IpConfiguration configuration) {
+ onAvailabilityChanged(iface, (state >= STATE_LINK_UP));
+ }
+
}
/**
@@ -121,7 +208,7 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public void setConfiguration(String iface, IpConfiguration config) {
+ public void setConfiguration(@NonNull String iface, @NonNull IpConfiguration config) {
try {
mService.setConfiguration(iface, config);
} catch (RemoteException e) {
@@ -155,9 +242,8 @@
/**
* Adds a listener.
+ * This has never been a supported API. Use {@link #addInterfaceStateListener} instead.
*
- * Consider using {@link #addListener(Listener, Executor)} instead: this method uses a default
- * executor that may have higher latency than a provided executor.
* @param listener A {@link Listener} to add.
* @throws IllegalArgumentException If the listener is null.
* @hide
@@ -169,6 +255,8 @@
/**
* Adds a listener.
+ * This has never been a supported API. Use {@link #addInterfaceStateListener} instead.
+ *
* @param listener A {@link Listener} to add.
* @param executor Executor to run callbacks on.
* @throws IllegalArgumentException If the listener or executor is null.
@@ -176,6 +264,28 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void addListener(@NonNull Listener listener, @NonNull Executor executor) {
+ addInterfaceStateListener(executor, listener);
+ }
+
+ /**
+ * Listen to changes in the state of Ethernet interfaces.
+ *
+ * Adds a listener to receive notification for any state change of all existing Ethernet
+ * interfaces.
+ * <p>{@link Listener#onInterfaceStateChanged} will be triggered immediately for all
+ * existing interfaces upon adding a listener. The same method will be called on the
+ * listener every time any of the interface changes state. In particular, if an
+ * interface is removed, it will be called with state {@link #STATE_ABSENT}.
+ * <p>Use {@link #removeInterfaceStateListener} with the same object to stop listening.
+ *
+ * @param executor Executor to run callbacks on.
+ * @param listener A {@link Listener} to add.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @SystemApi(client = MODULE_LIBRARIES)
+ public void addInterfaceStateListener(@NonNull Executor executor,
+ @NonNull InterfaceStateListener listener) {
if (listener == null || executor == null) {
throw new NullPointerException("listener and executor must not be null");
}
@@ -206,15 +316,13 @@
/**
* Removes a listener.
+ *
* @param listener A {@link Listener} to remove.
- * @throws IllegalArgumentException If the listener is null.
* @hide
*/
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public void removeListener(@NonNull Listener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("listener must not be null");
- }
+ @SystemApi(client = MODULE_LIBRARIES)
+ public void removeInterfaceStateListener(@NonNull InterfaceStateListener listener) {
+ Objects.requireNonNull(listener);
synchronized (mListeners) {
mListeners.removeIf(l -> l.listener == listener);
if (mListeners.isEmpty()) {
@@ -228,12 +336,26 @@
}
/**
+ * Removes a listener.
+ * This has never been a supported API. Use {@link #removeInterfaceStateListener} instead.
+ * @param listener A {@link Listener} to remove.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void removeListener(@NonNull Listener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+ removeInterfaceStateListener(listener);
+ }
+
+ /**
* Whether to treat interfaces created by {@link TestNetworkManager#createTapInterface}
* as Ethernet interfaces. The effects of this method apply to any test interfaces that are
* already present on the system.
* @hide
*/
- @TestApi
+ @SystemApi(client = MODULE_LIBRARIES)
public void setIncludeTestInterfaces(boolean include) {
try {
mService.setIncludeTestInterfaces(include);
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl b/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl
index 782fa19..6d2ba03 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl
@@ -16,8 +16,11 @@
package android.net;
+import android.net.IpConfiguration;
+
/** @hide */
oneway interface IEthernetServiceListener
{
- void onAvailabilityChanged(String iface, boolean isAvailable);
+ void onInterfaceStateChanged(String iface, int state, int role,
+ in IpConfiguration configuration);
}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
index 4ebaf2b..a48f94b 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
@@ -20,6 +20,7 @@
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.NetworkTemplate.NETWORK_TYPE_ALL;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -86,6 +87,7 @@
final int mType;
final int mRatType;
+ final int mSubId;
final String mSubscriberId;
final String mWifiNetworkKey;
final boolean mRoaming;
@@ -96,7 +98,7 @@
/** @hide */
public NetworkIdentity(
int type, int ratType, @Nullable String subscriberId, @Nullable String wifiNetworkKey,
- boolean roaming, boolean metered, boolean defaultNetwork, int oemManaged) {
+ boolean roaming, boolean metered, boolean defaultNetwork, int oemManaged, int subId) {
mType = type;
mRatType = ratType;
mSubscriberId = subscriberId;
@@ -105,12 +107,13 @@
mMetered = metered;
mDefaultNetwork = defaultNetwork;
mOemManaged = oemManaged;
+ mSubId = subId;
}
@Override
public int hashCode() {
return Objects.hash(mType, mRatType, mSubscriberId, mWifiNetworkKey, mRoaming, mMetered,
- mDefaultNetwork, mOemManaged);
+ mDefaultNetwork, mOemManaged, mSubId);
}
@Override
@@ -122,7 +125,8 @@
&& Objects.equals(mWifiNetworkKey, ident.mWifiNetworkKey)
&& mMetered == ident.mMetered
&& mDefaultNetwork == ident.mDefaultNetwork
- && mOemManaged == ident.mOemManaged;
+ && mOemManaged == ident.mOemManaged
+ && mSubId == ident.mSubId;
}
return false;
}
@@ -150,6 +154,7 @@
builder.append(", metered=").append(mMetered);
builder.append(", defaultNetwork=").append(mDefaultNetwork);
builder.append(", oemManaged=").append(getOemManagedNames(mOemManaged));
+ builder.append(", subId=").append(mSubId);
return builder.append("}").toString();
}
@@ -256,6 +261,11 @@
return mOemManaged;
}
+ /** Get the SubId of this instance. */
+ public int getSubId() {
+ return mSubId;
+ }
+
/**
* Assemble a {@link NetworkIdentity} from the passed arguments.
*
@@ -276,7 +286,8 @@
public static NetworkIdentity buildNetworkIdentity(Context context,
@NonNull NetworkStateSnapshot snapshot, boolean defaultNetwork, int ratType) {
final NetworkIdentity.Builder builder = new NetworkIdentity.Builder()
- .setNetworkStateSnapshot(snapshot).setDefaultNetwork(defaultNetwork);
+ .setNetworkStateSnapshot(snapshot).setDefaultNetwork(defaultNetwork)
+ .setSubId(snapshot.getSubId());
if (snapshot.getLegacyType() == TYPE_MOBILE && ratType != NETWORK_TYPE_ALL) {
builder.setRatType(ratType);
}
@@ -325,6 +336,9 @@
if (res == 0) {
res = Integer.compare(left.mOemManaged, right.mOemManaged);
}
+ if (res == 0) {
+ res = Integer.compare(left.mSubId, right.mSubId);
+ }
return res;
}
@@ -345,6 +359,7 @@
private boolean mMetered;
private boolean mDefaultNetwork;
private int mOemManaged;
+ private int mSubId;
/**
* Creates a new Builder.
@@ -359,6 +374,7 @@
mMetered = false;
mDefaultNetwork = false;
mOemManaged = NetworkTemplate.OEM_MANAGED_NO;
+ mSubId = INVALID_SUBSCRIPTION_ID;
}
/**
@@ -537,6 +553,19 @@
return this;
}
+ /**
+ * Set the Subscription Id.
+ *
+ * @param subId the Subscription Id of the network. Or INVALID_SUBSCRIPTION_ID if not
+ * applicable.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setSubId(int subId) {
+ mSubId = subId;
+ return this;
+ }
+
private void ensureValidParameters() {
// Assert non-mobile network cannot have a ratType.
if (mType != TYPE_MOBILE && mRatType != NetworkTemplate.NETWORK_TYPE_ALL) {
@@ -559,7 +588,7 @@
public NetworkIdentity build() {
ensureValidParameters();
return new NetworkIdentity(mType, mRatType, mSubscriberId, mWifiNetworkKey,
- mRoaming, mMetered, mDefaultNetwork, mOemManaged);
+ mRoaming, mMetered, mDefaultNetwork, mOemManaged, mSubId);
}
}
}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java
index 2236d70..56461ba 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java
@@ -17,6 +17,7 @@
package android.net;
import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import android.annotation.NonNull;
import android.service.NetworkIdentitySetProto;
@@ -42,6 +43,7 @@
private static final int VERSION_ADD_METERED = 4;
private static final int VERSION_ADD_DEFAULT_NETWORK = 5;
private static final int VERSION_ADD_OEM_MANAGED_NETWORK = 6;
+ private static final int VERSION_ADD_SUB_ID = 7;
/**
* Construct a {@link NetworkIdentitySet} object.
@@ -103,8 +105,15 @@
oemNetCapabilities = NetworkIdentity.OEM_NONE;
}
+ final int subId;
+ if (version >= VERSION_ADD_SUB_ID) {
+ subId = in.readInt();
+ } else {
+ subId = INVALID_SUBSCRIPTION_ID;
+ }
+
add(new NetworkIdentity(type, ratType, subscriberId, networkId, roaming, metered,
- defaultNetwork, oemNetCapabilities));
+ defaultNetwork, oemNetCapabilities, subId));
}
}
@@ -113,7 +122,7 @@
* @hide
*/
public void writeToStream(DataOutput out) throws IOException {
- out.writeInt(VERSION_ADD_OEM_MANAGED_NETWORK);
+ out.writeInt(VERSION_ADD_SUB_ID);
out.writeInt(size());
for (NetworkIdentity ident : this) {
out.writeInt(ident.getType());
@@ -124,6 +133,7 @@
out.writeBoolean(ident.isMetered());
out.writeBoolean(ident.isDefaultNetwork());
out.writeInt(ident.getOemManaged());
+ out.writeInt(ident.getSubId());
}
}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java
index d577aa8..c018e91 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java
@@ -17,6 +17,8 @@
package android.net;
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -98,12 +100,29 @@
return mLinkProperties;
}
- /** Get the Subscriber Id of the network associated with this snapshot. */
+ /**
+ * Get the Subscriber Id of the network associated with this snapshot.
+ * @deprecated Please use #getSubId, which doesn't return personally identifiable
+ * information.
+ */
+ @Deprecated
@Nullable
public String getSubscriberId() {
return mSubscriberId;
}
+ /** Get the subId of the network associated with this snapshot. */
+ public int getSubId() {
+ if (mNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+ final NetworkSpecifier spec = mNetworkCapabilities.getNetworkSpecifier();
+ if (spec instanceof TelephonyNetworkSpecifier) {
+ return ((TelephonyNetworkSpecifier) spec).getSubscriptionId();
+ }
+ }
+ return INVALID_SUBSCRIPTION_ID;
+ }
+
+
/**
* Get the legacy type of the network associated with this snapshot.
* @return the legacy network type. See {@code ConnectivityManager#TYPE_*}.
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
index 78ba218..e8b3d4c 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -1484,10 +1484,15 @@
NetworkCapabilities.NET_CAPABILITY_IMS) && !ident.isMetered()) {
// Copy the identify from IMS one but mark it as metered.
- NetworkIdentity vtIdent = new NetworkIdentity(ident.getType(),
- ident.getRatType(), ident.getSubscriberId(), ident.getWifiNetworkKey(),
- ident.isRoaming(), true /* metered */,
- true /* onDefaultNetwork */, ident.getOemManaged());
+ NetworkIdentity vtIdent = new NetworkIdentity.Builder()
+ .setType(ident.getType())
+ .setRatType(ident.getRatType())
+ .setSubscriberId(ident.getSubscriberId())
+ .setWifiNetworkKey(ident.getWifiNetworkKey())
+ .setRoaming(ident.isRoaming()).setMetered(true)
+ .setDefaultNetwork(true)
+ .setOemManaged(ident.getOemManaged())
+ .setSubId(ident.getSubId()).build();
final String ifaceVt = IFACE_VT + getSubIdForMobile(snapshot);
findOrCreateNetworkIdentitySet(mActiveIfaces, ifaceVt).add(vtIdent);
findOrCreateNetworkIdentitySet(mActiveUidIfaces, ifaceVt).add(vtIdent);
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index f7b2974..17db45d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -138,8 +138,6 @@
return true;
}
if (mDisabledByAppOps) {
- Preconditions.checkState(Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU,
- "Build SDK version needs >= T");
RestrictedLockUtilsInternal.sendShowRestrictedSettingDialogIntent(mContext, packageName,
uid);
return true;
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index 5c05a1b..342189d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -61,6 +61,7 @@
final TypedValue restrictedSwitchSummary = attributes.peekValue(
R.styleable.RestrictedSwitchPreference_restrictedSwitchSummary);
+ attributes.recycle();
if (restrictedSwitchSummary != null
&& restrictedSwitchSummary.type == TypedValue.TYPE_STRING) {
if (restrictedSwitchSummary.resourceId != 0) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index d179b82..4e06ed7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -26,8 +26,6 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -35,21 +33,14 @@
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
import android.text.TextUtils;
-import android.util.AttributeSet;
import android.util.Log;
-import android.util.Xml;
import com.android.settingslib.R;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
@@ -185,15 +176,18 @@
dreamInfo.componentName = componentName;
dreamInfo.isActive = dreamInfo.componentName.equals(activeDream);
- final DreamMetadata dreamMetadata = getDreamMetadata(pm, resolveInfo);
- dreamInfo.settingsComponentName = dreamMetadata.mSettingsActivity;
- dreamInfo.previewImage = dreamMetadata.mPreviewImage;
+ final DreamService.DreamMetadata dreamMetadata = DreamService.getDreamMetadata(mContext,
+ resolveInfo.serviceInfo);
+ if (dreamMetadata != null) {
+ dreamInfo.settingsComponentName = dreamMetadata.settingsActivity;
+ dreamInfo.previewImage = dreamMetadata.previewImage;
+ }
if (dreamInfo.previewImage == null) {
dreamInfo.previewImage = mDreamPreviewDefault;
}
dreamInfos.add(dreamInfo);
}
- Collections.sort(dreamInfos, mComparator);
+ dreamInfos.sort(mComparator);
return dreamInfos;
}
@@ -454,21 +448,26 @@
uiContext.startActivity(intent);
}
- public void preview(DreamInfo dreamInfo) {
- logd("preview(%s)", dreamInfo);
- if (mDreamManager == null || dreamInfo == null || dreamInfo.componentName == null)
+ /**
+ * Preview a dream, given the component name.
+ */
+ public void preview(ComponentName componentName) {
+ logd("preview(%s)", componentName);
+ if (mDreamManager == null || componentName == null) {
return;
+ }
try {
- mDreamManager.testDream(mContext.getUserId(), dreamInfo.componentName);
+ mDreamManager.testDream(mContext.getUserId(), componentName);
} catch (RemoteException e) {
- Log.w(TAG, "Failed to preview " + dreamInfo, e);
+ Log.w(TAG, "Failed to preview " + componentName, e);
}
}
public void startDreaming() {
logd("startDreaming()");
- if (mDreamManager == null)
+ if (mDreamManager == null) {
return;
+ }
try {
mDreamManager.dream();
} catch (RemoteException e) {
@@ -483,78 +482,6 @@
return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
}
- private static final class DreamMetadata {
- @Nullable
- Drawable mPreviewImage;
- @Nullable
- ComponentName mSettingsActivity;
- }
-
- @Nullable
- private static TypedArray readMetadata(PackageManager pm, ServiceInfo serviceInfo) {
- if (serviceInfo == null || serviceInfo.metaData == null) {
- return null;
- }
- try (XmlResourceParser parser =
- serviceInfo.loadXmlMetaData(pm, DreamService.DREAM_META_DATA)) {
- if (parser == null) {
- Log.w(TAG, "No " + DreamService.DREAM_META_DATA + " meta-data");
- return null;
- }
- Resources res = pm.getResourcesForApplication(serviceInfo.applicationInfo);
- AttributeSet attrs = Xml.asAttributeSet(parser);
- while (true) {
- final int type = parser.next();
- if (type == XmlPullParser.END_DOCUMENT || type == XmlPullParser.START_TAG) {
- break;
- }
- }
- String nodeName = parser.getName();
- if (!"dream".equals(nodeName)) {
- Log.w(TAG, "Meta-data does not start with dream tag");
- return null;
- }
- return res.obtainAttributes(attrs, com.android.internal.R.styleable.Dream);
- } catch (PackageManager.NameNotFoundException | IOException | XmlPullParserException e) {
- Log.w(TAG, "Error parsing : " + serviceInfo.packageName, e);
- return null;
- }
- }
-
- private static ComponentName convertToComponentName(String flattenedString,
- ServiceInfo serviceInfo) {
- if (flattenedString == null) return null;
-
- if (flattenedString.indexOf('/') < 0) {
- flattenedString = serviceInfo.packageName + "/" + flattenedString;
- }
-
- ComponentName cn = ComponentName.unflattenFromString(flattenedString);
-
- if (cn == null) return null;
- if (!cn.getPackageName().equals(serviceInfo.packageName)) {
- Log.w(TAG,
- "Inconsistent package name in component: " + cn.getPackageName()
- + ", should be: " + serviceInfo.packageName);
- return null;
- }
-
- return cn;
- }
-
- private static DreamMetadata getDreamMetadata(PackageManager pm, ResolveInfo resolveInfo) {
- DreamMetadata result = new DreamMetadata();
- if (resolveInfo == null) return result;
- TypedArray rawMetadata = readMetadata(pm, resolveInfo.serviceInfo);
- if (rawMetadata == null) return result;
- result.mSettingsActivity = convertToComponentName(rawMetadata.getString(
- com.android.internal.R.styleable.Dream_settingsActivity), resolveInfo.serviceInfo);
- result.mPreviewImage = rawMetadata.getDrawable(
- com.android.internal.R.styleable.Dream_previewImage);
- rawMetadata.recycle();
- return result;
- }
-
private static void logd(String msg, Object... args) {
if (DEBUG) {
Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args));
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
index 484ca93..1f4cafce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
@@ -221,8 +221,8 @@
setSwitchEnabled(false);
} else if (!mIsAllowedByOrganization) {
EnforcedAdmin admin =
- RestrictedLockUtilsInternal.checkIfInputMethodDisallowed(getContext(),
- mImi.getPackageName(), UserHandle.myUserId());
+ RestrictedLockUtilsInternal.checkIfInputMethodDisallowed(
+ getContext(), mImi.getPackageName(), mUserId);
setDisabledByAdmin(admin);
} else {
setEnabled(true);
diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java
index b4c95e6..2c2be03 100644
--- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java
+++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java
@@ -39,6 +39,8 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
import java.util.zip.GZIPInputStream;
/**
@@ -54,6 +56,7 @@
private static final String TAG_FILE_NAME = "file-name";
private static final String TAG_FILE_CONTENT = "file-content";
private static final String ATTR_CONTENT_ID = "contentId";
+ private static final String ATTR_LIBRARY_NAME = "lib";
private static final String HTML_HEAD_STRING =
"<html><head>\n"
@@ -67,8 +70,12 @@
+ "</style>\n"
+ "</head>"
+ "<body topmargin=\"0\" leftmargin=\"0\" rightmargin=\"0\" bottommargin=\"0\">\n"
- + "<div class=\"toc\">\n"
- + "<ul>";
+ + "<div class=\"toc\">\n";
+ private static final String LIBRARY_HEAD_STRING =
+ "<strong>Libraries</strong>\n<ul class=\"libraries\">";
+ private static final String LIBRARY_TAIL_STRING = "</ul>\n<strong>Files</strong>";
+
+ private static final String FILES_HEAD_STRING = "<ul class=\"files\">";
private static final String HTML_MIDDLE_STRING =
"</ul>\n"
@@ -81,12 +88,14 @@
private final List<File> mXmlFiles;
/*
- * A map from a file name to a content id (MD5 sum of file content) for its license.
- * For example, "/system/priv-app/TeleService/TeleService.apk" maps to
+ * A map from a file name to a library name (may be empty) to a content id (MD5 sum of file
+ * content) for its license.
+ * For example, "/system/priv-app/TeleService/TeleService.apk" maps to "service/Telephony" to
* "9645f39e9db895a4aa6e02cb57294595". Here "9645f39e9db895a4aa6e02cb57294595" is a MD5 sum
* of the content of packages/services/Telephony/MODULE_LICENSE_APACHE2.
*/
- private final Map<String, Set<String>> mFileNameToContentIdMap = new HashMap();
+ private final Map<String, Map<String, Set<String>>> mFileNameToLibraryToContentIdMap =
+ new HashMap();
/*
* A map from a content id (MD5 sum of file content) to a license file content.
@@ -98,7 +107,7 @@
static class ContentIdAndFileNames {
final String mContentId;
- final List<String> mFileNameList = new ArrayList();
+ final Map<String, List<String>> mLibraryToFileNameMap = new TreeMap();
ContentIdAndFileNames(String contentId) {
mContentId = contentId;
@@ -120,7 +129,7 @@
parse(xmlFile);
}
- if (mFileNameToContentIdMap.isEmpty() || mContentIdToFileContentMap.isEmpty()) {
+ if (mFileNameToLibraryToContentIdMap.isEmpty() || mContentIdToFileContentMap.isEmpty()) {
return false;
}
@@ -128,7 +137,7 @@
try {
writer = new PrintWriter(outputFile);
- generateHtml(mFileNameToContentIdMap, mContentIdToFileContentMap, writer,
+ generateHtml(mFileNameToLibraryToContentIdMap, mContentIdToFileContentMap, writer,
noticeHeader);
writer.flush();
@@ -157,7 +166,7 @@
in = new FileReader(xmlFile);
}
- parse(in, mFileNameToContentIdMap, mContentIdToFileContentMap);
+ parse(in, mFileNameToLibraryToContentIdMap, mContentIdToFileContentMap);
in.close();
} catch (XmlPullParserException | IOException e) {
@@ -180,7 +189,8 @@
*
* <licenses>
* <file-name contentId="content_id_of_license1">file1</file-name>
- * <file-name contentId="content_id_of_license2">file2</file-name>
+ * <file-name contentId="content_id_of_license2" lib="name of library">file2</file-name>
+ * <file-name contentId="content_id_of_license2" lib="another library">file2</file-name>
* ...
* <file-content contentId="content_id_of_license1">license1 file contents</file-content>
* <file-content contentId="content_id_of_license2">license2 file contents</file-content>
@@ -188,10 +198,12 @@
* </licenses>
*/
@VisibleForTesting
- static void parse(InputStreamReader in, Map<String, Set<String>> outFileNameToContentIdMap,
+ static void parse(InputStreamReader in,
+ Map<String, Map<String, Set<String>>> outFileNameToLibraryToContentIdMap,
Map<String, String> outContentIdToFileContentMap)
throws XmlPullParserException, IOException {
- Map<String, Set<String>> fileNameToContentIdMap = new HashMap<String, Set<String>>();
+ Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap =
+ new HashMap<String, Map<String, Set<String>>>();
Map<String, String> contentIdToFileContentMap = new HashMap<String, String>();
XmlPullParser parser = Xml.newPullParser();
@@ -205,12 +217,15 @@
if (state == XmlPullParser.START_TAG) {
if (TAG_FILE_NAME.equals(parser.getName())) {
String contentId = parser.getAttributeValue("", ATTR_CONTENT_ID);
+ String libraryName = parser.getAttributeValue("", ATTR_LIBRARY_NAME);
if (!TextUtils.isEmpty(contentId)) {
String fileName = readText(parser).trim();
if (!TextUtils.isEmpty(fileName)) {
- Set<String> contentIds =
- fileNameToContentIdMap.computeIfAbsent(
- fileName, k -> new HashSet<>());
+ Map<String, Set<String>> libs =
+ fileNameToLibraryToContentIdMap.computeIfAbsent(
+ fileName, k -> new HashMap<>());
+ Set<String> contentIds = libs.computeIfAbsent(
+ libraryName, k -> new HashSet<>());
contentIds.add(contentId);
}
}
@@ -229,11 +244,17 @@
state = parser.next();
}
- for (Map.Entry<String, Set<String>> entry : fileNameToContentIdMap.entrySet()) {
- outFileNameToContentIdMap.merge(
- entry.getKey(), entry.getValue(), (s1, s2) -> {
- s1.addAll(s2);
- return s1;
+ for (Map.Entry<String, Map<String, Set<String>>> mapEntry :
+ fileNameToLibraryToContentIdMap.entrySet()) {
+ outFileNameToLibraryToContentIdMap.merge(
+ mapEntry.getKey(), mapEntry.getValue(), (m1, m2) -> {
+ for (Map.Entry<String, Set<String>> entry : m2.entrySet()) {
+ m1.merge(entry.getKey(), entry.getValue(), (s1, s2) -> {
+ s1.addAll(s2);
+ return s1;
+ });
+ }
+ return m1;
});
}
outContentIdToFileContentMap.putAll(contentIdToFileContentMap);
@@ -251,13 +272,28 @@
}
@VisibleForTesting
- static void generateHtml(Map<String, Set<String>> fileNameToContentIdMap,
+ static void generateHtml(Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap,
Map<String, String> contentIdToFileContentMap, PrintWriter writer,
String noticeHeader) {
List<String> fileNameList = new ArrayList();
- fileNameList.addAll(fileNameToContentIdMap.keySet());
+ fileNameList.addAll(fileNameToLibraryToContentIdMap.keySet());
Collections.sort(fileNameList);
+ SortedMap<String, Set<String>> libraryToContentIdMap = new TreeMap();
+ for (Map<String, Set<String>> libraryToContentValue :
+ fileNameToLibraryToContentIdMap.values()) {
+ for (Map.Entry<String, Set<String>> entry : libraryToContentValue.entrySet()) {
+ if (TextUtils.isEmpty(entry.getKey())) {
+ continue;
+ }
+ libraryToContentIdMap.merge(
+ entry.getKey(), entry.getValue(), (s1, s2) -> {
+ s1.addAll(s2);
+ return s1;
+ });
+ }
+ }
+
writer.println(HTML_HEAD_STRING);
if (!TextUtils.isEmpty(noticeHeader)) {
@@ -268,21 +304,56 @@
Map<String, Integer> contentIdToOrderMap = new HashMap();
List<ContentIdAndFileNames> contentIdAndFileNamesList = new ArrayList();
+ if (!libraryToContentIdMap.isEmpty()) {
+ writer.println(LIBRARY_HEAD_STRING);
+ for (Map.Entry<String, Set<String>> entry: libraryToContentIdMap.entrySet()) {
+ String libraryName = entry.getKey();
+ for (String contentId : entry.getValue()) {
+ // Assigns an id to a newly referred license file content.
+ if (!contentIdToOrderMap.containsKey(contentId)) {
+ contentIdToOrderMap.put(contentId, count);
+
+ // An index in contentIdAndFileNamesList is the order of each element.
+ contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId));
+ count++;
+ }
+ int id = contentIdToOrderMap.get(contentId);
+ writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, libraryName);
+ }
+ }
+ writer.println(LIBRARY_TAIL_STRING);
+ }
+
// Prints all the file list with a link to its license file content.
for (String fileName : fileNameList) {
- for (String contentId : fileNameToContentIdMap.get(fileName)) {
- // Assigns an id to a newly referred license file content.
- if (!contentIdToOrderMap.containsKey(contentId)) {
- contentIdToOrderMap.put(contentId, count);
-
- // An index in contentIdAndFileNamesList is the order of each element.
- contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId));
- count++;
+ for (Map.Entry<String, Set<String>> libToContentId :
+ fileNameToLibraryToContentIdMap.get(fileName).entrySet()) {
+ String libraryName = libToContentId.getKey();
+ if (libraryName == null) {
+ libraryName = "";
}
+ for (String contentId : libToContentId.getValue()) {
+ // Assigns an id to a newly referred license file content.
+ if (!contentIdToOrderMap.containsKey(contentId)) {
+ contentIdToOrderMap.put(contentId, count);
- int id = contentIdToOrderMap.get(contentId);
- contentIdAndFileNamesList.get(id).mFileNameList.add(fileName);
- writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, fileName);
+ // An index in contentIdAndFileNamesList is the order of each element.
+ contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId));
+ count++;
+ }
+
+ int id = contentIdToOrderMap.get(contentId);
+ ContentIdAndFileNames elem = contentIdAndFileNamesList.get(id);
+ List<String> files = elem.mLibraryToFileNameMap.computeIfAbsent(
+ libraryName, k -> new ArrayList());
+ files.add(fileName);
+ if (TextUtils.isEmpty(libraryName)) {
+ writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, fileName);
+ } else {
+ writer.format("<li><a href=\"#id%d\">%s - %s</a></li>\n",
+ id, fileName, libraryName);
+ }
+ }
}
}
@@ -292,19 +363,27 @@
// Prints all contents of the license files in order of id.
for (ContentIdAndFileNames contentIdAndFileNames : contentIdAndFileNamesList) {
writer.format("<tr id=\"id%d\"><td class=\"same-license\">\n", count);
- writer.println("<div class=\"label\">Notices for file(s):</div>");
- writer.println("<div class=\"file-list\">");
- for (String fileName : contentIdAndFileNames.mFileNameList) {
- writer.format("%s <br/>\n", fileName);
+ for (Map.Entry<String, List<String>> libraryFiles :
+ contentIdAndFileNames.mLibraryToFileNameMap.entrySet()) {
+ String libraryName = libraryFiles.getKey();
+ if (TextUtils.isEmpty(libraryName)) {
+ writer.println("<div class=\"label\">Notices for file(s):</div>");
+ } else {
+ writer.format("<div class=\"label\"><strong>%s</strong> used by:</div>\n",
+ libraryName);
+ }
+ writer.println("<div class=\"file-list\">");
+ for (String fileName : libraryFiles.getValue()) {
+ writer.format("%s <br/>\n", fileName);
+ }
+ writer.println("</div><!-- file-list -->");
+ count++;
}
- writer.println("</div><!-- file-list -->");
writer.println("<pre class=\"license-text\">");
writer.println(contentIdToFileContentMap.get(
contentIdAndFileNames.mContentId));
writer.println("</pre><!-- license-text -->");
writer.println("</td></tr><!-- same-license -->");
-
- count++;
}
writer.println(HTML_REAR_STRING);
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/InjectedSetting.java b/packages/SettingsLib/src/com/android/settingslib/location/InjectedSetting.java
index 1805f1a..42e3af0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/InjectedSetting.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/InjectedSetting.java
@@ -46,12 +46,12 @@
public final String className;
/**
- * The {@link android.support.v7.preference.Preference#getTitle()} value.
+ * The {@link androidx.preference.Preference#getTitle()} value.
*/
public final String title;
/**
- * The {@link android.support.v7.preference.Preference#getIcon()} value.
+ * The {@link androidx.preference.Preference#getIcon()} value.
*/
public final int iconId;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
index 360361b..dd7db21 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -19,6 +19,7 @@
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
@@ -34,11 +35,13 @@
private static final String TAG = "BluetoothMediaDevice";
private CachedBluetoothDevice mCachedDevice;
+ private final AudioManager mAudioManager;
BluetoothMediaDevice(Context context, CachedBluetoothDevice device,
MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) {
super(context, routerManager, info, packageName);
mCachedDevice = device;
+ mAudioManager = context.getSystemService(AudioManager.class);
initDeviceRecord();
}
@@ -98,6 +101,12 @@
}
@Override
+ public boolean isMutingExpectedDevice() {
+ return mAudioManager.getMutingExpectedDevice() != null && mCachedDevice.getAddress().equals(
+ mAudioManager.getMutingExpectedDevice().getAddress());
+ }
+
+ @Override
public boolean isConnected() {
return mCachedDevice.getBondState() == BluetoothDevice.BOND_BONDED
&& mCachedDevice.isConnected();
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
new file mode 100644
index 0000000..6c0eab3
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
@@ -0,0 +1,126 @@
+/*
+ * 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.media;
+
+import android.annotation.DrawableRes;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.media.AudioDeviceInfo;
+import android.media.MediaRoute2Info;
+
+import com.android.settingslib.R;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** A util class to get the appropriate icon for different device types. */
+public class DeviceIconUtil {
+ // A map from a @AudioDeviceInfo.AudioDeviceType to full device information.
+ private final Map<Integer, Device> mAudioDeviceTypeToIconMap = new HashMap<>();
+ // A map from a @MediaRoute2Info.Type to full device information.
+ private final Map<Integer, Device> mMediaRouteTypeToIconMap = new HashMap<>();
+ // A default icon to use if the type is not present in the map.
+ @DrawableRes private static final int DEFAULT_ICON = R.drawable.ic_smartphone;
+
+ public DeviceIconUtil() {
+ List<Device> deviceList = Arrays.asList(
+ new Device(
+ AudioDeviceInfo.TYPE_USB_DEVICE,
+ MediaRoute2Info.TYPE_USB_DEVICE,
+ R.drawable.ic_headphone),
+ new Device(
+ AudioDeviceInfo.TYPE_USB_HEADSET,
+ MediaRoute2Info.TYPE_USB_HEADSET,
+ R.drawable.ic_headphone),
+ new Device(
+ AudioDeviceInfo.TYPE_USB_ACCESSORY,
+ MediaRoute2Info.TYPE_USB_ACCESSORY,
+ R.drawable.ic_headphone),
+ new Device(
+ AudioDeviceInfo.TYPE_DOCK,
+ MediaRoute2Info.TYPE_DOCK,
+ R.drawable.ic_headphone),
+ new Device(
+ AudioDeviceInfo.TYPE_HDMI,
+ MediaRoute2Info.TYPE_HDMI,
+ R.drawable.ic_headphone),
+ new Device(
+ AudioDeviceInfo.TYPE_WIRED_HEADSET,
+ MediaRoute2Info.TYPE_WIRED_HEADSET,
+ R.drawable.ic_headphone),
+ new Device(
+ AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
+ MediaRoute2Info.TYPE_WIRED_HEADPHONES,
+ R.drawable.ic_headphone),
+ new Device(
+ AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+ MediaRoute2Info.TYPE_BUILTIN_SPEAKER,
+ R.drawable.ic_smartphone));
+ for (int i = 0; i < deviceList.size(); i++) {
+ Device device = deviceList.get(i);
+ mAudioDeviceTypeToIconMap.put(device.mAudioDeviceType, device);
+ mMediaRouteTypeToIconMap.put(device.mMediaRouteType, device);
+ }
+ }
+
+ /** Returns a drawable for an icon representing the given audioDeviceType. */
+ public Drawable getIconFromAudioDeviceType(
+ @AudioDeviceInfo.AudioDeviceType int audioDeviceType, Context context) {
+ return context.getDrawable(getIconResIdFromAudioDeviceType(audioDeviceType));
+ }
+
+ /** Returns a drawable res ID for an icon representing the given audioDeviceType. */
+ @DrawableRes
+ public int getIconResIdFromAudioDeviceType(
+ @AudioDeviceInfo.AudioDeviceType int audioDeviceType) {
+ if (mAudioDeviceTypeToIconMap.containsKey(audioDeviceType)) {
+ return mAudioDeviceTypeToIconMap.get(audioDeviceType).mIconDrawableRes;
+ }
+ return DEFAULT_ICON;
+ }
+
+ /** Returns a drawable res ID for an icon representing the given mediaRouteType. */
+ @DrawableRes
+ public int getIconResIdFromMediaRouteType(
+ @MediaRoute2Info.Type int mediaRouteType) {
+ if (mMediaRouteTypeToIconMap.containsKey(mediaRouteType)) {
+ return mMediaRouteTypeToIconMap.get(mediaRouteType).mIconDrawableRes;
+ }
+ return DEFAULT_ICON;
+ }
+
+ private static class Device {
+ @AudioDeviceInfo.AudioDeviceType
+ private final int mAudioDeviceType;
+
+ @MediaRoute2Info.Type
+ private final int mMediaRouteType;
+
+ @DrawableRes
+ private final int mIconDrawableRes;
+
+ Device(@AudioDeviceInfo.AudioDeviceType int audioDeviceType,
+ @MediaRoute2Info.Type int mediaRouteType,
+ @DrawableRes int iconDrawableRes) {
+ mAudioDeviceType = audioDeviceType;
+ mMediaRouteType = mediaRouteType;
+ mIconDrawableRes = iconDrawableRes;
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index b5facf3..31d5921 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -21,6 +21,7 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.media.RoutingSessionInfo;
import android.os.Build;
import android.text.TextUtils;
@@ -227,6 +228,18 @@
}
/**
+ * Dispatch a change in the about-to-connect device. See
+ * {@link DeviceCallback#onAboutToConnectDeviceChanged} for more information.
+ */
+ public void dispatchAboutToConnectDeviceChanged(
+ @Nullable String deviceName,
+ @Nullable Drawable deviceIcon) {
+ for (DeviceCallback callback : getCallbacks()) {
+ callback.onAboutToConnectDeviceChanged(deviceName, deviceIcon);
+ }
+ }
+
+ /**
* Stop scan MediaDevice
*/
public void stopScan() {
@@ -674,6 +687,21 @@
* {@link android.media.MediaRoute2ProviderService#REASON_INVALID_COMMAND},
*/
default void onRequestFailed(int reason){};
+
+ /**
+ * Callback for notifying that we have a new about-to-connect device.
+ *
+ * An about-to-connect device is a device that is not yet connected but is expected to
+ * connect imminently and should be displayed as the current device in the media player.
+ * See [AudioManager.muteAwaitConnection] for more details.
+ *
+ * @param deviceName the name of the device (displayed to the user).
+ * @param deviceIcon the icon that should be used with the device.
+ */
+ default void onAboutToConnectDeviceChanged(
+ @Nullable String deviceName,
+ @Nullable Drawable deviceIcon
+ ) {}
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 970abff..c759962 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -320,6 +320,13 @@
}
if (mType == another.mType) {
+ // Check device is muting expected device
+ if (isMutingExpectedDevice()) {
+ return -1;
+ } else if (another.isMutingExpectedDevice()) {
+ return 1;
+ }
+
// Check fast pair device
if (isFastPairDevice()) {
return -1;
@@ -392,6 +399,14 @@
return false;
}
+ /**
+ * Check if it is muting expected device
+ * @return {@code true} if it is muting expected device, otherwise return {@code false}
+ */
+ protected boolean isMutingExpectedDevice() {
+ return false;
+ }
+
@Override
public boolean equals(Object obj) {
if (!(obj instanceof MediaDevice)) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index c16ecb5..921c245 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -47,10 +47,12 @@
private String mSummary = "";
+ private final DeviceIconUtil mDeviceIconUtil;
+
PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
String packageName) {
super(context, routerManager, info, packageName);
-
+ mDeviceIconUtil = new DeviceIconUtil();
initDeviceRecord();
}
@@ -94,23 +96,7 @@
@VisibleForTesting
int getDrawableResId() {
- int resId;
- switch (mRouteInfo.getType()) {
- case TYPE_USB_DEVICE:
- case TYPE_USB_HEADSET:
- case TYPE_USB_ACCESSORY:
- case TYPE_DOCK:
- case TYPE_HDMI:
- case TYPE_WIRED_HEADSET:
- case TYPE_WIRED_HEADPHONES:
- resId = R.drawable.ic_headphone;
- break;
- case TYPE_BUILTIN_SPEAKER:
- default:
- resId = R.drawable.ic_smartphone;
- break;
- }
- return resId;
+ return mDeviceIconUtil.getIconResIdFromMediaRouteType(mRouteInfo.getType());
}
@Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
index e87461f..e348865 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
@@ -36,7 +36,7 @@
@RunWith(RobolectricTestRunner.class)
public class LicenseHtmlGeneratorFromXmlTest {
- private static final String VALILD_XML_STRING =
+ private static final String VALID_OLD_XML_STRING =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<licenses>\n"
+ "<file-name contentId=\"0\">/file0</file-name>\n"
@@ -44,7 +44,15 @@
+ "<file-content contentId=\"0\"><![CDATA[license content #0]]></file-content>\n"
+ "</licenses>";
- private static final String INVALILD_XML_STRING =
+ private static final String VALID_NEW_XML_STRING =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<licenses>\n"
+ + "<file-name contentId=\"0\" lib=\"libA\">/file0</file-name>\n"
+ + "<file-name contentId=\"0\" lib=\"libB\">/file1</file-name>\n"
+ + "<file-content contentId=\"0\"><![CDATA[license content #0]]></file-content>\n"
+ + "</licenses>";
+
+ private static final String INVALID_XML_STRING =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<licenses2>\n"
+ "<file-name contentId=\"0\">/file0</file-name>\n"
@@ -64,13 +72,13 @@
+ "</style>\n"
+ "</head>"
+ "<body topmargin=\"0\" leftmargin=\"0\" rightmargin=\"0\" bottommargin=\"0\">\n"
- + "<div class=\"toc\">\n"
- + "<ul>\n";
+ + "<div class=\"toc\">\n";
private static final String HTML_CUSTOM_HEADING = "Custom heading";
- private static final String HTML_BODY_STRING =
- "<li><a href=\"#id0\">/file0</a></li>\n"
+ private static final String HTML_OLD_BODY_STRING =
+ "<ul class=\"files\">\n"
+ + "<li><a href=\"#id0\">/file0</a></li>\n"
+ "<li><a href=\"#id1\">/file0</a></li>\n"
+ "<li><a href=\"#id0\">/file1</a></li>\n"
+ "</ul>\n"
@@ -97,66 +105,181 @@
+ "</td></tr><!-- same-license -->\n"
+ "</table></body></html>\n";
- private static final String EXPECTED_HTML_STRING = HTML_HEAD_STRING + HTML_BODY_STRING;
+ private static final String HTML_NEW_BODY_STRING =
+ "<strong>Libraries</strong>\n"
+ + "<ul class=\"libraries\">\n"
+ + "<li><a href=\"#id0\">libA</a></li>\n"
+ + "<li><a href=\"#id1\">libB</a></li>\n"
+ + "</ul>\n"
+ + "<strong>Files</strong>\n"
+ + "<ul class=\"files\">\n"
+ + "<li><a href=\"#id0\">/file0 - libA</a></li>\n"
+ + "<li><a href=\"#id1\">/file0 - libB</a></li>\n"
+ + "<li><a href=\"#id0\">/file1 - libA</a></li>\n"
+ + "</ul>\n"
+ + "</div><!-- table of contents -->\n"
+ + "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n"
+ + "<tr id=\"id0\"><td class=\"same-license\">\n"
+ + "<div class=\"label\">Notices for file(s):</div>\n"
+ + "<div class=\"file-list\">\n"
+ + "/file0 <br/>\n"
+ + "/file1 <br/>\n"
+ + "</div><!-- file-list -->\n"
+ + "<pre class=\"license-text\">\n"
+ + "license content #0\n"
+ + "</pre><!-- license-text -->\n"
+ + "</td></tr><!-- same-license -->\n"
+ + "<tr id=\"id1\"><td class=\"same-license\">\n"
+ + "<div class=\"label\">Notices for file(s):</div>\n"
+ + "<div class=\"file-list\">\n"
+ + "/file0 <br/>\n"
+ + "</div><!-- file-list -->\n"
+ + "<pre class=\"license-text\">\n"
+ + "license content #1\n"
+ + "</pre><!-- license-text -->\n"
+ + "</td></tr><!-- same-license -->\n"
+ + "</table></body></html>\n";
- private static final String EXPECTED_HTML_STRING_WITH_CUSTOM_HEADING =
- HTML_HEAD_STRING + HTML_CUSTOM_HEADING + "\n" + HTML_BODY_STRING;
+ private static final String EXPECTED_OLD_HTML_STRING = HTML_HEAD_STRING + HTML_OLD_BODY_STRING;
+
+ private static final String EXPECTED_NEW_HTML_STRING = HTML_HEAD_STRING + HTML_NEW_BODY_STRING;
+
+ private static final String EXPECTED_OLD_HTML_STRING_WITH_CUSTOM_HEADING =
+ HTML_HEAD_STRING + HTML_CUSTOM_HEADING + "\n" + HTML_OLD_BODY_STRING;
+
+ private static final String EXPECTED_NEW_HTML_STRING_WITH_CUSTOM_HEADING =
+ HTML_HEAD_STRING + HTML_CUSTOM_HEADING + "\n" + HTML_NEW_BODY_STRING;
@Test
public void testParseValidXmlStream() throws XmlPullParserException, IOException {
- Map<String, Set<String>> fileNameToContentIdMap = new HashMap<>();
+ Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
Map<String, String> contentIdToFileContentMap = new HashMap<>();
LicenseHtmlGeneratorFromXml.parse(
- new InputStreamReader(new ByteArrayInputStream(VALILD_XML_STRING.getBytes())),
- fileNameToContentIdMap, contentIdToFileContentMap);
- assertThat(fileNameToContentIdMap.size()).isEqualTo(2);
- assertThat(fileNameToContentIdMap.get("/file0")).containsExactly("0");
- assertThat(fileNameToContentIdMap.get("/file1")).containsExactly("0");
+ new InputStreamReader(new ByteArrayInputStream(VALID_OLD_XML_STRING.getBytes())),
+ fileNameToLibraryToContentIdMap, contentIdToFileContentMap);
+ assertThat(fileNameToLibraryToContentIdMap.size()).isEqualTo(1);
+ assertThat(fileNameToLibraryToContentIdMap.get("").size()).isEqualTo(2);
+ assertThat(fileNameToLibraryToContentIdMap.get("").get("/file0")).containsExactly("0");
+ assertThat(fileNameToLibraryToContentIdMap.get("").get("/file1")).containsExactly("0");
+ assertThat(contentIdToFileContentMap.size()).isEqualTo(1);
+ assertThat(contentIdToFileContentMap.get("0")).isEqualTo("license content #0");
+ }
+
+ @Test
+ public void testParseNewValidXmlStream() throws XmlPullParserException, IOException {
+ Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
+ Map<String, String> contentIdToFileContentMap = new HashMap<>();
+
+ LicenseHtmlGeneratorFromXml.parse(
+ new InputStreamReader(new ByteArrayInputStream(VALID_NEW_XML_STRING.getBytes())),
+ fileNameToLibraryToContentIdMap, contentIdToFileContentMap);
+ assertThat(fileNameToLibraryToContentIdMap.size()).isEqualTo(2);
+ assertThat(fileNameToLibraryToContentIdMap.get("libA").size()).isEqualTo(1);
+ assertThat(fileNameToLibraryToContentIdMap.get("libB").size()).isEqualTo(1);
+ assertThat(fileNameToLibraryToContentIdMap.get("libA").get("/file0")).containsExactly("0");
+ assertThat(fileNameToLibraryToContentIdMap.get("libB").get("/file1")).containsExactly("0");
assertThat(contentIdToFileContentMap.size()).isEqualTo(1);
assertThat(contentIdToFileContentMap.get("0")).isEqualTo("license content #0");
}
@Test(expected = XmlPullParserException.class)
public void testParseInvalidXmlStream() throws XmlPullParserException, IOException {
- Map<String, Set<String>> fileNameToContentIdMap = new HashMap<>();
+ Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
Map<String, String> contentIdToFileContentMap = new HashMap<>();
LicenseHtmlGeneratorFromXml.parse(
- new InputStreamReader(new ByteArrayInputStream(INVALILD_XML_STRING.getBytes())),
- fileNameToContentIdMap, contentIdToFileContentMap);
+ new InputStreamReader(new ByteArrayInputStream(INVALID_XML_STRING.getBytes())),
+ fileNameToLibraryToContentIdMap, contentIdToFileContentMap);
}
@Test
public void testGenerateHtml() {
- Map<String, Set<String>> fileNameToContentIdMap = new HashMap<>();
+ Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
Map<String, String> contentIdToFileContentMap = new HashMap<>();
+ Map<String, Set<String>> toBoth = new HashMap<>();
+ Map<String, Set<String>> toOne = new HashMap<>();
- fileNameToContentIdMap.put("/file0", new HashSet<String>(Arrays.asList("0", "1")));
- fileNameToContentIdMap.put("/file1", new HashSet<String>(Arrays.asList("0")));
+ toBoth.put("", new HashSet<String>(Arrays.asList("0", "1")));
+ toOne.put("", new HashSet<String>(Arrays.asList("0")));
+
+ fileNameToLibraryToContentIdMap.put("/file0", toBoth);
+ fileNameToLibraryToContentIdMap.put("/file1", toOne);
contentIdToFileContentMap.put("0", "license content #0");
contentIdToFileContentMap.put("1", "license content #1");
StringWriter output = new StringWriter();
LicenseHtmlGeneratorFromXml.generateHtml(
- fileNameToContentIdMap, contentIdToFileContentMap, new PrintWriter(output), "");
- assertThat(output.toString()).isEqualTo(EXPECTED_HTML_STRING);
+ fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
+ new PrintWriter(output), "");
+ assertThat(output.toString()).isEqualTo(EXPECTED_OLD_HTML_STRING);
+ }
+
+ @Test
+ public void testGenerateNewHtml() {
+ Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
+ Map<String, String> contentIdToFileContentMap = new HashMap<>();
+ Map<String, Set<String>> toBoth = new HashMap<>();
+ Map<String, Set<String>> toOne = new HashMap<>();
+
+ toBoth.put("libA", new HashSet<String>(Arrays.asList("0")));
+ toBoth.put("libB", new HashSet<String>(Arrays.asList("1")));
+ toOne.put("libA", new HashSet<String>(Arrays.asList("0")));
+
+ fileNameToLibraryToContentIdMap.put("/file0", toBoth);
+ fileNameToLibraryToContentIdMap.put("/file1", toOne);
+ contentIdToFileContentMap.put("0", "license content #0");
+ contentIdToFileContentMap.put("1", "license content #1");
+
+ StringWriter output = new StringWriter();
+ LicenseHtmlGeneratorFromXml.generateHtml(
+ fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
+ new PrintWriter(output), "");
+ assertThat(output.toString()).isEqualTo(EXPECTED_NEW_HTML_STRING);
}
@Test
public void testGenerateHtmlWithCustomHeading() {
- Map<String, Set<String>> fileNameToContentIdMap = new HashMap<>();
+ Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
Map<String, String> contentIdToFileContentMap = new HashMap<>();
+ Map<String, Set<String>> toBoth = new HashMap<>();
+ Map<String, Set<String>> toOne = new HashMap<>();
- fileNameToContentIdMap.put("/file0", new HashSet<String>(Arrays.asList("0", "1")));
- fileNameToContentIdMap.put("/file1", new HashSet<String>(Arrays.asList("0")));
+ toBoth.put("", new HashSet<String>(Arrays.asList("0", "1")));
+ toOne.put("", new HashSet<String>(Arrays.asList("0")));
+
+ fileNameToLibraryToContentIdMap.put("/file0", toBoth);
+ fileNameToLibraryToContentIdMap.put("/file1", toOne);
contentIdToFileContentMap.put("0", "license content #0");
contentIdToFileContentMap.put("1", "license content #1");
StringWriter output = new StringWriter();
LicenseHtmlGeneratorFromXml.generateHtml(
- fileNameToContentIdMap, contentIdToFileContentMap, new PrintWriter(output),
- HTML_CUSTOM_HEADING);
- assertThat(output.toString()).isEqualTo(EXPECTED_HTML_STRING_WITH_CUSTOM_HEADING);
+ fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
+ new PrintWriter(output), HTML_CUSTOM_HEADING);
+ assertThat(output.toString()).isEqualTo(EXPECTED_OLD_HTML_STRING_WITH_CUSTOM_HEADING);
+ }
+
+ @Test
+ public void testGenerateNewHtmlWithCustomHeading() {
+ Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
+ Map<String, String> contentIdToFileContentMap = new HashMap<>();
+ Map<String, Set<String>> toBoth = new HashMap<>();
+ Map<String, Set<String>> toOne = new HashMap<>();
+
+ toBoth.put("libA", new HashSet<String>(Arrays.asList("0")));
+ toBoth.put("libB", new HashSet<String>(Arrays.asList("1")));
+ toOne.put("libA", new HashSet<String>(Arrays.asList("0")));
+
+ fileNameToLibraryToContentIdMap.put("/file0", toBoth);
+ fileNameToLibraryToContentIdMap.put("/file1", toOne);
+ contentIdToFileContentMap.put("0", "license content #0");
+ contentIdToFileContentMap.put("1", "license content #1");
+
+ StringWriter output = new StringWriter();
+ LicenseHtmlGeneratorFromXml.generateHtml(
+ fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
+ new PrintWriter(output), HTML_CUSTOM_HEADING);
+ assertThat(output.toString()).isEqualTo(EXPECTED_NEW_HTML_STRING_WITH_CUSTOM_HEADING);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java
new file mode 100644
index 0000000..72dfc17
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 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.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.media.AudioDeviceInfo;
+import android.media.MediaRoute2Info;
+
+import com.android.settingslib.R;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class DeviceIconUtilTest {
+ private final DeviceIconUtil mDeviceIconUtil = new DeviceIconUtil();
+
+ @Test
+ public void getIconResIdFromMediaRouteType_usbDevice_isHeadphone() {
+ assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_DEVICE))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromMediaRouteType_usbHeadset_isHeadphone() {
+ assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_HEADSET))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromMediaRouteType_usbAccessory_isHeadphone() {
+ assertThat(
+ mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_ACCESSORY))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromMediaRouteType_dock_isHeadphone() {
+ assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_DOCK))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromMediaRouteType_hdmi_isHeadphone() {
+ assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_HDMI))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromMediaRouteType_wiredHeadset_isHeadphone() {
+ assertThat(
+ mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_WIRED_HEADSET))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromMediaRouteType_wiredHeadphones_isHeadphone() {
+ assertThat(
+ mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_WIRED_HEADPHONES))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromMediaRouteType_builtinSpeaker_isSmartphone() {
+ assertThat(
+ mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER))
+ .isEqualTo(R.drawable.ic_smartphone);
+ }
+
+ @Test
+ public void getIconResIdFromMediaRouteType_unsupportedType_isSmartphone() {
+ assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_UNKNOWN))
+ .isEqualTo(R.drawable.ic_smartphone);
+ }
+
+ @Test
+ public void getIconResIdFromAudioDeviceType_usbDevice_isHeadphone() {
+ assertThat(mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_DEVICE))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromAudioDeviceType_usbHeadset_isHeadphone() {
+ assertThat(
+ mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_HEADSET))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromAudioDeviceType_usbAccessory_isHeadphone() {
+ assertThat(
+ mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_ACCESSORY))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromAudioDeviceType_dock_isHeadphone() {
+ assertThat(mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_DOCK))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromAudioDeviceType_hdmi_isHeadphone() {
+ assertThat(mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_HDMI))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromAudioDeviceType_wiredHeadset_isHeadphone() {
+ assertThat(
+ mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_WIRED_HEADSET))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromAudioDeviceType_wiredHeadphones_isHeadphone() {
+ assertThat(
+ mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_WIRED_HEADPHONES))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromAudioDeviceType_builtinSpeaker_isSmartphone() {
+ assertThat(
+ mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER))
+ .isEqualTo(R.drawable.ic_smartphone);
+ }
+
+ @Test
+ public void getIconResIdFromAudioDeviceType_unsupportedType_isSmartphone() {
+ assertThat(mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_UNKNOWN))
+ .isEqualTo(R.drawable.ic_smartphone);
+ }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 86ee3b3..0c6d40aa 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -652,29 +652,31 @@
return Collections.emptySet();
}
- Cursor cursor = getContentResolver().query(settingsUri, new String[] {
- Settings.NameValueTable.NAME, Settings.NameValueTable.IS_PRESERVED_IN_RESTORE },
- /* selection */ null, /* selectionArgs */ null, /* sortOrder */ null);
+ try (Cursor cursor = getContentResolver().query(settingsUri, new String[]{
+ Settings.NameValueTable.NAME,
+ Settings.NameValueTable.IS_PRESERVED_IN_RESTORE},
+ /* selection */ null, /* selectionArgs */ null, /* sortOrder */ null)) {
- if (!cursor.moveToFirst()) {
- Slog.i(TAG, "No settings to be preserved in restore");
- return Collections.emptySet();
- }
-
- int nameIndex = cursor.getColumnIndex(Settings.NameValueTable.NAME);
- int isPreservedIndex = cursor.getColumnIndex(
- Settings.NameValueTable.IS_PRESERVED_IN_RESTORE);
-
- Set<String> preservedSettings = new HashSet<>();
- while (!cursor.isAfterLast()) {
- if (Boolean.parseBoolean(cursor.getString(isPreservedIndex))) {
- preservedSettings.add(getQualifiedKeyForSetting(cursor.getString(nameIndex),
- settingsUri));
+ if (!cursor.moveToFirst()) {
+ Slog.i(TAG, "No settings to be preserved in restore");
+ return Collections.emptySet();
}
- cursor.moveToNext();
- }
- return preservedSettings;
+ int nameIndex = cursor.getColumnIndex(Settings.NameValueTable.NAME);
+ int isPreservedIndex = cursor.getColumnIndex(
+ Settings.NameValueTable.IS_PRESERVED_IN_RESTORE);
+
+ Set<String> preservedSettings = new HashSet<>();
+ while (!cursor.isAfterLast()) {
+ if (Boolean.parseBoolean(cursor.getString(isPreservedIndex))) {
+ preservedSettings.add(getQualifiedKeyForSetting(cursor.getString(nameIndex),
+ settingsUri));
+ }
+ cursor.moveToNext();
+ }
+
+ return preservedSettings;
+ }
}
/**
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
index 7ef0901..2e9a16f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
@@ -24,6 +24,7 @@
import android.os.RemoteException
import android.util.ArrayMap
import android.util.Log
+import android.util.RotationUtils
import android.view.IRemoteAnimationFinishedCallback
import android.view.IRemoteAnimationRunner
import android.view.RemoteAnimationAdapter
@@ -345,39 +346,33 @@
* Sets up this rotator.
*
* @param rotateDelta is the forward rotation change (the rotation the display is making).
- * @param displayW (and H) Is the size of the rotating display.
+ * @param parentW (and H) Is the size of the rotating parent.
*/
fun setup(
t: SurfaceControl.Transaction,
parent: SurfaceControl,
rotateDelta: Int,
- displayW: Float,
- displayH: Float
+ parentW: Float,
+ parentH: Float
) {
- var rotateDelta = rotateDelta
if (rotateDelta == 0) return
- // We want to counter-rotate, so subtract from 4
- rotateDelta = 4 - (rotateDelta + 4) % 4
- surface = SurfaceControl.Builder()
+ val surface = SurfaceControl.Builder()
.setName("Transition Unrotate")
.setContainerLayer()
.setParent(parent)
.build()
- // column-major
- when (rotateDelta) {
- 1 -> {
- t.setMatrix(surface, 0f, 1f, -1f, 0f)
- t.setPosition(surface!!, displayW, 0f)
- }
- 2 -> {
- t.setMatrix(surface, -1f, 0f, 0f, -1f)
- t.setPosition(surface!!, displayW, displayH)
- }
- 3 -> {
- t.setMatrix(surface, 0f, -1f, 1f, 0f)
- t.setPosition(surface!!, 0f, displayH)
- }
- }
+ // Rotate forward to match the new rotation (rotateDelta is the forward rotation the
+ // parent already took). Child surfaces will be in the old rotation relative to the new
+ // parent rotation, so we need to forward-rotate the child surfaces to match.
+ RotationUtils.rotateSurface(t, surface, rotateDelta)
+ val tmpPt = Point(0, 0)
+ // parentW/H are the size in the END rotation, the rotation utilities expect the
+ // starting size. So swap them if necessary
+ val flipped = rotateDelta % 2 != 0
+ val pw = if (flipped) parentH else parentW
+ val ph = if (flipped) parentW else parentH
+ RotationUtils.rotatePoint(tmpPt, rotateDelta, pw.toInt(), ph.toInt())
+ t.setPosition(surface, tmpPt.x.toFloat(), tmpPt.y.toFloat())
t.show(surface)
}
diff --git a/packages/SystemUI/docs/keyguard/aod.md b/packages/SystemUI/docs/keyguard/aod.md
index 6d76ed55..7f89984 100644
--- a/packages/SystemUI/docs/keyguard/aod.md
+++ b/packages/SystemUI/docs/keyguard/aod.md
@@ -1 +1,77 @@
# Always-on Display (AOD)
+
+AOD provides an alternatative 'screen-off' experience. Instead, of completely turning the display off, it provides a distraction-free, glanceable experience for the phone in a low-powered mode. In this low-powered mode, the display will have a lower refresh rate and the UI should frequently shift its displayed contents in order to prevent burn-in.
+
+The default doze component is specified by `config_dozeComponent` in the [framework config][1]. SystemUI provides a default Doze Component: [DozeService][2]. [DozeService][2] builds a [DozeMachine][3] with dependencies specified in [DozeModule][4] and configurations in [AmbientDisplayConfiguration][13] and [DozeParameters][14].
+
+[DozeMachine][3] handles the following main states:
+* AOD - persistently showing UI when the device is in a low-powered state
+* Pulsing - waking up the screen to show notifications (from AOD and screen off)
+* Docked UI - UI to show when the device is docked
+* Wake-up gestures - including lift to wake and tap to wake (from AOD and screen off)
+
+## Doze States ([see DozeMachine.State][3])
+### DOZE
+Device is asleep and listening for enabled pulsing and wake-up gesture triggers. In this state, no UI shows.
+
+### DOZE_AOD
+Device is asleep, showing UI, and listening for enabled pulsing and wake-up triggers. In this state, screen brightness is handled by [DozeScreenBrightness][5] which uses the brightness sensor specified by `doze_brightness_sensor_type` in the [SystemUI config][6]. To save power, this should be a low-powered sensor that shouldn't trigger as often as the light sensor used for on-screen adaptive brightness.
+
+### DOZE_AOD_PAUSED
+Device is asleep and would normally be in state `DOZE_AOD`; however, instead the display is temporarily off since the proximity sensor reported near for a minimum abount of time. [DozePauser][7] handles transitioning from `DOZE_AOD_PAUSING` after the minimum timeout after the NEAR is reported by the proximity sensor from [DozeTriggers][8]).
+
+### DOZE_PULSING
+Device is awake and showing UI. This is state typically occurs in response to incoming notification, but may also be from other pulse triggers specified in [DozeTriggers][8].
+
+### DOZE_AOD_DOCKED
+Device is awake, showing docking UI and listening for enabled pulsing and wake-up triggers. The default DockManager is provided by an empty interface at [DockManagerImpl][9]. SystemUI should override the DockManager for the DozeService to handle docking events.
+
+[DozeDockHandler][11] listens for Dock state changes from [DockManager][10] and updates the doze docking state.
+
+## Wake-up gestures
+Doze sensors are registered in [DozeTriggers][8] via [DozeSensors][12]. Sensors can be configured per posture for foldable devices.
+
+Relevant sensors include:
+* Proximity sensor
+* Brightness sensor
+* Wake-up gestures
+ * tap to wake
+ * double tap to wake
+ * lift to wake
+ * significant motion
+
+And are configured in the [AmbientDisplayConfiguration][13] with some related configurations specified in [DozeParameters][14].
+
+## Debugging Tips
+Enable DozeLog to print directly to logcat:
+```
+adb shell settings put global systemui/buffer/DozeLog v
+```
+
+Enable all DozeService logs to print directly to logcat:
+```
+adb shell setprop log.tag.DozeService DEBUG
+```
+
+Other helpful dumpsys commands (`adb shell dumpsys <service>`):
+* activity service com.android.systemui/.doze.DozeService
+* activity service com.android.systemui/.SystemUIService
+* display
+* power
+* dreams
+* sensorservice
+
+[1]: /frameworks/base/core/res/res/values/config.xml
+[2]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+[3]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+[4]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
+[5]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+[6]: /frameworks/base/packages/SystemUI/res/values/config.xml
+[7]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java
+[8]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+[9]: /frameworks/base/packages/SystemUI/src/com/android/systemui/dock/DockManagerImpl.java
+[10]: /frameworks/base/packages/SystemUI/src/com/android/systemui/dock/DockManager.java
+[11]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
+[12]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+[13]: /frameworks/base/core/java/android/hardware/display/AmbientDisplayConfiguration.java
+[14]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
index c7bc858..757ed76 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
@@ -25,6 +25,8 @@
import com.android.systemui.plugins.annotations.DependsOn;
import com.android.systemui.plugins.annotations.ProvidesInterface;
+import java.util.ArrayList;
+
/**
* Dispatches events to {@link DarkReceiver}s about changes in darkness, tint area and dark
* intensity. Accessible through {@link PluginDependency}
@@ -32,15 +34,15 @@
@ProvidesInterface(version = DarkIconDispatcher.VERSION)
@DependsOn(target = DarkReceiver.class)
public interface DarkIconDispatcher {
- int VERSION = 1;
+ int VERSION = 2;
/**
* Sets the dark area so {@link #applyDark} only affects the icons in the specified area.
*
- * @param r the area in which icons should change its tint, in logical screen
+ * @param r the areas in which icons should change its tint, in logical screen
* coordinates
*/
- void setIconsDarkArea(Rect r);
+ void setIconsDarkArea(ArrayList<Rect> r);
/**
* Adds a receiver to receive callbacks onDarkChanged
@@ -76,8 +78,8 @@
* @return the tint to apply to view depending on the desired tint color and
* the screen tintArea in which to apply that tint
*/
- static int getTint(Rect tintArea, View view, int color) {
- if (isInArea(tintArea, view)) {
+ static int getTint(ArrayList<Rect> tintAreas, View view, int color) {
+ if (isInAreas(tintAreas, view)) {
return color;
} else {
return DEFAULT_ICON_TINT;
@@ -85,15 +87,16 @@
}
/**
- * @return the dark intensity to apply to view depending on the desired dark
- * intensity and the screen tintArea in which to apply that intensity
+ * @return true if more than half of the view area are in any of the given
+ * areas, false otherwise
*/
- static float getDarkIntensity(Rect tintArea, View view, float intensity) {
- if (isInArea(tintArea, view)) {
- return intensity;
- } else {
- return 0f;
+ static boolean isInAreas(ArrayList<Rect> areas, View view) {
+ for (Rect area : areas) {
+ if (isInArea(area, view)) {
+ return true;
+ }
}
+ return false;
}
/**
@@ -122,7 +125,7 @@
*/
@ProvidesInterface(version = DarkReceiver.VERSION)
interface DarkReceiver {
- int VERSION = 1;
- void onDarkChanged(Rect area, float darkIntensity, int tint);
+ int VERSION = 2;
+ void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint);
}
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
deleted file mode 100644
index 6d1408d..0000000
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2017 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.plugins.qs;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.plugins.annotations.ProvidesInterface;
-
-@ProvidesInterface(version = DetailAdapter.VERSION)
-public interface DetailAdapter {
- public static final int VERSION = 1;
-
- CharSequence getTitle();
- Boolean getToggleState();
-
- default boolean getToggleEnabled() {
- return true;
- }
-
- View createDetailView(Context context, View convertView, ViewGroup parent);
-
- /**
- * @return intent for opening more settings related to this detail panel. If null, the more
- * settings button will not be shown
- */
- Intent getSettingsIntent();
-
- /**
- * @return resource id of the string to use for opening the settings intent. If
- * {@code Resources.ID_NULL}, then use the default string:
- * {@code com.android.systemui.R.string.quick_settings_more_settings}
- */
- default int getSettingsText() {
- return Resources.ID_NULL;
- }
-
- /**
- * @return resource id of the string to use for closing the detail panel. If
- * {@code Resources.ID_NULL}, then use the default string:
- * {@code com.android.systemui.R.string.quick_settings_done}
- */
- default int getDoneText() {
- return Resources.ID_NULL;
- }
-
- void setToggleState(boolean state);
- int getMetricsCategory();
-
- /**
- * Indicates whether the detail view wants to have its header (back button, title and
- * toggle) shown.
- */
- default boolean hasHeader() {
- return true;
- }
-
- /**
- * Indicates whether the detail view wants to animate when shown. This has no affect over the
- * closing animation. Detail panels will always animate when closed.
- */
- default boolean shouldAnimate() {
- return true;
- }
-
- /**
- * @return true if the callback handled the event and wants to keep the detail panel open, false
- * otherwise. Returning false will close the panel.
- */
- default boolean onDoneButtonClicked() {
- return false;
- }
-
- default UiEventLogger.UiEventEnum openDetailEvent() {
- return INVALID;
- }
-
- default UiEventLogger.UiEventEnum closeDetailEvent() {
- return INVALID;
- }
-
- default UiEventLogger.UiEventEnum moreSettingsEvent() {
- return INVALID;
- }
-
- UiEventLogger.UiEventEnum INVALID = () -> 0;
-}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 1ef5324..669d6a3 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -35,14 +35,12 @@
@ProvidesInterface(version = QSTile.VERSION)
@DependsOn(target = QSIconView.class)
-@DependsOn(target = DetailAdapter.class)
@DependsOn(target = Callback.class)
@DependsOn(target = Icon.class)
@DependsOn(target = State.class)
public interface QSTile {
- int VERSION = 2;
+ int VERSION = 3;
- DetailAdapter getDetailAdapter();
String getTileSpec();
boolean isAvailable();
@@ -117,12 +115,9 @@
}
@ProvidesInterface(version = Callback.VERSION)
- public interface Callback {
- public static final int VERSION = 1;
+ interface Callback {
+ static final int VERSION = 2;
void onStateChanged(State state);
- void onShowDetail(boolean show);
- void onToggleStateChanged(boolean state);
- void onScanStateChanged(boolean state);
}
@ProvidesInterface(version = Icon.VERSION)
diff --git a/packages/SystemUI/res-keyguard/values/config.xml b/packages/SystemUI/res-keyguard/values/config.xml
index e824443..a25ab51 100644
--- a/packages/SystemUI/res-keyguard/values/config.xml
+++ b/packages/SystemUI/res-keyguard/values/config.xml
@@ -28,5 +28,6 @@
<!-- Will display the bouncer on one side of the display, and the current user icon and
user switcher on the other side -->
<bool name="config_enableBouncerUserSwitcher">false</bool>
-
+ <!-- Time to be considered a consecutive fingerprint failure in ms -->
+ <integer name="fp_consecutive_failure_time_ms">3500</integer>
</resources>
diff --git a/packages/SystemUI/res/color-night/qs_detail_progress_track.xml b/packages/SystemUI/res/color-night/qs_detail_progress_track.xml
deleted file mode 100644
index c56382e..0000000
--- a/packages/SystemUI/res/color-night/qs_detail_progress_track.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 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.
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <!-- I really don't want to define this, but the View that uses this asset uses both the
- light and dark accent colors. -->
- <item android:alpha="0.6" android:drawable="@*android:color/accent_device_default_light" />
-</selector>
diff --git a/packages/SystemUI/res/color/qs_detail_progress_track.xml b/packages/SystemUI/res/color/qs_detail_progress_track.xml
deleted file mode 100644
index d86119f..0000000
--- a/packages/SystemUI/res/color/qs_detail_progress_track.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 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.
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <!-- I really don't want to define this, but the View that uses this asset uses both the
- light and dark accent colors. -->
- <item android:alpha="0.6" android:drawable="@*android:color/accent_device_default_dark" />
-</selector>
diff --git a/packages/SystemUI/res/drawable/ic_account_circle.xml b/packages/SystemUI/res/drawable/ic_account_circle.xml
new file mode 100644
index 0000000..5ca99f3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_account_circle.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M5.85,17.1Q7.125,16.125 8.7,15.562Q10.275,15 12,15Q13.725,15 15.3,15.562Q16.875,16.125 18.15,17.1Q19.025,16.075 19.513,14.775Q20,13.475 20,12Q20,8.675 17.663,6.337Q15.325,4 12,4Q8.675,4 6.338,6.337Q4,8.675 4,12Q4,13.475 4.488,14.775Q4.975,16.075 5.85,17.1ZM12,13Q10.525,13 9.512,11.988Q8.5,10.975 8.5,9.5Q8.5,8.025 9.512,7.012Q10.525,6 12,6Q13.475,6 14.488,7.012Q15.5,8.025 15.5,9.5Q15.5,10.975 14.488,11.988Q13.475,13 12,13ZM12,22Q9.925,22 8.1,21.212Q6.275,20.425 4.925,19.075Q3.575,17.725 2.788,15.9Q2,14.075 2,12Q2,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22ZM12,20Q13.325,20 14.5,19.613Q15.675,19.225 16.65,18.5Q15.675,17.775 14.5,17.387Q13.325,17 12,17Q10.675,17 9.5,17.387Q8.325,17.775 7.35,18.5Q8.325,19.225 9.5,19.613Q10.675,20 12,20ZM12,11Q12.65,11 13.075,10.575Q13.5,10.15 13.5,9.5Q13.5,8.85 13.075,8.425Q12.65,8 12,8Q11.35,8 10.925,8.425Q10.5,8.85 10.5,9.5Q10.5,10.15 10.925,10.575Q11.35,11 12,11ZM12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5ZM12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_account_circle_filled.xml b/packages/SystemUI/res/drawable/ic_account_circle_filled.xml
new file mode 100644
index 0000000..47c553b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_account_circle_filled.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM18.36,16.83c-1.43,-1.74 -4.9,-2.33 -6.36,-2.33s-4.93,0.59 -6.36,2.33A7.95,7.95 0,0 1,4 12c0,-4.41 3.59,-8 8,-8s8,3.59 8,8c0,1.82 -0.62,3.49 -1.64,4.83z"/>
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M12,6c-1.94,0 -3.5,1.56 -3.5,3.5S10.06,13 12,13s3.5,-1.56 3.5,-3.5S13.94,6 12,6z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/qs_detail_switch.xml b/packages/SystemUI/res/drawable/ic_add_supervised_user.xml
similarity index 62%
rename from packages/SystemUI/res/layout/qs_detail_switch.xml
rename to packages/SystemUI/res/drawable/ic_add_supervised_user.xml
index abb2497..627743e 100644
--- a/packages/SystemUI/res/layout/qs_detail_switch.xml
+++ b/packages/SystemUI/res/drawable/ic_add_supervised_user.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright (C) 2019 The Android Open Source Project
+ ~ 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.
@@ -13,11 +13,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<Switch
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@android:id/toggle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:clickable="false"
- android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
\ No newline at end of file
+<layer-list
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item android:drawable="@*android:drawable/ic_add_supervised_user" />
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/ic_manage_users.xml b/packages/SystemUI/res/drawable/ic_manage_users.xml
new file mode 100644
index 0000000..3a0805d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_manage_users.xml
@@ -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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path android:fillColor="@android:color/white"
+ android:pathData="M10,12Q8.35,12 7.175,10.825Q6,9.65 6,8Q6,6.35 7.175,5.175Q8.35,4 10,4Q11.65,4 12.825,5.175Q14,6.35 14,8Q14,9.65 12.825,10.825Q11.65,12 10,12ZM2,20V17.2Q2,16.375 2.425,15.65Q2.85,14.925 3.6,14.55Q4.875,13.9 6.475,13.45Q8.075,13 10,13Q10.2,13 10.35,13Q10.5,13 10.65,13.05Q10.45,13.5 10.312,13.988Q10.175,14.475 10.1,15H10Q8.225,15 6.812,15.45Q5.4,15.9 4.5,16.35Q4.275,16.475 4.138,16.7Q4,16.925 4,17.2V18H10.3Q10.45,18.525 10.7,19.038Q10.95,19.55 11.25,20ZM16,21L15.7,19.5Q15.4,19.375 15.137,19.238Q14.875,19.1 14.6,18.9L13.15,19.35L12.15,17.65L13.3,16.65Q13.25,16.3 13.25,16Q13.25,15.7 13.3,15.35L12.15,14.35L13.15,12.65L14.6,13.1Q14.875,12.9 15.137,12.762Q15.4,12.625 15.7,12.5L16,11H18L18.3,12.5Q18.6,12.625 18.863,12.775Q19.125,12.925 19.4,13.15L20.85,12.65L21.85,14.4L20.7,15.4Q20.75,15.7 20.75,16.025Q20.75,16.35 20.7,16.65L21.85,17.65L20.85,19.35L19.4,18.9Q19.125,19.1 18.863,19.238Q18.6,19.375 18.3,19.5L18,21ZM17,18Q17.825,18 18.413,17.413Q19,16.825 19,16Q19,15.175 18.413,14.587Q17.825,14 17,14Q16.175,14 15.588,14.587Q15,15.175 15,16Q15,16.825 15.588,17.413Q16.175,18 17,18ZM10,10Q10.825,10 11.413,9.412Q12,8.825 12,8Q12,7.175 11.413,6.588Q10.825,6 10,6Q9.175,6 8.588,6.588Q8,7.175 8,8Q8,8.825 8.588,9.412Q9.175,10 10,10ZM10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8ZM10,15Q10,15 10,15Q10,15 10,15Q10,15 10,15Q10,15 10,15Q10,15 10,15Q10,15 10,15Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/qs_detail_background.xml b/packages/SystemUI/res/drawable/qs_detail_background.xml
deleted file mode 100644
index c23649d..0000000
--- a/packages/SystemUI/res/drawable/qs_detail_background.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<!--
-Copyright (C) 2014 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.
--->
-<transition xmlns:android="http://schemas.android.com/apk/res/android">
- <item>
- <inset>
- <shape>
- <solid android:color="@android:color/transparent"/>
- <corners android:radius="@dimen/qs_footer_action_corner_radius" />
- </shape>
- </inset>
- </item>
- <item>
- <inset>
- <shape>
- <solid android:color="?android:attr/colorBackgroundFloating"/>
- <corners android:radius="@dimen/qs_footer_action_corner_radius" />
- </shape>
- </inset>
- </item>
-</transition>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml
deleted file mode 100644
index 78655c0..0000000
--- a/packages/SystemUI/res/layout/qs_detail.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<!-- Extends LinearLayout -->
-<com.android.systemui.qs.QSDetail
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@drawable/qs_detail_background"
- android:layout_marginTop="@dimen/qs_detail_margin_top"
- android:clickable="true"
- android:orientation="vertical"
- android:paddingBottom="8dp"
- android:visibility="invisible"
- android:elevation="4dp"
- android:importantForAccessibility="no" >
-
- <include
- android:id="@+id/qs_detail_header"
- layout="@layout/qs_detail_header"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- />
-
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@+id/qs_detail_header_progress"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:alpha="0"
- android:background="@color/qs_detail_progress_track"
- android:src="@drawable/indeterminate_anim"
- android:scaleType="fitXY"
- />
-
- <ScrollView
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:fillViewport="true">
-
- <FrameLayout
- android:id="@android:id/content"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
- </ScrollView>
-
- <include layout="@layout/qs_detail_buttons" />
-
-</com.android.systemui.qs.QSDetail>
diff --git a/packages/SystemUI/res/layout/qs_detail_buttons.xml b/packages/SystemUI/res/layout/qs_detail_buttons.xml
deleted file mode 100644
index 75f43f9..0000000
--- a/packages/SystemUI/res/layout/qs_detail_buttons.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2016 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.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingEnd="8dp"
- android:gravity="end">
-
- <TextView
- android:id="@android:id/button2"
- style="@style/QSBorderlessButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
- android:minHeight="48dp"
- android:minWidth="132dp"
- android:textAppearance="@style/TextAppearance.QS.DetailButton"
- android:focusable="true" />
-
- <TextView
- android:id="@android:id/button1"
- style="@style/QSBorderlessButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:minHeight="48dp"
- android:minWidth="88dp"
- android:textAppearance="@style/TextAppearance.QS.DetailButton"
- android:focusable="true"/>
-
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_detail_header.xml b/packages/SystemUI/res/layout/qs_detail_header.xml
deleted file mode 100644
index d1ab054..0000000
--- a/packages/SystemUI/res/layout/qs_detail_header.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.keyguard.AlphaOptimizedLinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingLeft="@dimen/qs_detail_header_padding"
- android:paddingTop="@dimen/qs_detail_header_padding"
- android:paddingBottom="@dimen/qs_detail_items_padding_top"
- android:paddingEnd="@dimen/qs_panel_padding"
- android:background="@drawable/btn_borderless_rect"
- android:orientation="vertical"
- android:gravity="center">
-
- <com.android.systemui.ResizingSpace
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_detail_header_margin_top" />
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
-
- <TextView
- android:id="@android:id/title"
- android:paddingStart="@dimen/qs_detail_header_text_padding"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:textDirection="locale"
- android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
-
- <ImageView
- android:id="@+id/settings"
- android:layout_width="@dimen/qs_detail_image_width"
- android:layout_height="@dimen/qs_detail_image_height"
- android:background="?android:attr/selectableItemBackground"
- android:padding="@dimen/qs_detail_image_padding"
- android:src="@drawable/ic_settings"
- android:visibility="gone"/>
-
- <ViewStub
- android:id="@+id/toggle_stub"
- android:inflatedId="@+id/toggle"
- android:layout="@layout/qs_detail_switch"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-
- </LinearLayout>
-
-</com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_detail_item.xml b/packages/SystemUI/res/layout/qs_detail_item.xml
deleted file mode 100644
index 0844bb4..0000000
--- a/packages/SystemUI/res/layout/qs_detail_item.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="@dimen/qs_detail_item_height"
- android:background="@drawable/btn_borderless_rect"
- android:clickable="true"
- android:focusable="true"
- android:gravity="center_vertical"
- android:orientation="horizontal" >
-
- <ImageView
- android:id="@android:id/icon"
- android:layout_width="@dimen/qs_detail_item_icon_width"
- android:layout_height="@dimen/qs_detail_item_icon_size"
- android:layout_marginStart="@dimen/qs_detail_item_icon_marginStart"
- android:layout_marginEnd="@dimen/qs_detail_item_icon_marginEnd"
- android:tint="?android:attr/textColorPrimary"/>
-
- <LinearLayout
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginStart="12dp"
- android:layout_weight="1"
- android:orientation="vertical" >
-
- <TextView
- android:id="@android:id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textDirection="locale"
- android:ellipsize="end"
- android:textAppearance="@style/TextAppearance.QS.DetailItemPrimary" />
-
- <TextView
- android:id="@android:id/summary"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textDirection="locale"
- android:layout_marginTop="2dp"
- android:textAppearance="@style/TextAppearance.QS.DetailItemSecondary" />
- </LinearLayout>
-
- <ImageView
- android:id="@android:id/icon2"
- style="@style/QSBorderlessButton"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:clickable="true"
- android:focusable="true"
- android:scaleType="center"
- android:contentDescription="@*android:string/media_route_controller_disconnect"
- android:tint="?android:attr/textColorPrimary" />
-
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_detail_items.xml b/packages/SystemUI/res/layout/qs_detail_items.xml
deleted file mode 100644
index 60cba67..0000000
--- a/packages/SystemUI/res/layout/qs_detail_items.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<!-- extends FrameLayout -->
-<com.android.systemui.qs.QSDetailItems
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:sysui="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingStart="@dimen/qs_detail_padding_start"
- android:paddingEnd="16dp">
-
- <com.android.systemui.qs.AutoSizingList
- android:id="@android:id/list"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- sysui:itemHeight="@dimen/qs_detail_item_height"
- style="@style/AutoSizingList"/>
-
- <LinearLayout
- android:id="@android:id/empty"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:gravity="center"
- android:orientation="vertical">
-
- <ImageView
- android:id="@android:id/icon"
- android:layout_width="56dp"
- android:layout_height="56dp"
- android:tint="?android:attr/textColorSecondary" />
-
- <TextView
- android:id="@android:id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="20dp"
- android:textAppearance="@style/TextAppearance.QS.DetailEmpty"/>
- </LinearLayout>
-</com.android.systemui.qs.QSDetailItems>
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index 85b33cc..2040051 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -47,10 +47,6 @@
<include layout="@layout/quick_status_bar_expanded_header" />
- <include
- android:id="@+id/qs_detail"
- layout="@layout/qs_detail" />
-
<ViewStub
android:id="@+id/container_stub"
android:inflatedId="@+id/qs_footer_actions"
diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml
index 3a0df28..0c847ed 100644
--- a/packages/SystemUI/res/layout/qs_user_detail_item.xml
+++ b/packages/SystemUI/res/layout/qs_user_detail_item.xml
@@ -52,7 +52,7 @@
android:gravity="center_horizontal" />
<ImageView
android:id="@+id/restricted_padlock"
- android:layout_width="@dimen/qs_detail_item_secondary_text_size"
+ android:layout_width="@dimen/qs_tile_text_size"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:src="@drawable/ic_info"
diff --git a/packages/SystemUI/res/layout/system_icons.xml b/packages/SystemUI/res/layout/system_icons.xml
index 4f4bae4..816dfd3 100644
--- a/packages/SystemUI/res/layout/system_icons.xml
+++ b/packages/SystemUI/res/layout/system_icons.xml
@@ -19,6 +19,7 @@
android:id="@+id/system_icons"
android:layout_width="wrap_content"
android:layout_height="match_parent"
+ android:layout_gravity="center_vertical|end"
android:gravity="center_vertical">
<com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons"
diff --git a/packages/SystemUI/res/layout/tuner_zen_mode_panel.xml b/packages/SystemUI/res/layout/tuner_zen_mode_panel.xml
deleted file mode 100644
index efe63d7..0000000
--- a/packages/SystemUI/res/layout/tuner_zen_mode_panel.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2016 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.
--->
-<!-- extends LinearLayout -->
-<com.android.systemui.tuner.TunerZenModePanel
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/tuner_zen_mode_panel"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:clipChildren="false"
- android:visibility="gone"
- android:orientation="vertical" >
-
- <View
- android:id="@+id/zen_embedded_divider"
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:layout_marginBottom="12dp"
- android:layout_marginTop="8dp"
- android:background="@color/qs_tile_divider" />
-
- <include
- android:layout_width="match_parent"
- android:layout_height="48dp"
- android:layout_marginStart="16dp"
- android:id="@+id/tuner_zen_switch"
- layout="@layout/qs_detail_header" />
-
- <include layout="@layout/zen_mode_panel" />
-
- <include
- android:id="@+id/tuner_zen_buttons"
- layout="@layout/qs_detail_buttons" />
-
-</com.android.systemui.tuner.TunerZenModePanel>
diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
index 7b95cf3c..1633e52 100644
--- a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
+++ b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
@@ -21,6 +21,7 @@
android:id="@+id/user_switcher_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:layout_marginBottom="64dp"
android:layout_marginEnd="60dp"
android:layout_marginStart="60dp">
diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml
index 8d02429..401c4bd 100644
--- a/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml
+++ b/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml
@@ -29,17 +29,19 @@
<ImageView
android:id="@+id/icon"
+ android:scaleType="centerInside"
android:layout_gravity="center"
android:layout_width="20dp"
android:layout_height="20dp"
android:contentDescription="@null"
+ android:tint="@color/user_switcher_fullscreen_popup_item_tint"
android:layout_marginEnd="10dp" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textColor="@*android:color/text_color_primary_device_default_dark"
+ android:textColor="@color/user_switcher_fullscreen_popup_item_tint"
android:textSize="14sp"
android:layout_gravity="start" />
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml
deleted file mode 100644
index 5862413..0000000
--- a/packages/SystemUI/res/layout/zen_mode_panel.xml
+++ /dev/null
@@ -1,170 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<!-- extends LinearLayout -->
-<com.android.systemui.volume.ZenModePanel xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/zen_mode_panel"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false" >
-
- <LinearLayout
- android:id="@+id/edit_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="?android:attr/colorPrimary"
- android:clipChildren="false"
- android:orientation="vertical">
-
- <com.android.systemui.volume.SegmentedButtons
- android:id="@+id/zen_buttons"
- android:background="@drawable/segmented_buttons_background"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="16dp"
- android:layout_marginBottom="8dp" />
-
- <RelativeLayout
- android:id="@+id/zen_introduction"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="16dp"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
- android:background="@drawable/zen_introduction_message_background"
- android:theme="@*android:style/ThemeOverlay.DeviceDefault.Accent.Light">
-
- <ImageView
- android:id="@+id/zen_introduction_confirm"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_marginEnd="8dp"
- android:layout_alignParentEnd="true"
- android:background="@drawable/btn_borderless_rect"
- android:clickable="true"
- android:contentDescription="@string/accessibility_desc_close"
- android:scaleType="center"
- android:src="@drawable/ic_close_white_rounded" />
-
- <TextView
- android:id="@+id/zen_introduction_message"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="12dp"
- android:layout_marginStart="24dp"
- android:textDirection="locale"
- android:lineSpacingMultiplier="1.20029"
- android:layout_toStartOf="@id/zen_introduction_confirm"
- android:textAppearance="@style/TextAppearance.QS.Introduction" />
-
- <TextView
- android:id="@+id/zen_introduction_customize"
- style="@style/QSBorderlessButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_marginEnd="12dp"
- android:layout_below="@id/zen_introduction_message"
- android:clickable="true"
- android:focusable="true"
- android:text="@string/zen_priority_customize_button"
- android:textAppearance="@style/TextAppearance.QS.DetailButton.White" />
-
- <View
- android:layout_width="0dp"
- android:layout_height="16dp"
- android:layout_below="@id/zen_introduction_message"
- android:layout_alignParentEnd="true" />
-
- </RelativeLayout>
-
- <com.android.settingslib.notification.ZenRadioLayout
- android:id="@+id/zen_conditions"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
- android:layout_marginEnd="4dp"
- android:layout_marginStart="4dp"
- android:paddingBottom="@dimen/zen_mode_condition_detail_bottom_padding"
- android:orientation="horizontal" >
- <RadioGroup
- android:id="@+id/zen_radio_buttons"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- <LinearLayout
- android:id="@+id/zen_radio_buttons_content"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical"/>
- </com.android.settingslib.notification.ZenRadioLayout>
-
- <TextView
- android:id="@+id/zen_alarm_warning"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginStart="18dp"
- android:layout_marginEnd="16dp"
- android:textDirection="locale"
- android:lineSpacingMultiplier="1.20029"
- android:textAppearance="@style/TextAppearance.QS.Warning" />
- </LinearLayout>
-
- <LinearLayout
- android:id="@android:id/empty"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:background="?android:attr/colorPrimary"
- android:gravity="center"
- android:orientation="vertical">
-
- <ImageView
- android:id="@android:id/icon"
- android:layout_width="56dp"
- android:layout_height="56dp"
- android:alpha="?android:attr/disabledAlpha"
- android:tint="?android:attr/colorForeground" />
-
- <TextView
- android:id="@android:id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="20dp"
- android:textAppearance="@style/TextAppearance.QS.DetailEmpty"/>
- </LinearLayout>
-
- <LinearLayout
- android:id="@+id/auto_rule"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="?android:attr/colorPrimary"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="16dp"
- android:layout_marginTop="16dp"
- android:layout_marginBottom="8dp"
- android:orientation="vertical">
-
- <TextView
- android:id="@android:id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.QS.DetailItemPrimary"/>
-
- </LinearLayout>
-
-</com.android.systemui.volume.ZenModePanel>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 9d24e9b..01eb09b 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -38,11 +38,6 @@
<dimen name="qs_footer_padding">14dp</dimen>
<dimen name="qs_security_footer_background_inset">12dp</dimen>
- <dimen name="battery_detail_graph_space_top">9dp</dimen>
- <dimen name="battery_detail_graph_space_bottom">9dp</dimen>
-
- <dimen name="qs_detail_header_margin_top">14dp</dimen>
-
<dimen name="volume_tool_tip_top_margin">12dp</dimen>
<dimen name="volume_row_slider_height">128dp</dimen>
diff --git a/packages/SystemUI/res/values-sw410dp/dimens.xml b/packages/SystemUI/res/values-sw410dp/dimens.xml
index d33ee99..7da47e5 100644
--- a/packages/SystemUI/res/values-sw410dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw410dp/dimens.xml
@@ -20,8 +20,6 @@
<!-- These resources are around just to allow their values to be customized
for different hardware and product builds. -->
<resources>
- <dimen name="qs_detail_items_padding_top">16dp</dimen>
-
<!-- Global actions grid -->
<dimen name="global_actions_grid_vertical_padding">8dp</dimen>
<dimen name="global_actions_grid_horizontal_padding">4dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index a66ed15..8f6bde5 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -65,9 +65,6 @@
<dimen name="qs_security_footer_single_line_height">48dp</dimen>
<dimen name="qs_security_footer_background_inset">0dp</dimen>
- <!-- When split shade is used, this panel should be aligned to the top -->
- <dimen name="qs_detail_margin_top">0dp</dimen>
-
<!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) -->
<dimen name="large_dialog_width">472dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index 2eff692..a9e6d22 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -24,29 +24,6 @@
<string name="config_systemUIFactoryComponent" translatable="false">
com.android.systemui.tv.TvSystemUIFactory
</string>
- <!-- SystemUI Services: The classes of the stuff to start. -->
- <string-array name="config_systemUIServiceComponents" translatable="false">
- <item>com.android.systemui.util.NotificationChannels</item>
- <item>com.android.systemui.volume.VolumeUI</item>
- <item>com.android.systemui.privacy.television.TvOngoingPrivacyChip</item>
- <item>com.android.systemui.statusbar.tv.TvStatusBar</item>
- <item>com.android.systemui.statusbar.tv.notifications.TvNotificationPanel</item>
- <item>com.android.systemui.statusbar.tv.notifications.TvNotificationHandler</item>
- <item>com.android.systemui.statusbar.tv.VpnStatusObserver</item>
- <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
- <item>com.android.systemui.usb.StorageNotification</item>
- <item>com.android.systemui.power.PowerUI</item>
- <item>com.android.systemui.media.RingtonePlayer</item>
- <item>com.android.systemui.keyboard.KeyboardUI</item>
- <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
- <item>@string/config_systemUIVendorServiceComponent</item>
- <item>com.android.systemui.SliceBroadcastRelayHandler</item>
- <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
- <item>com.android.systemui.accessibility.WindowMagnification</item>
- <item>com.android.systemui.toast.ToastUI</item>
- <item>com.android.systemui.wmshell.WMShell</item>
- <item>com.android.systemui.media.systemsounds.HomeSoundEffectController</item>
- </string-array>
<!-- Svelte specific logic, see RecentsConfiguration.SVELTE_* constants. -->
<integer name="recents_svelte_level">3</integer>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index e6ab0ff..2992859 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -114,13 +114,6 @@
<attr name="hybridNotificationTextStyle" format="reference" />
</declare-styleable>
- <declare-styleable name="AutoSizingList">
- <!-- Whether AutoSizingList will show only as many items as fit on screen and
- remove extra items instead of scrolling. -->
- <attr name="enableAutoSizing" format="boolean" />
- <attr name="itemHeight" format="dimension" />
- </declare-styleable>
-
<declare-styleable name="PluginInflateContainer">
<attr name="viewType" format="string" />
</declare-styleable>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 1514778..faf518e 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -88,6 +88,7 @@
<color name="keyguard_user_switcher_background_gradient_color">#77000000</color>
<color name="user_switcher_fullscreen_bg">@android:color/system_neutral1_900</color>
+ <color name="user_switcher_fullscreen_popup_item_tint">@*android:color/text_color_primary_device_default_dark</color>
<!-- The color of the navigation bar icons. Need to be in sync with ic_sysbar_* -->
<color name="navigation_bar_icon_color">#E5FFFFFF</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 47822b7..bb1ffa8 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -292,43 +292,6 @@
<!-- SystemUIFactory component -->
<string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string>
- <!-- SystemUI Services: The classes of base stuff to start by default for all
- configurations. -->
- <string-array name="config_systemUIServiceComponents" translatable="false">
- <item>com.android.systemui.util.NotificationChannels</item>
- <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
- <item>com.android.keyguard.KeyguardBiometricLockoutLogger</item>
- <item>com.android.systemui.recents.Recents</item>
- <item>com.android.systemui.volume.VolumeUI</item>
- <item>com.android.systemui.statusbar.phone.StatusBar</item>
- <item>com.android.systemui.usb.StorageNotification</item>
- <item>com.android.systemui.power.PowerUI</item>
- <item>com.android.systemui.media.RingtonePlayer</item>
- <item>com.android.systemui.keyboard.KeyboardUI</item>
- <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
- <item>@string/config_systemUIVendorServiceComponent</item>
- <item>com.android.systemui.util.leak.GarbageMonitor$Service</item>
- <item>com.android.systemui.LatencyTester</item>
- <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
- <item>com.android.systemui.ScreenDecorations</item>
- <item>com.android.systemui.biometrics.AuthController</item>
- <item>com.android.systemui.log.SessionTracker</item>
- <item>com.android.systemui.SliceBroadcastRelayHandler</item>
- <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
- <item>com.android.systemui.theme.ThemeOverlayController</item>
- <item>com.android.systemui.accessibility.WindowMagnification</item>
- <item>com.android.systemui.accessibility.SystemActions</item>
- <item>com.android.systemui.toast.ToastUI</item>
- <item>com.android.systemui.wmshell.WMShell</item>
- <item>com.android.systemui.clipboardoverlay.ClipboardListener</item>
- </string-array>
-
- <!-- SystemUI Services: The classes of the additional stuff to start. Services here are
- specified as an overlay to provide configuration-specific services that
- supplement those listed in config_systemUIServiceComponents. -->
- <string-array name="config_additionalSystemUIServiceComponents" translatable="false">
- </string-array>
-
<!-- QS tile shape store width. negative implies fill configuration instead of stroke-->
<dimen name="config_qsTileStrokeWidthActive">-1dp</dimen>
<dimen name="config_qsTileStrokeWidthInactive">-1dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index fe79f27..d1f4f19 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -500,26 +500,10 @@
<dimen name="qs_panel_elevation">4dp</dimen>
<dimen name="qs_panel_padding_bottom">@dimen/new_footer_height</dimen>
<dimen name="qs_panel_padding_top">48dp</dimen>
- <dimen name="qs_detail_header_padding">0dp</dimen>
- <dimen name="qs_detail_image_width">56dp</dimen>
- <dimen name="qs_detail_image_height">56dp</dimen>
- <dimen name="qs_detail_image_padding">16dp</dimen>
- <dimen name="qs_detail_item_height">48dp</dimen>
- <dimen name="qs_detail_header_text_size">20sp</dimen>
- <dimen name="qs_detail_button_text_size">14sp</dimen>
- <dimen name="qs_detail_item_primary_text_size">16sp</dimen>
- <dimen name="qs_detail_item_secondary_text_size">14sp</dimen>
- <dimen name="qs_detail_empty_text_size">14sp</dimen>
- <dimen name="qs_detail_header_margin_top">28dp</dimen>
- <dimen name="qs_detail_header_text_padding">16dp</dimen>
+
<dimen name="qs_data_usage_text_size">14sp</dimen>
<dimen name="qs_data_usage_usage_text_size">36sp</dimen>
- <dimen name="qs_detail_padding_start">16dp</dimen>
- <dimen name="qs_detail_items_padding_top">4dp</dimen>
- <dimen name="qs_detail_item_icon_size">24dp</dimen>
- <dimen name="qs_detail_item_icon_width">32dp</dimen>
- <dimen name="qs_detail_item_icon_marginStart">0dp</dimen>
- <dimen name="qs_detail_item_icon_marginEnd">20dp</dimen>
+
<dimen name="qs_header_mobile_icon_size">@dimen/status_bar_icon_drawing_size</dimen>
<dimen name="qs_header_carrier_separator_width">6dp</dimen>
<dimen name="qs_carrier_margin_width">4dp</dimen>
@@ -533,9 +517,6 @@
<dimen name="qs_security_footer_background_inset">0dp</dimen>
<dimen name="qs_security_footer_corner_radius">28dp</dimen>
- <!-- Desired qs icon overlay size. -->
- <dimen name="qs_detail_icon_overlay_size">24dp</dimen>
-
<dimen name="segmented_button_spacing">0dp</dimen>
<dimen name="borderless_button_radius">2dp</dimen>
@@ -547,8 +528,6 @@
<!-- Padding between subtitles and the following text in the QSFooter dialog -->
<dimen name="qs_footer_dialog_subtitle_padding">20dp</dimen>
- <dimen name="qs_detail_margin_top">@*android:dimen/quick_qs_offset_height</dimen>
-
<!-- Zen mode panel: spacing between two-line condition upper and lower lines -->
<dimen name="zen_mode_condition_detail_item_interline_spacing">4dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d39e295..3b7e9d4 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -609,8 +609,6 @@
<string name="quick_settings_inversion_label">Color inversion</string>
<!-- QuickSettings: Label for the toggle that controls whether display color correction is enabled. [CHAR LIMIT=NONE] -->
<string name="quick_settings_color_correction_label">Color correction</string>
- <!-- QuickSettings: Control panel: Label for button that navigates to settings. [CHAR LIMIT=NONE] -->
- <string name="quick_settings_more_settings">More settings</string>
<!-- QuickSettings: Control panel: Label for button that navigates to user settings. [CHAR LIMIT=NONE] -->
<string name="quick_settings_more_user_settings">User settings</string>
<!-- QuickSettings: Control panel: Label for button that dismisses control panel. [CHAR LIMIT=NONE] -->
@@ -1213,9 +1211,6 @@
<!-- Alarm template for far alarms [CHAR LIMIT=25] -->
<string name="alarm_template_far">on <xliff:g id="when" example="Fri 7:00 AM">%1$s</xliff:g></string>
- <!-- Accessibility label for Quick Settings detail screens [CHAR LIMIT=NONE] -->
- <string name="accessibility_quick_settings_detail">Quick Settings, <xliff:g id="title" example="Wi-Fi">%s</xliff:g>.</string>
-
<!-- Accessibility label for hotspot icon [CHAR LIMIT=NONE] -->
<string name="accessibility_status_bar_hotspot">Hotspot</string>
@@ -1731,11 +1726,6 @@
<string name="data_connection_no_internet">No internet</string>
<!-- accessibility label for quick settings items that open a details page [CHAR LIMIT=NONE] -->
- <string name="accessibility_quick_settings_open_details">Open details.</string>
-
- <!-- accessibility label for quick settings items that are currently disabled. Must have a reason [CHAR LIMIT=NONE] -->
-
- <!-- accessibility label for quick settings items that open a details page [CHAR LIMIT=NONE] -->
<string name="accessibility_quick_settings_open_settings">Open <xliff:g name="page" example="Bluetooth">%s</xliff:g> settings.</string>
<!-- accessibility label for button to edit quick settings [CHAR LIMIT=NONE] -->
@@ -2402,4 +2392,8 @@
<!-- Generic "add" string [CHAR LIMIT=NONE] -->
<string name="add">Add</string>
+ <!-- Add supervised user -->
+ <string name="add_user_supervised" translatable="false">@*android:string/supervised_user_creation_label</string>
+ <!-- Manage users - For system user management [CHAR LIMIT=40] -->
+ <string name="manage_users">Manage users</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 9448d3f..5d252fd 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -96,17 +96,12 @@
<item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
</style>
- <style name="TextAppearance.QS.DetailHeader">
- <item name="android:textSize">@dimen/qs_detail_header_text_size</item>
- <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
- </style>
-
<style name="TextAppearance.QS.DetailItemPrimary">
- <item name="android:textSize">@dimen/qs_detail_item_primary_text_size</item>
+ <item name="android:textSize">@dimen/qs_tile_text_size</item>
</style>
<style name="TextAppearance.QS.DetailItemSecondary">
- <item name="android:textSize">@dimen/qs_detail_item_secondary_text_size</item>
+ <item name="android:textSize">@dimen/qs_tile_text_size</item>
<item name="android:textColor">?android:attr/colorAccent</item>
</style>
@@ -120,23 +115,6 @@
<item name="android:textColor">?android:attr/colorError</item>
</style>
- <style name="TextAppearance.QS.DetailButton">
- <item name="android:textSize">@dimen/qs_detail_button_text_size</item>
- <item name="android:textColor">?android:attr/textColorSecondary</item>
- <item name="android:textAllCaps">true</item>
- <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
- <item name="android:gravity">center</item>
- </style>
-
- <style name="TextAppearance.QS.DetailButton.White">
- <item name="android:textColor">@color/zen_introduction</item>
- </style>
-
- <style name="TextAppearance.QS.DetailEmpty">
- <item name="android:textSize">@dimen/qs_detail_empty_text_size</item>
- <item name="android:textColor">?android:attr/textColorPrimary</item>
- </style>
-
<style name="TextAppearance.QS.SegmentedButton">
<item name="android:textSize">16sp</item>
<item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
@@ -167,7 +145,7 @@
</style>
<style name="TextAppearance.QS.UserSwitcher">
- <item name="android:textSize">@dimen/qs_detail_item_secondary_text_size</item>
+ <item name="android:textSize">@dimen/qs_tile_text_size</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
@@ -433,9 +411,6 @@
<item name="numColumns">3</item>
</style>
- <style name="AutoSizingList">
- <item name="enableAutoSizing">true</item>
- </style>
<style name="Theme.SystemUI.MediaProjectionAlertDialog">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 2ef8d6d..99e0ec1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -86,8 +86,6 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import androidx.lifecycle.Observer;
-
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.util.LatencyTracker;
@@ -109,7 +107,6 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.Assert;
-import com.android.systemui.util.RingerModeTracker;
import com.google.android.collect.Lists;
@@ -146,7 +143,6 @@
private static final boolean DEBUG_ACTIVE_UNLOCK = Build.IS_DEBUGGABLE;
private static final boolean DEBUG_SPEW = false;
private static final int BIOMETRIC_LOCKOUT_RESET_DELAY_MS = 600;
- private int mNumActiveUnlockTriggers = 0;
private static final String ACTION_FACE_UNLOCK_STARTED
= "com.android.facelock.FACE_UNLOCK_STARTED";
@@ -157,7 +153,6 @@
private static final int MSG_TIME_UPDATE = 301;
private static final int MSG_BATTERY_UPDATE = 302;
private static final int MSG_SIM_STATE_CHANGE = 304;
- private static final int MSG_RINGER_MODE_CHANGED = 305;
private static final int MSG_PHONE_STATE_CHANGED = 306;
private static final int MSG_DEVICE_PROVISIONED = 308;
private static final int MSG_DPM_STATE_CHANGED = 309;
@@ -313,7 +308,6 @@
private TrustManager mTrustManager;
private UserManager mUserManager;
private KeyguardBypassController mKeyguardBypassController;
- private RingerModeTracker mRingerModeTracker;
private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
private boolean mIsFaceAuthUserRequested;
@@ -325,8 +319,6 @@
private final InteractionJankMonitor mInteractionJankMonitor;
private final LatencyTracker mLatencyTracker;
private boolean mLogoutEnabled;
- // cached value to avoid IPCs
- private boolean mIsUdfpsEnrolled;
private boolean mIsFaceEnrolled;
// If the user long pressed the lock icon, disabling face auth for the current session.
private boolean mLockIconPressed;
@@ -362,13 +354,6 @@
private final Handler mHandler;
- private final Observer<Integer> mRingerModeObserver = new Observer<Integer>() {
- @Override
- public void onChanged(Integer ringer) {
- mHandler.obtainMessage(MSG_RINGER_MODE_CHANGED, ringer, 0).sendToTarget();
- }
- };
-
private SparseBooleanArray mBiometricEnabledForUser = new SparseBooleanArray();
private BiometricManager mBiometricManager;
private IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
@@ -1808,10 +1793,6 @@
mFaceRunningState = BIOMETRIC_STATE_STOPPED;
}
- private void registerRingerTracker() {
- mRingerModeTracker.getRingerMode().observeForever(mRingerModeObserver);
- }
-
@VisibleForTesting
@Inject
protected KeyguardUpdateMonitor(
@@ -1819,7 +1800,6 @@
@Main Looper mainLooper,
BroadcastDispatcher broadcastDispatcher,
DumpManager dumpManager,
- RingerModeTracker ringerModeTracker,
@Background Executor backgroundExecutor,
@Main Executor mainExecutor,
StatusBarStateController statusBarStateController,
@@ -1837,7 +1817,6 @@
mBroadcastDispatcher = broadcastDispatcher;
mInteractionJankMonitor = interactionJankMonitor;
mLatencyTracker = latencyTracker;
- mRingerModeTracker = ringerModeTracker;
mStatusBarStateController = statusBarStateController;
mStatusBarStateController.addCallback(mStatusBarStateControllerListener);
mStatusBarState = mStatusBarStateController.getState();
@@ -1862,9 +1841,6 @@
case MSG_SIM_STATE_CHANGE:
handleSimStateChange(msg.arg1, msg.arg2, (int) msg.obj);
break;
- case MSG_RINGER_MODE_CHANGED:
- handleRingerModeChange(msg.arg1);
- break;
case MSG_PHONE_STATE_CHANGED:
handlePhoneStateChanged((String) msg.obj);
break;
@@ -2006,8 +1982,6 @@
}
});
- mHandler.post(this::registerRingerTracker);
-
final IntentFilter allUserFilter = new IntentFilter();
allUserFilter.addAction(Intent.ACTION_USER_INFO_CHANGED);
allUserFilter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
@@ -2117,10 +2091,6 @@
false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
}
- private void updateUdfpsEnrolled(int userId) {
- mIsUdfpsEnrolled = mAuthController.isUdfpsEnrolled(userId);
- }
-
private void updateFaceEnrolled(int userId) {
mIsFaceEnrolled = whitelistIpcs(
() -> mFaceManager != null && mFaceManager.isHardwareDetected()
@@ -2132,7 +2102,7 @@
* @return true if there's at least one udfps enrolled for the current user.
*/
public boolean isUdfpsEnrolled() {
- return mIsUdfpsEnrolled;
+ return mAuthController.isUdfpsEnrolled(getCurrentUser());
}
/**
@@ -2186,7 +2156,6 @@
return;
}
- updateUdfpsEnrolled(getCurrentUser());
final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsSupported());
final boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING
|| mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING;
@@ -2750,12 +2719,6 @@
Assert.isMainThread();
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
updateSecondaryLockscreenRequirement(userId);
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onDevicePolicyManagerStateChanged();
- }
- }
}
/**
@@ -2834,21 +2797,6 @@
}
/**
- * Handle {@link #MSG_RINGER_MODE_CHANGED}
- */
- private void handleRingerModeChange(int mode) {
- Assert.isMainThread();
- if (DEBUG) Log.d(TAG, "handleRingerModeChange(" + mode + ")");
- mRingMode = mode;
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onRingerModeChanged(mode);
- }
- }
- }
-
- /**
* Handle {@link #MSG_TIME_UPDATE}
*/
private void handleTimeUpdate() {
@@ -3235,7 +3183,6 @@
// Notify listener of the current state
callback.onRefreshBatteryInfo(mBatteryStatus);
callback.onTimeChanged();
- callback.onRingerModeChanged(mRingMode);
callback.onPhoneStateChanged(mPhoneState);
callback.onRefreshCarrierInfo();
callback.onClockVisibilityChanged();
@@ -3546,7 +3493,6 @@
mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
mBroadcastDispatcher.unregisterReceiver(mBroadcastAllReceiver);
- mRingerModeTracker.getRingerMode().removeObserver(mRingerModeObserver);
mLockPatternUtils.unregisterStrongAuthTracker(mStrongAuthTracker);
mTrustManager.unregisterTrustListener(this);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 47e1035..8d5603d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -15,10 +15,7 @@
*/
package com.android.keyguard;
-import android.app.admin.DevicePolicyManager;
-import android.graphics.Bitmap;
import android.hardware.biometrics.BiometricSourceType;
-import android.media.AudioManager;
import android.os.SystemClock;
import android.telephony.TelephonyManager;
import android.view.WindowManagerPolicyConstants;
@@ -70,13 +67,6 @@
public void onRefreshCarrierInfo() { }
/**
- * Called when the ringer mode changes.
- * @param state the current ringer state, as defined in
- * {@link AudioManager#RINGER_MODE_CHANGED_ACTION}
- */
- public void onRingerModeChanged(int state) { }
-
- /**
* Called when the phone state changes. String will be one of:
* {@link TelephonyManager#EXTRA_STATE_IDLE}
* {@link TelephonyManager@EXTRA_STATE_RINGING}
@@ -124,12 +114,6 @@
public void onDeviceProvisioned() { }
/**
- * Called when the device policy changes.
- * See {@link DevicePolicyManager#ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED}
- */
- public void onDevicePolicyManagerStateChanged() { }
-
- /**
* Called when the user change begins.
*/
public void onUserSwitching(int userId) { }
@@ -168,14 +152,7 @@
public void onEmergencyCallAction() { }
/**
- * Called when the transport background changes.
- * @param bitmap
- */
- public void onSetBackground(Bitmap bitmap) {
- }
-
- /**
- * Called when the device has started waking up.
+ * Called when the device has started waking up and after biometric states are updated.
*
* @deprecated use {@link com.android.systemui.keyguard.WakefulnessLifecycle}.
*/
@@ -183,7 +160,8 @@
public void onStartedWakingUp() { }
/**
- * Called when the device has started going to sleep.
+ * Called when the device has started going to sleep and after biometric recognized
+ * states are reset.
* @param why see {@link #onFinishedGoingToSleep(int)}
*
* @deprecated use {@link com.android.systemui.keyguard.WakefulnessLifecycle}.
@@ -304,7 +282,6 @@
*/
public void onTrustAgentErrorMessage(CharSequence message) { }
-
/**
* Called when a value of logout enabled is change.
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 80a3a0e..4ad5183 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -433,7 +433,6 @@
public void onDozingChanged(boolean isDozing) {
mIsDozing = isDozing;
updateBurnInOffsets();
- updateIsUdfpsEnrolled();
updateVisibility();
}
@@ -513,7 +512,6 @@
mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(
KeyguardUpdateMonitor.getCurrentUser());
}
- updateIsUdfpsEnrolled();
updateVisibility();
}
diff --git a/packages/SystemUI/src/com/android/systemui/DarkReceiverImpl.kt b/packages/SystemUI/src/com/android/systemui/DarkReceiverImpl.kt
index 42d38cb..13d96e4 100644
--- a/packages/SystemUI/src/com/android/systemui/DarkReceiverImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/DarkReceiverImpl.kt
@@ -32,11 +32,11 @@
private val dualToneHandler = DualToneHandler(context)
init {
- onDarkChanged(Rect(), 1f, DarkIconDispatcher.DEFAULT_ICON_TINT)
+ onDarkChanged(ArrayList<Rect>(), 1f, DarkIconDispatcher.DEFAULT_ICON_TINT)
}
- override fun onDarkChanged(area: Rect?, darkIntensity: Float, tint: Int) {
- val intensity = if (DarkIconDispatcher.isInArea(area, this)) darkIntensity else 0f
+ override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
+ val intensity = if (DarkIconDispatcher.isInAreas(areas, this)) darkIntensity else 0f
setBackgroundColor(dualToneHandler.getSingleColor(intensity))
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
index c7f1006..95f666c 100644
--- a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
@@ -60,6 +60,7 @@
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PluginInflateContainer);
String viewType = a.getString(R.styleable.PluginInflateContainer_viewType);
+ a.recycle();
try {
mClass = (Class<ViewProvider>) Class.forName(viewType);
} catch (Exception e) {
diff --git a/packages/SystemUI/src/com/android/systemui/ResizingSpace.java b/packages/SystemUI/src/com/android/systemui/ResizingSpace.java
index c2bc53e..bb4176d 100644
--- a/packages/SystemUI/src/com/android/systemui/ResizingSpace.java
+++ b/packages/SystemUI/src/com/android/systemui/ResizingSpace.java
@@ -35,6 +35,7 @@
TypedArray a = context.obtainStyledAttributes(attrs, android.R.styleable.ViewGroup_Layout);
mWidth = a.getResourceId(android.R.styleable.ViewGroup_Layout_layout_width, 0);
mHeight = a.getResourceId(android.R.styleable.ViewGroup_Layout_layout_height, 0);
+ a.recycle();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 5bdee2a..23ca923 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -49,8 +49,11 @@
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.inject.Provider;
/**
* Application class for SystemUI.
@@ -181,17 +184,16 @@
*/
public void startServicesIfNeeded() {
- final String[] names = SystemUIFactory.getInstance()
- .getSystemUIServiceComponents(getResources());
- final String[] additionalNames = SystemUIFactory.getInstance()
- .getAdditionalSystemUIServiceComponents(getResources());
+ final String vendorComponent = SystemUIFactory.getInstance()
+ .getVendorComponent(getResources());
- final ArrayList<String> serviceComponents = new ArrayList<>();
- Collections.addAll(serviceComponents, names);
- Collections.addAll(serviceComponents, additionalNames);
-
- startServicesIfNeeded(/* metricsPrefix= */ "StartServices",
- serviceComponents.toArray(new String[serviceComponents.size()]));
+ // Sort the startables so that we get a deterministic ordering.
+ // TODO: make #start idempotent and require users of CoreStartable to call it.
+ Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>(
+ Comparator.comparing(Class::getName));
+ sortedStartables.putAll(SystemUIFactory.getInstance().getStartableComponents());
+ startServicesIfNeeded(
+ sortedStartables, "StartServices", vendorComponent);
}
/**
@@ -201,16 +203,22 @@
* <p>This method must only be called from the main thread.</p>
*/
void startSecondaryUserServicesIfNeeded() {
- String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponentsPerUser(
- getResources());
- startServicesIfNeeded(/* metricsPrefix= */ "StartSecondaryServices", names);
+ // Sort the startables so that we get a deterministic ordering.
+ Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>(
+ Comparator.comparing(Class::getName));
+ sortedStartables.putAll(SystemUIFactory.getInstance().getStartableComponentsPerUser());
+ startServicesIfNeeded(
+ sortedStartables, "StartSecondaryServices", null);
}
- private void startServicesIfNeeded(String metricsPrefix, String[] services) {
+ private void startServicesIfNeeded(
+ Map<Class<?>, Provider<CoreStartable>> startables,
+ String metricsPrefix,
+ String vendorComponent) {
if (mServicesStarted) {
return;
}
- mServices = new CoreStartable[services.length];
+ mServices = new CoreStartable[startables.size() + (vendorComponent == null ? 0 : 1)];
if (!mBootCompleteCache.isBootComplete()) {
// check to see if maybe it was already completed long before we began
@@ -230,36 +238,29 @@
TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",
Trace.TRACE_TAG_APP);
log.traceBegin(metricsPrefix);
- final int N = services.length;
- for (int i = 0; i < N; i++) {
- String clsName = services[i];
- if (DEBUG) Log.d(TAG, "loading: " + clsName);
- log.traceBegin(metricsPrefix + clsName);
- long ti = System.currentTimeMillis();
- try {
- CoreStartable obj = mComponentHelper.resolveCoreStartable(clsName);
- if (obj == null) {
- Constructor constructor = Class.forName(clsName).getConstructor(Context.class);
- obj = (CoreStartable) constructor.newInstance(this);
- }
- mServices[i] = obj;
- } catch (ClassNotFoundException
- | NoSuchMethodException
- | IllegalAccessException
- | InstantiationException
- | InvocationTargetException ex) {
- throw new RuntimeException(ex);
- }
- if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
- mServices[i].start();
- log.traceEnd();
+ int i = 0;
+ for (Map.Entry<Class<?>, Provider<CoreStartable>> entry : startables.entrySet()) {
+ String clsName = entry.getKey().getName();
+ int j = i; // Copied to make lambda happy.
+ timeInitialization(
+ clsName,
+ () -> mServices[j] = startStartable(clsName, entry.getValue()),
+ log,
+ metricsPrefix);
+ i++;
+ }
- // Warn if initialization of component takes too long
- ti = System.currentTimeMillis() - ti;
- if (ti > 1000) {
- Log.w(TAG, "Initialization of " + clsName + " took " + ti + " ms");
- }
+ if (vendorComponent != null) {
+ timeInitialization(
+ vendorComponent,
+ () -> mServices[mServices.length - 1] =
+ startAdditionalStartable(vendorComponent),
+ log,
+ metricsPrefix);
+ }
+
+ for (i = 0; i < mServices.length; i++) {
if (mBootCompleteCache.isBootComplete()) {
mServices[i].onBootCompleted();
}
@@ -272,6 +273,50 @@
mServicesStarted = true;
}
+ private void timeInitialization(String clsName, Runnable init, TimingsTraceLog log,
+ String metricsPrefix) {
+ long ti = System.currentTimeMillis();
+ log.traceBegin(metricsPrefix + " " + clsName);
+ init.run();
+ log.traceEnd();
+
+ // Warn if initialization of component takes too long
+ ti = System.currentTimeMillis() - ti;
+ if (ti > 1000) {
+ Log.w(TAG, "Initialization of " + clsName + " took " + ti + " ms");
+ }
+ }
+
+ private CoreStartable startAdditionalStartable(String clsName) {
+ CoreStartable startable;
+ if (DEBUG) Log.d(TAG, "loading: " + clsName);
+ try {
+ Constructor<?> constructor = Class.forName(clsName).getConstructor(
+ Context.class);
+ startable = (CoreStartable) constructor.newInstance(this);
+ } catch (ClassNotFoundException
+ | NoSuchMethodException
+ | IllegalAccessException
+ | InstantiationException
+ | InvocationTargetException ex) {
+ throw new RuntimeException(ex);
+ }
+
+ return startStartable(startable);
+ }
+
+ private CoreStartable startStartable(String clsName, Provider<CoreStartable> provider) {
+ if (DEBUG) Log.d(TAG, "loading: " + clsName);
+ return startStartable(provider.get());
+ }
+
+ private CoreStartable startStartable(CoreStartable startable) {
+ if (DEBUG) Log.d(TAG, "running: " + startable);
+ startable.start();
+
+ return startable;
+ }
+
// TODO(b/217567642): add unit tests? There doesn't seem to be a SystemUiApplicationTest...
@Override
public boolean addDumpable(Dumpable dumpable) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index b3be877..11fffd0 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -32,10 +32,13 @@
import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider;
import com.android.wm.shell.transition.ShellTransitions;
+import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
+import javax.inject.Provider;
+
/**
* Class factory to provide customizable SystemUI components.
*/
@@ -190,24 +193,24 @@
}
/**
- * Returns the list of system UI components that should be started.
+ * Returns the list of {@link CoreStartable} components that should be started at startup.
*/
- public String[] getSystemUIServiceComponents(Resources resources) {
- return resources.getStringArray(R.array.config_systemUIServiceComponents);
+ public Map<Class<?>, Provider<CoreStartable>> getStartableComponents() {
+ return mSysUIComponent.getStartables();
}
/**
* Returns the list of additional system UI components that should be started.
*/
- public String[] getAdditionalSystemUIServiceComponents(Resources resources) {
- return resources.getStringArray(R.array.config_additionalSystemUIServiceComponents);
+ public String getVendorComponent(Resources resources) {
+ return resources.getString(R.string.config_systemUIVendorServiceComponent);
}
/**
- * Returns the list of system UI components that should be started per user.
+ * Returns the list of {@link CoreStartable} components that should be started per user.
*/
- public String[] getSystemUIServiceComponentsPerUser(Resources resources) {
- return resources.getStringArray(R.array.config_systemUIServiceComponentsPerUser);
+ public Map<Class<?>, Provider<CoreStartable>> getStartableComponentsPerUser() {
+ return mSysUIComponent.getPerUserStartables();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index f8e7697..2b0c083 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -56,6 +56,7 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.text.NumberFormat;
+import java.util.ArrayList;
public class BatteryMeterView extends LinearLayout implements DarkReceiver {
@@ -125,7 +126,7 @@
updateShowPercent();
mDualToneHandler = new DualToneHandler(context);
// Init to not dark at all.
- onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
+ onDarkChanged(new ArrayList<Rect>(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
setClipChildren(false);
setClipToPadding(false);
@@ -353,8 +354,8 @@
}
@Override
- public void onDarkChanged(Rect area, float darkIntensity, int tint) {
- float intensity = DarkIconDispatcher.isInArea(area, this) ? darkIntensity : 0;
+ public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+ float intensity = DarkIconDispatcher.isInAreas(areas, this) ? darkIntensity : 0;
mNonAdaptedSingleToneColor = mDualToneHandler.getSingleColor(intensity);
mNonAdaptedForegroundColor = mDualToneHandler.getFillColor(intensity);
mNonAdaptedBackgroundColor = mDualToneHandler.getBackgroundColor(intensity);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 72b40d4..54664f2 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -48,7 +48,7 @@
@Override
public void start() {
if (DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, false)) {
+ DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, true)) {
mClipboardManager = requireNonNull(mContext.getSystemService(ClipboardManager.class));
mClipboardManager.addPrimaryClipChangedListener(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 236c43b..40689ee 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -262,7 +262,8 @@
resetActionChips();
for (RemoteAction action : actions) {
Intent targetIntent = action.getActionIntent().getIntent();
- if (!TextUtils.equals(source, targetIntent.getComponent().getPackageName())) {
+ ComponentName component = targetIntent.getComponent();
+ if (component != null && !TextUtils.equals(source, component.getPackageName())) {
OverlayActionChip chip = constructActionChip(action);
mActionContainer.addView(chip);
mActionChips.add(chip);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java
index f53221c..e868d43 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java
@@ -20,7 +20,6 @@
import android.app.Service;
import android.content.BroadcastReceiver;
-import com.android.systemui.CoreStartable;
import com.android.systemui.recents.RecentsImplementation;
/**
@@ -37,8 +36,5 @@
Service resolveService(String className);
/** Turns a classname into an instance of the class or returns null. */
- CoreStartable resolveCoreStartable(String className);
-
- /** Turns a classname into an instance of the class or returns null. */
BroadcastReceiver resolveBroadcastReceiver(String className);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java
index fba8d35..3607e25 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java
@@ -20,7 +20,6 @@
import android.app.Service;
import android.content.BroadcastReceiver;
-import com.android.systemui.CoreStartable;
import com.android.systemui.recents.RecentsImplementation;
import java.util.Map;
@@ -35,19 +34,16 @@
public class ContextComponentResolver implements ContextComponentHelper {
private final Map<Class<?>, Provider<Activity>> mActivityCreators;
private final Map<Class<?>, Provider<Service>> mServiceCreators;
- private final Map<Class<?>, Provider<CoreStartable>> mSystemUICreators;
private final Map<Class<?>, Provider<RecentsImplementation>> mRecentsCreators;
private final Map<Class<?>, Provider<BroadcastReceiver>> mBroadcastReceiverCreators;
@Inject
ContextComponentResolver(Map<Class<?>, Provider<Activity>> activityCreators,
Map<Class<?>, Provider<Service>> serviceCreators,
- Map<Class<?>, Provider<CoreStartable>> systemUICreators,
Map<Class<?>, Provider<RecentsImplementation>> recentsCreators,
Map<Class<?>, Provider<BroadcastReceiver>> broadcastReceiverCreators) {
mActivityCreators = activityCreators;
mServiceCreators = serviceCreators;
- mSystemUICreators = systemUICreators;
mRecentsCreators = recentsCreators;
mBroadcastReceiverCreators = broadcastReceiverCreators;
}
@@ -84,14 +80,6 @@
return resolve(className, mServiceCreators);
}
- /**
- * Looks up the SystemUI class name to see if Dagger has an instance of it.
- */
- @Override
- public CoreStartable resolveCoreStartable(String className) {
- return resolve(className, mSystemUICreators);
- }
-
private <T> T resolve(String className, Map<Class<?>, Provider<T>> creators) {
try {
Class<?> clazz = Class.forName(className);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
index 33f07c7..e1cbdcd 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
@@ -25,6 +25,7 @@
import com.android.systemui.dump.SystemUIAuxiliaryDumpService;
import com.android.systemui.keyguard.KeyguardService;
import com.android.systemui.screenrecord.RecordingService;
+import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
import dagger.Binds;
import dagger.Module;
@@ -63,6 +64,13 @@
/** */
@Binds
@IntoMap
+ @ClassKey(NotificationListenerWithPlugins.class)
+ public abstract Service bindNotificationListenerWithPlugins(
+ NotificationListenerWithPlugins service);
+
+ /** */
+ @Binds
+ @IntoMap
@ClassKey(SystemUIService.class)
public abstract Service bindSystemUIService(SystemUIService service);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index bda8e3c..81fa99a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -18,11 +18,14 @@
import com.android.keyguard.clock.ClockOptionsProvider;
import com.android.systemui.BootCompleteCacheImpl;
+import com.android.systemui.CoreStartable;
import com.android.systemui.Dependency;
import com.android.systemui.InitController;
import com.android.systemui.SystemUIAppComponentFactory;
+import com.android.systemui.dagger.qualifiers.PerUser;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
@@ -51,8 +54,11 @@
import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
import com.android.wm.shell.transition.ShellTransitions;
+import java.util.Map;
import java.util.Optional;
+import javax.inject.Provider;
+
import dagger.BindsInstance;
import dagger.Subcomponent;
@@ -65,6 +71,7 @@
DependencyProvider.class,
SystemUIBinder.class,
SystemUIModule.class,
+ SystemUICoreStartableModule.class,
SystemUIDefaultModule.class})
public interface SysUIComponent {
@@ -144,6 +151,7 @@
getMediaTttChipControllerSender();
getMediaTttChipControllerReceiver();
getMediaTttCommandLineHelper();
+ getMediaMuteAwaitConnectionCli();
getUnfoldLatencyTracker().init();
getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
getFoldStateLogger().ifPresent(FoldStateLogger::init);
@@ -220,6 +228,19 @@
/** */
Optional<MediaTttCommandLineHelper> getMediaTttCommandLineHelper();
+ /** */
+ Optional<MediaMuteAwaitConnectionCli> getMediaMuteAwaitConnectionCli();
+
+ /**
+ * Returns {@link CoreStartable}s that should be started with the application.
+ */
+ Map<Class<?>, Provider<CoreStartable>> getStartables();
+
+ /**
+ * Returns {@link CoreStartable}s that should be started for every user.
+ */
+ @PerUser Map<Class<?>, Provider<CoreStartable>> getPerUserStartables();
+
/**
* Member injection into the supplied argument.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index ec2beb1..b32f878 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -16,46 +16,11 @@
package com.android.systemui.dagger;
-import com.android.keyguard.KeyguardBiometricLockoutLogger;
-import com.android.systemui.CoreStartable;
-import com.android.systemui.LatencyTester;
-import com.android.systemui.ScreenDecorations;
-import com.android.systemui.SliceBroadcastRelayHandler;
-import com.android.systemui.accessibility.SystemActions;
-import com.android.systemui.accessibility.WindowMagnification;
-import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.clipboardoverlay.ClipboardListener;
-import com.android.systemui.dreams.DreamOverlayRegistrant;
-import com.android.systemui.dreams.SmartSpaceComplication;
-import com.android.systemui.dreams.complication.DreamClockDateComplication;
-import com.android.systemui.dreams.complication.DreamClockTimeComplication;
-import com.android.systemui.dreams.complication.DreamWeatherComplication;
-import com.android.systemui.globalactions.GlobalActionsComponent;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.dagger.KeyguardModule;
-import com.android.systemui.log.SessionTracker;
-import com.android.systemui.media.dream.MediaDreamSentinel;
-import com.android.systemui.media.systemsounds.HomeSoundEffectController;
-import com.android.systemui.power.PowerUI;
-import com.android.systemui.privacy.television.TvOngoingPrivacyChip;
-import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsModule;
-import com.android.systemui.shortcut.ShortcutKeyDispatcher;
import com.android.systemui.statusbar.dagger.StatusBarModule;
-import com.android.systemui.statusbar.notification.InstantAppNotifier;
-import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.tv.TvStatusBar;
-import com.android.systemui.statusbar.tv.notifications.TvNotificationPanel;
-import com.android.systemui.theme.ThemeOverlayController;
-import com.android.systemui.toast.ToastUI;
-import com.android.systemui.util.leak.GarbageMonitor;
-import com.android.systemui.volume.VolumeUI;
-import com.android.systemui.wmshell.WMShell;
-import dagger.Binds;
import dagger.Module;
-import dagger.multibindings.ClassKey;
-import dagger.multibindings.IntoMap;
/**
* SystemUI objects that are injectable should go here.
@@ -66,196 +31,4 @@
KeyguardModule.class,
})
public abstract class SystemUIBinder {
- /** Inject into AuthController. */
- @Binds
- @IntoMap
- @ClassKey(AuthController.class)
- public abstract CoreStartable bindAuthController(AuthController service);
-
- /** Inject into SessionTracker. */
- @Binds
- @IntoMap
- @ClassKey(SessionTracker.class)
- public abstract CoreStartable bindSessionTracker(SessionTracker service);
-
- /** Inject into GarbageMonitor.Service. */
- @Binds
- @IntoMap
- @ClassKey(GarbageMonitor.Service.class)
- public abstract CoreStartable bindGarbageMonitorService(GarbageMonitor.Service sysui);
-
- /** Inject into ClipboardListener. */
- @Binds
- @IntoMap
- @ClassKey(ClipboardListener.class)
- public abstract CoreStartable bindClipboardListener(ClipboardListener sysui);
-
- /** Inject into GlobalActionsComponent. */
- @Binds
- @IntoMap
- @ClassKey(GlobalActionsComponent.class)
- public abstract CoreStartable bindGlobalActionsComponent(GlobalActionsComponent sysui);
-
- /** Inject into InstantAppNotifier. */
- @Binds
- @IntoMap
- @ClassKey(InstantAppNotifier.class)
- public abstract CoreStartable bindInstantAppNotifier(InstantAppNotifier sysui);
-
- /** Inject into KeyguardViewMediator. */
- @Binds
- @IntoMap
- @ClassKey(KeyguardViewMediator.class)
- public abstract CoreStartable bindKeyguardViewMediator(KeyguardViewMediator sysui);
-
- /** Inject into KeyguardBiometricLockoutLogger. */
- @Binds
- @IntoMap
- @ClassKey(KeyguardBiometricLockoutLogger.class)
- public abstract CoreStartable bindKeyguardBiometricLockoutLogger(
- KeyguardBiometricLockoutLogger sysui);
-
- /** Inject into LatencyTests. */
- @Binds
- @IntoMap
- @ClassKey(LatencyTester.class)
- public abstract CoreStartable bindLatencyTester(LatencyTester sysui);
-
- /** Inject into PowerUI. */
- @Binds
- @IntoMap
- @ClassKey(PowerUI.class)
- public abstract CoreStartable bindPowerUI(PowerUI sysui);
-
- /** Inject into Recents. */
- @Binds
- @IntoMap
- @ClassKey(Recents.class)
- public abstract CoreStartable bindRecents(Recents sysui);
-
- /** Inject into ScreenDecorations. */
- @Binds
- @IntoMap
- @ClassKey(ScreenDecorations.class)
- public abstract CoreStartable bindScreenDecorations(ScreenDecorations sysui);
-
- /** Inject into ShortcutKeyDispatcher. */
- @Binds
- @IntoMap
- @ClassKey(ShortcutKeyDispatcher.class)
- public abstract CoreStartable bindsShortcutKeyDispatcher(ShortcutKeyDispatcher sysui);
-
- /** Inject into SliceBroadcastRelayHandler. */
- @Binds
- @IntoMap
- @ClassKey(SliceBroadcastRelayHandler.class)
- public abstract CoreStartable bindSliceBroadcastRelayHandler(SliceBroadcastRelayHandler sysui);
-
- /** Inject into StatusBar. */
- @Binds
- @IntoMap
- @ClassKey(StatusBar.class)
- public abstract CoreStartable bindsStatusBar(StatusBar sysui);
-
- /** Inject into SystemActions. */
- @Binds
- @IntoMap
- @ClassKey(SystemActions.class)
- public abstract CoreStartable bindSystemActions(SystemActions sysui);
-
- /** Inject into ThemeOverlayController. */
- @Binds
- @IntoMap
- @ClassKey(ThemeOverlayController.class)
- public abstract CoreStartable bindThemeOverlayController(ThemeOverlayController sysui);
-
- /** Inject into ToastUI. */
- @Binds
- @IntoMap
- @ClassKey(ToastUI.class)
- public abstract CoreStartable bindToastUI(ToastUI service);
-
- /** Inject into TvStatusBar. */
- @Binds
- @IntoMap
- @ClassKey(TvStatusBar.class)
- public abstract CoreStartable bindsTvStatusBar(TvStatusBar sysui);
-
- /** Inject into TvNotificationPanel. */
- @Binds
- @IntoMap
- @ClassKey(TvNotificationPanel.class)
- public abstract CoreStartable bindsTvNotificationPanel(TvNotificationPanel sysui);
-
- /** Inject into TvOngoingPrivacyChip. */
- @Binds
- @IntoMap
- @ClassKey(TvOngoingPrivacyChip.class)
- public abstract CoreStartable bindsTvOngoingPrivacyChip(TvOngoingPrivacyChip sysui);
-
- /** Inject into VolumeUI. */
- @Binds
- @IntoMap
- @ClassKey(VolumeUI.class)
- public abstract CoreStartable bindVolumeUI(VolumeUI sysui);
-
- /** Inject into WindowMagnification. */
- @Binds
- @IntoMap
- @ClassKey(WindowMagnification.class)
- public abstract CoreStartable bindWindowMagnification(WindowMagnification sysui);
-
- /** Inject into WMShell. */
- @Binds
- @IntoMap
- @ClassKey(WMShell.class)
- public abstract CoreStartable bindWMShell(WMShell sysui);
-
- /** Inject into HomeSoundEffectController. */
- @Binds
- @IntoMap
- @ClassKey(HomeSoundEffectController.class)
- public abstract CoreStartable bindHomeSoundEffectController(HomeSoundEffectController sysui);
-
- /** Inject into DreamOverlay. */
- @Binds
- @IntoMap
- @ClassKey(DreamOverlayRegistrant.class)
- public abstract CoreStartable bindDreamOverlayRegistrant(
- DreamOverlayRegistrant dreamOverlayRegistrant);
-
- /** Inject into SmartSpaceComplication.Registrant */
- @Binds
- @IntoMap
- @ClassKey(SmartSpaceComplication.Registrant.class)
- public abstract CoreStartable bindSmartSpaceComplicationRegistrant(
- SmartSpaceComplication.Registrant registrant);
-
- /** Inject into MediaDreamSentinel. */
- @Binds
- @IntoMap
- @ClassKey(MediaDreamSentinel.class)
- public abstract CoreStartable bindMediaDreamSentinel(
- MediaDreamSentinel sentinel);
-
- /** Inject into DreamClockTimeComplication.Registrant */
- @Binds
- @IntoMap
- @ClassKey(DreamClockTimeComplication.Registrant.class)
- public abstract CoreStartable bindDreamClockTimeComplicationRegistrant(
- DreamClockTimeComplication.Registrant registrant);
-
- /** Inject into DreamClockDateComplication.Registrant */
- @Binds
- @IntoMap
- @ClassKey(DreamClockDateComplication.Registrant.class)
- public abstract CoreStartable bindDreamClockDateComplicationRegistrant(
- DreamClockDateComplication.Registrant registrant);
-
- /** Inject into DreamWeatherComplication.Registrant */
- @Binds
- @IntoMap
- @ClassKey(DreamWeatherComplication.Registrant.class)
- public abstract CoreStartable bindDreamWeatherComplicationRegistrant(
- DreamWeatherComplication.Registrant registrant);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
new file mode 100644
index 0000000..f78929f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dagger
+
+import com.android.keyguard.KeyguardBiometricLockoutLogger
+import com.android.systemui.CoreStartable
+import com.android.systemui.LatencyTester
+import com.android.systemui.ScreenDecorations
+import com.android.systemui.SliceBroadcastRelayHandler
+import com.android.systemui.accessibility.SystemActions
+import com.android.systemui.accessibility.WindowMagnification
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.clipboardoverlay.ClipboardListener
+import com.android.systemui.dagger.qualifiers.PerUser
+import com.android.systemui.globalactions.GlobalActionsComponent
+import com.android.systemui.keyboard.KeyboardUI
+import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.log.SessionTracker
+import com.android.systemui.media.RingtonePlayer
+import com.android.systemui.power.PowerUI
+import com.android.systemui.recents.Recents
+import com.android.systemui.shortcut.ShortcutKeyDispatcher
+import com.android.systemui.statusbar.notification.InstantAppNotifier
+import com.android.systemui.theme.ThemeOverlayController
+import com.android.systemui.toast.ToastUI
+import com.android.systemui.usb.StorageNotification
+import com.android.systemui.util.NotificationChannels
+import com.android.systemui.util.leak.GarbageMonitor
+import com.android.systemui.volume.VolumeUI
+import com.android.systemui.wmshell.WMShell
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+/**
+ * Collection of {@link CoreStartable}s that should be run on AOSP.
+ */
+@Module
+abstract class SystemUICoreStartableModule {
+ /** Inject into AuthController. */
+ @Binds
+ @IntoMap
+ @ClassKey(AuthController::class)
+ abstract fun bindAuthController(service: AuthController): CoreStartable
+
+ /** Inject into ClipboardListener. */
+ @Binds
+ @IntoMap
+ @ClassKey(ClipboardListener::class)
+ abstract fun bindClipboardListener(sysui: ClipboardListener): CoreStartable
+
+ /** Inject into GarbageMonitor.Service. */
+ @Binds
+ @IntoMap
+ @ClassKey(GarbageMonitor::class)
+ abstract fun bindGarbageMonitorService(sysui: GarbageMonitor.Service): CoreStartable
+
+ /** Inject into GlobalActionsComponent. */
+ @Binds
+ @IntoMap
+ @ClassKey(GlobalActionsComponent::class)
+ abstract fun bindGlobalActionsComponent(sysui: GlobalActionsComponent): CoreStartable
+
+ /** Inject into InstantAppNotifier. */
+ @Binds
+ @IntoMap
+ @ClassKey(InstantAppNotifier::class)
+ abstract fun bindInstantAppNotifier(sysui: InstantAppNotifier): CoreStartable
+
+ /** Inject into KeyboardUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(KeyboardUI::class)
+ abstract fun bindKeyboardUI(sysui: KeyboardUI): CoreStartable
+
+ /** Inject into KeyguardBiometricLockoutLogger */
+ @Binds
+ @IntoMap
+ @ClassKey(KeyguardBiometricLockoutLogger::class)
+ abstract fun bindKeyguardBiometricLockoutLogger(
+ sysui: KeyguardBiometricLockoutLogger
+ ): CoreStartable
+
+ /** Inject into KeyguardViewMediator. */
+ @Binds
+ @IntoMap
+ @ClassKey(KeyguardViewMediator::class)
+ abstract fun bindKeyguardViewMediator(sysui: KeyguardViewMediator): CoreStartable
+
+ /** Inject into LatencyTests. */
+ @Binds
+ @IntoMap
+ @ClassKey(LatencyTester::class)
+ abstract fun bindLatencyTester(sysui: LatencyTester): CoreStartable
+
+ /** Inject into NotificationChannels. */
+ @Binds
+ @IntoMap
+ @ClassKey(NotificationChannels::class)
+ @PerUser
+ abstract fun bindNotificationChannels(sysui: NotificationChannels): CoreStartable
+
+ /** Inject into PowerUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(PowerUI::class)
+ abstract fun bindPowerUI(sysui: PowerUI): CoreStartable
+
+ /** Inject into Recents. */
+ @Binds
+ @IntoMap
+ @ClassKey(Recents::class)
+ abstract fun bindRecents(sysui: Recents): CoreStartable
+
+ /** Inject into RingtonePlayer. */
+ @Binds
+ @IntoMap
+ @ClassKey(RingtonePlayer::class)
+ abstract fun bind(sysui: RingtonePlayer): CoreStartable
+
+ /** Inject into ScreenDecorations. */
+ @Binds
+ @IntoMap
+ @ClassKey(ScreenDecorations::class)
+ abstract fun bindScreenDecorations(sysui: ScreenDecorations): CoreStartable
+
+ /** Inject into SessionTracker. */
+ @Binds
+ @IntoMap
+ @ClassKey(SessionTracker::class)
+ abstract fun bindSessionTracker(service: SessionTracker): CoreStartable
+
+ /** Inject into ShortcutKeyDispatcher. */
+ @Binds
+ @IntoMap
+ @ClassKey(ShortcutKeyDispatcher::class)
+ abstract fun bindShortcutKeyDispatcher(sysui: ShortcutKeyDispatcher): CoreStartable
+
+ /** Inject into SliceBroadcastRelayHandler. */
+ @Binds
+ @IntoMap
+ @ClassKey(SliceBroadcastRelayHandler::class)
+ abstract fun bindSliceBroadcastRelayHandler(sysui: SliceBroadcastRelayHandler): CoreStartable
+
+ /** Inject into StorageNotification. */
+ @Binds
+ @IntoMap
+ @ClassKey(StorageNotification::class)
+ abstract fun bindStorageNotification(sysui: StorageNotification): CoreStartable
+
+ /** Inject into SystemActions. */
+ @Binds
+ @IntoMap
+ @ClassKey(SystemActions::class)
+ abstract fun bindSystemActions(sysui: SystemActions): CoreStartable
+
+ /** Inject into ThemeOverlayController. */
+ @Binds
+ @IntoMap
+ @ClassKey(ThemeOverlayController::class)
+ abstract fun bindThemeOverlayController(sysui: ThemeOverlayController): CoreStartable
+
+ /** Inject into ToastUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(ToastUI::class)
+ abstract fun bindToastUI(service: ToastUI): CoreStartable
+
+ /** Inject into VolumeUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(VolumeUI::class)
+ abstract fun bindVolumeUI(sysui: VolumeUI): CoreStartable
+
+ /** Inject into WindowMagnification. */
+ @Binds
+ @IntoMap
+ @ClassKey(WindowMagnification::class)
+ abstract fun bindWindowMagnification(sysui: WindowMagnification): CoreStartable
+
+ /** Inject into WMShell. */
+ @Binds
+ @IntoMap
+ @ClassKey(WMShell::class)
+ abstract fun bindWMShell(sysui: WMShell): CoreStartable
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
index a178738..a4da6b4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
@@ -48,6 +48,7 @@
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.dagger.StartStatusBarModule;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -86,6 +87,7 @@
MediaModule.class,
PowerModule.class,
QSModule.class,
+ StartStatusBarModule.class,
VolumeModule.class
})
public abstract class SystemUIDefaultModule {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/AdditionalStartable.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/AdditionalStartable.java
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/AdditionalStartable.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/PerUser.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/PerUser.java
new file mode 100644
index 0000000..f6d5ece
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/PerUser.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 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.dagger.qualifiers;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface PerUser {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 63d4d6b..a9e310d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -105,17 +105,7 @@
updateUdfpsController();
if (mUdfpsController == null) {
- mAuthController.addCallback(new AuthController.Callback() {
- @Override
- public void onAllAuthenticatorsRegistered() {
- updateUdfpsController();
- }
-
- @Override
- public void onEnrollmentsChanged() {
- updateUdfpsController();
- }
- });
+ mAuthController.addCallback(mAuthControllerCallback);
}
}
@@ -128,6 +118,11 @@
}
@Override
+ public void destroy() {
+ mAuthController.removeCallback(mAuthControllerCallback);
+ }
+
+ @Override
public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
int screenState = newState.screenState(mParameters);
mDozeHost.cancelGentleSleep();
@@ -234,4 +229,16 @@
mWakeLock.setAcquired(false);
}
}
+
+ private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
+ @Override
+ public void onAllAuthenticatorsRegistered() {
+ updateUdfpsController();
+ }
+
+ @Override
+ public void onEnrollmentsChanged() {
+ updateUdfpsController();
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 77997e4..4696eed 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -122,10 +122,12 @@
@Override
public void onDestroy() {
- mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
+ mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback);
setCurrentState(Lifecycle.State.DESTROYED);
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
- windowManager.removeView(mWindow.getDecorView());
+ if (mWindow != null) {
+ windowManager.removeView(mWindow.getDecorView());
+ }
mStateController.setOverlayActive(false);
super.onDestroy();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index fa951fa..e4a7406 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.os.SystemClock
import android.os.Trace
+import com.android.systemui.CoreStartable
import com.android.systemui.R
import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL
import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_HIGH
@@ -27,6 +28,7 @@
import java.io.FileDescriptor
import java.io.PrintWriter
import javax.inject.Inject
+import javax.inject.Provider
/**
* Oversees SystemUI's output during bug reports (and dumpsys in general)
@@ -80,7 +82,8 @@
class DumpHandler @Inject constructor(
private val context: Context,
private val dumpManager: DumpManager,
- private val logBufferEulogizer: LogBufferEulogizer
+ private val logBufferEulogizer: LogBufferEulogizer,
+ private val startables: MutableMap<Class<*>, Provider<CoreStartable>>
) {
/**
* Dump the diagnostics! Behavior can be controlled via [args].
@@ -173,12 +176,21 @@
pw.println("SystemUiServiceComponents configuration:")
pw.print("vendor component: ")
pw.println(context.resources.getString(R.string.config_systemUIVendorServiceComponent))
- dumpServiceList(pw, "global", R.array.config_systemUIServiceComponents)
+ val services: MutableList<String> = startables.keys
+ .map({ cls: Class<*> -> cls.simpleName })
+ .toMutableList()
+
+ services.add(context.resources.getString(R.string.config_systemUIVendorServiceComponent))
+ dumpServiceList(pw, "global", services.toTypedArray())
dumpServiceList(pw, "per-user", R.array.config_systemUIServiceComponentsPerUser)
}
private fun dumpServiceList(pw: PrintWriter, type: String, resId: Int) {
- val services: Array<String>? = context.resources.getStringArray(resId)
+ val services: Array<String> = context.resources.getStringArray(resId)
+ dumpServiceList(pw, type, services)
+ }
+
+ private fun dumpServiceList(pw: PrintWriter, type: String, services: Array<String>?) {
pw.print(type)
pw.print(": ")
if (services == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 51101da..1ba6e34 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -95,6 +95,10 @@
/***************************************/
// 500 - quick settings
+ /**
+ * @deprecated Not needed anymore
+ */
+ @Deprecated
public static final BooleanFlag NEW_USER_SWITCHER =
new BooleanFlag(500, true);
@@ -142,6 +146,12 @@
public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true);
public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, true);
public static final BooleanFlag MEDIA_SESSION_LAYOUT = new BooleanFlag(902, false);
+ public static final BooleanFlag MEDIA_MUTE_AWAIT = new BooleanFlag(904, true);
+
+ // 1000 - dock
+ public static final BooleanFlag SIMULATE_DOCK_THROUGH_CHARGING =
+ new BooleanFlag(1000, true);
+
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index 1c0b104..6f7e73f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -52,6 +52,7 @@
import com.android.systemui.CoreStartable;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -60,6 +61,10 @@
import java.util.List;
import java.util.Set;
+import javax.inject.Inject;
+
+/** */
+@SysUISingleton
public class KeyboardUI extends CoreStartable implements InputManager.OnTabletModeChangedListener {
private static final String TAG = "KeyboardUI";
private static final boolean DEBUG = false;
@@ -117,6 +122,7 @@
private int mState;
+ @Inject
public KeyboardUI(Context context) {
super(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 88555ed..b96eee7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -102,7 +102,7 @@
"persist.wm.enable_remote_keyguard_animation";
private static final int sEnableRemoteKeyguardAnimation =
- SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 1);
+ SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 2);
/**
* @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 0f08a18..ae7147e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -789,9 +789,10 @@
@Override
public int getBouncerPromptReason() {
int currentUser = KeyguardUpdateMonitor.getCurrentUser();
- boolean trust = mUpdateMonitor.isTrustUsuallyManaged(currentUser);
- boolean biometrics = mUpdateMonitor.isUnlockingWithBiometricsPossible(currentUser);
- boolean any = trust || biometrics;
+ boolean trustAgentsEnabled = mUpdateMonitor.isTrustUsuallyManaged(currentUser);
+ boolean biometricsEnrolled =
+ mUpdateMonitor.isUnlockingWithBiometricsPossible(currentUser);
+ boolean any = trustAgentsEnabled || biometricsEnrolled;
KeyguardUpdateMonitor.StrongAuthTracker strongAuthTracker =
mUpdateMonitor.getStrongAuthTracker();
int strongAuth = strongAuthTracker.getStrongAuthForUser(currentUser);
@@ -800,9 +801,10 @@
return KeyguardSecurityView.PROMPT_REASON_RESTART;
} else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_TIMEOUT) != 0) {
return KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
- } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) != 0) {
+ } else if ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) != 0) {
return KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
- } else if (trust && (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) {
+ } else if (trustAgentsEnabled
+ && (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) {
return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
} else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0
|| mUpdateMonitor.isFingerprintLockedOut())) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index b3e6682..ce7a697 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -371,13 +371,6 @@
// Output switcher chip
ViewGroup seamlessView = mMediaViewHolder.getSeamless();
seamlessView.setVisibility(View.VISIBLE);
- seamlessView.setOnClickListener(
- v -> {
- if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- mMediaOutputDialogFactory.create(data.getPackageName(), true,
- mMediaViewHolder.getSeamlessButton());
- }
- });
ImageView iconView = mMediaViewHolder.getSeamlessIcon();
TextView deviceName = mMediaViewHolder.getSeamlessText();
final MediaDeviceData device = data.getDevice();
@@ -387,8 +380,8 @@
final float seamlessAlpha = seamlessDisabled ? DISABLED_ALPHA : 1.0f;
mMediaViewHolder.getSeamlessButton().setAlpha(seamlessAlpha);
seamlessView.setEnabled(!seamlessDisabled);
- String deviceString = null;
- if (device != null && device.getEnabled()) {
+ CharSequence deviceString = mContext.getString(R.string.media_seamless_other_device);
+ if (device != null) {
Drawable icon = device.getIcon();
if (icon instanceof AdaptiveIcon) {
AdaptiveIcon aIcon = (AdaptiveIcon) icon;
@@ -399,13 +392,32 @@
}
deviceString = device.getName();
} else {
- // Reset to default
- Log.w(TAG, "Device is null or not enabled: " + device + ", not binding output chip.");
+ // Set to default icon
iconView.setImageResource(R.drawable.ic_media_home_devices);
- deviceString = mContext.getString(R.string.media_seamless_other_device);
}
deviceName.setText(deviceString);
seamlessView.setContentDescription(deviceString);
+ seamlessView.setOnClickListener(
+ v -> {
+ if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ return;
+ }
+ if (device.getIntent() != null) {
+ if (device.getIntent().isActivity()) {
+ mActivityStarter.startActivity(
+ device.getIntent().getIntent(), true);
+ } else {
+ try {
+ device.getIntent().send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "Device pending intent was canceled");
+ }
+ }
+ } else {
+ mMediaOutputDialogFactory.create(data.getPackageName(), true,
+ mMediaViewHolder.getSeamlessButton());
+ }
+ });
// Dismiss
mMediaViewHolder.getDismissText().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index 4b8dfde..500e82e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -164,8 +164,17 @@
)
/** State of the media device. */
-data class MediaDeviceData(
+data class MediaDeviceData
+@JvmOverloads constructor(
+ /** Whether or not to enable the chip */
val enabled: Boolean,
+
+ /** Device icon to show in the chip */
val icon: Drawable?,
- val name: String?
+
+ /** Device display name */
+ val name: CharSequence?,
+
+ /** Optional intent to override the default output switcher for this control */
+ val intent: PendingIntent? = null
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 240ca36..e1ff110 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -27,8 +27,6 @@
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
-import android.content.pm.ApplicationInfo
-import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.ImageDecoder
import android.graphics.drawable.Icon
@@ -129,7 +127,7 @@
private val useQsMediaPlayer: Boolean,
private val systemClock: SystemClock,
private val tunerService: TunerService,
- private val mediaFlags: MediaFlags,
+ private val mediaFlags: MediaFlags
) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
companion object {
@@ -170,20 +168,9 @@
/**
* Check whether this notification is an RCN
- * TODO(b/204910409) implement new API for explicitly declaring this
*/
private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean {
- val pm = context.packageManager
- try {
- val info = pm.getApplicationInfo(sbn.packageName, PackageManager.MATCH_SYSTEM_ONLY)
- if (info.privateFlags and ApplicationInfo.PRIVATE_FLAG_PRIVILEGED != 0) {
- val extras = sbn.notification.extras
- if (extras.containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME)) {
- return true
- }
- }
- } catch (e: PackageManager.NameNotFoundException) { }
- return false
+ return sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE)
}
@Inject
@@ -597,6 +584,25 @@
artist = HybridGroupManager.resolveText(notif)
}
+ // Device name (used for remote cast notifications)
+ var device: MediaDeviceData? = null
+ if (isRemoteCastNotification(sbn)) {
+ val extras = sbn.notification.extras
+ val deviceName = extras.getCharSequence(Notification.EXTRA_MEDIA_REMOTE_DEVICE, null)
+ val deviceIcon = extras.getInt(Notification.EXTRA_MEDIA_REMOTE_ICON, -1)
+ val deviceIntent = extras.getParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT)
+ as PendingIntent?
+ Log.d(TAG, "$key is RCN for $deviceName")
+
+ if (deviceName != null && deviceIcon > -1) {
+ // Name and icon must be present, but intent may be null
+ val enabled = deviceIntent != null && deviceIntent.isActivity
+ val deviceDrawable = Icon.createWithResource(sbn.packageName, deviceIcon)
+ .loadDrawable(sbn.getPackageContext(context))
+ device = MediaDeviceData(enabled, deviceDrawable, deviceName, deviceIntent)
+ }
+ }
+
// Control buttons
// If flag is enabled and controller has a PlaybackState, create actions from session info
// Otherwise, use the notification actions
@@ -624,7 +630,7 @@
val active = mediaEntries[key]?.active ?: true
onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app,
smallIcon, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed,
- semanticActions, sbn.packageName, token, notif.contentIntent, null,
+ semanticActions, sbn.packageName, token, notif.contentIntent, device,
active, resumeAction = resumeAction, playbackLocation = playbackLocation,
notificationKey = key, hasCheckedForResume = hasCheckedForResume,
isPlaying = isPlaying, isClearable = sbn.isClearable(),
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index bed254f..ffae898 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media
+import android.graphics.drawable.Drawable
import android.media.MediaRouter2Manager
import android.media.session.MediaController
import androidx.annotation.AnyThread
@@ -27,6 +28,8 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
+import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
import java.io.FileDescriptor
import java.io.PrintWriter
import java.util.concurrent.Executor
@@ -41,6 +44,7 @@
private val controllerFactory: MediaControllerFactory,
private val localMediaManagerFactory: LocalMediaManagerFactory,
private val mr2manager: MediaRouter2Manager,
+ private val muteAwaitConnectionManagerFactory: MediaMuteAwaitConnectionManagerFactory,
@Main private val fgExecutor: Executor,
@Background private val bgExecutor: Executor,
dumpManager: DumpManager
@@ -77,11 +81,25 @@
var entry = entries[key]
if (entry == null || entry?.token != data.token) {
entry?.stop()
+ if (data.device != null) {
+ // If we were already provided device info (e.g. from RCN), keep that and don't
+ // listen for updates, but process once to push updates to listeners
+ processDevice(key, oldKey, data.device)
+ return
+ }
val controller = data.token?.let {
controllerFactory.create(it)
}
- entry = Entry(key, oldKey, controller,
- localMediaManagerFactory.create(data.packageName))
+ val localMediaManager = localMediaManagerFactory.create(data.packageName)
+ val muteAwaitConnectionManager =
+ muteAwaitConnectionManagerFactory.create(localMediaManager)
+ entry = Entry(
+ key,
+ oldKey,
+ controller,
+ localMediaManager,
+ muteAwaitConnectionManager
+ )
entries[key] = entry
entry.start()
}
@@ -126,7 +144,8 @@
val key: String,
val oldKey: String?,
val controller: MediaController?,
- val localMediaManager: LocalMediaManager
+ val localMediaManager: LocalMediaManager,
+ val muteAwaitConnectionManager: MediaMuteAwaitConnectionManager?
) : LocalMediaManager.DeviceCallback, MediaController.Callback() {
val token
@@ -142,11 +161,15 @@
}
}
}
+ // A device that is not yet connected but is expected to connect imminently. Because it's
+ // expected to connect imminently, it should be displayed as the current device.
+ private var aboutToConnectDeviceOverride: MediaDeviceData? = null
@AnyThread
fun start() = bgExecutor.execute {
localMediaManager.registerCallback(this)
localMediaManager.startScan()
+ muteAwaitConnectionManager?.startListening()
playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
controller?.registerCallback(this)
updateCurrent()
@@ -159,6 +182,7 @@
controller?.unregisterCallback(this)
localMediaManager.stopScan()
localMediaManager.unregisterCallback(this)
+ muteAwaitConnectionManager?.stopListening()
}
fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
@@ -197,8 +221,21 @@
}
}
+ override fun onAboutToConnectDeviceChanged(deviceName: String?, deviceIcon: Drawable?) {
+ aboutToConnectDeviceOverride = if (deviceName == null || deviceIcon == null) {
+ null
+ } else {
+ MediaDeviceData(enabled = true, deviceIcon, deviceName)
+ }
+ updateCurrent()
+ }
+
@WorkerThread
private fun updateCurrent() {
+ if (aboutToConnectDeviceOverride != null) {
+ current = aboutToConnectDeviceOverride
+ return
+ }
val device = localMediaManager.currentConnectedDevice
val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
index b9795f1..e146768 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
@@ -37,4 +37,9 @@
return featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS) &&
featureFlags.isEnabled(Flags.MEDIA_SESSION_LAYOUT)
}
-}
\ No newline at end of file
+
+ /**
+ * Check whether we support displaying information about mute await connections.
+ */
+ fun areMuteAwaitConnectionsEnabled() = featureFlags.isEnabled(Flags.MEDIA_MUTE_AWAIT)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index ae5f9b6..4e35d16 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -38,16 +38,20 @@
import android.util.Log;
import com.android.systemui.CoreStartable;
+import com.android.systemui.dagger.SysUISingleton;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
+import javax.inject.Inject;
+
/**
* Service that offers to play ringtones by {@link Uri}, since our process has
* {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}.
*/
+@SysUISingleton
public class RingtonePlayer extends CoreStartable {
private static final String TAG = "RingtonePlayer";
private static final boolean LOGD = false;
@@ -59,6 +63,7 @@
private final NotificationPlayer mAsyncPlayer = new NotificationPlayer(TAG);
private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();
+ @Inject
public RingtonePlayer(Context context) {
super(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index f8b34f9..3225f73 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -18,15 +18,18 @@
import android.app.Service;
import android.content.Context;
+import android.os.Handler;
import android.view.WindowManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.media.MediaDataManager;
+import com.android.systemui.media.MediaFlags;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.media.MediaHostStatesManager;
import com.android.systemui.media.dream.dagger.MediaComplicationComponent;
+import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
import com.android.systemui.media.nearby.NearbyMediaDevicesService;
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.MediaTttFlags;
@@ -117,12 +120,14 @@
MediaTttFlags mediaTttFlags,
CommandQueue commandQueue,
Context context,
- WindowManager windowManager) {
+ WindowManager windowManager,
+ @Main Handler mainHandler) {
if (!mediaTttFlags.isMediaTttEnabled()) {
return Optional.empty();
}
return Optional.of(
- new MediaTttChipControllerReceiver(commandQueue, context, windowManager));
+ new MediaTttChipControllerReceiver(
+ commandQueue, context, windowManager, mainHandler));
}
/** */
@@ -140,6 +145,20 @@
new MediaTttCommandLineHelper(commandRegistry, context, mainExecutor));
}
+ /** */
+ @Provides
+ @SysUISingleton
+ static Optional<MediaMuteAwaitConnectionCli> providesMediaMuteAwaitConnectionCli(
+ MediaFlags mediaFlags,
+ CommandRegistry commandRegistry,
+ Context context
+ ) {
+ if (!mediaFlags.areMuteAwaitConnectionsEnabled()) {
+ return Optional.empty();
+ }
+ return Optional.of(new MediaMuteAwaitConnectionCli(commandRegistry, context));
+ }
+
/** Inject into NearbyMediaDevicesService. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index a9e9f0f..8731a2c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -153,6 +153,10 @@
@VisibleForTesting
void refresh() {
+ refresh(false);
+ }
+
+ void refresh(boolean deviceSetChanged) {
// Update header icon
final int iconRes = getHeaderIconRes();
final IconCompat iconCompat = getHeaderIcon();
@@ -190,7 +194,8 @@
}
if (!mAdapter.isDragging() && !mAdapter.isAnimating()) {
int currentActivePosition = mAdapter.getCurrentActivePosition();
- if (currentActivePosition >= 0 && currentActivePosition < mAdapter.getItemCount()) {
+ if (!deviceSetChanged && currentActivePosition >= 0
+ && currentActivePosition < mAdapter.getItemCount()) {
mAdapter.notifyItemChanged(currentActivePosition);
} else {
mAdapter.notifyDataSetChanged();
@@ -232,6 +237,11 @@
}
@Override
+ public void onDeviceListChanged() {
+ mMainThreadHandler.post(() -> refresh(true));
+ }
+
+ @Override
public void dismissDialog() {
dismiss();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 2caecf2..4961711 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -171,7 +171,7 @@
@Override
public void onDeviceListUpdate(List<MediaDevice> devices) {
buildMediaDevices(devices);
- mCallback.onRouteChanged();
+ mCallback.onDeviceListChanged();
}
@Override
@@ -570,11 +570,16 @@
void onMediaStoppedOrPaused();
/**
- * Override to handle the device updating.
+ * Override to handle the device status or attributes updating.
*/
void onRouteChanged();
/**
+ * Override to handle the devices set updating.
+ */
+ void onDeviceListChanged();
+
+ /**
* Override to dismiss dialog.
*/
void dismissDialog();
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt
new file mode 100644
index 0000000..2ae3a63
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.media.muteawait
+
+import android.content.Context
+import android.media.AudioAttributes.USAGE_MEDIA
+import android.media.AudioDeviceAttributes
+import android.media.AudioManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import java.io.PrintWriter
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+/** A command line interface to manually test [MediaMuteAwaitConnectionManager]. */
+@SysUISingleton
+class MediaMuteAwaitConnectionCli @Inject constructor(
+ commandRegistry: CommandRegistry,
+ private val context: Context
+) {
+ init {
+ commandRegistry.registerCommand(MEDIA_MUTE_AWAIT_COMMAND) { MuteAwaitCommand() }
+ }
+
+ inner class MuteAwaitCommand : Command {
+ override fun execute(pw: PrintWriter, args: List<String>) {
+ val device = AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ /* type= */ Integer.parseInt(args[0]),
+ ADDRESS,
+ /* name= */ args[1],
+ listOf(),
+ listOf(),
+ )
+ val startOrCancel = args[2]
+
+ val audioManager: AudioManager =
+ context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+ when (startOrCancel) {
+ START ->
+ audioManager.muteAwaitConnection(
+ intArrayOf(USAGE_MEDIA), device, TIMEOUT, TIMEOUT_UNITS
+ )
+ CANCEL -> audioManager.cancelMuteAwaitConnection(device)
+ else -> pw.println("Must specify `$START` or `$CANCEL`; was $startOrCancel")
+ }
+ }
+ override fun help(pw: PrintWriter) {
+ pw.println("Usage: adb shell cmd statusbar $MEDIA_MUTE_AWAIT_COMMAND " +
+ "[type] [name] [$START|$CANCEL]")
+ }
+ }
+}
+
+private const val MEDIA_MUTE_AWAIT_COMMAND = "media-mute-await"
+private const val START = "start"
+private const val CANCEL = "cancel"
+private const val ADDRESS = "address"
+private const val TIMEOUT = 5L
+private val TIMEOUT_UNITS = TimeUnit.SECONDS
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt
new file mode 100644
index 0000000..22bc557
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.media.muteawait
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.media.AudioAttributes.USAGE_MEDIA
+import android.media.AudioDeviceAttributes
+import android.media.AudioManager
+import com.android.settingslib.media.DeviceIconUtil
+import com.android.settingslib.media.LocalMediaManager
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
+
+/**
+ * A class responsible for keeping track of devices that have muted audio playback until the device
+ * is connected. The device connection expected to happen imminently, so we'd like to display the
+ * device name in the media player. When the about-to-connect device changes, [localMediaManager]
+ * will be notified.
+ *
+ * See [AudioManager.muteAwaitConnection] and b/206614671 for more details.
+ *
+ * TODO(b/206614671): Add logging.
+ */
+class MediaMuteAwaitConnectionManager constructor(
+ @Main private val mainExecutor: Executor,
+ private val localMediaManager: LocalMediaManager,
+ private val context: Context,
+ private val deviceIconUtil: DeviceIconUtil
+) {
+ var currentMutedDevice: AudioDeviceAttributes? = null
+
+ val audioManager: AudioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+
+ val muteAwaitConnectionChangeListener = object : AudioManager.MuteAwaitConnectionCallback() {
+ override fun onMutedUntilConnection(device: AudioDeviceAttributes, mutedUsages: IntArray) {
+ if (USAGE_MEDIA in mutedUsages) {
+ // There should only be one device that's mutedUntilConnection at a time, so we can
+ // safely override any previous value.
+ currentMutedDevice = device
+ localMediaManager.dispatchAboutToConnectDeviceChanged(device.name, device.getIcon())
+ }
+ }
+
+ override fun onUnmutedEvent(
+ @UnmuteEvent unmuteEvent: Int,
+ device: AudioDeviceAttributes,
+ mutedUsages: IntArray
+ ) {
+ if (currentMutedDevice == device && USAGE_MEDIA in mutedUsages) {
+ currentMutedDevice = null
+ localMediaManager.dispatchAboutToConnectDeviceChanged(null, null)
+ }
+ }
+ }
+
+ /** Start listening for mute await events. */
+ fun startListening() {
+ audioManager.registerMuteAwaitConnectionCallback(
+ mainExecutor, muteAwaitConnectionChangeListener
+ )
+ val currentDevice = audioManager.mutingExpectedDevice
+ if (currentDevice != null) {
+ currentMutedDevice = currentDevice
+ localMediaManager.dispatchAboutToConnectDeviceChanged(
+ currentDevice.name, currentDevice.getIcon()
+ )
+ }
+ }
+
+ /** Stop listening for mute await events. */
+ fun stopListening() {
+ audioManager.unregisterMuteAwaitConnectionCallback(muteAwaitConnectionChangeListener)
+ }
+
+ private fun AudioDeviceAttributes.getIcon(): Drawable {
+ return deviceIconUtil.getIconFromAudioDeviceType(this.type, context)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
new file mode 100644
index 0000000..118b2dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.media.muteawait
+
+import android.content.Context
+import com.android.settingslib.media.DeviceIconUtil
+import com.android.settingslib.media.LocalMediaManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.MediaFlags
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/** Factory class to create [MediaMuteAwaitConnectionManager] instances. */
+@SysUISingleton
+class MediaMuteAwaitConnectionManagerFactory @Inject constructor(
+ private val mediaFlags: MediaFlags,
+ private val context: Context,
+ @Main private val mainExecutor: Executor
+) {
+ private val deviceIconUtil = DeviceIconUtil()
+
+ /** Creates a [MediaMuteAwaitConnectionManager]. */
+ fun create(localMediaManager: LocalMediaManager): MediaMuteAwaitConnectionManager? {
+ if (!mediaFlags.areMuteAwaitConnectionsEnabled()) {
+ return null
+ }
+ return MediaMuteAwaitConnectionManager(
+ mainExecutor, localMediaManager, context, deviceIconUtil
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index 26f31cd..9dd8222 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -78,6 +78,7 @@
override fun execute(pw: PrintWriter, args: List<String>) {
val routeInfo = MediaRoute2Info.Builder("id", args[0])
.addFeature("feature")
+ .setPackageName(TEST_PACKAGE_NAME)
.build()
val commandName = args[1]
@@ -137,16 +138,25 @@
override fun execute(pw: PrintWriter, args: List<String>) {
val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE)
as StatusBarManager
+ val routeInfo = MediaRoute2Info.Builder("id", "Test Name")
+ .addFeature("feature")
+ .setPackageName(TEST_PACKAGE_NAME)
+ .build()
+
when(val commandName = args[0]) {
CLOSE_TO_SENDER_STATE ->
statusBarManager.updateMediaTapToTransferReceiverDisplay(
StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
- routeInfo
+ routeInfo,
+ null,
+ null
)
FAR_FROM_SENDER_STATE ->
statusBarManager.updateMediaTapToTransferReceiverDisplay(
StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
- routeInfo
+ routeInfo,
+ null,
+ null
)
else ->
pw.println("Invalid command name $commandName")
@@ -170,7 +180,4 @@
@VisibleForTesting
const val FAR_FROM_SENDER_STATE = "FarFromSender"
private const val CLI_TAG = "MediaTransferCli"
-
-private val routeInfo = MediaRoute2Info.Builder("id", "Test Name")
- .addFeature("feature")
- .build()
\ No newline at end of file
+private const val TEST_PACKAGE_NAME = "com.android.systemui"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index 2ed2f4f..4993105 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -22,6 +22,7 @@
import android.graphics.PixelFormat
import android.view.Gravity
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import com.android.internal.widget.CachingIconView
@@ -35,7 +36,7 @@
* gets displayed to the user.
*/
abstract class MediaTttChipControllerCommon<T : MediaTttChipState>(
- private val context: Context,
+ internal val context: Context,
private val windowManager: WindowManager,
@LayoutRes private val chipLayoutRes: Int
) {
@@ -100,10 +101,17 @@
* This is in the common superclass since both the sender and the receiver show an icon.
*/
internal fun setIcon(chipState: T, currentChipView: ViewGroup) {
- currentChipView.findViewById<CachingIconView>(R.id.app_icon).apply {
- this.setImageDrawable(chipState.appIconDrawable)
- this.contentDescription = chipState.appIconContentDescription
+ val appIconView = currentChipView.requireViewById<CachingIconView>(R.id.app_icon)
+ appIconView.contentDescription = chipState.getAppName(context)
+
+ val appIcon = chipState.getAppIcon(context)
+ val visibility = if (appIcon != null) {
+ View.VISIBLE
+ } else {
+ View.GONE
}
+ appIconView.setImageDrawable(appIcon)
+ appIconView.visibility = visibility
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt
index c510cbb..2da48ce 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt
@@ -16,15 +16,42 @@
package com.android.systemui.media.taptotransfer.common
+import android.content.Context
+import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
+import android.util.Log
/**
* A superclass chip state that will be subclassed by the sender chip and receiver chip.
*
- * @property appIconDrawable a drawable representing the icon of the app playing the media.
- * @property appIconContentDescription a string to use as the content description for the icon.
+ * @property appPackageName the package name of the app playing the media. Will be used to fetch the
+ * app icon and app name.
*/
open class MediaTttChipState(
- internal val appIconDrawable: Drawable,
- internal val appIconContentDescription: String
-)
+ internal val appPackageName: String?,
+) {
+ open fun getAppIcon(context: Context): Drawable? {
+ appPackageName ?: return null
+ return try {
+ context.packageManager.getApplicationIcon(appPackageName)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Cannot find icon for package $appPackageName", e)
+ null
+ }
+ }
+
+ /** Returns the name of the app playing the media or null if we can't find it. */
+ open fun getAppName(context: Context): String? {
+ appPackageName ?: return null
+ return try {
+ context.packageManager.getApplicationInfo(
+ appPackageName, PackageManager.ApplicationInfoFlags.of(0)
+ ).loadLabel(context.packageManager).toString()
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Cannot find name for package $appPackageName", e)
+ null
+ }
+ }
+}
+
+private val TAG = MediaTttChipState::class.simpleName!!
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt
index df6b934..6a4b62a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt
@@ -16,14 +16,35 @@
package com.android.systemui.media.taptotransfer.receiver
+import android.content.Context
import android.graphics.drawable.Drawable
import com.android.systemui.media.taptotransfer.common.MediaTttChipState
/**
* A class that stores all the information necessary to display the media tap-to-transfer chip on
* the receiver device.
+ *
+ * @property appIconDrawable a drawable representing the icon of the app playing the media. If
+ * present, this will be used in [this.getAppIcon] instead of [appPackageName].
+ * @property appName a name for the app playing the media. If present, this will be used in
+ * [this.getAppName] instead of [appPackageName].
*/
class ChipStateReceiver(
- appIconDrawable: Drawable,
- appIconContentDescription: String
-) : MediaTttChipState(appIconDrawable, appIconContentDescription)
+ appPackageName: String?,
+ private val appIconDrawable: Drawable?,
+ private val appName: CharSequence?
+) : MediaTttChipState(appPackageName) {
+ override fun getAppIcon(context: Context): Drawable? {
+ if (appIconDrawable != null) {
+ return appIconDrawable
+ }
+ return super.getAppIcon(context)
+ }
+
+ override fun getAppName(context: Context): String? {
+ if (appName != null) {
+ return appName.toString()
+ }
+ return super.getAppName(context)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 2d3ca5f..18623c0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -18,14 +18,17 @@
import android.app.StatusBarManager
import android.content.Context
-import android.graphics.Color
+import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.media.MediaRoute2Info
+import android.os.Handler
import android.util.Log
import android.view.ViewGroup
import android.view.WindowManager
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon
import com.android.systemui.statusbar.CommandQueue
import javax.inject.Inject
@@ -40,22 +43,19 @@
commandQueue: CommandQueue,
context: Context,
windowManager: WindowManager,
+ @Main private val mainHandler: Handler,
) : MediaTttChipControllerCommon<ChipStateReceiver>(
context, windowManager, R.layout.media_ttt_chip_receiver
) {
- // TODO(b/216141279): Use app icon from media route info instead of this fake one.
- private val fakeAppIconDrawable =
- Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also {
- it.setTint(Color.YELLOW)
- }
-
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
override fun updateMediaTapToTransferReceiverDisplay(
@StatusBarManager.MediaTransferReceiverState displayState: Int,
- routeInfo: MediaRoute2Info
+ routeInfo: MediaRoute2Info,
+ appIcon: Icon?,
+ appName: CharSequence?
) {
this@MediaTttChipControllerReceiver.updateMediaTapToTransferReceiverDisplay(
- displayState, routeInfo
+ displayState, routeInfo, appIcon, appName
)
}
}
@@ -66,11 +66,28 @@
private fun updateMediaTapToTransferReceiverDisplay(
@StatusBarManager.MediaTransferReceiverState displayState: Int,
- routeInfo: MediaRoute2Info
+ routeInfo: MediaRoute2Info,
+ appIcon: Icon?,
+ appName: CharSequence?
) {
when(displayState) {
- StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER ->
- displayChip(ChipStateReceiver(fakeAppIconDrawable, routeInfo.name.toString()))
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER -> {
+ val packageName = routeInfo.packageName
+ if (appIcon == null) {
+ displayChip(ChipStateReceiver(packageName, null, appName))
+ } else {
+ appIcon.loadDrawableAsync(
+ context,
+ Icon.OnDrawableLoadedListener { drawable ->
+ displayChip(
+ ChipStateReceiver(packageName, drawable, appName)
+ )},
+ // Notify the listener on the main handler since the listener will update
+ // the UI.
+ mainHandler
+ )
+ }
+ }
StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER -> removeChip()
else ->
Log.e(RECEIVER_TAG, "Unhandled MediaTransferReceiverState $displayState")
@@ -82,4 +99,4 @@
}
}
-private const val RECEIVER_TAG = "MediaTapToTransferReceiver"
+private const val RECEIVER_TAG = "MediaTapToTransferRcvr"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index 05baf78..9b537fb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -17,7 +17,6 @@
package com.android.systemui.media.taptotransfer.sender
import android.content.Context
-import android.graphics.drawable.Drawable
import android.view.View
import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.R
@@ -31,9 +30,8 @@
* contain additional information that is necessary for only that state.
*/
sealed class ChipStateSender(
- appIconDrawable: Drawable,
- appIconContentDescription: String
-) : MediaTttChipState(appIconDrawable, appIconContentDescription) {
+ appPackageName: String?
+) : MediaTttChipState(appPackageName) {
/** Returns a fully-formed string with the text that the chip should display. */
abstract fun getChipTextString(context: Context): String
@@ -60,10 +58,9 @@
* @property otherDeviceName the name of the other device involved in the transfer.
*/
class AlmostCloseToStartCast(
- appIconDrawable: Drawable,
- appIconContentDescription: String,
+ appPackageName: String?,
private val otherDeviceName: String,
-) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+) : ChipStateSender(appPackageName) {
override fun getChipTextString(context: Context): String {
return context.getString(R.string.media_move_closer_to_start_cast, otherDeviceName)
}
@@ -77,10 +74,9 @@
* @property otherDeviceName the name of the other device involved in the transfer.
*/
class AlmostCloseToEndCast(
- appIconDrawable: Drawable,
- appIconContentDescription: String,
+ appPackageName: String?,
private val otherDeviceName: String,
-) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+) : ChipStateSender(appPackageName) {
override fun getChipTextString(context: Context): String {
return context.getString(R.string.media_move_closer_to_end_cast, otherDeviceName)
}
@@ -93,10 +89,9 @@
* @property otherDeviceName the name of the other device involved in the transfer.
*/
class TransferToReceiverTriggered(
- appIconDrawable: Drawable,
- appIconContentDescription: String,
+ appPackageName: String?,
private val otherDeviceName: String
-) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+) : ChipStateSender(appPackageName) {
override fun getChipTextString(context: Context): String {
return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName)
}
@@ -109,9 +104,8 @@
* sender) has been initiated (but not completed).
*/
class TransferToThisDeviceTriggered(
- appIconDrawable: Drawable,
- appIconContentDescription: String
-) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+ appPackageName: String?,
+) : ChipStateSender(appPackageName) {
override fun getChipTextString(context: Context): String {
return context.getString(R.string.media_transfer_playing_this_device)
}
@@ -127,11 +121,10 @@
* undo button. The undo button will only be shown if this is non-null.
*/
class TransferToReceiverSucceeded(
- appIconDrawable: Drawable,
- appIconContentDescription: String,
+ appPackageName: String?,
private val otherDeviceName: String,
val undoCallback: IUndoMediaTransferCallback? = null
-) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+) : ChipStateSender(appPackageName) {
override fun getChipTextString(context: Context): String {
return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName)
}
@@ -149,10 +142,7 @@
// but that may take too long to go through the binder and the user may be confused as
// to why the UI hasn't changed yet. So, we immediately change the UI here.
controllerSender.displayChip(
- TransferToThisDeviceTriggered(
- this.appIconDrawable,
- this.appIconContentDescription
- )
+ TransferToThisDeviceTriggered(this.appPackageName)
)
}
}
@@ -166,11 +156,10 @@
* undo button. The undo button will only be shown if this is non-null.
*/
class TransferToThisDeviceSucceeded(
- appIconDrawable: Drawable,
- appIconContentDescription: String,
+ appPackageName: String?,
private val otherDeviceName: String,
val undoCallback: IUndoMediaTransferCallback? = null
-) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+) : ChipStateSender(appPackageName) {
override fun getChipTextString(context: Context): String {
return context.getString(R.string.media_transfer_playing_this_device)
}
@@ -189,8 +178,7 @@
// to why the UI hasn't changed yet. So, we immediately change the UI here.
controllerSender.displayChip(
TransferToReceiverTriggered(
- this.appIconDrawable,
- this.appIconContentDescription,
+ this.appPackageName,
this.otherDeviceName
)
)
@@ -200,9 +188,8 @@
/** A state representing that a transfer has failed. */
class TransferFailed(
- appIconDrawable: Drawable,
- appIconContentDescription: String
-) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+ appPackageName: String?,
+) : ChipStateSender(appPackageName) {
override fun getChipTextString(context: Context): String {
return context.getString(R.string.media_transfer_failed)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index d1790d2..da767ea 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -18,8 +18,6 @@
import android.app.StatusBarManager
import android.content.Context
-import android.graphics.Color
-import android.graphics.drawable.Icon
import android.media.MediaRoute2Info
import android.util.Log
import android.view.View
@@ -45,12 +43,6 @@
) : MediaTttChipControllerCommon<ChipStateSender>(
context, windowManager, R.layout.media_ttt_chip
) {
- // TODO(b/216141276): Use app icon from media route info instead of this fake one.
- private val fakeAppIconDrawable =
- Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also {
- it.setTint(Color.YELLOW)
- }
-
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
override fun updateMediaTapToTransferSenderDisplay(
@StatusBarManager.MediaTransferSenderState displayState: Int,
@@ -72,46 +64,24 @@
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?
) {
- // TODO(b/217418566): This app icon content description is incorrect --
- // routeInfo.name is the name of the device, not the name of the app.
- val appIconContentDescription = routeInfo.name.toString()
+ val appPackageName = routeInfo.packageName
val otherDeviceName = routeInfo.name.toString()
val chipState = when(displayState) {
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST ->
- AlmostCloseToStartCast(
- fakeAppIconDrawable, appIconContentDescription, otherDeviceName
- )
+ AlmostCloseToStartCast(appPackageName, otherDeviceName)
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST ->
- AlmostCloseToEndCast(
- fakeAppIconDrawable, appIconContentDescription, otherDeviceName
- )
+ AlmostCloseToEndCast(appPackageName, otherDeviceName)
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED ->
- TransferToReceiverTriggered(
- fakeAppIconDrawable, appIconContentDescription, otherDeviceName
- )
+ TransferToReceiverTriggered(appPackageName, otherDeviceName)
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED ->
- TransferToThisDeviceTriggered(
- fakeAppIconDrawable, appIconContentDescription
- )
+ TransferToThisDeviceTriggered(appPackageName)
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED ->
- TransferToReceiverSucceeded(
- fakeAppIconDrawable,
- appIconContentDescription,
- otherDeviceName,
- undoCallback
- )
+ TransferToReceiverSucceeded(appPackageName, otherDeviceName, undoCallback)
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED ->
- TransferToThisDeviceSucceeded(
- fakeAppIconDrawable,
- appIconContentDescription,
- otherDeviceName,
- undoCallback
- )
+ TransferToThisDeviceSucceeded(appPackageName, otherDeviceName, undoCallback)
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED ->
- TransferFailed(
- fakeAppIconDrawable, appIconContentDescription
- )
+ TransferFailed(appPackageName)
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER -> {
removeChip()
null
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 98b49b1..aa1117c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -31,6 +31,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
@@ -215,9 +216,11 @@
/** @return {@code true} if taskbar is enabled, false otherwise */
private boolean initializeTaskbarIfNecessary() {
if (mIsTablet) {
+ Trace.beginSection("NavigationBarController#initializeTaskbarIfNecessary");
// Remove navigation bar when taskbar is showing
removeNavigationBar(mContext.getDisplayId());
mTaskbarDelegate.init(mContext.getDisplayId());
+ Trace.endSection();
} else {
mTaskbarDelegate.destroy();
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index be45a62..9199911 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -263,11 +263,14 @@
// Notify FalsingManager that an intentional gesture has occurred.
// TODO(b/186519446): use a different method than isFalseTouch
mFalsingManager.isFalseTouch(BACK_GESTURE);
- boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
- boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
- if (DEBUG_MISSING_GESTURE) {
- Log.d(DEBUG_MISSING_GESTURE_TAG, "Triggered back: down=" + sendDown
- + ", up=" + sendUp);
+ // Only inject back keycodes when ahead-of-time back dispatching is disabled.
+ if (mBackAnimation == null) {
+ boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
+ boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
+ if (DEBUG_MISSING_GESTURE) {
+ Log.d(DEBUG_MISSING_GESTURE_TAG, "Triggered back: down="
+ + sendDown + ", up=" + sendUp);
+ }
}
mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index c18209d..4da574d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -43,6 +43,7 @@
import android.view.WindowManager;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import android.window.BackEvent;
import androidx.core.graphics.ColorUtils;
import androidx.dynamicanimation.animation.DynamicAnimation;
@@ -464,7 +465,8 @@
@Override
public void onMotionEvent(MotionEvent event) {
if (mBackAnimation != null) {
- mBackAnimation.onBackMotion(event);
+ mBackAnimation.onBackMotion(
+ event, mIsLeftPanel ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT);
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoSizingList.java b/packages/SystemUI/src/com/android/systemui/qs/AutoSizingList.java
deleted file mode 100644
index 18d28bf..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoSizingList.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.database.DataSetObserver;
-import android.os.Handler;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.ListAdapter;
-
-import com.android.systemui.R;
-
-/**
- * Similar to a ListView, but it will show only as many items as fit on screen and
- * bind those instead of scrolling.
- */
-public class AutoSizingList extends LinearLayout {
-
- private static final String TAG = "AutoSizingList";
- private final int mItemSize;
- private final Handler mHandler;
-
- @Nullable
- private ListAdapter mAdapter;
- private int mCount;
- private boolean mEnableAutoSizing;
-
- public AutoSizingList(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
-
- mHandler = new Handler();
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoSizingList);
- mItemSize = a.getDimensionPixelSize(R.styleable.AutoSizingList_itemHeight, 0);
- mEnableAutoSizing = a.getBoolean(R.styleable.AutoSizingList_enableAutoSizing, true);
- a.recycle();
- }
-
- public void setAdapter(ListAdapter adapter) {
- if (mAdapter != null) {
- mAdapter.unregisterDataSetObserver(mDataObserver);
- }
- mAdapter = adapter;
- if (adapter != null) {
- adapter.registerDataSetObserver(mDataObserver);
- }
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int requestedHeight = MeasureSpec.getSize(heightMeasureSpec);
- if (requestedHeight != 0) {
- int count = getItemCount(requestedHeight);
- if (mCount != count) {
- postRebindChildren();
- mCount = count;
- }
- }
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- private int getItemCount(int requestedHeight) {
- int desiredCount = getDesiredCount();
- return mEnableAutoSizing ? Math.min(requestedHeight / mItemSize, desiredCount)
- : desiredCount;
- }
-
- private int getDesiredCount() {
- return mAdapter != null ? mAdapter.getCount() : 0;
- }
-
- private void postRebindChildren() {
- mHandler.post(mBindChildren);
- }
-
- private void rebindChildren() {
- if (mAdapter == null) {
- return;
- }
- for (int i = 0; i < mCount; i++) {
- View v = i < getChildCount() ? getChildAt(i) : null;
- View newView = mAdapter.getView(i, v, this);
- if (newView != v) {
- if (v != null) {
- removeView(v);
- }
- addView(newView, i);
- }
- }
- // Ditch extra views.
- while (getChildCount() > mCount) {
- removeViewAt(getChildCount() - 1);
- }
- }
-
- private final Runnable mBindChildren = new Runnable() {
- @Override
- public void run() {
- rebindChildren();
- }
- };
-
- private final DataSetObserver mDataObserver = new DataSetObserver() {
- @Override
- public void onChanged() {
- if (mCount > getDesiredCount()) {
- mCount = getDesiredCount();
- }
- postRebindChildren();
- }
-
- @Override
- public void onInvalidated() {
- postRebindChildren();
- }
- };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 707313f..f868055 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -44,7 +44,6 @@
private final float[] mFancyClippingRadii = new float[] {0, 0, 0, 0, 0, 0, 0, 0};
private final Path mFancyClippingPath = new Path();
private int mHeightOverride = -1;
- private View mQSDetail;
private QuickStatusBarHeader mHeader;
private float mQsExpansion;
private QSCustomizer mQSCustomizer;
@@ -63,7 +62,6 @@
protected void onFinishInflate() {
super.onFinishInflate();
mQSPanelContainer = findViewById(R.id.expanded_qs_scroll_view);
- mQSDetail = findViewById(R.id.qs_detail);
mHeader = findViewById(R.id.header);
mQSCustomizer = findViewById(R.id.qs_customize);
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
@@ -175,9 +173,6 @@
int height = calculateContainerHeight();
int scrollBottom = calculateContainerBottom();
setBottom(getTop() + height);
- mQSDetail.setBottom(getTop() + scrollBottom);
- int qsDetailBottomMargin = ((MarginLayoutParams) mQSDetail.getLayoutParams()).bottomMargin;
- mQSDetail.setBottom(getTop() + scrollBottom - qsDetailBottomMargin);
}
protected int calculateContainerHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
deleted file mode 100644
index 04e2252..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ /dev/null
@@ -1,439 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_MORE_SETTINGS;
-
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.AnimatorListenerAdapter;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.drawable.Animatable;
-import android.util.AttributeSet;
-import android.util.SparseArray;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.view.WindowInsets;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.Switch;
-import android.widget.TextView;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.policy.SystemBarUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.FontSizeUtils;
-import com.android.systemui.R;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
-import com.android.systemui.plugins.qs.QSContainerController;
-import com.android.systemui.statusbar.CommandQueue;
-
-public class QSDetail extends LinearLayout {
-
- private static final String TAG = "QSDetail";
- private static final long FADE_DURATION = 300;
-
- private final SparseArray<View> mDetailViews = new SparseArray<>();
- private final UiEventLogger mUiEventLogger = QSEvents.INSTANCE.getQsUiEventsLogger();
-
- private ViewGroup mDetailContent;
- private FalsingManager mFalsingManager;
- protected TextView mDetailSettingsButton;
- protected TextView mDetailDoneButton;
- @VisibleForTesting
- QSDetailClipper mClipper;
- @Nullable
- private DetailAdapter mDetailAdapter;
- private QSPanelController mQsPanelController;
-
- protected View mQsDetailHeader;
- protected TextView mQsDetailHeaderTitle;
- private ViewStub mQsDetailHeaderSwitchStub;
- @Nullable
- private Switch mQsDetailHeaderSwitch;
- protected ImageView mQsDetailHeaderProgress;
-
- @Nullable
- protected QSTileHost mHost;
-
- private boolean mScanState;
- private boolean mClosingDetail;
- private boolean mFullyExpanded;
- private QuickStatusBarHeader mHeader;
- private boolean mTriggeredExpand;
- private boolean mShouldAnimate;
- private int mOpenX;
- private int mOpenY;
- private boolean mAnimatingOpen;
- private boolean mSwitchState;
- private QSFooter mFooter;
-
- @Nullable
- private QSContainerController mQsContainerController;
-
- public QSDetail(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- FontSizeUtils.updateFontSize(mDetailDoneButton, R.dimen.qs_detail_button_text_size);
- FontSizeUtils.updateFontSize(mDetailSettingsButton, R.dimen.qs_detail_button_text_size);
-
- for (int i = 0; i < mDetailViews.size(); i++) {
- mDetailViews.valueAt(i).dispatchConfigurationChanged(newConfig);
- }
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mDetailContent = findViewById(android.R.id.content);
- mDetailSettingsButton = findViewById(android.R.id.button2);
- mDetailDoneButton = findViewById(android.R.id.button1);
-
- mQsDetailHeader = findViewById(R.id.qs_detail_header);
- mQsDetailHeaderTitle = (TextView) mQsDetailHeader.findViewById(android.R.id.title);
- mQsDetailHeaderSwitchStub = mQsDetailHeader.findViewById(R.id.toggle_stub);
- mQsDetailHeaderProgress = findViewById(R.id.qs_detail_header_progress);
-
- updateDetailText();
-
- mClipper = new QSDetailClipper(this);
- }
-
- public void setContainerController(QSContainerController controller) {
- mQsContainerController = controller;
- }
-
- /** */
- public void setQsPanel(QSPanelController panelController, QuickStatusBarHeader header,
- QSFooter footer, FalsingManager falsingManager) {
- mQsPanelController = panelController;
- mHeader = header;
- mFooter = footer;
- mHeader.setCallback(mQsPanelCallback);
- mQsPanelController.setCallback(mQsPanelCallback);
- mFalsingManager = falsingManager;
- }
-
- public void setHost(QSTileHost host) {
- mHost = host;
- }
- public boolean isShowingDetail() {
- return mDetailAdapter != null;
- }
-
- public void setFullyExpanded(boolean fullyExpanded) {
- mFullyExpanded = fullyExpanded;
- }
-
- public void setExpanded(boolean qsExpanded) {
- if (!qsExpanded) {
- mTriggeredExpand = false;
- }
- }
-
- private void updateDetailText() {
- int resId = mDetailAdapter != null ? mDetailAdapter.getDoneText() : Resources.ID_NULL;
- mDetailDoneButton.setText(
- (resId != Resources.ID_NULL) ? resId : R.string.quick_settings_done);
- resId = mDetailAdapter != null ? mDetailAdapter.getSettingsText() : Resources.ID_NULL;
- mDetailSettingsButton.setText(
- (resId != Resources.ID_NULL) ? resId : R.string.quick_settings_more_settings);
- }
-
- public void updateResources() {
- updateDetailText();
- MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
- lp.topMargin = SystemBarUtils.getQuickQsOffsetHeight(mContext);
- setLayoutParams(lp);
- }
-
- @Override
- public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- int bottomNavBar = insets.getInsets(WindowInsets.Type.navigationBars()).bottom;
- MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
- lp.bottomMargin = bottomNavBar;
- setLayoutParams(lp);
- return super.onApplyWindowInsets(insets);
- }
-
- public boolean isClosingDetail() {
- return mClosingDetail;
- }
-
- public interface Callback {
- /** Handle an event of showing detail. */
- void onShowingDetail(@Nullable DetailAdapter detail, int x, int y);
- void onToggleStateChanged(boolean state);
- void onScanStateChanged(boolean state);
- }
-
- /** Handle an event of showing detail. */
- public void handleShowingDetail(final @Nullable DetailAdapter adapter, int x, int y,
- boolean toggleQs) {
- final boolean showingDetail = adapter != null;
- final boolean wasShowingDetail = mDetailAdapter != null;
- setClickable(showingDetail);
- if (showingDetail) {
- setupDetailHeader(adapter);
- if (toggleQs && !mFullyExpanded) {
- mTriggeredExpand = true;
- Dependency.get(CommandQueue.class).animateExpandSettingsPanel(null);
- } else {
- mTriggeredExpand = false;
- }
- mShouldAnimate = adapter.shouldAnimate();
- mOpenX = x;
- mOpenY = y;
- } else {
- // Ensure we collapse into the same point we opened from.
- x = mOpenX;
- y = mOpenY;
- if (toggleQs && mTriggeredExpand) {
- Dependency.get(CommandQueue.class).animateCollapsePanels();
- mTriggeredExpand = false;
- }
- // Always animate on close, even if the last opened detail adapter had shouldAnimate()
- // return false. This is necessary to avoid a race condition which could leave the
- // keyguard in a bad state where QS remains visible underneath the notifications, clock,
- // and status area.
- mShouldAnimate = true;
- }
-
- boolean visibleDiff = wasShowingDetail != showingDetail;
- if (!visibleDiff && !wasShowingDetail) return; // already in right state
- AnimatorListener listener;
- if (showingDetail) {
- int viewCacheIndex = adapter.getMetricsCategory();
- View detailView = adapter.createDetailView(mContext, mDetailViews.get(viewCacheIndex),
- mDetailContent);
- if (detailView == null) throw new IllegalStateException("Must return detail view");
-
- setupDetailFooter(adapter);
-
- mDetailContent.removeAllViews();
- mDetailContent.addView(detailView);
- mDetailViews.put(viewCacheIndex, detailView);
- Dependency.get(MetricsLogger.class).visible(adapter.getMetricsCategory());
- mUiEventLogger.log(adapter.openDetailEvent());
- announceForAccessibility(mContext.getString(
- R.string.accessibility_quick_settings_detail,
- adapter.getTitle()));
- mDetailAdapter = adapter;
- listener = mHideGridContentWhenDone;
- setVisibility(View.VISIBLE);
- updateDetailText();
- } else {
- if (wasShowingDetail) {
- Dependency.get(MetricsLogger.class).hidden(mDetailAdapter.getMetricsCategory());
- mUiEventLogger.log(mDetailAdapter.closeDetailEvent());
- }
- mClosingDetail = true;
- mDetailAdapter = null;
- listener = mTeardownDetailWhenDone;
- // Only update visibility if already expanded. Otherwise, a race condition can cause the
- // keyguard to enter a bad state where the QS tiles are displayed underneath the
- // notifications, clock, and status area.
- if (mQsPanelController.isExpanded()) {
- mHeader.setVisibility(View.VISIBLE);
- mFooter.setVisibility(View.VISIBLE);
- mQsPanelController.setGridContentVisibility(true);
- mQsPanelCallback.onScanStateChanged(false);
- }
- }
- sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- animateDetailVisibleDiff(x, y, visibleDiff, listener);
- if (mQsContainerController != null) {
- mQsContainerController.setDetailShowing(showingDetail);
- }
- }
-
- protected void animateDetailVisibleDiff(int x, int y, boolean visibleDiff, AnimatorListener listener) {
- if (visibleDiff) {
- mAnimatingOpen = mDetailAdapter != null;
- if (mFullyExpanded || mDetailAdapter != null) {
- setAlpha(1);
- mClipper.updateCircularClip(mShouldAnimate, x, y, mDetailAdapter != null, listener);
- } else {
- animate().alpha(0)
- .setDuration(mShouldAnimate ? FADE_DURATION : 0)
- .setListener(listener)
- .start();
- }
- }
- }
-
- protected void setupDetailFooter(DetailAdapter adapter) {
- final Intent settingsIntent = adapter.getSettingsIntent();
- mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
- mDetailSettingsButton.setOnClickListener(v -> {
- if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- return;
- }
- Dependency.get(MetricsLogger.class).action(ACTION_QS_MORE_SETTINGS,
- adapter.getMetricsCategory());
- mUiEventLogger.log(adapter.moreSettingsEvent());
- Dependency.get(ActivityStarter.class)
- .postStartActivityDismissingKeyguard(settingsIntent, 0);
- });
- mDetailDoneButton.setOnClickListener(v -> {
- if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- return;
- }
- announceForAccessibility(
- mContext.getString(R.string.accessibility_desc_quick_settings));
- if (!adapter.onDoneButtonClicked()) {
- mQsPanelController.closeDetail();
- }
- });
- }
-
- protected void setupDetailHeader(final DetailAdapter adapter) {
- mQsDetailHeaderTitle.setText(adapter.getTitle());
- final Boolean toggleState = adapter.getToggleState();
- if (toggleState == null) {
- if (mQsDetailHeaderSwitch != null) mQsDetailHeaderSwitch.setVisibility(INVISIBLE);
- mQsDetailHeader.setClickable(false);
- } else {
- if (mQsDetailHeaderSwitch == null) {
- mQsDetailHeaderSwitch = (Switch) mQsDetailHeaderSwitchStub.inflate();
- }
- mQsDetailHeaderSwitch.setVisibility(VISIBLE);
- handleToggleStateChanged(toggleState, adapter.getToggleEnabled());
- mQsDetailHeader.setClickable(true);
- mQsDetailHeader.setOnClickListener(v -> {
- if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- return;
- }
- boolean checked = !mQsDetailHeaderSwitch.isChecked();
- mQsDetailHeaderSwitch.setChecked(checked);
- adapter.setToggleState(checked);
- });
- }
- }
-
- private void handleToggleStateChanged(boolean state, boolean toggleEnabled) {
- mSwitchState = state;
- if (mAnimatingOpen) {
- return;
- }
- if (mQsDetailHeaderSwitch != null) mQsDetailHeaderSwitch.setChecked(state);
- mQsDetailHeader.setEnabled(toggleEnabled);
- if (mQsDetailHeaderSwitch != null) mQsDetailHeaderSwitch.setEnabled(toggleEnabled);
- }
-
- private void handleScanStateChanged(boolean state) {
- if (mScanState == state) return;
- mScanState = state;
- final Animatable anim = (Animatable) mQsDetailHeaderProgress.getDrawable();
- if (state) {
- mQsDetailHeaderProgress.animate().cancel();
- mQsDetailHeaderProgress.animate()
- .alpha(1)
- .withEndAction(anim::start)
- .start();
- } else {
- mQsDetailHeaderProgress.animate().cancel();
- mQsDetailHeaderProgress.animate()
- .alpha(0f)
- .withEndAction(anim::stop)
- .start();
- }
- }
-
- private void checkPendingAnimations() {
- handleToggleStateChanged(mSwitchState,
- mDetailAdapter != null && mDetailAdapter.getToggleEnabled());
- }
-
- protected Callback mQsPanelCallback = new Callback() {
- @Override
- public void onToggleStateChanged(final boolean state) {
- post(new Runnable() {
- @Override
- public void run() {
- handleToggleStateChanged(state,
- mDetailAdapter != null && mDetailAdapter.getToggleEnabled());
- }
- });
- }
-
- @Override
- public void onShowingDetail(
- final @Nullable DetailAdapter detail, final int x, final int y) {
- post(new Runnable() {
- @Override
- public void run() {
- if (isAttachedToWindow()) {
- handleShowingDetail(detail, x, y, false /* toggleQs */);
- }
- }
- });
- }
-
- @Override
- public void onScanStateChanged(final boolean state) {
- post(new Runnable() {
- @Override
- public void run() {
- handleScanStateChanged(state);
- }
- });
- }
- };
-
- private final AnimatorListenerAdapter mHideGridContentWhenDone = new AnimatorListenerAdapter() {
- public void onAnimationCancel(Animator animation) {
- // If we have been cancelled, remove the listener so that onAnimationEnd doesn't get
- // called, this will avoid accidentally turning off the grid when we don't want to.
- animation.removeListener(this);
- mAnimatingOpen = false;
- checkPendingAnimations();
- };
-
- @Override
- public void onAnimationEnd(Animator animation) {
- // Only hide content if still in detail state.
- if (mDetailAdapter != null) {
- mQsPanelController.setGridContentVisibility(false);
- mHeader.setVisibility(View.INVISIBLE);
- mFooter.setVisibility(View.INVISIBLE);
- }
- mAnimatingOpen = false;
- checkPendingAnimations();
- }
- };
-
- private final AnimatorListenerAdapter mTeardownDetailWhenDone = new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animation) {
- mDetailContent.removeAllViews();
- setVisibility(View.INVISIBLE);
- mClosingDetail = false;
- };
- };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
index 43136d3..b02efba 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
@@ -25,7 +25,7 @@
import androidx.annotation.Nullable;
-/** Helper for quick settings detail panel clip animations. **/
+/** Helper for quick settings detail panel clip animations. Currently used by the customizer **/
public class QSDetailClipper {
private final View mDetail;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java
deleted file mode 100644
index afd4f0f..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import androidx.annotation.Nullable;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.plugins.qs.DetailAdapter;
-
-import javax.inject.Inject;
-
-/**
- * Proxy class for talking with the QSPanel and showing custom content within it.
- */
-@SysUISingleton
-public class QSDetailDisplayer {
- @Nullable
- private QSPanelController mQsPanelController;
-
- @Inject
- public QSDetailDisplayer() {
- }
-
- public void setQsPanelController(@Nullable QSPanelController qsPanelController) {
- mQsPanelController = qsPanelController;
- }
-
- /** Show the supplied DetailAdapter in the Quick Settings. */
- public void showDetailAdapter(DetailAdapter detailAdapter, int x, int y) {
- if (mQsPanelController != null) {
- mQsPanelController.showDetailAdapter(detailAdapter, x, y);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
deleted file mode 100644
index eb3247b..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-import com.android.systemui.FontSizeUtils;
-import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QSTile;
-
-/**
- * Quick settings common detail view with line items.
- */
-public class QSDetailItems extends FrameLayout {
- private static final String TAG = "QSDetailItems";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private final int mQsDetailIconOverlaySize;
- private final Context mContext;
- private final H mHandler = new H();
- private final Adapter mAdapter = new Adapter();
-
- private String mTag;
- @Nullable
- private Callback mCallback;
- private boolean mItemsVisible = true;
- private AutoSizingList mItemList;
- private View mEmpty;
- private TextView mEmptyText;
- private ImageView mEmptyIcon;
-
- private Item[] mItems;
-
- public QSDetailItems(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
- mTag = TAG;
- mQsDetailIconOverlaySize = (int) getResources().getDimension(
- R.dimen.qs_detail_icon_overlay_size);
- }
-
- public static QSDetailItems convertOrInflate(Context context, View convert, ViewGroup parent) {
- if (convert instanceof QSDetailItems) {
- return (QSDetailItems) convert;
- }
- return (QSDetailItems) LayoutInflater.from(context).inflate(R.layout.qs_detail_items,
- parent, false);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mItemList = findViewById(android.R.id.list);
- mItemList.setVisibility(GONE);
- mItemList.setAdapter(mAdapter);
- mEmpty = findViewById(android.R.id.empty);
- mEmpty.setVisibility(GONE);
- mEmptyText = mEmpty.findViewById(android.R.id.title);
- mEmptyIcon = mEmpty.findViewById(android.R.id.icon);
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- FontSizeUtils.updateFontSize(mEmptyText, R.dimen.qs_detail_empty_text_size);
- int count = mItemList.getChildCount();
- for (int i = 0; i < count; i++) {
- View item = mItemList.getChildAt(i);
- FontSizeUtils.updateFontSize(item, android.R.id.title,
- R.dimen.qs_detail_item_primary_text_size);
- FontSizeUtils.updateFontSize(item, android.R.id.summary,
- R.dimen.qs_detail_item_secondary_text_size);
- }
- }
-
- public void setTagSuffix(String suffix) {
- mTag = TAG + "." + suffix;
- }
-
- public void setEmptyState(int icon, int text) {
- mEmptyIcon.post(() -> {
- mEmptyIcon.setImageResource(icon);
- mEmptyText.setText(text);
- });
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- if (DEBUG) Log.d(mTag, "onAttachedToWindow");
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (DEBUG) Log.d(mTag, "onDetachedFromWindow");
- mCallback = null;
- }
-
- public void setCallback(Callback callback) {
- mHandler.removeMessages(H.SET_CALLBACK);
- mHandler.obtainMessage(H.SET_CALLBACK, callback).sendToTarget();
- }
-
- /** Set items. */
- public void setItems(@Nullable Item[] items) {
- mHandler.removeMessages(H.SET_ITEMS);
- mHandler.obtainMessage(H.SET_ITEMS, items).sendToTarget();
- }
-
- public void setItemsVisible(boolean visible) {
- mHandler.removeMessages(H.SET_ITEMS_VISIBLE);
- mHandler.obtainMessage(H.SET_ITEMS_VISIBLE, visible ? 1 : 0, 0).sendToTarget();
- }
-
- private void handleSetCallback(Callback callback) {
- mCallback = callback;
- }
-
- private void handleSetItems(Item[] items) {
- final int itemCount = items != null ? items.length : 0;
- mEmpty.setVisibility(itemCount == 0 ? VISIBLE : GONE);
- mItemList.setVisibility(itemCount == 0 ? GONE : VISIBLE);
- mItems = items;
- mAdapter.notifyDataSetChanged();
- }
-
- private void handleSetItemsVisible(boolean visible) {
- if (mItemsVisible == visible) return;
- mItemsVisible = visible;
- for (int i = 0; i < mItemList.getChildCount(); i++) {
- mItemList.getChildAt(i).setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
- }
- }
-
- private class Adapter extends BaseAdapter {
-
- @Override
- public int getCount() {
- return mItems != null ? mItems.length : 0;
- }
-
- @Override
- public Object getItem(int position) {
- return mItems[position];
- }
-
- @Override
- public long getItemId(int position) {
- return 0;
- }
-
- @Override
- public View getView(int position, View view, ViewGroup parent) {
- final Item item = mItems[position];
- if (view == null) {
- view = LayoutInflater.from(mContext).inflate(R.layout.qs_detail_item, parent,
- false);
- }
- view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
- final ImageView iv = (ImageView) view.findViewById(android.R.id.icon);
- if (item.icon != null) {
- iv.setImageDrawable(item.icon.getDrawable(iv.getContext()));
- } else {
- iv.setImageResource(item.iconResId);
- }
- iv.getOverlay().clear();
- if (item.overlay != null) {
- item.overlay.setBounds(0, 0, mQsDetailIconOverlaySize, mQsDetailIconOverlaySize);
- iv.getOverlay().add(item.overlay);
- }
- final TextView title = (TextView) view.findViewById(android.R.id.title);
- title.setText(item.line1);
- final TextView summary = (TextView) view.findViewById(android.R.id.summary);
- final boolean twoLines = !TextUtils.isEmpty(item.line2);
- title.setMaxLines(twoLines ? 1 : 2);
- summary.setVisibility(twoLines ? VISIBLE : GONE);
- summary.setText(twoLines ? item.line2 : null);
- view.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mCallback != null) {
- mCallback.onDetailItemClick(item);
- }
- }
- });
-
- final ImageView icon2 = (ImageView) view.findViewById(android.R.id.icon2);
- if (item.canDisconnect) {
- icon2.setImageResource(R.drawable.ic_qs_cancel);
- icon2.setVisibility(VISIBLE);
- icon2.setClickable(true);
- icon2.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mCallback != null) {
- mCallback.onDetailItemDisconnect(item);
- }
- }
- });
- } else if (item.icon2 != -1) {
- icon2.setVisibility(VISIBLE);
- icon2.setImageResource(item.icon2);
- icon2.setClickable(false);
- } else {
- icon2.setVisibility(GONE);
- }
-
- return view;
- }
- };
-
- private class H extends Handler {
- private static final int SET_ITEMS = 1;
- private static final int SET_CALLBACK = 2;
- private static final int SET_ITEMS_VISIBLE = 3;
-
- public H() {
- super(Looper.getMainLooper());
- }
-
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == SET_ITEMS) {
- handleSetItems((Item[]) msg.obj);
- } else if (msg.what == SET_CALLBACK) {
- handleSetCallback((QSDetailItems.Callback) msg.obj);
- } else if (msg.what == SET_ITEMS_VISIBLE) {
- handleSetItemsVisible(msg.arg1 != 0);
- }
- }
- }
-
- public static class Item {
- public Item(int iconResId, CharSequence line1, Object tag) {
- this.iconResId = iconResId;
- this.line1 = line1;
- this.tag = tag;
- }
-
- public int iconResId;
- @Nullable
- public QSTile.Icon icon;
- @Nullable
- public Drawable overlay;
- public CharSequence line1;
- @Nullable
- public CharSequence line2;
- public Object tag;
- public boolean canDisconnect;
- public int icon2 = -1;
- }
-
- public interface Callback {
- void onDetailItemClick(Item item);
- void onDetailItemDisconnect(Item item);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
index 26adf46..24bb16a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
@@ -124,13 +124,13 @@
@UiEvent(doc = "The current user has been switched in the detail panel")
QS_USER_SWITCH(424),
- @UiEvent(doc = "User switcher QS detail panel open")
+ @UiEvent(doc = "User switcher QS dialog open")
QS_USER_DETAIL_OPEN(425),
- @UiEvent(doc = "User switcher QS detail panel closed")
+ @UiEvent(doc = "User switcher QS dialog closed")
QS_USER_DETAIL_CLOSE(426),
- @UiEvent(doc = "User switcher QS detail panel more settings pressed")
+ @UiEvent(doc = "User switcher QS dialog more settings pressed")
QS_USER_MORE_SETTINGS(427),
@UiEvent(doc = "The user has added a guest in the detail panel")
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 50952bd..ea68c40 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -33,7 +33,6 @@
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
-import android.widget.FrameLayout.LayoutParams;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -85,7 +84,6 @@
private QSSquishinessController mQSSquishinessController;
protected QuickStatusBarHeader mHeader;
protected NonInterceptingScrollView mQSPanelScrollView;
- private QSDetail mQSDetail;
private boolean mListening;
private QSContainerImpl mContainer;
private int mLayoutDirection;
@@ -96,8 +94,6 @@
private boolean mQsDisabled;
private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
- private final CommandQueue mCommandQueue;
- private final QSDetailDisplayer mQsDetailDisplayer;
private final MediaHost mQsMediaHost;
private final MediaHost mQqsMediaHost;
private final QSFragmentComponent.Factory mQsComponentFactory;
@@ -126,7 +122,6 @@
* otherwise.
*/
private boolean mInSplitShade;
- private boolean mPulseExpanding;
/**
* Are we currently transitioning from lockscreen to the full shade?
@@ -150,15 +145,13 @@
public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
QSTileHost qsTileHost,
StatusBarStateController statusBarStateController, CommandQueue commandQueue,
- QSDetailDisplayer qsDetailDisplayer, @Named(QS_PANEL) MediaHost qsMediaHost,
+ @Named(QS_PANEL) MediaHost qsMediaHost,
@Named(QUICK_QS_PANEL) MediaHost qqsMediaHost,
KeyguardBypassController keyguardBypassController,
QSFragmentComponent.Factory qsComponentFactory,
QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger,
FalsingManager falsingManager, DumpManager dumpManager) {
mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
- mCommandQueue = commandQueue;
- mQsDetailDisplayer = qsDetailDisplayer;
mQsMediaHost = qsMediaHost;
mQqsMediaHost = qqsMediaHost;
mQsComponentFactory = qsComponentFactory;
@@ -209,19 +202,15 @@
mScrollListener.onQsPanelScrollChanged(scrollY);
}
});
- mQSDetail = view.findViewById(R.id.qs_detail);
mHeader = view.findViewById(R.id.header);
mQSPanelController.setHeaderContainer(view.findViewById(R.id.header_text_container));
mFooter = qsFragmentComponent.getQSFooter();
- mQsDetailDisplayer.setQsPanelController(mQSPanelController);
-
mQSContainerImplController = qsFragmentComponent.getQSContainerImplController();
mQSContainerImplController.init();
mContainer = mQSContainerImplController.getView();
mDumpManager.registerDumpable(mContainer.getClass().getName(), mContainer);
- mQSDetail.setQsPanel(mQSPanelController, mHeader, mFooter, mFalsingManager);
mQSAnimator = qsFragmentComponent.getQSAnimator();
mQSSquishinessController = qsFragmentComponent.getQSSquishinessController();
@@ -237,7 +226,6 @@
mQSPanelController.getTileLayout().restoreInstanceState(savedInstanceState);
}
}
- setHost(mHost);
mStatusBarStateController.addCallback(this);
onStateChanged(mStatusBarStateController.getState());
view.addOnLayoutChangeListener(
@@ -271,7 +259,6 @@
setListening(false);
}
mQSCustomizerController.setQs(null);
- mQsDetailDisplayer.setQsPanelController(null);
mScrollListener = null;
mDumpManager.unregisterDumpable(mContainer.getClass().getName());
}
@@ -352,7 +339,6 @@
@Override
public void setContainerController(QSContainerController controller) {
mQSCustomizerController.setContainerController(controller);
- mQSDetail.setContainerController(controller);
}
@Override
@@ -360,10 +346,6 @@
return mQSCustomizerController.isCustomizing();
}
- public void setHost(QSTileHost qsh) {
- mQSDetail.setHost(qsh);
- }
-
@Override
public void disable(int displayId, int state1, int state2, boolean animate) {
if (displayId != getContext().getDisplayId()) {
@@ -392,7 +374,6 @@
final boolean expandVisually = expanded || mStackScrollerOverscrolling
|| mHeaderAnimating;
mQSPanelController.setExpanded(expanded);
- mQSDetail.setExpanded(expanded);
boolean keyguardShowing = isKeyguardState();
mHeader.setVisibility((expanded || !keyguardShowing || mHeaderAnimating
|| mShowCollapsedOnKeyguard)
@@ -444,7 +425,7 @@
@Override
public boolean isShowingDetail() {
- return mQSCustomizerController.isCustomizing() || mQSDetail.isShowingDetail();
+ return mQSCustomizerController.isCustomizing();
}
@Override
@@ -573,7 +554,6 @@
if (fullyCollapsed) {
mQSPanelScrollView.setScrollY(0);
}
- mQSDetail.setFullyExpanded(fullyExpanded);
if (!fullyExpanded) {
// Set bounds on the QS panel so it doesn't run over the header when animating.
@@ -732,14 +712,7 @@
if (mQSCustomizerController.isCustomizing()) {
return getView().getHeight();
}
- if (mQSDetail.isClosingDetail()) {
- LayoutParams layoutParams = (LayoutParams) mQSPanelScrollView.getLayoutParams();
- int panelHeight = layoutParams.topMargin + layoutParams.bottomMargin +
- + mQSPanelScrollView.getMeasuredHeight();
- return panelHeight + getView().getPaddingBottom();
- } else {
- return getView().getMeasuredHeight();
- }
+ return getView().getMeasuredHeight();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 7c04cd4..0c854df 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -41,10 +41,8 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.widget.RemeasuringLinearLayout;
import com.android.systemui.R;
-import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.settings.brightness.BrightnessSliderController;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -80,7 +78,6 @@
protected boolean mExpanded;
protected boolean mListening;
- private QSDetail.Callback mCallback;
protected QSTileHost mHost;
private final List<OnConfigurationChangedListener> mOnConfigurationChangedListeners =
new ArrayList<>();
@@ -100,9 +97,6 @@
private int mContentMarginEnd;
private boolean mUsingHorizontalLayout;
- private Record mDetailRecord;
-
- private BrightnessMirrorController mBrightnessMirrorController;
private LinearLayout mHorizontalLinearLayout;
protected LinearLayout mHorizontalContentContainer;
@@ -315,24 +309,12 @@
view.setVisibility(TunerService.parseIntegerSwitch(newValue, true) ? VISIBLE : GONE);
}
- /** */
- public void openDetails(QSTile tile) {
- // If there's no tile with that name (as defined in QSFactoryImpl or other QSFactory),
- // QSFactory will not be able to create a tile and getTile will return null
- if (tile != null) {
- showDetailAdapter(true, tile.getDetailAdapter(), new int[]{getWidth() / 2, 0});
- }
- }
@Nullable
View getBrightnessView() {
return mBrightnessView;
}
- public void setCallback(QSDetail.Callback callback) {
- mCallback = callback;
- }
-
/**
* Links the footer's page indicator, which is used in landscape orientation to save space.
*
@@ -519,25 +501,6 @@
mListening = listening;
}
- public void showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow) {
- int xInWindow = locationInWindow[0];
- int yInWindow = locationInWindow[1];
- ((View) getParent()).getLocationInWindow(locationInWindow);
-
- Record r = new Record();
- r.detailAdapter = adapter;
- r.x = xInWindow - locationInWindow[0];
- r.y = yInWindow - locationInWindow[1];
-
- locationInWindow[0] = xInWindow;
- locationInWindow[1] = yInWindow;
-
- showDetail(show, r);
- }
-
- protected void showDetail(boolean show, Record r) {
- mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
- }
protected void drawTile(QSPanelControllerBase.TileRecord r, QSTile.State state) {
r.tileView.onStateChanged(state);
@@ -565,30 +528,6 @@
public void onStateChanged(QSTile.State state) {
drawTile(tileRecord, state);
}
-
- @Override
- public void onShowDetail(boolean show) {
- // Both the collapsed and full QS panels get this callback, this check determines
- // which one should handle showing the detail.
- if (shouldShowDetail()) {
- QSPanel.this.showDetail(show, tileRecord);
- }
- }
-
- @Override
- public void onToggleStateChanged(boolean state) {
- if (mDetailRecord == tileRecord) {
- fireToggleStateChanged(state);
- }
- }
-
- @Override
- public void onScanStateChanged(boolean state) {
- tileRecord.scanState = state;
- if (mDetailRecord == tileRecord) {
- fireScanStateChanged(tileRecord.scanState);
- }
- }
};
tileRecord.tile.addCallback(callback);
@@ -605,72 +544,10 @@
mTileLayout.removeTile(tileRecord);
}
- void closeDetail() {
- showDetail(false, mDetailRecord);
- }
-
public int getGridHeight() {
return getMeasuredHeight();
}
- protected void handleShowDetail(Record r, boolean show) {
- if (r instanceof QSPanelControllerBase.TileRecord) {
- handleShowDetailTile((QSPanelControllerBase.TileRecord) r, show);
- } else {
- int x = 0;
- int y = 0;
- if (r != null) {
- x = r.x;
- y = r.y;
- }
- handleShowDetailImpl(r, show, x, y);
- }
- }
-
- private void handleShowDetailTile(QSPanelControllerBase.TileRecord r, boolean show) {
- if ((mDetailRecord != null) == show && mDetailRecord == r) return;
-
- if (show) {
- r.detailAdapter = r.tile.getDetailAdapter();
- if (r.detailAdapter == null) return;
- }
- r.tile.setDetailListening(show);
- int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
- int y = r.tileView.getDetailY() + mTileLayout.getOffsetTop(r) + getTop();
- handleShowDetailImpl(r, show, x, y);
- }
-
- private void handleShowDetailImpl(Record r, boolean show, int x, int y) {
- setDetailRecord(show ? r : null);
- fireShowingDetail(show ? r.detailAdapter : null, x, y);
- }
-
- protected void setDetailRecord(Record r) {
- if (r == mDetailRecord) return;
- mDetailRecord = r;
- final boolean scanState = mDetailRecord instanceof QSPanelControllerBase.TileRecord
- && ((QSPanelControllerBase.TileRecord) mDetailRecord).scanState;
- fireScanStateChanged(scanState);
- }
-
- private void fireShowingDetail(DetailAdapter detail, int x, int y) {
- if (mCallback != null) {
- mCallback.onShowingDetail(detail, x, y);
- }
- }
-
- private void fireToggleStateChanged(boolean state) {
- if (mCallback != null) {
- mCallback.onToggleStateChanged(state);
- }
- }
-
- private void fireScanStateChanged(boolean state) {
- if (mCallback != null) {
- mCallback.onScanStateChanged(state);
- }
- }
-
QSTileLayout getTileLayout() {
return mTileLayout;
}
@@ -774,26 +651,16 @@
}
private class H extends Handler {
- private static final int SHOW_DETAIL = 1;
- private static final int SET_TILE_VISIBILITY = 2;
- private static final int ANNOUNCE_FOR_ACCESSIBILITY = 3;
+ private static final int ANNOUNCE_FOR_ACCESSIBILITY = 1;
@Override
public void handleMessage(Message msg) {
- if (msg.what == SHOW_DETAIL) {
- handleShowDetail((Record) msg.obj, msg.arg1 != 0);
- } else if (msg.what == ANNOUNCE_FOR_ACCESSIBILITY) {
+ if (msg.what == ANNOUNCE_FOR_ACCESSIBILITY) {
announceForAccessibility((CharSequence) msg.obj);
}
}
}
- protected static class Record {
- DetailAdapter detailAdapter;
- int x;
- int y;
- }
-
public interface QSTileLayout {
/** */
default void saveInstanceState(Bundle outState) {}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 418c4ae..6a7f3c3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -36,15 +36,12 @@
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
-import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.brightness.BrightnessController;
import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
import com.android.systemui.settings.brightness.BrightnessSliderController;
-import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.Utils;
@@ -57,7 +54,6 @@
*/
@QSScope
public class QSPanelController extends QSPanelControllerBase<QSPanel> {
- public static final String QS_REMOVE_LABELS = "sysui_remove_labels";
private final QSFgsManagerFooter mQSFgsManagerFooter;
private final QSSecurityFooter mQsSecurityFooter;
@@ -65,7 +61,6 @@
private final QSCustomizerController mQsCustomizerController;
private final QSTileRevealController.Factory mQsTileRevealControllerFactory;
private final FalsingManager mFalsingManager;
- private final CommandQueue mCommandQueue;
private final BrightnessController mBrightnessController;
private final BrightnessSliderController mBrightnessSliderController;
private final BrightnessMirrorHandler mBrightnessMirrorHandler;
@@ -107,7 +102,7 @@
DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
BrightnessSliderController.Factory brightnessSliderFactory,
- FalsingManager falsingManager, CommandQueue commandQueue, FeatureFlags featureFlags) {
+ FalsingManager falsingManager, FeatureFlags featureFlags) {
super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
metricsLogger, uiEventLogger, qsLogger, dumpManager);
mQSFgsManagerFooter = qsFgsManagerFooter;
@@ -116,7 +111,6 @@
mQsCustomizerController = qsCustomizerController;
mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
mFalsingManager = falsingManager;
- mCommandQueue = commandQueue;
mBrightnessSliderController = brightnessSliderFactory.create(getContext(), mView);
mView.setBrightnessView(mBrightnessSliderController.getRootView());
@@ -221,15 +215,6 @@
return mHost;
}
-
- /** Open the details for a specific tile.. */
- public void openDetails(String subPanel) {
- QSTile tile = getTile(subPanel);
- if (tile != null) {
- mView.openDetails(tile);
- }
- }
-
/** Show the device monitoring dialog. */
public void showDeviceMonitoringDialog() {
mQsSecurityFooter.showDeviceMonitoringDialog();
@@ -261,11 +246,6 @@
}
/** */
- public void setCallback(QSDetail.Callback qsPanelCallback) {
- mView.setCallback(qsPanelCallback);
- }
-
- /** */
public void setGridContentVisibility(boolean visible) {
int newVis = visible ? View.VISIBLE : View.INVISIBLE;
setVisibility(newVis);
@@ -294,19 +274,6 @@
}
/** */
- public void showDetailAdapter(DetailAdapter detailAdapter, int x, int y) {
- // TODO(b/199296365)
- // Workaround for opening detail from QQS, when there might not be enough space to
- // display e.g. in case of multiuser detail from split shade. Currently showing detail works
- // only for QS (mView below) and that's why expanding panel (thus showing QS instead of QQS)
- // makes it displayed correctly.
- if (!isExpanded()) {
- mCommandQueue.animateExpandSettingsPanel(null);
- }
- mView.showDetailAdapter(true, detailAdapter, new int[]{x, y});
- }
-
- /** */
public void setFooterPageIndicator(PageIndicator pageIndicator) {
mView.setFooterPageIndicator(pageIndicator);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index d4d6da8..0bff722 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -298,21 +298,8 @@
mQsCustomizerController.hide();
return;
}
- mView.closeDetail();
}
- /** */
- public void openDetails(String subPanel) {
- QSTile tile = getTile(subPanel);
- // If there's no tile with that name (as defined in QSFactoryImpl or other QSFactory),
- // QSFactory will not be able to create a tile and getTile will return null
- if (tile != null) {
- mView.showDetailAdapter(
- true, tile.getDetailAdapter(), new int[]{mView.getWidth() / 2, 0});
- }
- }
-
-
void setListening(boolean listening) {
mView.setListening(listening);
@@ -429,7 +416,7 @@
}
/** */
- public static final class TileRecord extends QSPanel.Record {
+ public static final class TileRecord {
public TileRecord(QSTile tile, com.android.systemui.plugins.qs.QSTileView tileView) {
this.tile = tile;
this.tileView = tileView;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index d3f8db38..8c08873 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -39,7 +39,6 @@
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView;
-import com.android.systemui.qs.QSDetail.Callback;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
import com.android.systemui.statusbar.phone.StatusIconContainer;
@@ -548,10 +547,6 @@
post(() -> setClickable(!mExpanded));
}
- public void setCallback(Callback qsPanelCallback) {
- mHeaderQsPanel.setCallback(qsPanelCallback);
- }
-
private void setContentMargins(View view, int marginStart, int marginEnd) {
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
lp.setMarginStart(marginStart);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index b29687f..32a7916 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -205,15 +205,6 @@
finished();
}
}
-
- @Override
- public void onShowDetail(boolean show) {}
-
- @Override
- public void onToggleStateChanged(boolean state) {}
-
- @Override
- public void onScanStateChanged(boolean state) {}
}
private void addPackageTiles(final QSTileHost host) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
index 73d6b97..237b66e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
@@ -21,6 +21,7 @@
import android.content.ComponentName
import android.content.DialogInterface
import android.graphics.drawable.Icon
+import android.os.RemoteException
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.internal.statusbar.IAddTileResultCallback
@@ -32,6 +33,7 @@
import com.android.systemui.R
import com.android.systemui.statusbar.CommandQueue
import java.io.PrintWriter
+import java.util.concurrent.atomic.AtomicBoolean
import java.util.function.Consumer
import javax.inject.Inject
@@ -67,7 +69,11 @@
callback: IAddTileResultCallback
) {
requestTileAdd(componentName, appName, label, icon) {
- callback.onTileRequest(it)
+ try {
+ callback.onTileRequest(it)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Couldn't respond to request", e)
+ }
}
}
@@ -105,7 +111,7 @@
eventLogger.logTileAlreadyAdded(packageName, instanceId)
return
}
- val dialogResponse = Consumer<Int> { response ->
+ val dialogResponse = SingleShotConsumer<Int> { response ->
if (response == ADD_TILE) {
addTile(componentName)
}
@@ -127,7 +133,7 @@
private fun createDialog(
tileData: TileRequestDialog.TileData,
- responseHandler: Consumer<Int>
+ responseHandler: SingleShotConsumer<Int>
): SystemUIDialog {
val dialogClickListener = DialogInterface.OnClickListener { _, which ->
if (which == Dialog.BUTTON_POSITIVE) {
@@ -141,6 +147,10 @@
setShowForAllUsers(true)
setCanceledOnTouchOutside(true)
setOnCancelListener { responseHandler.accept(DISMISSED) }
+ // We want this in case the dialog is dismissed without it being cancelled (for example
+ // by going home or locking the device). We use a SingleShotConsumer so the response
+ // is only sent once, with the first value.
+ setOnDismissListener { responseHandler.accept(DISMISSED) }
setPositiveButton(R.string.qs_tile_request_dialog_add, dialogClickListener)
setNegativeButton(R.string.qs_tile_request_dialog_not_add, dialogClickListener)
}
@@ -169,6 +179,16 @@
}
}
+ private class SingleShotConsumer<T>(private val consumer: Consumer<T>) : Consumer<T> {
+ private val dispatched = AtomicBoolean(false)
+
+ override fun accept(t: T) {
+ if (dispatched.compareAndSet(false, true)) {
+ consumer.accept(t)
+ }
+ }
+ }
+
@SysUISingleton
class Builder @Inject constructor(
private val commandQueue: CommandQueue,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 86fc4de..1488231 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -53,7 +53,6 @@
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.qs.tiles.ScreenRecordTile;
import com.android.systemui.qs.tiles.UiModeNightTile;
-import com.android.systemui.qs.tiles.UserTile;
import com.android.systemui.qs.tiles.WifiTile;
import com.android.systemui.qs.tiles.WorkModeTile;
import com.android.systemui.util.leak.GarbageMonitor;
@@ -82,7 +81,6 @@
private final Provider<LocationTile> mLocationTileProvider;
private final Provider<CastTile> mCastTileProvider;
private final Provider<HotspotTile> mHotspotTileProvider;
- private final Provider<UserTile> mUserTileProvider;
private final Provider<BatterySaverTile> mBatterySaverTileProvider;
private final Provider<DataSaverTile> mDataSaverTileProvider;
private final Provider<NightDisplayTile> mNightDisplayTileProvider;
@@ -119,7 +117,6 @@
Provider<LocationTile> locationTileProvider,
Provider<CastTile> castTileProvider,
Provider<HotspotTile> hotspotTileProvider,
- Provider<UserTile> userTileProvider,
Provider<BatterySaverTile> batterySaverTileProvider,
Provider<DataSaverTile> dataSaverTileProvider,
Provider<NightDisplayTile> nightDisplayTileProvider,
@@ -152,7 +149,6 @@
mLocationTileProvider = locationTileProvider;
mCastTileProvider = castTileProvider;
mHotspotTileProvider = hotspotTileProvider;
- mUserTileProvider = userTileProvider;
mBatterySaverTileProvider = batterySaverTileProvider;
mDataSaverTileProvider = dataSaverTileProvider;
mNightDisplayTileProvider = nightDisplayTileProvider;
@@ -212,8 +208,6 @@
return mCastTileProvider.get();
case "hotspot":
return mHotspotTileProvider.get();
- case "user":
- return mUserTileProvider.get();
case "battery":
return mBatterySaverTileProvider.get();
case "saver":
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 6d9d5b1..131589f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -60,7 +60,6 @@
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSIconView;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.State;
@@ -261,16 +260,6 @@
return new QSIconViewImpl(context);
}
- /** Returns corresponding DetailAdapter. */
- @Nullable
- public DetailAdapter getDetailAdapter() {
- return null; // optional
- }
-
- protected DetailAdapter createDetailAdapter() {
- throw new UnsupportedOperationException();
- }
-
/**
* Is a startup check whether this device currently supports this tile.
* Should not be used to conditionally hide tiles. Only checked on tile
@@ -337,10 +326,6 @@
.addTaggedData(FIELD_QS_POSITION, mHost.indexOf(mTileSpec));
}
- public void showDetail(boolean show) {
- mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
- }
-
public void refreshState() {
refreshState(null);
}
@@ -353,14 +338,6 @@
mHandler.obtainMessage(H.USER_SWITCH, newUserId, 0).sendToTarget();
}
- public void fireToggleStateChanged(boolean state) {
- mHandler.obtainMessage(H.TOGGLE_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
- }
-
- public void fireScanStateChanged(boolean state) {
- mHandler.obtainMessage(H.SCAN_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
- }
-
public void destroy() {
mHandler.sendEmptyMessage(H.DESTROY);
}
@@ -462,29 +439,6 @@
}
}
- private void handleShowDetail(boolean show) {
- mShowingDetail = show;
- for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).onShowDetail(show);
- }
- }
-
- protected boolean isShowingDetail() {
- return mShowingDetail;
- }
-
- private void handleToggleStateChanged(boolean state) {
- for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).onToggleStateChanged(state);
- }
- }
-
- private void handleScanStateChanged(boolean state) {
- for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).onScanStateChanged(state);
- }
- }
-
protected void handleUserSwitch(int newUserId) {
handleRefreshState(null);
}
@@ -591,17 +545,14 @@
private static final int SECONDARY_CLICK = 3;
private static final int LONG_CLICK = 4;
private static final int REFRESH_STATE = 5;
- private static final int SHOW_DETAIL = 6;
- private static final int USER_SWITCH = 7;
- private static final int TOGGLE_STATE_CHANGED = 8;
- private static final int SCAN_STATE_CHANGED = 9;
- private static final int DESTROY = 10;
- private static final int REMOVE_CALLBACKS = 11;
- private static final int REMOVE_CALLBACK = 12;
- private static final int SET_LISTENING = 13;
+ private static final int USER_SWITCH = 6;
+ private static final int DESTROY = 7;
+ private static final int REMOVE_CALLBACKS = 8;
+ private static final int REMOVE_CALLBACK = 9;
+ private static final int SET_LISTENING = 10;
@VisibleForTesting
- protected static final int STALE = 14;
- private static final int INITIALIZE = 15;
+ protected static final int STALE = 11;
+ private static final int INITIALIZE = 12;
@VisibleForTesting
protected H(Looper looper) {
@@ -639,18 +590,9 @@
} else if (msg.what == REFRESH_STATE) {
name = "handleRefreshState";
handleRefreshState(msg.obj);
- } else if (msg.what == SHOW_DETAIL) {
- name = "handleShowDetail";
- handleShowDetail(msg.arg1 != 0);
} else if (msg.what == USER_SWITCH) {
name = "handleUserSwitch";
handleUserSwitch(msg.arg1);
- } else if (msg.what == TOGGLE_STATE_CHANGED) {
- name = "handleToggleStateChanged";
- handleToggleStateChanged(msg.arg1 != 0);
- } else if (msg.what == SCAN_STATE_CHANGED) {
- name = "handleScanStateChanged";
- handleScanStateChanged(msg.arg1 != 0);
} else if (msg.what == DESTROY) {
name = "handleDestroy";
handleDestroy();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 754f8e2..c61c18a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -19,7 +19,6 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
@@ -29,7 +28,6 @@
import android.service.quicksettings.Tile;
import android.text.TextUtils;
import android.view.View;
-import android.view.ViewGroup;
import android.widget.Switch;
import androidx.annotation.Nullable;
@@ -38,24 +36,18 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSDetailItems;
-import com.android.systemui.qs.QSDetailItems.Item;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.BluetoothController;
-import java.util.ArrayList;
-import java.util.Collection;
import java.util.List;
import javax.inject.Inject;
@@ -65,7 +57,6 @@
private static final Intent BLUETOOTH_SETTINGS = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
private final BluetoothController mController;
- private final BluetoothDetailAdapter mDetailAdapter;
@Inject
public BluetoothTile(
@@ -82,16 +73,10 @@
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mController = bluetoothController;
- mDetailAdapter = (BluetoothDetailAdapter) createDetailAdapter();
mController.observe(getLifecycle(), mCallback);
}
@Override
- public DetailAdapter getDetailAdapter() {
- return mDetailAdapter;
- }
-
- @Override
public BooleanState newTileState() {
return new BooleanState();
}
@@ -117,7 +102,6 @@
new Intent(Settings.ACTION_BLUETOOTH_SETTINGS), 0);
return;
}
- showDetail(true);
if (!mState.value) {
mController.setBluetoothEnabled(true);
}
@@ -251,53 +235,14 @@
@Override
public void onBluetoothStateChange(boolean enabled) {
refreshState();
- if (isShowingDetail()) {
- mDetailAdapter.updateItems();
- fireToggleStateChanged(mDetailAdapter.getToggleState());
- }
}
@Override
public void onBluetoothDevicesChanged() {
refreshState();
- if (isShowingDetail()) {
- mDetailAdapter.updateItems();
- }
}
};
- @Override
- protected DetailAdapter createDetailAdapter() {
- return new BluetoothDetailAdapter();
- }
-
- /**
- * Bluetooth icon wrapper for Quick Settings with a battery indicator that reflects the
- * connected device's battery level. This is used instead of
- * {@link com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon} in order to use a context
- * that reflects dark/light theme attributes.
- */
- private class BluetoothBatteryTileIcon extends Icon {
- private int mBatteryLevel;
- private float mIconScale;
-
- BluetoothBatteryTileIcon(int batteryLevel, float iconScale) {
- mBatteryLevel = batteryLevel;
- mIconScale = iconScale;
- }
-
- @Override
- public Drawable getDrawable(Context context) {
- // This method returns Pair<Drawable, String> while first value is the drawable
- return BluetoothDeviceLayerDrawable.createLayerDrawable(
- context,
- R.drawable.ic_bluetooth_connected,
- mBatteryLevel,
- mIconScale);
- }
- }
-
-
/**
* Bluetooth icon wrapper (when connected with no battery indicator) for Quick Settings. This is
* used instead of {@link com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon} in order to
@@ -315,129 +260,4 @@
return context.getDrawable(R.drawable.ic_bluetooth_connected);
}
}
-
- protected class BluetoothDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
- // We probably won't ever have space in the UI for more than 20 devices, so don't
- // get info for them.
- private static final int MAX_DEVICES = 20;
- @Nullable
- private QSDetailItems mItems;
-
- @Override
- public CharSequence getTitle() {
- return mContext.getString(R.string.quick_settings_bluetooth_label);
- }
-
- @Override
- public Boolean getToggleState() {
- return mState.value;
- }
-
- @Override
- public boolean getToggleEnabled() {
- return mController.getBluetoothState() == BluetoothAdapter.STATE_OFF
- || mController.getBluetoothState() == BluetoothAdapter.STATE_ON;
- }
-
- @Override
- public Intent getSettingsIntent() {
- return BLUETOOTH_SETTINGS;
- }
-
- @Override
- public void setToggleState(boolean state) {
- MetricsLogger.action(mContext, MetricsEvent.QS_BLUETOOTH_TOGGLE, state);
- mController.setBluetoothEnabled(state);
- }
-
- @Override
- public int getMetricsCategory() {
- return MetricsEvent.QS_BLUETOOTH_DETAILS;
- }
-
- @Override
- public View createDetailView(Context context, View convertView, ViewGroup parent) {
- mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
- mItems.setTagSuffix("Bluetooth");
- mItems.setCallback(this);
- updateItems();
- setItemsVisible(mState.value);
- return mItems;
- }
-
- public void setItemsVisible(boolean visible) {
- if (mItems == null) return;
- mItems.setItemsVisible(visible);
- }
-
- private void updateItems() {
- if (mItems == null) return;
- if (mController.isBluetoothEnabled()) {
- mItems.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
- R.string.quick_settings_bluetooth_detail_empty_text);
- } else {
- mItems.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
- R.string.bt_is_off);
- }
- ArrayList<Item> items = new ArrayList<Item>();
- final Collection<CachedBluetoothDevice> devices = mController.getDevices();
- if (devices != null) {
- int connectedDevices = 0;
- int count = 0;
- for (CachedBluetoothDevice device : devices) {
- if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue;
- final Item item =
- new Item(
- com.android.internal.R.drawable.ic_qs_bluetooth,
- device.getName(),
- device);
- int state = device.getMaxConnectionState();
- if (state == BluetoothProfile.STATE_CONNECTED) {
- item.iconResId = R.drawable.ic_bluetooth_connected;
- int batteryLevel = device.getBatteryLevel();
- if (batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
- item.icon = new BluetoothBatteryTileIcon(batteryLevel,1 /* iconScale */);
- item.line2 = mContext.getString(
- R.string.quick_settings_connected_battery_level,
- Utils.formatPercentage(batteryLevel));
- } else {
- item.line2 = mContext.getString(R.string.quick_settings_connected);
- }
- item.canDisconnect = true;
- items.add(connectedDevices, item);
- connectedDevices++;
- } else if (state == BluetoothProfile.STATE_CONNECTING) {
- item.iconResId = R.drawable.ic_qs_bluetooth_connecting;
- item.line2 = mContext.getString(R.string.quick_settings_connecting);
- items.add(connectedDevices, item);
- } else {
- items.add(item);
- }
- if (++count == MAX_DEVICES) {
- break;
- }
- }
- }
- mItems.setItems(items.toArray(new Item[items.size()]));
- }
-
- @Override
- public void onDetailItemClick(Item item) {
- if (item == null || item.tag == null) return;
- final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
- if (device != null && device.getMaxConnectionState()
- == BluetoothProfile.STATE_DISCONNECTED) {
- mController.connect(device);
- }
- }
-
- @Override
- public void onDetailItemDisconnect(Item item) {
- if (item == null || item.tag == null) return;
- final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
- if (device != null) {
- mController.disconnect(device);
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 76c84f9..4d9c4c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.app.Dialog;
-import android.content.Context;
import android.content.Intent;
import android.media.MediaRouter.RouteInfo;
import android.os.Handler;
@@ -29,8 +28,6 @@
import android.service.quicksettings.Tile;
import android.util.Log;
import android.view.View;
-import android.view.View.OnAttachStateChangeListener;
-import android.view.ViewGroup;
import android.widget.Button;
import androidx.annotation.Nullable;
@@ -44,11 +41,8 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSDetailItems;
-import com.android.systemui.qs.QSDetailItems.Item;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -62,7 +56,6 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.util.ArrayList;
-import java.util.LinkedHashMap;
import java.util.List;
import javax.inject.Inject;
@@ -73,7 +66,6 @@
new Intent(Settings.ACTION_CAST_SETTINGS);
private final CastController mController;
- private final CastDetailAdapter mDetailAdapter;
private final KeyguardStateController mKeyguard;
private final NetworkController mNetworkController;
private final DialogLaunchAnimator mDialogLaunchAnimator;
@@ -100,7 +92,6 @@
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mController = castController;
- mDetailAdapter = new CastDetailAdapter();
mKeyguard = keyguardStateController;
mNetworkController = networkController;
mDialogLaunchAnimator = dialogLaunchAnimator;
@@ -111,11 +102,6 @@
}
@Override
- public DetailAdapter getDetailAdapter() {
- return mDetailAdapter;
- }
-
- @Override
public BooleanState newTileState() {
BooleanState state = new BooleanState();
state.handlesLongClick = false;
@@ -154,14 +140,14 @@
}
List<CastDevice> activeDevices = getActiveDevices();
- if (willPopDetail()) {
+ if (willPopDialog()) {
if (!mKeyguard.isShowing()) {
- showDetail(view);
+ showDialog(view);
} else {
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
// Dismissing the keyguard will collapse the shade, so we don't animate from the
// view here as it would not look good.
- showDetail(null /* view */);
+ showDialog(null /* view */);
});
}
} else {
@@ -173,7 +159,7 @@
// (neither routes nor projection), or if we have an active route. In other cases, we assume
// that a projection is active. This is messy, but this tile never correctly handled the
// case where multiple devices were active :-/.
- private boolean willPopDetail() {
+ private boolean willPopDialog() {
List<CastDevice> activeDevices = getActiveDevices();
return activeDevices.isEmpty() || (activeDevices.get(0).tag instanceof RouteInfo);
}
@@ -190,11 +176,6 @@
return activeDevices;
}
- @Override
- public void showDetail(boolean show) {
- showDetail(null /* view */);
- }
-
private static class DialogHolder {
private Dialog mDialog;
@@ -203,7 +184,7 @@
}
}
- private void showDetail(@Nullable View view) {
+ private void showDialog(@Nullable View view) {
mUiHandler.post(() -> {
final DialogHolder holder = new DialogHolder();
final Dialog dialog = MediaRouteDialogPresenter.createDialog(
@@ -268,10 +249,8 @@
if (!state.value) {
state.secondaryLabel = "";
}
- state.contentDescription = state.contentDescription + ","
- + mContext.getString(R.string.accessibility_quick_settings_open_details);
state.expandedAccessibilityClassName = Button.class.getName();
- state.forceExpandIcon = willPopDetail();
+ state.forceExpandIcon = willPopDialog();
} else {
state.state = Tile.STATE_UNAVAILABLE;
String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi);
@@ -279,7 +258,6 @@
state.forceExpandIcon = false;
}
state.stateDescription = state.stateDescription + ", " + state.secondaryLabel;
- mDetailAdapter.updateItems(devices);
}
@Override
@@ -339,118 +317,4 @@
refreshState();
}
};
-
- private final class CastDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
- private final LinkedHashMap<String, CastDevice> mVisibleOrder = new LinkedHashMap<>();
-
- private QSDetailItems mItems;
-
- @Override
- public CharSequence getTitle() {
- return mContext.getString(R.string.quick_settings_cast_title);
- }
-
- @Override
- public Boolean getToggleState() {
- return null;
- }
-
- @Override
- public Intent getSettingsIntent() {
- return CAST_SETTINGS;
- }
-
- @Override
- public void setToggleState(boolean state) {
- // noop
- }
-
- @Override
- public int getMetricsCategory() {
- return MetricsEvent.QS_CAST_DETAILS;
- }
-
- @Override
- public View createDetailView(Context context, View convertView, ViewGroup parent) {
- mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
- mItems.setTagSuffix("Cast");
- if (convertView == null) {
- if (DEBUG) Log.d(TAG, "addOnAttachStateChangeListener");
- mItems.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- if (DEBUG) Log.d(TAG, "onViewAttachedToWindow");
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- if (DEBUG) Log.d(TAG, "onViewDetachedFromWindow");
- mVisibleOrder.clear();
- }
- });
- }
- mItems.setEmptyState(R.drawable.ic_qs_cast_detail_empty,
- R.string.quick_settings_cast_detail_empty_text);
- mItems.setCallback(this);
- updateItems(mController.getCastDevices());
- mController.setDiscovering(true);
- return mItems;
- }
-
- private void updateItems(List<CastDevice> devices) {
- if (mItems == null) return;
- Item[] items = null;
- if (devices != null && !devices.isEmpty()) {
- // if we are connected, simply show that device
- for (CastDevice device : devices) {
- if (device.state == CastDevice.STATE_CONNECTED) {
- final Item item =
- new Item(
- R.drawable.ic_cast_connected,
- getDeviceName(device),
- device);
- item.line2 = mContext.getString(R.string.quick_settings_connected);
- item.canDisconnect = true;
- items = new Item[] { item };
- break;
- }
- }
- // otherwise list all available devices, and don't move them around
- if (items == null) {
- for (CastDevice device : devices) {
- mVisibleOrder.put(device.id, device);
- }
- items = new Item[devices.size()];
- int i = 0;
- for (String id : mVisibleOrder.keySet()) {
- final CastDevice device = mVisibleOrder.get(id);
- if (!devices.contains(device)) continue;
- final Item item =
- new Item(R.drawable.ic_cast, getDeviceName(device), device);
- if (device.state == CastDevice.STATE_CONNECTING) {
- item.line2 = mContext.getString(R.string.quick_settings_connecting);
- }
- items[i++] = item;
- }
- }
- }
- mItems.setItems(items);
- }
-
- @Override
- public void onDetailItemClick(Item item) {
- if (item == null || item.tag == null) return;
- MetricsLogger.action(mContext, MetricsEvent.QS_CAST_SELECT);
- final CastDevice device = (CastDevice) item.tag;
- mController.startCasting(device);
- }
-
- @Override
- public void onDetailItemDisconnect(Item item) {
- if (item == null || item.tag == null) return;
- MetricsLogger.action(mContext, MetricsEvent.QS_CAST_DISCONNECT);
- final CastDevice device = (CastDevice) item.tag;
- mController.stopCasting(device);
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 698a253..e116f75 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -32,9 +32,7 @@
import android.telephony.SubscriptionManager;
import android.text.Html;
import android.text.TextUtils;
-import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
import android.view.WindowManager.LayoutParams;
import android.widget.Switch;
@@ -49,7 +47,6 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSIconView;
import com.android.systemui.plugins.qs.QSTile.SignalState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -71,7 +68,6 @@
private final NetworkController mController;
private final DataUsageController mDataController;
- private final CellularDetailAdapter mDetailAdapter;
private final CellSignalCallback mSignalCallback = new CellSignalCallback();
@@ -91,7 +87,6 @@
statusBarStateController, activityStarter, qsLogger);
mController = networkController;
mDataController = mController.getMobileDataController();
- mDetailAdapter = new CellularDetailAdapter();
mController.observe(getLifecycle(), mSignalCallback);
}
@@ -106,11 +101,6 @@
}
@Override
- public DetailAdapter getDetailAdapter() {
- return mDetailAdapter;
- }
-
- @Override
public Intent getLongClickIntent() {
if (getState().state == Tile.STATE_UNAVAILABLE) {
return new Intent(Settings.ACTION_WIRELESS_SETTINGS);
@@ -161,12 +151,7 @@
@Override
protected void handleSecondaryClick(@Nullable View view) {
- if (mDataController.isMobileDataSupported()) {
- showDetail(true);
- } else {
- mActivityStarter
- .postStartActivityDismissingKeyguard(getCellularSettingIntent(),0 /* delay */);
- }
+ handleLongClick(view);
}
@Override
@@ -298,11 +283,6 @@
mInfo.airplaneModeEnabled = icon.visible;
refreshState(mInfo);
}
-
- @Override
- public void setMobileDataEnabled(boolean enabled) {
- mDetailAdapter.setMobileDataEnabled(enabled);
- }
}
static Intent getCellularSettingIntent() {
@@ -314,53 +294,4 @@
}
return intent;
}
-
- private final class CellularDetailAdapter implements DetailAdapter {
-
- @Override
- public CharSequence getTitle() {
- return mContext.getString(R.string.quick_settings_cellular_detail_title);
- }
-
- @Nullable
- @Override
- public Boolean getToggleState() {
- return mDataController.isMobileDataSupported()
- ? mDataController.isMobileDataEnabled()
- : null;
- }
-
- @Override
- public Intent getSettingsIntent() {
- return getCellularSettingIntent();
- }
-
- @Override
- public void setToggleState(boolean state) {
- MetricsLogger.action(mContext, MetricsEvent.QS_CELLULAR_TOGGLE, state);
- mDataController.setMobileDataEnabled(state);
- }
-
- @Override
- public int getMetricsCategory() {
- return MetricsEvent.QS_DATAUSAGEDETAIL;
- }
-
- @Override
- public View createDetailView(Context context, View convertView, ViewGroup parent) {
- final DataUsageDetailView v = (DataUsageDetailView) (convertView != null
- ? convertView
- : LayoutInflater.from(mContext).inflate(R.layout.data_usage, parent, false));
- final DataUsageController.DataUsageInfo info = mDataController.getDataUsageInfo();
- if (info == null) return v;
- v.bind(info);
- v.findViewById(R.id.roaming_text).setVisibility(mSignalCallback.mInfo.roaming
- ? View.VISIBLE : View.INVISIBLE);
- return v;
- }
-
- public void setMobileDataEnabled(boolean enabled) {
- fireToggleStateChanged(enabled);
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index a33650c..6cff4cd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -25,8 +25,6 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
@@ -34,16 +32,10 @@
import android.provider.Settings;
import android.provider.Settings.Global;
import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.ZenRule;
import android.service.quicksettings.Tile;
import android.text.TextUtils;
-import android.util.Slog;
-import android.view.LayoutInflater;
import android.view.View;
-import android.view.View.OnAttachStateChangeListener;
-import android.view.ViewGroup;
import android.widget.Switch;
-import android.widget.Toast;
import androidx.annotation.Nullable;
@@ -52,13 +44,11 @@
import com.android.settingslib.notification.EnableZenModeDialog;
import com.android.systemui.Prefs;
import com.android.systemui.R;
-import com.android.systemui.SysUIToast;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
@@ -69,7 +59,6 @@
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.util.settings.SecureSettings;
-import com.android.systemui.volume.ZenModePanel;
import javax.inject.Inject;
@@ -83,14 +72,12 @@
new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS);
private final ZenModeController mController;
- private final DndDetailAdapter mDetailAdapter;
private final SharedPreferences mSharedPreferences;
private final SettingObserver mSettingZenDuration;
private final DialogLaunchAnimator mDialogLaunchAnimator;
private final QSZenModeDialogMetricsLogger mQSZenDialogMetricsLogger;
private boolean mListening;
- private boolean mShowingDetail;
@Inject
public DndTile(
@@ -111,7 +98,6 @@
statusBarStateController, activityStarter, qsLogger);
mController = zenModeController;
mSharedPreferences = sharedPreferences;
- mDetailAdapter = new DndDetailAdapter();
mController.observe(getLifecycle(), mZenCallback);
mDialogLaunchAnimator = dialogLaunchAnimator;
mSettingZenDuration = new SettingObserver(secureSettings, mUiHandler,
@@ -142,11 +128,6 @@
}
@Override
- public DetailAdapter getDetailAdapter() {
- return mDetailAdapter;
- }
-
- @Override
public BooleanState newTileState() {
return new BooleanState();
}
@@ -225,28 +206,7 @@
@Override
protected void handleSecondaryClick(@Nullable View view) {
- if (mController.isVolumeRestricted()) {
- // Collapse the panels, so the user can see the toast.
- mHost.collapsePanels();
- SysUIToast.makeText(mContext, mContext.getString(
- com.android.internal.R.string.error_message_change_not_allowed),
- Toast.LENGTH_LONG).show();
- return;
- }
- if (!mState.value) {
- // Because of the complexity of the zen panel, it needs to be shown after
- // we turn on zen below.
- mController.addCallback(new ZenModeController.Callback() {
- @Override
- public void onZenChanged(int zen) {
- mController.removeCallback(this);
- showDetail(true);
- }
- });
- mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
- } else {
- showDetail(true);
- }
+ handleLongClick(view);
}
@Override
@@ -295,9 +255,6 @@
R.string.accessibility_quick_settings_dnd);
break;
}
- if (valueChanged) {
- fireToggleStateChanged(state.value);
- }
state.dualLabelContentDescription = mContext.getResources().getString(
R.string.accessibility_quick_settings_open_settings, getTileLabel());
state.expandedAccessibilityClassName = Switch.class.getName();
@@ -349,146 +306,6 @@
private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
public void onZenChanged(int zen) {
refreshState(zen);
- if (isShowingDetail()) {
- mDetailAdapter.updatePanel();
- }
- }
-
- @Override
- public void onConfigChanged(ZenModeConfig config) {
- if (isShowingDetail()) {
- mDetailAdapter.updatePanel();
- }
- }
- };
-
- private final class DndDetailAdapter implements DetailAdapter, OnAttachStateChangeListener {
-
- @Nullable
- private ZenModePanel mZenPanel;
- private boolean mAuto;
-
- @Override
- public CharSequence getTitle() {
- return mContext.getString(R.string.quick_settings_dnd_label);
- }
-
- @Override
- public Boolean getToggleState() {
- return mState.value;
- }
-
- @Override
- public Intent getSettingsIntent() {
- return ZEN_SETTINGS;
- }
-
- @Override
- public void setToggleState(boolean state) {
- MetricsLogger.action(mContext, MetricsEvent.QS_DND_TOGGLE, state);
- if (!state) {
- mController.setZen(ZEN_MODE_OFF, null, TAG);
- mAuto = false;
- } else {
- mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
- }
- }
-
- @Override
- public int getMetricsCategory() {
- return MetricsEvent.QS_DND_DETAILS;
- }
-
- @Override
- public View createDetailView(Context context, View convertView, ViewGroup parent) {
- mZenPanel = convertView != null ? (ZenModePanel) convertView
- : (ZenModePanel) LayoutInflater.from(context).inflate(
- R.layout.zen_mode_panel, parent, false);
- if (convertView == null) {
- mZenPanel.init(mController);
- mZenPanel.addOnAttachStateChangeListener(this);
- mZenPanel.setCallback(mZenModePanelCallback);
- mZenPanel.setEmptyState(R.drawable.ic_qs_dnd_detail_empty, R.string.dnd_is_off);
- }
- updatePanel();
- return mZenPanel;
- }
-
- private void updatePanel() {
- if (mZenPanel == null) return;
- mAuto = false;
- if (mController.getZen() == ZEN_MODE_OFF) {
- mZenPanel.setState(ZenModePanel.STATE_OFF);
- } else {
- ZenModeConfig config = mController.getConfig();
- String summary = "";
- if (config.manualRule != null && config.manualRule.enabler != null) {
- summary = getOwnerCaption(config.manualRule.enabler);
- }
- for (ZenRule automaticRule : config.automaticRules.values()) {
- if (automaticRule.isAutomaticActive()) {
- if (summary.isEmpty()) {
- summary = mContext.getString(R.string.qs_dnd_prompt_auto_rule,
- automaticRule.name);
- } else {
- summary = mContext.getString(R.string.qs_dnd_prompt_auto_rule_app);
- }
- }
- }
- if (summary.isEmpty()) {
- mZenPanel.setState(ZenModePanel.STATE_MODIFY);
- } else {
- mAuto = true;
- mZenPanel.setState(ZenModePanel.STATE_AUTO_RULE);
- mZenPanel.setAutoText(summary);
- }
- }
- }
-
- private String getOwnerCaption(String owner) {
- final PackageManager pm = mContext.getPackageManager();
- try {
- final ApplicationInfo info = pm.getApplicationInfo(owner, 0);
- if (info != null) {
- final CharSequence seq = info.loadLabel(pm);
- if (seq != null) {
- final String str = seq.toString().trim();
- return mContext.getString(R.string.qs_dnd_prompt_app, str);
- }
- }
- } catch (Throwable e) {
- Slog.w(TAG, "Error loading owner caption", e);
- }
- return "";
- }
-
- @Override
- public void onViewAttachedToWindow(View v) {
- mShowingDetail = true;
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- mShowingDetail = false;
- mZenPanel = null;
- }
- }
-
- private final ZenModePanel.Callback mZenModePanelCallback = new ZenModePanel.Callback() {
- @Override
- public void onPrioritySettings() {
- mActivityStarter.postStartActivityDismissingKeyguard(
- ZEN_PRIORITY_SETTINGS, 0);
- }
-
- @Override
- public void onInteraction() {
- // noop
- }
-
- @Override
- public void onExpanded(boolean expanded) {
- // noop
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 9df942d..cd7021e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -411,10 +411,6 @@
}
boolean wifiConnected = cb.mEnabled && (cb.mWifiSignalIconId > 0) && (cb.mSsid != null);
boolean wifiNotConnected = (cb.mWifiSignalIconId > 0) && (cb.mSsid == null);
- boolean enabledChanging = state.value != cb.mEnabled;
- if (enabledChanging) {
- fireToggleStateChanged(cb.mEnabled);
- }
if (state.slash == null) {
state.slash = new SlashState();
state.slash.rotation = 6;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index 0be0619..f4dd415 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -36,7 +36,6 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
@@ -134,12 +133,6 @@
return new Intent(Settings.ACTION_PRIVACY_SETTINGS);
}
- @Nullable
- @Override
- public DetailAdapter getDetailAdapter() {
- return super.getDetailAdapter();
- }
-
@Override
public void onSensorBlockedChanged(int sensor, boolean blocked) {
if (sensor == getSensorId()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
index 076ef35..5840a3d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
@@ -150,6 +150,6 @@
}
protected int getFontSizeDimen() {
- return R.dimen.qs_detail_item_secondary_text_size;
+ return R.dimen.qs_tile_text_size;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
deleted file mode 100644
index db1b6e6..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.qs.tiles;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
-import android.util.Pair;
-import android.view.View;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
-import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.plugins.qs.QSTile.State;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-
-import javax.inject.Inject;
-
-public class UserTile extends QSTileImpl<State> implements UserInfoController.OnUserInfoChangedListener {
-
- private final UserSwitcherController mUserSwitcherController;
- private final UserInfoController mUserInfoController;
- @Nullable
- private Pair<String, Drawable> mLastUpdate;
-
- @Inject
- public UserTile(
- QSHost host,
- @Background Looper backgroundLooper,
- @Main Handler mainHandler,
- FalsingManager falsingManager,
- MetricsLogger metricsLogger,
- StatusBarStateController statusBarStateController,
- ActivityStarter activityStarter,
- QSLogger qsLogger,
- UserSwitcherController userSwitcherController,
- UserInfoController userInfoController
- ) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
- statusBarStateController, activityStarter, qsLogger);
- mUserSwitcherController = userSwitcherController;
- mUserInfoController = userInfoController;
- mUserInfoController.observe(getLifecycle(), this);
- }
-
- @Override
- public State newTileState() {
- return new QSTile.State();
- }
-
- @Override
- public Intent getLongClickIntent() {
- return new Intent(Settings.ACTION_USER_SETTINGS);
- }
-
- @Override
- protected void handleClick(@Nullable View view) {
- showDetail(true);
- }
-
- @Override
- public DetailAdapter getDetailAdapter() {
- return mUserSwitcherController.mUserDetailAdapter;
- }
-
- @Override
- public int getMetricsCategory() {
- return MetricsEvent.QS_USER_TILE;
- }
-
- @Override
- public CharSequence getTileLabel() {
- return getState().label;
- }
-
- @Override
- protected void handleUpdateState(State state, Object arg) {
- final Pair<String, Drawable> p = arg != null ? (Pair<String, Drawable>) arg : mLastUpdate;
- if (p != null) {
- state.label = p.first;
- // TODO: Better content description.
- state.contentDescription = p.first;
- state.icon = new Icon() {
- @Override
- public Drawable getDrawable(Context context) {
- return p.second;
- }
- };
- } else {
- // TODO: Default state.
- }
- }
-
- @Override
- public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
- mLastUpdate = new Pair<>(name, picture);
- refreshState(mLastUpdate);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index c82ff34..b2be56cc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -28,27 +28,22 @@
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
-import android.view.ViewGroup;
import android.widget.Switch;
import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settingslib.wifi.AccessPoint;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSIconView;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.SignalState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.AlphaControlledSignalTileView;
-import com.android.systemui.qs.QSDetailItems;
-import com.android.systemui.qs.QSDetailItems.Item;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSIconViewImpl;
@@ -58,9 +53,6 @@
import com.android.systemui.statusbar.connectivity.SignalCallback;
import com.android.systemui.statusbar.connectivity.WifiIcons;
import com.android.systemui.statusbar.connectivity.WifiIndicators;
-import com.android.wifitrackerlib.WifiEntry;
-
-import java.util.List;
import javax.inject.Inject;
@@ -70,7 +62,6 @@
protected final NetworkController mController;
private final AccessPointController mWifiController;
- private final WifiDetailAdapter mDetailAdapter;
private final QSTile.SignalState mStateBeforeClick = newTileState();
protected final WifiSignalCallback mSignalCallback = new WifiSignalCallback();
@@ -93,7 +84,6 @@
statusBarStateController, activityStarter, qsLogger);
mController = networkController;
mWifiController = accessPointController;
- mDetailAdapter = (WifiDetailAdapter) createDetailAdapter();
mController.observe(getLifecycle(), mSignalCallback);
mStateBeforeClick.spec = "wifi";
}
@@ -104,25 +94,6 @@
}
@Override
- public void setDetailListening(boolean listening) {
- if (listening) {
- mWifiController.addAccessPointCallback(mDetailAdapter);
- } else {
- mWifiController.removeAccessPointCallback(mDetailAdapter);
- }
- }
-
- @Override
- public DetailAdapter getDetailAdapter() {
- return mDetailAdapter;
- }
-
- @Override
- protected DetailAdapter createDetailAdapter() {
- return new WifiDetailAdapter();
- }
-
- @Override
public QSIconView createTileView(Context context) {
return new AlphaControlledSignalTileView(context);
}
@@ -158,7 +129,6 @@
new Intent(Settings.ACTION_WIFI_SETTINGS), 0);
return;
}
- showDetail(true);
if (!mState.value) {
mController.setWifiEnabled(true);
}
@@ -185,11 +155,6 @@
&& (cb.ssid != null || cb.wifiSignalIconId != WifiIcons.QS_WIFI_NO_NETWORK);
boolean wifiNotConnected = (cb.ssid == null)
&& (cb.wifiSignalIconId == WifiIcons.QS_WIFI_NO_NETWORK);
- boolean enabledChanging = state.value != cb.enabled;
- if (enabledChanging) {
- mDetailAdapter.setItemsVisible(cb.enabled);
- fireToggleStateChanged(cb.enabled);
- }
if (state.slash == null) {
state.slash = new SlashState();
state.slash.rotation = 6;
@@ -315,150 +280,7 @@
mInfo.wifiSignalContentDescription = indicators.qsIcon.contentDescription;
mInfo.isTransient = indicators.isTransient;
mInfo.statusLabel = indicators.statusLabel;
- if (isShowingDetail()) {
- mDetailAdapter.updateItems();
- }
refreshState();
}
}
-
- protected class WifiDetailAdapter implements DetailAdapter,
- AccessPointController.AccessPointCallback, QSDetailItems.Callback {
-
- @Nullable
- private QSDetailItems mItems;
- @Nullable
- private WifiEntry[] mAccessPoints;
-
- @Override
- public CharSequence getTitle() {
- return mContext.getString(R.string.quick_settings_wifi_label);
- }
-
- public Intent getSettingsIntent() {
- return WIFI_SETTINGS;
- }
-
- @Override
- public Boolean getToggleState() {
- return mState.value;
- }
-
- @Override
- public void setToggleState(boolean state) {
- if (DEBUG) Log.d(TAG, "setToggleState " + state);
- MetricsLogger.action(mContext, MetricsEvent.QS_WIFI_TOGGLE, state);
- mController.setWifiEnabled(state);
- }
-
- @Override
- public int getMetricsCategory() {
- return MetricsEvent.QS_WIFI_DETAILS;
- }
-
- @Override
- public View createDetailView(Context context, View convertView, ViewGroup parent) {
- if (DEBUG) Log.d(TAG, "createDetailView convertView=" + (convertView != null));
- mAccessPoints = null;
- mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
- mItems.setTagSuffix("Wifi");
- mItems.setCallback(this);
- mWifiController.scanForAccessPoints(); // updates APs and items
- setItemsVisible(mState.value);
- return mItems;
- }
-
- @Override
- public void onAccessPointsChanged(final List<WifiEntry> accessPoints) {
- mAccessPoints = filterUnreachableAPs(accessPoints);
-
- updateItems();
- }
-
- /** Filter unreachable APs from mAccessPoints */
- private WifiEntry[] filterUnreachableAPs(List<WifiEntry> unfiltered) {
- int numReachable = 0;
- for (WifiEntry ap : unfiltered) {
- if (isWifiEntryReachable(ap)) numReachable++;
- }
- if (numReachable != unfiltered.size()) {
- WifiEntry[] accessPoints = new WifiEntry[numReachable];
- int i = 0;
- for (WifiEntry ap : unfiltered) {
- if (isWifiEntryReachable(ap)) accessPoints[i++] = ap;
- }
- return accessPoints;
- }
- return unfiltered.toArray(new WifiEntry[0]);
- }
-
- @Override
- public void onSettingsActivityTriggered(Intent settingsIntent) {
- mActivityStarter.postStartActivityDismissingKeyguard(settingsIntent, 0);
- }
-
- @Override
- public void onDetailItemClick(Item item) {
- if (item == null || item.tag == null) return;
- final WifiEntry ap = (WifiEntry) item.tag;
- if (ap.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED) {
- if (mWifiController.connect(ap)) {
- mHost.collapsePanels();
- }
- }
- showDetail(false);
- }
-
- @Override
- public void onDetailItemDisconnect(Item item) {
- // noop
- }
-
- public void setItemsVisible(boolean visible) {
- if (mItems == null) return;
- mItems.setItemsVisible(visible);
- }
-
- private void updateItems() {
- if (mItems == null) return;
- if ((mAccessPoints != null && mAccessPoints.length > 0)
- || !mSignalCallback.mInfo.enabled) {
- fireScanStateChanged(false);
- } else {
- fireScanStateChanged(true);
- }
-
- // Wi-Fi is off
- if (!mSignalCallback.mInfo.enabled) {
- mItems.setEmptyState(WifiIcons.QS_WIFI_NO_NETWORK,
- R.string.wifi_is_off);
- mItems.setItems(null);
- return;
- }
-
- // No available access points
- mItems.setEmptyState(WifiIcons.QS_WIFI_NO_NETWORK,
- R.string.quick_settings_wifi_detail_empty_text);
-
- // Build the list
- Item[] items = null;
- if (mAccessPoints != null) {
- items = new Item[mAccessPoints.length];
- for (int i = 0; i < mAccessPoints.length; i++) {
- final WifiEntry ap = mAccessPoints[i];
- final Item item = new Item(mWifiController.getIcon(ap), ap.getSsid(), ap);
- item.line2 = ap.getSummary();
- item.icon2 = ap.getSecurity() != AccessPoint.SECURITY_NONE
- ? R.drawable.qs_ic_wifi_lock
- : -1;
- items[i] = item;
- }
- }
- mItems.setItems(items);
- }
- }
-
- private static boolean isWifiEntryReachable(WifiEntry ap) {
- return ap.getLevel() != WifiEntry.WIFI_LEVEL_UNREACHABLE;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index 7c8f4b15..8c8c5c8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -24,11 +24,13 @@
import android.view.LayoutInflater
import android.view.View
import androidx.annotation.VisibleForTesting
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.R
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.QSUserSwitcherEvent
import com.android.systemui.qs.tiles.UserDetailView
import com.android.systemui.statusbar.phone.SystemUIDialog
import javax.inject.Inject
@@ -43,6 +45,7 @@
private val activityStarter: ActivityStarter,
private val falsingManager: FalsingManager,
private val dialogLaunchAnimator: DialogLaunchAnimator,
+ private val uiEventLogger: UiEventLogger,
private val dialogFactory: (Context) -> SystemUIDialog
) {
@@ -51,12 +54,14 @@
userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>,
activityStarter: ActivityStarter,
falsingManager: FalsingManager,
- dialogLaunchAnimator: DialogLaunchAnimator
+ dialogLaunchAnimator: DialogLaunchAnimator,
+ uiEventLogger: UiEventLogger
) : this(
userDetailViewAdapterProvider,
activityStarter,
falsingManager,
dialogLaunchAnimator,
+ uiEventLogger,
{ SystemUIDialog(it) }
)
@@ -76,10 +81,13 @@
setCanceledOnTouchOutside(true)
setTitle(R.string.qs_user_switch_dialog_title)
- setPositiveButton(R.string.quick_settings_done, null)
+ setPositiveButton(R.string.quick_settings_done) { _, _ ->
+ uiEventLogger.log(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE)
+ }
setNeutralButton(R.string.quick_settings_more_user_settings) { _, _ ->
if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
+ uiEventLogger.log(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS)
activityStarter.postStartActivityDismissingKeyguard(
USER_SETTINGS_INTENT,
0
@@ -95,6 +103,7 @@
adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid))
dialogLaunchAnimator.showFromView(this, view)
+ uiEventLogger.log(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN)
adapter.injectDialogShower(DialogShowerImpl(this, dialogLaunchAnimator))
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 30456a8..50765f2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -51,7 +51,9 @@
import android.graphics.Insets;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
-import android.media.MediaActionSound;
+import android.media.AudioAttributes;
+import android.media.AudioSystem;
+import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
@@ -93,6 +95,7 @@
import com.google.common.util.concurrent.ListenableFuture;
+import java.io.File;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
@@ -248,7 +251,7 @@
private final WindowManager mWindowManager;
private final WindowManager.LayoutParams mWindowLayoutParams;
private final AccessibilityManager mAccessibilityManager;
- private final MediaActionSound mCameraSound;
+ private final MediaPlayer mCameraSound;
private final ScrollCaptureClient mScrollCaptureClient;
private final PhoneWindow mWindow;
private final DisplayManager mDisplayManager;
@@ -331,8 +334,13 @@
reloadAssets();
// Setup the Camera shutter sound
- mCameraSound = new MediaActionSound();
- mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
+ mCameraSound = MediaPlayer.create(mContext,
+ Uri.fromFile(new File(mContext.getResources().getString(
+ com.android.internal.R.string.config_cameraShutterSound))), null,
+ new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .build(), AudioSystem.newAudioSessionId());
mCopyBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -430,7 +438,9 @@
void releaseContext() {
mContext.unregisterReceiver(mCopyBroadcastReceiver);
mContext.release();
- mCameraSound.release();
+ if (mCameraSound != null) {
+ mCameraSound.release();
+ }
mBgExecutor.shutdownNow();
}
@@ -806,7 +816,9 @@
*/
private void saveScreenshotAndToast(Consumer<Uri> finisher) {
// Play the shutter sound to notify that we've taken a screenshot
- mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
+ if (mCameraSound != null) {
+ mCameraSound.start();
+ }
saveScreenshotInWorkerThread(
/* onComplete */ finisher,
@@ -840,7 +852,9 @@
mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash);
// Play the shutter sound to notify that we've taken a screenshot
- mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
+ if (mCameraSound != null) {
+ mCameraSound.start();
+ }
if (DEBUG_ANIM) {
Log.d(TAG, "starting post-screenshot animation");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 2f5eaa6..b355b05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -453,7 +453,9 @@
/** @see IStatusBar#updateMediaTapToTransferReceiverDisplay */
default void updateMediaTapToTransferReceiverDisplay(
@StatusBarManager.MediaTransferReceiverState int displayState,
- @NonNull MediaRoute2Info routeInfo) {}
+ @NonNull MediaRoute2Info routeInfo,
+ @Nullable Icon appIcon,
+ @Nullable CharSequence appName) {}
}
public CommandQueue(Context context) {
@@ -1208,10 +1210,14 @@
@Override
public void updateMediaTapToTransferReceiverDisplay(
int displayState,
- MediaRoute2Info routeInfo) {
+ @NonNull MediaRoute2Info routeInfo,
+ @Nullable Icon appIcon,
+ @Nullable CharSequence appName) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = displayState;
args.arg2 = routeInfo;
+ args.arg3 = appIcon;
+ args.arg4 = appName;
mHandler.obtainMessage(MSG_MEDIA_TRANSFER_RECEIVER_STATE, args).sendToTarget();
}
@@ -1629,9 +1635,11 @@
args = (SomeArgs) msg.obj;
int receiverDisplayState = (int) args.arg1;
MediaRoute2Info receiverRouteInfo = (MediaRoute2Info) args.arg2;
+ Icon appIcon = (Icon) args.arg3;
+ appName = (CharSequence) args.arg4;
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).updateMediaTapToTransferReceiverDisplay(
- receiverDisplayState, receiverRouteInfo);
+ receiverDisplayState, receiverRouteInfo, appIcon, appName);
}
args.recycle();
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
index 8e6cf36..4d933d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
@@ -31,6 +31,8 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry.OnSensitivityChangedListener;
+import java.util.ArrayList;
+
/**
* The view in the statusBar that contains part of the heads-up information
@@ -161,8 +163,8 @@
return mIconDrawingRect;
}
- public void onDarkChanged(Rect area, float darkIntensity, int tint) {
- mTextView.setTextColor(DarkIconDispatcher.getTint(area, this, tint));
+ public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+ mTextView.setTextColor(DarkIconDispatcher.getTint(areas, this, tint));
}
public void setOnDrawingRectChangedListener(Runnable onDrawingRectChangedListener) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index e19fd7a..01bdb40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -29,7 +29,9 @@
import android.service.notification.StatusBarNotification;
import android.util.Log;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
@@ -42,10 +44,13 @@
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.Executor;
+import javax.inject.Inject;
+
/**
* This class handles listening to notification updates and passing them along to
* NotificationPresenter to be displayed to the user.
*/
+@SysUISingleton
@SuppressLint("OverrideAbstract")
public class NotificationListener extends NotificationListenerWithPlugins {
private static final String TAG = "NotificationListener";
@@ -66,11 +71,14 @@
/**
* Injected constructor. See {@link StatusBarModule}.
*/
+ @Inject
public NotificationListener(
Context context,
NotificationManager notificationManager,
SystemClock systemClock,
- @Main Executor mainExecutor) {
+ @Main Executor mainExecutor,
+ PluginManager pluginManager) {
+ super(pluginManager);
mContext = context;
mNotificationManager = notificationManager;
mSystemClock = systemClock;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 4a7606c..72c4ce8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -37,6 +37,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Parcelable;
+import android.os.Trace;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
@@ -60,6 +61,7 @@
import com.android.systemui.util.drawable.DrawableSize;
import java.text.NumberFormat;
+import java.util.ArrayList;
import java.util.Arrays;
public class StatusBarIconView extends AnimatedImageView implements StatusIconDisplayable {
@@ -370,10 +372,13 @@
}
Drawable drawable;
try {
+ Trace.beginSection("StatusBarIconView#updateDrawable()");
drawable = getIcon(mIcon);
} catch (OutOfMemoryError e) {
Log.w(TAG, "OOM while inflating " + mIcon.icon + " for slot " + mSlot);
return false;
+ } finally {
+ Trace.endSection();
}
if (drawable == null) {
@@ -961,8 +966,8 @@
}
@Override
- public void onDarkChanged(Rect area, float darkIntensity, int tint) {
- int areaTint = getTint(area, this, tint);
+ public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+ int areaTint = getTint(areas, this, tint);
ColorStateList color = ColorStateList.valueOf(areaTint);
setImageTintList(color);
setDecorColor(areaTint);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index 68dcdd9..465ab93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar;
import static com.android.systemui.plugins.DarkIconDispatcher.getTint;
-import static com.android.systemui.plugins.DarkIconDispatcher.isInArea;
+import static com.android.systemui.plugins.DarkIconDispatcher.isInAreas;
import static com.android.systemui.statusbar.StatusBarIconView.STATE_DOT;
import static com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN;
import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON;
@@ -40,6 +40,8 @@
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
+import java.util.ArrayList;
+
public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
StatusIconDisplayable {
private static final String TAG = "StatusBarMobileView";
@@ -222,11 +224,11 @@
}
@Override
- public void onDarkChanged(Rect area, float darkIntensity, int tint) {
- float intensity = isInArea(area, this) ? darkIntensity : 0;
+ public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+ float intensity = isInAreas(areas, this) ? darkIntensity : 0;
mMobileDrawable.setTintList(
ColorStateList.valueOf(mDualToneHandler.getSingleColor(intensity)));
- ColorStateList color = ColorStateList.valueOf(getTint(area, this, tint));
+ ColorStateList color = ColorStateList.valueOf(getTint(areas, this, tint));
mIn.setImageTintList(color);
mOut.setImageTintList(color);
mMobileType.setImageTintList(color);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
index 6dbcc44..a6986d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar;
import static com.android.systemui.plugins.DarkIconDispatcher.getTint;
-import static com.android.systemui.plugins.DarkIconDispatcher.isInArea;
import static com.android.systemui.statusbar.StatusBarIconView.STATE_DOT;
import static com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN;
import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON;
@@ -37,6 +36,8 @@
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import java.util.ArrayList;
+
/**
* Start small: StatusBarWifiView will be able to layout from a WifiIconState
*/
@@ -235,8 +236,8 @@
}
@Override
- public void onDarkChanged(Rect area, float darkIntensity, int tint) {
- int areaTint = getTint(area, this, tint);
+ public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+ int areaTint = getTint(areas, this, tint);
ColorStateList color = ColorStateList.valueOf(areaTint);
mWifiIcon.setImageTintList(color);
mIn.setImageTintList(color);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartStatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartStatusBarModule.kt
new file mode 100644
index 0000000..46c1abb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartStatusBarModule.kt
@@ -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 com.android.systemui.statusbar.dagger
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.phone.StatusBar
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface StartStatusBarModule {
+ /** Start the StatusBar */
+ @Binds
+ @IntoMap
+ @ClassKey(StatusBar::class)
+ abstract fun bindsStatusBar(statusBar: StatusBar): CoreStartable
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index e3d0d98..c687e82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.dagger;
import android.app.IActivityManager;
-import android.app.NotificationManager;
import android.content.Context;
import android.os.Handler;
import android.service.dreams.IDreamManager;
@@ -38,7 +37,6 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.MediaArtworkProcessor;
import com.android.systemui.statusbar.NotificationClickNotifier;
-import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -167,18 +165,6 @@
/** */
@SysUISingleton
@Provides
- static NotificationListener provideNotificationListener(
- Context context,
- NotificationManager notificationManager,
- SystemClock systemClock,
- @Main Executor mainExecutor) {
- return new NotificationListener(
- context, notificationManager, systemClock, mainExecutor);
- }
-
- /** */
- @SysUISingleton
- @Provides
static SmartReplyController provideSmartReplyController(
DumpManager dumpManager,
NotificationVisibilityProvider visibilityProvider,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index e739b9f..e3ebef9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -204,7 +204,7 @@
static VisualStabilityManager provideVisualStabilityManager(
NotificationEntryManager notificationEntryManager,
VisualStabilityProvider visualStabilityProvider,
- Handler handler,
+ @Main Handler handler,
StatusBarStateController statusBarStateController,
WakefulnessLifecycle wakefulnessLifecycle,
DumpManager dumpManager) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 6a78370..d5d1cea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -19,7 +19,6 @@
import static android.app.StatusBarManager.SESSION_KEYGUARD;
import android.annotation.IntDef;
-import android.content.Context;
import android.content.res.Resources;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.fingerprint.FingerprintManager;
@@ -45,6 +44,7 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.KeyguardViewController;
import com.android.systemui.Dumpable;
+import com.android.systemui.R;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
@@ -163,7 +163,7 @@
private final KeyguardStateController mKeyguardStateController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final SessionTracker mSessionTracker;
- private final Context mContext;
+ private final int mConsecutiveFpFailureThreshold;
private final int mWakeUpDelay;
private int mMode;
private BiometricSourceType mBiometricType;
@@ -266,7 +266,7 @@
private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Inject
- public BiometricUnlockController(Context context, DozeScrimController dozeScrimController,
+ public BiometricUnlockController(DozeScrimController dozeScrimController,
KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
ShadeController shadeController,
NotificationShadeWindowController notificationShadeWindowController,
@@ -284,7 +284,6 @@
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
SessionTracker sessionTracker,
LatencyTracker latencyTracker) {
- mContext = context;
mPowerManager = powerManager;
mShadeController = shadeController;
mUpdateMonitor = keyguardUpdateMonitor;
@@ -302,6 +301,8 @@
mKeyguardStateController = keyguardStateController;
mHandler = handler;
mWakeUpDelay = resources.getInteger(com.android.internal.R.integer.config_wakeUpDelayDoze);
+ mConsecutiveFpFailureThreshold = resources.getInteger(
+ R.integer.fp_consecutive_failure_time_ms);
mKeyguardBypassController = keyguardBypassController;
mKeyguardBypassController.setUnlockController(this);
mMetricsLogger = metricsLogger;
@@ -666,8 +667,7 @@
if (biometricSourceType == BiometricSourceType.FINGERPRINT
&& mUpdateMonitor.isUdfpsSupported()) {
long currUptimeMillis = SystemClock.uptimeMillis();
- if (currUptimeMillis - mLastFpFailureUptimeMillis
- < (mStatusBarStateController.isDozing() ? 3500 : 2000)) {
+ if (currUptimeMillis - mLastFpFailureUptimeMillis < mConsecutiveFpFailureThreshold) {
mNumConsecutiveFpFailures += 1;
} else {
mNumConsecutiveFpFailures = 1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
index d06de75..150da16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -30,6 +30,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import javax.inject.Inject;
@@ -40,7 +41,7 @@
LightBarTransitionsController.DarkIntensityApplier {
private final LightBarTransitionsController mTransitionsController;
- private final Rect mTintArea = new Rect();
+ private final ArrayList<Rect> mTintAreas = new ArrayList<>();
private final ArrayMap<Object, DarkReceiver> mReceivers = new ArrayMap<>();
private int mIconTint = DEFAULT_ICON_TINT;
@@ -69,14 +70,14 @@
public void addDarkReceiver(DarkReceiver receiver) {
mReceivers.put(receiver, receiver);
- receiver.onDarkChanged(mTintArea, mDarkIntensity, mIconTint);
+ receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
}
public void addDarkReceiver(ImageView imageView) {
DarkReceiver receiver = (area, darkIntensity, tint) -> imageView.setImageTintList(
- ColorStateList.valueOf(getTint(mTintArea, imageView, mIconTint)));
+ ColorStateList.valueOf(getTint(mTintAreas, imageView, mIconTint)));
mReceivers.put(imageView, receiver);
- receiver.onDarkChanged(mTintArea, mDarkIntensity, mIconTint);
+ receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
}
public void removeDarkReceiver(DarkReceiver object) {
@@ -88,23 +89,23 @@
}
public void applyDark(DarkReceiver object) {
- mReceivers.get(object).onDarkChanged(mTintArea, mDarkIntensity, mIconTint);
+ mReceivers.get(object).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
}
/**
* Sets the dark area so {@link #applyDark} only affects the icons in the specified area.
*
- * @param darkArea the area in which icons should change it's tint, in logical screen
- * coordinates
+ * @param darkAreas the areas in which icons should change it's tint, in logical screen
+ * coordinates
*/
- public void setIconsDarkArea(Rect darkArea) {
- if (darkArea == null && mTintArea.isEmpty()) {
+ public void setIconsDarkArea(ArrayList<Rect> darkAreas) {
+ if (darkAreas == null && mTintAreas.isEmpty()) {
return;
}
- if (darkArea == null) {
- mTintArea.setEmpty();
- } else {
- mTintArea.set(darkArea);
+
+ mTintAreas.clear();
+ if (darkAreas != null) {
+ mTintAreas.addAll(darkAreas);
}
applyIconTint();
}
@@ -124,7 +125,7 @@
private void applyIconTint() {
for (int i = 0; i < mReceivers.size(); i++) {
- mReceivers.valueAt(i).onDarkChanged(mTintArea, mDarkIntensity, mIconTint);
+ mReceivers.valueAt(i).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
}
}
@@ -133,6 +134,6 @@
pw.println("DarkIconDispatcher: ");
pw.println(" mIconTint: 0x" + Integer.toHexString(mIconTint));
pw.println(" mDarkIntensity: " + mDarkIntensity + "f");
- pw.println(" mTintArea: " + mTintArea);
+ pw.println(" mTintAreas: " + mTintAreas);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index ee51efb..6dbbf0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -315,14 +315,14 @@
}
@Override
- public void onDarkChanged(Rect area, float darkIntensity, int tint) {
- setColor(DarkIconDispatcher.getTint(area, mStatusIcons, tint));
+ public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+ setColor(DarkIconDispatcher.getTint(areas, mStatusIcons, tint));
if (mWifiView != null) {
- mWifiView.onDarkChanged(area, darkIntensity, tint);
+ mWifiView.onDarkChanged(areas, darkIntensity, tint);
}
for (StatusBarMobileView view : mMobileViews) {
- view.onDarkChanged(area, darkIntensity, tint);
+ view.onDarkChanged(areas, darkIntensity, tint);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index f421d23..9863a0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -16,34 +16,37 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.OPERATOR_NAME_FRAME_VIEW;
+
import android.graphics.Rect;
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ViewClippingUtil;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.RootView;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.HeadsUpStatusBarView;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
+import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.util.ViewController;
+import java.util.Optional;
+import java.util.ArrayList;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import javax.inject.Inject;
+import javax.inject.Named;
/**
* Controls the appearance of heads up notifications in the icon area and the header itself.
@@ -69,8 +72,8 @@
private final CommandQueue mCommandQueue;
private final NotificationWakeUpCoordinator mWakeUpCoordinator;
- private View mClockView;
- private View mOperatorNameView;
+ private final View mClockView;
+ private final Optional<View> mOperatorNameViewOptional;
@VisibleForTesting
float mExpandedHeight;
@@ -86,45 +89,24 @@
}
};
private boolean mAnimationsEnabled = true;
- private KeyguardStateController mKeyguardStateController;
-
- @Inject
- public HeadsUpAppearanceController(
- NotificationIconAreaController notificationIconAreaController,
- HeadsUpManagerPhone headsUpManager,
- NotificationStackScrollLayoutController notificationStackScrollLayoutController,
- SysuiStatusBarStateController statusBarStateController,
- KeyguardBypassController keyguardBypassController,
- KeyguardStateController keyguardStateController,
- NotificationWakeUpCoordinator wakeUpCoordinator, CommandQueue commandQueue,
- NotificationPanelViewController notificationPanelViewController,
- @RootView PhoneStatusBarView statusBarView) {
- this(notificationIconAreaController, headsUpManager, statusBarStateController,
- keyguardBypassController, wakeUpCoordinator, keyguardStateController,
- commandQueue, notificationStackScrollLayoutController,
- notificationPanelViewController,
- // TODO(b/205609837): We should have the StatusBarFragmentComponent provide these
- // four views, and then we can delete this constructor and just use the one below
- // (which also removes the undesirable @VisibleForTesting).
- statusBarView.findViewById(R.id.heads_up_status_bar_view),
- statusBarView.findViewById(R.id.clock),
- statusBarView.findViewById(R.id.operator_name_frame));
- }
+ private final KeyguardStateController mKeyguardStateController;
@VisibleForTesting
+ @Inject
public HeadsUpAppearanceController(
NotificationIconAreaController notificationIconAreaController,
HeadsUpManagerPhone headsUpManager,
StatusBarStateController stateController,
KeyguardBypassController bypassController,
NotificationWakeUpCoordinator wakeUpCoordinator,
+ DarkIconDispatcher darkIconDispatcher,
KeyguardStateController keyguardStateController,
CommandQueue commandQueue,
NotificationStackScrollLayoutController stackScrollerController,
NotificationPanelViewController notificationPanelViewController,
HeadsUpStatusBarView headsUpStatusBarView,
- View clockView,
- View operatorNameView) {
+ Clock clockView,
+ @Named(OPERATOR_NAME_FRAME_VIEW) Optional<View> operatorNameViewOptional) {
super(headsUpStatusBarView);
mNotificationIconAreaController = notificationIconAreaController;
mHeadsUpManager = headsUpManager;
@@ -141,8 +123,8 @@
mNotificationPanelViewController = notificationPanelViewController;
mStackScrollerController.setHeadsUpAppearanceController(this);
mClockView = clockView;
- mOperatorNameView = operatorNameView;
- mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
+ mOperatorNameViewOptional = operatorNameViewOptional;
+ mDarkIconDispatcher = darkIconDispatcher;
mView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
@@ -232,14 +214,10 @@
mView.setVisibility(View.VISIBLE);
show(mView);
hide(mClockView, View.INVISIBLE);
- if (mOperatorNameView != null) {
- hide(mOperatorNameView, View.INVISIBLE);
- }
+ mOperatorNameViewOptional.ifPresent(view -> hide(view, View.INVISIBLE));
} else {
show(mClockView);
- if (mOperatorNameView != null) {
- show(mOperatorNameView);
- }
+ mOperatorNameViewOptional.ifPresent(this::show);
hide(mView, View.GONE, () -> {
updateParentClipping(true /* shouldClip */);
});
@@ -392,8 +370,8 @@
}
@Override
- public void onDarkChanged(Rect area, float darkIntensity, int tint) {
- mView.onDarkChanged(area, darkIntensity, tint);
+ public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+ mView.onDarkChanged(areas, darkIntensity, tint);
}
public void onStateChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 565b2d3..95a2a6e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -41,6 +41,7 @@
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.DejankUtils;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -115,7 +116,7 @@
BouncerExpansionCallback expansionCallback,
KeyguardStateController keyguardStateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardBypassController keyguardBypassController, Handler handler,
+ KeyguardBypassController keyguardBypassController, @Main Handler handler,
KeyguardSecurityModel keyguardSecurityModel,
KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory) {
mContext = context;
@@ -647,7 +648,7 @@
DismissCallbackRegistry dismissCallbackRegistry, FalsingCollector falsingCollector,
KeyguardStateController keyguardStateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardBypassController keyguardBypassController, Handler handler,
+ KeyguardBypassController keyguardBypassController, @Main Handler handler,
KeyguardSecurityModel keyguardSecurityModel,
KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory) {
mContext = context;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java
index 817b86b..9bdefcd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java
@@ -20,7 +20,6 @@
import android.service.notification.StatusBarNotification;
import android.util.Log;
-import com.android.systemui.Dependency;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
@@ -33,13 +32,15 @@
private static final String TAG = "KeyguardEnvironmentImpl";
- private final NotificationLockscreenUserManager mLockscreenUserManager =
- Dependency.get(NotificationLockscreenUserManager.class);
- private final DeviceProvisionedController mDeviceProvisionedController =
- Dependency.get(DeviceProvisionedController.class);
+ private final NotificationLockscreenUserManager mLockscreenUserManager;
+ private final DeviceProvisionedController mDeviceProvisionedController;
@Inject
- public KeyguardEnvironmentImpl() {
+ public KeyguardEnvironmentImpl(
+ NotificationLockscreenUserManager notificationLockscreenUserManager,
+ DeviceProvisionedController deviceProvisionedController) {
+ mLockscreenUserManager = notificationLockscreenUserManager;
+ mDeviceProvisionedController = deviceProvisionedController;
}
@Override // NotificationEntryManager.KeyguardEnvironment
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index b8e9875..65173a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -50,6 +50,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
/**
* The header group on Keyguard.
@@ -60,7 +61,7 @@
private static final int LAYOUT_CUTOUT = 1;
private static final int LAYOUT_NO_CUTOUT = 2;
- private final Rect mEmptyRect = new Rect(0, 0, 0, 0);
+ private final ArrayList<Rect> mEmptyTintRect = new ArrayList<>();
private boolean mShowPercentAvailable;
private boolean mBatteryCharging;
@@ -476,14 +477,14 @@
iconManager.setTint(iconColor);
}
- applyDarkness(R.id.battery, mEmptyRect, intensity, iconColor);
- applyDarkness(R.id.clock, mEmptyRect, intensity, iconColor);
+ applyDarkness(R.id.battery, mEmptyTintRect, intensity, iconColor);
+ applyDarkness(R.id.clock, mEmptyTintRect, intensity, iconColor);
}
- private void applyDarkness(int id, Rect tintArea, float intensity, int color) {
+ private void applyDarkness(int id, ArrayList<Rect> tintAreas, float intensity, int color) {
View v = findViewById(id);
if (v instanceof DarkReceiver) {
- ((DarkReceiver) v).onDarkChanged(tintArea, intensity, color);
+ ((DarkReceiver) v).onDarkChanged(tintAreas, intensity, color);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 88ae0db..4082db7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -25,6 +25,7 @@
import android.content.Context;
import android.graphics.Color;
+import android.graphics.Rect;
import android.view.InsetsFlags;
import android.view.ViewDebug;
import android.view.WindowInsetsController.Appearance;
@@ -41,6 +42,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import javax.inject.Inject;
@@ -214,27 +216,23 @@
private void updateStatus() {
final int numStacks = mAppearanceRegions.length;
- int numLightStacks = 0;
-
- // We can only have maximum one light stack.
- int indexLightStack = -1;
+ final ArrayList<Rect> lightBarBounds = new ArrayList<>();
for (int i = 0; i < numStacks; i++) {
- if (isLight(mAppearanceRegions[i].getAppearance(), mStatusBarMode,
- APPEARANCE_LIGHT_STATUS_BARS)) {
- numLightStacks++;
- indexLightStack = i;
+ final AppearanceRegion ar = mAppearanceRegions[i];
+ if (isLight(ar.getAppearance(), mStatusBarMode, APPEARANCE_LIGHT_STATUS_BARS)) {
+ lightBarBounds.add(ar.getBounds());
}
}
// If no one is light, all icons become white.
- if (numLightStacks == 0) {
+ if (lightBarBounds.isEmpty()) {
mStatusBarIconController.getTransitionsController().setIconsDark(
false, animateChange());
}
// If all stacks are light, all icons get dark.
- else if (numLightStacks == numStacks) {
+ else if (lightBarBounds.size() == numStacks) {
mStatusBarIconController.setIconsDarkArea(null);
mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
@@ -242,8 +240,7 @@
// Not the same for every stack, magic!
else {
- mStatusBarIconController.setIconsDarkArea(
- mAppearanceRegions[indexLightStack].getBounds());
+ mStatusBarIconController.setIconsDarkArea(lightBarBounds);
mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
index 357a12b..324d47e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
@@ -29,9 +29,7 @@
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.qs.FooterActionsView;
-import com.android.systemui.qs.QSDetailDisplayer;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.user.UserSwitchDialogController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -44,7 +42,6 @@
public class MultiUserSwitchController extends ViewController<MultiUserSwitch> {
private final UserManager mUserManager;
private final UserSwitcherController mUserSwitcherController;
- private final QSDetailDisplayer mQsDetailDisplayer;
private final FalsingManager mFalsingManager;
private final UserSwitchDialogController mUserSwitchDialogController;
private final ActivityStarter mActivityStarter;
@@ -66,17 +63,8 @@
mActivityStarter.startActivity(intent, true /* dismissShade */,
ActivityLaunchAnimator.Controller.fromView(v, null),
true /* showOverlockscreenwhenlocked */);
- } else if (mFeatureFlags.isEnabled(Flags.NEW_USER_SWITCHER)) {
- mUserSwitchDialogController.showDialog(v);
} else {
- View center = mView.getChildCount() > 0 ? mView.getChildAt(0) : mView;
-
- int[] tmpInt = new int[2];
- center.getLocationInWindow(tmpInt);
- tmpInt[0] += center.getWidth() / 2;
- tmpInt[1] += center.getHeight() / 2;
-
- mQsDetailDisplayer.showDetailAdapter(getUserDetailAdapter(), tmpInt[0], tmpInt[1]);
+ mUserSwitchDialogController.showDialog(v);
}
}
};
@@ -85,7 +73,6 @@
public static class Factory {
private final UserManager mUserManager;
private final UserSwitcherController mUserSwitcherController;
- private final QSDetailDisplayer mQsDetailDisplayer;
private final FalsingManager mFalsingManager;
private final UserSwitchDialogController mUserSwitchDialogController;
private final ActivityStarter mActivityStarter;
@@ -93,12 +80,11 @@
@Inject
public Factory(UserManager userManager, UserSwitcherController userSwitcherController,
- QSDetailDisplayer qsDetailDisplayer, FalsingManager falsingManager,
+ FalsingManager falsingManager,
UserSwitchDialogController userSwitchDialogController, FeatureFlags featureFlags,
ActivityStarter activityStarter) {
mUserManager = userManager;
mUserSwitcherController = userSwitcherController;
- mQsDetailDisplayer = qsDetailDisplayer;
mFalsingManager = falsingManager;
mUserSwitchDialogController = userSwitchDialogController;
mActivityStarter = activityStarter;
@@ -107,20 +93,19 @@
public MultiUserSwitchController create(FooterActionsView view) {
return new MultiUserSwitchController(view.findViewById(R.id.multi_user_switch),
- mUserManager, mUserSwitcherController, mQsDetailDisplayer,
+ mUserManager, mUserSwitcherController,
mFalsingManager, mUserSwitchDialogController, mFeatureFlags,
mActivityStarter);
}
}
private MultiUserSwitchController(MultiUserSwitch view, UserManager userManager,
- UserSwitcherController userSwitcherController, QSDetailDisplayer qsDetailDisplayer,
+ UserSwitcherController userSwitcherController,
FalsingManager falsingManager, UserSwitchDialogController userSwitchDialogController,
FeatureFlags featureFlags, ActivityStarter activityStarter) {
super(view);
mUserManager = userManager;
mUserSwitcherController = userSwitcherController;
- mQsDetailDisplayer = qsDetailDisplayer;
mFalsingManager = falsingManager;
mUserSwitchDialogController = userSwitchDialogController;
mFeatureFlags = featureFlags;
@@ -143,10 +128,6 @@
mView.setOnClickListener(null);
}
- protected DetailAdapter getUserDetailAdapter() {
- return mUserSwitcherController.mUserDetailAdapter;
- }
-
private void registerListener() {
if (mUserManager.isUserSwitcherEnabled() && mUserListener == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index 8bababf..ca6e67e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -28,7 +28,7 @@
import android.util.Log;
import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.Dependency;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -48,11 +48,14 @@
import java.util.List;
import java.util.Objects;
+import javax.inject.Inject;
+
/**
* A helper class dealing with the alert interactions between {@link NotificationGroupManagerLegacy}
* and {@link HeadsUpManager}. In particular, this class deals with keeping
* the correct notification in a group alerting based off the group suppression and alertOverride.
*/
+@SysUISingleton
public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedListener,
StateListener {
@@ -74,8 +77,7 @@
private HeadsUpManager mHeadsUpManager;
private final RowContentBindStage mRowContentBindStage;
- private final NotificationGroupManagerLegacy mGroupManager =
- Dependency.get(NotificationGroupManagerLegacy.class);
+ private final NotificationGroupManagerLegacy mGroupManager;
private NotificationEntryManager mEntryManager;
@@ -84,9 +86,14 @@
/**
* Injected constructor. See {@link StatusBarPhoneModule}.
*/
- public NotificationGroupAlertTransferHelper(RowContentBindStage bindStage) {
- Dependency.get(StatusBarStateController.class).addCallback(this);
+ @Inject
+ public NotificationGroupAlertTransferHelper(
+ RowContentBindStage bindStage,
+ StatusBarStateController statusBarStateController,
+ NotificationGroupManagerLegacy notificationGroupManagerLegacy) {
mRowContentBindStage = bindStage;
+ mGroupManager = notificationGroupManagerLegacy;
+ statusBarStateController.addCallback(this);
}
/** Causes the TransferHelper to register itself as a listener to the appropriate classes. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index c361300..e70c81d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -83,7 +83,7 @@
private NotificationIconContainer mNotificationIcons;
private NotificationIconContainer mShelfIcons;
private NotificationIconContainer mAodIcons;
- private final Rect mTintArea = new Rect();
+ private final ArrayList<Rect> mTintAreas = new ArrayList<>();
private Context mContext;
private final DemoModeController mDemoModeController;
@@ -240,17 +240,14 @@
* See {@link com.android.systemui.statusbar.policy.DarkIconDispatcher#setIconsDarkArea}.
* Sets the color that should be used to tint any icons in the notification area.
*
- * @param tintArea the area in which to tint the icons, specified in screen coordinates
+ * @param tintAreas the areas in which to tint the icons, specified in screen coordinates
* @param darkIntensity
*/
- public void onDarkChanged(Rect tintArea, float darkIntensity, int iconTint) {
- if (tintArea == null) {
- mTintArea.setEmpty();
- } else {
- mTintArea.set(tintArea);
- }
+ public void onDarkChanged(ArrayList<Rect> tintAreas, float darkIntensity, int iconTint) {
+ mTintAreas.clear();
+ mTintAreas.addAll(tintAreas);
- if (DarkIconDispatcher.isInArea(tintArea, mNotificationIconArea)) {
+ if (DarkIconDispatcher.isInAreas(tintAreas, mNotificationIconArea)) {
mIconTint = iconTint;
}
@@ -489,7 +486,7 @@
int color = StatusBarIconView.NO_COLOR;
boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mContrastColorUtil);
if (colorize) {
- color = DarkIconDispatcher.getTint(mTintArea, v, tint);
+ color = DarkIconDispatcher.getTint(mTintAreas, v, tint);
}
v.setStaticDrawableColor(color);
v.setDecorColor(tint);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
index c68d39b..3811689 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
@@ -22,7 +22,6 @@
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
-import com.android.systemui.Dependency;
import com.android.systemui.plugins.NotificationListenerController;
import com.android.systemui.plugins.NotificationListenerController.NotificationProvider;
import com.android.systemui.plugins.PluginListener;
@@ -30,6 +29,8 @@
import java.util.ArrayList;
+import javax.inject.Inject;
+
/**
* A version of NotificationListenerService that passes all info to
* any plugins connected. Also allows those plugins the chance to cancel
@@ -40,19 +41,25 @@
private ArrayList<NotificationListenerController> mPlugins = new ArrayList<>();
private boolean mConnected;
+ private PluginManager mPluginManager;
+
+ @Inject
+ public NotificationListenerWithPlugins(PluginManager pluginManager) {
+ super();
+ mPluginManager = pluginManager;
+ }
@Override
public void registerAsSystemService(Context context, ComponentName componentName,
int currentUser) throws RemoteException {
super.registerAsSystemService(context, componentName, currentUser);
- Dependency.get(PluginManager.class).addPluginListener(this,
- NotificationListenerController.class);
+ mPluginManager.addPluginListener(this, NotificationListenerController.class);
}
@Override
public void unregisterAsSystemService() throws RemoteException {
super.unregisterAsSystemService();
- Dependency.get(PluginManager.class).removePluginListener(this);
+ mPluginManager.removePluginListener(this);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 62a96ad..d93c013 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -113,7 +113,6 @@
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
import com.android.systemui.DejankUtils;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.Interpolators;
@@ -147,12 +146,10 @@
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.FalsingManager.FalsingTapListener;
-import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
-import com.android.systemui.qs.QSDetailDisplayer;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
@@ -326,7 +323,6 @@
private final KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory;
private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
private final IdleViewComponent.Factory mIdleViewComponentFactory;
- private final QSDetailDisplayer mQSDetailDisplayer;
private final FragmentService mFragmentService;
private final ScrimController mScrimController;
private final PrivacyDotViewController mPrivacyDotViewController;
@@ -774,7 +770,6 @@
CommunalViewComponent.Factory communalViewComponentFactory,
IdleViewComponent.Factory idleViewComponentFactory,
LockscreenShadeTransitionController lockscreenShadeTransitionController,
- QSDetailDisplayer qsDetailDisplayer,
NotificationGroupManagerLegacy groupManager,
NotificationIconAreaController notificationIconAreaController,
AuthController authController,
@@ -804,6 +799,7 @@
ControlsComponent controlsComponent,
InteractionJankMonitor interactionJankMonitor,
QsFrameTranslateController qsFrameTranslateController,
+ SysUiState sysUiState,
KeyguardUnlockAnimationController keyguardUnlockAnimationController) {
super(view,
falsingManager,
@@ -848,7 +844,6 @@
mContentResolver = contentResolver;
mKeyguardQsUserSwitchComponentFactory = keyguardQsUserSwitchComponentFactory;
mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory;
- mQSDetailDisplayer = qsDetailDisplayer;
mFragmentService = fragmentService;
mSettingsChangeObserver = new SettingsChangeObserver(handler);
mShouldUseSplitNotificationShade =
@@ -876,8 +871,7 @@
mUiExecutor = uiExecutor;
mSecureSettings = secureSettings;
mInteractionJankMonitor = interactionJankMonitor;
- // TODO: inject via dagger instead of Dependency
- mSysUiState = Dependency.get(SysUiState.class);
+ mSysUiState = sysUiState;
pulseExpansionHandler.setPulseExpandAbortListener(() -> {
if (mQs != null) {
mQs.animateHeaderSlidingOut();
@@ -1137,7 +1131,6 @@
mKeyguardQsUserSwitchComponentFactory.build(userAvatarView);
mKeyguardQsUserSwitchController =
userSwitcherComponent.getKeyguardQsUserSwitchController();
- mKeyguardQsUserSwitchController.setNotificationPanelViewController(this);
mKeyguardQsUserSwitchController.init();
mKeyguardStatusBarViewController.setKeyguardUserSwitcherEnabled(true);
} else if (keyguardUserSwitcherView != null) {
@@ -1849,18 +1842,6 @@
}
}
- public void expandWithQsDetail(DetailAdapter qsDetailAdapter) {
- traceQsJank(true /* startTracing */, false /* wasCancelled */);
- flingSettings(0 /* velocity */, FLING_EXPAND);
- // When expanding with a panel, there's no meaningful touch point to correspond to. Set the
- // origin to somewhere above the screen. This is used for animations.
- int x = mQsFrame.getWidth() / 2;
- mQSDetailDisplayer.showDetailAdapter(qsDetailAdapter, x, -getHeight());
- if (mAccessibilityManager.isEnabled()) {
- mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
- }
- }
-
public void expandWithoutQs() {
if (isQsExpanded()) {
flingSettings(0 /* velocity */, FLING_COLLAPSE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 716e8c7..f13334e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1110,6 +1110,12 @@
}
private void onFoldedStateChanged(boolean isFolded, boolean willGoToSleep) {
+ Trace.beginSection("StatusBar#onFoldedStateChanged");
+ onFoldedStateChangedInternal(isFolded, willGoToSleep);
+ Trace.endSection();
+ }
+
+ private void onFoldedStateChangedInternal(boolean isFolded, boolean willGoToSleep) {
// Folded state changes are followed by a screen off event.
// By default turning off the screen also closes the shade.
// We want to make sure that the shade status is kept after
@@ -3672,6 +3678,7 @@
@Override
public void onScreenTurnedOff() {
+ Trace.beginSection("StatusBar#onScreenTurnedOff");
mFalsingCollector.onScreenOff();
mScrimController.onScreenTurnedOff();
if (mCloseQsBeforeScreenOff) {
@@ -3679,6 +3686,7 @@
mCloseQsBeforeScreenOff = false;
}
updateIsKeyguard();
+ Trace.endSection();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
index cf9a5db..4081962 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
@@ -235,11 +235,6 @@
// Settings are not available in setup
if (!mDeviceProvisionedController.isCurrentUserSetup()) return;
-
- QSPanelController qsPanelController = mStatusBar.getQSPanelController();
- if (subPanel != null && qsPanelController != null) {
- qsPanelController.openDetails(subPanel);
- }
mNotificationPanelViewController.expandWithQs();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 88a7dc7..c5d3937 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -27,7 +27,6 @@
import android.view.ViewGroup;
import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
@@ -74,17 +73,19 @@
Context context,
CommandQueue commandQueue,
DemoModeController demoModeController,
+ ConfigurationController configurationController,
+ TunerService tunerService,
DumpManager dumpManager) {
super(context.getResources().getStringArray(
com.android.internal.R.array.config_statusBarIcons));
- Dependency.get(ConfigurationController.class).addCallback(this);
+ configurationController.addCallback(this);
mContext = context;
loadDimens();
commandQueue.addCallback(this);
- Dependency.get(TunerService.class).addTunable(this, ICON_HIDE_LIST);
+ tunerService.addTunable(this, ICON_HIDE_LIST);
demoModeController.addCallback(this);
dumpManager.registerDumpable(getClass().getSimpleName(), this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java
deleted file mode 100644
index 79d72b3..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone.dagger;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.notification.row.RowContentBindStage;
-import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
-import com.android.systemui.statusbar.phone.StatusBar;
-
-import dagger.Module;
-import dagger.Provides;
-
-/**
- * This module provides instances needed to construct {@link StatusBar}. These are moved to this
- * separate from {@link StatusBarPhoneModule} module so that components that wish to build their own
- * version of StatusBar can include just dependencies, without injecting StatusBar itself.
- */
-@Module
-public interface StatusBarPhoneDependenciesModule {
-
- /** */
- @SysUISingleton
- @Provides
- static NotificationGroupAlertTransferHelper provideNotificationGroupAlertTransferHelper(
- RowContentBindStage bindStage) {
- return new NotificationGroupAlertTransferHelper(bindStage);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index d3ff4a7..83bdd1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -129,7 +129,7 @@
/**
* Dagger Module providing {@link StatusBar}.
*/
-@Module(includes = {StatusBarPhoneDependenciesModule.class})
+@Module
public interface StatusBarPhoneModule {
/**
* Provides our instance of StatusBar which is considered optional.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index e2dc905..d5f5362 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -21,6 +21,7 @@
import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.dagger.qualifiers.RootView;
+import com.android.systemui.statusbar.HeadsUpStatusBarView;
import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
@@ -32,6 +33,8 @@
import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.window.StatusBarWindowController;
+import java.util.Optional;
+
import javax.inject.Named;
import dagger.Binds;
@@ -44,6 +47,7 @@
String LIGHTS_OUT_NOTIF_VIEW = "lights_out_notif_view";
String OPERATOR_NAME_VIEW = "operator_name_view";
+ String OPERATOR_NAME_FRAME_VIEW = "operator_name_frame_view";
/** */
@Provides
@@ -80,6 +84,14 @@
/** */
@Provides
@StatusBarFragmentScope
+ @Named(OPERATOR_NAME_FRAME_VIEW)
+ static Optional<View> provideOperatorFrameNameView(@RootView PhoneStatusBarView view) {
+ return Optional.ofNullable(view.findViewById(R.id.operator_name_frame));
+ }
+
+ /** */
+ @Provides
+ @StatusBarFragmentScope
static Clock provideClock(@RootView PhoneStatusBarView view) {
return view.findViewById(R.id.clock);
}
@@ -119,4 +131,11 @@
) {
return new PhoneStatusBarTransitions(view, statusBarWindowController.getBackgroundView());
}
+
+ /** */
+ @Provides
+ @StatusBarFragmentScope
+ static HeadsUpStatusBarView providesHeasdUpStatusBarView(@RootView PhoneStatusBarView view) {
+ return view.findViewById(R.id.heads_up_status_bar_view);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
index 2dbc19c..b0f7629 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
@@ -19,12 +19,19 @@
import android.graphics.drawable.Drawable
import android.os.UserManager
-import com.android.systemui.DejankUtils.whitelistIpcs
+import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.policy.CallbackController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.concurrent.Executor
+
import javax.inject.Inject
/**
@@ -34,8 +41,11 @@
@SysUISingleton
class StatusBarUserInfoTracker @Inject constructor(
private val userInfoController: UserInfoController,
- private val userManager: UserManager
-) : CallbackController<CurrentUserChipInfoUpdatedListener> {
+ private val userManager: UserManager,
+ private val dumpManager: DumpManager,
+ @Main private val mainExecutor: Executor,
+ @Background private val backgroundExecutor: Executor
+) : CallbackController<CurrentUserChipInfoUpdatedListener>, Dumpable {
var currentUserName: String? = null
private set
var currentUserAvatar: Drawable? = null
@@ -53,7 +63,7 @@
}
init {
- startListening()
+ dumpManager.registerDumpable(TAG, this)
}
override fun addCallback(listener: CurrentUserChipInfoUpdatedListener) {
@@ -96,27 +106,33 @@
userInfoController.removeCallback(userInfoChangedListener)
}
- private fun checkUserSwitcherEnabled() {
- whitelistIpcs {
- userSwitcherEnabled = userManager.isUserSwitcherEnabled
- }
- }
-
/**
* Force a check to [UserManager.isUserSwitcherEnabled], and update listeners if the value has
* changed
*/
fun checkEnabled() {
- val wasEnabled = userSwitcherEnabled
- checkUserSwitcherEnabled()
+ backgroundExecutor.execute {
+ // Check on a background thread to avoid main thread Binder calls
+ val wasEnabled = userSwitcherEnabled
+ userSwitcherEnabled = userManager.isUserSwitcherEnabled
- if (wasEnabled != userSwitcherEnabled) {
- notifyListenersSettingChanged()
+ if (wasEnabled != userSwitcherEnabled) {
+ mainExecutor.execute {
+ notifyListenersSettingChanged()
+ }
+ }
}
}
+
+ override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+ pw.println(" userSwitcherEnabled=$userSwitcherEnabled")
+ pw.println(" listening=$listening")
+ }
}
interface CurrentUserChipInfoUpdatedListener {
fun onCurrentUserChipInfoUpdated()
fun onStatusBarUserSwitcherSettingChanged(enabled: Boolean) {}
}
+
+private const val TAG = "StatusBarUserInfoTracker"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
index a124753..909261f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
@@ -16,9 +16,15 @@
package com.android.systemui.statusbar.phone.userswitcher
+import android.content.Intent
import android.view.View
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController
+import com.android.systemui.user.UserSwitcherActivity
import com.android.systemui.util.ViewController
import javax.inject.Inject
@@ -30,7 +36,9 @@
view: StatusBarUserSwitcherContainer,
private val tracker: StatusBarUserInfoTracker,
private val featureController: StatusBarUserSwitcherFeatureController,
- private val userSwitcherDialogController: UserSwitchDialogController
+ private val userSwitcherDialogController: UserSwitchDialogController,
+ private val featureFlags: FeatureFlags,
+ private val activityStarter: ActivityStarter
) : ViewController<StatusBarUserSwitcherContainer>(view),
StatusBarUserSwitcherController {
private val listener = object : CurrentUserChipInfoUpdatedListener {
@@ -52,8 +60,17 @@
override fun onViewAttached() {
tracker.addCallback(listener)
featureController.addCallback(featureFlagListener)
- mView.setOnClickListener {
- userSwitcherDialogController.showDialog(it)
+ mView.setOnClickListener { view: View ->
+ if (featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
+ val intent = Intent(context, UserSwitcherActivity::class.java)
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
+
+ activityStarter.startActivity(intent, true /* dismissShade */,
+ ActivityLaunchAnimator.Controller.fromView(view, null),
+ true /* showOverlockscreenwhenlocked */)
+ } else {
+ userSwitcherDialogController.showDialog(view)
+ }
}
updateEnabled()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 97d344a..562816f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -56,6 +56,7 @@
import com.android.systemui.tuner.TunerService.Tunable;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
@@ -314,8 +315,8 @@
}
@Override
- public void onDarkChanged(Rect area, float darkIntensity, int tint) {
- mNonAdaptedColor = DarkIconDispatcher.getTint(area, this, tint);
+ public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+ mNonAdaptedColor = DarkIconDispatcher.getTint(areas, this, tint);
setTextColor(mNonAdaptedColor);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
index d903639..1d414745 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.hardware.devicestate.DeviceStateManager;
+import android.os.Trace;
import android.util.Log;
import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
@@ -117,11 +118,16 @@
private void updateDeviceState(int state) {
Log.v(TAG, "updateDeviceState [state=" + state + "]");
- if (mDeviceState == state) {
- return;
- }
+ Trace.beginSection("updateDeviceState [state=" + state + "]");
+ try {
+ if (mDeviceState == state) {
+ return;
+ }
- readPersistedSetting(state);
+ readPersistedSetting(state);
+ } finally {
+ Trace.endSection();
+ }
}
private void readPersistedSetting(int state) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index b591545..7e2488f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -37,12 +37,9 @@
import com.android.systemui.R;
import com.android.systemui.communal.CommunalStateController;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.tiles.UserDetailView;
import com.android.systemui.qs.user.UserSwitchDialogController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
@@ -51,13 +48,11 @@
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.UserAvatarView;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
-import javax.inject.Provider;
/**
* Manages the user switch on the Keyguard that is used for opening the QS user panel.
@@ -81,11 +76,8 @@
protected final SysuiStatusBarStateController mStatusBarStateController;
private final ConfigurationController mConfigurationController;
private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
- private final KeyguardUserDetailAdapter mUserDetailAdapter;
- private final FeatureFlags mFeatureFlags;
private final UserSwitchDialogController mUserSwitchDialogController;
private final UiEventLogger mUiEventLogger;
- private NotificationPanelViewController mNotificationPanelViewController;
private UserAvatarView mUserAvatarView;
UserSwitcherController.UserRecord mCurrentUser;
@@ -133,9 +125,7 @@
ConfigurationController configurationController,
SysuiStatusBarStateController statusBarStateController,
DozeParameters dozeParameters,
- Provider<UserDetailView.Adapter> userDetailViewAdapterProvider,
ScreenOffAnimationController screenOffAnimationController,
- FeatureFlags featureFlags,
UserSwitchDialogController userSwitchDialogController,
UiEventLogger uiEventLogger) {
super(view);
@@ -152,8 +142,6 @@
keyguardStateController, dozeParameters,
screenOffAnimationController, /* animateYPos= */ false,
/* visibleOnCommunal= */ false);
- mUserDetailAdapter = new KeyguardUserDetailAdapter(context, userDetailViewAdapterProvider);
- mFeatureFlags = featureFlags;
mUserSwitchDialogController = userSwitchDialogController;
mUiEventLogger = uiEventLogger;
}
@@ -182,11 +170,7 @@
mUiEventLogger.log(
LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP);
- if (mFeatureFlags.isEnabled(Flags.NEW_USER_SWITCHER)) {
- mUserSwitchDialogController.showDialog(mView);
- } else {
- openQsUserPanel();
- }
+ mUserSwitchDialogController.showDialog(mView);
});
mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
@@ -331,40 +315,4 @@
private boolean isListAnimating() {
return mKeyguardVisibilityHelper.isVisibilityAnimating();
}
-
- private void openQsUserPanel() {
- mNotificationPanelViewController.expandWithQsDetail(mUserDetailAdapter);
- }
-
- public void setNotificationPanelViewController(
- NotificationPanelViewController notificationPanelViewController) {
- mNotificationPanelViewController = notificationPanelViewController;
- }
-
- class KeyguardUserDetailAdapter extends UserSwitcherController.UserDetailAdapter {
- KeyguardUserDetailAdapter(Context context,
- Provider<UserDetailView.Adapter> userDetailViewAdapterProvider) {
- super(context, userDetailViewAdapterProvider);
- }
-
- @Override
- public boolean shouldAnimate() {
- return false;
- }
-
- @Override
- public int getDoneText() {
- return R.string.quick_settings_close_user_panel;
- }
-
- @Override
- public boolean onDoneButtonClicked() {
- if (mNotificationPanelViewController != null) {
- mNotificationPanelViewController.animateCloseQs(true /* animateAway */);
- return true;
- } else {
- return false;
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 3ece240..e416ed1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -46,11 +46,11 @@
import android.os.UserManager;
import android.provider.Settings;
import android.telephony.TelephonyCallback;
+import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.View;
-import android.view.ViewGroup;
import android.view.WindowManagerGlobal;
import android.widget.BaseAdapter;
@@ -59,7 +59,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.LatencyTracker;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.systemui.Dumpable;
@@ -76,9 +75,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.qs.QSUserSwitcherEvent;
-import com.android.systemui.qs.tiles.UserDetailView;
import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -95,7 +92,6 @@
import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject;
-import javax.inject.Provider;
/**
* Keeps a list of all users on the device for user switching.
@@ -152,13 +148,13 @@
private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2);
private final UiEventLogger mUiEventLogger;
private final IActivityManager mActivityManager;
- public final DetailAdapter mUserDetailAdapter;
private final Executor mBgExecutor;
private final boolean mGuestUserAutoCreated;
private final AtomicBoolean mGuestIsResetting;
private final AtomicBoolean mGuestCreationScheduled;
private FalsingManager mFalsingManager;
private View mView;
+ private String mCreateSupervisedUserPackage;
@Inject
public UserSwitcherController(Context context,
@@ -175,7 +171,6 @@
FalsingManager falsingManager,
TelephonyListenerManager telephonyListenerManager,
IActivityTaskManager activityTaskManager,
- UserDetailAdapter userDetailAdapter,
SecureSettings secureSettings,
@Background Executor bgExecutor,
InteractionJankMonitor interactionJankMonitor,
@@ -194,7 +189,6 @@
mLatencyTracker = latencyTracker;
mGuestResumeSessionReceiver = new GuestResumeSessionReceiver(
this, mUserTracker, mUiEventLogger, secureSettings);
- mUserDetailAdapter = userDetailAdapter;
mBgExecutor = bgExecutor;
if (!UserManager.isGuestUserEphemeral()) {
mGuestResumeSessionReceiver.register(mBroadcastDispatcher);
@@ -255,6 +249,9 @@
keyguardStateController.addCallback(mCallback);
listenForCallState();
+ mCreateSupervisedUserPackage = mContext.getString(
+ com.android.internal.R.string.config_supervisedUserCreationPackage);
+
dumpManager.registerDumpable(getClass().getSimpleName(), this);
refreshUsers(UserHandle.USER_NULL);
@@ -307,14 +304,10 @@
// User 0
boolean canSwitchUsers = mUserManager.getUserSwitchability(
UserHandle.of(mUserTracker.getUserId())) == SWITCHABILITY_STATUS_OK;
- UserInfo currentUserInfo = null;
UserRecord guestRecord = null;
for (UserInfo info : infos) {
boolean isCurrent = currentId == info.id;
- if (isCurrent) {
- currentUserInfo = info;
- }
boolean switchToEnabled = canSwitchUsers || isCurrent;
if (info.isEnabled()) {
if (info.isGuest()) {
@@ -322,7 +315,8 @@
// the icon shouldn't be enabled even if the user is current
guestRecord = new UserRecord(info, null /* picture */,
true /* isGuest */, isCurrent, false /* isAddUser */,
- false /* isRestricted */, canSwitchUsers);
+ false /* isRestricted */, canSwitchUsers,
+ false /* isAddSupervisedUser */);
} else if (info.supportsSwitchToByUser()) {
Bitmap picture = bitmaps.get(info.id);
if (picture == null) {
@@ -337,7 +331,7 @@
}
records.add(new UserRecord(info, picture, false /* isGuest */,
isCurrent, false /* isAddUser */, false /* isRestricted */,
- switchToEnabled));
+ switchToEnabled, false /* isAddSupervisedUser */));
}
}
}
@@ -345,19 +339,6 @@
Prefs.putBoolean(mContext, Key.SEEN_MULTI_USER, true);
}
- boolean systemCanCreateUsers = !mUserManager.hasBaseUserRestriction(
- UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM);
- boolean currentUserCanCreateUsers = currentUserInfo != null
- && (currentUserInfo.isAdmin()
- || currentUserInfo.id == UserHandle.USER_SYSTEM)
- && systemCanCreateUsers;
- boolean anyoneCanCreateUsers = systemCanCreateUsers && addUsersWhenLocked;
- boolean canCreateGuest = (currentUserCanCreateUsers || anyoneCanCreateUsers)
- && guestRecord == null;
- boolean canCreateUser = (currentUserCanCreateUsers || anyoneCanCreateUsers)
- && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY);
- boolean createIsRestricted = !addUsersWhenLocked;
-
if (guestRecord == null) {
if (mGuestUserAutoCreated) {
// If mGuestIsResetting=true, the switch should be disabled since
@@ -368,13 +349,14 @@
guestRecord = new UserRecord(null /* info */, null /* picture */,
true /* isGuest */, false /* isCurrent */,
false /* isAddUser */, false /* isRestricted */,
- isSwitchToGuestEnabled);
+ isSwitchToGuestEnabled, false /* isAddSupervisedUser */);
checkIfAddUserDisallowedByAdminOnly(guestRecord);
records.add(guestRecord);
- } else if (canCreateGuest) {
+ } else if (canCreateGuest(guestRecord != null)) {
guestRecord = new UserRecord(null /* info */, null /* picture */,
true /* isGuest */, false /* isCurrent */,
- false /* isAddUser */, createIsRestricted, canSwitchUsers);
+ false /* isAddUser */, createIsRestricted(), canSwitchUsers,
+ false /* isAddSupervisedUser */);
checkIfAddUserDisallowedByAdminOnly(guestRecord);
records.add(guestRecord);
}
@@ -382,10 +364,19 @@
records.add(guestRecord);
}
- if (canCreateUser) {
+ if (canCreateUser()) {
UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */,
false /* isGuest */, false /* isCurrent */, true /* isAddUser */,
- createIsRestricted, canSwitchUsers);
+ createIsRestricted(), canSwitchUsers,
+ false /* isAddSupervisedUser */);
+ checkIfAddUserDisallowedByAdminOnly(addUserRecord);
+ records.add(addUserRecord);
+ }
+
+ if (canCreateSupervisedUser()) {
+ UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */,
+ false /* isGuest */, false /* isCurrent */, false /* isAddUser */,
+ createIsRestricted(), canSwitchUsers, true /* isAddSupervisedUser */);
checkIfAddUserDisallowedByAdminOnly(addUserRecord);
records.add(addUserRecord);
}
@@ -403,6 +394,40 @@
}.execute((SparseArray) bitmaps);
}
+ boolean systemCanCreateUsers() {
+ return !mUserManager.hasBaseUserRestriction(
+ UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM);
+ }
+
+ boolean currentUserCanCreateUsers() {
+ UserInfo currentUser = mUserTracker.getUserInfo();
+ return currentUser != null
+ && (currentUser.isAdmin() || mUserTracker.getUserId() == UserHandle.USER_SYSTEM)
+ && systemCanCreateUsers();
+ }
+
+ boolean anyoneCanCreateUsers() {
+ return systemCanCreateUsers() && mAddUsersFromLockScreen;
+ }
+
+ boolean canCreateGuest(boolean hasExistingGuest) {
+ return (currentUserCanCreateUsers() || anyoneCanCreateUsers())
+ && !hasExistingGuest;
+ }
+
+ boolean canCreateUser() {
+ return (currentUserCanCreateUsers() || anyoneCanCreateUsers())
+ && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY);
+ }
+
+ boolean createIsRestricted() {
+ return !mAddUsersFromLockScreen;
+ }
+
+ boolean canCreateSupervisedUser() {
+ return !TextUtils.isEmpty(mCreateSupervisedUserPackage) && canCreateUser();
+ }
+
private void pauseRefreshUsers() {
if (!mPauseRefreshUsers) {
mHandler.postDelayed(mUnpauseRefreshUsers, PAUSE_REFRESH_USERS_TIMEOUT_MS);
@@ -485,6 +510,9 @@
} else if (record.isAddUser) {
showAddUserDialog(dialogShower);
return;
+ } else if (record.isAddSupervisedUser) {
+ startSupervisedUserActivity();
+ return;
} else {
id = record.info.id;
}
@@ -561,6 +589,22 @@
}
}
+ private void startSupervisedUserActivity() {
+ final Intent intent = new Intent()
+ .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
+ .setPackage(mCreateSupervisedUserPackage)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // TODO(b/209659998): [to-be-removed] fallback activity for supervised user creation.
+ if (mContext.getPackageManager().resolveActivity(intent, 0) == null) {
+ intent.setPackage(null)
+ .setClassName("com.android.settings",
+ "com.android.settings.users.AddSupervisedUserActivity");
+ }
+
+ mContext.startActivity(intent);
+ }
+
private void listenForCallState() {
mTelephonyListenerManager.addCallStateListener(mPhoneStateListener);
}
@@ -941,6 +985,8 @@
}
} else if (item.isAddUser) {
return context.getString(R.string.user_add_user);
+ } else if (item.isAddSupervisedUser) {
+ return context.getString(R.string.add_user_supervised);
} else {
return item.info.name;
}
@@ -955,9 +1001,11 @@
protected static Drawable getIconDrawable(Context context, UserRecord item) {
int iconRes;
if (item.isAddUser) {
- iconRes = R.drawable.ic_add_circle;
+ iconRes = R.drawable.ic_account_circle;
} else if (item.isGuest) {
- iconRes = R.drawable.ic_avatar_guest_user;
+ iconRes = R.drawable.ic_account_circle_filled;
+ } else if (item.isAddSupervisedUser) {
+ iconRes = R.drawable.ic_add_supervised_user;
} else {
iconRes = R.drawable.ic_avatar_user;
}
@@ -1000,6 +1048,7 @@
public final boolean isGuest;
public final boolean isCurrent;
public final boolean isAddUser;
+ public final boolean isAddSupervisedUser;
/** If true, the record is only visible to the owner and only when unlocked. */
public final boolean isRestricted;
public boolean isDisabledByAdmin;
@@ -1007,7 +1056,8 @@
public boolean isSwitchToEnabled;
public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent,
- boolean isAddUser, boolean isRestricted, boolean isSwitchToEnabled) {
+ boolean isAddUser, boolean isRestricted, boolean isSwitchToEnabled,
+ boolean isAddSupervisedUser) {
this.info = info;
this.picture = picture;
this.isGuest = isGuest;
@@ -1015,11 +1065,12 @@
this.isAddUser = isAddUser;
this.isRestricted = isRestricted;
this.isSwitchToEnabled = isSwitchToEnabled;
+ this.isAddSupervisedUser = isAddSupervisedUser;
}
public UserRecord copyWithIsCurrent(boolean _isCurrent) {
return new UserRecord(info, picture, isGuest, _isCurrent, isAddUser, isRestricted,
- isSwitchToEnabled);
+ isSwitchToEnabled, isAddSupervisedUser);
}
public int resolveId() {
@@ -1043,6 +1094,7 @@
}
if (isGuest) sb.append(" <isGuest>");
if (isAddUser) sb.append(" <isAddUser>");
+ if (isAddSupervisedUser) sb.append(" <isAddSupervisedUser>");
if (isCurrent) sb.append(" <isCurrent>");
if (picture != null) sb.append(" <hasPicture>");
if (isRestricted) sb.append(" <isRestricted>");
@@ -1058,77 +1110,6 @@
}
}
- public static class UserDetailAdapter implements DetailAdapter {
- private final Intent USER_SETTINGS_INTENT = new Intent(Settings.ACTION_USER_SETTINGS);
-
- private final Context mContext;
- private final Provider<UserDetailView.Adapter> mUserDetailViewAdapterProvider;
-
- @Inject
- UserDetailAdapter(Context context,
- Provider<UserDetailView.Adapter> userDetailViewAdapterProvider) {
- mContext = context;
- mUserDetailViewAdapterProvider = userDetailViewAdapterProvider;
- }
-
- @Override
- public CharSequence getTitle() {
- return mContext.getString(R.string.quick_settings_user_title);
- }
-
- @Override
- public View createDetailView(Context context, View convertView, ViewGroup parent) {
- UserDetailView v;
- if (!(convertView instanceof UserDetailView)) {
- v = UserDetailView.inflate(context, parent, false);
- v.setAdapter(mUserDetailViewAdapterProvider.get());
- } else {
- v = (UserDetailView) convertView;
- }
- v.refreshAdapter();
- return v;
- }
-
- @Override
- public Intent getSettingsIntent() {
- return USER_SETTINGS_INTENT;
- }
-
- @Override
- public int getSettingsText() {
- return R.string.quick_settings_more_user_settings;
- }
-
- @Override
- public Boolean getToggleState() {
- return null;
- }
-
- @Override
- public void setToggleState(boolean state) {
- }
-
- @Override
- public int getMetricsCategory() {
- return MetricsEvent.QS_USERDETAIL;
- }
-
- @Override
- public UiEventLogger.UiEventEnum openDetailEvent() {
- return QSUserSwitcherEvent.QS_USER_DETAIL_OPEN;
- }
-
- @Override
- public UiEventLogger.UiEventEnum closeDetailEvent() {
- return QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE;
- }
-
- @Override
- public UiEventLogger.UiEventEnum moreSettingsEvent() {
- return QSUserSwitcherEvent.QS_USER_MORE_SETTINGS;
- }
- };
-
private final KeyguardStateController.Callback mCallback =
new KeyguardStateController.Callback() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java
index 52b58d4..71355bb 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java
@@ -23,6 +23,7 @@
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TunerSwitch);
mDefault = a.getBoolean(R.styleable.TunerSwitch_defValue, false);
mAction = a.getInt(R.styleable.TunerSwitch_metricsAction, -1);
+ a.recycle();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerZenModePanel.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerZenModePanel.java
deleted file mode 100644
index 4d95969..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerZenModePanel.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2016 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.tuner;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.Intent;
-import android.provider.Settings;
-import android.provider.Settings.Global;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.Checkable;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.systemui.Prefs;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.volume.ZenModePanel;
-import com.android.systemui.volume.ZenModePanel.Callback;
-
-public class TunerZenModePanel extends LinearLayout implements OnClickListener {
- private static final String TAG = "TunerZenModePanel";
-
- private Callback mCallback;
- private ZenModePanel mZenModePanel;
- private View mHeaderSwitch;
- private int mZenMode;
- private ZenModeController mController;
- private View mButtons;
- private View mMoreSettings;
- private View mDone;
- private OnClickListener mDoneListener;
- private boolean mEditing;
-
- public TunerZenModePanel(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- }
-
- public void init(ZenModeController zenModeController) {
- mController = zenModeController;
- mHeaderSwitch = findViewById(R.id.tuner_zen_switch);
- mHeaderSwitch.setVisibility(View.VISIBLE);
- mHeaderSwitch.setOnClickListener(this);
- ((TextView) mHeaderSwitch.findViewById(android.R.id.title)).setText(
- R.string.quick_settings_dnd_label);
- mZenModePanel = (ZenModePanel) findViewById(R.id.zen_mode_panel);
- mZenModePanel.init(zenModeController);
- mButtons = findViewById(R.id.tuner_zen_buttons);
- mMoreSettings = mButtons.findViewById(android.R.id.button2);
- mMoreSettings.setOnClickListener(this);
- ((TextView) mMoreSettings).setText(R.string.quick_settings_more_settings);
- mDone = mButtons.findViewById(android.R.id.button1);
- mDone.setOnClickListener(this);
- ((TextView) mDone).setText(R.string.quick_settings_done);
- // Hide the resizing space because it causes issues in the volume panel.
- ViewGroup detail_header = findViewById(R.id.tuner_zen_switch);
- detail_header.getChildAt(0).setVisibility(View.GONE);
- // No background so it can blend with volume panel.
- findViewById(R.id.edit_container).setBackground(null);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mEditing = false;
- }
-
- public void setCallback(Callback zenPanelCallback) {
- mCallback = zenPanelCallback;
- mZenModePanel.setCallback(zenPanelCallback);
- }
-
- @Override
- public void onClick(View v) {
- if (v == mHeaderSwitch) {
- mEditing = true;
- if (mZenMode == Global.ZEN_MODE_OFF) {
- mZenMode = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN,
- Global.ZEN_MODE_ALARMS);
- mController.setZen(mZenMode, null, TAG);
- postUpdatePanel();
- } else {
- mZenMode = Global.ZEN_MODE_OFF;
- mController.setZen(Global.ZEN_MODE_OFF, null, TAG);
- postUpdatePanel();
- }
- } else if (v == mMoreSettings) {
- Intent intent = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- getContext().startActivity(intent);
- } else if (v == mDone) {
- mEditing = false;
- setVisibility(View.GONE);
- mDoneListener.onClick(v);
- }
- }
-
- public boolean isEditing() {
- return mEditing;
- }
-
- public void setZenState(int zenMode) {
- mZenMode = zenMode;
- postUpdatePanel();
- }
-
- private void postUpdatePanel() {
- // The complicated structure from reusing the same ZenPanel has resulted in some
- // unstableness/flickering from callbacks coming in quickly. To solve this just
- // post the UI updates a little bit.
- removeCallbacks(mUpdate);
- postDelayed(mUpdate, 40);
- }
-
- public void setDoneListener(OnClickListener onClickListener) {
- mDoneListener = onClickListener;
- }
-
- private void updatePanel() {
- boolean zenOn = mZenMode != Global.ZEN_MODE_OFF;
- ((Checkable) mHeaderSwitch.findViewById(android.R.id.toggle)).setChecked(zenOn);
- mZenModePanel.setVisibility(zenOn ? View.VISIBLE : View.GONE);
- mButtons.setVisibility(zenOn ? View.VISIBLE : View.GONE);
- }
-
- private final Runnable mUpdate = new Runnable() {
- @Override
- public void run() {
- updatePanel();
- }
- };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt
new file mode 100644
index 0000000..ad8dc82
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.tv
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.SliceBroadcastRelayHandler
+import com.android.systemui.accessibility.WindowMagnification
+import com.android.systemui.dagger.qualifiers.PerUser
+import com.android.systemui.globalactions.GlobalActionsComponent
+import com.android.systemui.keyboard.KeyboardUI
+import com.android.systemui.media.RingtonePlayer
+import com.android.systemui.media.systemsounds.HomeSoundEffectController
+import com.android.systemui.power.PowerUI
+import com.android.systemui.privacy.television.TvOngoingPrivacyChip
+import com.android.systemui.shortcut.ShortcutKeyDispatcher
+import com.android.systemui.statusbar.notification.InstantAppNotifier
+import com.android.systemui.statusbar.tv.TvStatusBar
+import com.android.systemui.statusbar.tv.VpnStatusObserver
+import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler
+import com.android.systemui.statusbar.tv.notifications.TvNotificationPanel
+import com.android.systemui.toast.ToastUI
+import com.android.systemui.usb.StorageNotification
+import com.android.systemui.util.NotificationChannels
+import com.android.systemui.volume.VolumeUI
+import com.android.systemui.wmshell.WMShell
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+/**
+ * Collection of {@link CoreStartable}s that should be run on TV.
+ */
+@Module
+abstract class TVSystemUICoreStartableModule {
+ /** Inject into GlobalActionsComponent. */
+ @Binds
+ @IntoMap
+ @ClassKey(GlobalActionsComponent::class)
+ abstract fun bindGlobalActionsComponent(sysui: GlobalActionsComponent): CoreStartable
+
+ /** Inject into HomeSoundEffectController. */
+ @Binds
+ @IntoMap
+ @ClassKey(HomeSoundEffectController::class)
+ abstract fun bindHomeSoundEffectController(sysui: HomeSoundEffectController): CoreStartable
+
+ /** Inject into InstantAppNotifier. */
+ @Binds
+ @IntoMap
+ @ClassKey(InstantAppNotifier::class)
+ abstract fun bindInstantAppNotifier(sysui: InstantAppNotifier): CoreStartable
+
+ /** Inject into KeyboardUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(KeyboardUI::class)
+ abstract fun bindKeyboardUI(sysui: KeyboardUI): CoreStartable
+
+ /** Inject into NotificationChannels. */
+ @Binds
+ @IntoMap
+ @ClassKey(NotificationChannels::class)
+ @PerUser
+ abstract fun bindNotificationChannels(sysui: NotificationChannels): CoreStartable
+
+ /** Inject into PowerUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(PowerUI::class)
+ abstract fun bindPowerUI(sysui: PowerUI): CoreStartable
+
+ /** Inject into RingtonePlayer. */
+ @Binds
+ @IntoMap
+ @ClassKey(RingtonePlayer::class)
+ abstract fun bind(sysui: RingtonePlayer): CoreStartable
+
+ /** Inject into ShortcutKeyDispatcher. */
+ @Binds
+ @IntoMap
+ @ClassKey(ShortcutKeyDispatcher::class)
+ abstract fun bindShortcutKeyDispatcher(sysui: ShortcutKeyDispatcher): CoreStartable
+
+ /** Inject into SliceBroadcastRelayHandler. */
+ @Binds
+ @IntoMap
+ @ClassKey(SliceBroadcastRelayHandler::class)
+ abstract fun bindSliceBroadcastRelayHandler(sysui: SliceBroadcastRelayHandler): CoreStartable
+
+ /** Inject into StorageNotification. */
+ @Binds
+ @IntoMap
+ @ClassKey(StorageNotification::class)
+ abstract fun bindStorageNotification(sysui: StorageNotification): CoreStartable
+
+ /** Inject into ToastUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(ToastUI::class)
+ abstract fun bindToastUI(service: ToastUI): CoreStartable
+
+ /** Inject into TvNotificationHandler. */
+ @Binds
+ @IntoMap
+ @ClassKey(TvNotificationHandler::class)
+ abstract fun bindTvNotificationHandler(sysui: TvNotificationHandler): CoreStartable
+
+ /** Inject into TvNotificationPanel. */
+ @Binds
+ @IntoMap
+ @ClassKey(TvNotificationPanel::class)
+ abstract fun bindTvNotificationPanel(sysui: TvNotificationPanel): CoreStartable
+
+ /** Inject into TvOngoingPrivacyChip. */
+ @Binds
+ @IntoMap
+ @ClassKey(TvOngoingPrivacyChip::class)
+ abstract fun bindTvOngoingPrivacyChip(sysui: TvOngoingPrivacyChip): CoreStartable
+
+ /** Inject into TvStatusBar. */
+ @Binds
+ @IntoMap
+ @ClassKey(TvStatusBar::class)
+ abstract fun bindTvStatusBar(sysui: TvStatusBar): CoreStartable
+
+ /** Inject into VolumeUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(VolumeUI::class)
+ abstract fun bindVolumeUI(sysui: VolumeUI): CoreStartable
+
+ /** Inject into VpnStatusObserver. */
+ @Binds
+ @IntoMap
+ @ClassKey(VpnStatusObserver::class)
+ abstract fun bindVpnStatusObserver(sysui: VpnStatusObserver): CoreStartable
+
+ /** Inject into WindowMagnification. */
+ @Binds
+ @IntoMap
+ @ClassKey(WindowMagnification::class)
+ abstract fun bindWindowMagnification(sysui: WindowMagnification): CoreStartable
+
+ /** Inject into WMShell. */
+ @Binds
+ @IntoMap
+ @ClassKey(WMShell::class)
+ abstract fun bindWMShell(sysui: WMShell): CoreStartable
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
index bef05eb..6fdce1a 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
@@ -34,6 +34,7 @@
DependencyProvider.class,
SystemUIBinder.class,
SystemUIModule.class,
+ TVSystemUICoreStartableModule.class,
TvSystemUIModule.class,
TvSystemUIBinder.class})
public interface TvSysUIComponent extends SysUIComponent {
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
index d0fb91c..23f37ec 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
@@ -16,28 +16,13 @@
package com.android.systemui.tv;
-import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.GlobalRootComponent;
-import com.android.systemui.statusbar.tv.VpnStatusObserver;
-import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler;
import dagger.Binds;
import dagger.Module;
-import dagger.multibindings.ClassKey;
-import dagger.multibindings.IntoMap;
@Module
interface TvSystemUIBinder {
@Binds
GlobalRootComponent bindGlobalRootComponent(TvGlobalRootComponent globalRootComponent);
-
- @Binds
- @IntoMap
- @ClassKey(TvNotificationHandler.class)
- CoreStartable bindTvNotificationHandler(TvNotificationHandler systemui);
-
- @Binds
- @IntoMap
- @ClassKey(VpnStatusObserver.class)
- CoreStartable bindVpnStatusObserver(VpnStatusObserver systemui);
}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index cf361ec..345fc99 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -46,10 +46,15 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.CoreStartable;
import com.android.systemui.SystemUIApplication;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.util.NotificationChannels;
import java.util.List;
+import javax.inject.Inject;
+
+/** */
+@SysUISingleton
public class StorageNotification extends CoreStartable {
private static final String TAG = "StorageNotification";
@@ -61,6 +66,7 @@
private NotificationManager mNotificationManager;
private StorageManager mStorageManager;
+ @Inject
public StorageNotification(Context context) {
super(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
index d6a8ab2..41da44a 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -27,11 +27,10 @@
import android.graphics.drawable.LayerDrawable
import android.os.Bundle
import android.os.UserManager
+import android.provider.Settings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.view.WindowInsets
-import android.view.WindowInsets.Type
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.ImageView
@@ -71,8 +70,18 @@
private lateinit var broadcastReceiver: BroadcastReceiver
private var popupMenu: UserSwitcherPopupMenu? = null
private lateinit var addButton: View
- private var addUserItem: UserRecord? = null
- private var addGuestItem: UserRecord? = null
+ private var addUserRecords = mutableListOf<UserRecord>()
+ // When the add users options become available, insert another option to manage users
+ private val manageUserRecord = UserRecord(
+ null /* info */,
+ null /* picture */,
+ false /* isGuest */,
+ false /* isCurrent */,
+ false /* isAddUser */,
+ false /* isRestricted */,
+ false /* isSwitchToEnabled */,
+ false /* isAddSupervisedUser */
+ )
private val adapter = object : BaseUserAdapter(userSwitcherController) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
@@ -104,7 +113,18 @@
return view
}
+ override fun getName(context: Context, item: UserRecord): String {
+ return if (item == manageUserRecord) {
+ getString(R.string.manage_users)
+ } else {
+ super.getName(context, item)
+ }
+ }
+
fun findUserIcon(item: UserRecord): Drawable {
+ if (item == manageUserRecord) {
+ return getDrawable(R.drawable.ic_manage_users)
+ }
if (item.info == null) {
return getIconDrawable(this@UserSwitcherActivity, item)
}
@@ -169,20 +189,11 @@
super.onCreate(savedInstanceState)
setContentView(R.layout.user_switcher_fullscreen)
+ window.decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
- parent = requireViewById<ViewGroup>(R.id.user_switcher_root).apply {
- setOnApplyWindowInsetsListener {
- v: View, insets: WindowInsets ->
- v.apply {
- val l = getPaddingLeft()
- val t = getPaddingTop()
- val r = getPaddingRight()
- setPadding(l, t, r, insets.getInsets(Type.systemBars()).bottom)
- }
-
- WindowInsets.CONSUMED
- }
- }
+ parent = requireViewById<ViewGroup>(R.id.user_switcher_root)
requireViewById<View>(R.id.cancel).apply {
setOnClickListener {
@@ -203,15 +214,19 @@
private fun showPopupMenu() {
val items = mutableListOf<UserRecord>()
- addUserItem?.let { items.add(it) }
- addGuestItem?.let { items.add(it) }
+ addUserRecords.forEach { items.add(it) }
var popupMenuAdapter = ItemAdapter(
this,
R.layout.user_switcher_fullscreen_popup_item,
layoutInflater,
{ item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item) },
- { item: UserRecord -> adapter.findUserIcon(item) }
+ { item: UserRecord -> adapter.findUserIcon(item).mutate().apply {
+ setTint(resources.getColor(
+ R.color.user_switcher_fullscreen_popup_item_tint,
+ getTheme()
+ ))
+ } }
)
popupMenuAdapter.addAll(items)
@@ -225,10 +240,17 @@
}
// -1 for the header
val item = popupMenuAdapter.getItem(pos - 1)
- adapter.onUserListItemClicked(item)
+ if (item == manageUserRecord) {
+ val i = Intent().setAction(Settings.ACTION_USER_SETTINGS)
+ this@UserSwitcherActivity.startActivity(i)
+ } else {
+ adapter.onUserListItemClicked(item)
+ }
dismiss()
popupMenu = null
+
+ this@UserSwitcherActivity.finish()
}
show()
@@ -245,14 +267,15 @@
}
}
parent.removeViews(start, count)
+ addUserRecords.clear()
val flow = requireViewById<Flow>(R.id.flow)
for (i in 0 until adapter.getCount()) {
val item = adapter.getItem(i)
- if (item.isAddUser) {
- addUserItem = item
- } else if (item.isGuest && item.info == null) {
- addGuestItem = item
+ if (item.isAddUser ||
+ item.isAddSupervisedUser ||
+ item.isGuest && item.info == null) {
+ addUserRecords.add(item)
} else {
val userView = adapter.getView(i, null, parent)
userView.setId(View.generateViewId())
@@ -273,7 +296,8 @@
}
}
- if (addUserItem != null || addGuestItem != null) {
+ if (!addUserRecords.isEmpty()) {
+ addUserRecords.add(manageUserRecord)
addButton.visibility = View.VISIBLE
} else {
addButton.visibility = View.GONE
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
index 8963547..754a934 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
@@ -42,7 +42,7 @@
setBackgroundDrawable(
res.getDrawable(R.drawable.bouncer_user_switcher_popup_bg, context.getTheme())
)
- setModal(true)
+ setModal(false)
setOverlapAnchor(true)
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
index ce7e4cf..76dfcb1 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
+++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
@@ -29,6 +29,9 @@
import java.util.Arrays;
+import javax.inject.Inject;
+
+// NOT Singleton. Started per-user.
public class NotificationChannels extends CoreStartable {
public static String ALERTS = "ALR";
public static String SCREENSHOTS_HEADSUP = "SCN_HEADSUP";
@@ -38,6 +41,7 @@
public static String TVPIP = TvPipNotificationController.NOTIFICATION_CHANNEL; // "TVPIP"
public static String HINTS = "HNT";
+ @Inject
public NotificationChannels(Context context) {
super(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
index 0bbf56c..db35437e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
@@ -36,6 +36,7 @@
private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
private boolean mIsConditionMet = false;
private boolean mStarted = false;
+ private boolean mOverriding = false;
/**
* Starts monitoring the condition.
@@ -48,6 +49,21 @@
protected abstract void stop();
/**
+ * Sets whether this condition's value overrides others in determining the overall state.
+ */
+ public void setOverriding(boolean overriding) {
+ mOverriding = overriding;
+ updateCondition(mIsConditionMet);
+ }
+
+ /**
+ * Returns whether the current condition overrides
+ */
+ public boolean isOverridingCondition() {
+ return mOverriding;
+ }
+
+ /**
* Registers a callback to receive updates once started. This should be called before
* {@link #start()}. Also triggers the callback immediately if already started.
*/
@@ -57,7 +73,7 @@
mCallbacks.add(new WeakReference<>(callback));
if (mStarted) {
- callback.onConditionChanged(this, mIsConditionMet);
+ callback.onConditionChanged(this);
return;
}
@@ -107,11 +123,15 @@
if (cb == null) {
iterator.remove();
} else {
- cb.onConditionChanged(this, mIsConditionMet);
+ cb.onConditionChanged(this);
}
}
}
+ public boolean isConditionMet() {
+ return mIsConditionMet;
+ }
+
private boolean shouldLog() {
return Log.isLoggable(mTag, Log.DEBUG);
}
@@ -124,8 +144,7 @@
* Called when the fulfillment of the condition changes.
*
* @param condition The condition in question.
- * @param isConditionMet True if the condition has been fulfilled. False otherwise.
*/
- void onConditionChanged(Condition condition, boolean isConditionMet);
+ void onConditionChanged(Condition condition);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
index 8b6e982..7f3d54d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
@@ -18,15 +18,17 @@
import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.statusbar.policy.CallbackController;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Collection;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -41,9 +43,7 @@
// Set of all conditions that need to be monitored.
private final Set<Condition> mConditions;
-
- // Map of values of each condition.
- private final HashMap<Condition, Boolean> mConditionsMap = new HashMap<>();
+ private final Executor mExecutor;
// Whether all conditions have been met.
private boolean mAllConditionsMet = false;
@@ -52,10 +52,43 @@
private boolean mHaveConditionsStarted = false;
// Callback for when each condition has been updated.
- private final Condition.Callback mConditionCallback = (condition, isConditionMet) -> {
- mConditionsMap.put(condition, isConditionMet);
+ private final Condition.Callback mConditionCallback = new Condition.Callback() {
+ @Override
+ public void onConditionChanged(Condition condition) {
+ mExecutor.execute(() -> updateConditionMetState());
+ }
+ };
- final boolean newAllConditionsMet = !mConditionsMap.containsValue(false);
+ @Inject
+ public Monitor(Executor executor, Set<Condition> conditions, Set<Callback> callbacks) {
+ mConditions = new HashSet<>();
+ mExecutor = executor;
+
+ if (conditions != null) {
+ mConditions.addAll(conditions);
+ }
+
+ if (callbacks == null) {
+ return;
+ }
+
+ for (Callback callback : callbacks) {
+ addCallbackLocked(callback);
+ }
+ }
+
+ private void updateConditionMetState() {
+ // Overriding conditions do not override each other
+ final Collection<Condition> overridingConditions = mConditions.stream()
+ .filter(Condition::isOverridingCondition).collect(Collectors.toSet());
+
+ final Collection<Condition> targetCollection = overridingConditions.isEmpty()
+ ? mConditions : overridingConditions;
+
+ final boolean newAllConditionsMet = targetCollection.isEmpty() ? true : targetCollection
+ .stream()
+ .map(Condition::isConditionMet)
+ .allMatch(conditionMet -> conditionMet);
if (newAllConditionsMet == mAllConditionsMet) {
return;
@@ -74,32 +107,44 @@
callback.onConditionsChanged(mAllConditionsMet);
}
}
- };
-
- @Inject
- public Monitor(Set<Condition> conditions, Set<Callback> callbacks) {
- mConditions = conditions;
-
- // If there is no condition, give green pass.
- if (mConditions.isEmpty()) {
- mAllConditionsMet = true;
- return;
- }
-
- // Initializes the conditions map and registers a callback for each condition.
- mConditions.forEach((condition -> mConditionsMap.put(condition, false)));
-
- if (callbacks == null) {
- return;
- }
-
- for (Callback callback : callbacks) {
- addCallback(callback);
- }
}
- @Override
- public void addCallback(@NotNull Callback callback) {
+ private void addConditionLocked(@NotNull Condition condition) {
+ mConditions.add(condition);
+
+ if (!mHaveConditionsStarted) {
+ return;
+ }
+
+ condition.addCallback(mConditionCallback);
+ updateConditionMetState();
+ }
+
+ /**
+ * Adds a condition for the monitor to listen to and consider when determining whether the
+ * overall condition state is met.
+ */
+ public void addCondition(@NotNull Condition condition) {
+ mExecutor.execute(() -> addConditionLocked(condition));
+ }
+
+ /**
+ * Removes a condition from further consideration.
+ */
+ public void removeCondition(@NotNull Condition condition) {
+ mExecutor.execute(() -> {
+ mConditions.remove(condition);
+
+ if (!mHaveConditionsStarted) {
+ return;
+ }
+
+ condition.removeCallback(mConditionCallback);
+ updateConditionMetState();
+ });
+ }
+
+ private void addCallbackLocked(@NotNull Callback callback) {
if (shouldLog()) Log.d(mTag, "adding callback");
mCallbacks.add(callback);
@@ -109,36 +154,36 @@
if (!mHaveConditionsStarted) {
if (shouldLog()) Log.d(mTag, "starting all conditions");
mConditions.forEach(condition -> condition.addCallback(mConditionCallback));
+ updateConditionMetState();
mHaveConditionsStarted = true;
}
}
@Override
- public void removeCallback(@NotNull Callback callback) {
- if (shouldLog()) Log.d(mTag, "removing callback");
- final Iterator<Callback> iterator = mCallbacks.iterator();
- while (iterator.hasNext()) {
- final Callback cb = iterator.next();
- if (cb == null || cb == callback) {
- iterator.remove();
- }
- }
-
- if (mCallbacks.isEmpty() && mHaveConditionsStarted) {
- if (shouldLog()) Log.d(mTag, "stopping all conditions");
- mConditions.forEach(condition -> condition.removeCallback(mConditionCallback));
-
- mAllConditionsMet = false;
- mHaveConditionsStarted = false;
- }
+ public void addCallback(@NotNull Callback callback) {
+ mExecutor.execute(() -> addCallbackLocked(callback));
}
- /**
- * Force updates each condition to the value provided.
- */
- @VisibleForTesting
- public void overrideAllConditionsMet(boolean value) {
- mConditions.forEach(condition -> condition.updateCondition(value));
+ @Override
+ public void removeCallback(@NotNull Callback callback) {
+ mExecutor.execute(() -> {
+ if (shouldLog()) Log.d(mTag, "removing callback");
+ final Iterator<Callback> iterator = mCallbacks.iterator();
+ while (iterator.hasNext()) {
+ final Callback cb = iterator.next();
+ if (cb == null || cb == callback) {
+ iterator.remove();
+ }
+ }
+
+ if (mCallbacks.isEmpty() && mHaveConditionsStarted) {
+ if (shouldLog()) Log.d(mTag, "stopping all conditions");
+ mConditions.forEach(condition -> condition.removeCallback(mConditionCallback));
+
+ mAllConditionsMet = false;
+ mHaveConditionsStarted = false;
+ }
+ });
}
private boolean shouldLog() {
diff --git a/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt b/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
index b64d7be..d8de07d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
@@ -21,6 +21,7 @@
import com.android.internal.view.RotationPolicy
import com.android.internal.view.RotationPolicy.RotationPolicyListener
import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.traceSection
import javax.inject.Inject
/**
@@ -44,7 +45,9 @@
RotationPolicyWrapper {
override fun setRotationLock(enabled: Boolean) {
- RotationPolicy.setRotationLock(context, enabled)
+ traceSection("RotationPolicyWrapperImpl#setRotationLock") {
+ RotationPolicy.setRotationLock(context, enabled)
+ }
}
override fun setRotationLockAtAngle(enabled: Boolean, rotation: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index c083c14..955d616 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -22,6 +22,7 @@
import android.content.res.Configuration;
import android.media.VolumePolicy;
import android.os.Bundle;
+import android.provider.Settings;
import android.view.WindowManager.LayoutParams;
import com.android.settingslib.applications.InterestingConfigChanges;
@@ -59,6 +60,11 @@
public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = false;
public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = false;
+ private static final Intent ZEN_SETTINGS =
+ new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
+ private static final Intent ZEN_PRIORITY_SETTINGS =
+ new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS);
+
protected final Context mContext;
private final VolumeDialogControllerImpl mController;
private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
@@ -191,12 +197,12 @@
private final VolumeDialogImpl.Callback mVolumeDialogCallback = new VolumeDialogImpl.Callback() {
@Override
public void onZenSettingsClicked() {
- startSettings(ZenModePanel.ZEN_SETTINGS);
+ startSettings(ZEN_SETTINGS);
}
@Override
public void onZenPrioritySettingsClicked() {
- startSettings(ZenModePanel.ZEN_PRIORITY_SETTINGS);
+ startSettings(ZEN_PRIORITY_SETTINGS);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 7bb987c..e69de29 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -1,1077 +0,0 @@
-/**
- * Copyright (C) 2014 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.volume;
-
-import android.animation.LayoutTransition;
-import android.animation.LayoutTransition.TransitionListener;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.res.Configuration;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.provider.Settings;
-import android.provider.Settings.Global;
-import android.service.notification.Condition;
-import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.ZenRule;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
-import android.util.ArraySet;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.MathUtils;
-import android.util.Slog;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CompoundButton;
-import android.widget.CompoundButton.OnCheckedChangeListener;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.RadioButton;
-import android.widget.RadioGroup;
-import android.widget.TextView;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.Prefs;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.ZenModeController;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-import java.util.Locale;
-import java.util.Objects;
-
-public class ZenModePanel extends FrameLayout {
- private static final String TAG = "ZenModePanel";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- public static final int STATE_MODIFY = 0;
- public static final int STATE_AUTO_RULE = 1;
- public static final int STATE_OFF = 2;
-
- private static final int SECONDS_MS = 1000;
- private static final int MINUTES_MS = 60 * SECONDS_MS;
-
- private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS;
- private static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0];
- private static final int MAX_BUCKET_MINUTES = MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1];
- private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60);
- private static final int FOREVER_CONDITION_INDEX = 0;
- private static final int COUNTDOWN_CONDITION_INDEX = 1;
- private static final int COUNTDOWN_ALARM_CONDITION_INDEX = 2;
- private static final int COUNTDOWN_CONDITION_COUNT = 2;
-
- public static final Intent ZEN_SETTINGS
- = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
- public static final Intent ZEN_PRIORITY_SETTINGS
- = new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS);
-
- private static final long TRANSITION_DURATION = 300;
-
- private final Context mContext;
- protected final LayoutInflater mInflater;
- private final H mHandler = new H();
- private final ZenPrefs mPrefs;
- private final TransitionHelper mTransitionHelper = new TransitionHelper();
- private final Uri mForeverId;
- private final ConfigurableTexts mConfigurableTexts;
-
- private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this));
-
- protected SegmentedButtons mZenButtons;
- private View mZenIntroduction;
- private TextView mZenIntroductionMessage;
- private View mZenIntroductionConfirm;
- private TextView mZenIntroductionCustomize;
- protected LinearLayout mZenConditions;
- private TextView mZenAlarmWarning;
- private RadioGroup mZenRadioGroup;
- private LinearLayout mZenRadioGroupContent;
-
- private Callback mCallback;
- private ZenModeController mController;
- private Condition mExitCondition;
- private int mBucketIndex = -1;
- private boolean mExpanded;
- private boolean mHidden;
- private int mSessionZen;
- private int mAttachedZen;
- private boolean mAttached;
- private Condition mSessionExitCondition;
- private boolean mVoiceCapable;
-
- protected int mZenModeConditionLayoutId;
- protected int mZenModeButtonLayoutId;
- private View mEmpty;
- private TextView mEmptyText;
- private ImageView mEmptyIcon;
- private View mAutoRule;
- private TextView mAutoTitle;
- private int mState = STATE_MODIFY;
- private ViewGroup mEdit;
-
- public ZenModePanel(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
- mPrefs = new ZenPrefs();
- mInflater = LayoutInflater.from(mContext);
- mForeverId = Condition.newId(mContext).appendPath("forever").build();
- mConfigurableTexts = new ConfigurableTexts(mContext);
- mVoiceCapable = Util.isVoiceCapable(mContext);
- mZenModeConditionLayoutId = R.layout.zen_mode_condition;
- mZenModeButtonLayoutId = R.layout.zen_mode_button;
- if (DEBUG) Log.d(mTag, "new ZenModePanel");
- }
-
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("ZenModePanel state:");
- pw.print(" mAttached="); pw.println(mAttached);
- pw.print(" mHidden="); pw.println(mHidden);
- pw.print(" mExpanded="); pw.println(mExpanded);
- pw.print(" mSessionZen="); pw.println(mSessionZen);
- pw.print(" mAttachedZen="); pw.println(mAttachedZen);
- pw.print(" mConfirmedPriorityIntroduction=");
- pw.println(mPrefs.mConfirmedPriorityIntroduction);
- pw.print(" mConfirmedSilenceIntroduction=");
- pw.println(mPrefs.mConfirmedSilenceIntroduction);
- pw.print(" mVoiceCapable="); pw.println(mVoiceCapable);
- mTransitionHelper.dump(fd, pw, args);
- }
-
- protected void createZenButtons() {
- mZenButtons = findViewById(R.id.zen_buttons);
- mZenButtons.addButton(R.string.interruption_level_none_twoline,
- R.string.interruption_level_none_with_warning,
- Global.ZEN_MODE_NO_INTERRUPTIONS);
- mZenButtons.addButton(R.string.interruption_level_alarms_twoline,
- R.string.interruption_level_alarms,
- Global.ZEN_MODE_ALARMS);
- mZenButtons.addButton(R.string.interruption_level_priority_twoline,
- R.string.interruption_level_priority,
- Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- mZenButtons.setCallback(mZenButtonsCallback);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- createZenButtons();
- mZenIntroduction = findViewById(R.id.zen_introduction);
- mZenIntroductionMessage = findViewById(R.id.zen_introduction_message);
- mZenIntroductionConfirm = findViewById(R.id.zen_introduction_confirm);
- mZenIntroductionConfirm.setOnClickListener(v -> confirmZenIntroduction());
- mZenIntroductionCustomize = findViewById(R.id.zen_introduction_customize);
- mZenIntroductionCustomize.setOnClickListener(v -> {
- confirmZenIntroduction();
- if (mCallback != null) {
- mCallback.onPrioritySettings();
- }
- });
- mConfigurableTexts.add(mZenIntroductionCustomize, R.string.zen_priority_customize_button);
-
- mZenConditions = findViewById(R.id.zen_conditions);
- mZenAlarmWarning = findViewById(R.id.zen_alarm_warning);
- mZenRadioGroup = findViewById(R.id.zen_radio_buttons);
- mZenRadioGroupContent = findViewById(R.id.zen_radio_buttons_content);
-
- mEdit = findViewById(R.id.edit_container);
-
- mEmpty = findViewById(android.R.id.empty);
- mEmpty.setVisibility(INVISIBLE);
- mEmptyText = mEmpty.findViewById(android.R.id.title);
- mEmptyIcon = mEmpty.findViewById(android.R.id.icon);
-
- mAutoRule = findViewById(R.id.auto_rule);
- mAutoTitle = mAutoRule.findViewById(android.R.id.title);
- mAutoRule.setVisibility(INVISIBLE);
- }
-
- public void setEmptyState(int icon, int text) {
- mEmptyIcon.post(() -> {
- mEmptyIcon.setImageResource(icon);
- mEmptyText.setText(text);
- });
- }
-
- public void setAutoText(CharSequence text) {
- mAutoTitle.post(() -> mAutoTitle.setText(text));
- }
-
- public void setState(int state) {
- if (mState == state) return;
- transitionFrom(getView(mState), getView(state));
- mState = state;
- }
-
- private void transitionFrom(View from, View to) {
- from.post(() -> {
- // TODO: Better transitions
- to.setAlpha(0);
- to.setVisibility(VISIBLE);
- to.bringToFront();
- to.animate().cancel();
- to.animate().alpha(1)
- .setDuration(TRANSITION_DURATION)
- .withEndAction(() -> from.setVisibility(INVISIBLE))
- .start();
- });
- }
-
- private View getView(int state) {
- switch (state) {
- case STATE_AUTO_RULE:
- return mAutoRule;
- case STATE_OFF:
- return mEmpty;
- default:
- return mEdit;
- }
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- mConfigurableTexts.update();
- if (mZenButtons != null) {
- mZenButtons.update();
- }
- }
-
- private void confirmZenIntroduction() {
- final String prefKey = prefKeyForConfirmation(getSelectedZen(Global.ZEN_MODE_OFF));
- if (prefKey == null) return;
- if (DEBUG) Log.d(TAG, "confirmZenIntroduction " + prefKey);
- Prefs.putBoolean(mContext, prefKey, true);
- mHandler.sendEmptyMessage(H.UPDATE_WIDGETS);
- }
-
- private static String prefKeyForConfirmation(int zen) {
- switch (zen) {
- case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
- return Prefs.Key.DND_CONFIRMED_PRIORITY_INTRODUCTION;
- case Global.ZEN_MODE_NO_INTERRUPTIONS:
- return Prefs.Key.DND_CONFIRMED_SILENCE_INTRODUCTION;
- case Global.ZEN_MODE_ALARMS:
- return Prefs.Key.DND_CONFIRMED_ALARM_INTRODUCTION;
- default:
- return null;
- }
- }
-
- private void onAttach() {
- setExpanded(true);
- mAttachedZen = mController.getZen();
- ZenRule manualRule = mController.getManualRule();
- mExitCondition = manualRule != null ? manualRule.condition : null;
- if (DEBUG) Log.d(mTag, "onAttach " + mAttachedZen + " " + manualRule);
- handleUpdateManualRule(manualRule);
- mZenButtons.setSelectedValue(mAttachedZen, false);
- mSessionZen = mAttachedZen;
- mTransitionHelper.clear();
- mController.addCallback(mZenCallback);
- setSessionExitCondition(copy(mExitCondition));
- updateWidgets();
- setAttached(true);
- }
-
- private void onDetach() {
- if (DEBUG) Log.d(mTag, "onDetach");
- setExpanded(false);
- checkForAttachedZenChange();
- setAttached(false);
- mAttachedZen = -1;
- mSessionZen = -1;
- mController.removeCallback(mZenCallback);
- setSessionExitCondition(null);
- mTransitionHelper.clear();
- }
-
- @VisibleForTesting
- void setAttached(boolean attached) {
- mAttached = attached;
- }
-
- @Override
- public void onVisibilityAggregated(boolean isVisible) {
- super.onVisibilityAggregated(isVisible);
- if (isVisible == mAttached) return;
- if (isVisible) {
- onAttach();
- } else {
- onDetach();
- }
- }
-
- private void setSessionExitCondition(Condition condition) {
- if (Objects.equals(condition, mSessionExitCondition)) return;
- if (DEBUG) Log.d(mTag, "mSessionExitCondition=" + getConditionId(condition));
- mSessionExitCondition = condition;
- }
-
- public void setHidden(boolean hidden) {
- if (mHidden == hidden) return;
- if (DEBUG) Log.d(mTag, "hidden=" + hidden);
- mHidden = hidden;
- updateWidgets();
- }
-
- private void checkForAttachedZenChange() {
- final int selectedZen = getSelectedZen(-1);
- if (DEBUG) Log.d(mTag, "selectedZen=" + selectedZen);
- if (selectedZen != mAttachedZen) {
- if (DEBUG) Log.d(mTag, "attachedZen: " + mAttachedZen + " -> " + selectedZen);
- if (selectedZen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
- mPrefs.trackNoneSelected();
- }
- }
- }
-
- private void setExpanded(boolean expanded) {
- if (expanded == mExpanded) return;
- if (DEBUG) Log.d(mTag, "setExpanded " + expanded);
- mExpanded = expanded;
- updateWidgets();
- fireExpanded();
- }
-
- protected void addZenConditions(int count) {
- for (int i = 0; i < count; i++) {
- final View rb = mInflater.inflate(mZenModeButtonLayoutId, mEdit, false);
- rb.setId(i);
- mZenRadioGroup.addView(rb);
- final View rbc = mInflater.inflate(mZenModeConditionLayoutId, mEdit, false);
- rbc.setId(i + count);
- mZenRadioGroupContent.addView(rbc);
- }
- }
-
- public void init(ZenModeController controller) {
- mController = controller;
- final int minConditions = 1 /*forever*/ + COUNTDOWN_CONDITION_COUNT;
- addZenConditions(minConditions);
- mSessionZen = getSelectedZen(-1);
- handleUpdateManualRule(mController.getManualRule());
- if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition);
- hideAllConditions();
- }
-
- private void setExitCondition(Condition exitCondition) {
- if (Objects.equals(mExitCondition, exitCondition)) return;
- mExitCondition = exitCondition;
- if (DEBUG) Log.d(mTag, "mExitCondition=" + getConditionId(mExitCondition));
- updateWidgets();
- }
-
- private static Uri getConditionId(Condition condition) {
- return condition != null ? condition.id : null;
- }
-
- private Uri getRealConditionId(Condition condition) {
- return isForever(condition) ? null : getConditionId(condition);
- }
-
- private static Condition copy(Condition condition) {
- return condition == null ? null : condition.copy();
- }
-
- public void setCallback(Callback callback) {
- mCallback = callback;
- }
-
- @VisibleForTesting
- void handleUpdateManualRule(ZenRule rule) {
- final int zen = rule != null ? rule.zenMode : Global.ZEN_MODE_OFF;
- handleUpdateZen(zen);
- final Condition c = rule == null ? null
- : rule.condition != null ? rule.condition
- : createCondition(rule.conditionId);
- handleUpdateConditions(c);
- setExitCondition(c);
- }
-
- private Condition createCondition(Uri conditionId) {
- if (ZenModeConfig.isValidCountdownToAlarmConditionId(conditionId)) {
- long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
- Condition c = ZenModeConfig.toNextAlarmCondition(
- mContext, time, ActivityManager.getCurrentUser());
- return c;
- } else if (ZenModeConfig.isValidCountdownConditionId(conditionId)) {
- long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
- int mins = (int) ((time - System.currentTimeMillis() + DateUtils.MINUTE_IN_MILLIS / 2)
- / DateUtils.MINUTE_IN_MILLIS);
- Condition c = ZenModeConfig.toTimeCondition(mContext, time, mins,
- ActivityManager.getCurrentUser(), false);
- return c;
- }
- // If there is a manual rule, but it has no condition listed then it is forever.
- return forever();
- }
-
- private void handleUpdateZen(int zen) {
- if (mSessionZen != -1 && mSessionZen != zen) {
- mSessionZen = zen;
- }
- mZenButtons.setSelectedValue(zen, false /* fromClick */);
- updateWidgets();
- }
-
- @VisibleForTesting
- int getSelectedZen(int defValue) {
- final Object zen = mZenButtons.getSelectedValue();
- return zen != null ? (Integer) zen : defValue;
- }
-
- private void updateWidgets() {
- if (mTransitionHelper.isTransitioning()) {
- mTransitionHelper.pendingUpdateWidgets();
- return;
- }
- final int zen = getSelectedZen(Global.ZEN_MODE_OFF);
- final boolean zenImportant = zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS;
- final boolean zenAlarm = zen == Global.ZEN_MODE_ALARMS;
- final boolean introduction = (zenImportant && !mPrefs.mConfirmedPriorityIntroduction
- || zenNone && !mPrefs.mConfirmedSilenceIntroduction
- || zenAlarm && !mPrefs.mConfirmedAlarmIntroduction);
-
- mZenButtons.setVisibility(mHidden ? GONE : VISIBLE);
- mZenIntroduction.setVisibility(introduction ? VISIBLE : GONE);
- if (introduction) {
- int message = zenImportant
- ? R.string.zen_priority_introduction
- : zenAlarm
- ? R.string.zen_alarms_introduction
- : mVoiceCapable
- ? R.string.zen_silence_introduction_voice
- : R.string.zen_silence_introduction;
- mConfigurableTexts.add(mZenIntroductionMessage, message);
- mConfigurableTexts.update();
- mZenIntroductionCustomize.setVisibility(zenImportant ? VISIBLE : GONE);
- }
- final String warning = computeAlarmWarningText(zenNone);
- mZenAlarmWarning.setVisibility(warning != null ? VISIBLE : GONE);
- mZenAlarmWarning.setText(warning);
- }
-
- private String computeAlarmWarningText(boolean zenNone) {
- if (!zenNone) {
- return null;
- }
- final long now = System.currentTimeMillis();
- final long nextAlarm = mController.getNextAlarm();
- if (nextAlarm < now) {
- return null;
- }
- int warningRes = 0;
- if (mSessionExitCondition == null || isForever(mSessionExitCondition)) {
- warningRes = R.string.zen_alarm_warning_indef;
- } else {
- final long time = ZenModeConfig.tryParseCountdownConditionId(mSessionExitCondition.id);
- if (time > now && nextAlarm < time) {
- warningRes = R.string.zen_alarm_warning;
- }
- }
- if (warningRes == 0) {
- return null;
- }
- final boolean soon = (nextAlarm - now) < 24 * 60 * 60 * 1000;
- final boolean is24 = DateFormat.is24HourFormat(mContext, ActivityManager.getCurrentUser());
- final String skeleton = soon ? (is24 ? "Hm" : "hma") : (is24 ? "EEEHm" : "EEEhma");
- final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
- final CharSequence formattedTime = DateFormat.format(pattern, nextAlarm);
- final int templateRes = soon ? R.string.alarm_template : R.string.alarm_template_far;
- final String template = getResources().getString(templateRes, formattedTime);
- return getResources().getString(warningRes, template);
- }
-
- @VisibleForTesting
- void handleUpdateConditions(Condition c) {
- if (mTransitionHelper.isTransitioning()) {
- return;
- }
- // forever
- bind(forever(), mZenRadioGroupContent.getChildAt(FOREVER_CONDITION_INDEX),
- FOREVER_CONDITION_INDEX);
- if (c == null) {
- bindGenericCountdown();
- bindNextAlarm(getTimeUntilNextAlarmCondition());
- } else if (isForever(c)) {
-
- getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true);
- bindGenericCountdown();
- bindNextAlarm(getTimeUntilNextAlarmCondition());
- } else {
- if (isAlarm(c)) {
- bindGenericCountdown();
- bindNextAlarm(c);
- getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.setChecked(true);
- } else if (isCountdown(c)) {
- bindNextAlarm(getTimeUntilNextAlarmCondition());
- bind(c, mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX),
- COUNTDOWN_CONDITION_INDEX);
- getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.setChecked(true);
- } else {
- Slog.wtf(TAG, "Invalid manual condition: " + c);
- }
- }
- mZenConditions.setVisibility(mSessionZen != Global.ZEN_MODE_OFF ? View.VISIBLE : View.GONE);
- }
-
- private void bindGenericCountdown() {
- mBucketIndex = DEFAULT_BUCKET_INDEX;
- Condition countdown = ZenModeConfig.toTimeCondition(mContext,
- MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
- // don't change the hour condition while the user is viewing the panel
- if (!mAttached || getConditionTagAt(COUNTDOWN_CONDITION_INDEX).condition == null) {
- bind(countdown, mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX),
- COUNTDOWN_CONDITION_INDEX);
- }
- }
-
- private void bindNextAlarm(Condition c) {
- View alarmContent = mZenRadioGroupContent.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX);
- ConditionTag tag = (ConditionTag) alarmContent.getTag();
- // Don't change the alarm condition while the user is viewing the panel
- if (c != null && (!mAttached || tag == null || tag.condition == null)) {
- bind(c, alarmContent, COUNTDOWN_ALARM_CONDITION_INDEX);
- }
-
- tag = (ConditionTag) alarmContent.getTag();
- boolean showAlarm = tag != null && tag.condition != null;
- mZenRadioGroup.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX).setVisibility(
- showAlarm ? View.VISIBLE : View.INVISIBLE);
- alarmContent.setVisibility(showAlarm ? View.VISIBLE : View.INVISIBLE);
- }
-
- private Condition forever() {
- return new Condition(mForeverId, foreverSummary(mContext), "", "", 0 /*icon*/,
- Condition.STATE_TRUE, 0 /*flags*/);
- }
-
- private static String foreverSummary(Context context) {
- return context.getString(com.android.internal.R.string.zen_mode_forever);
- }
-
- // Returns a time condition if the next alarm is within the next week.
- private Condition getTimeUntilNextAlarmCondition() {
- GregorianCalendar weekRange = new GregorianCalendar();
- setToMidnight(weekRange);
- weekRange.add(Calendar.DATE, 6);
- final long nextAlarmMs = mController.getNextAlarm();
- if (nextAlarmMs > 0) {
- GregorianCalendar nextAlarm = new GregorianCalendar();
- nextAlarm.setTimeInMillis(nextAlarmMs);
- setToMidnight(nextAlarm);
-
- if (weekRange.compareTo(nextAlarm) >= 0) {
- return ZenModeConfig.toNextAlarmCondition(mContext, nextAlarmMs,
- ActivityManager.getCurrentUser());
- }
- }
- return null;
- }
-
- private void setToMidnight(Calendar calendar) {
- calendar.set(Calendar.HOUR_OF_DAY, 0);
- calendar.set(Calendar.MINUTE, 0);
- calendar.set(Calendar.SECOND, 0);
- calendar.set(Calendar.MILLISECOND, 0);
- }
-
- @VisibleForTesting
- ConditionTag getConditionTagAt(int index) {
- return (ConditionTag) mZenRadioGroupContent.getChildAt(index).getTag();
- }
-
- @VisibleForTesting
- int getVisibleConditions() {
- int rt = 0;
- final int N = mZenRadioGroupContent.getChildCount();
- for (int i = 0; i < N; i++) {
- rt += mZenRadioGroupContent.getChildAt(i).getVisibility() == VISIBLE ? 1 : 0;
- }
- return rt;
- }
-
- private void hideAllConditions() {
- final int N = mZenRadioGroupContent.getChildCount();
- for (int i = 0; i < N; i++) {
- mZenRadioGroupContent.getChildAt(i).setVisibility(GONE);
- }
- }
-
- private static boolean isAlarm(Condition c) {
- return c != null && ZenModeConfig.isValidCountdownToAlarmConditionId(c.id);
- }
-
- private static boolean isCountdown(Condition c) {
- return c != null && ZenModeConfig.isValidCountdownConditionId(c.id);
- }
-
- private boolean isForever(Condition c) {
- return c != null && mForeverId.equals(c.id);
- }
-
- private void bind(final Condition condition, final View row, final int rowId) {
- if (condition == null) throw new IllegalArgumentException("condition must not be null");
- final boolean enabled = condition.state == Condition.STATE_TRUE;
- final ConditionTag tag =
- row.getTag() != null ? (ConditionTag) row.getTag() : new ConditionTag();
- row.setTag(tag);
- final boolean first = tag.rb == null;
- if (tag.rb == null) {
- tag.rb = (RadioButton) mZenRadioGroup.getChildAt(rowId);
- }
- tag.condition = condition;
- final Uri conditionId = getConditionId(tag.condition);
- if (DEBUG) Log.d(mTag, "bind i=" + mZenRadioGroupContent.indexOfChild(row) + " first="
- + first + " condition=" + conditionId);
- tag.rb.setEnabled(enabled);
- tag.rb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- if (mExpanded && isChecked) {
- tag.rb.setChecked(true);
- if (DEBUG) Log.d(mTag, "onCheckedChanged " + conditionId);
- MetricsLogger.action(mContext, MetricsEvent.QS_DND_CONDITION_SELECT);
- select(tag.condition);
- announceConditionSelection(tag);
- }
- }
- });
-
- if (tag.lines == null) {
- tag.lines = row.findViewById(android.R.id.content);
- }
- if (tag.line1 == null) {
- tag.line1 = (TextView) row.findViewById(android.R.id.text1);
- mConfigurableTexts.add(tag.line1);
- }
- if (tag.line2 == null) {
- tag.line2 = (TextView) row.findViewById(android.R.id.text2);
- mConfigurableTexts.add(tag.line2);
- }
- final String line1 = !TextUtils.isEmpty(condition.line1) ? condition.line1
- : condition.summary;
- final String line2 = condition.line2;
- tag.line1.setText(line1);
- if (TextUtils.isEmpty(line2)) {
- tag.line2.setVisibility(GONE);
- } else {
- tag.line2.setVisibility(VISIBLE);
- tag.line2.setText(line2);
- }
- tag.lines.setEnabled(enabled);
- tag.lines.setAlpha(enabled ? 1 : .4f);
-
- final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1);
- button1.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- onClickTimeButton(row, tag, false /*down*/, rowId);
- }
- });
-
- final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2);
- button2.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- onClickTimeButton(row, tag, true /*up*/, rowId);
- }
- });
- tag.lines.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- tag.rb.setChecked(true);
- }
- });
-
- final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
- if (rowId != COUNTDOWN_ALARM_CONDITION_INDEX && time > 0) {
- button1.setVisibility(VISIBLE);
- button2.setVisibility(VISIBLE);
- if (mBucketIndex > -1) {
- button1.setEnabled(mBucketIndex > 0);
- button2.setEnabled(mBucketIndex < MINUTE_BUCKETS.length - 1);
- } else {
- final long span = time - System.currentTimeMillis();
- button1.setEnabled(span > MIN_BUCKET_MINUTES * MINUTES_MS);
- final Condition maxCondition = ZenModeConfig.toTimeCondition(mContext,
- MAX_BUCKET_MINUTES, ActivityManager.getCurrentUser());
- button2.setEnabled(!Objects.equals(condition.summary, maxCondition.summary));
- }
-
- button1.setAlpha(button1.isEnabled() ? 1f : .5f);
- button2.setAlpha(button2.isEnabled() ? 1f : .5f);
- } else {
- button1.setVisibility(GONE);
- button2.setVisibility(GONE);
- }
- // wire up interaction callbacks for newly-added condition rows
- if (first) {
- Interaction.register(tag.rb, mInteractionCallback);
- Interaction.register(tag.lines, mInteractionCallback);
- Interaction.register(button1, mInteractionCallback);
- Interaction.register(button2, mInteractionCallback);
- }
- row.setVisibility(VISIBLE);
- }
-
- private void announceConditionSelection(ConditionTag tag) {
- final int zen = getSelectedZen(Global.ZEN_MODE_OFF);
- String modeText;
- switch(zen) {
- case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
- modeText = mContext.getString(R.string.interruption_level_priority);
- break;
- case Global.ZEN_MODE_NO_INTERRUPTIONS:
- modeText = mContext.getString(R.string.interruption_level_none);
- break;
- case Global.ZEN_MODE_ALARMS:
- modeText = mContext.getString(R.string.interruption_level_alarms);
- break;
- default:
- return;
- }
- announceForAccessibility(mContext.getString(R.string.zen_mode_and_condition, modeText,
- tag.line1.getText()));
- }
-
- private void onClickTimeButton(View row, ConditionTag tag, boolean up, int rowId) {
- MetricsLogger.action(mContext, MetricsEvent.QS_DND_TIME, up);
- Condition newCondition = null;
- final int N = MINUTE_BUCKETS.length;
- if (mBucketIndex == -1) {
- // not on a known index, search for the next or prev bucket by time
- final Uri conditionId = getConditionId(tag.condition);
- final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
- final long now = System.currentTimeMillis();
- for (int i = 0; i < N; i++) {
- int j = up ? i : N - 1 - i;
- final int bucketMinutes = MINUTE_BUCKETS[j];
- final long bucketTime = now + bucketMinutes * MINUTES_MS;
- if (up && bucketTime > time || !up && bucketTime < time) {
- mBucketIndex = j;
- newCondition = ZenModeConfig.toTimeCondition(mContext,
- bucketTime, bucketMinutes, ActivityManager.getCurrentUser(),
- false /*shortVersion*/);
- break;
- }
- }
- if (newCondition == null) {
- mBucketIndex = DEFAULT_BUCKET_INDEX;
- newCondition = ZenModeConfig.toTimeCondition(mContext,
- MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
- }
- } else {
- // on a known index, simply increment or decrement
- mBucketIndex = Math.max(0, Math.min(N - 1, mBucketIndex + (up ? 1 : -1)));
- newCondition = ZenModeConfig.toTimeCondition(mContext,
- MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
- }
- bind(newCondition, row, rowId);
- tag.rb.setChecked(true);
- select(newCondition);
- announceConditionSelection(tag);
- }
-
- private void select(final Condition condition) {
- if (DEBUG) Log.d(mTag, "select " + condition);
- if (mSessionZen == -1 || mSessionZen == Global.ZEN_MODE_OFF) {
- if (DEBUG) Log.d(mTag, "Ignoring condition selection outside of manual zen");
- return;
- }
- final Uri realConditionId = getRealConditionId(condition);
- if (mController != null) {
- AsyncTask.execute(new Runnable() {
- @Override
- public void run() {
- mController.setZen(mSessionZen, realConditionId, TAG + ".selectCondition");
- }
- });
- }
- setExitCondition(condition);
- if (realConditionId == null) {
- mPrefs.setMinuteIndex(-1);
- } else if ((isAlarm(condition) || isCountdown(condition)) && mBucketIndex != -1) {
- mPrefs.setMinuteIndex(mBucketIndex);
- }
- setSessionExitCondition(copy(condition));
- }
-
- private void fireInteraction() {
- if (mCallback != null) {
- mCallback.onInteraction();
- }
- }
-
- private void fireExpanded() {
- if (mCallback != null) {
- mCallback.onExpanded(mExpanded);
- }
- }
-
- private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
- @Override
- public void onManualRuleChanged(ZenRule rule) {
- mHandler.obtainMessage(H.MANUAL_RULE_CHANGED, rule).sendToTarget();
- }
- };
-
- private final class H extends Handler {
- private static final int MANUAL_RULE_CHANGED = 2;
- private static final int UPDATE_WIDGETS = 3;
-
- private H() {
- super(Looper.getMainLooper());
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MANUAL_RULE_CHANGED: handleUpdateManualRule((ZenRule) msg.obj); break;
- case UPDATE_WIDGETS: updateWidgets(); break;
- }
- }
- }
-
- public interface Callback {
- void onPrioritySettings();
- void onInteraction();
- void onExpanded(boolean expanded);
- }
-
- // used as the view tag on condition rows
- @VisibleForTesting
- static class ConditionTag {
- RadioButton rb;
- View lines;
- TextView line1;
- TextView line2;
- Condition condition;
- }
-
- private final class ZenPrefs implements OnSharedPreferenceChangeListener {
- private final int mNoneDangerousThreshold;
-
- private int mMinuteIndex;
- private int mNoneSelected;
- private boolean mConfirmedPriorityIntroduction;
- private boolean mConfirmedSilenceIntroduction;
- private boolean mConfirmedAlarmIntroduction;
-
- private ZenPrefs() {
- mNoneDangerousThreshold = mContext.getResources()
- .getInteger(R.integer.zen_mode_alarm_warning_threshold);
- Prefs.registerListener(mContext, this);
- updateMinuteIndex();
- updateNoneSelected();
- updateConfirmedPriorityIntroduction();
- updateConfirmedSilenceIntroduction();
- updateConfirmedAlarmIntroduction();
- }
-
- public void trackNoneSelected() {
- mNoneSelected = clampNoneSelected(mNoneSelected + 1);
- if (DEBUG) Log.d(mTag, "Setting none selected: " + mNoneSelected + " threshold="
- + mNoneDangerousThreshold);
- Prefs.putInt(mContext, Prefs.Key.DND_NONE_SELECTED, mNoneSelected);
- }
-
- public int getMinuteIndex() {
- return mMinuteIndex;
- }
-
- public void setMinuteIndex(int minuteIndex) {
- minuteIndex = clampIndex(minuteIndex);
- if (minuteIndex == mMinuteIndex) return;
- mMinuteIndex = clampIndex(minuteIndex);
- if (DEBUG) Log.d(mTag, "Setting favorite minute index: " + mMinuteIndex);
- Prefs.putInt(mContext, Prefs.Key.DND_FAVORITE_BUCKET_INDEX, mMinuteIndex);
- }
-
- @Override
- public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
- updateMinuteIndex();
- updateNoneSelected();
- updateConfirmedPriorityIntroduction();
- updateConfirmedSilenceIntroduction();
- updateConfirmedAlarmIntroduction();
- }
-
- private void updateMinuteIndex() {
- mMinuteIndex = clampIndex(Prefs.getInt(mContext,
- Prefs.Key.DND_FAVORITE_BUCKET_INDEX, DEFAULT_BUCKET_INDEX));
- if (DEBUG) Log.d(mTag, "Favorite minute index: " + mMinuteIndex);
- }
-
- private int clampIndex(int index) {
- return MathUtils.constrain(index, -1, MINUTE_BUCKETS.length - 1);
- }
-
- private void updateNoneSelected() {
- mNoneSelected = clampNoneSelected(Prefs.getInt(mContext,
- Prefs.Key.DND_NONE_SELECTED, 0));
- if (DEBUG) Log.d(mTag, "None selected: " + mNoneSelected);
- }
-
- private int clampNoneSelected(int noneSelected) {
- return MathUtils.constrain(noneSelected, 0, Integer.MAX_VALUE);
- }
-
- private void updateConfirmedPriorityIntroduction() {
- final boolean confirmed = Prefs.getBoolean(mContext,
- Prefs.Key.DND_CONFIRMED_PRIORITY_INTRODUCTION, false);
- if (confirmed == mConfirmedPriorityIntroduction) return;
- mConfirmedPriorityIntroduction = confirmed;
- if (DEBUG) Log.d(mTag, "Confirmed priority introduction: "
- + mConfirmedPriorityIntroduction);
- }
-
- private void updateConfirmedSilenceIntroduction() {
- final boolean confirmed = Prefs.getBoolean(mContext,
- Prefs.Key.DND_CONFIRMED_SILENCE_INTRODUCTION, false);
- if (confirmed == mConfirmedSilenceIntroduction) return;
- mConfirmedSilenceIntroduction = confirmed;
- if (DEBUG) Log.d(mTag, "Confirmed silence introduction: "
- + mConfirmedSilenceIntroduction);
- }
-
- private void updateConfirmedAlarmIntroduction() {
- final boolean confirmed = Prefs.getBoolean(mContext,
- Prefs.Key.DND_CONFIRMED_ALARM_INTRODUCTION, false);
- if (confirmed == mConfirmedAlarmIntroduction) return;
- mConfirmedAlarmIntroduction = confirmed;
- if (DEBUG) Log.d(mTag, "Confirmed alarm introduction: "
- + mConfirmedAlarmIntroduction);
- }
- }
-
- protected final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() {
- @Override
- public void onSelected(final Object value, boolean fromClick) {
- if (value != null && mZenButtons.isShown() && isAttachedToWindow()) {
- final int zen = (Integer) value;
- if (fromClick) {
- MetricsLogger.action(mContext, MetricsEvent.QS_DND_ZEN_SELECT, zen);
- }
- if (DEBUG) Log.d(mTag, "mZenButtonsCallback selected=" + zen);
- final Uri realConditionId = getRealConditionId(mSessionExitCondition);
- AsyncTask.execute(new Runnable() {
- @Override
- public void run() {
- mController.setZen(zen, realConditionId, TAG + ".selectZen");
- if (zen != Global.ZEN_MODE_OFF) {
- Prefs.putInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, zen);
- }
- }
- });
- }
- }
-
- @Override
- public void onInteraction() {
- fireInteraction();
- }
- };
-
- private final Interaction.Callback mInteractionCallback = new Interaction.Callback() {
- @Override
- public void onInteraction() {
- fireInteraction();
- }
- };
-
- private final class TransitionHelper implements TransitionListener, Runnable {
- private final ArraySet<View> mTransitioningViews = new ArraySet<View>();
-
- private boolean mTransitioning;
- private boolean mPendingUpdateWidgets;
-
- public void clear() {
- mTransitioningViews.clear();
- mPendingUpdateWidgets = false;
- }
-
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println(" TransitionHelper state:");
- pw.print(" mPendingUpdateWidgets="); pw.println(mPendingUpdateWidgets);
- pw.print(" mTransitioning="); pw.println(mTransitioning);
- pw.print(" mTransitioningViews="); pw.println(mTransitioningViews);
- }
-
- public void pendingUpdateWidgets() {
- mPendingUpdateWidgets = true;
- }
-
- public boolean isTransitioning() {
- return !mTransitioningViews.isEmpty();
- }
-
- @Override
- public void startTransition(LayoutTransition transition,
- ViewGroup container, View view, int transitionType) {
- mTransitioningViews.add(view);
- updateTransitioning();
- }
-
- @Override
- public void endTransition(LayoutTransition transition,
- ViewGroup container, View view, int transitionType) {
- mTransitioningViews.remove(view);
- updateTransitioning();
- }
-
- @Override
- public void run() {
- if (DEBUG) Log.d(mTag, "TransitionHelper run"
- + " mPendingUpdateWidgets=" + mPendingUpdateWidgets);
- if (mPendingUpdateWidgets) {
- updateWidgets();
- }
- mPendingUpdateWidgets = false;
- }
-
- private void updateTransitioning() {
- final boolean transitioning = isTransitioning();
- if (mTransitioning == transitioning) return;
- mTransitioning = transitioning;
- if (DEBUG) Log.d(mTag, "TransitionHelper mTransitioning=" + mTransitioning);
- if (!mTransitioning) {
- if (mPendingUpdateWidgets) {
- mHandler.post(this);
- } else {
- mPendingUpdateWidgets = false;
- }
- }
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 24b01e0..6736bfd 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -307,7 +307,8 @@
UserInfo info = new UserInfo(i /* id */, "Name: " + i, null /* iconPath */,
0 /* flags */);
users.add(new UserRecord(info, null, false /* isGuest */, false /* isCurrent */,
- false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */));
+ false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */,
+ false /* isAddSupervisedUser */));
}
return users;
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
index 9f8f6c1..06082b6 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
@@ -32,6 +32,7 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tuner.TunerService;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -66,6 +67,11 @@
mController.setupUri(KeyguardSliceProvider.KEYGUARD_SLICE_URI);
}
+ @After
+ public void tearDown() {
+ mController.onViewDetached();
+ }
+
@Test
public void refresh_replacesSliceContentAndNotifiesListener() {
mController.refresh();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 08d881f..f71dd24 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -32,7 +32,6 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -58,7 +57,6 @@
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
-import android.media.AudioManager;
import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.os.Handler;
@@ -74,9 +72,6 @@
import android.testing.TestableContext;
import android.testing.TestableLooper;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.Observer;
-
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.telephony.TelephonyIntents;
@@ -92,7 +87,6 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
-import com.android.systemui.util.RingerModeTracker;
import org.junit.After;
import org.junit.Assert;
@@ -101,7 +95,6 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
-import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
@@ -161,10 +154,6 @@
@Mock
private TelephonyManager mTelephonyManager;
@Mock
- private RingerModeTracker mRingerModeTracker;
- @Mock
- private LiveData<Integer> mRingerModeLiveData;
- @Mock
private StatusBarStateController mStatusBarStateController;
@Mock
private AuthController mAuthController;
@@ -242,8 +231,6 @@
mSpiedContext.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
mSpiedContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
- when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData);
-
mMockitoSession = ExtendedMockito.mockitoSession()
.spyStatic(SubscriptionManager.class).startMocking();
ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
@@ -866,29 +853,6 @@
}
@Test
- public void testRingerModeChange() {
- ArgumentCaptor<Observer<Integer>> captor = ArgumentCaptor.forClass(Observer.class);
- verify(mRingerModeLiveData).observeForever(captor.capture());
- Observer<Integer> observer = captor.getValue();
-
- KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
-
- mKeyguardUpdateMonitor.registerCallback(callback);
-
- observer.onChanged(AudioManager.RINGER_MODE_NORMAL);
- observer.onChanged(AudioManager.RINGER_MODE_SILENT);
- observer.onChanged(AudioManager.RINGER_MODE_VIBRATE);
-
- mTestableLooper.processAllMessages();
-
- InOrder orderVerify = inOrder(callback);
- orderVerify.verify(callback).onRingerModeChanged(anyInt()); // Initial update on register
- orderVerify.verify(callback).onRingerModeChanged(AudioManager.RINGER_MODE_NORMAL);
- orderVerify.verify(callback).onRingerModeChanged(AudioManager.RINGER_MODE_SILENT);
- orderVerify.verify(callback).onRingerModeChanged(AudioManager.RINGER_MODE_VIBRATE);
- }
-
- @Test
public void testRegisterAuthControllerCallback() {
assertThat(mKeyguardUpdateMonitor.isUdfpsEnrolled()).isFalse();
@@ -1120,7 +1084,7 @@
super(context,
TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
mBroadcastDispatcher, mDumpManager,
- mRingerModeTracker, mBackgroundExecutor, mMainExecutor,
+ mBackgroundExecutor, mMainExecutor,
mStatusBarStateController, mLockPatternUtils,
mAuthController, mTelephonyListenerManager,
mInteractionJankMonitor, mLatencyTracker);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java
index 2d52c42..c5b1a1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java
@@ -16,7 +16,8 @@
package com.android.systemui.communal;
-import static org.mockito.ArgumentMatchers.anyBoolean;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -59,7 +60,8 @@
final Condition.Callback callback = mock(Condition.Callback.class);
mCondition.addCallback(callback);
- verify(callback).onConditionChanged(mCondition, true);
+ verify(callback).onConditionChanged(mCondition);
+ assertThat(mCondition.isConditionMet()).isTrue();
}
@Test
@@ -68,7 +70,7 @@
final Condition.Callback callback = mock(Condition.Callback.class);
mCondition.addCallback(callback);
- verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean());
+ verify(callback, never()).onConditionChanged(eq(mCondition));
}
@Test
@@ -80,7 +82,8 @@
clearInvocations(callback);
updateCommunalSetting(true);
- verify(callback).onConditionChanged(mCondition, true);
+ verify(callback).onConditionChanged(mCondition);
+ assertThat(mCondition.isConditionMet()).isTrue();
}
@Test
@@ -92,7 +95,8 @@
clearInvocations(callback);
updateCommunalSetting(false);
- verify(callback).onConditionChanged(mCondition, false);
+ verify(callback).onConditionChanged(mCondition);
+ assertThat(mCondition.isConditionMet()).isFalse();
}
@Test
@@ -104,7 +108,8 @@
clearInvocations(callback);
updateCommunalSetting(true);
- verify(callback, never()).onConditionChanged(mCondition, true);
+ verify(callback, never()).onConditionChanged(mCondition);
+ assertThat(mCondition.isConditionMet()).isTrue();
}
private void updateCommunalSetting(boolean value) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java
index 61a5126..500205c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java
@@ -16,7 +16,8 @@
package com.android.systemui.communal;
-import static org.mockito.ArgumentMatchers.anyBoolean;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.clearInvocations;
@@ -49,6 +50,7 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@SmallTest
@@ -89,7 +91,8 @@
networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi1));
// Verifies that the callback is triggered.
- verify(callback).onConditionChanged(mCondition, true);
+ verify(callback).onConditionChanged(mCondition);
+ assertThat(mCondition.isConditionMet()).isTrue();
}
@Test
@@ -110,7 +113,7 @@
networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi2));
// Verifies that the callback is not triggered.
- verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean());
+ verify(callback, never()).onConditionChanged(eq(mCondition));
}
@Test
@@ -126,11 +129,13 @@
networkCallback.onAvailable(network);
networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi1));
+ Mockito.clearInvocations(callback);
// Connected to non-trusted Wi-Fi network.
networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities("random-wifi"));
// Verifies that the callback is triggered.
- verify(callback).onConditionChanged(mCondition, false);
+ verify(callback).onConditionChanged(mCondition);
+ assertThat(mCondition.isConditionMet()).isFalse();
}
@Test
@@ -151,7 +156,8 @@
networkCallback.onLost(network);
// Verifies that the callback is triggered.
- verify(callback).onConditionChanged(mCondition, false);
+ verify(callback).onConditionChanged(mCondition);
+ assertThat(mCondition.isConditionMet()).isFalse();
}
// Captures and returns the network callback, assuming it is registered with the connectivity
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
index cdffaec..7e1edd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
@@ -35,6 +35,7 @@
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
@@ -306,4 +307,11 @@
// THEN the display screen state will change
assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState);
}
+
+ @Test
+ public void authCallbackRemovedOnDestroy() {
+ mScreen.destroy();
+
+ verify(mAuthController).removeCallback(anyObject());
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 8adb55b..529a163 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -31,6 +31,7 @@
import android.view.WindowManager;
import android.view.WindowManagerImpl;
+import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
import androidx.test.filters.SmallTest;
@@ -191,4 +192,14 @@
| Complication.COMPLICATION_TYPE_WEATHER;
verify(mStateController).setAvailableComplicationTypes(expectedTypes);
}
+
+ @Test
+ public void testDestroy() {
+ mService.onDestroy();
+ mMainExecutor.runAllReady();
+
+ verify(mKeyguardUpdateMonitor).removeCallback(any());
+ verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
+ verify(mStateController).setOverlayActive(false);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
index 9e67eda..57fbbc9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
@@ -62,7 +62,7 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- dumpHandler = DumpHandler(mContext, dumpManager, logBufferEulogizer)
+ dumpHandler = DumpHandler(mContext, dumpManager, logBufferEulogizer, mutableMapOf())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 210cb82..a80aed7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -18,6 +18,9 @@
import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
+
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -43,6 +46,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardDisplayManager;
+import com.android.keyguard.KeyguardSecurityView;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.mediator.ScreenOnCoordinator;
import com.android.systemui.SysuiTestCase;
@@ -181,6 +185,24 @@
verify(mStatusBarKeyguardViewManager, atLeast(1)).show(null);
}
+ @Test
+ public void testBouncerPrompt_deviceLockedByAdmin() {
+ // GIVEN no trust agents enabled and biometrics aren't enrolled
+ when(mUpdateMonitor.isTrustUsuallyManaged(anyInt())).thenReturn(false);
+ when(mUpdateMonitor.isUnlockingWithBiometricsPossible(anyInt())).thenReturn(false);
+
+ // WHEN the strong auth reason is AFTER_DPM_LOCK_NOW
+ KeyguardUpdateMonitor.StrongAuthTracker strongAuthTracker =
+ mock(KeyguardUpdateMonitor.StrongAuthTracker.class);
+ when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(strongAuthTracker);
+ when(strongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
+ STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW);
+
+ // THEN the bouncer prompt reason should return PROMPT_REASON_DEVICE_ADMIN
+ assertEquals(KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN,
+ mViewMediator.mViewMediatorCallback.getBouncerPromptReason());
+ }
+
private void createAndStartViewMediator() {
mViewMediator = new KeyguardViewMediator(
mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 609291a..708fc91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -74,6 +74,7 @@
private const val SESSION_ARTIST = "SESSION_ARTIST"
private const val SESSION_TITLE = "SESSION_TITLE"
private const val USER_ID = 0
+private const val DISABLED_DEVICE_NAME = "DISABLED_DEVICE_NAME"
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -131,7 +132,7 @@
private lateinit var session: MediaSession
private val device = MediaDeviceData(true, null, DEVICE_NAME)
- private val disabledDevice = MediaDeviceData(false, null, "Disabled Device")
+ private val disabledDevice = MediaDeviceData(false, null, DISABLED_DEVICE_NAME)
private lateinit var mediaData: MediaData
private val clock = FakeSystemClock()
@@ -396,13 +397,12 @@
@Test
fun bindDisabledDevice() {
seamless.id = 1
- val fallbackString = context.getString(R.string.media_seamless_other_device)
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
val state = mediaData.copy(device = disabledDevice)
player.bindPlayer(state, PACKAGE)
assertThat(seamless.isEnabled()).isFalse()
- assertThat(seamlessText.getText()).isEqualTo(fallbackString)
- assertThat(seamless.contentDescription).isEqualTo(fallbackString)
+ assertThat(seamlessText.getText()).isEqualTo(DISABLED_DEVICE_NAME)
+ assertThat(seamless.contentDescription).isEqualTo(DISABLED_DEVICE_NAME)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index 649ee87..f4fa921 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -1,6 +1,5 @@
package com.android.systemui.media
-import android.app.Notification
import android.app.Notification.MediaStyle
import android.app.PendingIntent
import android.app.smartspace.SmartspaceAction
@@ -240,15 +239,14 @@
@Test
fun testOnNotificationAdded_isRcn_markedRemote() {
- val bundle = Bundle().apply {
- putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, "Remote Cast Notification")
- }
val rcn = SbnBuilder().run {
setPkg("com.android.systemui") // System package
modifyNotification(context).also {
it.setSmallIcon(android.R.drawable.ic_media_pause)
- it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
- it.addExtras(bundle)
+ it.setStyle(MediaStyle().apply {
+ setMediaSession(session.sessionToken)
+ setRemotePlaybackInfo("Remote device", 0, null)
+ })
}
build()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index 3d59497..d912a89 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -30,6 +30,8 @@
import com.android.settingslib.media.MediaDevice
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
+import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
@@ -44,6 +46,7 @@
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
@@ -71,6 +74,8 @@
@Mock private lateinit var lmmFactory: LocalMediaManagerFactory
@Mock private lateinit var lmm: LocalMediaManager
@Mock private lateinit var mr2: MediaRouter2Manager
+ @Mock private lateinit var muteAwaitFactory: MediaMuteAwaitConnectionManagerFactory
+ @Mock private lateinit var muteAwaitManager: MediaMuteAwaitConnectionManager
private lateinit var fakeFgExecutor: FakeExecutor
private lateinit var fakeBgExecutor: FakeExecutor
@Mock private lateinit var dumpster: DumpManager
@@ -88,14 +93,22 @@
fun setUp() {
fakeFgExecutor = FakeExecutor(FakeSystemClock())
fakeBgExecutor = FakeExecutor(FakeSystemClock())
- manager = MediaDeviceManager(controllerFactory, lmmFactory, mr2, fakeFgExecutor,
- fakeBgExecutor, dumpster)
+ manager = MediaDeviceManager(
+ controllerFactory,
+ lmmFactory,
+ mr2,
+ muteAwaitFactory,
+ fakeFgExecutor,
+ fakeBgExecutor,
+ dumpster
+ )
manager.addListener(listener)
// Configure mocks.
whenever(device.name).thenReturn(DEVICE_NAME)
whenever(device.iconWithoutBackground).thenReturn(icon)
whenever(lmmFactory.create(PACKAGE)).thenReturn(lmm)
+ whenever(muteAwaitFactory.create(lmm)).thenReturn(muteAwaitManager)
whenever(lmm.getCurrentConnectedDevice()).thenReturn(device)
whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(route)
@@ -146,6 +159,7 @@
manager.onMediaDataRemoved(KEY)
fakeBgExecutor.runAllReady()
verify(lmm).unregisterCallback(any())
+ verify(muteAwaitManager).stopListening()
}
@Test
@@ -169,6 +183,7 @@
fakeFgExecutor.runAllReady()
// THEN the listener for the old key should removed.
verify(lmm).unregisterCallback(any())
+ verify(muteAwaitManager).stopListening()
// AND a new device event emitted
val data = captureDeviceData(KEY, KEY_OLD)
assertThat(data.enabled).isTrue()
@@ -240,6 +255,7 @@
manager.onMediaDataLoaded(KEY, null, mediaData)
fakeBgExecutor.runAllReady()
val deviceCallback = captureCallback()
+ verify(muteAwaitManager).startListening()
// WHEN the device list changes
deviceCallback.onDeviceListUpdate(mutableListOf(device))
assertThat(fakeBgExecutor.runAllReady()).isEqualTo(1)
@@ -268,6 +284,51 @@
}
@Test
+ fun onAboutToConnectDeviceChangedWithNonNullParams() {
+ manager.onMediaDataLoaded(KEY, null, mediaData)
+ // Run and reset the executors and listeners so we only focus on new events.
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+ reset(listener)
+
+ val deviceCallback = captureCallback()
+ // WHEN the about-to-connect device changes to non-null
+ val name = "AboutToConnectDeviceName"
+ val mockIcon = mock(Drawable::class.java)
+ deviceCallback.onAboutToConnectDeviceChanged(name, mockIcon)
+ assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1)
+ // THEN the about-to-connect device is returned
+ val data = captureDeviceData(KEY)
+ assertThat(data.enabled).isTrue()
+ assertThat(data.name).isEqualTo(name)
+ assertThat(data.icon).isEqualTo(mockIcon)
+ }
+
+ @Test
+ fun onAboutToConnectDeviceChangedWithNullParams() {
+ manager.onMediaDataLoaded(KEY, null, mediaData)
+ fakeBgExecutor.runAllReady()
+ val deviceCallback = captureCallback()
+ // First set a non-null about-to-connect device
+ deviceCallback.onAboutToConnectDeviceChanged(
+ "AboutToConnectDeviceName", mock(Drawable::class.java)
+ )
+ // Run and reset the executors and listeners so we only focus on new events.
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+ reset(listener)
+
+ // WHEN the about-to-connect device changes to null
+ deviceCallback.onAboutToConnectDeviceChanged(null, null)
+ assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1)
+ // THEN the normal device is returned
+ val data = captureDeviceData(KEY)
+ assertThat(data.enabled).isTrue()
+ assertThat(data.name).isEqualTo(DEVICE_NAME)
+ assertThat(data.icon).isEqualTo(icon)
+ }
+
+ @Test
fun listenerReceivesKeyRemoved() {
manager.onMediaDataLoaded(KEY, null, mediaData)
// WHEN the notification is removed
@@ -376,6 +437,24 @@
verify(mr2, never()).getRoutingSessionForMediaController(eq(controller))
}
+ @Test
+ fun testRemotePlaybackDeviceOverride() {
+ whenever(route.name).thenReturn(DEVICE_NAME)
+ val deviceData = MediaDeviceData(false, null, REMOTE_DEVICE_NAME, null)
+ val mediaDataWithDevice = mediaData.copy(device = deviceData)
+
+ // GIVEN media data that already has a device set
+ manager.onMediaDataLoaded(KEY, null, mediaDataWithDevice)
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+
+ // THEN we keep the device info, and don't register a listener
+ val data = captureDeviceData(KEY)
+ assertThat(data.enabled).isFalse()
+ assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME)
+ verify(lmm, never()).registerCallback(any())
+ }
+
fun captureCallback(): LocalMediaManager.DeviceCallback {
val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java)
verify(lmm).registerCallback(captor.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 0576987..bdc3117 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -201,7 +201,7 @@
assertThat(devices.containsAll(mMediaDevices)).isTrue();
assertThat(devices.size()).isEqualTo(mMediaDevices.size());
- verify(mCb).onRouteChanged();
+ verify(mCb).onDeviceListChanged();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
new file mode 100644
index 0000000..88c4514
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
@@ -0,0 +1,202 @@
+/*
+ * 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.media.muteawait
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.media.AudioAttributes.USAGE_MEDIA
+import android.media.AudioAttributes.USAGE_UNKNOWN
+import android.media.AudioDeviceAttributes
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import android.media.AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.settingslib.media.DeviceIconUtil
+import com.android.settingslib.media.LocalMediaManager
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+
+@SmallTest
+class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() {
+ private lateinit var muteAwaitConnectionManager: MediaMuteAwaitConnectionManager
+ @Mock
+ private lateinit var audioManager: AudioManager
+ @Mock
+ private lateinit var deviceIconUtil: DeviceIconUtil
+ @Mock
+ private lateinit var localMediaManager: LocalMediaManager
+ private lateinit var icon: Drawable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ context.addMockSystemService(Context.AUDIO_SERVICE, audioManager)
+ icon = context.getDrawable(R.drawable.ic_cake)!!
+ whenever(deviceIconUtil.getIconFromAudioDeviceType(any(), any())).thenReturn(icon)
+
+ muteAwaitConnectionManager = MediaMuteAwaitConnectionManager(
+ FakeExecutor(FakeSystemClock()),
+ localMediaManager,
+ context,
+ deviceIconUtil
+ )
+ }
+
+ @Test
+ fun constructor_audioManagerCallbackNotRegistered() {
+ verify(audioManager, never()).registerMuteAwaitConnectionCallback(any(), any())
+ }
+
+ @Test
+ fun startListening_audioManagerCallbackRegistered() {
+ muteAwaitConnectionManager.startListening()
+
+ verify(audioManager).registerMuteAwaitConnectionCallback(any(), any())
+ }
+
+ @Test
+ fun stopListening_audioManagerCallbackUnregistered() {
+ muteAwaitConnectionManager.stopListening()
+
+ verify(audioManager).unregisterMuteAwaitConnectionCallback(any())
+ }
+
+ @Test
+ fun startListening_audioManagerHasNoMuteAwaitDevice_localMediaMangerNotNotified() {
+ whenever(audioManager.mutingExpectedDevice).thenReturn(null)
+
+ muteAwaitConnectionManager.startListening()
+
+ verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+ }
+
+ @Test
+ fun startListening_audioManagerHasMuteAwaitDevice_localMediaMangerNotified() {
+ whenever(audioManager.mutingExpectedDevice).thenReturn(DEVICE)
+
+ muteAwaitConnectionManager.startListening()
+
+ verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), eq(icon))
+ }
+
+ @Test
+ fun onMutedUntilConnection_notUsageMedia_localMediaManagerNotNotified() {
+ muteAwaitConnectionManager.startListening()
+ val muteAwaitListener = getMuteAwaitListener()
+
+ muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_UNKNOWN))
+
+ verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+ }
+
+ @Test
+ fun onMutedUntilConnection_isUsageMedia_localMediaManagerNotified() {
+ muteAwaitConnectionManager.startListening()
+ val muteAwaitListener = getMuteAwaitListener()
+
+
+ muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA))
+
+ verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), eq(icon))
+ }
+
+ @Test
+ fun onUnmutedEvent_noDeviceMutedBefore_localMediaManagerNotNotified() {
+ muteAwaitConnectionManager.startListening()
+ val muteAwaitListener = getMuteAwaitListener()
+
+ muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_MEDIA))
+
+ verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+ }
+
+ @Test
+ fun onUnmutedEvent_notSameDevice_localMediaManagerNotNotified() {
+ muteAwaitConnectionManager.startListening()
+ val muteAwaitListener = getMuteAwaitListener()
+ muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA))
+ reset(localMediaManager)
+
+ val otherDevice = AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_USB_HEADSET,
+ "address",
+ "DifferentName",
+ listOf(),
+ listOf(),
+ )
+ muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, otherDevice, intArrayOf(USAGE_MEDIA))
+
+ verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+ }
+
+ @Test
+ fun onUnmutedEvent_notUsageMedia_localMediaManagerNotNotified() {
+ muteAwaitConnectionManager.startListening()
+ val muteAwaitListener = getMuteAwaitListener()
+ muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA))
+ reset(localMediaManager)
+
+ muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_UNKNOWN))
+
+ verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+ }
+
+ @Test
+ fun onUnmutedEvent_sameDeviceAndUsageMedia_localMediaManagerNotified() {
+ muteAwaitConnectionManager.startListening()
+ val muteAwaitListener = getMuteAwaitListener()
+ muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA))
+ reset(localMediaManager)
+
+ muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_MEDIA))
+
+ verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(null), eq(null))
+ }
+
+ private fun getMuteAwaitListener(): AudioManager.MuteAwaitConnectionCallback {
+ val listenerCaptor = ArgumentCaptor.forClass(
+ AudioManager.MuteAwaitConnectionCallback::class.java
+ )
+ verify(audioManager).registerMuteAwaitConnectionCallback(any(), listenerCaptor.capture())
+ return listenerCaptor.value!!
+ }
+}
+
+private const val DEVICE_NAME = "DeviceName"
+private val DEVICE = AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_USB_HEADSET,
+ "address",
+ DEVICE_NAME,
+ listOf(),
+ listOf(),
+)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
index cb05d03..14afece 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
@@ -203,7 +203,9 @@
verify(statusBarManager).updateMediaTapToTransferReceiverDisplay(
eq(StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER),
- any()
+ any(),
+ nullable(),
+ nullable()
)
}
@@ -213,7 +215,9 @@
verify(statusBarManager).updateMediaTapToTransferReceiverDisplay(
eq(StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER),
- any()
+ any(),
+ nullable(),
+ nullable()
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index 242fd19..f05d621 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -92,16 +92,16 @@
fun setIcon_viewHasIconAndContentDescription() {
controllerCommon.displayChip(getState())
val chipView = getChipView()
- val drawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
- val contentDescription = "test description"
- controllerCommon.setIcon(MediaTttChipState(drawable, contentDescription), chipView)
+ val state = MediaTttChipState(PACKAGE_NAME)
+ controllerCommon.setIcon(state, chipView)
- assertThat(chipView.getAppIconView().drawable).isEqualTo(drawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(contentDescription)
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
+ assertThat(chipView.getAppIconView().contentDescription)
+ .isEqualTo(state.getAppName(context))
}
- private fun getState() = MediaTttChipState(appIconDrawable, APP_ICON_CONTENT_DESCRIPTION)
+ private fun getState() = MediaTttChipState(PACKAGE_NAME)
private fun getChipView(): ViewGroup {
val viewCaptor = ArgumentCaptor.forClass(View::class.java)
@@ -122,4 +122,4 @@
}
}
-private const val APP_ICON_CONTENT_DESCRIPTION = "Content description"
+private const val PACKAGE_NAME = "com.android.systemui"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index fce4954..44f691c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -19,6 +19,7 @@
import android.app.StatusBarManager
import android.graphics.drawable.Icon
import android.media.MediaRoute2Info
+import android.os.Handler
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
@@ -52,7 +53,8 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- controllerReceiver = MediaTttChipControllerReceiver(commandQueue, context, windowManager)
+ controllerReceiver = MediaTttChipControllerReceiver(
+ commandQueue, context, windowManager, Handler.getMain())
val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
verify(commandQueue).addCallback(callbackCaptor.capture())
@@ -61,19 +63,24 @@
@Test
fun commandQueueCallback_closeToSender_triggersChip() {
+ val appName = "FakeAppName"
commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
- routeInfo
+ routeInfo,
+ /* appIcon= */ null,
+ appName
)
- assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(ROUTE_NAME)
+ assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(appName)
}
@Test
fun commandQueueCallback_farFromSender_noChipShown() {
commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
- routeInfo
+ routeInfo,
+ null,
+ null
)
verify(windowManager, never()).addView(any(), any())
@@ -83,12 +90,16 @@
fun commandQueueCallback_closeThenFar_chipShownThenHidden() {
commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
- routeInfo
+ routeInfo,
+ null,
+ null
)
commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
- routeInfo
+ routeInfo,
+ null,
+ null
)
val viewCaptor = ArgumentCaptor.forClass(View::class.java)
@@ -97,14 +108,43 @@
}
@Test
- fun displayChip_chipContainsIcon() {
- val drawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
- val contentDescription = "Test description"
+ fun displayChip_nullAppIconDrawable_iconIsFromPackageName() {
+ val state = ChipStateReceiver(PACKAGE_NAME, appIconDrawable = null, "appName")
- controllerReceiver.displayChip(ChipStateReceiver(drawable, contentDescription))
+ controllerReceiver.displayChip(state)
+
+ assertThat(getChipView().getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
+
+ }
+
+ @Test
+ fun displayChip_hasAppIconDrawable_iconIsDrawable() {
+ val drawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
+ val state = ChipStateReceiver(PACKAGE_NAME, drawable, "appName")
+
+ controllerReceiver.displayChip(state)
assertThat(getChipView().getAppIconView().drawable).isEqualTo(drawable)
- assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(contentDescription)
+ }
+
+ @Test
+ fun displayChip_nullAppName_iconContentDescriptionIsFromPackageName() {
+ val state = ChipStateReceiver(PACKAGE_NAME, appIconDrawable = null, appName = null)
+
+ controllerReceiver.displayChip(state)
+
+ assertThat(getChipView().getAppIconView().contentDescription)
+ .isEqualTo(state.getAppName(context))
+ }
+
+ @Test
+ fun displayChip_hasAppName_iconContentDescriptionIsAppNameOverride() {
+ val appName = "FakeAppName"
+ val state = ChipStateReceiver(PACKAGE_NAME, appIconDrawable = null, appName)
+
+ controllerReceiver.displayChip(state)
+
+ assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(appName)
}
private fun getChipView(): ViewGroup {
@@ -116,7 +156,9 @@
private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
}
-private const val ROUTE_NAME = "Test name"
-private val routeInfo = MediaRoute2Info.Builder("id", ROUTE_NAME)
+private const val PACKAGE_NAME = "com.android.systemui"
+
+private val routeInfo = MediaRoute2Info.Builder("id", "Test route name")
.addFeature("feature")
+ .setPackageName(PACKAGE_NAME)
.build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index c74ac64..dc39893 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -17,8 +17,6 @@
package com.android.systemui.media.taptotransfer.sender
import android.app.StatusBarManager
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.Icon
import android.media.MediaRoute2Info
import android.view.View
import android.view.WindowManager
@@ -44,8 +42,6 @@
@SmallTest
@Ignore("b/216286227")
class MediaTttChipControllerSenderTest : SysuiTestCase() {
- private lateinit var appIconDrawable: Drawable
-
private lateinit var controllerSender: MediaTttChipControllerSender
@Mock
@@ -57,7 +53,6 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- appIconDrawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
controllerSender = MediaTttChipControllerSender(commandQueue, context, windowManager)
val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
@@ -197,8 +192,9 @@
controllerSender.displayChip(state)
val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
+ assertThat(chipView.getAppIconView().contentDescription)
+ .isEqualTo(state.getAppName(context))
assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
@@ -211,8 +207,9 @@
controllerSender.displayChip(state)
val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
+ assertThat(chipView.getAppIconView().contentDescription)
+ .isEqualTo(state.getAppName(context))
assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
@@ -225,8 +222,9 @@
controllerSender.displayChip(state)
val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
+ assertThat(chipView.getAppIconView().contentDescription)
+ .isEqualTo(state.getAppName(context))
assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
@@ -239,8 +237,9 @@
controllerSender.displayChip(state)
val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
+ assertThat(chipView.getAppIconView().contentDescription)
+ .isEqualTo(state.getAppName(context))
assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
@@ -253,8 +252,9 @@
controllerSender.displayChip(state)
val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
+ assertThat(chipView.getAppIconView().contentDescription)
+ .isEqualTo(state.getAppName(context))
assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
@@ -314,8 +314,9 @@
controllerSender.displayChip(state)
val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
+ assertThat(chipView.getAppIconView().contentDescription)
+ .isEqualTo(state.getAppName(context))
assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
@@ -375,8 +376,9 @@
controllerSender.displayChip(state)
val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
+ assertThat(chipView.getAppIconView().contentDescription)
+ .isEqualTo(state.getAppName(context))
assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
@@ -449,39 +451,40 @@
/** Helper method providing default parameters to not clutter up the tests. */
private fun almostCloseToStartCast() =
- AlmostCloseToStartCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
+ AlmostCloseToStartCast(PACKAGE_NAME, DEVICE_NAME)
/** Helper method providing default parameters to not clutter up the tests. */
private fun almostCloseToEndCast() =
- AlmostCloseToEndCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
+ AlmostCloseToEndCast(PACKAGE_NAME, DEVICE_NAME)
/** Helper method providing default parameters to not clutter up the tests. */
private fun transferToReceiverTriggered() =
- TransferToReceiverTriggered(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
+ TransferToReceiverTriggered(PACKAGE_NAME, DEVICE_NAME)
/** Helper method providing default parameters to not clutter up the tests. */
private fun transferToThisDeviceTriggered() =
- TransferToThisDeviceTriggered(appIconDrawable, APP_ICON_CONTENT_DESC)
+ TransferToThisDeviceTriggered(PACKAGE_NAME)
/** Helper method providing default parameters to not clutter up the tests. */
private fun transferToReceiverSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
TransferToReceiverSucceeded(
- appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback
+ PACKAGE_NAME, DEVICE_NAME, undoCallback
)
/** Helper method providing default parameters to not clutter up the tests. */
private fun transferToThisDeviceSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
TransferToThisDeviceSucceeded(
- appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback
+ PACKAGE_NAME, DEVICE_NAME, undoCallback
)
/** Helper method providing default parameters to not clutter up the tests. */
- private fun transferFailed() = TransferFailed(appIconDrawable, APP_ICON_CONTENT_DESC)
+ private fun transferFailed() = TransferFailed(PACKAGE_NAME)
}
private const val DEVICE_NAME = "My Tablet"
-private const val APP_ICON_CONTENT_DESC = "Content description"
+private const val PACKAGE_NAME = "com.android.systemui"
private val routeInfo = MediaRoute2Info.Builder("id", "Test Name")
.addFeature("feature")
+ .setPackageName(PACKAGE_NAME)
.build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
deleted file mode 100644
index 84776c7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_MORE_SETTINGS;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-import android.testing.ViewUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class QSDetailTest extends SysuiTestCase {
-
- private MetricsLogger mMetricsLogger;
- private QSDetail mQsDetail;
- private QSPanelController mQsPanelController;
- private QuickStatusBarHeader mQuickHeader;
- private ActivityStarter mActivityStarter;
- private DetailAdapter mMockDetailAdapter;
- private TestableLooper mTestableLooper;
- private UiEventLoggerFake mUiEventLogger;
- private FrameLayout mParent;
-
- @Before
- public void setup() throws Exception {
- mTestableLooper = TestableLooper.get(this);
- mUiEventLogger = QSEvents.INSTANCE.setLoggerForTesting();
-
- mParent = new FrameLayout(mContext);
- mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
- mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class);
- LayoutInflater.from(mContext).inflate(R.layout.qs_detail, mParent);
- mQsDetail = (QSDetail) mParent.getChildAt(0);
-
- mQsPanelController = mock(QSPanelController.class);
- mQuickHeader = mock(QuickStatusBarHeader.class);
- mQsDetail.setQsPanel(mQsPanelController, mQuickHeader, mock(QSFooter.class),
- mock(FalsingManager.class));
- mQsDetail.mClipper = mock(QSDetailClipper.class);
-
- mMockDetailAdapter = mock(DetailAdapter.class);
- when(mMockDetailAdapter.createDetailView(any(), any(), any()))
- .thenReturn(new View(mContext));
-
- // Only detail in use is the user detail
- when(mMockDetailAdapter.openDetailEvent())
- .thenReturn(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN);
- when(mMockDetailAdapter.closeDetailEvent())
- .thenReturn(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE);
- when(mMockDetailAdapter.moreSettingsEvent())
- .thenReturn(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS);
- ViewUtils.attachView(mParent);
- }
-
- @After
- public void tearDown() {
- QSEvents.INSTANCE.resetLogger();
- mTestableLooper.processAllMessages();
- ViewUtils.detachView(mParent);
- }
-
- @Test
- public void testShowDetail_Metrics() {
- mTestableLooper.processAllMessages();
-
- mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
- verify(mMetricsLogger).visible(eq(mMockDetailAdapter.getMetricsCategory()));
- assertEquals(1, mUiEventLogger.numLogs());
- assertEquals(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN.getId(), mUiEventLogger.eventId(0));
- mUiEventLogger.getLogs().clear();
-
- mQsDetail.handleShowingDetail(null, 0, 0, false);
- verify(mMetricsLogger).hidden(eq(mMockDetailAdapter.getMetricsCategory()));
-
- assertEquals(1, mUiEventLogger.numLogs());
- assertEquals(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE.getId(), mUiEventLogger.eventId(0));
- }
-
- @Test
- public void testShowDetail_ShouldAnimate() {
- mTestableLooper.processAllMessages();
-
- when(mMockDetailAdapter.shouldAnimate()).thenReturn(true);
- mQsDetail.setFullyExpanded(true);
-
- mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
- verify(mQsDetail.mClipper).updateCircularClip(eq(true) /* animate */, anyInt(), anyInt(),
- eq(true) /* in */, any());
- clearInvocations(mQsDetail.mClipper);
-
- mQsDetail.handleShowingDetail(null, 0, 0, false);
- verify(mQsDetail.mClipper).updateCircularClip(eq(true) /* animate */, anyInt(), anyInt(),
- eq(false) /* in */, any());
- }
-
- @Test
- public void testShowDetail_ShouldNotAnimate() {
- mTestableLooper.processAllMessages();
-
- when(mMockDetailAdapter.shouldAnimate()).thenReturn(false);
- mQsDetail.setFullyExpanded(true);
-
- mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
- verify(mQsDetail.mClipper).updateCircularClip(eq(false) /* animate */, anyInt(), anyInt(),
- eq(true) /* in */, any());
- clearInvocations(mQsDetail.mClipper);
-
- // Detail adapters should always animate on close. shouldAnimate() should only affect the
- // open transition
- mQsDetail.handleShowingDetail(null, 0, 0, false);
- verify(mQsDetail.mClipper).updateCircularClip(eq(true) /* animate */, anyInt(), anyInt(),
- eq(false) /* in */, any());
- }
-
- @Test
- public void testDoneButton_CloseDetailPanel() {
- mTestableLooper.processAllMessages();
-
- when(mMockDetailAdapter.onDoneButtonClicked()).thenReturn(false);
-
- mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
- mQsDetail.requireViewById(android.R.id.button1).performClick();
- verify(mQsPanelController).closeDetail();
- }
-
- @Test
- public void testDoneButton_KeepDetailPanelOpen() {
- mTestableLooper.processAllMessages();
-
- when(mMockDetailAdapter.onDoneButtonClicked()).thenReturn(true);
-
- mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
- mQsDetail.requireViewById(android.R.id.button1).performClick();
- verify(mQsPanelController, never()).closeDetail();
- }
-
- @Test
- public void testMoreSettingsButton() {
- mTestableLooper.processAllMessages();
-
- mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
- mUiEventLogger.getLogs().clear();
- mQsDetail.requireViewById(android.R.id.button2).performClick();
-
- int metricsCategory = mMockDetailAdapter.getMetricsCategory();
- verify(mMetricsLogger).action(eq(ACTION_QS_MORE_SETTINGS), eq(metricsCategory));
- assertEquals(1, mUiEventLogger.numLogs());
- assertEquals(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS.getId(), mUiEventLogger.eventId(0));
-
- verify(mActivityStarter).postStartActivityDismissingKeyguard(any(), anyInt());
- }
-
- @Test
- public void testNullAdapterClick() {
- DetailAdapter mock = mock(DetailAdapter.class);
- when(mock.moreSettingsEvent()).thenReturn(DetailAdapter.INVALID);
- mQsDetail.setupDetailFooter(mock);
- mQsDetail.requireViewById(android.R.id.button2).performClick();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 3266d6a..4ab3926 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -146,7 +146,6 @@
mock(QSLogger.class), mock(UiEventLogger.class), mock(UserTracker.class),
mock(SecureSettings.class), mock(CustomTileStatePersister.class),
mTileServiceRequestControllerBuilder, mock(TileLifecycleManager.Factory.class));
- qs.setHost(host);
qs.setListening(true);
processAllMessages();
@@ -186,7 +185,6 @@
mock(QSTileHost.class),
mock(StatusBarStateController.class),
commandQueue,
- new QSDetailDisplayer(),
mQSMediaHost,
mQQSMediaHost,
mBypassController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
deleted file mode 100644
index b2ca62f..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.MediaHost;
-import com.android.systemui.plugins.qs.QSTileView;
-import com.android.systemui.qs.customize.QSCustomizerController;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.settings.brightness.BrightnessController;
-import com.android.systemui.settings.brightness.BrightnessSliderController;
-import com.android.systemui.settings.brightness.ToggleSlider;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.animation.DisappearParameters;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Collections;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class QSPanelControllerTest extends SysuiTestCase {
-
- @Mock
- private QSPanel mQSPanel;
- @Mock
- private QSTileHost mQSTileHost;
- @Mock
- private QSCustomizerController mQSCustomizerController;
- @Mock
- private QSTileRevealController.Factory mQSTileRevealControllerFactory;
- @Mock
- private QSTileRevealController mQSTileRevealController;
- @Mock
- private MediaHost mMediaHost;
- @Mock
- private MetricsLogger mMetricsLogger;
- private UiEventLogger mUiEventLogger = new UiEventLoggerFake();
- private DumpManager mDumpManager = new DumpManager();
- @Mock
- private TunerService mTunerService;
- @Mock
- private QSFgsManagerFooter mQSFgsManagerFooter;
- @Mock
- private QSSecurityFooter mQSSecurityFooter;
- @Mock
- private QSLogger mQSLogger;
- @Mock
- private BrightnessController.Factory mBrightnessControllerFactory;
- @Mock
- private BrightnessController mBrightnessController;
- @Mock
- private BrightnessSliderController.Factory mToggleSliderViewControllerFactory;
- @Mock
- private BrightnessSliderController mBrightnessSliderController;
- @Mock
- QSTileImpl mQSTile;
- @Mock
- QSTileView mQSTileView;
- @Mock
- PagedTileLayout mPagedTileLayout;
- @Mock
- CommandQueue mCommandQueue;
- FalsingManagerFake mFalsingManager = new FalsingManagerFake();
- @Mock
- Resources mResources;
- @Mock
- Configuration mConfiguration;
- @Mock
- FeatureFlags mFeatureFlags;
-
- private QSPanelController mController;
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- when(mQSPanel.isAttachedToWindow()).thenReturn(true);
- when(mQSPanel.getDumpableTag()).thenReturn("QSPanel");
- when(mQSPanel.getOrCreateTileLayout()).thenReturn(mPagedTileLayout);
- when(mQSPanel.getTileLayout()).thenReturn(mPagedTileLayout);
- when(mQSPanel.getResources()).thenReturn(mResources);
- when(mResources.getConfiguration()).thenReturn(mConfiguration);
- when(mQSTileHost.getTiles()).thenReturn(Collections.singleton(mQSTile));
- when(mQSTileHost.createTileView(any(), eq(mQSTile), anyBoolean())).thenReturn(mQSTileView);
- when(mToggleSliderViewControllerFactory.create(any(), any()))
- .thenReturn(mBrightnessSliderController);
- when(mBrightnessControllerFactory.create(any(ToggleSlider.class)))
- .thenReturn(mBrightnessController);
- when(mQSTileRevealControllerFactory.create(any(), any()))
- .thenReturn(mQSTileRevealController);
- when(mMediaHost.getDisappearParameters()).thenReturn(new DisappearParameters());
-
- mController = new QSPanelController(mQSPanel, mQSFgsManagerFooter, mQSSecurityFooter,
- mTunerService, mQSTileHost, mQSCustomizerController, true, mMediaHost,
- mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger,
- mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory,
- mFalsingManager, mCommandQueue, mFeatureFlags
- );
-
- mController.init();
- }
-
- @Test
- public void testOpenDetailsWithNonExistingTile_NoException() {
- mController.openDetails("none");
-
- verify(mQSPanel, never()).openDetails(any());
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
index 4ae19332..5213a30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -36,10 +36,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
@@ -64,9 +61,6 @@
private lateinit var mParentView: ViewGroup
@Mock
- private lateinit var mCallback: QSDetail.Callback
-
- @Mock
private lateinit var mQSTileView: QSTileView
private lateinit var mFooter: View
@@ -97,29 +91,10 @@
whenever(mHost.tiles).thenReturn(emptyList())
whenever(mHost.createTileView(any(), any(), anyBoolean())).thenReturn(mQSTileView)
mQsPanel.addTile(mDndTileRecord)
- mQsPanel.setCallback(mCallback)
}
}
@Test
- fun testOpenDetailsWithExistingTile_NoException() {
- mTestableLooper.runWithLooper {
- mQsPanel.openDetails(dndTile)
- }
-
- verify(mCallback).onShowingDetail(any(), anyInt(), anyInt())
- }
-
- @Test
- fun testOpenDetailsWithNullParameter_NoException() {
- mTestableLooper.runWithLooper {
- mQsPanel.openDetails(null)
- }
-
- verify(mCallback, never()).onShowingDetail(any(), anyInt(), anyInt())
- }
-
- @Test
fun testSecurityFooter_appearsOnBottomOnSplitShade() {
mQsPanel.onConfigurationChanged(getNewOrientationConfig(ORIENTATION_LANDSCAPE))
mQsPanel.switchSecurityFooter(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index 8b7346d..30b464b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -53,7 +53,6 @@
import com.android.internal.logging.InstanceId;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSIconView;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSTileHost;
@@ -420,11 +419,5 @@
@Override
public void destroy() {}
-
-
- @Override
- public DetailAdapter getDetailAdapter() {
- return null;
- }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
index a1c60a6..bdfbca4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
@@ -20,6 +20,7 @@
import android.content.ComponentName
import android.content.DialogInterface
import android.graphics.drawable.Icon
+import android.os.RemoteException
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
@@ -275,11 +276,89 @@
assertThat(c.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED)
}
+ @Test
+ fun interfaceThrowsRemoteException_doesntCrash() {
+ val cancelListenerCaptor =
+ ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
+ val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
+ verify(commandQueue, atLeastOnce()).addCallback(capture(captor))
+
+ val callback = object : IAddTileResultCallback.Stub() {
+ override fun onTileRequest(p0: Int) {
+ throw RemoteException()
+ }
+ }
+ captor.value.requestAddTile(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+ verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
+
+ cancelListenerCaptor.value.onCancel(tileRequestDialog)
+ }
+
+ @Test
+ fun testDismissDialogResponse() {
+ val dismissListenerCaptor =
+ ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java)
+
+ val callback = Callback()
+ controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+ verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
+
+ dismissListenerCaptor.value.onDismiss(tileRequestDialog)
+ assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DISMISSED)
+ }
+
+ @Test
+ fun addTileAndThenDismissSendsOnlyAddTile() {
+ // After clicking, the dialog is dismissed. This tests that only one response
+ // is sent (the first one)
+ val dismissListenerCaptor =
+ ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java)
+ val clickListenerCaptor =
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
+
+ val callback = Callback()
+ controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+ verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
+ verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
+
+ clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE)
+ dismissListenerCaptor.value.onDismiss(tileRequestDialog)
+
+ assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.ADD_TILE)
+ assertThat(callback.timesCalled).isEqualTo(1)
+ }
+
+ @Test
+ fun cancelAndThenDismissSendsOnlyOnce() {
+ // After cancelling, the dialog is dismissed. This tests that only one response
+ // is sent.
+ val dismissListenerCaptor =
+ ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java)
+ val cancelListenerCaptor =
+ ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
+
+ val callback = Callback()
+ controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+ verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
+ verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
+
+ cancelListenerCaptor.value.onCancel(tileRequestDialog)
+ dismissListenerCaptor.value.onDismiss(tileRequestDialog)
+
+ assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DISMISSED)
+ assertThat(callback.timesCalled).isEqualTo(1)
+ }
+
private class Callback : IAddTileResultCallback.Stub(), Consumer<Int> {
var lastAccepted: Int? = null
private set
+
+ var timesCalled = 0
+ private set
+
override fun accept(t: Int) {
lastAccepted = t
+ timesCalled++
}
override fun onTileRequest(r: Int) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
index 88b133e..0f2c2647 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
@@ -48,7 +48,6 @@
import com.android.systemui.qs.tiles.RotationLockTile
import com.android.systemui.qs.tiles.ScreenRecordTile
import com.android.systemui.qs.tiles.UiModeNightTile
-import com.android.systemui.qs.tiles.UserTile
import com.android.systemui.qs.tiles.WifiTile
import com.android.systemui.qs.tiles.WorkModeTile
import com.android.systemui.util.leak.GarbageMonitor
@@ -76,7 +75,6 @@
"location" to LocationTile::class.java,
"cast" to CastTile::class.java,
"hotspot" to HotspotTile::class.java,
- "user" to UserTile::class.java,
"battery" to BatterySaverTile::class.java,
"saver" to DataSaverTile::class.java,
"night" to NightDisplayTile::class.java,
@@ -115,7 +113,6 @@
@Mock private lateinit var locationTile: LocationTile
@Mock private lateinit var castTile: CastTile
@Mock private lateinit var hotspotTile: HotspotTile
- @Mock private lateinit var userTile: UserTile
@Mock private lateinit var batterySaverTile: BatterySaverTile
@Mock private lateinit var dataSaverTile: DataSaverTile
@Mock private lateinit var nightDisplayTile: NightDisplayTile
@@ -159,7 +156,6 @@
{ locationTile },
{ castTile },
{ hotspotTile },
- { userTile },
{ batterySaverTile },
{ dataSaverTile },
{ nightDisplayTile },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
index 3a3d154..9b0142d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -147,5 +147,6 @@
current,
false /* isAddUser */,
false /* isRestricted */,
- true /* isSwitchToEnabled */)
+ true /* isSwitchToEnabled */,
+ false /* isAddSupervisedUser */)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
index b7fdc1a..8695b29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -22,11 +22,13 @@
import android.testing.AndroidTestingRunner
import android.view.View
import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PseudoGridView
+import com.android.systemui.qs.QSUserSwitcherEvent
import com.android.systemui.qs.tiles.UserDetailView
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.mockito.any
@@ -62,6 +64,8 @@
private lateinit var launchView: View
@Mock
private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+ @Mock
+ private lateinit var uiEventLogger: UiEventLogger
@Captor
private lateinit var clickCaptor: ArgumentCaptor<DialogInterface.OnClickListener>
@@ -79,6 +83,7 @@
activityStarter,
falsingManager,
dialogLaunchAnimator,
+ uiEventLogger,
{ dialog }
)
}
@@ -87,6 +92,7 @@
fun showDialog_callsDialogShow() {
controller.showDialog(launchView)
verify(dialogLaunchAnimator).showFromView(dialog, launchView)
+ verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN)
}
@Test
@@ -108,10 +114,14 @@
}
@Test
- fun doneButtonSetWithNullHandler() {
+ fun doneButtonLogsCorrectly() {
controller.showDialog(launchView)
- verify(dialog).setPositiveButton(anyInt(), eq(null))
+ verify(dialog).setPositiveButton(anyInt(), capture(clickCaptor))
+
+ clickCaptor.value.onClick(dialog, DialogInterface.BUTTON_NEUTRAL)
+
+ verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE)
}
@Test
@@ -129,6 +139,7 @@
argThat(IntentMatcher(Settings.ACTION_USER_SETTINGS)),
eq(0)
)
+ verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/VersionInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/VersionInfoTest.java
index 218e7db..711187b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/VersionInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/VersionInfoTest.java
@@ -22,7 +22,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.OverlayPlugin;
import com.android.systemui.plugins.annotations.Requires;
-import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.qs.QS.HeightListener;
import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException;
@@ -99,7 +98,6 @@
@Requires(target = QS.class, version = QS.VERSION)
@Requires(target = HeightListener.class, version = HeightListener.VERSION)
- @Requires(target = DetailAdapter.class, version = DetailAdapter.VERSION)
public static class QSImpl {
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 5924329..466d954 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -244,6 +244,9 @@
verify(mKeyguardStateController).addCallback(
mKeyguardStateControllerCallbackCaptor.capture());
mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
+
+ mExecutor.runAllReady();
+ reset(mRotateTextViewController);
}
@Test
@@ -328,6 +331,7 @@
@Test
public void disclosure_unmanaged() {
createController();
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(false);
sendUpdateDisclosureBroadcast();
@@ -339,6 +343,7 @@
@Test
public void disclosure_deviceOwner_noOrganizationName() {
createController();
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null);
sendUpdateDisclosureBroadcast();
@@ -350,6 +355,7 @@
@Test
public void disclosure_orgOwnedDeviceWithManagedProfile_noOrganizationName() {
createController();
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true);
when(mUserManager.getProfiles(anyInt())).thenReturn(Collections.singletonList(
new UserInfo(10, /* name */ null, /* flags */ FLAG_MANAGED_PROFILE)));
@@ -363,6 +369,7 @@
@Test
public void disclosure_deviceOwner_withOrganizationName() {
createController();
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
sendUpdateDisclosureBroadcast();
@@ -374,6 +381,7 @@
@Test
public void disclosure_orgOwnedDeviceWithManagedProfile_withOrganizationName() {
createController();
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true);
when(mUserManager.getProfiles(anyInt())).thenReturn(Collections.singletonList(
new UserInfo(10, /* name */ null, FLAG_MANAGED_PROFILE)));
@@ -386,6 +394,7 @@
@Test
public void disclosure_updateOnTheFly() {
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
createController();
@@ -416,6 +425,7 @@
public void disclosure_deviceOwner_financedDeviceWithOrganizationName() {
createController();
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
when(mDevicePolicyManager.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
@@ -738,7 +748,8 @@
public void testEmptyOwnerInfoHidesIndicationArea() {
createController();
- // GIVEN the owner info is set to an empty string
+ // GIVEN the owner info is set to an empty string & keyguard is showing
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mLockPatternUtils.getDeviceOwnerInfo()).thenReturn("");
// WHEN asked to update the indication area
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index 8c5f04f..5e11858 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -37,6 +37,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -55,6 +56,7 @@
@Mock private NotificationHandler mNotificationHandler;
@Mock private NotificationManager mNotificationManager;
+ @Mock private PluginManager mPluginManager;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
@@ -70,7 +72,8 @@
mContext,
mNotificationManager,
mFakeSystemClock,
- mFakeExecutor);
+ mFakeExecutor,
+ mPluginManager);
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
new Notification(), UserHandle.CURRENT, null, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index fb232ba..ed144fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -126,10 +126,9 @@
.thenReturn(true);
when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
when(mKeyguardBypassController.canPlaySubtleWindowAnimations()).thenReturn(true);
- mContext.addMockSystemService(PowerManager.class, mPowerManager);
mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
res.addOverride(com.android.internal.R.integer.config_wakeUpDelayDoze, 0);
- mBiometricUnlockController = new BiometricUnlockController(mContext, mDozeScrimController,
+ mBiometricUnlockController = new BiometricUnlockController(mDozeScrimController,
mKeyguardViewMediator, mScrimController, mShadeController,
mNotificationShadeWindowController, mKeyguardStateController, mHandler,
mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index e9590b0..ed22cd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -40,6 +40,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Assert;
@@ -47,6 +48,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Optional;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -76,7 +79,6 @@
mDependency,
TestableLooper.get(this));
mFirst = testHelper.createRow();
- mDependency.injectTestDependency(DarkIconDispatcher.class, mDarkIconDispatcher);
mHeadsUpStatusBarView = new HeadsUpStatusBarView(mContext, mock(View.class),
mock(TextView.class));
mHeadsUpManager = mock(HeadsUpManagerPhone.class);
@@ -92,13 +94,14 @@
mStatusbarStateController,
mBypassController,
mWakeUpCoordinator,
+ mDarkIconDispatcher,
mKeyguardStateController,
mCommandQueue,
mStackScrollerController,
mPanelView,
mHeadsUpStatusBarView,
- new View(mContext),
- mOperatorNameView);
+ new Clock(mContext, null),
+ Optional.of(mOperatorNameView));
mHeadsUpAppearanceController.setAppearFraction(0.0f, 0.0f);
}
@@ -173,13 +176,14 @@
mStatusbarStateController,
mBypassController,
mWakeUpCoordinator,
+ mDarkIconDispatcher,
mKeyguardStateController,
mCommandQueue,
mStackScrollerController,
mPanelView,
mHeadsUpStatusBarView,
- new View(mContext),
- new View(mContext));
+ new Clock(mContext, null),
+ Optional.empty());
Assert.assertEquals(expandedHeight, newController.mExpandedHeight, 0.0f);
Assert.assertEquals(appearFraction, newController.mAppearFraction, 0.0f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index 7e33c01..cc4abfc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -20,6 +20,8 @@
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
+import static junit.framework.Assert.assertTrue;
+
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -41,6 +43,9 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.ArrayList;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -91,7 +96,9 @@
mLightBarController.onStatusBarAppearanceChanged(
appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT,
false /* navbarColorManagedByIme */);
- verify(mStatusBarIconController).setIconsDarkArea(eq(firstBounds));
+ ArgumentCaptor<ArrayList<Rect>> captor = ArgumentCaptor.forClass(ArrayList.class);
+ verify(mStatusBarIconController).setIconsDarkArea(captor.capture());
+ assertTrue(captor.getValue().contains(firstBounds));
verify(mLightBarTransitionsController).setIconsDark(eq(true), anyBoolean());
}
@@ -106,7 +113,29 @@
mLightBarController.onStatusBarAppearanceChanged(
appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT,
false /* navbarColorManagedByIme */);
- verify(mStatusBarIconController).setIconsDarkArea(eq(secondBounds));
+ ArgumentCaptor<ArrayList<Rect>> captor = ArgumentCaptor.forClass(ArrayList.class);
+ verify(mStatusBarIconController).setIconsDarkArea(captor.capture());
+ assertTrue(captor.getValue().contains(secondBounds));
+ verify(mLightBarTransitionsController).setIconsDark(eq(true), anyBoolean());
+ }
+
+ @Test
+ public void testOnStatusBarAppearanceChanged_multipleStacks_oneStackLightMultipleStackDark() {
+ final Rect firstBounds = new Rect(0, 0, 1, 1);
+ final Rect secondBounds = new Rect(1, 0, 2, 1);
+ final Rect thirdBounds = new Rect(2, 0, 3, 1);
+ final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{
+ new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, firstBounds),
+ new AppearanceRegion(0 /* appearance */, secondBounds),
+ new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, thirdBounds)
+ };
+ mLightBarController.onStatusBarAppearanceChanged(
+ appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT,
+ false /* navbarColorManagedByIme */);
+ ArgumentCaptor<ArrayList<Rect>> captor = ArgumentCaptor.forClass(ArrayList.class);
+ verify(mStatusBarIconController).setIconsDarkArea(captor.capture());
+ assertTrue(captor.getValue().contains(firstBounds));
+ assertTrue(captor.getValue().contains(thirdBounds));
verify(mLightBarTransitionsController).setIconsDark(eq(true), anyBoolean());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
index c13b335..7070bc1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
@@ -78,6 +78,7 @@
@Mock private NotificationEntryManager mNotificationEntryManager;
@Mock private RowContentBindStage mBindStage;
@Mock PeopleNotificationIdentifier mPeopleNotificationIdentifier;
+ @Mock StatusBarStateController mStatusBarStateController;
@Captor private ArgumentCaptor<NotificationEntryListener> mListenerCaptor;
private NotificationEntryListener mNotificationEntryListener;
private final HashMap<String, NotificationEntry> mPendingEntries = new HashMap<>();
@@ -94,7 +95,7 @@
.thenReturn(mPendingEntries.values());
mGroupManager = new NotificationGroupManagerLegacy(
- mock(StatusBarStateController.class),
+ mStatusBarStateController,
() -> mPeopleNotificationIdentifier,
Optional.of(mock(Bubbles.class)),
mock(DumpManager.class));
@@ -103,7 +104,8 @@
when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
- mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper(mBindStage);
+ mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper(
+ mBindStage, mStatusBarStateController, mGroupManager);
mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager);
mGroupAlertTransferHelper.bind(mNotificationEntryManager, mGroupManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 7347565..f6eff82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -106,10 +106,10 @@
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
+import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
-import com.android.systemui.qs.QSDetailDisplayer;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardAffordanceView;
@@ -272,8 +272,6 @@
@Mock
private IdleHostViewController mIdleHostViewController;
@Mock
- private QSDetailDisplayer mQSDetailDisplayer;
- @Mock
private KeyguardStatusViewComponent mKeyguardStatusViewComponent;
@Mock
private KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
@@ -366,6 +364,8 @@
private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock
private NotificationShadeWindowController mNotificationShadeWindowController;
+ @Mock
+ private SysUiState mSysUiState;
private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
private SysuiStatusBarStateController mStatusBarStateController;
private NotificationPanelViewController mNotificationPanelViewController;
@@ -525,7 +525,6 @@
mCommunalViewComponentFactory,
mIdleViewComponentFactory,
mLockscreenShadeTransitionController,
- mQSDetailDisplayer,
mGroupManager,
mNotificationAreaController,
mAuthController,
@@ -555,6 +554,7 @@
mControlsComponent,
mInteractionJankMonitor,
mQsFrameTranslateController,
+ mSysUiState,
mKeyguardUnlockAnimationController);
mNotificationPanelViewController.initDependencies(
mStatusBar,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
index a57f6a1..4a579cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
@@ -27,26 +27,22 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.CommunalStateController
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.ScreenLifecycle
import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.tiles.UserDetailView
import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.LockscreenGestureLogger
-import com.android.systemui.statusbar.phone.NotificationPanelViewController
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.`when`
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import javax.inject.Provider
@SmallTest
@TestableLooper.RunWithLooper
@@ -77,23 +73,14 @@
private lateinit var dozeParameters: DozeParameters
@Mock
- private lateinit var userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>
-
- @Mock
private lateinit var screenOffAnimationController: ScreenOffAnimationController
@Mock
- private lateinit var featureFlags: FeatureFlags
-
- @Mock
private lateinit var userSwitchDialogController: UserSwitchDialogController
@Mock
private lateinit var uiEventLogger: UiEventLogger
- @Mock
- private lateinit var notificationPanelViewController: NotificationPanelViewController
-
private lateinit var view: FrameLayout
private lateinit var testableLooper: TestableLooper
private lateinit var keyguardQsUserSwitchController: KeyguardQsUserSwitchController
@@ -118,16 +105,12 @@
configurationController,
statusBarStateController,
dozeParameters,
- userDetailViewAdapterProvider,
screenOffAnimationController,
- featureFlags,
userSwitchDialogController,
uiEventLogger)
ViewUtils.attachView(view)
testableLooper.processAllMessages()
- keyguardQsUserSwitchController
- .setNotificationPanelViewController(notificationPanelViewController)
`when`(userSwitcherController.keyguardStateController).thenReturn(keyguardStateController)
`when`(userSwitcherController.keyguardStateController.isShowing).thenReturn(true)
keyguardQsUserSwitchController.init()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
index e479882..0dd6cbb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
@@ -193,5 +193,6 @@
isCurrentUser,
false /* isAddUser */,
false /* isRestricted */,
- true /* isSwitchToEnabled */)
+ true /* isSwitchToEnabled */,
+ false /* isAddSupervisedUser */)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index 9a7e702..c344aea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -54,8 +54,9 @@
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.time.FakeSystemClock
import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNotNull
import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -85,7 +86,6 @@
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock private lateinit var activityTaskManager: IActivityTaskManager
- @Mock private lateinit var userDetailAdapter: UserSwitcherController.UserDetailAdapter
@Mock private lateinit var telephonyListenerManager: TelephonyListenerManager
@Mock private lateinit var secureSettings: SecureSettings
@Mock private lateinit var falsingManager: FalsingManager
@@ -130,6 +130,21 @@
.thenReturn(true)
`when`(notificationShadeWindowView.context).thenReturn(context)
+ // Since userSwitcherController involves InteractionJankMonitor.
+ // Let's fulfill the dependencies.
+ val mockedContext = mock(Context::class.java)
+ doReturn(mockedContext).`when`(notificationShadeWindowView).context
+ doReturn(true).`when`(notificationShadeWindowView).isAttachedToWindow
+ doNothing().`when`(threadedRenderer).addObserver(any())
+ doNothing().`when`(threadedRenderer).removeObserver(any())
+ doReturn(threadedRenderer).`when`(notificationShadeWindowView).threadedRenderer
+
+ picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user))
+
+ setupController()
+ }
+
+ private fun setupController() {
userSwitcherController = UserSwitcherController(
context,
activityManager,
@@ -145,7 +160,6 @@
falsingManager,
telephonyListenerManager,
activityTaskManager,
- userDetailAdapter,
secureSettings,
uiBgExecutor,
interactionJankMonitor,
@@ -153,18 +167,6 @@
dumpManager,
dialogLaunchAnimator)
userSwitcherController.mPauseRefreshUsers = true
-
- // Since userSwitcherController involves InteractionJankMonitor.
- // Let's fulfill the dependencies.
- val mockedContext = mock(Context::class.java)
- doReturn(mockedContext).`when`(notificationShadeWindowView).context
- doReturn(true).`when`(notificationShadeWindowView).isAttachedToWindow
- doNothing().`when`(threadedRenderer).addObserver(any())
- doNothing().`when`(threadedRenderer).removeObserver(any())
- doReturn(threadedRenderer).`when`(notificationShadeWindowView).threadedRenderer
- userSwitcherController.init(notificationShadeWindowView)
-
- picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user))
userSwitcherController.init(notificationShadeWindowView)
}
@@ -177,7 +179,8 @@
false /* current */,
false /* isAddUser */,
false /* isRestricted */,
- true /* isSwitchToEnabled */)
+ true /* isSwitchToEnabled */,
+ false /* isAddSupervisedUser */)
`when`(userTracker.userId).thenReturn(ownerId)
`when`(userTracker.userInfo).thenReturn(ownerInfo)
@@ -196,7 +199,8 @@
false /* current */,
false /* isAddUser */,
false /* isRestricted */,
- true /* isSwitchToEnabled */)
+ true /* isSwitchToEnabled */,
+ false /* isAddSupervisedUser */)
`when`(userTracker.userId).thenReturn(ownerId)
`when`(userTracker.userInfo).thenReturn(ownerInfo)
@@ -220,7 +224,8 @@
false /* current */,
false /* isAddUser */,
false /* isRestricted */,
- true /* isSwitchToEnabled */)
+ true /* isSwitchToEnabled */,
+ false /* isAddSupervisedUser */)
`when`(userTracker.userId).thenReturn(ownerId)
`when`(userTracker.userInfo).thenReturn(ownerInfo)
@@ -240,7 +245,8 @@
true /* current */,
false /* isAddUser */,
false /* isRestricted */,
- true /* isSwitchToEnabled */)
+ true /* isSwitchToEnabled */,
+ false /* isAddSupervisedUser */)
`when`(userTracker.userId).thenReturn(guestInfo.id)
`when`(userTracker.userInfo).thenReturn(guestInfo)
@@ -262,7 +268,8 @@
true /* current */,
false /* isAddUser */,
false /* isRestricted */,
- true /* isSwitchToEnabled */)
+ true /* isSwitchToEnabled */,
+ false /* isAddSupervisedUser */)
`when`(userTracker.userId).thenReturn(guestInfo.id)
`when`(userTracker.userInfo).thenReturn(guestInfo)
@@ -283,7 +290,8 @@
true /* current */,
false /* isAddUser */,
false /* isRestricted */,
- true /* isSwitchToEnabled */)
+ true /* isSwitchToEnabled */,
+ false /* isAddSupervisedUser */)
`when`(userTracker.userId).thenReturn(guestInfo.id)
`when`(userTracker.userInfo).thenReturn(guestInfo)
@@ -302,7 +310,8 @@
true /* current */,
false /* isAddUser */,
false /* isRestricted */,
- true /* isSwitchToEnabled */)
+ true /* isSwitchToEnabled */,
+ false /* isAddSupervisedUser */)
`when`(userTracker.userId).thenReturn(guestId)
`when`(userTracker.userInfo).thenReturn(guestInfo)
@@ -323,7 +332,8 @@
false /* current */,
false /* isAddUser */,
false /* isRestricted */,
- true /* isSwitchToEnabled */)
+ true /* isSwitchToEnabled */,
+ false /* isAddSupervisedUser */)
`when`(userTracker.userId).thenReturn(guestId)
`when`(userTracker.userInfo).thenReturn(guestInfo)
@@ -357,7 +367,8 @@
false /* current */,
false /* isAddUser */,
false /* isRestricted */,
- true /* isSwitchToEnabled */)
+ true /* isSwitchToEnabled */,
+ false /* isAddSupervisedUser */)
`when`(userTracker.userId).thenReturn(guestId)
`when`(userTracker.userInfo).thenReturn(guestInfo)
@@ -389,7 +400,7 @@
userSwitcherController.users.add(UserSwitcherController.UserRecord(
UserInfo(id, name, 0),
null, false, isCurrent, false,
- false, false
+ false, false, false
))
}
val bgUserName = "background_user"
@@ -412,4 +423,42 @@
`when`(userTracker.userId).thenReturn(1)
assertEquals(false, userSwitcherController.isSystemUser)
}
+
+ @Test
+ fun testCanCreateSupervisedUserWithConfiguredPackage() {
+ // GIVEN the supervised user creation package is configured
+ `when`(context.getString(
+ com.android.internal.R.string.config_supervisedUserCreationPackage))
+ .thenReturn("some_pkg")
+
+ // AND the current user is allowed to create new users
+ `when`(userTracker.userId).thenReturn(ownerId)
+ `when`(userTracker.userInfo).thenReturn(ownerInfo)
+
+ // WHEN the controller is started with the above config
+ setupController()
+ testableLooper.processAllMessages()
+
+ // THEN a supervised user can be constructed
+ assertTrue(userSwitcherController.canCreateSupervisedUser())
+ }
+
+ @Test
+ fun testCannotCreateSupervisedUserWithConfiguredPackage() {
+ // GIVEN the supervised user creation package is NOT configured
+ `when`(context.getString(
+ com.android.internal.R.string.config_supervisedUserCreationPackage))
+ .thenReturn(null)
+
+ // AND the current user is allowed to create new users
+ `when`(userTracker.userId).thenReturn(ownerId)
+ `when`(userTracker.userInfo).thenReturn(ownerInfo)
+
+ // WHEN the controller is started with the above config
+ setupController()
+ testableLooper.processAllMessages()
+
+ // THEN a supervised user can NOT be constructed
+ assertFalse(userSwitcherController.canCreateSupervisedUser())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
index d645449..dff77f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.util.condition;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -24,16 +25,21 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.Arrays;
@@ -46,6 +52,7 @@
private FakeCondition mCondition2;
private FakeCondition mCondition3;
private HashSet<Condition> mConditions;
+ private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
private Monitor mConditionMonitor;
@@ -58,7 +65,83 @@
mCondition3 = spy(new FakeCondition());
mConditions = new HashSet<>(Arrays.asList(mCondition1, mCondition2, mCondition3));
- mConditionMonitor = new Monitor(mConditions, null /*callbacks*/);
+ mConditionMonitor = new Monitor(mExecutor, mConditions, null /*callbacks*/);
+ }
+
+ @Test
+ public void testOverridingCondition() {
+ final Condition overridingCondition = Mockito.mock(Condition.class);
+ final Condition regularCondition = Mockito.mock(Condition.class);
+ final Monitor.Callback callback = Mockito.mock(Monitor.Callback.class);
+
+ final Monitor monitor = new Monitor(
+ mExecutor,
+ new HashSet<>(Arrays.asList(overridingCondition, regularCondition)),
+ new HashSet<>(Arrays.asList(callback)));
+
+ when(overridingCondition.isOverridingCondition()).thenReturn(true);
+ when(overridingCondition.isConditionMet()).thenReturn(true);
+ when(regularCondition.isConditionMet()).thenReturn(false);
+
+ final ArgumentCaptor<Condition.Callback> mCallbackCaptor =
+ ArgumentCaptor.forClass(Condition.Callback.class);
+
+ verify(overridingCondition).addCallback(mCallbackCaptor.capture());
+
+ mCallbackCaptor.getValue().onConditionChanged(overridingCondition);
+ mExecutor.runAllReady();
+
+ verify(callback).onConditionsChanged(eq(true));
+ Mockito.clearInvocations(callback);
+
+ when(regularCondition.isConditionMet()).thenReturn(true);
+ when(overridingCondition.isConditionMet()).thenReturn(false);
+
+ mCallbackCaptor.getValue().onConditionChanged(overridingCondition);
+ mExecutor.runAllReady();
+
+ verify(callback).onConditionsChanged(eq(false));
+
+ clearInvocations(callback);
+ monitor.removeCondition(overridingCondition);
+ mExecutor.runAllReady();
+
+ verify(callback).onConditionsChanged(eq(true));
+ }
+
+ /**
+ * Ensures that when multiple overriding conditions are present, it is the aggregate of those
+ * conditions that are considered.
+ */
+ @Test
+ public void testMultipleOverridingConditions() {
+ final Condition overridingCondition = Mockito.mock(Condition.class);
+ final Condition overridingCondition2 = Mockito.mock(Condition.class);
+ final Condition regularCondition = Mockito.mock(Condition.class);
+ final Monitor.Callback callback = Mockito.mock(Monitor.Callback.class);
+
+ final Monitor monitor = new Monitor(
+ mExecutor,
+ new HashSet<>(Arrays.asList(overridingCondition, overridingCondition2,
+ regularCondition)),
+ new HashSet<>(Arrays.asList(callback)));
+
+ when(overridingCondition.isOverridingCondition()).thenReturn(true);
+ when(overridingCondition.isConditionMet()).thenReturn(true);
+ when(overridingCondition2.isOverridingCondition()).thenReturn(true);
+ when(overridingCondition.isConditionMet()).thenReturn(false);
+ when(regularCondition.isConditionMet()).thenReturn(true);
+
+ final ArgumentCaptor<Condition.Callback> mCallbackCaptor =
+ ArgumentCaptor.forClass(Condition.Callback.class);
+
+ verify(overridingCondition).addCallback(mCallbackCaptor.capture());
+
+ mCallbackCaptor.getValue().onConditionChanged(overridingCondition);
+ mExecutor.runAllReady();
+
+ verify(callback).onConditionsChanged(eq(false));
+ Mockito.clearInvocations(callback);
}
@Test
@@ -66,11 +149,13 @@
final Monitor.Callback callback1 =
mock(Monitor.Callback.class);
mConditionMonitor.addCallback(callback1);
+ mExecutor.runAllReady();
mConditions.forEach(condition -> verify(condition).addCallback(any()));
final Monitor.Callback callback2 =
mock(Monitor.Callback.class);
mConditionMonitor.addCallback(callback2);
+ mExecutor.runAllReady();
mConditions.forEach(condition -> verify(condition, times(1)).addCallback(any()));
}
@@ -79,6 +164,7 @@
final Monitor.Callback callback =
mock(Monitor.Callback.class);
mConditionMonitor.addCallback(callback);
+ mExecutor.runAllReady();
verify(callback).onConditionsChanged(false);
}
@@ -86,38 +172,53 @@
public void addCallback_addSecondCallback_reportWithExistingValue() {
final Monitor.Callback callback1 =
mock(Monitor.Callback.class);
- mConditionMonitor.addCallback(callback1);
-
- mConditionMonitor.overrideAllConditionsMet(true);
+ final Condition condition = mock(Condition.class);
+ when(condition.isConditionMet()).thenReturn(true);
+ final Monitor monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(condition)),
+ new HashSet<>(Arrays.asList(callback1)));
final Monitor.Callback callback2 =
mock(Monitor.Callback.class);
- mConditionMonitor.addCallback(callback2);
- verify(callback2).onConditionsChanged(true);
+ monitor.addCallback(callback2);
+ mExecutor.runAllReady();
+ verify(callback2).onConditionsChanged(eq(true));
}
@Test
public void addCallback_noConditions_reportAllConditionsMet() {
- final Monitor monitor = new Monitor(new HashSet<>(), null /*callbacks*/);
+ final Monitor monitor = new Monitor(mExecutor, new HashSet<>(), null /*callbacks*/);
final Monitor.Callback callback = mock(Monitor.Callback.class);
monitor.addCallback(callback);
-
+ mExecutor.runAllReady();
verify(callback).onConditionsChanged(true);
}
@Test
public void removeCallback_shouldNoLongerReceiveUpdate() {
+ final Condition condition = mock(Condition.class);
+ final Monitor monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(condition)),
+ null);
final Monitor.Callback callback =
mock(Monitor.Callback.class);
- mConditionMonitor.addCallback(callback);
+ monitor.addCallback(callback);
+ monitor.removeCallback(callback);
+ mExecutor.runAllReady();
clearInvocations(callback);
- mConditionMonitor.removeCallback(callback);
- mConditionMonitor.overrideAllConditionsMet(true);
+ final ArgumentCaptor<Condition.Callback> conditionCallbackCaptor =
+ ArgumentCaptor.forClass(Condition.Callback.class);
+ verify(condition).addCallback(conditionCallbackCaptor.capture());
+ final Condition.Callback conditionCallback = conditionCallbackCaptor.getValue();
+
+ when(condition.isConditionMet()).thenReturn(true);
+ conditionCallback.onConditionChanged(condition);
+ mExecutor.runAllReady();
verify(callback, never()).onConditionsChanged(true);
- mConditionMonitor.overrideAllConditionsMet(false);
+ when(condition.isConditionMet()).thenReturn(false);
+ conditionCallback.onConditionChanged(condition);
+ mExecutor.runAllReady();
verify(callback, never()).onConditionsChanged(false);
}
@@ -131,9 +232,11 @@
mConditionMonitor.addCallback(callback2);
mConditionMonitor.removeCallback(callback1);
+ mExecutor.runAllReady();
mConditions.forEach(condition -> verify(condition, never()).removeCallback(any()));
mConditionMonitor.removeCallback(callback2);
+ mExecutor.runAllReady();
mConditions.forEach(condition -> verify(condition).removeCallback(any()));
}
@@ -147,6 +250,7 @@
mCondition1.fakeUpdateCondition(true);
mCondition2.fakeUpdateCondition(true);
mCondition3.fakeUpdateCondition(true);
+ mExecutor.runAllReady();
verify(callback).onConditionsChanged(true);
}
@@ -163,6 +267,7 @@
clearInvocations(callback);
mCondition1.fakeUpdateCondition(false);
+ mExecutor.runAllReady();
verify(callback).onConditionsChanged(false);
}
@@ -171,16 +276,20 @@
final Monitor.Callback callback =
mock(Monitor.Callback.class);
mConditionMonitor.addCallback(callback);
+ mExecutor.runAllReady();
verify(callback).onConditionsChanged(false);
clearInvocations(callback);
mCondition1.fakeUpdateCondition(true);
+ mExecutor.runAllReady();
verify(callback, never()).onConditionsChanged(anyBoolean());
mCondition2.fakeUpdateCondition(true);
+ mExecutor.runAllReady();
verify(callback, never()).onConditionsChanged(anyBoolean());
mCondition3.fakeUpdateCondition(true);
+ mExecutor.runAllReady();
verify(callback).onConditionsChanged(true);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
index 7fc6b51..9e0f863 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
@@ -16,7 +16,8 @@
package com.android.systemui.util.condition;
-import static org.mockito.ArgumentMatchers.anyBoolean;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -73,7 +74,8 @@
final Condition.Callback callback2 = mock(Condition.Callback.class);
mCondition.addCallback(callback2);
- verify(callback2).onConditionChanged(mCondition, true);
+ verify(callback2).onConditionChanged(mCondition);
+ assertThat(mCondition.isConditionMet()).isTrue();
}
@Test
@@ -94,7 +96,8 @@
mCondition.addCallback(callback);
mCondition.fakeUpdateCondition(true);
- verify(callback).onConditionChanged(eq(mCondition), eq(true));
+ verify(callback).onConditionChanged(eq(mCondition));
+ assertThat(mCondition.isConditionMet()).isTrue();
}
@Test
@@ -105,7 +108,8 @@
mCondition.addCallback(callback);
mCondition.fakeUpdateCondition(false);
- verify(callback).onConditionChanged(eq(mCondition), eq(false));
+ verify(callback).onConditionChanged(eq(mCondition));
+ assertThat(mCondition.isConditionMet()).isFalse();
}
@Test
@@ -116,7 +120,7 @@
mCondition.addCallback(callback);
mCondition.fakeUpdateCondition(true);
- verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean());
+ verify(callback, never()).onConditionChanged(eq(mCondition));
}
@Test
@@ -127,6 +131,6 @@
mCondition.addCallback(callback);
mCondition.fakeUpdateCondition(false);
- verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean());
+ verify(callback, never()).onConditionChanged(eq(mCondition));
}
}
diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/PackageUtils.java
index 818f0cf..a2b2059 100644
--- a/services/companion/java/com/android/server/companion/PackageUtils.java
+++ b/services/companion/java/com/android/server/companion/PackageUtils.java
@@ -18,7 +18,6 @@
import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP;
import static android.content.pm.PackageManager.GET_CONFIGURATIONS;
-import static android.content.pm.PackageManager.GET_META_DATA;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static com.android.server.companion.CompanionDeviceManagerService.TAG;
@@ -53,7 +52,8 @@
final class PackageUtils {
private static final Intent COMPANION_SERVICE_INTENT =
new Intent(CompanionDeviceService.SERVICE_INTERFACE);
- private static final String META_DATA_PRIMARY_TAG = "android.companion.primary";
+ private static final String PROPERTY_PRIMARY_TAG =
+ "android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE";
static @Nullable PackageInfo getPackageInfo(@NonNull Context context,
@UserIdInt int userId, @NonNull String packageName) {
@@ -84,9 +84,8 @@
static @NonNull Map<String, List<ComponentName>> getCompanionServicesForUser(
@NonNull Context context, @UserIdInt int userId) {
final PackageManager pm = context.getPackageManager();
- final ResolveInfoFlags flags = ResolveInfoFlags.of(GET_META_DATA);
- final List<ResolveInfo> companionServices =
- pm.queryIntentServicesAsUser(COMPANION_SERVICE_INTENT, flags, userId);
+ final List<ResolveInfo> companionServices = pm.queryIntentServicesAsUser(
+ COMPANION_SERVICE_INTENT, ResolveInfoFlags.of(0), userId);
final Map<String, List<ComponentName>> packageNameToServiceInfoList = new HashMap<>();
@@ -109,7 +108,8 @@
service.packageName, it -> new LinkedList<>());
final ComponentName componentName = service.getComponentName();
- if (isPrimaryCompanionDeviceService(service)) {
+
+ if (isPrimaryCompanionDeviceService(pm, componentName)) {
// "Primary" service should be at the head of the list.
services.addFirst(componentName);
} else {
@@ -120,7 +120,12 @@
return packageNameToServiceInfoList;
}
- private static boolean isPrimaryCompanionDeviceService(ServiceInfo service) {
- return service.metaData != null && service.metaData.getBoolean(META_DATA_PRIMARY_TAG);
+ private static boolean isPrimaryCompanionDeviceService(@NonNull PackageManager pm,
+ @NonNull ComponentName componentName) {
+ try {
+ return pm.getProperty(PROPERTY_PRIMARY_TAG, componentName).getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
new file mode 100644
index 0000000..2c42c91
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
@@ -0,0 +1,212 @@
+/*
+ * 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.companion.virtual;
+
+import static android.hardware.camera2.CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_UNSUPPORTED;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraInjectionSession;
+import android.hardware.camera2.CameraManager;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Handles blocking access to the camera for apps running on virtual devices.
+ */
+class CameraAccessController extends CameraManager.AvailabilityCallback {
+ private static final String TAG = "CameraAccessController";
+
+ private final Object mLock = new Object();
+
+ private final Context mContext;
+ private VirtualDeviceManagerInternal mVirtualDeviceManagerInternal;
+ CameraAccessBlockedCallback mBlockedCallback;
+ private CameraManager mCameraManager;
+ private boolean mListeningForCameraEvents;
+ private PackageManager mPackageManager;
+
+ @GuardedBy("mLock")
+ private ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>();
+
+ static class InjectionSessionData {
+ public int appUid;
+ public ArrayMap<String, CameraInjectionSession> cameraIdToSession = new ArrayMap<>();
+ }
+
+ interface CameraAccessBlockedCallback {
+ /**
+ * Called whenever an app was blocked from accessing a camera.
+ * @param appUid uid for the app which was blocked
+ */
+ void onCameraAccessBlocked(int appUid);
+ }
+
+ CameraAccessController(Context context,
+ VirtualDeviceManagerInternal virtualDeviceManagerInternal,
+ CameraAccessBlockedCallback blockedCallback) {
+ mContext = context;
+ mVirtualDeviceManagerInternal = virtualDeviceManagerInternal;
+ mBlockedCallback = blockedCallback;
+ mCameraManager = mContext.getSystemService(CameraManager.class);
+ mPackageManager = mContext.getPackageManager();
+ }
+
+ /**
+ * Starts watching for camera access by uids running on a virtual device, if we were not
+ * already doing so.
+ */
+ public void startObservingIfNeeded() {
+ synchronized (mLock) {
+ if (!mListeningForCameraEvents) {
+ mCameraManager.registerAvailabilityCallback(mContext.getMainExecutor(), this);
+ mListeningForCameraEvents = true;
+ }
+ }
+ }
+
+ /**
+ * Stop watching for camera access.
+ */
+ public void stopObserving() {
+ synchronized (mLock) {
+ mCameraManager.unregisterAvailabilityCallback(this);
+ mListeningForCameraEvents = false;
+ }
+ }
+
+ @Override
+ public void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) {
+ synchronized (mLock) {
+ try {
+ final ApplicationInfo ainfo =
+ mPackageManager.getApplicationInfo(packageName, 0);
+ InjectionSessionData data = mPackageToSessionData.get(packageName);
+ if (!mVirtualDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(ainfo.uid)) {
+ CameraInjectionSession existingSession =
+ (data != null) ? data.cameraIdToSession.get(cameraId) : null;
+ if (existingSession != null) {
+ existingSession.close();
+ data.cameraIdToSession.remove(cameraId);
+ if (data.cameraIdToSession.isEmpty()) {
+ mPackageToSessionData.remove(packageName);
+ }
+ }
+ return;
+ }
+ if (data == null) {
+ data = new InjectionSessionData();
+ data.appUid = ainfo.uid;
+ mPackageToSessionData.put(packageName, data);
+ }
+ if (data.cameraIdToSession.containsKey(cameraId)) {
+ return;
+ }
+ startBlocking(packageName, cameraId);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "onCameraOpened - unknown package " + packageName, e);
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void onCameraClosed(@NonNull String cameraId) {
+ synchronized (mLock) {
+ for (int i = mPackageToSessionData.size() - 1; i >= 0; i--) {
+ InjectionSessionData data = mPackageToSessionData.valueAt(i);
+ CameraInjectionSession session = data.cameraIdToSession.get(cameraId);
+ if (session != null) {
+ session.close();
+ data.cameraIdToSession.remove(cameraId);
+ if (data.cameraIdToSession.isEmpty()) {
+ mPackageToSessionData.removeAt(i);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Turns on blocking for a particular camera and package.
+ */
+ private void startBlocking(String packageName, String cameraId) {
+ try {
+ mCameraManager.injectCamera(packageName, cameraId, /* externalCamId */ "",
+ mContext.getMainExecutor(),
+ new CameraInjectionSession.InjectionStatusCallback() {
+ @Override
+ public void onInjectionSucceeded(
+ @NonNull CameraInjectionSession session) {
+ CameraAccessController.this.onInjectionSucceeded(cameraId, packageName,
+ session);
+ }
+
+ @Override
+ public void onInjectionError(@NonNull int errorCode) {
+ CameraAccessController.this.onInjectionError(cameraId, packageName,
+ errorCode);
+ }
+ });
+ } catch (CameraAccessException e) {
+ Slog.e(TAG,
+ "Failed to injectCamera for cameraId:" + cameraId + " package:" + packageName,
+ e);
+ }
+ }
+
+ private void onInjectionSucceeded(String cameraId, String packageName,
+ @NonNull CameraInjectionSession session) {
+ synchronized (mLock) {
+ InjectionSessionData data = mPackageToSessionData.get(packageName);
+ if (data == null) {
+ Slog.e(TAG, "onInjectionSucceeded didn't find expected entry for package "
+ + packageName);
+ session.close();
+ return;
+ }
+ CameraInjectionSession existingSession = data.cameraIdToSession.put(cameraId, session);
+ if (existingSession != null) {
+ Slog.e(TAG, "onInjectionSucceeded found unexpected existing session for camera "
+ + cameraId);
+ existingSession.close();
+ }
+ }
+ }
+
+ private void onInjectionError(String cameraId, String packageName, @NonNull int errorCode) {
+ if (errorCode != ERROR_INJECTION_UNSUPPORTED) {
+ // ERROR_INJECTION_UNSUPPORTED means that there wasn't an external camera to map to the
+ // internal camera, which is expected when using the injection interface as we are in
+ // this class to simply block camera access. Any other error is unexpected.
+ Slog.e(TAG, "Unexpected injection error code:" + errorCode + " for camera:" + cameraId
+ + " and package:" + packageName);
+ return;
+ }
+ synchronized (mLock) {
+ InjectionSessionData data = mPackageToSessionData.get(packageName);
+ if (data != null) {
+ mBlockedCallback.onCameraAccessBlocked(data.appUid);
+ }
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 3fa1c86..2f6a46a 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -24,6 +24,7 @@
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
+import android.annotation.StringRes;
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.PendingIntent;
@@ -57,6 +58,8 @@
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
+import android.view.Display;
+import android.widget.Toast;
import android.window.DisplayWindowPolicyController;
import com.android.internal.annotations.GuardedBy;
@@ -583,6 +586,26 @@
return false;
}
+ /**
+ * Shows a toast on virtual displays owned by this device which have a given uid running.
+ */
+ void showToastWhereUidIsRunning(int uid, @StringRes int resId, @Toast.Duration int duration) {
+ synchronized (mVirtualDeviceLock) {
+ DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ final int size = mWindowPolicyControllers.size();
+ for (int i = 0; i < size; i++) {
+ if (mWindowPolicyControllers.valueAt(i).containsUid(uid)) {
+ int displayId = mWindowPolicyControllers.keyAt(i);
+ Display display = displayManager.getDisplay(displayId);
+ if (display != null && display.isValid()) {
+ Toast.makeText(mContext.createDisplayContext(display), resId,
+ duration).show();
+ }
+ }
+ }
+ }
+ }
+
interface OnDeviceCloseListener {
void onClose(int associationId);
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index b507110..7842697 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -39,6 +39,7 @@
import android.util.ExceptionUtils;
import android.util.Slog;
import android.util.SparseArray;
+import android.widget.Toast;
import android.window.DisplayWindowPolicyController;
import com.android.internal.annotations.GuardedBy;
@@ -62,8 +63,10 @@
private final Object mVirtualDeviceManagerLock = new Object();
private final VirtualDeviceManagerImpl mImpl;
+ private VirtualDeviceManagerInternal mLocalService;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler);
+ private final CameraAccessController mCameraAccessController;
/**
* Mapping from CDM association IDs to virtual devices. Only one virtual device is allowed for
@@ -90,6 +93,9 @@
public VirtualDeviceManagerService(Context context) {
super(context);
mImpl = new VirtualDeviceManagerImpl();
+ mLocalService = new LocalService();
+ mCameraAccessController = new CameraAccessController(getContext(), mLocalService,
+ this::onCameraAccessBlocked);
}
private final ActivityInterceptorCallback mActivityInterceptorCallback =
@@ -118,8 +124,7 @@
@Override
public void onStart() {
publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl);
- publishLocalService(VirtualDeviceManagerInternal.class, new LocalService());
-
+ publishLocalService(VirtualDeviceManagerInternal.class, mLocalService);
ActivityTaskManagerInternal activityTaskManagerInternal = getLocalService(
ActivityTaskManagerInternal.class);
activityTaskManagerInternal.registerActivityStartInterceptor(
@@ -169,6 +174,16 @@
}
}
+ void onCameraAccessBlocked(int appUid) {
+ synchronized (mVirtualDeviceManagerLock) {
+ int size = mVirtualDevices.size();
+ for (int i = 0; i < size; i++) {
+ mVirtualDevices.valueAt(i).showToastWhereUidIsRunning(appUid,
+ com.android.internal.R.string.vdm_camera_access_denied, Toast.LENGTH_LONG);
+ }
+ }
+ }
+
class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements
VirtualDeviceImpl.PendingTrampolineCallback {
@@ -205,10 +220,14 @@
public void onClose(int associationId) {
synchronized (mVirtualDeviceManagerLock) {
mVirtualDevices.remove(associationId);
+ if (mVirtualDevices.size() == 0) {
+ mCameraAccessController.stopObserving();
+ }
}
}
},
this, activityListener, params);
+ mCameraAccessController.startObservingIfNeeded();
mVirtualDevices.put(associationInfo.getId(), virtualDevice);
return virtualDevice;
}
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 91d2f55..6c220f6 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -45,6 +45,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executors;
import java.util.stream.Collectors;
/**
@@ -369,10 +370,13 @@
// we are only interested in doing things at PHASE_BOOT_COMPLETED
if (phase == PHASE_BOOT_COMPLETED) {
- // due to potentially long computation that holds up boot time, apex sha computations
- // are deferred to first call
Slog.i(TAG, "Boot completed. Getting VBMeta Digest.");
getVBMetaDigestInformation();
+
+ // due to potentially long computation that may hold up boot time, SHA256 computations
+ // for APEXs and Modules will be executed via threads.
+ Slog.i(TAG, "Executing APEX & Module digest computations");
+ computeApexAndModuleDigests();
}
}
@@ -382,6 +386,12 @@
FrameworkStatsLog.write(FrameworkStatsLog.VBMETA_DIGEST_REPORTED, mVbmetaDigest);
}
+ private void computeApexAndModuleDigests() {
+ // using Executors will allow the computations to be done asynchronously, thus not holding
+ // up boot time.
+ Executors.defaultThreadFactory().newThread(() -> updateBinaryMeasurements()).start();
+ }
+
@NonNull
private List<PackageInfo> getInstalledApexs() {
List<PackageInfo> results = new ArrayList<PackageInfo>();
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index a0575cf..26d76a84 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -51,15 +51,12 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import java.io.BufferedReader;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.regex.Matcher;
@@ -128,11 +125,9 @@
// Location of ftrace pipe for notifications from kernel memory tools like KFENCE and KASAN.
private static final String ERROR_REPORT_TRACE_PIPE =
"/sys/kernel/tracing/instances/bootreceiver/trace_pipe";
- // Stop after sending this many reports. See http://b/182159975.
+ // Stop after sending too many reports. See http://b/182159975.
private static final int MAX_ERROR_REPORTS = 8;
private static int sSentReports = 0;
- // Avoid reporing the same bug from processDmesg() twice.
- private static String sLastReportedBug = null;
@Override
public void onReceive(final Context context, Intent intent) {
@@ -175,7 +170,8 @@
* We read from /sys/kernel/tracing/instances/bootreceiver/trace_pipe (set up by the
* system), which will print an ftrace event when a memory corruption is detected in the
* kernel.
- * When an error is detected, we run the dmesg shell command and process its output.
+ * When an error is detected, we set the dmesg.start system property to notify dmesgd
+ * about a new error.
*/
OnFileDescriptorEventListener traceCallback = new OnFileDescriptorEventListener() {
final int mBufferSize = 1024;
@@ -191,8 +187,7 @@
* line, but to be on the safe side we keep reading until the buffer
* contains a '\n' character. In the unlikely case of a very buggy kernel
* the buffer may contain multiple tracing events that cannot be attributed
- * to particular error reports. In that case the latest error report
- * residing in dmesg is picked.
+ * to particular error reports. dmesgd will take care of all errors.
*/
try {
int nbytes = Os.read(fd, mTraceBuffer, 0, mBufferSize);
@@ -201,10 +196,13 @@
if (readStr.indexOf("\n") == -1) {
return OnFileDescriptorEventListener.EVENT_INPUT;
}
- processDmesg(context);
+ if (sSentReports < MAX_ERROR_REPORTS) {
+ SystemProperties.set("dmesgd.start", "1");
+ sSentReports++;
+ }
}
} catch (Exception e) {
- Slog.wtf(TAG, "Error processing dmesg output", e);
+ Slog.wtf(TAG, "Error watching for trace events", e);
return 0; // Unregister the handler.
}
return OnFileDescriptorEventListener.EVENT_INPUT;
@@ -216,157 +214,6 @@
}
- /**
- * Check whether it is safe to collect this dmesg line or not.
- *
- * We only consider lines belonging to KASAN or KFENCE reports, but those may still contain
- * user information, namely the process name:
- *
- * [ 69.547684] [ T6006]c7 6006 CPU: 7 PID: 6006 Comm: sh Tainted: G S C O ...
- *
- * hardware information:
- *
- * [ 69.558923] [ T6006]c7 6006 Hardware name: <REDACTED>
- *
- * or register dump (in KASAN reports only):
- *
- * ... RIP: 0033:0x7f96443109da
- * ... RSP: 002b:00007ffcf0b51b08 EFLAGS: 00000202 ORIG_RAX: 00000000000000af
- * ... RAX: ffffffffffffffda RBX: 000055dc3ee521a0 RCX: 00007f96443109da
- *
- * (on x86_64)
- *
- * ... pc : lpm_cpuidle_enter+0x258/0x384
- * ... lr : lpm_cpuidle_enter+0x1d4/0x384
- * ... sp : ffffff800820bea0
- * ... x29: ffffff800820bea0 x28: ffffffc2305f3ce0
- * ... ...
- * ... x9 : 0000000000000001 x8 : 0000000000000000
- * (on ARM64)
- *
- * We therefore omit the lines that contain "Comm:", "Hardware name:", or match the general
- * purpose register regexp.
- *
- * @param line single line of `dmesg` output.
- * @return updated line with sensitive data removed, or null if the line must be skipped.
- */
- public static String stripSensitiveData(String line) {
- /*
- * General purpose register names begin with "R" on x86_64 and "x" on ARM64. The letter is
- * followed by two symbols (numbers, letters or spaces) and a colon, which is followed by a
- * 16-digit hex number. The optional "_" prefix accounts for ORIG_RAX on x86.
- */
- final String registerRegex = "[ _][Rx]..: [0-9a-f]{16}";
- final Pattern registerPattern = Pattern.compile(registerRegex);
-
- final String corruptionRegex = "Detected corrupted memory at 0x[0-9a-f]+";
- final Pattern corruptionPattern = Pattern.compile(corruptionRegex);
-
- if (line.contains("Comm: ") || line.contains("Hardware name: ")) return null;
- if (registerPattern.matcher(line).find()) return null;
-
- Matcher cm = corruptionPattern.matcher(line);
- if (cm.find()) return cm.group(0);
- return line;
- }
-
- /*
- * Search dmesg output for the last error report from KFENCE or KASAN and copy it to Dropbox.
- *
- * Example report printed by the kernel (redacted to fit into 100 column limit):
- * [ 69.236673] [ T6006]c7 6006 =========================================================
- * [ 69.245688] [ T6006]c7 6006 BUG: KFENCE: out-of-bounds in kfence_handle_page_fault
- * [ 69.245688] [ T6006]c7 6006
- * [ 69.257816] [ T6006]c7 6006 Out-of-bounds access at 0xffffffca75c45000 (...)
- * [ 69.267102] [ T6006]c7 6006 kfence_handle_page_fault+0x1bc/0x208
- * [ 69.273536] [ T6006]c7 6006 __do_kernel_fault+0xa8/0x11c
- * ...
- * [ 69.355427] [ T6006]c7 6006 kfence-#2 [0xffffffca75c46f30-0xffffffca75c46fff, ...
- * [ 69.366938] [ T6006]c7 6006 __d_alloc+0x3c/0x1b4
- * [ 69.371946] [ T6006]c7 6006 d_alloc_parallel+0x48/0x538
- * [ 69.377578] [ T6006]c7 6006 __lookup_slow+0x60/0x15c
- * ...
- * [ 69.547684] [ T6006]c7 6006 CPU: 7 PID: 6006 Comm: sh Tainted: G S C O ...
- * [ 69.558923] [ T6006]c7 6006 Hardware name: <REDACTED>
- * [ 69.567059] [ T6006]c7 6006 =========================================================
- *
- * We rely on the kernel printing task/CPU ID for every log line (CONFIG_PRINTK_CALLER=y).
- * E.g. for the above report the task ID is T6006. Report lines may interleave with lines
- * printed by other kernel tasks, which will have different task IDs, so in order to collect
- * the report we:
- * - find the next occurrence of the "BUG: " line in the kernel log, parse it to obtain the
- * task ID and the tool name;
- * - scan the rest of dmesg output and pick every line that has the same task ID, until we
- * encounter a horizontal ruler, i.e.:
- * [ 69.567059] [ T6006]c7 6006 ======================================================
- * - add that line to the error report, unless it contains sensitive information (see
- * logLinePotentiallySensitive())
- * - repeat the above steps till the last report is found.
- */
- private void processDmesg(Context ctx) throws IOException {
- if (sSentReports == MAX_ERROR_REPORTS) return;
- /*
- * Only SYSTEM_KASAN_ERROR_REPORT and SYSTEM_KFENCE_ERROR_REPORT are supported at the
- * moment.
- */
- final String[] bugTypes = new String[] { "KASAN", "KFENCE" };
- final String tsRegex = "^\\[[^]]+\\] ";
- final String bugRegex =
- tsRegex + "\\[([^]]+)\\].*BUG: (" + String.join("|", bugTypes) + "):";
- final Pattern bugPattern = Pattern.compile(bugRegex);
-
- Process p = new ProcessBuilder("/system/bin/timeout", "-k", "90s", "60s",
- "dmesg").redirectErrorStream(true).start();
- BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
- String line = null;
- String task = null;
- String tool = null;
- String bugTitle = null;
- Pattern reportPattern = null;
- ArrayList<String> currentReport = null;
- String lastReport = null;
-
- while ((line = reader.readLine()) != null) {
- if (currentReport == null) {
- Matcher bm = bugPattern.matcher(line);
- if (!bm.find()) continue;
- task = bm.group(1);
- tool = bm.group(2);
- bugTitle = line;
- currentReport = new ArrayList<String>();
- currentReport.add(line);
- String reportRegex = tsRegex + "\\[" + task + "\\].*";
- reportPattern = Pattern.compile(reportRegex);
- continue;
- }
- Matcher rm = reportPattern.matcher(line);
- if (!rm.matches()) continue;
- if ((line = stripSensitiveData(line)) == null) continue;
- if (line.contains("================================")) {
- lastReport = String.join("\n", currentReport);
- currentReport = null;
- continue;
- }
- currentReport.add(line);
- }
- if (lastReport == null) {
- Slog.w(TAG, "Could not find report in dmesg.");
- return;
- }
-
- // Avoid sending the same bug report twice.
- if (bugTitle.equals(sLastReportedBug)) return;
-
- final String reportTag = "SYSTEM_" + tool + "_ERROR_REPORT";
- final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
- final String headers = getCurrentBootHeaders();
- final String reportText = headers + lastReport;
-
- addTextToDropBox(db, reportTag, reportText, "/dev/kmsg", LOG_SIZE);
- sLastReportedBug = bugTitle;
- sSentReports++;
- }
-
private void removeOldUpdatePackages(Context context) {
Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS);
}
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 39ac5ef..b59cd4c 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -21,6 +21,7 @@
import static android.Manifest.permission.OBSERVE_NETWORK_POLICY;
import static android.Manifest.permission.SHUTDOWN;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
@@ -30,6 +31,7 @@
import static android.net.INetd.FIREWALL_RULE_ALLOW;
import static android.net.INetd.FIREWALL_RULE_DENY;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
@@ -206,6 +208,11 @@
*/
@GuardedBy("mRulesLock")
private SparseIntArray mUidFirewallRestrictedRules = new SparseIntArray();
+ /**
+ * Contains the per-UID firewall rules that are used when Low Power Standby is enabled.
+ */
+ @GuardedBy("mRulesLock")
+ private SparseIntArray mUidFirewallLowPowerStandbyRules = new SparseIntArray();
/** Set of states for the child firewall chains. True if the chain is active. */
@GuardedBy("mRulesLock")
final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();
@@ -506,12 +513,14 @@
syncFirewallChainLocked(FIREWALL_CHAIN_DOZABLE, "dozable ");
syncFirewallChainLocked(FIREWALL_CHAIN_POWERSAVE, "powersave ");
syncFirewallChainLocked(FIREWALL_CHAIN_RESTRICTED, "restricted ");
+ syncFirewallChainLocked(FIREWALL_CHAIN_LOW_POWER_STANDBY, "low power standby ");
final int[] chains = {
FIREWALL_CHAIN_STANDBY,
FIREWALL_CHAIN_DOZABLE,
FIREWALL_CHAIN_POWERSAVE,
- FIREWALL_CHAIN_RESTRICTED
+ FIREWALL_CHAIN_RESTRICTED,
+ FIREWALL_CHAIN_LOW_POWER_STANDBY
};
for (int chain : chains) {
@@ -1438,6 +1447,8 @@
return FIREWALL_CHAIN_NAME_POWERSAVE;
case FIREWALL_CHAIN_RESTRICTED:
return FIREWALL_CHAIN_NAME_RESTRICTED;
+ case FIREWALL_CHAIN_LOW_POWER_STANDBY:
+ return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
default:
throw new IllegalArgumentException("Bad child chain: " + chain);
}
@@ -1453,6 +1464,8 @@
return FIREWALL_ALLOWLIST;
case FIREWALL_CHAIN_RESTRICTED:
return FIREWALL_ALLOWLIST;
+ case FIREWALL_CHAIN_LOW_POWER_STANDBY:
+ return FIREWALL_ALLOWLIST;
default:
return isFirewallEnabled() ? FIREWALL_ALLOWLIST : FIREWALL_DENYLIST;
}
@@ -1571,6 +1584,8 @@
return mUidFirewallPowerSaveRules;
case FIREWALL_CHAIN_RESTRICTED:
return mUidFirewallRestrictedRules;
+ case FIREWALL_CHAIN_LOW_POWER_STANDBY:
+ return mUidFirewallLowPowerStandbyRules;
case FIREWALL_CHAIN_NONE:
return mUidFirewallRules;
default:
@@ -1626,6 +1641,11 @@
pw.println(getFirewallChainState(FIREWALL_CHAIN_RESTRICTED));
dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_RESTRICTED,
mUidFirewallRestrictedRules);
+
+ pw.print("UID firewall low power standby chain enabled: ");
+ pw.println(getFirewallChainState(FIREWALL_CHAIN_LOW_POWER_STANDBY));
+ dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY,
+ mUidFirewallLowPowerStandbyRules);
}
pw.print("Firewall enabled: "); pw.println(mFirewallEnabled);
@@ -1749,6 +1769,11 @@
if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of restricted mode");
return true;
}
+ if (getFirewallChainState(FIREWALL_CHAIN_LOW_POWER_STANDBY)
+ && mUidFirewallLowPowerStandbyRules.get(uid) != FIREWALL_RULE_ALLOW) {
+ if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of low power standby");
+ return true;
+ }
if (mUidRejectOnMetered.get(uid)) {
if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of no metered data"
+ " in the background");
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 178b666..454028d 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -872,13 +872,13 @@
break;
}
case H_COMPLETE_UNLOCK_USER: {
- completeUnlockUser((int) msg.obj);
+ completeUnlockUser(msg.arg1);
break;
}
case H_VOLUME_STATE_CHANGED: {
final SomeArgs args = (SomeArgs) msg.obj;
- onVolumeStateChangedAsync((VolumeInfo) args.arg1, (int) args.arg2,
- (int) args.arg3);
+ onVolumeStateChangedAsync((VolumeInfo) args.arg1, args.argi1, args.argi2);
+ args.recycle();
}
}
}
@@ -1205,7 +1205,8 @@
Slog.w(TAG, "UNLOCK_USER lost from vold reset, will retry, user:" + userId);
mVold.onUserStarted(userId);
mStoraged.onUserStarted(userId);
- mHandler.obtainMessage(H_COMPLETE_UNLOCK_USER, userId).sendToTarget();
+ mHandler.obtainMessage(H_COMPLETE_UNLOCK_USER, userId, /* arg2 (unusued) */ 0)
+ .sendToTarget();
}
}
@@ -1263,7 +1264,8 @@
Slog.wtf(TAG, e);
}
- mHandler.obtainMessage(H_COMPLETE_UNLOCK_USER, userId).sendToTarget();
+ mHandler.obtainMessage(H_COMPLETE_UNLOCK_USER, userId, /* arg2 (unusued) */ 0)
+ .sendToTarget();
if (mRemountCurrentUserVolumesOnUnlock && userId == mCurrentUserId) {
maybeRemountVolumes(userId);
mRemountCurrentUserVolumesOnUnlock = false;
@@ -1493,18 +1495,17 @@
}
@Override
- public void onVolumeStateChanged(String volId, int state) {
+ public void onVolumeStateChanged(String volId, final int newState) {
synchronized (mLock) {
final VolumeInfo vol = mVolumes.get(volId);
if (vol != null) {
final int oldState = vol.state;
- final int newState = state;
vol.state = newState;
final VolumeInfo vInfo = new VolumeInfo(vol);
final SomeArgs args = SomeArgs.obtain();
args.arg1 = vInfo;
- args.arg2 = oldState;
- args.arg3 = newState;
+ args.argi1 = oldState;
+ args.argi2 = newState;
mHandler.obtainMessage(H_VOLUME_STATE_CHANGED, args).sendToTarget();
onVolumeStateChangedLocked(vInfo, oldState, newState);
}
@@ -4674,7 +4675,7 @@
private int getMountModeInternal(int uid, String packageName) {
try {
// Get some easy cases out of the way first
- if (Process.isIsolated(uid)) {
+ if (Process.isIsolated(uid) || Process.isSupplemental(uid)) {
return StorageManager.MOUNT_MODE_EXTERNAL_NONE;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2da6fb4..a478c31 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -458,7 +458,7 @@
* broadcasts
*/
private static final boolean ENFORCE_DYNAMIC_RECEIVER_EXPLICIT_EXPORT =
- SystemProperties.getBoolean("fw.enforce_dynamic_receiver_explicit_export", true);
+ SystemProperties.getBoolean("fw.enforce_dynamic_receiver_explicit_export", false);
static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM;
static final String TAG_BACKUP = TAG + POSTFIX_BACKUP;
@@ -16140,6 +16140,23 @@
}
/**
+ * Returns package name by pid.
+ */
+ @Override
+ @Nullable
+ public String getPackageNameByPid(int pid) {
+ synchronized (mPidsSelfLocked) {
+ final ProcessRecord app = mPidsSelfLocked.get(pid);
+
+ if (app != null && app.info != null) {
+ return app.info.packageName;
+ }
+
+ return null;
+ }
+ }
+
+ /**
* Sets if the given pid has an overlay UI or not.
*
* @param pid The pid we are setting overlay UI for.
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 97727b08..08508b2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -31,6 +31,7 @@
import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_NORMAL;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATTERY_USAGE_COUNT;
import static com.android.server.am.LowMemDetector.ADJ_MEM_FACTOR_NOTHING;
import android.app.ActivityManager;
@@ -3256,12 +3257,12 @@
return -1;
}
if (arg == null) {
- batteryTracker.mDebugUidPercentages.clear();
+ batteryTracker.clearDebugUidPercentage();
return 0;
}
String[] pairs = arg.split(",");
int[] uids = new int[pairs.length];
- double[] values = new double[pairs.length];
+ double[][] values = new double[pairs.length][];
try {
for (int i = 0; i < pairs.length; i++) {
String[] pair = pairs[i].split("=");
@@ -3270,16 +3271,21 @@
return -1;
}
uids[i] = Integer.parseInt(pair[0]);
- values[i] = Double.parseDouble(pair[1]);
+ final String[] vals = pair[1].split(":");
+ if (vals.length != BATTERY_USAGE_COUNT) {
+ getErrPrintWriter().println("Malformed input");
+ return -1;
+ }
+ values[i] = new double[vals.length];
+ for (int j = 0; j < vals.length; j++) {
+ values[i][j] = Double.parseDouble(vals[j]);
+ }
}
} catch (NumberFormatException e) {
getErrPrintWriter().println("Malformed input");
return -1;
}
- batteryTracker.mDebugUidPercentages.clear();
- for (int i = 0; i < pairs.length; i++) {
- batteryTracker.mDebugUidPercentages.put(uids[i], values[i]);
- }
+ batteryTracker.setDebugUidPercentage(uids, values);
return 0;
}
diff --git a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
index 75de3a1..3c780aa 100644
--- a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
@@ -18,6 +18,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.AppBatteryTracker.BATTERY_USAGE_NONE;
import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX;
import static com.android.server.am.BaseAppStateDurationsTracker.EVENT_NUM;
@@ -28,10 +29,13 @@
import android.util.Pair;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.am.AppBatteryExemptionTracker.AppBatteryExemptionPolicy;
import com.android.server.am.AppBatteryExemptionTracker.UidBatteryStates;
import com.android.server.am.AppBatteryTracker.AppBatteryPolicy;
+import com.android.server.am.AppBatteryTracker.BatteryUsage;
+import com.android.server.am.AppBatteryTracker.ImmutableBatteryUsage;
import com.android.server.am.BaseAppStateDurationsTracker.EventListener;
import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
import com.android.server.am.BaseAppStateTracker.Injector;
@@ -97,7 +101,8 @@
if (!mInjector.getPolicy().isEnabled()) {
return;
}
- final double batteryUsage = mAppRestrictionController.getUidBatteryUsage(uid);
+ final ImmutableBatteryUsage batteryUsage = mAppRestrictionController
+ .getUidBatteryUsage(uid);
synchronized (mLock) {
UidBatteryStates pkg = mPkgEvents.get(uid, DEFAULT_NAME);
if (pkg == null) {
@@ -120,22 +125,23 @@
* @return The to-be-exempted battery usage of the given UID in the given duration; it could
* be considered as "exempted" due to various use cases, i.e. media playback.
*/
- double getUidBatteryExemptedUsageSince(int uid, long since, long now) {
+ ImmutableBatteryUsage getUidBatteryExemptedUsageSince(int uid, long since, long now) {
if (!mInjector.getPolicy().isEnabled()) {
- return 0.0d;
+ return BATTERY_USAGE_NONE;
}
- Pair<Double, Double> result;
+ Pair<ImmutableBatteryUsage, ImmutableBatteryUsage> result;
synchronized (mLock) {
final UidBatteryStates pkg = mPkgEvents.get(uid, DEFAULT_NAME);
if (pkg == null) {
- return 0.0d;
+ return BATTERY_USAGE_NONE;
}
result = pkg.getBatteryUsageSince(since, now);
}
- if (result.second > 0.0d) {
+ if (!result.second.isEmpty()) {
// We have an open event (just start, no stop), get the battery usage till now.
- final double batteryUsage = mAppRestrictionController.getUidBatteryUsage(uid);
- return result.first + batteryUsage - result.second;
+ final ImmutableBatteryUsage batteryUsage = mAppRestrictionController
+ .getUidBatteryUsage(uid);
+ return result.first.mutate().add(batteryUsage).subtract(result.second).unmutate();
}
return result.first;
}
@@ -156,7 +162,7 @@
* @param batteryUsage The background current drain since the system boots.
* @param eventType One of EVENT_TYPE_* defined in the class BaseAppStateDurationsTracker.
*/
- void addEvent(boolean start, long now, double batteryUsage, int eventType) {
+ void addEvent(boolean start, long now, ImmutableBatteryUsage batteryUsage, int eventType) {
if (start) {
addEvent(start, new UidStateEventWithBattery(start, now, batteryUsage, null),
eventType);
@@ -169,7 +175,8 @@
return;
}
addEvent(start, new UidStateEventWithBattery(start, now,
- batteryUsage - last.getBatteryUsage(), last), eventType);
+ batteryUsage.mutate().subtract(last.getBatteryUsage()).unmutate(), last),
+ eventType);
}
}
@@ -183,34 +190,37 @@
* the second value is the battery usage since the system boots, if there is
* an open event(just start, no stop) at the end of the duration.
*/
- Pair<Double, Double> getBatteryUsageSince(long since, long now, int eventType) {
+ Pair<ImmutableBatteryUsage, ImmutableBatteryUsage> getBatteryUsageSince(long since,
+ long now, int eventType) {
return getBatteryUsageSince(since, now, mEvents[eventType]);
}
- private Pair<Double, Double> getBatteryUsageSince(long since, long now,
- LinkedList<UidStateEventWithBattery> events) {
+ private Pair<ImmutableBatteryUsage, ImmutableBatteryUsage> getBatteryUsageSince(long since,
+ long now, LinkedList<UidStateEventWithBattery> events) {
if (events == null || events.size() == 0) {
- return Pair.create(0.0d, 0.0d);
+ return Pair.create(BATTERY_USAGE_NONE, BATTERY_USAGE_NONE);
}
- double batteryUsage = 0.0d;
+ final BatteryUsage batteryUsage = new BatteryUsage();
UidStateEventWithBattery lastEvent = null;
for (UidStateEventWithBattery event : events) {
lastEvent = event;
if (event.getTimestamp() < since || event.isStart()) {
continue;
}
- batteryUsage += event.getBatteryUsage(since, Math.min(now, event.getTimestamp()));
+ batteryUsage.add(event.getBatteryUsage(since, Math.min(now, event.getTimestamp())));
if (now <= event.getTimestamp()) {
break;
}
}
- return Pair.create(batteryUsage, lastEvent.isStart() ? lastEvent.getBatteryUsage() : 0);
+ return Pair.create(batteryUsage.unmutate(), lastEvent.isStart()
+ ? lastEvent.getBatteryUsage() : BATTERY_USAGE_NONE);
}
/**
* @return The aggregated battery usage amongst all the event types we're tracking.
*/
- Pair<Double, Double> getBatteryUsageSince(long since, long now) {
+ Pair<ImmutableBatteryUsage, ImmutableBatteryUsage> getBatteryUsageSince(long since,
+ long now) {
LinkedList<UidStateEventWithBattery> result = new LinkedList<>();
for (int i = 0; i < mEvents.length; i++) {
result = add(result, mEvents[i]);
@@ -236,7 +246,7 @@
UidStateEventWithBattery l = itl.next(), r = itr.next();
LinkedList<UidStateEventWithBattery> dest = new LinkedList<>();
boolean actl = false, actr = false, overlapping = false;
- double batteryUsage = 0.0d;
+ final BatteryUsage batteryUsage = new BatteryUsage();
long recentActTs = 0, overlappingDuration = 0;
for (long lts = l.getTimestamp(), rts = r.getTimestamp();
lts != Long.MAX_VALUE || rts != Long.MAX_VALUE;) {
@@ -245,8 +255,8 @@
if (lts == rts) {
earliest = l;
// we'll deal with the double counting problem later.
- batteryUsage += actl ? l.getBatteryUsage() : 0.0d;
- batteryUsage += actr ? r.getBatteryUsage() : 0.0d;
+ if (actl) batteryUsage.add(l.getBatteryUsage());
+ if (actr) batteryUsage.add(r.getBatteryUsage());
overlappingDuration += overlapping && (actl || actr)
? (lts - recentActTs) : 0;
actl = !actl;
@@ -255,13 +265,13 @@
rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE;
} else if (lts < rts) {
earliest = l;
- batteryUsage += actl ? l.getBatteryUsage() : 0.0d;
+ if (actl) batteryUsage.add(l.getBatteryUsage());
overlappingDuration += overlapping && actl ? (lts - recentActTs) : 0;
actl = !actl;
lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE;
} else {
earliest = r;
- batteryUsage += actr ? r.getBatteryUsage() : 0.0d;
+ if (actr) batteryUsage.add(r.getBatteryUsage());
overlappingDuration += overlapping && actr ? (rts - recentActTs) : 0;
actr = !actr;
rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE;
@@ -281,12 +291,12 @@
final long durationWithOverlapping = duration + overlappingDuration;
// Get the proportional batteryUsage.
if (durationWithOverlapping != 0) {
- batteryUsage *= duration * 1.0d / durationWithOverlapping;
+ batteryUsage.scale(duration * 1.0d / durationWithOverlapping);
+ event.update(lastEvent, new ImmutableBatteryUsage(batteryUsage));
} else {
- batteryUsage = 0.0d;
+ event.update(lastEvent, BATTERY_USAGE_NONE);
}
- event.update(lastEvent, batteryUsage);
- batteryUsage = 0.0d;
+ batteryUsage.setTo(BATTERY_USAGE_NONE);
overlappingDuration = 0;
}
dest.add(event);
@@ -322,14 +332,15 @@
* the system boots if the {@link #mIsStart} is true, but will be the delta of the bg
* battery usage since the start event if the {@link #mIsStart} is false.
*/
- private double mBatteryUsage;
+ private @NonNull ImmutableBatteryUsage mBatteryUsage;
/**
* The peer event of this pair (a pair of start/stop events).
*/
private @Nullable UidStateEventWithBattery mPeer;
- UidStateEventWithBattery(boolean isStart, long now, double batteryUsage,
+ UidStateEventWithBattery(boolean isStart, long now,
+ @NonNull ImmutableBatteryUsage batteryUsage,
@Nullable UidStateEventWithBattery peer) {
super(now);
mIsStart = isStart;
@@ -355,15 +366,19 @@
}
if (mPeer != null) {
// Reduce the bg battery usage proportionally.
- final double batteryUsage = mPeer.getBatteryUsage();
+ final ImmutableBatteryUsage batteryUsage = mPeer.getBatteryUsage();
mPeer.mBatteryUsage = mPeer.getBatteryUsage(timestamp, mPeer.mTimestamp);
// Update the battery data of the start event too.
- mBatteryUsage += batteryUsage - mPeer.mBatteryUsage;
+ mBatteryUsage = mBatteryUsage.mutate()
+ .add(batteryUsage)
+ .subtract(mPeer.mBatteryUsage)
+ .unmutate();
}
mTimestamp = timestamp;
}
- void update(@NonNull UidStateEventWithBattery peer, double batteryUsage) {
+ void update(@NonNull UidStateEventWithBattery peer,
+ @NonNull ImmutableBatteryUsage batteryUsage) {
mPeer = peer;
peer.mPeer = this;
mBatteryUsage = batteryUsage;
@@ -373,18 +388,19 @@
return mIsStart;
}
- double getBatteryUsage(long start, long end) {
+ @NonNull ImmutableBatteryUsage getBatteryUsage(long start, long end) {
if (mIsStart || start >= mTimestamp || end <= start) {
- return 0.0d;
+ return BATTERY_USAGE_NONE;
}
start = Math.max(start, mPeer.mTimestamp);
end = Math.min(end, mTimestamp);
final long totalDur = mTimestamp - mPeer.mTimestamp;
final long inputDur = end - start;
- return totalDur != 0 ? mBatteryUsage * (1.0d * inputDur) / totalDur : 0.0d;
+ return totalDur != 0 ? (totalDur == inputDur ? mBatteryUsage : mBatteryUsage.mutate()
+ .scale((1.0d * inputDur) / totalDur).unmutate()) : BATTERY_USAGE_NONE;
}
- double getBatteryUsage() {
+ @NonNull ImmutableBatteryUsage getBatteryUsage() {
return mBatteryUsage;
}
@@ -404,14 +420,20 @@
final UidStateEventWithBattery otherEvent = (UidStateEventWithBattery) other;
return otherEvent.mIsStart == mIsStart
&& otherEvent.mTimestamp == mTimestamp
- && Double.compare(otherEvent.mBatteryUsage, mBatteryUsage) == 0;
+ && mBatteryUsage.equals(otherEvent.mBatteryUsage);
+ }
+
+ @Override
+ public String toString() {
+ return "UidStateEventWithBattery(" + mIsStart + ", " + mTimestamp
+ + ", " + mBatteryUsage + ")";
}
@Override
public int hashCode() {
return (Boolean.hashCode(mIsStart) * 31
+ Long.hashCode(mTimestamp)) * 31
- + Double.hashCode(mBatteryUsage);
+ + mBatteryUsage.hashCode();
}
}
@@ -433,7 +455,8 @@
super(injector, tracker,
KEY_BG_BATTERY_EXEMPTION_ENABLED, DEFAULT_BG_BATTERY_EXEMPTION_ENABLED,
AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_WINDOW,
- AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS);
+ tracker.mContext.getResources()
+ .getInteger(R.integer.config_bg_current_drain_window));
}
@Override
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
index b8f5c50..6492662 100644
--- a/services/core/java/com/android/server/am/AppBatteryTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -28,23 +28,28 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_COUNT;
import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
import static android.os.PowerExemptionManager.REASON_DENIED;
import static android.util.TimeUtils.formatTime;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX;
-import static com.android.server.am.BaseAppStateTracker.ONE_DAY;
import static com.android.server.am.BaseAppStateTracker.ONE_MINUTE;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager.RestrictionLevel;
import android.content.Context;
import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.os.BatteryConsumer;
+import android.os.BatteryConsumer.Dimensions;
import android.os.BatteryStatsInternal;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
@@ -58,9 +63,9 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import android.util.SparseDoubleArray;
import android.util.TimeUtils;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -98,12 +103,7 @@
static final long BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_LONG = 5 * ONE_MINUTE; // 5 mins
static final long BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_DEBUG = 2_000L; // 2s
- static final BatteryConsumer.Dimensions BATT_DIMEN_FG =
- new BatteryConsumer.Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_FOREGROUND);
- static final BatteryConsumer.Dimensions BATT_DIMEN_BG =
- new BatteryConsumer.Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_BACKGROUND);
- static final BatteryConsumer.Dimensions BATT_DIMEN_FGS =
- new BatteryConsumer.Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_FOREGROUND_SERVICE);
+ static final ImmutableBatteryUsage BATTERY_USAGE_NONE = new ImmutableBatteryUsage();
private final Runnable mBgBatteryUsageStatsPolling = this::updateBatteryUsageStatsAndCheck;
private final Runnable mBgBatteryUsageStatsCheck = this::checkBatteryUsageStats;
@@ -132,29 +132,30 @@
* the last battery stats reset prior to that (whoever is earlier).
*/
@GuardedBy("mLock")
- private final SparseDoubleArray mUidBatteryUsage = new SparseDoubleArray();
+ private final SparseArray<BatteryUsage> mUidBatteryUsage = new SparseArray<>();
/**
* The battery usage for each UID, in the rolling window of the past.
*/
@GuardedBy("mLock")
- private final SparseDoubleArray mUidBatteryUsageInWindow = new SparseDoubleArray();
+ private final SparseArray<ImmutableBatteryUsage> mUidBatteryUsageInWindow = new SparseArray<>();
/**
* The uid battery usage stats data from our last query, it consists of the data since
* last battery stats reset.
*/
@GuardedBy("mLock")
- private final SparseDoubleArray mLastUidBatteryUsage = new SparseDoubleArray();
+ private final SparseArray<ImmutableBatteryUsage> mLastUidBatteryUsage = new SparseArray<>();
// No lock is needed.
- private final SparseDoubleArray mTmpUidBatteryUsage = new SparseDoubleArray();
+ private final SparseArray<BatteryUsage> mTmpUidBatteryUsage = new SparseArray<>();
// No lock is needed.
- private final SparseDoubleArray mTmpUidBatteryUsage2 = new SparseDoubleArray();
+ private final SparseArray<ImmutableBatteryUsage> mTmpUidBatteryUsage2 = new SparseArray<>();
// No lock is needed.
- private final SparseDoubleArray mTmpUidBatteryUsageInWindow = new SparseDoubleArray();
+ private final SparseArray<ImmutableBatteryUsage> mTmpUidBatteryUsageInWindow =
+ new SparseArray<>();
// No lock is needed.
private final ArraySet<UserHandle> mTmpUserIds = new ArraySet<>();
@@ -166,7 +167,7 @@
private long mLastUidBatteryUsageStartTs;
// For debug only.
- final SparseDoubleArray mDebugUidPercentages = new SparseDoubleArray();
+ private final SparseArray<BatteryUsage> mDebugUidPercentages = new SparseArray<>();
AppBatteryTracker(Context context, AppRestrictionController controller) {
this(context, controller, null, null);
@@ -277,18 +278,24 @@
* </p>
*/
@Override
- public double getUidBatteryUsage(int uid) {
+ @NonNull
+ public ImmutableBatteryUsage getUidBatteryUsage(int uid) {
final long now = mInjector.currentTimeMillis();
final boolean updated = updateBatteryUsageStatsIfNecessary(now, false);
synchronized (mLock) {
if (updated) {
// We just got fresh data, schedule a check right a way.
mBgHandler.removeCallbacks(mBgBatteryUsageStatsPolling);
- if (!mBgHandler.hasCallbacks(mBgBatteryUsageStatsCheck)) {
- mBgHandler.post(mBgBatteryUsageStatsCheck);
- }
+ scheduleBgBatteryUsageStatsCheck();
}
- return mUidBatteryUsage.get(uid, 0.0d);
+ final BatteryUsage usage = mUidBatteryUsage.get(uid);
+ return usage != null ? new ImmutableBatteryUsage(usage) : BATTERY_USAGE_NONE;
+ }
+ }
+
+ private void scheduleBgBatteryUsageStatsCheck() {
+ if (!mBgHandler.hasCallbacks(mBgBatteryUsageStatsCheck)) {
+ mBgHandler.post(mBgBatteryUsageStatsCheck);
}
}
@@ -309,27 +316,32 @@
final long now = SystemClock.elapsedRealtime();
final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
try {
- final SparseDoubleArray uidConsumers = mTmpUidBatteryUsageInWindow;
+ final SparseArray<ImmutableBatteryUsage> uidConsumers = mTmpUidBatteryUsageInWindow;
synchronized (mLock) {
copyUidBatteryUsage(mUidBatteryUsageInWindow, uidConsumers);
}
final long since = Math.max(0, now - bgPolicy.mBgCurrentDrainWindowMs);
for (int i = 0, size = uidConsumers.size(); i < size; i++) {
final int uid = uidConsumers.keyAt(i);
- final double actualUsage = uidConsumers.valueAt(i);
- final double exemptedUsage = mAppRestrictionController
+ final ImmutableBatteryUsage actualUsage = uidConsumers.valueAt(i);
+ final ImmutableBatteryUsage exemptedUsage = mAppRestrictionController
.getUidBatteryExemptedUsageSince(uid, since, now);
// It's possible the exemptedUsage could be larger than actualUsage,
// as the former one is an approximate value.
- final double bgUsage = Math.max(0.0d, actualUsage - exemptedUsage);
- final double percentage = bgPolicy.getPercentage(uid, bgUsage);
+ final BatteryUsage bgUsage = actualUsage.mutate()
+ .subtract(exemptedUsage)
+ .calcPercentage(uid, bgPolicy);
if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
Slog.i(TAG, String.format(
- "UID %d: %.3f mAh (or %4.2f%%) %.3f %.3f over the past %s",
- uid, bgUsage, percentage, exemptedUsage, actualUsage,
+ "UID %d: %s (%s) | %s | %s over the past %s",
+ uid,
+ bgUsage.toString(),
+ bgUsage.percentageToString(),
+ exemptedUsage.toString(),
+ actualUsage.toString(),
TimeUtils.formatDuration(bgPolicy.mBgCurrentDrainWindowMs)));
}
- bgPolicy.handleUidBatteryUsage(uid, percentage);
+ bgPolicy.handleUidBatteryUsage(uid, bgUsage);
}
// For debugging only.
for (int i = 0, size = mDebugUidPercentages.size(); i < size; i++) {
@@ -384,7 +396,7 @@
private void updateBatteryUsageStatsOnce(long now) {
final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
final ArraySet<UserHandle> userIds = mTmpUserIds;
- final SparseDoubleArray buf = mTmpUidBatteryUsage;
+ final SparseArray<BatteryUsage> buf = mTmpUidBatteryUsage;
final BatteryStatsInternal batteryStatsInternal = mInjector.getBatteryStatsInternal();
final long windowSize = bgPolicy.mBgCurrentDrainWindowMs;
@@ -453,26 +465,26 @@
for (int i = 0, size = buf.size(); i < size; i++) {
final int uid = buf.keyAt(i);
final int index = mUidBatteryUsage.indexOfKey(uid);
- final double lastUsage = mLastUidBatteryUsage.get(uid, 0.0d);
- final double curUsage = buf.valueAt(i);
- final double before;
+ final BatteryUsage lastUsage = mLastUidBatteryUsage.get(uid, BATTERY_USAGE_NONE);
+ final BatteryUsage curUsage = buf.valueAt(i);
+ final BatteryUsage before;
if (index >= 0) {
before = mUidBatteryUsage.valueAt(index);
- mUidBatteryUsage.setValueAt(index, before - lastUsage + curUsage);
+ before.subtract(lastUsage).add(curUsage);
} else {
- before = 0.0d;
+ before = BATTERY_USAGE_NONE;
mUidBatteryUsage.put(uid, curUsage);
}
if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
- final double actualDelta = curUsage - lastUsage;
+ final BatteryUsage actualDelta = new BatteryUsage(curUsage).subtract(lastUsage);
String msg = "Updating mUidBatteryUsage uid=" + uid + ", before=" + before
- + ", after=" + mUidBatteryUsage.get(uid, 0.0d)
+ + ", after=" + mUidBatteryUsage.get(uid, BATTERY_USAGE_NONE)
+ ", delta=" + actualDelta
+ ", last=" + lastUsage
+ ", curStart=" + curStart
+ ", lastLastStart=" + lastUidBatteryUsageStartTs
+ ", thisLastStart=" + mLastUidBatteryUsageStartTs;
- if (actualDelta < 0.0d) {
+ if (!actualDelta.isValid()) {
// Something is wrong, the battery usage shouldn't be negative.
Slog.e(TAG, msg);
} else {
@@ -508,8 +520,8 @@
}
}
- private static BatteryUsageStats updateBatteryUsageStatsOnceInternal(long expectedDuration,
- SparseDoubleArray buf, BatteryUsageStatsQuery.Builder builder,
+ private BatteryUsageStats updateBatteryUsageStatsOnceInternal(long expectedDuration,
+ SparseArray<BatteryUsage> buf, BatteryUsageStatsQuery.Builder builder,
ArraySet<UserHandle> userIds, BatteryStatsInternal batteryStatsInternal) {
for (int i = 0, size = userIds.size(); i < size; i++) {
builder.addUser(userIds.valueAt(i));
@@ -527,16 +539,19 @@
final long end = stats.getStatsEndTimestamp();
final double scale = expectedDuration > 0
? (expectedDuration * 1.0d) / (end - start) : 1.0d;
+ final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
for (UidBatteryConsumer uidConsumer : uidConsumers) {
// TODO: b/200326767 - as we are not supporting per proc state attribution yet,
// we couldn't distinguish between a real FGS vs. a bound FGS proc state.
final int uid = uidConsumer.getUid();
- final double bgUsage = getBgUsage(uidConsumer) * scale;
+ final BatteryUsage bgUsage = new BatteryUsage(uidConsumer, bgPolicy)
+ .scale(scale);
int index = buf.indexOfKey(uid);
if (index < 0) {
buf.put(uid, bgUsage);
} else {
- buf.setValueAt(index, buf.valueAt(index) + bgUsage);
+ final BatteryUsage before = buf.valueAt(index);
+ before.add(bgUsage);
}
if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
Slog.i(TAG, "updateBatteryUsageStatsOnceInternal uid=" + uid
@@ -549,32 +564,19 @@
return stats;
}
- private static void copyUidBatteryUsage(SparseDoubleArray source, SparseDoubleArray dest) {
+ private static void copyUidBatteryUsage(SparseArray<? extends BatteryUsage> source,
+ SparseArray<ImmutableBatteryUsage> dest) {
dest.clear();
for (int i = source.size() - 1; i >= 0; i--) {
- dest.put(source.keyAt(i), source.valueAt(i));
+ dest.put(source.keyAt(i), new ImmutableBatteryUsage(source.valueAt(i)));
}
}
- private static void copyUidBatteryUsage(SparseDoubleArray source, SparseDoubleArray dest,
- double scale) {
+ private static void copyUidBatteryUsage(SparseArray<? extends BatteryUsage> source,
+ SparseArray<ImmutableBatteryUsage> dest, double scale) {
dest.clear();
for (int i = source.size() - 1; i >= 0; i--) {
- dest.put(source.keyAt(i), source.valueAt(i) * scale);
- }
- }
-
- private static double getBgUsage(final UidBatteryConsumer uidConsumer) {
- return getConsumedPowerNoThrow(uidConsumer, BATT_DIMEN_BG)
- + getConsumedPowerNoThrow(uidConsumer, BATT_DIMEN_FGS);
- }
-
- private static double getConsumedPowerNoThrow(final UidBatteryConsumer uidConsumer,
- final BatteryConsumer.Dimensions dimens) {
- try {
- return uidConsumer.getConsumedPower(dimens);
- } catch (IllegalArgumentException e) {
- return 0.0d;
+ dest.put(source.keyAt(i), new ImmutableBatteryUsage(source.valueAt(i), scale));
}
}
@@ -602,6 +604,19 @@
}
}
+ void setDebugUidPercentage(int[] uids, double[][] percentages) {
+ mDebugUidPercentages.clear();
+ for (int i = 0; i < uids.length; i++) {
+ mDebugUidPercentages.put(uids[i], new BatteryUsage().setPercentage(percentages[i]));
+ }
+ scheduleBgBatteryUsageStatsCheck();
+ }
+
+ void clearDebugUidPercentage() {
+ mDebugUidPercentages.clear();
+ scheduleBgBatteryUsageStatsCheck();
+ }
+
@VisibleForTesting
void reset() {
synchronized (mLock) {
@@ -620,7 +635,7 @@
pw.println("APP BATTERY STATE TRACKER:");
updateBatteryUsageStatsIfNecessary(mInjector.currentTimeMillis(), true);
synchronized (mLock) {
- final SparseDoubleArray uidConsumers = mUidBatteryUsageInWindow;
+ final SparseArray<ImmutableBatteryUsage> uidConsumers = mUidBatteryUsageInWindow;
pw.print(" " + prefix);
pw.print(" Last battery usage start=");
TimeUtils.dumpTime(pw, mLastUidBatteryUsageStartTs);
@@ -638,26 +653,285 @@
} else {
for (int i = 0, size = uidConsumers.size(); i < size; i++) {
final int uid = uidConsumers.keyAt(i);
- final double bgUsage = uidConsumers.valueAt(i);
- final double exemptedUsage = mAppRestrictionController
- .getUidBatteryExemptedUsageSince(uid, since, now);
- final double reportedUsage = Math.max(0.0d, bgUsage - exemptedUsage);
- pw.format("%s%s: [%s] %.3f mAh (%4.2f%%) | %.3f mAh (%4.2f%%) | "
- + "%.3f mAh (%4.2f%%) | %.3f mAh\n",
+ final BatteryUsage bgUsage = uidConsumers.valueAt(i)
+ .calcPercentage(uid, bgPolicy);
+ final BatteryUsage exemptedUsage = mAppRestrictionController
+ .getUidBatteryExemptedUsageSince(uid, since, now)
+ .calcPercentage(uid, bgPolicy);
+ final BatteryUsage reportedUsage = new BatteryUsage(bgUsage)
+ .subtract(exemptedUsage)
+ .calcPercentage(uid, bgPolicy);
+ pw.format("%s%s: [%s] %s (%s) | %s (%s) | %s (%s) | %s\n",
newPrefix, UserHandle.formatUid(uid),
PowerExemptionManager.reasonCodeToString(bgPolicy.shouldExemptUid(uid)),
- bgUsage , bgPolicy.getPercentage(uid, bgUsage),
- exemptedUsage, bgPolicy.getPercentage(-1, exemptedUsage),
- reportedUsage, bgPolicy.getPercentage(-1, reportedUsage),
- mUidBatteryUsage.get(uid, 0.0d));
+ bgUsage.toString(),
+ bgUsage.percentageToString(),
+ exemptedUsage.toString(),
+ exemptedUsage.percentageToString(),
+ reportedUsage.toString(),
+ reportedUsage.percentageToString(),
+ mUidBatteryUsage.get(uid, BATTERY_USAGE_NONE).toString());
}
}
}
super.dump(pw, prefix);
}
+ static class BatteryUsage {
+ static final int BATTERY_USAGE_INDEX_UNSPECIFIED = PROCESS_STATE_UNSPECIFIED;
+ static final int BATTERY_USAGE_INDEX_FOREGROUND = PROCESS_STATE_FOREGROUND;
+ static final int BATTERY_USAGE_INDEX_BACKGROUND = PROCESS_STATE_BACKGROUND;
+ static final int BATTERY_USAGE_INDEX_FOREGROUND_SERVICE = PROCESS_STATE_FOREGROUND_SERVICE;
+ static final int BATTERY_USAGE_COUNT = PROCESS_STATE_COUNT;
+
+ static final Dimensions[] BATT_DIMENS = new Dimensions[] {
+ new Dimensions(AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS,
+ PROCESS_STATE_UNSPECIFIED),
+ new Dimensions(AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS,
+ PROCESS_STATE_FOREGROUND),
+ new Dimensions(AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS,
+ PROCESS_STATE_BACKGROUND),
+ new Dimensions(AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS,
+ PROCESS_STATE_FOREGROUND_SERVICE),
+ };
+
+ @NonNull double[] mUsage;
+ @Nullable double[] mPercentage;
+
+ BatteryUsage() {
+ this(0.0d, 0.0d, 0.0d, 0.0d);
+ }
+
+ BatteryUsage(double unspecifiedUsage, double fgUsage, double bgUsage, double fgsUsage) {
+ mUsage = new double[] {unspecifiedUsage, fgUsage, bgUsage, fgsUsage};
+ }
+
+ BatteryUsage(@NonNull double[] usage) {
+ mUsage = usage;
+ }
+
+ BatteryUsage(@NonNull BatteryUsage other, double scale) {
+ this(other);
+ scaleInternal(scale);
+ }
+
+ BatteryUsage(@NonNull BatteryUsage other) {
+ mUsage = new double[other.mUsage.length];
+ setToInternal(other);
+ }
+
+ BatteryUsage(@NonNull UidBatteryConsumer consumer, @NonNull AppBatteryPolicy policy) {
+ final Dimensions[] dims = policy.mBatteryDimensions;
+ mUsage = new double[] {
+ getConsumedPowerNoThrow(consumer, dims[BATTERY_USAGE_INDEX_UNSPECIFIED]),
+ getConsumedPowerNoThrow(consumer, dims[BATTERY_USAGE_INDEX_FOREGROUND]),
+ getConsumedPowerNoThrow(consumer, dims[BATTERY_USAGE_INDEX_BACKGROUND]),
+ getConsumedPowerNoThrow(consumer, dims[BATTERY_USAGE_INDEX_FOREGROUND_SERVICE])
+ };
+ }
+
+ BatteryUsage setTo(@NonNull BatteryUsage other) {
+ return setToInternal(other);
+ }
+
+ private BatteryUsage setToInternal(@NonNull BatteryUsage other) {
+ for (int i = 0; i < other.mUsage.length; i++) {
+ mUsage[i] = other.mUsage[i];
+ }
+ return this;
+ }
+
+ BatteryUsage add(@NonNull BatteryUsage other) {
+ for (int i = 0; i < other.mUsage.length; i++) {
+ mUsage[i] += other.mUsage[i];
+ }
+ return this;
+ }
+
+ BatteryUsage subtract(@NonNull BatteryUsage other) {
+ for (int i = 0; i < other.mUsage.length; i++) {
+ mUsage[i] = Math.max(0.0d, mUsage[i] - other.mUsage[i]);
+ }
+ return this;
+ }
+
+ BatteryUsage scale(double scale) {
+ return scaleInternal(scale);
+ }
+
+ private BatteryUsage scaleInternal(double scale) {
+ for (int i = 0; i < mUsage.length; i++) {
+ mUsage[i] *= scale;
+ }
+ return this;
+ }
+
+ ImmutableBatteryUsage unmutate() {
+ return new ImmutableBatteryUsage(this);
+ }
+
+ BatteryUsage calcPercentage(int uid, @NonNull AppBatteryPolicy policy) {
+ if (mPercentage == null || mPercentage.length != mUsage.length) {
+ mPercentage = new double[mUsage.length];
+ }
+ policy.calcPercentage(uid, mUsage, mPercentage);
+ return this;
+ }
+
+ BatteryUsage setPercentage(@NonNull double[] percentage) {
+ mPercentage = percentage;
+ return this;
+ }
+
+ double[] getPercentage() {
+ return mPercentage;
+ }
+
+ String percentageToString() {
+ return formatBatteryUsagePercentage(mPercentage);
+ }
+
+ @Override
+ public String toString() {
+ return formatBatteryUsage(mUsage);
+ }
+
+ boolean isValid() {
+ for (int i = 0; i < mUsage.length; i++) {
+ if (mUsage[i] < 0.0d) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ boolean isEmpty() {
+ for (int i = 0; i < mUsage.length; i++) {
+ if (mUsage[i] > 0.0d) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null) {
+ return false;
+ }
+ final BatteryUsage otherUsage = (BatteryUsage) other;
+ for (int i = 0; i < mUsage.length; i++) {
+ if (Double.compare(mUsage[i], otherUsage.mUsage[i]) != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = 0;
+ for (int i = 0; i < mUsage.length; i++) {
+ hashCode = Double.hashCode(mUsage[i]) + hashCode * 31;
+ }
+ return hashCode;
+ }
+
+ private static String formatBatteryUsage(double[] usage) {
+ return String.format("%.3f %.3f %.3f %.3f mAh",
+ usage[BATTERY_USAGE_INDEX_UNSPECIFIED],
+ usage[BATTERY_USAGE_INDEX_FOREGROUND],
+ usage[BATTERY_USAGE_INDEX_BACKGROUND],
+ usage[BATTERY_USAGE_INDEX_FOREGROUND_SERVICE]);
+ }
+
+ static String formatBatteryUsagePercentage(double[] percentage) {
+ return String.format("%4.2f%% %4.2f%% %4.2f%% %4.2f%%",
+ percentage[BATTERY_USAGE_INDEX_UNSPECIFIED],
+ percentage[BATTERY_USAGE_INDEX_FOREGROUND],
+ percentage[BATTERY_USAGE_INDEX_BACKGROUND],
+ percentage[BATTERY_USAGE_INDEX_FOREGROUND_SERVICE]);
+ }
+
+ private static double getConsumedPowerNoThrow(final UidBatteryConsumer uidConsumer,
+ final Dimensions dimens) {
+ try {
+ return uidConsumer.getConsumedPower(dimens);
+ } catch (IllegalArgumentException e) {
+ return 0.0d;
+ }
+ }
+ }
+
+ static final class ImmutableBatteryUsage extends BatteryUsage {
+ ImmutableBatteryUsage() {
+ super();
+ }
+
+ ImmutableBatteryUsage(double unspecifiedUsage, double fgUsage, double bgUsage,
+ double fgsUsage) {
+ super(unspecifiedUsage, fgUsage, bgUsage, fgsUsage);
+ }
+
+ ImmutableBatteryUsage(@NonNull double[] usage) {
+ super(usage);
+ }
+
+ ImmutableBatteryUsage(@NonNull BatteryUsage other, double scale) {
+ super(other, scale);
+ }
+
+ ImmutableBatteryUsage(@NonNull BatteryUsage other) {
+ super(other);
+ }
+
+ ImmutableBatteryUsage(@NonNull UidBatteryConsumer consumer,
+ @NonNull AppBatteryPolicy policy) {
+ super(consumer, policy);
+ }
+
+ @Override
+ BatteryUsage setTo(@NonNull BatteryUsage other) {
+ throw new RuntimeException("Readonly");
+ }
+
+ @Override
+ BatteryUsage add(@NonNull BatteryUsage other) {
+ throw new RuntimeException("Readonly");
+ }
+
+ @Override
+ BatteryUsage subtract(@NonNull BatteryUsage other) {
+ throw new RuntimeException("Readonly");
+ }
+
+ @Override
+ BatteryUsage scale(double scale) {
+ throw new RuntimeException("Readonly");
+ }
+
+ @Override
+ BatteryUsage setPercentage(@NonNull double[] percentage) {
+ throw new RuntimeException("Readonly");
+ }
+
+ BatteryUsage mutate() {
+ return new BatteryUsage(this);
+ }
+ }
+
static final class AppBatteryPolicy extends BaseAppStatePolicy<AppBatteryTracker> {
/**
+ * The type of battery usage we could choose to apply the policy on.
+ *
+ * Must be in sync with android.os.BatteryConsumer.PROCESS_STATE_*.
+ */
+ static final int BATTERY_USAGE_TYPE_UNSPECIFIED = 1;
+ static final int BATTERY_USAGE_TYPE_FOREGROUND = 1 << 1;
+ static final int BATTERY_USAGE_TYPE_BACKGROUND = 1 << 2;
+ static final int BATTERY_USAGE_TYPE_FOREGROUND_SERVICE = 1 << 3;
+
+ /**
* Whether or not we should enable the monitoring on background current drains.
*/
static final String KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED =
@@ -729,58 +1003,90 @@
+ "current_drain_event_duration_based_threshold_enabled";
/**
- * Default value to {@link #mTrackerEnabled}.
+ * The types of battery drain we're checking on each app; if the sum of the battery drain
+ * exceeds the threshold, it'll be moved to restricted standby bucket; the type here
+ * must be one of, or combination of {@link #BATTERY_USAGE_TYPE_BACKGROUND} and
+ * {@link #BATTERY_USAGE_TYPE_FOREGROUND_SERVICE}.
*/
- static final boolean DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED = true;
+ static final String KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_types_to_restricted_bucket";
+
+ /**
+ * The types of battery drain we're checking on each app; if the sum of the battery drain
+ * exceeds the threshold, it'll be moved to background restricted level; the type here
+ * must be one of, or combination of {@link #BATTERY_USAGE_TYPE_BACKGROUND} and
+ * {@link #BATTERY_USAGE_TYPE_FOREGROUND_SERVICE}.
+ */
+ static final String KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_types_to_bg_restricted";
+
+ /**
+ * The power usage components we're monitoring.
+ */
+ static final String KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_power_components";
/**
* Default value to the {@link #INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD} of
* the {@link #mBgCurrentDrainRestrictedBucketThreshold}.
*/
- static final float DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_THRESHOLD =
- isLowRamDeviceStatic() ? 4.0f : 2.0f;
+ final float mDefaultBgCurrentDrainRestrictedBucket;
/**
* Default value to the {@link #INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD} of
* the {@link #mBgCurrentDrainBgRestrictedThreshold}.
*/
- static final float DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD =
- isLowRamDeviceStatic() ? 8.0f : 4.0f;
+ final float mDefaultBgCurrentDrainBgRestrictedThreshold;
/**
* Default value to {@link #mBgCurrentDrainWindowMs}.
*/
- static final long DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS = ONE_DAY;
+ final long mDefaultBgCurrentDrainWindowMs;
/**
* Default value to the {@link #INDEX_HIGH_CURRENT_DRAIN_THRESHOLD} of
* the {@link #mBgCurrentDrainRestrictedBucketThreshold}.
*/
- static final float DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_HIGH_THRESHOLD =
- isLowRamDeviceStatic() ? 60.0f : 30.0f;
+ final float mDefaultBgCurrentDrainRestrictedBucketHighThreshold;
/**
* Default value to the {@link #INDEX_HIGH_CURRENT_DRAIN_THRESHOLD} of
* the {@link #mBgCurrentDrainBgRestrictedThreshold}.
*/
- static final float DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD =
- isLowRamDeviceStatic() ? 60.0f : 30.0f;
+ final float mDefaultBgCurrentDrainBgRestrictedHighThreshold;
/**
* Default value to {@link #mBgCurrentDrainMediaPlaybackMinDuration}.
*/
- static final long DEFAULT_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION = 30 * ONE_MINUTE;
+ final long mDefaultBgCurrentDrainMediaPlaybackMinDuration;
/**
* Default value to {@link #mBgCurrentDrainLocationMinDuration}.
*/
- static final long DEFAULT_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION = 30 * ONE_MINUTE;
+ final long mDefaultBgCurrentDrainLocationMinDuration;
/**
* Default value to {@link #mBgCurrentDrainEventDurationBasedThresholdEnabled}.
*/
- static final boolean DEFAULT_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED =
- false;
+ final boolean mDefaultBgCurrentDrainEventDurationBasedThresholdEnabled;
+
+ /**
+ * Default value to {@link #mBgCurrentDrainRestrictedBucketTypes}.
+ */
+ final int mDefaultCurrentDrainTypesToRestrictedBucket;
+
+ /**
+ * Default value to {@link #mBgCurrentDrainBgRestrictedTypes}.
+ */
+ final int mDefaultBgCurrentDrainTypesToBgRestricted;
+
+ /**
+ * Default value to {@link #mBgCurrentDrainPowerComponents}.
+ **/
+ @BatteryConsumer.PowerComponent
+ static final int DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS = POWER_COMPONENT_ANY;
+
+ final int mDefaultBgCurrentDrainPowerComponent;
/**
* The index to {@link #mBgCurrentDrainRestrictedBucketThreshold}
@@ -793,36 +1099,28 @@
* @see #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET.
* @see #KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET.
*/
- volatile float[] mBgCurrentDrainRestrictedBucketThreshold = {
- DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_THRESHOLD,
- DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD,
- };
+ volatile float[] mBgCurrentDrainRestrictedBucketThreshold = new float[2];
/**
* @see #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED.
* @see #KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED.
*/
- volatile float[] mBgCurrentDrainBgRestrictedThreshold = {
- DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD,
- DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD,
- };
+ volatile float[] mBgCurrentDrainBgRestrictedThreshold = new float[2];
/**
* @see #KEY_BG_CURRENT_DRAIN_WINDOW.
*/
- volatile long mBgCurrentDrainWindowMs = DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS;
+ volatile long mBgCurrentDrainWindowMs;
/**
* @see #KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION.
*/
- volatile long mBgCurrentDrainMediaPlaybackMinDuration =
- DEFAULT_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION;
+ volatile long mBgCurrentDrainMediaPlaybackMinDuration;
/**
* @see #KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION.
*/
- volatile long mBgCurrentDrainLocationMinDuration =
- DEFAULT_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION;
+ volatile long mBgCurrentDrainLocationMinDuration;
/**
* @see #KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED.
@@ -830,6 +1128,24 @@
volatile boolean mBgCurrentDrainEventDurationBasedThresholdEnabled;
/**
+ * @see #KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET.
+ */
+ volatile int mBgCurrentDrainRestrictedBucketTypes;
+
+ /**
+ * @see #KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED.
+ */
+ volatile int mBgCurrentDrainBgRestrictedTypes;
+
+ /**
+ * @see #KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS.
+ */
+ @BatteryConsumer.PowerComponent
+ volatile int mBgCurrentDrainPowerComponents;
+
+ volatile Dimensions[] mBatteryDimensions;
+
+ /**
* The capacity of the battery when fully charged in mAh.
*/
private int mBatteryFullChargeMah;
@@ -851,8 +1167,62 @@
AppBatteryPolicy(@NonNull Injector injector, @NonNull AppBatteryTracker tracker) {
super(injector, tracker, KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED,
- DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED);
+ tracker.mContext.getResources()
+ .getBoolean(R.bool.config_bg_current_drain_monitor_enabled));
mLock = tracker.mLock;
+ final Resources resources = tracker.mContext.getResources();
+ float[] val = getFloatArray(resources.obtainTypedArray(
+ R.array.config_bg_current_drain_threshold_to_restricted_bucket));
+ mDefaultBgCurrentDrainRestrictedBucket =
+ isLowRamDeviceStatic() ? val[1] : val[0];
+ val = getFloatArray(resources.obtainTypedArray(
+ R.array.config_bg_current_drain_threshold_to_bg_restricted));
+ mDefaultBgCurrentDrainBgRestrictedThreshold =
+ isLowRamDeviceStatic() ? val[1] : val[0];
+ mDefaultBgCurrentDrainWindowMs = resources.getInteger(
+ R.integer.config_bg_current_drain_window);
+ val = getFloatArray(resources.obtainTypedArray(
+ R.array.config_bg_current_drain_high_threshold_to_restricted_bucket));
+ mDefaultBgCurrentDrainRestrictedBucketHighThreshold =
+ isLowRamDeviceStatic() ? val[1] : val[0];
+ val = getFloatArray(resources.obtainTypedArray(
+ R.array.config_bg_current_drain_high_threshold_to_bg_restricted));
+ mDefaultBgCurrentDrainBgRestrictedHighThreshold =
+ isLowRamDeviceStatic() ? val[1] : val[0];
+ mDefaultBgCurrentDrainMediaPlaybackMinDuration = resources.getInteger(
+ R.integer.config_bg_current_drain_media_playback_min_duration);
+ mDefaultBgCurrentDrainLocationMinDuration = resources.getInteger(
+ R.integer.config_bg_current_drain_location_min_duration);
+ mDefaultBgCurrentDrainEventDurationBasedThresholdEnabled = resources.getBoolean(
+ R.bool.config_bg_current_drain_event_duration_based_threshold_enabled);
+ mDefaultCurrentDrainTypesToRestrictedBucket = resources.getInteger(
+ R.integer.config_bg_current_drain_types_to_restricted_bucket);
+ mDefaultBgCurrentDrainTypesToBgRestricted = resources.getInteger(
+ R.integer.config_bg_current_drain_types_to_bg_restricted);
+ mDefaultBgCurrentDrainPowerComponent = resources.getInteger(
+ R.integer.config_bg_current_drain_power_components);
+ mBgCurrentDrainRestrictedBucketThreshold[0] =
+ mDefaultBgCurrentDrainRestrictedBucket;
+ mBgCurrentDrainRestrictedBucketThreshold[1] =
+ mDefaultBgCurrentDrainRestrictedBucketHighThreshold;
+ mBgCurrentDrainBgRestrictedThreshold[0] =
+ mDefaultBgCurrentDrainBgRestrictedThreshold;
+ mBgCurrentDrainBgRestrictedThreshold[1] =
+ mDefaultBgCurrentDrainBgRestrictedHighThreshold;
+ mBgCurrentDrainWindowMs = mDefaultBgCurrentDrainWindowMs;
+ mBgCurrentDrainMediaPlaybackMinDuration =
+ mDefaultBgCurrentDrainMediaPlaybackMinDuration;
+ mBgCurrentDrainLocationMinDuration = mDefaultBgCurrentDrainLocationMinDuration;
+ }
+
+ static float[] getFloatArray(TypedArray array) {
+ int length = array.length();
+ float[] floatArray = new float[length];
+ for (int i = 0; i < length; i++) {
+ floatArray[i] = array.getFloat(i, Float.NaN);
+ }
+ array.recycle();
+ return floatArray;
}
@Override
@@ -862,6 +1232,9 @@
case KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED:
case KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET:
case KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED:
+ case KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET:
+ case KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED:
+ case KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS:
updateCurrentDrainThreshold();
break;
case KEY_BG_CURRENT_DRAIN_WINDOW:
@@ -899,48 +1272,67 @@
mBgCurrentDrainRestrictedBucketThreshold[INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD] =
DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET,
- DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_THRESHOLD);
+ mDefaultBgCurrentDrainRestrictedBucket);
mBgCurrentDrainRestrictedBucketThreshold[INDEX_HIGH_CURRENT_DRAIN_THRESHOLD] =
DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET,
- DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_HIGH_THRESHOLD);
+ mDefaultBgCurrentDrainRestrictedBucketHighThreshold);
mBgCurrentDrainBgRestrictedThreshold[INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD] =
DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED,
- DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+ mDefaultBgCurrentDrainBgRestrictedThreshold);
mBgCurrentDrainBgRestrictedThreshold[INDEX_HIGH_CURRENT_DRAIN_THRESHOLD] =
DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED,
- DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD);
+ mDefaultBgCurrentDrainBgRestrictedHighThreshold);
+ mBgCurrentDrainRestrictedBucketTypes =
+ DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET,
+ mDefaultCurrentDrainTypesToRestrictedBucket);
+ mBgCurrentDrainBgRestrictedTypes =
+ DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED,
+ mDefaultBgCurrentDrainTypesToBgRestricted);
+ mBgCurrentDrainPowerComponents =
+ DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS,
+ mDefaultBgCurrentDrainPowerComponent);
+ if (mBgCurrentDrainPowerComponents == DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS) {
+ mBatteryDimensions = BatteryUsage.BATT_DIMENS;
+ } else {
+ mBatteryDimensions = new Dimensions[BatteryUsage.BATTERY_USAGE_COUNT];
+ for (int i = 0; i < BatteryUsage.BATTERY_USAGE_COUNT; i++) {
+ mBatteryDimensions[i] = new Dimensions(mBgCurrentDrainPowerComponents, i);
+ }
+ }
}
private void updateCurrentDrainWindow() {
mBgCurrentDrainWindowMs = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_WINDOW,
- mBgCurrentDrainWindowMs != DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS
- ? mBgCurrentDrainWindowMs : DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS);
+ mDefaultBgCurrentDrainWindowMs);
}
private void updateCurrentDrainMediaPlaybackMinDuration() {
mBgCurrentDrainMediaPlaybackMinDuration = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION,
- DEFAULT_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION);
+ mDefaultBgCurrentDrainMediaPlaybackMinDuration);
}
private void updateCurrentDrainLocationMinDuration() {
mBgCurrentDrainLocationMinDuration = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION,
- DEFAULT_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION);
+ mDefaultBgCurrentDrainLocationMinDuration);
}
private void updateCurrentDrainEventDurationBasedThresholdEnabled() {
mBgCurrentDrainEventDurationBasedThresholdEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED,
- DEFAULT_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED);
+ mDefaultBgCurrentDrainEventDurationBasedThresholdEnabled);
}
@Override
@@ -970,18 +1362,58 @@
}
}
- double getBgUsage(final UidBatteryConsumer uidConsumer) {
- return getConsumedPowerNoThrow(uidConsumer, BATT_DIMEN_BG)
- + getConsumedPowerNoThrow(uidConsumer, BATT_DIMEN_FGS);
+ double[] calcPercentage(final int uid, final double[] usage, double[] percentage) {
+ final BatteryUsage debugUsage = uid > 0 ? mTracker.mDebugUidPercentages.get(uid) : null;
+ final double[] forced = debugUsage != null ? debugUsage.getPercentage() : null;
+ for (int i = 0; i < usage.length; i++) {
+ percentage[i] = forced != null ? forced[i] : usage[i] / mBatteryFullChargeMah * 100;
+ }
+ return percentage;
}
- double getPercentage(final int uid, final double usage) {
- final double actualPercentage = usage / mBatteryFullChargeMah * 100;
- return DEBUG_BACKGROUND_BATTERY_TRACKER
- ? mTracker.mDebugUidPercentages.get(uid, actualPercentage) : actualPercentage;
+ private double sumPercentageOfTypes(double[] percentage, int types) {
+ double result = 0.0d;
+ for (int type = Integer.highestOneBit(types); type != 0;
+ type = Integer.highestOneBit(types)) {
+ final int index = Integer.numberOfTrailingZeros(type);
+ result += percentage[index];
+ types &= ~type;
+ }
+ return result;
}
- void handleUidBatteryUsage(final int uid, final double percentage) {
+ private static String batteryUsageTypesToString(int types) {
+ final StringBuilder sb = new StringBuilder("[");
+ boolean needDelimiter = false;
+ for (int type = Integer.highestOneBit(types); type != 0;
+ type = Integer.highestOneBit(types)) {
+ if (needDelimiter) {
+ sb.append('|');
+ }
+ needDelimiter = true;
+ switch (type) {
+ case BATTERY_USAGE_TYPE_UNSPECIFIED:
+ sb.append("UNSPECIFIED");
+ break;
+ case BATTERY_USAGE_TYPE_FOREGROUND:
+ sb.append("FOREGROUND");
+ break;
+ case BATTERY_USAGE_TYPE_BACKGROUND:
+ sb.append("BACKGROUND");
+ break;
+ case BATTERY_USAGE_TYPE_FOREGROUND_SERVICE:
+ sb.append("FOREGROUND_SERVICE");
+ break;
+ default:
+ return "[UNKNOWN(" + Integer.toHexString(types) + ")]";
+ }
+ types &= ~type;
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ void handleUidBatteryUsage(final int uid, final BatteryUsage usage) {
final @ReasonCode int reason = shouldExemptUid(uid);
if (reason != REASON_DENIED) {
if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
@@ -992,6 +1424,10 @@
}
boolean notifyController = false;
boolean excessive = false;
+ final double rbPercentage = sumPercentageOfTypes(usage.getPercentage(),
+ mBgCurrentDrainRestrictedBucketTypes);
+ final double brPercentage = sumPercentageOfTypes(usage.getPercentage(),
+ mBgCurrentDrainBgRestrictedTypes);
synchronized (mLock) {
final int curLevel = mTracker.mAppRestrictionController.getRestrictionLevel(uid);
if (curLevel >= RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
@@ -1003,7 +1439,7 @@
mBgCurrentDrainWindowMs);
final int index = mHighBgBatteryPackages.indexOfKey(uid);
if (index < 0) {
- if (percentage >= mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]) {
+ if (rbPercentage >= mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]) {
// New findings to us, track it and let the controller know.
final long[] ts = new long[TIME_STAMP_INDEX_LAST];
ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now;
@@ -1012,13 +1448,13 @@
}
} else {
final long[] ts = mHighBgBatteryPackages.valueAt(index);
- if (percentage < mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]) {
+ if (rbPercentage < mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]) {
// it's actually back to normal, but we don't untrack it until
// explicit user interactions.
notifyController = true;
} else {
excessive = true;
- if (percentage >= mBgCurrentDrainBgRestrictedThreshold[thresholdIndex]) {
+ if (brPercentage >= mBgCurrentDrainBgRestrictedThreshold[thresholdIndex]) {
// If we're in the restricted standby bucket but still seeing high
// current drains, tell the controller again.
if (curLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET
@@ -1037,7 +1473,7 @@
if (excessive) {
if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
Slog.i(TAG, "Excessive background current drain " + uid
- + String.format(" %.2f%%", percentage) + " over "
+ + usage + " (" + usage.percentageToString() + " ) over "
+ TimeUtils.formatDuration(mBgCurrentDrainWindowMs));
}
if (notifyController) {
@@ -1048,7 +1484,7 @@
} else {
if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
Slog.i(TAG, "Background current drain backs to normal " + uid
- + String.format(" %.2f%%", percentage) + " over "
+ + usage + " (" + usage.percentageToString() + " ) over "
+ TimeUtils.formatDuration(mBgCurrentDrainWindowMs));
}
// For now, we're not lifting the restrictions if the bg current drain backs to
@@ -1120,15 +1556,6 @@
}
}
- private double getConsumedPowerNoThrow(final UidBatteryConsumer uidConsumer,
- final BatteryConsumer.Dimensions dimens) {
- try {
- return uidConsumer.getConsumedPower(dimens);
- } catch (IllegalArgumentException e) {
- return 0.0d;
- }
- }
-
@VisibleForTesting
void reset() {
mHighBgBatteryPackages.clear();
@@ -1179,6 +1606,18 @@
pw.print(KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED);
pw.print('=');
pw.println(mBgCurrentDrainEventDurationBasedThresholdEnabled);
+ pw.print(prefix);
+ pw.print(KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET);
+ pw.print('=');
+ pw.println(batteryUsageTypesToString(mBgCurrentDrainRestrictedBucketTypes));
+ pw.print(prefix);
+ pw.print(KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED);
+ pw.print('=');
+ pw.println(batteryUsageTypesToString(mBgCurrentDrainBgRestrictedTypes));
+ pw.print(prefix);
+ pw.print(KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS);
+ pw.print('=');
+ pw.println(mBgCurrentDrainPowerComponents);
pw.print(prefix);
pw.println("Excessive current drain detected:");
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index 8cff13e..a3aa129 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -142,6 +142,7 @@
import com.android.server.AppStateTracker;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
+import com.android.server.am.AppBatteryTracker.ImmutableBatteryUsage;
import com.android.server.apphibernation.AppHibernationManagerInternal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.usage.AppStandbyInternal;
@@ -1076,7 +1077,7 @@
* @return The to-be-exempted battery usage of the given UID in the given duration; it could
* be considered as "exempted" due to various use cases, i.e. media playback.
*/
- double getUidBatteryExemptedUsageSince(int uid, long since, long now) {
+ ImmutableBatteryUsage getUidBatteryExemptedUsageSince(int uid, long since, long now) {
return mInjector.getAppBatteryExemptionTracker()
.getUidBatteryExemptedUsageSince(uid, since, now);
}
@@ -1084,7 +1085,7 @@
/**
* @return The total battery usage of the given UID since the system boots.
*/
- double getUidBatteryUsage(int uid) {
+ @NonNull ImmutableBatteryUsage getUidBatteryUsage(int uid) {
return mInjector.getUidBatteryUsageProvider().getUidBatteryUsage(uid);
}
@@ -1092,7 +1093,7 @@
/**
* @return The total battery usage of the given UID since the system boots.
*/
- double getUidBatteryUsage(int uid);
+ @NonNull ImmutableBatteryUsage getUidBatteryUsage(int uid);
}
void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 4539cc8..c4163e6 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1723,6 +1723,12 @@
return Zygote.MEMORY_TAG_LEVEL_TBI;
}
+ String defaultLevel = SystemProperties.get("persist.arm64.memtag.app_default");
+ if ("sync".equals(defaultLevel)) {
+ return Zygote.MEMORY_TAG_LEVEL_SYNC;
+ } else if ("async".equals(defaultLevel)) {
+ return Zygote.MEMORY_TAG_LEVEL_ASYNC;
+ }
return Zygote.MEMORY_TAG_LEVEL_NONE;
}
@@ -1918,7 +1924,7 @@
if ((app.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PROFILEABLE_BY_SHELL) != 0) {
runtimeFlags |= Zygote.PROFILE_FROM_SHELL;
}
- if ((app.info.privateFlagsExt & ApplicationInfo.PRIVATE_FLAG_EXT_PROFILEABLE) != 0) {
+ if (app.info.isProfileable()) {
runtimeFlags |= Zygote.PROFILEABLE;
}
if ("1".equals(SystemProperties.get("debug.checkjni"))) {
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index 256cffd..84d2b1f 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -787,7 +787,6 @@
}
private SparseArray<long[]> getUidProcStateStatsOverTime(long minTime) {
- final Parcel current = Parcel.obtain();
final ProcessStats stats = new ProcessStats();
long curTime;
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index cedc7db..d2c6c13 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2968,8 +2968,9 @@
int step;
// skip a2dp absolute volume control request when the device
- // is not an a2dp device
- if (!AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
+ // is neither an a2dp device nor BLE device
+ if ((!AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
+ && !AudioSystem.DEVICE_OUT_ALL_BLE_SET.contains(device))
&& (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) {
return;
}
@@ -3107,7 +3108,8 @@
}
if (device == AudioSystem.DEVICE_OUT_BLE_HEADSET
- && streamType == getBluetoothContextualVolumeStream()) {
+ && streamType == getBluetoothContextualVolumeStream()
+ && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
if (DEBUG_VOL) {
Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index="
+ newIndex + " stream=" + streamType);
@@ -3707,8 +3709,9 @@
int oldIndex;
// skip a2dp absolute volume control request when the device
- // is not an a2dp device
- if (!AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
+ // is neither an a2dp device nor BLE device
+ if ((!AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
+ && !AudioSystem.DEVICE_OUT_ALL_BLE_SET.contains(device))
&& (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) {
return;
}
@@ -3751,7 +3754,8 @@
}
if (device == AudioSystem.DEVICE_OUT_BLE_HEADSET
- && streamType == getBluetoothContextualVolumeStream()) {
+ && streamType == getBluetoothContextualVolumeStream()
+ && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
if (DEBUG_VOL) {
Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index="
+ index + " stream=" + streamType);
diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index 603f206..108e7bc 100644
--- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -25,7 +25,6 @@
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicy.WARNING_DISABLED;
import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES;
-import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
@@ -191,6 +190,7 @@
class MultipathTracker {
final Network network;
final String subscriberId;
+ private final int mSubId;
private long mQuota;
/** Current multipath budget. Nonzero iff we have budget and a UsageCallback is armed. */
@@ -204,9 +204,8 @@
this.network = network;
this.mNetworkCapabilities = new NetworkCapabilities(nc);
NetworkSpecifier specifier = nc.getNetworkSpecifier();
- int subId = INVALID_SUBSCRIPTION_ID;
if (specifier instanceof TelephonyNetworkSpecifier) {
- subId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
+ mSubId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
} else {
throw new IllegalStateException(String.format(
"Can't get subId from mobile network %s (%s)",
@@ -217,14 +216,14 @@
if (tele == null) {
throw new IllegalStateException(String.format("Missing TelephonyManager"));
}
- tele = tele.createForSubscriptionId(subId);
+ tele = tele.createForSubscriptionId(mSubId);
if (tele == null) {
throw new IllegalStateException(String.format(
- "Can't get TelephonyManager for subId %d", subId));
+ "Can't get TelephonyManager for subId %d", mSubId));
}
subscriberId = Objects.requireNonNull(tele.getSubscriberId(),
- "Null subscriber Id for subId " + subId);
+ "Null subscriber Id for subId " + mSubId);
mNetworkTemplate = new NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE)
.setSubscriberIds(Set.of(subscriberId))
.setMeteredness(NetworkStats.METERED_YES)
@@ -282,6 +281,7 @@
.setSubscriberId(subscriberId)
.setRoaming(!nc.hasCapability(NET_CAPABILITY_NOT_ROAMING))
.setMetered(!nc.hasCapability(NET_CAPABILITY_NOT_METERED))
+ .setSubId(mSubId)
.build();
}
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index 1894c0f..2c6257f 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -2421,10 +2421,10 @@
public static final int STATISTICS_FILE_ITEM = 101;
private void readStatsParcelLocked(File parcel) {
+ Parcel in = Parcel.obtain();
try {
final AtomicFile parcelFile = new AtomicFile(parcel);
byte[] data = parcelFile.readFully();
- Parcel in = Parcel.obtain();
in.unmarshall(data, 0, data.length);
in.setDataPosition(0);
int token;
@@ -2452,6 +2452,8 @@
}
} catch (IOException e) {
Slog.i(TAG, "No initial statistics");
+ } finally {
+ in.recycle();
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 7f1482e..f5001102 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -78,6 +78,7 @@
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
import android.hardware.display.WifiDisplayStatus;
+import android.hardware.graphics.common.DisplayDecorationSupport;
import android.hardware.input.InputManagerInternal;
import android.media.projection.IMediaProjection;
import android.media.projection.IMediaProjectionManager;
@@ -1860,10 +1861,10 @@
return mDisplayModeDirector.getModeSwitchingType();
}
- private boolean getDisplayDecorationSupportInternal(int displayId) {
+ private DisplayDecorationSupport getDisplayDecorationSupportInternal(int displayId) {
final IBinder displayToken = getDisplayToken(displayId);
if (null == displayToken) {
- return false;
+ return null;
}
return SurfaceControl.getDisplayDecorationSupport(displayToken);
}
@@ -3550,7 +3551,7 @@
}
@Override // Binder call
- public boolean getDisplayDecorationSupport(int displayId) {
+ public DisplayDecorationSupport getDisplayDecorationSupport(int displayId) {
final long token = Binder.clearCallingIdentity();
try {
return getDisplayDecorationSupportInternal(displayId);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d71e07a..418e91d 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -75,6 +75,7 @@
import com.android.server.policy.WindowManagerPolicy;
import java.io.PrintWriter;
+import java.util.Objects;
/**
* Controls the power state of the display.
@@ -236,42 +237,42 @@
// True if we should fade the screen while turning it off, false if we should play
// a stylish color fade animation instead.
- private boolean mColorFadeFadesConfig;
+ private final boolean mColorFadeFadesConfig;
// True if we need to fake a transition to off when coming out of a doze state.
// Some display hardware will blank itself when coming out of doze in order to hide
// artifacts. For these displays we fake a transition into OFF so that policy can appropriately
// blank itself and begin an appropriate power on animation.
- private boolean mDisplayBlanksAfterDozeConfig;
+ private final boolean mDisplayBlanksAfterDozeConfig;
// True if there are only buckets of brightness values when the display is in the doze state,
// rather than a full range of values. If this is true, then we'll avoid animating the screen
// brightness since it'd likely be multiple jarring brightness transitions instead of just one
// to reach the final state.
- private boolean mBrightnessBucketsInDozeConfig;
+ private final boolean mBrightnessBucketsInDozeConfig;
// The pending power request.
// Initially null until the first call to requestPowerState.
- // Guarded by mLock.
+ @GuardedBy("mLock")
private DisplayPowerRequest mPendingRequestLocked;
// True if a request has been made to wait for the proximity sensor to go negative.
- // Guarded by mLock.
+ @GuardedBy("mLock")
private boolean mPendingWaitForNegativeProximityLocked;
// True if the pending power request or wait for negative proximity flag
// has been changed since the last update occurred.
- // Guarded by mLock.
+ @GuardedBy("mLock")
private boolean mPendingRequestChangedLocked;
// Set to true when the important parts of the pending power request have been applied.
// The important parts are mainly the screen state. Brightness changes may occur
// concurrently.
- // Guarded by mLock.
+ @GuardedBy("mLock")
private boolean mDisplayReadyLocked;
// Set to true if a power state update is required.
- // Guarded by mLock.
+ @GuardedBy("mLock")
private boolean mPendingUpdatePowerStateLocked;
/* The following state must only be accessed by the handler thread. */
@@ -352,8 +353,8 @@
// information.
// At the time of this writing, this value is changed within updatePowerState() only, which is
// limited to the thread used by DisplayControllerHandler.
- private BrightnessReason mBrightnessReason = new BrightnessReason();
- private BrightnessReason mBrightnessReasonTemp = new BrightnessReason();
+ private final BrightnessReason mBrightnessReason = new BrightnessReason();
+ private final BrightnessReason mBrightnessReasonTemp = new BrightnessReason();
// Brightness animation ramp rates in brightness units per second
private float mBrightnessRampRateFastDecrease;
@@ -849,6 +850,7 @@
}
}
+ @GuardedBy("mLock")
private void sendUpdatePowerStateLocked() {
if (!mStopped && !mPendingUpdatePowerStateLocked) {
mPendingUpdatePowerStateLocked = true;
@@ -2318,6 +2320,7 @@
return mAutomaticBrightnessController.convertToNits(brightness);
}
+ @GuardedBy("mLock")
private void updatePendingProximityRequestsLocked() {
mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked;
mPendingWaitForNegativeProximityLocked = false;
@@ -2421,12 +2424,7 @@
pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
- mHandler.runWithScissors(new Runnable() {
- @Override
- public void run() {
- dumpLocal(pw);
- }
- }, 1000);
+ mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
}
private void dumpLocal(PrintWriter pw) {
@@ -2458,6 +2456,9 @@
pw.println(" mAppliedThrottling=" + mAppliedThrottling);
pw.println(" mAppliedScreenBrightnessOverride=" + mAppliedScreenBrightnessOverride);
pw.println(" mAppliedTemporaryBrightness=" + mAppliedTemporaryBrightness);
+ pw.println(" mAppliedTemporaryAutoBrightnessAdjustment="
+ + mAppliedTemporaryAutoBrightnessAdjustment);
+ pw.println(" mAppliedBrightnessBoost=" + mAppliedBrightnessBoost);
pw.println(" mDozing=" + mDozing);
pw.println(" mSkipRampState=" + skipRampStateToString(mSkipRampState));
pw.println(" mScreenOnBlockStartRealTime=" + mScreenOnBlockStartRealTime);
@@ -2465,21 +2466,21 @@
pw.println(" mPendingScreenOnUnblocker=" + mPendingScreenOnUnblocker);
pw.println(" mPendingScreenOffUnblocker=" + mPendingScreenOffUnblocker);
pw.println(" mPendingScreenOff=" + mPendingScreenOff);
- pw.println(" mReportedToPolicy=" +
- reportedToPolicyToString(mReportedScreenStateToPolicy));
+ pw.println(" mReportedToPolicy="
+ + reportedToPolicyToString(mReportedScreenStateToPolicy));
if (mScreenBrightnessRampAnimator != null) {
- pw.println(" mScreenBrightnessRampAnimator.isAnimating()=" +
- mScreenBrightnessRampAnimator.isAnimating());
+ pw.println(" mScreenBrightnessRampAnimator.isAnimating()="
+ + mScreenBrightnessRampAnimator.isAnimating());
}
if (mColorFadeOnAnimator != null) {
- pw.println(" mColorFadeOnAnimator.isStarted()=" +
- mColorFadeOnAnimator.isStarted());
+ pw.println(" mColorFadeOnAnimator.isStarted()="
+ + mColorFadeOnAnimator.isStarted());
}
if (mColorFadeOffAnimator != null) {
- pw.println(" mColorFadeOffAnimator.isStarted()=" +
- mColorFadeOffAnimator.isStarted());
+ pw.println(" mColorFadeOffAnimator.isStarted()="
+ + mColorFadeOffAnimator.isStarted());
}
if (mPowerState != null) {
@@ -2605,7 +2606,7 @@
}
}
- private final void logHbmBrightnessStats(float brightness, int displayStatsId) {
+ private void logHbmBrightnessStats(float brightness, int displayStatsId) {
synchronized (mHandler) {
FrameworkStatsLog.write(
FrameworkStatsLog.DISPLAY_HBM_BRIGHTNESS_CHANGED, displayStatsId, brightness);
@@ -2824,7 +2825,7 @@
@Override
public boolean equals(Object obj) {
- if (obj == null || !(obj instanceof BrightnessReason)) {
+ if (!(obj instanceof BrightnessReason)) {
return false;
}
BrightnessReason other = (BrightnessReason) obj;
@@ -2832,6 +2833,11 @@
}
@Override
+ public int hashCode() {
+ return Objects.hash(reason, modifier);
+ }
+
+ @Override
public String toString() {
return toString(0);
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index c02e725..d233c5e 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -182,8 +182,11 @@
private final long mPhysicalDisplayId;
private final SparseArray<DisplayModeRecord> mSupportedModes = new SparseArray<>();
private final ArrayList<Integer> mSupportedColorModes = new ArrayList<>();
+ private final DisplayModeDirector.DesiredDisplayModeSpecs mDisplayModeSpecs =
+ new DisplayModeDirector.DesiredDisplayModeSpecs();
private final boolean mIsDefaultDisplay;
private final BacklightAdapter mBacklightAdapter;
+ private final SidekickInternal mSidekickInternal;
private DisplayDeviceInfo mInfo;
private boolean mHavePendingChanges;
@@ -200,8 +203,6 @@
private int mActiveDisplayModeAtStartId = INVALID_MODE_ID;
private Display.Mode mUserPreferredMode;
private int mActiveModeId = INVALID_MODE_ID;
- private DisplayModeDirector.DesiredDisplayModeSpecs mDisplayModeSpecs =
- new DisplayModeDirector.DesiredDisplayModeSpecs();
private boolean mDisplayModeSpecsInvalid;
private int mActiveColorMode;
private Display.HdrCapabilities mHdrCapabilities;
@@ -210,13 +211,11 @@
private boolean mAllmRequested;
private boolean mGameContentTypeRequested;
private boolean mSidekickActive;
- private SidekickInternal mSidekickInternal;
private SurfaceControl.StaticDisplayInfo mStaticDisplayInfo;
// The supported display modes according to SurfaceFlinger
private SurfaceControl.DisplayMode[] mSfDisplayModes;
// The active display mode in SurfaceFlinger
private SurfaceControl.DisplayMode mActiveSfDisplayMode;
- private DisplayDeviceConfig mDisplayDeviceConfig;
private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides =
new DisplayEventReceiver.FrameRateOverride[0];
@@ -233,7 +232,6 @@
mSidekickInternal = LocalServices.getService(SidekickInternal.class);
mBacklightAdapter = new BacklightAdapter(displayToken, isDefaultDisplay,
mSurfaceControlProxy);
- mDisplayDeviceConfig = null;
mActiveDisplayModeAtStartId = dynamicInfo.activeDisplayModeId;
}
@@ -459,9 +457,6 @@
final Context context = getOverlayContext();
mDisplayDeviceConfig = DisplayDeviceConfig.create(context, mPhysicalDisplayId,
mIsDefaultDisplay);
- if (mDisplayDeviceConfig == null) {
- return;
- }
// Load brightness HWC quirk
mBacklightAdapter.setForceSurfaceControl(mDisplayDeviceConfig.hasQuirk(
@@ -1083,8 +1078,8 @@
pw.println("mGameContentTypeRequested=" + mGameContentTypeRequested);
pw.println("mStaticDisplayInfo=" + mStaticDisplayInfo);
pw.println("mSfDisplayModes=");
- for (int i = 0; i < mSfDisplayModes.length; i++) {
- pw.println(" " + mSfDisplayModes[i]);
+ for (SurfaceControl.DisplayMode sfDisplayMode : mSfDisplayModes) {
+ pw.println(" " + sfDisplayMode);
}
pw.println("mActiveSfDisplayMode=" + mActiveSfDisplayMode);
pw.println("mSupportedModes=");
@@ -1238,6 +1233,8 @@
}
public static class Injector {
+ // Native callback.
+ @SuppressWarnings("unused")
private ProxyDisplayEventReceiver mReceiver;
public void setDisplayEventListenerLocked(Looper looper, DisplayEventListener listener) {
mReceiver = new ProxyDisplayEventReceiver(looper, listener);
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 93c73be..6f5729f 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -825,7 +825,6 @@
*
* @param device The device to associate with the LogicalDisplay.
* @param displayId The display ID to give the new display. If invalid, a new ID is assigned.
- * @param isDefault Indicates if we are creating the default display.
* @return The new logical display if created, null otherwise.
*/
private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) {
diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java
index d8672fc..2567e43 100644
--- a/services/core/java/com/android/server/display/RampAnimator.java
+++ b/services/core/java/com/android/server/display/RampAnimator.java
@@ -55,7 +55,7 @@
* If this is the first time the property is being set or if the rate is 0,
* the value jumps directly to the target.
*
- * @param target The target value.
+ * @param targetLinear The target value.
* @param rate The convergence rate in units per second, or 0 to set the value immediately.
* @return True if the target differs from the previous target.
*/
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index d48ccd5..025ccd1 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -45,7 +45,6 @@
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.content.pm.SigningInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.net.Uri;
@@ -70,6 +69,7 @@
import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.pkg.PackageUserStateInternal;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import java.io.ByteArrayInputStream;
import java.io.File;
@@ -307,6 +307,7 @@
}
List<String> appCertificates = getCertificateFingerprint(packageInfo);
+ List<String> appCertificateLineage = getCertificateLineage(packageInfo);
List<String> installerCertificates =
getInstallerCertificateFingerprint(installerPackageName);
@@ -314,6 +315,7 @@
builder.setPackageName(getPackageNameNormalized(packageName));
builder.setAppCertificates(appCertificates);
+ builder.setAppCertificateLineage(appCertificateLineage);
builder.setVersionCode(intent.getLongExtra(EXTRA_LONG_VERSION_CODE, -1));
builder.setInstallerName(getPackageNameNormalized(installerPackageName));
builder.setInstallerCertificates(installerCertificates);
@@ -460,6 +462,14 @@
return certificateFingerprints;
}
+ private List<String> getCertificateLineage(@NonNull PackageInfo packageInfo) {
+ ArrayList<String> certificateLineage = new ArrayList();
+ for (Signature signature : getSignatureLineage(packageInfo)) {
+ certificateLineage.add(getFingerprint(signature));
+ }
+ return certificateLineage;
+ }
+
/** Get the allowed installers and their associated certificate hashes from <meta-data> tag. */
private Map<String, String> getAllowedInstallers(@NonNull PackageInfo packageInfo) {
Map<String, String> packageCertMap = new HashMap<>();
@@ -541,6 +551,38 @@
return signingInfo.getApkContentsSigners();
}
+ private static Signature[] getSignatureLineage(@NonNull PackageInfo packageInfo) {
+ // Obtain the signing info of the package.
+ SigningInfo signingInfo = packageInfo.signingInfo;
+ if (signingInfo == null) {
+ throw new IllegalArgumentException(
+ "Package signature not found in " + packageInfo);
+ }
+
+ // Obtain the active signatures of the package.
+ Signature[] signatureLineage = getSignatures(packageInfo);
+
+ // Obtain the past signatures of the package.
+ if (!signingInfo.hasMultipleSigners() && signingInfo.hasPastSigningCertificates()) {
+ Signature[] pastSignatures = signingInfo.getSigningCertificateHistory();
+
+ // Merge the signatures and return.
+ Signature[] allSignatures =
+ new Signature[signatureLineage.length + pastSignatures.length];
+ int i;
+ for (i = 0; i < signatureLineage.length; i++) {
+ allSignatures[i] = signatureLineage[i];
+ }
+ for (int j = 0; j < pastSignatures.length; j++) {
+ allSignatures[i] = pastSignatures[j];
+ i++;
+ }
+ signatureLineage = allSignatures;
+ }
+
+ return signatureLineage;
+ }
+
private static String getFingerprint(Signature cert) {
InputStream input = new ByteArrayInputStream(cert.toByteArray());
diff --git a/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java b/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java
new file mode 100644
index 0000000..6b442a6
--- /dev/null
+++ b/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java
@@ -0,0 +1,130 @@
+/*
+ * 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.logcat;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.ServiceManager;
+import android.os.logcat.ILogcatManagerService;
+import android.util.Slog;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+
+
+/**
+ * This dialog is shown to the user before an activity in a harmful app is launched.
+ *
+ * See {@code PackageManager.setLogcatAppInfo} for more info.
+ */
+public class LogAccessConfirmationActivity extends AlertActivity implements
+ DialogInterface.OnClickListener {
+ private static final String TAG = LogAccessConfirmationActivity.class.getSimpleName();
+
+ private String mPackageName;
+ private IntentSender mTarget;
+ private final ILogcatManagerService mLogcatManagerService =
+ ILogcatManagerService.Stub.asInterface(ServiceManager.getService("logcat"));
+
+ private int mUid;
+ private int mGid;
+ private int mPid;
+ private int mFd;
+
+ private static final String EXTRA_UID = "uid";
+ private static final String EXTRA_GID = "gid";
+ private static final String EXTRA_PID = "pid";
+ private static final String EXTRA_FD = "fd";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Intent intent = getIntent();
+ mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ mUid = intent.getIntExtra("uid", 0);
+ mGid = intent.getIntExtra("gid", 0);
+ mPid = intent.getIntExtra("pid", 0);
+ mFd = intent.getIntExtra("fd", 0);
+
+ final AlertController.AlertParams p = mAlertParams;
+ p.mTitle = getString(R.string.log_access_confirmation_title);
+ p.mView = createView();
+
+ p.mPositiveButtonText = getString(R.string.log_access_confirmation_allow);
+ p.mPositiveButtonListener = this;
+ p.mNegativeButtonText = getString(R.string.log_access_confirmation_deny);
+ p.mNegativeButtonListener = this;
+
+ mAlert.installContent(mAlertParams);
+ }
+
+ private View createView() {
+ final View view = getLayoutInflater().inflate(R.layout.harmful_app_warning_dialog,
+ null /*root*/);
+ ((TextView) view.findViewById(R.id.app_name_text))
+ .setText(mPackageName);
+ ((TextView) view.findViewById(R.id.message))
+ .setText(getIntent().getExtras().getString("body"));
+ return view;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which) {
+ case DialogInterface.BUTTON_POSITIVE:
+ try {
+ mLogcatManagerService.approve(mUid, mGid, mPid, mFd);
+ } catch (Throwable t) {
+ Slog.e(TAG, "Could not start the LogcatManagerService.", t);
+ }
+ finish();
+ break;
+ case DialogInterface.BUTTON_NEGATIVE:
+ try {
+ mLogcatManagerService.decline(mUid, mGid, mPid, mFd);
+ } catch (Throwable t) {
+ Slog.e(TAG, "Could not start the LogcatManagerService.", t);
+ }
+ finish();
+ break;
+ }
+ }
+
+ /**
+ * Create the Intent for a LogAccessConfirmationActivity.
+ */
+ public static Intent createIntent(Context context, String targetPackageName,
+ IntentSender target, int uid, int gid, int pid, int fd) {
+ final Intent intent = new Intent();
+ intent.setClass(context, LogAccessConfirmationActivity.class);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, targetPackageName);
+ intent.putExtra(EXTRA_UID, uid);
+ intent.putExtra(EXTRA_GID, gid);
+ intent.putExtra(EXTRA_PID, pid);
+ intent.putExtra(EXTRA_FD, fd);
+
+ return intent;
+ }
+
+}
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index ff6372ae..34614d5 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -16,20 +16,36 @@
package com.android.server.logcat;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.app.ActivityManagerInternal;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.os.ILogd;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.os.logcat.ILogcatManagerService;
import android.util.Slog;
+import com.android.internal.R;
+import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
+import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
- * Service responsible for manage the access to Logcat.
+ * Service responsible for managing the access to Logcat.
*/
public final class LogcatManagerService extends SystemService {
@@ -38,6 +54,43 @@
private final BinderService mBinderService;
private final ExecutorService mThreadExecutor;
private ILogd mLogdService;
+ private NotificationManager mNotificationManager;
+ private @NonNull ActivityManager mActivityManager;
+ private ActivityManagerInternal mActivityManagerInternal;
+ private static final int MAX_UID_IMPORTANCE_COUNT_LISTENER = 2;
+ private static int sUidImportanceListenerCount = 0;
+ private static final int AID_SHELL_UID = 2000;
+
+ // TODO This allowlist is just a temporary workaround for the tests:
+ // FrameworksServicesTests
+ // PlatformRuleTests
+ // After adapting the test suites, the allowlist will be removed in
+ // the upcoming bug fix patches.
+ private static final String[] ALLOWABLE_TESTING_PACKAGES = {
+ "android.platform.test.rule.tests",
+ "com.android.frameworks.servicestests"
+ };
+
+ // TODO Same as the above ALLOWABLE_TESTING_PACKAGES.
+ private boolean isAllowableTestingPackage(int uid) {
+ PackageManager pm = mContext.getPackageManager();
+
+ String[] packageNames = pm.getPackagesForUid(uid);
+
+ if (ArrayUtils.isEmpty(packageNames)) {
+ return false;
+ }
+
+ for (String name : packageNames) {
+ Slog.e(TAG, "isAllowableTestingPackage: " + name);
+
+ if (Arrays.asList(ALLOWABLE_TESTING_PACKAGES).contains(name)) {
+ return true;
+ }
+ }
+
+ return false;
+ };
private final class BinderService extends ILogcatManagerService.Stub {
@Override
@@ -51,6 +104,197 @@
// the logd data access is finished.
mThreadExecutor.execute(new LogdMonitor(uid, gid, pid, fd, false));
}
+
+ @Override
+ public void approve(int uid, int gid, int pid, int fd) {
+ try {
+ getLogdService().approve(uid, gid, pid, fd);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void decline(int uid, int gid, int pid, int fd) {
+ try {
+ getLogdService().decline(uid, gid, pid, fd);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private ILogd getLogdService() {
+ synchronized (LogcatManagerService.this) {
+ if (mLogdService == null) {
+ LogcatManagerService.this.addLogdService();
+ }
+ return mLogdService;
+ }
+ }
+
+ private String getBodyString(Context context, String callingPackage, int uid) {
+ PackageManager pm = context.getPackageManager();
+ try {
+ return context.getString(
+ com.android.internal.R.string.log_access_confirmation_body,
+ pm.getApplicationInfoAsUser(callingPackage, PackageManager.MATCH_DIRECT_BOOT_AUTO,
+ UserHandle.getUserId(uid)).loadLabel(pm));
+ } catch (NameNotFoundException e) {
+ // App name is unknown.
+ return null;
+ }
+ }
+
+ private void sendNotification(int notificationId, String clientInfo, int uid, int gid, int pid,
+ int fd) {
+
+ final ActivityManagerInternal activityManagerInternal =
+ LocalServices.getService(ActivityManagerInternal.class);
+
+ PackageManager pm = mContext.getPackageManager();
+ String packageName = activityManagerInternal.getPackageNameByPid(pid);
+ if (packageName != null) {
+ String notificationBody = getBodyString(mContext, packageName, uid);
+
+ final Intent mIntent = LogAccessConfirmationActivity.createIntent(mContext,
+ packageName, null, uid, gid, pid, fd);
+
+ if (notificationBody == null) {
+ // Decline the logd access if the nofitication body is unknown
+ Slog.e(TAG, "Unknown notification body, declining the logd access");
+ declineLogdAccess(uid, gid, pid, fd);
+ return;
+ }
+
+ // TODO Next version will replace notification with dialogue
+ // per UX guidance.
+ generateNotificationWithBodyContent(notificationId, clientInfo, notificationBody,
+ mIntent);
+ return;
+
+ }
+
+ String[] packageNames = pm.getPackagesForUid(uid);
+
+ if (ArrayUtils.isEmpty(packageNames)) {
+ // Decline the logd access if the app name is unknown
+ Slog.e(TAG, "Unknown calling package name, declining the logd access");
+ declineLogdAccess(uid, gid, pid, fd);
+ return;
+ }
+
+ String firstPackageName = packageNames[0];
+
+ if (firstPackageName == null || firstPackageName.length() == 0) {
+ // Decline the logd access if the package name from uid is unknown
+ Slog.e(TAG, "Unknown calling package name, declining the logd access");
+ declineLogdAccess(uid, gid, pid, fd);
+ return;
+ }
+
+ String notificationBody = getBodyString(mContext, firstPackageName, uid);
+
+ final Intent mIntent = LogAccessConfirmationActivity.createIntent(mContext,
+ firstPackageName, null, uid, gid, pid, fd);
+
+ if (notificationBody == null) {
+ Slog.e(TAG, "Unknown notification body, declining the logd access");
+ declineLogdAccess(uid, gid, pid, fd);
+ return;
+ }
+
+ // TODO Next version will replace notification with dialogue
+ // per UX guidance.
+ generateNotificationWithBodyContent(notificationId, clientInfo,
+ notificationBody, mIntent);
+ }
+
+ private void declineLogdAccess(int uid, int gid, int pid, int fd) {
+ try {
+ getLogdService().decline(uid, gid, pid, fd);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Fails to call remote functions ", ex);
+ }
+ }
+
+ private void generateNotificationWithBodyContent(int notificationId, String clientInfo,
+ String notificationBody, Intent intent) {
+ final Notification.Builder notificationBuilder = new Notification.Builder(
+ mContext,
+ SystemNotificationChannels.ACCESSIBILITY_SECURITY_POLICY);
+ intent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.setIdentifier(String.valueOf(notificationId) + clientInfo);
+ intent.putExtra("body", notificationBody);
+
+ notificationBuilder
+ .setSmallIcon(R.drawable.ic_info)
+ .setContentTitle(
+ mContext.getString(R.string.log_access_confirmation_title))
+ .setContentText(notificationBody)
+ .setContentIntent(
+ PendingIntent.getActivity(mContext, 0, intent,
+ PendingIntent.FLAG_IMMUTABLE))
+ .setTicker(mContext.getString(R.string.log_access_confirmation_title))
+ .setOnlyAlertOnce(true)
+ .setAutoCancel(true);
+ mNotificationManager.notify(notificationId, notificationBuilder.build());
+ }
+
+ /**
+ * A class which watches an uid for background access and notifies the logdMonitor when
+ * the package status becomes foreground (importance change)
+ */
+ private class UidImportanceListener implements ActivityManager.OnUidImportanceListener {
+ private final int mExpectedUid;
+ private final int mExpectedGid;
+ private final int mExpectedPid;
+ private final int mExpectedFd;
+ private int mExpectedImportance;
+ private int mCurrentImportance = RunningAppProcessInfo.IMPORTANCE_GONE;
+
+ UidImportanceListener(int uid, int gid, int pid, int fd, int importance) {
+ mExpectedUid = uid;
+ mExpectedGid = gid;
+ mExpectedPid = pid;
+ mExpectedFd = fd;
+ mExpectedImportance = importance;
+ }
+
+ @Override
+ public void onUidImportance(int uid, int importance) {
+ if (uid == mExpectedUid) {
+ mCurrentImportance = importance;
+
+ /**
+ * 1) If the process status changes to foreground, send a notification
+ * for user consent.
+ * 2) If the process status remains background, we decline logd access request.
+ **/
+ if (importance <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE) {
+ String clientInfo = getClientInfo(uid, mExpectedGid, mExpectedPid, mExpectedFd);
+ sendNotification(0, clientInfo, uid, mExpectedGid, mExpectedPid,
+ mExpectedFd);
+ mActivityManager.removeOnUidImportanceListener(this);
+
+ synchronized (LogcatManagerService.this) {
+ sUidImportanceListenerCount--;
+ }
+ } else {
+ try {
+ getLogdService().decline(uid, mExpectedGid, mExpectedPid, mExpectedFd);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Fails to call remote functions ", ex);
+ }
+ }
+ }
+ }
+ }
+
+ private static String getClientInfo(int uid, int gid, int pid, int fd) {
+ return "UID=" + Integer.toString(uid) + " GID=" + Integer.toString(gid) + " PID="
+ + Integer.toString(pid) + " FD=" + Integer.toString(fd);
}
private class LogdMonitor implements Runnable {
@@ -74,9 +318,7 @@
}
/**
- * The current version grant the permission by default.
- * And track the logd access.
- * The next version will generate a prompt for users.
+ * LogdMonitor generates a prompt for users.
* The users decide whether the logd access is allowed.
*/
@Override
@@ -86,11 +328,14 @@
}
if (mStart) {
+
+ // TODO Temporarily approve all the requests to unblock testing failures.
try {
- mLogdService.approve(mUid, mGid, mPid, mFd);
- } catch (RemoteException ex) {
- Slog.e(TAG, "Fails to call remote functions ", ex);
+ getLogdService().approve(mUid, mGid, mPid, mFd);
+ } catch (RemoteException e) {
+ e.printStackTrace();
}
+ return;
}
}
}
@@ -100,6 +345,8 @@
mContext = context;
mBinderService = new BinderService();
mThreadExecutor = Executors.newCachedThreadPool();
+ mActivityManager = context.getSystemService(ActivityManager.class);
+ mNotificationManager = mContext.getSystemService(NotificationManager.class);
}
@Override
@@ -114,5 +361,4 @@
private void addLogdService() {
mLogdService = ILogd.Stub.asInterface(ServiceManager.getService("logd"));
}
-
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index b66c466..33ac6cd 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -16,14 +16,16 @@
package com.android.server.net;
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
-import static android.net.INetd.FIREWALL_CHAIN_DOZABLE;
-import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE;
-import static android.net.INetd.FIREWALL_CHAIN_RESTRICTED;
-import static android.net.INetd.FIREWALL_CHAIN_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
import static android.net.INetd.FIREWALL_RULE_ALLOW;
import static android.net.INetd.FIREWALL_RULE_DENY;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
@@ -328,6 +330,8 @@
return FIREWALL_CHAIN_NAME_POWERSAVE;
case FIREWALL_CHAIN_RESTRICTED:
return FIREWALL_CHAIN_NAME_RESTRICTED;
+ case FIREWALL_CHAIN_LOW_POWER_STANDBY:
+ return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
default:
return String.valueOf(chain);
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
index 8ef42ff..3cb5878 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
@@ -91,4 +91,10 @@
*/
public abstract void setMeteredRestrictedPackagesAsync(
Set<String> packageNames, int userId);
+
+ /** Informs that Low Power Standby has become active */
+ public abstract void setLowPowerStandbyActive(boolean active);
+
+ /** Informs that the Low Power Standby allowlist has changed */
+ public abstract void setLowPowerStandbyAllowlist(int[] uids);
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index bb22902..8d05415 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -46,22 +46,23 @@
import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.BLOCKED_REASON_RESTRICTED_MODE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.INetd.FIREWALL_CHAIN_DOZABLE;
-import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE;
-import static android.net.INetd.FIREWALL_CHAIN_RESTRICTED;
-import static android.net.INetd.FIREWALL_CHAIN_STANDBY;
import static android.net.INetd.FIREWALL_RULE_ALLOW;
import static android.net.INetd.FIREWALL_RULE_DENY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicy.SNOOZE_NEVER;
import static android.net.NetworkPolicy.WARNING_DISABLED;
@@ -70,11 +71,13 @@
import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_SYSTEM;
import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_USER_EXEMPTED;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_FOREGROUND;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_ALLOWLIST;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_TOP;
import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
@@ -88,6 +91,7 @@
import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED;
import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED;
import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
+import static android.net.NetworkPolicyManager.isProcStateAllowedWhileInLowPowerStandby;
import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground;
import static android.net.NetworkPolicyManager.resolveNetworkId;
import static android.net.NetworkPolicyManager.uidPoliciesToString;
@@ -171,11 +175,9 @@
import android.net.NetworkPolicyManager;
import android.net.NetworkPolicyManager.UidState;
import android.net.NetworkRequest;
-import android.net.NetworkSpecifier;
import android.net.NetworkStack;
import android.net.NetworkStateSnapshot;
import android.net.NetworkTemplate;
-import android.net.TelephonyNetworkSpecifier;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.BestClock;
@@ -478,6 +480,8 @@
volatile boolean mRestrictBackgroundChangedInBsm;
@GuardedBy("mUidRulesFirstLock")
volatile boolean mRestrictedNetworkingMode;
+ @GuardedBy("mUidRulesFirstLock")
+ volatile boolean mLowPowerStandbyActive;
private final boolean mSuppressDefaultPolicy;
@@ -517,6 +521,8 @@
final SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray();
@GuardedBy("mUidRulesFirstLock")
final SparseIntArray mUidFirewallRestrictedModeRules = new SparseIntArray();
+ @GuardedBy("mUidRulesFirstLock")
+ final SparseIntArray mUidFirewallLowPowerStandbyModeRules = new SparseIntArray();
/** Set of states for the child firewall chains. True if the chain is active. */
@GuardedBy("mUidRulesFirstLock")
@@ -545,6 +551,9 @@
@GuardedBy("mUidRulesFirstLock")
private final SparseBooleanArray mPowerSaveTempWhitelistAppIds = new SparseBooleanArray();
+ @GuardedBy("mUidRulesFirstLock")
+ private final SparseBooleanArray mLowPowerStandbyAllowlistUids = new SparseBooleanArray();
+
/**
* UIDs that have been allowlisted temporarily to be able to have network access despite being
* idle. Other power saving restrictions still apply.
@@ -1508,7 +1517,8 @@
.setType(TYPE_MOBILE)
.setSubscriberId(subscriberId)
.setMetered(true)
- .setDefaultNetwork(true).build();
+ .setDefaultNetwork(true)
+ .setSubId(subId).build();
if (template.matches(probeIdent)) {
return subId;
}
@@ -1745,7 +1755,8 @@
.setType(TYPE_MOBILE)
.setSubscriberId(subscriberId)
.setMetered(true)
- .setDefaultNetwork(true).build();
+ .setDefaultNetwork(true)
+ .setSubId(subId).build();
for (int i = mNetworkPolicy.size() - 1; i >= 0; i--) {
final NetworkTemplate template = mNetworkPolicy.keyAt(i);
if (template.matches(probeIdent)) {
@@ -1977,7 +1988,8 @@
.setType(TYPE_MOBILE)
.setSubscriberId(subscriberId)
.setMetered(true)
- .setDefaultNetwork(true).build();
+ .setDefaultNetwork(true)
+ .setSubId(subId).build();
// Template is matched when subscriber id matches.
if (template.matches(probeIdent)) {
matchingSubIds.add(subId);
@@ -2079,7 +2091,8 @@
mNetIdToSubId.clear();
final ArrayMap<NetworkStateSnapshot, NetworkIdentity> identified = new ArrayMap<>();
for (final NetworkStateSnapshot snapshot : snapshots) {
- mNetIdToSubId.put(snapshot.getNetwork().getNetId(), parseSubId(snapshot));
+ final int subId = snapshot.getSubId();
+ mNetIdToSubId.put(snapshot.getNetwork().getNetId(), subId);
// Policies matched by NPMS only match by subscriber ID or by network ID.
final NetworkIdentity ident = new NetworkIdentity.Builder()
@@ -2284,7 +2297,8 @@
.setType(TYPE_MOBILE)
.setSubscriberId(subscriberId)
.setMetered(true)
- .setDefaultNetwork(true).build();
+ .setDefaultNetwork(true)
+ .setSubId(subId).build();
for (int i = mNetworkPolicy.size() - 1; i >= 0; i--) {
final NetworkTemplate template = mNetworkPolicy.keyAt(i);
if (template.matches(probeIdent)) {
@@ -3763,6 +3777,7 @@
fout.print("Restrict power: "); fout.println(mRestrictPower);
fout.print("Device idle: "); fout.println(mDeviceIdleMode);
fout.print("Restricted networking mode: "); fout.println(mRestrictedNetworkingMode);
+ fout.print("Low Power Standby mode: "); fout.println(mLowPowerStandbyActive);
synchronized (mMeteredIfacesLock) {
fout.print("Metered ifaces: ");
fout.println(mMeteredIfaces);
@@ -3898,6 +3913,18 @@
fout.decreaseIndent();
}
+ size = mLowPowerStandbyAllowlistUids.size();
+ if (size > 0) {
+ fout.println("Low Power Standby allowlist uids:");
+ fout.increaseIndent();
+ for (int i = 0; i < size; i++) {
+ fout.print("UID=");
+ fout.print(mLowPowerStandbyAllowlistUids.keyAt(i));
+ fout.println();
+ }
+ fout.decreaseIndent();
+ }
+
final SparseBooleanArray knownUids = new SparseBooleanArray();
collectKeys(mUidState, knownUids);
collectKeys(mUidBlockedState, knownUids);
@@ -3979,6 +4006,12 @@
return isProcStateAllowedWhileIdleOrPowerSaveMode(uidState);
}
+ @GuardedBy("mUidRulesFirstLock")
+ private boolean isUidTop(int uid) {
+ final UidState uidState = mUidState.get(uid);
+ return isProcStateAllowedWhileInLowPowerStandby(uidState);
+ }
+
/**
* Process state of UID changed; if needed, will trigger
* {@link #updateRulesForDataUsageRestrictionsUL(int)} and
@@ -3995,8 +4028,10 @@
// state changed, push updated rules
mUidState.put(uid, newUidState);
updateRestrictBackgroundRulesOnUidStatusChangedUL(uid, oldUidState, newUidState);
- if (isProcStateAllowedWhileIdleOrPowerSaveMode(oldUidState)
- != isProcStateAllowedWhileIdleOrPowerSaveMode(newUidState)) {
+ boolean allowedWhileIdleOrPowerSaveModeChanged =
+ isProcStateAllowedWhileIdleOrPowerSaveMode(oldUidState)
+ != isProcStateAllowedWhileIdleOrPowerSaveMode(newUidState);
+ if (allowedWhileIdleOrPowerSaveModeChanged) {
updateRuleForAppIdleUL(uid);
if (mDeviceIdleMode) {
updateRuleForDeviceIdleUL(uid);
@@ -4006,6 +4041,17 @@
}
updateRulesForPowerRestrictionsUL(uid);
}
+ if (mLowPowerStandbyActive) {
+ boolean allowedInLpsChanged =
+ isProcStateAllowedWhileInLowPowerStandby(oldUidState)
+ != isProcStateAllowedWhileInLowPowerStandby(newUidState);
+ if (allowedInLpsChanged) {
+ if (!allowedWhileIdleOrPowerSaveModeChanged) {
+ updateRulesForPowerRestrictionsUL(uid);
+ }
+ updateRuleForLowPowerStandbyUL(uid);
+ }
+ }
return true;
}
} finally {
@@ -4029,6 +4075,9 @@
updateRuleForRestrictPowerUL(uid);
}
updateRulesForPowerRestrictionsUL(uid);
+ if (mLowPowerStandbyActive) {
+ updateRuleForLowPowerStandbyUL(uid);
+ }
return true;
}
}
@@ -4232,6 +4281,50 @@
}
}
+ @GuardedBy("mUidRulesFirstLock")
+ void updateRulesForLowPowerStandbyUL() {
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForLowPowerStandbyUL");
+ try {
+ if (mLowPowerStandbyActive) {
+ mUidFirewallLowPowerStandbyModeRules.clear();
+ for (int i = mUidState.size() - 1; i >= 0; i--) {
+ final int uid = mUidState.keyAt(i);
+ UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
+ if (hasInternetPermissionUL(uid) && uidBlockedState != null
+ && (uidBlockedState.effectiveBlockedReasons
+ & BLOCKED_REASON_LOW_POWER_STANDBY) == 0) {
+ mUidFirewallLowPowerStandbyModeRules.put(mUidBlockedState.keyAt(i),
+ FIREWALL_RULE_ALLOW);
+ }
+ }
+ setUidFirewallRulesUL(FIREWALL_CHAIN_LOW_POWER_STANDBY,
+ mUidFirewallLowPowerStandbyModeRules, CHAIN_TOGGLE_ENABLE);
+ } else {
+ setUidFirewallRulesUL(FIREWALL_CHAIN_LOW_POWER_STANDBY, null, CHAIN_TOGGLE_DISABLE);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+ }
+ }
+
+ @GuardedBy("mUidRulesFirstLock")
+ void updateRuleForLowPowerStandbyUL(int uid) {
+ if (!hasInternetPermissionUL(uid)) {
+ return;
+ }
+
+ final UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
+ if (mUidState.contains(uid) && uidBlockedState != null
+ && (uidBlockedState.effectiveBlockedReasons & BLOCKED_REASON_LOW_POWER_STANDBY)
+ == 0) {
+ mUidFirewallLowPowerStandbyModeRules.put(uid, FIREWALL_RULE_ALLOW);
+ setUidFirewallRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, FIREWALL_RULE_ALLOW);
+ } else {
+ mUidFirewallLowPowerStandbyModeRules.delete(uid);
+ setUidFirewallRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, FIREWALL_RULE_DEFAULT);
+ }
+ }
+
/**
* Returns whether a uid is allowlisted from power saving restrictions (eg: Battery Saver, Doze
* mode, and app idle).
@@ -4261,6 +4354,14 @@
return mPowerSaveWhitelistExceptIdleAppIds.get(appId);
}
+ /**
+ * Returns whether a uid is allowlisted from low power standby restrictions.
+ */
+ @GuardedBy("mUidRulesFirstLock")
+ private boolean isAllowlistedFromLowPowerStandbyUL(int uid) {
+ return mLowPowerStandbyAllowlistUids.get(uid);
+ }
+
// NOTE: since both fw_dozable and fw_powersave uses the same map
// (mPowerSaveTempWhitelistAppIds) for allowlisting, we can reuse their logic in this method.
@GuardedBy("mUidRulesFirstLock")
@@ -4598,6 +4699,7 @@
mPowerSaveTempWhitelistAppIds.delete(uid);
mAppIdleTempWhitelistAppIds.delete(uid);
mUidFirewallRestrictedModeRules.delete(uid);
+ mUidFirewallLowPowerStandbyModeRules.delete(uid);
synchronized (mUidStateCallbackInfos) {
mUidStateCallbackInfos.remove(uid);
}
@@ -4823,6 +4925,7 @@
}
final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid);
+ final boolean isTop = isUidTop(uid);
final boolean isWhitelisted = isWhitelistedFromPowerSaveUL(uid, mDeviceIdleMode);
@@ -4836,17 +4939,21 @@
int newAllowedReasons = ALLOWED_REASON_NONE;
newBlockedReasons |= (mRestrictPower ? BLOCKED_REASON_BATTERY_SAVER : 0);
newBlockedReasons |= (mDeviceIdleMode ? BLOCKED_REASON_DOZE : 0);
+ newBlockedReasons |= (mLowPowerStandbyActive ? BLOCKED_REASON_LOW_POWER_STANDBY : 0);
newBlockedReasons |= (isUidIdle ? BLOCKED_REASON_APP_STANDBY : 0);
newBlockedReasons |= (uidBlockedState.blockedReasons & BLOCKED_REASON_RESTRICTED_MODE);
newAllowedReasons |= (isSystem(uid) ? ALLOWED_REASON_SYSTEM : 0);
newAllowedReasons |= (isForeground ? ALLOWED_REASON_FOREGROUND : 0);
+ newAllowedReasons |= (isTop ? ALLOWED_REASON_TOP : 0);
newAllowedReasons |= (isWhitelistedFromPowerSaveUL(uid, true)
? ALLOWED_REASON_POWER_SAVE_ALLOWLIST : 0);
newAllowedReasons |= (isWhitelistedFromPowerSaveExceptIdleUL(uid)
? ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST : 0);
newAllowedReasons |= (uidBlockedState.allowedReasons
& ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS);
+ newAllowedReasons |= (isAllowlistedFromLowPowerStandbyUL(uid))
+ ? ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST : 0;
uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
& BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
@@ -4868,6 +4975,7 @@
+ ", mRestrictPower: " + mRestrictPower
+ ", mDeviceIdleMode: " + mDeviceIdleMode
+ ", isForeground=" + isForeground
+ + ", isTop=" + isTop
+ ", isWhitelisted=" + isWhitelisted
+ ", oldUidBlockedState=" + previousUidBlockedState.toString()
+ ", newUidBlockedState=" + uidBlockedState.toString());
@@ -5398,6 +5506,8 @@
mUidFirewallPowerSaveRules.put(uid, rule);
} else if (chain == FIREWALL_CHAIN_RESTRICTED) {
mUidFirewallRestrictedModeRules.put(uid, rule);
+ } else if (chain == FIREWALL_CHAIN_LOW_POWER_STANDBY) {
+ mUidFirewallLowPowerStandbyModeRules.put(uid, rule);
}
try {
@@ -5445,6 +5555,9 @@
.setFirewallUidRule(FIREWALL_CHAIN_POWERSAVE, uid, FIREWALL_RULE_DEFAULT);
mNetworkManager
.setFirewallUidRule(FIREWALL_CHAIN_RESTRICTED, uid, FIREWALL_RULE_DEFAULT);
+ mNetworkManager
+ .setFirewallUidRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid,
+ FIREWALL_RULE_DEFAULT);
mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false);
mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false);
} catch (IllegalStateException e) {
@@ -5720,6 +5833,67 @@
mHandler.obtainMessage(MSG_METERED_RESTRICTED_PACKAGES_CHANGED,
userId, 0, packageNames).sendToTarget();
}
+
+ @Override
+ public void setLowPowerStandbyActive(boolean active) {
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setLowPowerStandbyActive");
+ try {
+ synchronized (mUidRulesFirstLock) {
+ if (mLowPowerStandbyActive == active) {
+ return;
+ }
+ mLowPowerStandbyActive = active;
+ synchronized (mNetworkPoliciesSecondLock) {
+ if (!mSystemReady) return;
+ }
+
+ forEachUid("updateRulesForRestrictPower",
+ uid -> updateRulesForPowerRestrictionsUL(uid));
+ updateRulesForLowPowerStandbyUL();
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+ }
+ }
+
+ @Override
+ public void setLowPowerStandbyAllowlist(int[] uids) {
+ synchronized (mUidRulesFirstLock) {
+ final SparseBooleanArray changedUids = new SparseBooleanArray();
+ for (int i = 0; i < mLowPowerStandbyAllowlistUids.size(); i++) {
+ final int oldUid = mLowPowerStandbyAllowlistUids.keyAt(i);
+ if (!ArrayUtils.contains(uids, oldUid)) {
+ changedUids.put(oldUid, true);
+ }
+ }
+
+ for (int i = 0; i < changedUids.size(); i++) {
+ final int deletedUid = changedUids.keyAt(i);
+ mLowPowerStandbyAllowlistUids.delete(deletedUid);
+ }
+
+ for (int newUid : uids) {
+ if (mLowPowerStandbyAllowlistUids.indexOfKey(newUid) < 0) {
+ changedUids.append(newUid, true);
+ mLowPowerStandbyAllowlistUids.append(newUid, true);
+ }
+ }
+
+ if (!mLowPowerStandbyActive) {
+ return;
+ }
+
+ synchronized (mNetworkPoliciesSecondLock) {
+ if (!mSystemReady) return;
+ }
+
+ for (int i = 0; i < changedUids.size(); i++) {
+ final int changedUid = changedUids.keyAt(i);
+ updateRulesForPowerRestrictionsUL(changedUid);
+ updateRuleForLowPowerStandbyUL(changedUid);
+ }
+ }
+ }
}
private void setMeteredRestrictedPackagesInternal(Set<String> packageNames, int userId) {
@@ -5747,17 +5921,6 @@
}
}
- private int parseSubId(@NonNull NetworkStateSnapshot snapshot) {
- int subId = INVALID_SUBSCRIPTION_ID;
- if (snapshot.getNetworkCapabilities().hasTransport(TRANSPORT_CELLULAR)) {
- NetworkSpecifier spec = snapshot.getNetworkCapabilities().getNetworkSpecifier();
- if (spec instanceof TelephonyNetworkSpecifier) {
- subId = ((TelephonyNetworkSpecifier) spec).getSubscriptionId();
- }
- }
- return subId;
- }
-
@GuardedBy("mNetworkPoliciesSecondLock")
private int getSubIdLocked(Network network) {
return mNetIdToSubId.get(network.getNetId(), INVALID_SUBSCRIPTION_ID);
@@ -5888,6 +6051,9 @@
effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_DATA_SAVER;
effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_USER_RESTRICTED;
}
+ if ((allowedReasons & ALLOWED_REASON_TOP) != 0) {
+ effectiveBlockedReasons &= ~BLOCKED_REASON_LOW_POWER_STANDBY;
+ }
if ((allowedReasons & ALLOWED_REASON_POWER_SAVE_ALLOWLIST) != 0) {
effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER;
effectiveBlockedReasons &= ~BLOCKED_REASON_DOZE;
@@ -5903,6 +6069,10 @@
if ((allowedReasons & ALLOWED_METERED_REASON_USER_EXEMPTED) != 0) {
effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_DATA_SAVER;
}
+ if ((allowedReasons & ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST) != 0) {
+ effectiveBlockedReasons &= ~BLOCKED_REASON_LOW_POWER_STANDBY;
+ }
+
return effectiveBlockedReasons;
}
@@ -5927,6 +6097,7 @@
BLOCKED_REASON_DOZE,
BLOCKED_REASON_APP_STANDBY,
BLOCKED_REASON_RESTRICTED_MODE,
+ BLOCKED_REASON_LOW_POWER_STANDBY,
BLOCKED_METERED_REASON_DATA_SAVER,
BLOCKED_METERED_REASON_USER_RESTRICTED,
BLOCKED_METERED_REASON_ADMIN_DISABLED,
@@ -5935,9 +6106,11 @@
private static final int[] ALLOWED_REASONS = {
ALLOWED_REASON_SYSTEM,
ALLOWED_REASON_FOREGROUND,
+ ALLOWED_REASON_TOP,
ALLOWED_REASON_POWER_SAVE_ALLOWLIST,
ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST,
ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS,
+ ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST,
ALLOWED_METERED_REASON_USER_EXEMPTED,
ALLOWED_METERED_REASON_SYSTEM,
ALLOWED_METERED_REASON_FOREGROUND,
@@ -5955,6 +6128,8 @@
return "APP_STANDBY";
case BLOCKED_REASON_RESTRICTED_MODE:
return "RESTRICTED_MODE";
+ case BLOCKED_REASON_LOW_POWER_STANDBY:
+ return "LOW_POWER_STANDBY";
case BLOCKED_METERED_REASON_DATA_SAVER:
return "DATA_SAVER";
case BLOCKED_METERED_REASON_USER_RESTRICTED:
@@ -5975,12 +6150,16 @@
return "SYSTEM";
case ALLOWED_REASON_FOREGROUND:
return "FOREGROUND";
+ case ALLOWED_REASON_TOP:
+ return "TOP";
case ALLOWED_REASON_POWER_SAVE_ALLOWLIST:
return "POWER_SAVE_ALLOWLIST";
case ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST:
return "POWER_SAVE_EXCEPT_IDLE_ALLOWLIST";
case ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS:
return "RESTRICTED_MODE_PERMISSIONS";
+ case ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST:
+ return "LOW_POWER_STANDBY_ALLOWLIST";
case ALLOWED_METERED_REASON_USER_EXEMPTED:
return "METERED_USER_EXEMPTED";
case ALLOWED_METERED_REASON_SYSTEM:
@@ -6047,7 +6226,8 @@
int powerBlockedReasons = BLOCKED_REASON_APP_STANDBY
| BLOCKED_REASON_DOZE
- | BLOCKED_REASON_BATTERY_SAVER;
+ | BLOCKED_REASON_BATTERY_SAVER
+ | BLOCKED_REASON_LOW_POWER_STANDBY;
if ((effectiveBlockedReasons & powerBlockedReasons) != 0) {
uidRule |= RULE_REJECT_ALL;
} else if ((blockedReasons & powerBlockedReasons) != 0) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 2717f0c..3d34976 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5805,6 +5805,7 @@
|| channel.isImportanceLockedByCriticalDeviceFunction());
final StatusBarNotification adjustedSbn = notificationRecord.getSbn();
userId = adjustedSbn.getUser().getIdentifier();
+ int uid = adjustedSbn.getUid();
ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
if (summaries == null) {
summaries = new ArrayMap<>();
@@ -5851,7 +5852,7 @@
notificationRecord.getIsAppImportanceLocked());
summaries.put(pkg, summarySbn.getKey());
}
- if (summaryRecord != null && checkDisqualifyingFeatures(userId, MY_UID,
+ if (summaryRecord != null && checkDisqualifyingFeatures(userId, uid,
summaryRecord.getSbn().getId(), summaryRecord.getSbn().getTag(), summaryRecord,
true)) {
return summaryRecord;
@@ -6527,7 +6528,7 @@
@VisibleForTesting
protected void fixNotification(Notification notification, String pkg, String tag, int id,
- int userId) throws NameNotFoundException {
+ int userId) throws NameNotFoundException, RemoteException {
final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
(userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId);
@@ -6561,6 +6562,21 @@
actions.toArray(notification.actions);
}
+ // Ensure MediaStyle has correct permissions for remote device extras
+ if (notification.isStyle(Notification.MediaStyle.class)) {
+ int hasMediaContentControlPermission = mPackageManager.checkPermission(
+ android.Manifest.permission.MEDIA_CONTENT_CONTROL, pkg, userId);
+ if (hasMediaContentControlPermission != PERMISSION_GRANTED) {
+ notification.extras.remove(Notification.EXTRA_MEDIA_REMOTE_DEVICE);
+ notification.extras.remove(Notification.EXTRA_MEDIA_REMOTE_ICON);
+ notification.extras.remove(Notification.EXTRA_MEDIA_REMOTE_INTENT);
+ if (DBG) {
+ Slog.w(TAG, "Package " + pkg + ": Use of setRemotePlayback requires the "
+ + "MEDIA_CONTENT_CONTROL permission");
+ }
+ }
+ }
+
// Remote views? Are they too big?
checkRemoteViews(pkg, tag, id, notification);
}
@@ -6822,7 +6838,6 @@
return false;
}
-
// blocked apps
boolean isBlocked = !areNotificationsEnabledForPackageInt(pkg, uid);
synchronized (mNotificationLock) {
@@ -7215,10 +7230,12 @@
if (mAssistants.isEnabled()) {
mAssistants.onNotificationEnqueuedLocked(r);
mHandler.postDelayed(
- new PostNotificationRunnable(r.getKey(), enqueueElapsedTimeMs),
+ new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), enqueueElapsedTimeMs),
DELAY_FOR_ASSISTANT_TIME);
} else {
- mHandler.post(new PostNotificationRunnable(r.getKey(), enqueueElapsedTimeMs));
+ mHandler.post(new PostNotificationRunnable(r.getKey(),
+ r.getSbn().getPackageName(), r.getUid(), enqueueElapsedTimeMs));
}
}
}
@@ -7242,16 +7259,19 @@
protected class PostNotificationRunnable implements Runnable {
private final String key;
private final long postElapsedTimeMs;
+ private final String pkg;
+ private final int uid;
- PostNotificationRunnable(String key, @ElapsedRealtimeLong long postElapsedTimeMs) {
+ PostNotificationRunnable(String key, String pkg, int uid,
+ @ElapsedRealtimeLong long postElapsedTimeMs) {
this.key = key;
+ this.pkg = pkg;
+ this.uid = uid;
this.postElapsedTimeMs = postElapsedTimeMs;
}
@Override
public void run() {
- String pkg = StatusBarNotification.getPkgFromKey(key);
- int uid = StatusBarNotification.getUidFromKey(key);
boolean appBanned = !areNotificationsEnabledForPackageInt(pkg, uid);
synchronized (mNotificationLock) {
try {
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index 5d18069..86ac7c1 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -179,7 +179,7 @@
assertFlag();
final long callingId = Binder.clearCallingIdentity();
try {
- if (grant) {
+ if (grant && !reviewRequired) {
mPermManager.grantRuntimePermission(packageName, NOTIFICATION_PERMISSION, userId);
} else {
mPermManager.revokeRuntimePermission(packageName, NOTIFICATION_PERMISSION, userId,
@@ -210,8 +210,10 @@
if (pkgPerm == null || pkgPerm.packageName == null) {
return;
}
- setNotificationPermission(pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted,
- pkgPerm.userSet, !pkgPerm.userSet);
+ if (!isPermissionFixed(pkgPerm.packageName, pkgPerm.userId)) {
+ setNotificationPermission(pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted,
+ pkgPerm.userSet, !pkgPerm.userSet);
+ }
}
public boolean isPermissionFixed(String packageName, @UserIdInt int userId) {
@@ -239,7 +241,8 @@
try {
int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
userId);
- return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
+ return (flags & (PackageManager.FLAG_PERMISSION_USER_SET
+ | PackageManager.FLAG_PERMISSION_USER_FIXED)) != 0;
} catch (RemoteException e) {
Slog.e(TAG, "Could not reach system server", e);
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 2011528..78aa45a 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2134,6 +2134,9 @@
private String[] getPackagesForUidInternal(int uid, int callingUid) {
final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
final int userId = UserHandle.getUserId(uid);
+ if (Process.isSupplemental(uid)) {
+ uid = getSupplementalProcessUid();
+ }
final int appId = UserHandle.getAppId(uid);
return getPackagesForUidInternalBody(callingUid, userId, appId, isCallerInstantApp);
}
@@ -4292,6 +4295,9 @@
if (getInstantAppPackageName(callingUid) != null) {
return null;
}
+ if (Process.isSupplemental(uid)) {
+ uid = getSupplementalProcessUid();
+ }
final int callingUserId = UserHandle.getUserId(callingUid);
final int appId = UserHandle.getAppId(uid);
final Object obj = mSettings.getSettingBase(appId);
@@ -4324,7 +4330,11 @@
final int callingUserId = UserHandle.getUserId(callingUid);
final String[] names = new String[uids.length];
for (int i = uids.length - 1; i >= 0; i--) {
- final int appId = UserHandle.getAppId(uids[i]);
+ int uid = uids[i];
+ if (Process.isSupplemental(uid)) {
+ uid = getSupplementalProcessUid();
+ }
+ final int appId = UserHandle.getAppId(uid);
final Object obj = mSettings.getSettingBase(appId);
if (obj instanceof SharedUserSetting) {
final SharedUserSetting sus = (SharedUserSetting) obj;
@@ -4370,6 +4380,9 @@
if (getInstantAppPackageName(callingUid) != null) {
return 0;
}
+ if (Process.isSupplemental(uid)) {
+ uid = getSupplementalProcessUid();
+ }
final int callingUserId = UserHandle.getUserId(callingUid);
final int appId = UserHandle.getAppId(uid);
final Object obj = mSettings.getSettingBase(appId);
@@ -4395,6 +4408,9 @@
if (getInstantAppPackageName(callingUid) != null) {
return 0;
}
+ if (Process.isSupplemental(uid)) {
+ uid = getSupplementalProcessUid();
+ }
final int callingUserId = UserHandle.getUserId(callingUid);
final int appId = UserHandle.getAppId(uid);
final Object obj = mSettings.getSettingBase(appId);
@@ -4419,6 +4435,9 @@
if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
return false;
}
+ if (Process.isSupplemental(uid)) {
+ uid = getSupplementalProcessUid();
+ }
final int appId = UserHandle.getAppId(uid);
final Object obj = mSettings.getSettingBase(appId);
if (obj instanceof SharedUserSetting) {
@@ -5543,6 +5562,9 @@
@Override
public int getUidTargetSdkVersion(int uid) {
+ if (Process.isSupplemental(uid)) {
+ uid = getSupplementalProcessUid();
+ }
final int appId = UserHandle.getAppId(uid);
final SettingBase settingBase = mSettings.getSettingBase(appId);
if (settingBase instanceof SharedUserSetting) {
@@ -5569,6 +5591,9 @@
@Nullable
@Override
public ArrayMap<String, ProcessInfo> getProcessesForUid(int uid) {
+ if (Process.isSupplemental(uid)) {
+ uid = getSupplementalProcessUid();
+ }
final int appId = UserHandle.getAppId(uid);
final SettingBase settingBase = mSettings.getSettingBase(appId);
if (settingBase instanceof SharedUserSetting) {
@@ -5598,4 +5623,8 @@
return null;
}
}
+
+ private int getSupplementalProcessUid() {
+ return getPackage(mService.getSupplementalProcessPackageName()).getUid();
+ }
}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index e657838..6b3ce77 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -18,6 +18,7 @@
import static android.app.ActivityOptions.KEY_SPLASH_SCREEN_THEME;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
+import static android.app.PendingIntent.FLAG_MUTABLE;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
@@ -1218,8 +1219,9 @@
}
@Override
- public PendingIntent getActivityLaunchIntent(ComponentName component, Bundle opts,
+ public PendingIntent getActivityLaunchIntent(String callingPackage, ComponentName component,
UserHandle user) {
+ ensureShortcutPermission(callingPackage);
if (!canAccessProfile(user.getIdentifier(), "Cannot start activity")) {
throw new ActivityNotFoundException("Activity could not be found");
}
@@ -1237,7 +1239,7 @@
// calling identity to mirror the startActivityAsUser() call which does not validate
// the calling user
return PendingIntent.getActivityAsUser(mContext, 0 /* requestCode */, launchIntent,
- FLAG_IMMUTABLE, null /* options */, user);
+ FLAG_MUTABLE, null /* opts */, user);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 279de83..0575b8c 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -51,13 +51,6 @@
import android.content.pm.UserInfo;
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.overlay.OverlayPaths;
-import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
-import com.android.server.pm.pkg.component.ParsedComponent;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
-import com.android.server.pm.pkg.component.ParsedPermission;
-import com.android.server.pm.pkg.component.ParsedProcess;
-import com.android.server.pm.pkg.PackageUserStateUtils;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -118,10 +111,18 @@
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageUserState;
import com.android.server.pm.pkg.PackageUserStateInternal;
+import com.android.server.pm.pkg.PackageUserStateUtils;
import com.android.server.pm.pkg.SuspendParams;
+import com.android.server.pm.pkg.component.ParsedComponent;
+import com.android.server.pm.pkg.component.ParsedIntentInfo;
+import com.android.server.pm.pkg.component.ParsedMainComponent;
+import com.android.server.pm.pkg.component.ParsedPermission;
+import com.android.server.pm.pkg.component.ParsedProcess;
+import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
import com.android.server.pm.verify.domain.DomainVerificationLegacySettings;
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.verify.domain.DomainVerificationPersistence;
+import com.android.server.utils.Slogf;
import com.android.server.utils.Snappable;
import com.android.server.utils.SnapshotCache;
import com.android.server.utils.TimingsTraceAndSlog;
@@ -982,7 +983,8 @@
Slog.i(PackageManagerService.TAG, "Stopping package " + pkgName, e);
}
List<UserInfo> users = getAllUsers(userManager);
- final int installUserId = installUser != null ? installUser.getIdentifier() : 0;
+ int installUserId = installUser != null ? installUser.getIdentifier()
+ : UserHandle.USER_SYSTEM;
if (users != null && allowInstall) {
for (UserInfo user : users) {
// By default we consider this app to be installed
@@ -993,8 +995,14 @@
// user we are installing for.
final boolean installed = installUser == null
|| (installUserId == UserHandle.USER_ALL
- && !isAdbInstallDisallowed(userManager, user.id))
+ && !isAdbInstallDisallowed(userManager, user.id)
+ && !user.preCreated)
|| installUserId == user.id;
+ if (DEBUG_MU) {
+ Slogf.d(TAG, "createNewSetting(pkg=%s, installUserId=%s, user=%s, "
+ + "installed=%b)",
+ pkgName, installUserId, user.toFullString(), installed);
+ }
pkgSetting.setUserState(user.id, 0, COMPONENT_ENABLED_STATE_DEFAULT,
installed,
true /*stopped*/,
@@ -2039,11 +2047,14 @@
serializer.startTag(null, TAG_PACKAGE_RESTRICTIONS);
- if (DEBUG_MU) Log.i(TAG, "Writing " + userPackagesStateFile);
+ if (DEBUG_MU) {
+ Slogf.i(TAG, "Writing %s (%d packages)", userPackagesStateFile,
+ mPackages.values().size());
+ }
for (final PackageSetting pkg : mPackages.values()) {
final PackageUserStateInternal ustate = pkg.readUserState(userId);
if (DEBUG_MU) {
- Log.i(TAG, " pkg=" + pkg.getPackageName()
+ Log.v(TAG, " pkg=" + pkg.getPackageName()
+ ", installed=" + ustate.isInstalled()
+ ", state=" + ustate.getEnabledState());
}
@@ -5617,7 +5628,8 @@
userId);
packageSetting.setInstallPermissionsFixed(true);
} else if (packageSetting.getSharedUser() == null && !isUpgradeToR) {
- Slog.w(TAG, "Missing permission state for package: " + packageName);
+ Slogf.w(TAG, "Missing permission state for package %s on user %d",
+ packageName, userId);
packageSetting.getLegacyPermissionState().setMissing(true, userId);
}
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index ed351fd..e9074c4 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -17,6 +17,7 @@
package com.android.server.pm.permission;
import static android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT;
@@ -752,11 +753,14 @@
flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
- flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
flagValues &= ~FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
flagValues &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
flagValues &= ~FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
flagValues &= ~PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
+ // REVIEW_REQUIRED can only be set by non-system apps for for POST_NOTIFICATIONS
+ if (!POST_NOTIFICATIONS.equals(permName)) {
+ flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+ }
}
final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
index c2b3cbc..0320818 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
@@ -20,6 +20,8 @@
import android.annotation.Nullable;
import android.content.pm.ActivityInfo;
+import java.util.Set;
+
/** @hide **/
//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
public interface ParsedActivity extends ParsedMainComponent {
@@ -59,6 +61,12 @@
@Nullable
String getPermission();
+ /**
+ * Gets the trusted host certificates of apps that are allowed to embed this activity.
+ */
+ @NonNull
+ Set<String> getKnownActivityEmbeddingCerts();
+
int getPersistableMode();
int getPrivateFlags();
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
index 91c0b07..acd5a81 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
@@ -23,6 +23,7 @@
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString;
+import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForStringSet;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -33,12 +34,17 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DataClass;
import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
import com.android.server.pm.pkg.parsing.ParsingUtils;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Set;
+
/**
* @hide
**/
@@ -63,6 +69,8 @@
@Nullable
@DataClass.ParcelWith(ForInternedString.class)
private String permission;
+ @Nullable
+ private Set<String> mKnownActivityEmbeddingCerts;
private int launchMode;
private int documentLaunchMode;
@@ -113,6 +121,7 @@
this.rotationAnimation = other.rotationAnimation;
this.colorMode = other.colorMode;
this.windowLayout = other.windowLayout;
+ this.mKnownActivityEmbeddingCerts = other.mKnownActivityEmbeddingCerts;
}
/**
@@ -239,6 +248,25 @@
return this;
}
+ @NonNull
+ @Override
+ public Set<String> getKnownActivityEmbeddingCerts() {
+ return mKnownActivityEmbeddingCerts == null ? Collections.emptySet()
+ : mKnownActivityEmbeddingCerts;
+ }
+
+ /**
+ * Sets the trusted host certificates of apps that are allowed to embed this activity.
+ */
+ public void setKnownActivityEmbeddingCerts(@NonNull Set<String> knownActivityEmbeddingCerts) {
+ // Convert the provided digest to upper case for consistent Set membership
+ // checks when verifying the signing certificate digests of requesting apps.
+ this.mKnownActivityEmbeddingCerts = new ArraySet<>();
+ for (String knownCert : knownActivityEmbeddingCerts) {
+ this.mKnownActivityEmbeddingCerts.add(knownCert.toUpperCase(Locale.US));
+ }
+ }
+
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append("Activity{");
@@ -287,6 +315,7 @@
} else {
dest.writeBoolean(false);
}
+ sForStringSet.parcel(this.mKnownActivityEmbeddingCerts, dest, flags);
}
public ParsedActivityImpl() {
@@ -320,6 +349,7 @@
if (in.readBoolean()) {
windowLayout = new ActivityInfo.WindowLayout(in);
}
+ this.mKnownActivityEmbeddingCerts = sForStringSet.unparcel(in);
}
@NonNull
@@ -344,7 +374,7 @@
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedActivityImpl.java
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -360,6 +390,7 @@
@Nullable String taskAffinity,
int privateFlags,
@Nullable String permission,
+ @Nullable Set<String> knownActivityEmbeddingCerts,
int launchMode,
int documentLaunchMode,
int maxRecents,
@@ -383,6 +414,7 @@
this.taskAffinity = taskAffinity;
this.privateFlags = privateFlags;
this.permission = permission;
+ this.mKnownActivityEmbeddingCerts = knownActivityEmbeddingCerts;
this.launchMode = launchMode;
this.documentLaunchMode = documentLaunchMode;
this.maxRecents = maxRecents;
@@ -645,10 +677,10 @@
}
@DataClass.Generated(
- time = 1641431949361L,
+ time = 1644372875433L,
codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedActivityImpl.java",
- inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull android.content.pm.parsing.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull android.content.pm.parsing.component.ParsedActivityImpl makeAlias(java.lang.String,android.content.pm.parsing.component.ParsedActivity)\npublic android.content.pm.parsing.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic android.content.pm.parsing.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic android.content.pm.parsing.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic android.content.pm.parsing.component.ParsedActivityImpl setPermission(java.lang.String)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+ sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java",
+ inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.server.pm.pkg.component.ParsedActivity)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.server.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
index a6c22a18..bbbf598 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
@@ -21,6 +21,7 @@
import static com.android.server.pm.pkg.component.ComponentParseUtils.flag;
import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET;
+import static com.android.server.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -151,7 +152,8 @@
| flag(ActivityInfo.FLAG_SHOW_WHEN_LOCKED, R.styleable.AndroidManifestActivity_showWhenLocked, sa)
| flag(ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE, R.styleable.AndroidManifestActivity_supportsPictureInPicture, sa)
| flag(ActivityInfo.FLAG_TURN_SCREEN_ON, R.styleable.AndroidManifestActivity_turnScreenOn, sa)
- | flag(ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING, R.styleable.AndroidManifestActivity_preferMinimalPostProcessing, sa)));
+ | flag(ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING, R.styleable.AndroidManifestActivity_preferMinimalPostProcessing, sa))
+ | flag(ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING, R.styleable.AndroidManifestActivity_allowUntrustedActivityEmbedding, sa));
activity.setPrivateFlags(activity.getPrivateFlags() | (flag(ActivityInfo.FLAG_INHERIT_SHOW_WHEN_LOCKED,
R.styleable.AndroidManifestActivity_inheritShowWhenLocked, sa)
@@ -338,6 +340,20 @@
activity.setPermission(permission != null ? permission : pkg.getPermission());
}
+ final ParseResult<Set<String>> knownActivityEmbeddingCertsResult =
+ parseKnownActivityEmbeddingCerts(array, resources, isAlias
+ ? R.styleable.AndroidManifestActivityAlias_knownActivityEmbeddingCerts
+ : R.styleable.AndroidManifestActivity_knownActivityEmbeddingCerts, input);
+ if (knownActivityEmbeddingCertsResult.isError()) {
+ return input.error(knownActivityEmbeddingCertsResult);
+ } else {
+ final Set<String> knownActivityEmbeddingCerts = knownActivityEmbeddingCertsResult
+ .getResult();
+ if (knownActivityEmbeddingCerts != null) {
+ activity.setKnownActivityEmbeddingCerts(knownActivityEmbeddingCerts);
+ }
+ }
+
final boolean setExported = array.hasValue(exportedAttr);
if (setExported) {
activity.setExported(array.getBoolean(exportedAttr, false));
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java
index b92b845..f199841 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java
@@ -549,6 +549,7 @@
ai.metaData = a.getMetaData();
}
ai.applicationInfo = applicationInfo;
+ ai.setKnownActivityEmbeddingCerts(a.getKnownActivityEmbeddingCerts());
return ai;
}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
index 52d9b7a..1754877 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
@@ -381,6 +381,12 @@
ParsingPackage setLocaleConfigRes(int localeConfigRes);
+ /**
+ * Sets the trusted host certificates of apps that are allowed to embed activities of this
+ * application.
+ */
+ ParsingPackage setKnownActivityEmbeddingCerts(Set<String> knownActivityEmbeddingCerts);
+
// TODO(b/135203078): This class no longer has access to ParsedPackage, find a replacement
// for moving to the next step
@CallSuper
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
index 84422cd..c4b1275 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
@@ -563,6 +563,9 @@
return (mBooleans & flag) != 0;
}
+ @Nullable
+ private Set<String> mKnownActivityEmbeddingCerts;
+
// Derived fields
@NonNull
private UUID mStorageUuid;
@@ -1150,6 +1153,9 @@
appInfo.setVersionCode(mLongVersionCode);
appInfo.setAppClassNamesByProcess(buildAppClassNamesByProcess());
appInfo.setLocaleConfigRes(mLocaleConfigRes);
+ if (mKnownActivityEmbeddingCerts != null) {
+ appInfo.setKnownActivityEmbeddingCerts(mKnownActivityEmbeddingCerts);
+ }
return appInfo;
}
@@ -1329,6 +1335,7 @@
dest.writeInt(this.nativeHeapZeroInitialized);
sForBoolean.parcel(this.requestRawExternalStorageAccess, dest, flags);
dest.writeInt(this.mLocaleConfigRes);
+ sForStringSet.parcel(mKnownActivityEmbeddingCerts, dest, flags);
}
public ParsingPackageImpl(Parcel in) {
@@ -1477,6 +1484,7 @@
this.nativeHeapZeroInitialized = in.readInt();
this.requestRawExternalStorageAccess = sForBoolean.unparcel(in);
this.mLocaleConfigRes = in.readInt();
+ this.mKnownActivityEmbeddingCerts = sForStringSet.unparcel(in);
assignDerivedFields();
}
@@ -2376,6 +2384,13 @@
return getBoolean(Booleans.RESET_ENABLED_SETTINGS_ON_APP_DATA_CLEARED);
}
+ @NonNull
+ @Override
+ public Set<String> getKnownActivityEmbeddingCerts() {
+ return mKnownActivityEmbeddingCerts == null ? Collections.emptySet()
+ : mKnownActivityEmbeddingCerts;
+ }
+
@Override
public boolean shouldInheritKeyStoreKeys() {
return getBoolean(Booleans.INHERIT_KEYSTORE_KEYS);
@@ -2973,4 +2988,11 @@
mLocaleConfigRes = value;
return this;
}
+
+ @Override
+ public ParsingPackage setKnownActivityEmbeddingCerts(
+ @Nullable Set<String> knownEmbeddingCerts) {
+ mKnownActivityEmbeddingCerts = knownEmbeddingCerts;
+ return this;
+ }
}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index e8f03ff..a891980 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -30,6 +30,8 @@
import static android.os.Build.VERSION_CODES.O;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static com.android.server.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts;
+
import android.annotation.AnyRes;
import android.annotation.CheckResult;
import android.annotation.IntDef;
@@ -2009,6 +2011,19 @@
.AndroidManifestApplication_requestForegroundServiceExemption,
false));
}
+ final ParseResult<Set<String>> knownActivityEmbeddingCertsResult =
+ parseKnownActivityEmbeddingCerts(sa, res,
+ R.styleable.AndroidManifestApplication_knownActivityEmbeddingCerts,
+ input);
+ if (knownActivityEmbeddingCertsResult.isError()) {
+ return input.error(knownActivityEmbeddingCertsResult);
+ } else {
+ final Set<String> knownActivityEmbeddingCerts = knownActivityEmbeddingCertsResult
+ .getResult();
+ if (knownActivityEmbeddingCerts != null) {
+ pkg.setKnownActivityEmbeddingCerts(knownActivityEmbeddingCerts);
+ }
+ }
} finally {
sa.recycle();
}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
index 9430e98..cb474df 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
@@ -22,6 +22,8 @@
import android.annotation.Nullable;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.os.Parcel;
import android.os.Parcelable;
@@ -38,6 +40,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
/** @hide **/
public class ParsingUtils {
@@ -168,4 +171,50 @@
return list;
}
}
+
+ /**
+ * Parse the {@link android.R.attr#knownActivityEmbeddingCerts} attribute, if available.
+ */
+ @NonNull
+ public static ParseResult<Set<String>> parseKnownActivityEmbeddingCerts(@NonNull TypedArray sa,
+ @NonNull Resources res, int resourceId, @NonNull ParseInput input) {
+ if (!sa.hasValue(resourceId)) {
+ return input.success(null);
+ }
+
+ final int knownActivityEmbeddingCertsResource = sa.getResourceId(resourceId, 0);
+ if (knownActivityEmbeddingCertsResource != 0) {
+ // The knownCerts attribute supports both a string array resource as well as a
+ // string resource for the case where the permission should only be granted to a
+ // single known signer.
+ Set<String> knownEmbeddingCertificates = null;
+ final String resourceType = res.getResourceTypeName(
+ knownActivityEmbeddingCertsResource);
+ if (resourceType.equals("array")) {
+ final String[] knownCerts = res.getStringArray(knownActivityEmbeddingCertsResource);
+ if (knownCerts != null) {
+ knownEmbeddingCertificates = Set.of(knownCerts);
+ }
+ } else {
+ final String knownCert = res.getString(knownActivityEmbeddingCertsResource);
+ if (knownCert != null) {
+ knownEmbeddingCertificates = Set.of(knownCert);
+ }
+ }
+ if (knownEmbeddingCertificates == null || knownEmbeddingCertificates.isEmpty()) {
+ return input.error("Defined a knownActivityEmbeddingCerts attribute but the "
+ + "provided resource is null");
+ }
+ return input.success(knownEmbeddingCertificates);
+ }
+
+ // If the knownCerts resource ID is null - the app specified a string value for the
+ // attribute representing a single trusted signer.
+ final String knownCert = sa.getString(resourceId);
+ if (knownCert == null || knownCert.isEmpty()) {
+ return input.error("Defined a knownActivityEmbeddingCerts attribute but the provided "
+ + "string is empty");
+ }
+ return input.success(Set.of(knownCert));
+ }
}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java
index 2ef90ac..3205b76 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java
@@ -21,6 +21,8 @@
import android.content.pm.ApplicationInfo;
import android.util.SparseArray;
+import java.util.Set;
+
/**
* Container for fields that are eventually exposed through {@link ApplicationInfo}.
* <p>
@@ -285,6 +287,13 @@
String getPermission();
/**
+ * @see ApplicationInfo#knownActivityEmbeddingCerts
+ * @see R.styleable#AndroidManifestApplication_knownActivityEmbeddingCerts
+ */
+ @NonNull
+ Set<String> getKnownActivityEmbeddingCerts();
+
+ /**
* @see ApplicationInfo#processName
* @see R.styleable#AndroidManifestApplication_process
*/
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index c9a8701..c637c67 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -37,7 +37,7 @@
import android.app.TaskInfo;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
+import android.compat.annotation.EnabledAfter;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -58,6 +58,7 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.permission.PermissionControllerManager;
+import android.permission.PermissionManager;
import android.provider.Settings;
import android.provider.Telephony;
import android.telecom.TelecomManager;
@@ -141,7 +142,7 @@
* This change reflects the presence of the new Notification Permission
*/
@ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2)
private static final long NOTIFICATION_PERM_CHANGE_ID = 194833441L;
private List<String> mAppOpPermissions;
@@ -149,7 +150,6 @@
private Context mContext;
private PackageManagerInternal mPackageManagerInternal;
private NotificationManagerInternal mNotificationManager;
- private PermissionManagerServiceInternal mPermissionManagerService;
private final PackageManager mPackageManager;
public PermissionPolicyService(@NonNull Context context) {
@@ -1001,13 +1001,48 @@
private class Internal extends PermissionPolicyInternal {
- private ActivityInterceptorCallback mActivityInterceptorCallback =
+ // UIDs that, if a grant dialog is shown for POST_NOTIFICATIONS before next reboot,
+ // should display a "continue allowing" message, rather than an "allow" message
+ private final ArraySet<Integer> mContinueNotifGrantMessageUids = new ArraySet<>();
+
+ private final ActivityInterceptorCallback mActivityInterceptorCallback =
new ActivityInterceptorCallback() {
@Nullable
@Override
public ActivityInterceptorCallback.ActivityInterceptResult intercept(
ActivityInterceptorInfo info) {
- return null;
+ String action = info.intent.getAction();
+ ActivityInterceptResult result = null;
+ if (!PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(action)
+ && !PackageManager.ACTION_REQUEST_PERMISSIONS.equals(action)) {
+ return null;
+ }
+ // Only this interceptor can add LEGACY_ACCESS_PERMISSION_NAMES
+ if (info.intent.getStringArrayExtra(PackageManager
+ .EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES)
+ != null) {
+ result = new ActivityInterceptResult(
+ new Intent(info.intent), info.checkedOptions);
+ result.intent.removeExtra(PackageManager
+ .EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES);
+ }
+ if (PackageManager.ACTION_REQUEST_PERMISSIONS.equals(action)
+ && !mContinueNotifGrantMessageUids.contains(info.realCallingUid)) {
+ return result;
+ }
+ if (PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(action)) {
+ String otherPkg = info.intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ if (otherPkg == null || (mPackageManager.getPermissionFlags(
+ POST_NOTIFICATIONS, otherPkg, UserHandle.of(info.userId))
+ & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
+ return result;
+ }
+ }
+
+ mContinueNotifGrantMessageUids.remove(info.realCallingUid);
+ return new ActivityInterceptResult(info.intent.putExtra(PackageManager
+ .EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES,
+ new String[] { POST_NOTIFICATIONS }), info.checkedOptions);
}
@Override
@@ -1057,12 +1092,21 @@
launchNotificationPermissionRequestDialog(packageName, user, taskId);
}
- private void clearNotificationReviewFlagsIfNeeded(String packageName, UserHandle userId) {
- if (!CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID, packageName, userId)) {
+ private void clearNotificationReviewFlagsIfNeeded(String packageName, UserHandle user) {
+ if (!CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID, packageName, user)
+ || ((mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, packageName, user)
+ & FLAG_PERMISSION_REVIEW_REQUIRED) == 0)) {
return;
}
- mPackageManager.updatePermissionFlags(POST_NOTIFICATIONS, packageName,
- FLAG_PERMISSION_REVIEW_REQUIRED, 0, userId);
+ try {
+ int uid = mPackageManager.getPackageUidAsUser(packageName, 0,
+ user.getIdentifier());
+ mContinueNotifGrantMessageUids.add(uid);
+ mPackageManager.updatePermissionFlags(POST_NOTIFICATIONS, packageName,
+ FLAG_PERMISSION_REVIEW_REQUIRED, 0, user);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Do nothing
+ }
}
private void launchNotificationPermissionRequestDialog(String pkgName, UserHandle user,
@@ -1142,8 +1186,10 @@
if (pkg == null || pkg.getPackageName() == null
|| Objects.equals(pkgName, mPackageManager.getPermissionControllerPackageName())
|| pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) {
- Slog.w(LOG_TAG, "Cannot check for Notification prompt, no package for "
- + pkgName + " or pkg is Permission Controller");
+ if (pkg == null) {
+ Slog.w(LOG_TAG, "Cannot check for Notification prompt, no package for "
+ + pkgName);
+ }
return false;
}
@@ -1175,9 +1221,10 @@
}
boolean hasCreatedNotificationChannels = mNotificationManager
.getNumNotificationChannelsForPackage(pkg.getPackageName(), uid, true) > 0;
- boolean needsReview = (mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, pkgName,
- user) & FLAG_PERMISSION_REVIEW_REQUIRED) != 0;
- return hasCreatedNotificationChannels && needsReview;
+ int flags = mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, pkgName, user);
+ boolean explicitlySet = (flags & PermissionManager.EXPLICIT_SET_FLAGS) != 0;
+ boolean needsReview = (flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0;
+ return hasCreatedNotificationChannels && (needsReview || !explicitlySet);
}
}
}
diff --git a/services/core/java/com/android/server/power/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java
index cea84b5..2d2bad2 100644
--- a/services/core/java/com/android/server/power/LowPowerStandbyController.java
+++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java
@@ -42,6 +42,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
+import com.android.server.net.NetworkPolicyManagerInternal;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -394,7 +395,11 @@
/** Notify other system components about the updated Low Power Standby active state */
private void notifyActiveChanged(boolean active) {
final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+ final NetworkPolicyManagerInternal npmi = LocalServices.getService(
+ NetworkPolicyManagerInternal.class);
+
pmi.setLowPowerStandbyActive(active);
+ npmi.setLowPowerStandbyActive(active);
}
@VisibleForTesting
@@ -580,7 +585,10 @@
private void notifyAllowlistChanged(int[] allowlistUids) {
final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+ final NetworkPolicyManagerInternal npmi = LocalServices.getService(
+ NetworkPolicyManagerInternal.class);
pmi.setLowPowerStandbyAllowlist(allowlistUids);
+ npmi.setLowPowerStandbyAllowlist(allowlistUids);
}
private final class LocalService extends LowPowerStandbyControllerInternal {
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index f6a9359..adee325 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -2392,7 +2392,8 @@
metrics.unaccountedKb,
metrics.gpuTotalUsageKb,
metrics.gpuPrivateAllocationsKb,
- metrics.dmaBufTotalExportedKb));
+ metrics.dmaBufTotalExportedKb,
+ metrics.shmemKb));
return StatsManager.PULL_SUCCESS;
}
@@ -4490,6 +4491,9 @@
}
int pullMediaCapabilitiesStats(int atomTag, List<StatsEvent> pulledData) {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+ return StatsManager.PULL_SKIP;
+ }
AudioManager audioManager = mContext.getSystemService(AudioManager.class);
if (audioManager == null) {
return StatsManager.PULL_SKIP;
diff --git a/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java b/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java
index 30b6e68..9ebf59e 100644
--- a/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java
+++ b/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java
@@ -82,6 +82,7 @@
result.vmallocUsedKb = (int) mInfos[Debug.MEMINFO_VM_ALLOC_USED];
result.pageTablesKb = (int) mInfos[Debug.MEMINFO_PAGE_TABLES];
result.kernelStackKb = (int) mInfos[Debug.MEMINFO_KERNEL_STACK];
+ result.shmemKb = (int) mInfos[Debug.MEMINFO_SHMEM];
result.totalIonKb = totalIonKb;
result.gpuTotalUsageKb = gpuTotalUsageKb;
result.gpuPrivateAllocationsKb = gpuPrivateAllocationsKb;
@@ -95,6 +96,7 @@
public int vmallocUsedKb;
public int pageTablesKb;
public int kernelStackKb;
+ public int shmemKb;
public int totalIonKb;
public int gpuTotalUsageKb;
public int gpuPrivateAllocationsKb;
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 94f483c..2049f3d 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -2031,11 +2031,14 @@
@Override
public void updateMediaTapToTransferReceiverDisplay(
@StatusBarManager.MediaTransferReceiverState int displayState,
- MediaRoute2Info routeInfo) {
+ MediaRoute2Info routeInfo,
+ @Nullable Icon appIcon,
+ @Nullable CharSequence appName) {
enforceMediaContentControl();
if (mBar != null) {
try {
- mBar.updateMediaTapToTransferReceiverDisplay(displayState, routeInfo);
+ mBar.updateMediaTapToTransferReceiverDisplay(
+ displayState, routeInfo, appIcon, appName);
} catch (RemoteException e) {
Slog.e(TAG, "updateMediaTapToTransferReceiverDisplay", e);
}
diff --git a/services/core/java/com/android/server/tracing/TracingServiceProxy.java b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
index 27c0bee..10e868d 100644
--- a/services/core/java/com/android/server/tracing/TracingServiceProxy.java
+++ b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
@@ -15,6 +15,13 @@
*/
package com.android.server.tracing;
+import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT;
+import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_BEGIN;
+import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_BIND_PERM_INCORRECT;
+import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_COMM_ERROR;
+import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_HANDOFF;
+import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_PERM_MISSING;
+
import android.Manifest;
import android.annotation.NonNull;
import android.content.ComponentName;
@@ -39,6 +46,7 @@
import android.util.Slog;
import com.android.internal.infra.ServiceConnector;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.SystemService;
import java.io.IOException;
@@ -71,6 +79,17 @@
private static final String INTENT_ACTION_NOTIFY_SESSION_STOLEN =
"com.android.traceur.NOTIFY_SESSION_STOLEN";
+ private static final int REPORT_BEGIN =
+ TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_BEGIN;
+ private static final int REPORT_SVC_HANDOFF =
+ TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_HANDOFF;
+ private static final int REPORT_BIND_PERM_INCORRECT =
+ TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_BIND_PERM_INCORRECT;
+ private static final int REPORT_SVC_PERM_MISSING =
+ TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_PERM_MISSING;
+ private static final int REPORT_SVC_COMM_ERROR =
+ TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_COMM_ERROR;
+
private final Context mContext;
private final PackageManager mPackageManager;
private final LruCache<ComponentName, ServiceConnector<IMessenger>> mCachedReporterServices;
@@ -134,17 +153,24 @@
}
private void reportTrace(@NonNull TraceReportParams params) {
+ FrameworkStatsLog.write(TRACING_SERVICE_REPORT_EVENT, REPORT_BEGIN,
+ params.uuidLsb, params.uuidMsb);
+
// We don't need to do any permission checks on the caller because access
// to this service is guarded by SELinux.
ComponentName component = new ComponentName(params.reporterPackageName,
params.reporterClassName);
if (!hasBindServicePermission(component)) {
+ FrameworkStatsLog.write(TRACING_SERVICE_REPORT_EVENT, REPORT_BIND_PERM_INCORRECT,
+ params.uuidLsb, params.uuidMsb);
return;
}
boolean hasDumpPermission = hasPermission(component, Manifest.permission.DUMP);
boolean hasUsageStatsPermission = hasPermission(component,
Manifest.permission.PACKAGE_USAGE_STATS);
if (!hasDumpPermission || !hasUsageStatsPermission) {
+ FrameworkStatsLog.write(TRACING_SERVICE_REPORT_EVENT, REPORT_SVC_PERM_MISSING,
+ params.uuidLsb, params.uuidMsb);
return;
}
final long ident = Binder.clearCallingIdentity();
@@ -178,8 +204,13 @@
message.what = TraceReportService.MSG_REPORT_TRACE;
message.obj = params;
messenger.send(message);
+
+ FrameworkStatsLog.write(TRACING_SERVICE_REPORT_EVENT, REPORT_SVC_HANDOFF,
+ params.uuidLsb, params.uuidMsb);
}).whenComplete((res, err) -> {
if (err != null) {
+ FrameworkStatsLog.write(TRACING_SERVICE_REPORT_EVENT, REPORT_SVC_COMM_ERROR,
+ params.uuidLsb, params.uuidMsb);
Slog.e(TAG, "Failed to report trace", err);
}
try {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 56985af..87c8a79 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -429,7 +429,8 @@
if (needsExtraction) {
extractColors(wallpaper);
}
- notifyColorListeners(wallpaper.primaryColors, which, wallpaper.userId, displayId);
+ notifyColorListeners(getAdjustedWallpaperColorsOnDimming(wallpaper), which,
+ wallpaper.userId, displayId);
}
private static <T extends IInterface> boolean emptyCallbackList(RemoteCallbackList<T> list) {
@@ -1519,7 +1520,6 @@
if (mWallpaper.mWallpaperDimAmount != 0f) {
try {
connector.mEngine.applyDimming(mWallpaper.mWallpaperDimAmount);
- notifyWallpaperColorsChanged(mWallpaper, FLAG_SYSTEM);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to dim wallpaper", e);
}
@@ -2711,8 +2711,31 @@
extractColors(wallpaperData);
}
+ return getAdjustedWallpaperColorsOnDimming(wallpaperData);
+ }
+
+ /**
+ * Gets the adjusted {@link WallpaperColors} if the wallpaper colors were not extracted from
+ * bitmap (i.e. it's a live wallpaper) and the dim amount is not 0. If these conditions apply,
+ * default to using color hints that do not support dark theme and dark text.
+ *
+ * @param wallpaperData WallpaperData containing the WallpaperColors and mWallpaperDimAmount
+ */
+ WallpaperColors getAdjustedWallpaperColorsOnDimming(WallpaperData wallpaperData) {
synchronized (mLock) {
- return wallpaperData.primaryColors;
+ WallpaperColors wallpaperColors = wallpaperData.primaryColors;
+
+ if (wallpaperColors != null
+ && (wallpaperColors.getColorHints() & WallpaperColors.HINT_FROM_BITMAP) == 0
+ && wallpaperData.mWallpaperDimAmount != 0f) {
+ int adjustedColorHints = wallpaperColors.getColorHints()
+ & ~WallpaperColors.HINT_SUPPORTS_DARK_TEXT
+ & ~WallpaperColors.HINT_SUPPORTS_DARK_THEME;
+ return new WallpaperColors(
+ wallpaperColors.getPrimaryColor(), wallpaperColors.getSecondaryColor(),
+ wallpaperColors.getTertiaryColor(), adjustedColorHints);
+ }
+ return wallpaperColors;
}
}
@@ -2782,6 +2805,7 @@
wallpaper.fromForegroundApp = fromForegroundApp;
wallpaper.cropHint.set(cropHint);
wallpaper.allowBackup = allowBackup;
+ wallpaper.mWallpaperDimAmount = getWallpaperDimAmount();
}
return pfd;
} finally {
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index ec4c58f..d526845 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1088,7 +1088,7 @@
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null && r.isState(RESUMED, PAUSING)) {
r.mDisplayContent.mAppTransition.overridePendingAppTransition(
- packageName, enterAnim, exitAnim, null, null,
+ packageName, enterAnim, exitAnim, backgroundColor, null, null,
r.mOverrideTaskTransition);
r.mTransitionController.setOverrideAnimation(
TransitionInfo.AnimationOptions.makeCustomAnimOptions(packageName,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index bb33e6f..6ef6c7a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2504,10 +2504,20 @@
}
void removeStartingWindow() {
+ boolean prevEligibleForLetterboxEducation = isEligibleForLetterboxEducation();
+
if (transferSplashScreenIfNeeded()) {
return;
}
removeStartingWindowAnimation(true /* prepareAnimation */);
+
+ // TODO(b/215316431): Add tests
+ final Task task = getTask();
+ if (prevEligibleForLetterboxEducation != isEligibleForLetterboxEducation()
+ && task != null) {
+ // Trigger TaskInfoChanged to update the letterbox education.
+ task.dispatchTaskInfoChangedIfNeeded(true /* force */);
+ }
}
void removeStartingWindowAnimation(boolean prepareAnimation) {
@@ -4506,6 +4516,7 @@
pendingOptions.getPackageName(),
pendingOptions.getCustomEnterResId(),
pendingOptions.getCustomExitResId(),
+ pendingOptions.getCustomBackgroundColor(),
pendingOptions.getAnimationStartedListener(),
pendingOptions.getAnimationFinishedListener(),
pendingOptions.getOverrideTaskTransition());
@@ -7701,6 +7712,8 @@
* <li>The activity is eligible for fixed orientation letterbox.
* <li>The activity is in fullscreen.
* <li>The activity is portrait-only.
+ * <li>The activity doesn't have a starting window (education should only be displayed
+ * once the starting window is removed in {@link #removeStartingWindow}).
* </ul>
*/
// TODO(b/215316431): Add tests
@@ -7708,7 +7721,8 @@
return mWmService.mLetterboxConfiguration.getIsEducationEnabled()
&& mIsEligibleForFixedOrientationLetterbox
&& getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- && getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT;
+ && getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT
+ && mStartingWindow == null;
}
/**
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5164bf0..fb3d17a 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2003,8 +2003,8 @@
* @param targetTask the target task for launching activity, which could be different from
* the one who hosting the embedding.
*/
- private boolean canEmbedActivity(@NonNull TaskFragment taskFragment, ActivityRecord starting,
- boolean newTask, Task targetTask) {
+ private boolean canEmbedActivity(@NonNull TaskFragment taskFragment,
+ @NonNull ActivityRecord starting, boolean newTask, Task targetTask) {
final Task hostTask = taskFragment.getTask();
if (hostTask == null) {
return false;
@@ -2016,8 +2016,7 @@
return true;
}
- // Not allowed embedding an activity of another app.
- if (hostUid != starting.getUid()) {
+ if (!taskFragment.isAllowedToEmbedActivity(starting)) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index 5899a4e..a743091 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -42,6 +42,22 @@
boolean getShowWallpaper();
/**
+ * @return Whether we should show a background behind the animating windows.
+ * @see Animation#getShowBackground()
+ */
+ default boolean getShowBackground() {
+ return false;
+ }
+
+ /**
+ * @return The background color to use during an animation if getShowBackground returns true.
+ * @see Animation#getBackgroundColor()
+ */
+ default int getBackgroundColor() {
+ return 0;
+ }
+
+ /**
* Requests to start the animation.
*
* @param animationLeash The surface to run the animation on. See {@link SurfaceAnimator} for an
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 56adcfd..05efb29 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -93,6 +93,7 @@
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE;
+import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
@@ -198,6 +199,7 @@
private IRemoteCallback mAnimationFinishedCallback;
private int mNextAppTransitionEnter;
private int mNextAppTransitionExit;
+ private @ColorInt int mNextAppTransitionBackgroundColor;
private int mNextAppTransitionInPlace;
private boolean mNextAppTransitionIsSync;
@@ -829,6 +831,7 @@
break;
}
a = animAttr != 0 ? loadAnimationAttr(lp, animAttr, transit) : null;
+
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
"applyAnimation: anim=%s animAttr=0x%x transit=%s isEntrance=%b "
+ "Callers=%s",
@@ -836,6 +839,11 @@
Debug.getCallers(3));
}
setAppTransitionFinishedCallbackIfNeeded(a);
+
+ if (mNextAppTransitionBackgroundColor != 0) {
+ a.setBackgroundColor(mNextAppTransitionBackgroundColor);
+ }
+
return a;
}
@@ -861,14 +869,15 @@
}
void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim,
- IRemoteCallback startedCallback, IRemoteCallback endedCallback,
- boolean overrideTaskTransaction) {
+ @ColorInt int backgroundColor, IRemoteCallback startedCallback,
+ IRemoteCallback endedCallback, boolean overrideTaskTransaction) {
if (canOverridePendingAppTransition()) {
clear();
mNextAppTransitionOverrideRequested = true;
mNextAppTransitionPackage = packageName;
mNextAppTransitionEnter = enterAnim;
mNextAppTransitionExit = exitAnim;
+ mNextAppTransitionBackgroundColor = backgroundColor;
postAnimationCallback();
mNextAppTransitionCallback = startedCallback;
mAnimationFinishedCallback = endedCallback;
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 5a2cf17..afa4f19 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -544,6 +544,11 @@
return getActivityType() == ACTIVITY_TYPE_RECENTS;
}
+ final boolean isActivityTypeHomeOrRecents() {
+ final int activityType = getActivityType();
+ return activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS;
+ }
+
public boolean isActivityTypeAssistant() {
return getActivityType() == ACTIVITY_TYPE_ASSISTANT;
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 092cff3..818e4f6 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1054,6 +1054,7 @@
mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier);
mAppTransition.registerListenerLocked(mFixedRotationTransitionListener);
mAppTransitionController = new AppTransitionController(mWmService, this);
+ mTransitionController.registerLegacyListener(mFixedRotationTransitionListener);
mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this);
final InputChannel inputChannel = mWmService.mInputManager.monitorInput(
@@ -1111,6 +1112,7 @@
t.remove(mSurfaceControl);
mLastSurfacePosition.set(0, 0);
+ mLastDeltaRotation = Surface.ROTATION_0;
configureSurfaces(t);
@@ -1604,7 +1606,7 @@
*/
@Rotation
int rotationForActivityInDifferentOrientation(@NonNull ActivityRecord r) {
- if (mTransitionController.isShellTransitionsEnabled()) {
+ if (mTransitionController.useShellTransitionsRotation()) {
return ROTATION_UNDEFINED;
}
if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM) {
@@ -1645,18 +1647,30 @@
// It has been set and not yet finished.
return true;
}
- if (!r.occludesParent() || r.isVisible()) {
+ if (!r.occludesParent()) {
// While entering or leaving a translucent or floating activity (e.g. dialog style),
// there is a visible activity in the background. Then it still needs rotation animation
// to cover the activity configuration change.
return false;
}
+ if (mTransitionController.isShellTransitionsEnabled()
+ ? mTransitionController.wasVisibleAtStart(r) : r.isVisible()) {
+ // If activity is already visible, then it's not "launching". However, shell-transitions
+ // will make it visible immediately.
+ return false;
+ }
if (checkOpening) {
- if (!mAppTransition.isTransitionSet() || !mOpeningApps.contains(r)) {
- // Apply normal rotation animation in case of the activity set different requested
- // orientation without activity switch, or the transition is unset due to starting
- // window was transferred ({@link #mSkipAppTransitionAnimation}).
- return false;
+ if (mTransitionController.isShellTransitionsEnabled()) {
+ if (!mTransitionController.isCollecting(r)) {
+ return false;
+ }
+ } else {
+ if (!mAppTransition.isTransitionSet() || !mOpeningApps.contains(r)) {
+ // Apply normal rotation animation in case of the activity set different
+ // requested orientation without activity switch, or the transition is unset due
+ // to starting window was transferred ({@link #mSkipAppTransitionAnimation}).
+ return false;
+ }
}
if (r.isState(RESUMED) && !r.getRootTask().mInResumeTopActivity) {
// If the activity is executing or has done the lifecycle callback, use normal
@@ -1733,15 +1747,19 @@
}
void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) {
+ final boolean useAsyncRotation = !mTransitionController.isShellTransitionsEnabled();
if (mFixedRotationLaunchingApp == null && r != null) {
- mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation);
- startAsyncRotation(
- // Delay the hide animation to avoid blinking by clicking navigation bar that
- // may toggle fixed rotation in a short time.
- r == mFixedRotationTransitionListener.mAnimatingRecents /* shouldDebounce */);
+ mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this,
+ rotation);
+ if (useAsyncRotation) {
+ startAsyncRotation(
+ // Delay the hide animation to avoid blinking by clicking navigation bar
+ // that may toggle fixed rotation in a short time.
+ r == mFixedRotationTransitionListener.mAnimatingRecents);
+ }
} else if (mFixedRotationLaunchingApp != null && r == null) {
mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this);
- finishAsyncRotationIfPossible();
+ if (useAsyncRotation) finishAsyncRotationIfPossible();
}
mFixedRotationLaunchingApp = r;
}
@@ -1760,7 +1778,8 @@
if (prevRotatedLaunchingApp != null
&& prevRotatedLaunchingApp.getWindowConfiguration().getRotation() == rotation
// It is animating so we can expect there will have a transition callback.
- && prevRotatedLaunchingApp.isAnimating(TRANSITION | PARENTS)) {
+ && (prevRotatedLaunchingApp.isAnimating(TRANSITION | PARENTS)
+ || mTransitionController.inTransition(prevRotatedLaunchingApp))) {
// It may be the case that multiple activities launch consecutively. Because their
// rotation are the same, the transformed state can be shared to avoid duplicating
// the heavy operations. This also benefits that the states of multiple activities
@@ -1798,6 +1817,7 @@
}
// Update directly because the app which will change the orientation of display is ready.
if (mDisplayRotation.updateOrientation(getOrientation(), false /* forceUpdate */)) {
+ mTransitionController.setSeamlessRotation(this);
sendNewConfiguration();
return;
}
@@ -3129,6 +3149,7 @@
mChangingContainers.clear();
mUnknownAppVisibilityController.clear();
mAppTransition.removeAppTransitionTimeoutCallbacks();
+ mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener);
handleAnimatingStoppedAndTransition();
mWmService.stopFreezingDisplayLocked();
super.removeImmediately();
@@ -4062,7 +4083,7 @@
final boolean renewImeSurface = mImeScreenshot == null
|| mImeScreenshot.getWidth() != mInputMethodWindow.getFrame().width()
|| mImeScreenshot.getHeight() != mInputMethodWindow.getFrame().height();
- if (task != null && !task.isHomeOrRecentsRootTask()) {
+ if (task != null && !task.isActivityTypeHomeOrRecents()) {
SurfaceControl.ScreenshotHardwareBuffer imeBuffer = renewImeSurface
? mWmService.mTaskSnapshotController.snapshotImeFromAttachedTask(task)
: null;
@@ -5761,7 +5782,10 @@
mCurrentOverrideConfigurationChanges = currOverrideConfig.diff(overrideConfiguration);
super.onRequestedOverrideConfigurationChanged(overrideConfiguration);
mCurrentOverrideConfigurationChanges = 0;
- mWmService.setNewDisplayOverrideConfiguration(currOverrideConfig, this);
+ if (mWaitingForConfig) {
+ mWaitingForConfig = false;
+ mWmService.mLastFinishedFreezeSource = "new-config";
+ }
mAtmService.addWindowLayoutReasons(
ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
}
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 8db4306..2ab08e6 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -156,6 +156,7 @@
final IWindow mClient;
@Nullable final WindowState mHostWindowState;
@Nullable final ActivityRecord mHostActivityRecord;
+ final String mName;
final int mOwnerUid;
final int mOwnerPid;
final WindowManagerService mWmService;
@@ -186,7 +187,7 @@
*/
EmbeddedWindow(Session session, WindowManagerService service, IWindow clientToken,
WindowState hostWindowState, int ownerUid, int ownerPid, int windowType,
- int displayId, IBinder focusGrantToken) {
+ int displayId, IBinder focusGrantToken, String inputHandleName) {
mSession = session;
mWmService = service;
mClient = clientToken;
@@ -198,14 +199,15 @@
mWindowType = windowType;
mDisplayId = displayId;
mFocusGrantToken = focusGrantToken;
+ final String hostWindowName =
+ (mHostWindowState != null) ? "-" + mHostWindowState.getWindowTag().toString()
+ : "";
+ mName = "Embedded{" + inputHandleName + hostWindowName + "}";
}
@Override
public String toString() {
- final String hostWindowName = (mHostWindowState != null)
- ? mHostWindowState.getWindowTag().toString() : "Internal";
- return "EmbeddedWindow{ u" + UserHandle.getUserId(mOwnerUid) + " " + hostWindowName
- + "}";
+ return mName;
}
InputApplicationHandle getApplicationHandle() {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 2f95119..d28dfd5 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -144,15 +144,21 @@
getStatusControlTarget(focusedWin, false /* fake */);
final InsetsControlTarget navControlTarget =
getNavControlTarget(focusedWin, false /* fake */);
+ final WindowState notificationShade = mPolicy.getNotificationShade();
+ final WindowState topApp = mPolicy.getTopFullscreenOpaqueWindow();
mStateController.onBarControlTargetChanged(
statusControlTarget,
statusControlTarget == mDummyControlTarget
? getStatusControlTarget(focusedWin, true /* fake */)
- : null,
+ : statusControlTarget == notificationShade
+ ? getStatusControlTarget(topApp, true /* fake */)
+ : null,
navControlTarget,
navControlTarget == mDummyControlTarget
? getNavControlTarget(focusedWin, true /* fake */)
- : null);
+ : navControlTarget == notificationShade
+ ? getNavControlTarget(topApp, true /* fake */)
+ : null);
mStatusBar.updateVisibility(statusControlTarget, ITYPE_STATUS_BAR);
mNavBar.updateVisibility(navControlTarget, ITYPE_NAVIGATION_BAR);
}
diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
index a3eb980..61f9fe2 100644
--- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
@@ -51,6 +51,16 @@
}
@Override
+ public boolean getShowBackground() {
+ return mSpec.getShowBackground();
+ }
+
+ @Override
+ public int getBackgroundColor() {
+ return mSpec.getBackgroundColor();
+ }
+
+ @Override
public void startAnimation(SurfaceControl animationLeash, Transaction t,
@AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
mAnimator.startAnimation(mSpec, animationLeash, t,
@@ -97,6 +107,20 @@
}
/**
+ * @see AnimationAdapter#getShowBackground
+ */
+ default boolean getShowBackground() {
+ return false;
+ }
+
+ /**
+ * @see AnimationAdapter#getBackgroundColor
+ */
+ default int getBackgroundColor() {
+ return 0;
+ }
+
+ /**
* @see AnimationAdapter#getStatusBarTransitionsStartTime
*/
default long calculateStatusBarTransitionStartTime() {
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index d4a7a5d..e259238 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -529,7 +529,7 @@
*/
void notifyTaskPersisterLocked(Task task, boolean flush) {
final Task rootTask = task != null ? task.getRootTask() : null;
- if (rootTask != null && rootTask.isHomeOrRecentsRootTask()) {
+ if (rootTask != null && rootTask.isActivityTypeHomeOrRecents()) {
// Never persist the home or recents task.
return;
}
@@ -563,7 +563,7 @@
private static boolean shouldPersistTaskLocked(Task task) {
final Task rootTask = task.getRootTask();
- return task.isPersistable && (rootTask == null || !rootTask.isHomeOrRecentsRootTask());
+ return task.isPersistable && (rootTask == null || !rootTask.isActivityTypeHomeOrRecents());
}
void onSystemReadyLocked() {
@@ -994,7 +994,7 @@
}
final Task rootTask = task.getRootTask();
if ((task.isPersistable || task.inRecents)
- && (rootTask == null || !rootTask.isHomeOrRecentsRootTask())) {
+ && (rootTask == null || !rootTask.isActivityTypeHomeOrRecents())) {
if (TaskPersister.DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
persistentTaskIds.add(task.mTaskId);
} else {
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index c1835a0..30906e5 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -613,7 +613,7 @@
final TaskFragment adjacentTask = task.getRootTask().getAdjacentTaskFragment();
final boolean inSplitScreen = task.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
&& adjacentTask != null;
- if (task.isHomeOrRecentsRootTask()
+ if (task.isActivityTypeHomeOrRecents()
// Skip if the task is in split screen and in landscape.
|| (inSplitScreen && isDisplayLandscape)
// Skip if the task is the top task in split screen.
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 76a7981..d535018 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -637,36 +637,6 @@
return null;
}
- /**
- * Set new display override config. If called for the default display, global configuration
- * will also be updated.
- */
- void setDisplayOverrideConfigurationIfNeeded(Configuration newConfiguration,
- @NonNull DisplayContent displayContent) {
-
- final Configuration currentConfig = displayContent.getRequestedOverrideConfiguration();
- final boolean configChanged = currentConfig.diff(newConfiguration) != 0;
- if (!configChanged) {
- return;
- }
-
- displayContent.onRequestedOverrideConfigurationChanged(newConfiguration);
-
- if (displayContent.getDisplayId() == DEFAULT_DISPLAY) {
- // Override configuration of the default display duplicates global config. In this case
- // we also want to update the global config.
- setGlobalConfigurationIfNeeded(newConfiguration);
- }
- }
-
- private void setGlobalConfigurationIfNeeded(Configuration newConfiguration) {
- final boolean configChanged = getConfiguration().diff(newConfiguration) != 0;
- if (!configChanged) {
- return;
- }
- onConfigurationChanged(newConfiguration);
- }
-
@Override
void dispatchConfigurationToChild(DisplayContent child, Configuration config) {
if (child.isDefaultDisplay) {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 7c5144e..de8ea8c 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -837,7 +837,7 @@
@Override
public void grantInputChannel(int displayId, SurfaceControl surface,
IWindow window, IBinder hostInputToken, int flags, int privateFlags, int type,
- IBinder focusGrantToken, InputChannel outInputChannel) {
+ IBinder focusGrantToken, String inputHandleName, InputChannel outInputChannel) {
if (hostInputToken == null && !mCanAddInternalSystemWindow) {
// Callers without INTERNAL_SYSTEM_WINDOW permission cannot grant input channel to
// embedded windows without providing a host window input token
@@ -853,7 +853,8 @@
try {
mService.grantInputChannel(this, mUid, mPid, displayId, surface, window, hostInputToken,
flags, mCanAddInternalSystemWindow ? privateFlags : 0,
- mCanAddInternalSystemWindow ? type : 0, focusGrantToken, outInputChannel);
+ mCanAddInternalSystemWindow ? type : 0, focusGrantToken, inputHandleName,
+ outInputChannel);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b84ef77..6df54cd 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2711,7 +2711,7 @@
// they extend past their root task and sysui uses the root task surface to control
// cropping.
// TODO(b/158242495): get rid of this when drag/drop can use surface bounds.
- if (isActivityTypeHome() || isActivityTypeRecents()) {
+ if (isActivityTypeHomeOrRecents()) {
// Make sure this is the top-most non-organizer root task (if not top-most, it means
// another translucent task could be above this, so this needs to stay cropped.
final Task rootTask = getRootTask();
@@ -3306,7 +3306,7 @@
if (control != null) {
// We let the transition to be controlled by RecentsAnimation, and callback task's
// RemoteAnimationTarget for remote runner to animate.
- if (enter && !isHomeOrRecentsRootTask()) {
+ if (enter && !isActivityTypeHomeOrRecents()) {
ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
"applyAnimationUnchecked, control: %s, task: %s, transit: %s",
control, asTask(), AppTransition.appTransitionOldToString(transit));
@@ -4591,10 +4591,6 @@
!PRESERVE_WINDOWS);
}
- final boolean isHomeOrRecentsRootTask() {
- return isActivityTypeHome() || isActivityTypeRecents();
- }
-
final boolean isOnHomeDisplay() {
return getDisplayId() == DEFAULT_DISPLAY;
}
@@ -5044,7 +5040,7 @@
// The transition animation and starting window are not needed if {@code allowMoveToFront}
// is false, because the activity won't be visible.
- if ((!isHomeOrRecentsRootTask() || hasActivity()) && allowMoveToFront) {
+ if ((!isActivityTypeHomeOrRecents() || hasActivity()) && allowMoveToFront) {
final DisplayContent dc = mDisplayContent;
if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,
"Prepare open transition: starting " + r);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index a59d7b6..c880aba 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -25,6 +25,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -95,6 +96,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -102,6 +104,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -505,6 +508,56 @@
}
/**
+ * Checks if the organized task fragment is allowed to have the specified activity, which is
+ * allowed if an activity allows embedding in untrusted mode, or if the trusted mode can be
+ * enabled.
+ * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord)
+ */
+ boolean isAllowedToEmbedActivity(@NonNull ActivityRecord a) {
+ if ((a.info.flags & FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING)
+ == FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING) {
+ return true;
+ }
+
+ return isAllowedToEmbedActivityInTrustedMode(a);
+ }
+
+ /**
+ * Checks if the organized task fragment is allowed to embed activity in fully trusted mode,
+ * which means that all transactions are allowed. This is supported in the following cases:
+ * <li>the activity belongs to the same app as the organizer host;</li>
+ * <li>the activity has declared the organizer host as trusted explicitly via known
+ * certificate.</li>
+ */
+ private boolean isAllowedToEmbedActivityInTrustedMode(@NonNull ActivityRecord a) {
+ if (mTaskFragmentOrganizerUid == a.getUid()) {
+ // Activities from the same UID can be embedded freely by the host.
+ return true;
+ }
+
+ Set<String> knownActivityEmbeddingCerts = a.info.getKnownActivityEmbeddingCerts();
+ if (knownActivityEmbeddingCerts.isEmpty()) {
+ // An application must either declare that it allows untrusted embedding, or specify
+ // a set of app certificates that are allowed to embed it in trusted mode.
+ return false;
+ }
+
+ AndroidPackage hostPackage = mAtmService.getPackageManagerInternalLocked()
+ .getPackage(mTaskFragmentOrganizerUid);
+
+ return hostPackage != null && hostPackage.getSigningDetails().hasAncestorOrSelfWithDigest(
+ knownActivityEmbeddingCerts);
+ }
+
+ /**
+ * Checks if all activities in the task fragment are allowed to be embedded in trusted mode.
+ * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord)
+ */
+ boolean isAllowedToBeEmbeddedInTrustedMode() {
+ return forAllActivities(this::isAllowedToEmbedActivityInTrustedMode);
+ }
+
+ /**
* Returns the TaskFragment that is being organized, which could be this or the ascendant
* TaskFragment.
*/
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 123ca88..19f921d 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -294,14 +294,28 @@
}
}
- /** Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. */
+ /**
+ * Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. Returns
+ * {@code null} if it doesn't, or if the organizer has activity(ies) embedded in untrusted mode.
+ */
@Nullable
public RemoteAnimationDefinition getRemoteAnimationDefinition(
ITaskFragmentOrganizer organizer) {
synchronized (mGlobalLock) {
final TaskFragmentOrganizerState organizerState =
mTaskFragmentOrganizerState.get(organizer.asBinder());
- return organizerState != null ? organizerState.mRemoteAnimationDefinition : null;
+ if (organizerState == null) {
+ return null;
+ }
+ for (TaskFragment tf : organizerState.mOrganizedTaskFragments) {
+ if (!tf.isAllowedToBeEmbeddedInTrustedMode()) {
+ // Disable client-driven animations for organizer if at least one of the
+ // embedded task fragments is not embedding in trusted mode.
+ // TODO(b/197364677): replace with a stub or Shell-driven one instead of skip?
+ return null;
+ }
+ }
+ return organizerState.mRemoteAnimationDefinition;
}
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index d86382d..8b8fd2c 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -209,6 +209,12 @@
return mTransientLaunches != null && mTransientLaunches.contains(activity);
}
+ void setSeamlessRotation(@NonNull WindowContainer wc) {
+ final ChangeInfo info = mChanges.get(wc);
+ if (info == null) return;
+ info.mFlags = info.mFlags | ChangeInfo.FLAG_SEAMLESS_ROTATION;
+ }
+
@VisibleForTesting
int getSyncId() {
return mSyncId;
@@ -819,7 +825,7 @@
// the bottom of the screen, so we need to animate it.
for (int i = 0; i < mTargets.size(); ++i) {
final Task task = mTargets.get(i).asTask();
- if (task == null || !task.isHomeOrRecentsRootTask()) continue;
+ if (task == null || !task.isActivityTypeHomeOrRecents()) continue;
animate = task.isVisibleRequested();
break;
}
@@ -1122,6 +1128,15 @@
// hardware-screen-level surfaces.
return asDC.getWindowingLayer();
}
+ if (!wc.mTransitionController.useShellTransitionsRotation()) {
+ final WindowToken asToken = wc.asWindowToken();
+ if (asToken != null) {
+ // WindowTokens can have a fixed-rotation applied to them. In the current
+ // implementation this fact is hidden from the player, so we must create a leash.
+ final SurfaceControl leash = asToken.getOrCreateFixedRotationLeash();
+ if (leash != null) return leash;
+ }
+ }
return wc.getSurfaceControl();
}
@@ -1224,6 +1239,8 @@
final ActivityRecord topMostActivity = task.getTopMostActivity();
change.setAllowEnterPip(topMostActivity != null
&& topMostActivity.checkEnterPictureInPictureAppOpsState());
+ } else if ((info.mFlags & ChangeInfo.FLAG_SEAMLESS_ROTATION) != 0) {
+ change.setRotationAnimation(ROTATION_ANIMATION_SEAMLESS);
}
final ActivityRecord activityRecord = target.asActivityRecord();
if (activityRecord != null) {
@@ -1337,6 +1354,21 @@
@VisibleForTesting
static class ChangeInfo {
+ private static final int FLAG_NONE = 0;
+
+ /**
+ * When set, the associated WindowContainer has been explicitly requested to be a
+ * seamless rotation. This is currently only used by DisplayContent during fixed-rotation.
+ */
+ private static final int FLAG_SEAMLESS_ROTATION = 1;
+
+ @IntDef(prefix = { "FLAG_" }, value = {
+ FLAG_NONE,
+ FLAG_SEAMLESS_ROTATION
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Flag {}
+
// Usually "post" change state.
WindowContainer mParent;
@@ -1350,6 +1382,9 @@
int mRotation = ROTATION_UNDEFINED;
@ActivityInfo.Config int mKnownConfigChanges;
+ /** These are just extra info. They aren't used for change-detection. */
+ @Flag int mFlags = FLAG_NONE;
+
ChangeInfo(@NonNull WindowContainer origState) {
mVisible = origState.isVisibleRequested();
mWindowingMode = origState.getWindowingMode();
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 60307ce..3d9d824 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -34,6 +34,7 @@
import android.os.IRemoteCallback;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -58,6 +59,10 @@
class TransitionController {
private static final String TAG = "TransitionController";
+ /** Whether to use shell-transitions rotation instead of fixed-rotation. */
+ private static final boolean SHELL_TRANSITIONS_ROTATION =
+ SystemProperties.getBoolean("persist.debug.shell_transit_rotate", false);
+
/** The same as legacy APP_TRANSITION_TIMEOUT_MS. */
private static final int DEFAULT_TIMEOUT_MS = 5000;
/** Less duration for CHANGE type because it does not involve app startup. */
@@ -203,6 +208,11 @@
return mTransitionPlayer != null;
}
+ /** @return {@code true} if using shell-transitions rotation instead of fixed-rotation. */
+ boolean useShellTransitionsRotation() {
+ return isShellTransitionsEnabled() && SHELL_TRANSITIONS_ROTATION;
+ }
+
/**
* @return {@code true} if transition is actively collecting changes. This is {@code false}
* once a transition is playing
@@ -260,6 +270,21 @@
return false;
}
+ /**
+ * Temporary work-around to deal with integration of legacy fixed-rotation. Returns whether
+ * the activity was visible before the collecting transition.
+ * TODO: at-least replace the polling mechanism.
+ */
+ boolean wasVisibleAtStart(@NonNull ActivityRecord ar) {
+ if (mCollectingTransition == null) return ar.isVisible();
+ final Transition.ChangeInfo ci = mCollectingTransition.mChanges.get(ar);
+ if (ci == null) {
+ // not part of transition, so use current state.
+ return ar.isVisible();
+ }
+ return ci.mVisible;
+ }
+
@WindowManager.TransitionType
int getCollectingTransitionType() {
return mCollectingTransition != null ? mCollectingTransition.mType : TRANSIT_NONE;
@@ -484,6 +509,11 @@
}
}
+ void setSeamlessRotation(@NonNull WindowContainer wc) {
+ if (mCollectingTransition == null) return;
+ mCollectingTransition.setSeamlessRotation(wc);
+ }
+
void legacyDetachNavigationBarFromApp(@NonNull IBinder token) {
final Transition transition = Transition.fromBinder(token);
if (transition == null || !mPlayingTransitions.contains(transition)) {
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 36bb55e..6ee30bb 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -111,6 +111,14 @@
changed = true;
}
if (mTransitionController.isShellTransitionsEnabled()) {
+ // Apply legacy fixed rotation to wallpaper if it is becoming visible
+ if (!mTransitionController.useShellTransitionsRotation() && changed && visible) {
+ final WindowState wallpaperTarget =
+ mDisplayContent.mWallpaperController.getWallpaperTarget();
+ if (wallpaperTarget != null && wallpaperTarget.mToken.hasFixedRotationTransform()) {
+ linkFixedRotationTransform(wallpaperTarget.mToken);
+ }
+ }
return changed;
}
diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
index 073a508..5bfd546 100644
--- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@@ -80,6 +80,16 @@
}
@Override
+ public boolean getShowBackground() {
+ return mAnimation.getShowBackground();
+ }
+
+ @Override
+ public int getBackgroundColor() {
+ return mAnimation.getBackgroundColor();
+ }
+
+ @Override
public long getDuration() {
return mAnimation.computeDurationHint();
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 8a373bf..e1746cc 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -39,6 +39,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION;
+import static com.android.server.wm.AppTransition.isActivityTransitOld;
import static com.android.server.wm.AppTransition.isTaskTransitOld;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
@@ -78,13 +79,14 @@
import android.util.ArraySet;
import android.util.Pair;
import android.util.Pools;
+import android.util.RotationUtils;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
-import android.view.InsetsState;
import android.view.MagnificationSpec;
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationTarget;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Builder;
import android.view.SurfaceControlViewHost;
@@ -98,6 +100,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
import com.android.server.wm.SurfaceAnimator.Animatable;
@@ -197,6 +200,7 @@
private final Point mTmpPos = new Point();
protected final Point mLastSurfacePosition = new Point();
+ protected @Surface.Rotation int mLastDeltaRotation = Surface.ROTATION_0;
/** Total number of elements in this subtree, including our own hierarchy element. */
private int mTreeWeight = 1;
@@ -473,6 +477,7 @@
t.remove(mSurfaceControl);
// Clear the last position so the new SurfaceControl will get correct position
mLastSurfacePosition.set(0, 0);
+ mLastDeltaRotation = Surface.ROTATION_0;
final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(null)
.setContainerLayer()
@@ -644,6 +649,7 @@
getSyncTransaction().remove(mSurfaceControl);
setSurfaceControl(null);
mLastSurfacePosition.set(0, 0);
+ mLastDeltaRotation = Surface.ROTATION_0;
scheduleAnimation();
}
if (mOverlayHost != null) {
@@ -2786,7 +2792,7 @@
@TransitionOldType int transit, boolean isVoiceInteraction,
@Nullable ArrayList<WindowContainer> sources) {
final Task task = asTask();
- if (task != null && !enter && !task.isHomeOrRecentsRootTask()) {
+ if (task != null && !enter && !task.isActivityTypeHomeOrRecents()) {
final InsetsControlTarget imeTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING);
final boolean isImeLayeringTarget = imeTarget != null && imeTarget.getWindow() != null
&& imeTarget.getWindow().getTask() == task;
@@ -2816,6 +2822,25 @@
}
}
+ final ActivityRecord activityRecord = asActivityRecord();
+ if (activityRecord != null && isActivityTransitOld(transit)
+ && adapter.getShowBackground()) {
+ final @ColorInt int backgroundColorForTransition;
+ if (adapter.getBackgroundColor() != 0) {
+ // If available use the background color provided through getBackgroundColor
+ // which if set originates from a call to overridePendingAppTransition.
+ backgroundColorForTransition = adapter.getBackgroundColor();
+ } else {
+ // Otherwise default to the window's background color if provided through
+ // the theme as the background color for the animation - the top most window
+ // with a valid background color and showBackground set takes precedence.
+ final Task arTask = activityRecord.getTask();
+ backgroundColorForTransition = ColorUtils.setAlphaComponent(
+ arTask.getTaskDescription().getBackgroundColor(), 255);
+ }
+ animationRunnerBuilder.setTaskBackgroundColor(backgroundColorForTransition);
+ }
+
animationRunnerBuilder.build()
.startAnimation(getPendingTransaction(), adapter, !isVisible(),
ANIMATION_TYPE_APP_TRANSITION, thumbnailAdapter);
@@ -3127,12 +3152,43 @@
}
getRelativePosition(mTmpPos);
- if (mTmpPos.equals(mLastSurfacePosition)) {
+ final int deltaRotation = getRelativeDisplayRotation();
+ if (mTmpPos.equals(mLastSurfacePosition) && deltaRotation == mLastDeltaRotation) {
return;
}
t.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y);
+ // set first, since we don't want rotation included in this (for now).
mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y);
+
+ if (mTransitionController.isShellTransitionsEnabled()
+ && !mTransitionController.useShellTransitionsRotation()) {
+ if (deltaRotation != Surface.ROTATION_0) {
+ updateSurfaceRotation(t, deltaRotation, null /* positionLeash */);
+ } else if (deltaRotation != mLastDeltaRotation) {
+ t.setMatrix(mSurfaceControl, 1, 0, 0, 1);
+ }
+ }
+ mLastDeltaRotation = deltaRotation;
+ }
+
+ /**
+ * Updates the surface transform based on a difference in displayed-rotation from its parent.
+ * @param positionLeash If non-null, the rotated position will be set on this surface instead
+ * of the window surface. {@see WindowToken#getOrCreateFixedRotationLeash}.
+ */
+ protected void updateSurfaceRotation(Transaction t, @Surface.Rotation int deltaRotation,
+ @Nullable SurfaceControl positionLeash) {
+ // parent must be non-null otherwise deltaRotation would be 0.
+ RotationUtils.rotateSurface(t, mSurfaceControl, deltaRotation);
+ mTmpPos.set(mLastSurfacePosition.x, mLastSurfacePosition.y);
+ final Rect parentBounds = getParent().getBounds();
+ final boolean flipped = (deltaRotation % 2) != 0;
+ RotationUtils.rotatePoint(mTmpPos, deltaRotation,
+ flipped ? parentBounds.height() : parentBounds.width(),
+ flipped ? parentBounds.width() : parentBounds.height());
+ t.setPosition(positionLeash != null ? positionLeash : mSurfaceControl,
+ mTmpPos.x, mTmpPos.y);
}
@VisibleForTesting
@@ -3170,6 +3226,16 @@
}
}
+ /** @return the difference in displayed-rotation from parent. */
+ @Surface.Rotation
+ int getRelativeDisplayRotation() {
+ final WindowContainer parent = getParent();
+ if (parent == null) return Surface.ROTATION_0;
+ final int rotation = getWindowConfiguration().getDisplayRotation();
+ final int parentRotation = parent.getWindowConfiguration().getDisplayRotation();
+ return RotationUtils.deltaRotation(rotation, parentRotation);
+ }
+
void waitForAllWindowsDrawn() {
forAllWindows(w -> {
w.requestDrawIfNeeded(mWaitingForDrawn);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0f3b903..8536b08 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -421,7 +421,7 @@
"persist.wm.enable_remote_keyguard_animation";
private static final int sEnableRemoteKeyguardAnimation =
- SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 1);
+ SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 2);
/**
* @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
@@ -2893,16 +2893,6 @@
}
}
- void setNewDisplayOverrideConfiguration(Configuration overrideConfig,
- @NonNull DisplayContent dc) {
- if (dc.mWaitingForConfig) {
- dc.mWaitingForConfig = false;
- mLastFinishedFreezeSource = "new-config";
- }
-
- mRoot.setDisplayOverrideConfigurationIfNeeded(overrideConfig, dc);
- }
-
// TODO(multi-display): remove when no default display use case.
void prepareAppTransitionNone() {
if (!checkCallingPermission(MANAGE_APP_TOKENS, "prepareAppTransition()")) {
@@ -8352,9 +8342,9 @@
* views.
*/
void grantInputChannel(Session session, int callingUid, int callingPid, int displayId,
- SurfaceControl surface, IWindow window, IBinder hostInputToken,
- int flags, int privateFlags, int type, IBinder focusGrantToken,
- InputChannel outInputChannel) {
+ SurfaceControl surface, IWindow window, IBinder hostInputToken,
+ int flags, int privateFlags, int type, IBinder focusGrantToken,
+ String inputHandleName, InputChannel outInputChannel) {
final InputApplicationHandle applicationHandle;
final String name;
final InputChannel clientChannel;
@@ -8362,7 +8352,7 @@
EmbeddedWindowController.EmbeddedWindow win =
new EmbeddedWindowController.EmbeddedWindow(session, this, window,
mInputToWindowMap.get(hostInputToken), callingUid, callingPid, type,
- displayId, focusGrantToken);
+ displayId, focusGrantToken, inputHandleName);
clientChannel = win.openInputChannel();
mEmbeddedWindowController.add(clientChannel.getToken(), win);
applicationHandle = win.getApplicationHandle();
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 1d5c184..4c7891b 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -19,6 +19,8 @@
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.app.ActivityManager.isStartResultSuccessful;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.WindowContainerTransaction.Change.CHANGE_BOUNDS_TRANSACTION;
+import static android.window.WindowContainerTransaction.Change.CHANGE_BOUNDS_TRANSACTION_RECT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT;
@@ -1224,10 +1226,14 @@
while (entries.hasNext()) {
final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next();
// Only allow to apply changes to TaskFragment that is created by this organizer.
- enforceTaskFragmentOrganized(func, WindowContainer.fromBinder(entry.getKey()),
- organizer);
+ WindowContainer wc = WindowContainer.fromBinder(entry.getKey());
+ enforceTaskFragmentOrganized(func, wc, organizer);
+ enforceTaskFragmentConfigChangeAllowed(func, wc, entry.getValue(), organizer);
}
+ // TODO(b/197364677): Enforce safety of hierarchy operations in untrusted mode. E.g. one
+ // could first change a trusted TF, and then start/reparent untrusted activity there.
+
// Hierarchy changes
final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
for (int i = hops.size() - 1; i >= 0; i--) {
@@ -1297,6 +1303,57 @@
}
}
+ /**
+ * Makes sure that SurfaceControl transactions and the ability to set bounds outside of the
+ * parent bounds are not allowed for embedding without full trust between the host and the
+ * target.
+ * TODO(b/197364677): Allow SC transactions when the client-driven animations are protected from
+ * tapjacking.
+ */
+ private void enforceTaskFragmentConfigChangeAllowed(String func, @Nullable WindowContainer wc,
+ WindowContainerTransaction.Change change, ITaskFragmentOrganizer organizer) {
+ if (wc == null) {
+ Slog.e(TAG, "Attempt to operate on task fragment that no longer exists");
+ return;
+ }
+ // Check if TaskFragment is embedded in fully trusted mode
+ if (wc.asTaskFragment().isAllowedToBeEmbeddedInTrustedMode()) {
+ // Fully trusted, no need to check further
+ return;
+ }
+
+ if (change == null) {
+ return;
+ }
+ final int changeMask = change.getChangeMask();
+ if ((changeMask & (CHANGE_BOUNDS_TRANSACTION | CHANGE_BOUNDS_TRANSACTION_RECT)) != 0) {
+ String msg = "Permission Denial: " + func + " from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ + " trying to apply SurfaceControl changes to TaskFragment in non-trusted "
+ + "embedding mode, TaskFragmentOrganizer=" + organizer;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ if (change.getWindowSetMask() == 0) {
+ // Nothing else to check.
+ return;
+ }
+ WindowConfiguration requestedWindowConfig = change.getConfiguration().windowConfiguration;
+ WindowContainer wcParent = wc.getParent();
+ if (wcParent == null) {
+ Slog.e(TAG, "Attempt to set bounds on task fragment that has no parent");
+ return;
+ }
+ if (!wcParent.getBounds().contains(requestedWindowConfig.getBounds())) {
+ String msg = "Permission Denial: " + func + " from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ + " trying to apply bounds outside of parent for non-trusted host,"
+ + " TaskFragmentOrganizer=" + organizer;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ }
+
void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams,
@Nullable IBinder errorCallbackToken, @NonNull CallerInfo caller) {
final ActivityRecord ownerActivity =
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 11a6141..498eaab 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2385,6 +2385,7 @@
dc.getDisplayPolicy().removeWindowLw(this);
disposeInputChannel();
+ mOnBackInvokedCallback = null;
mSession.windowRemovedLocked();
try {
@@ -2438,6 +2439,7 @@
try {
disposeInputChannel();
+ mOnBackInvokedCallback = null;
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b "
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index db231f6..f398034 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -44,6 +44,7 @@
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
import android.view.InsetsState;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager.LayoutParams.WindowType;
import android.window.WindowContext;
@@ -99,6 +100,7 @@
final boolean mOwnerCanManageAppTokens;
private FixedRotationTransformState mFixedRotationTransformState;
+ private SurfaceControl mFixedRotationTransformLeash;
/**
* When set to {@code true}, this window token is created from {@link WindowContext}
@@ -521,8 +523,14 @@
if (state == null) {
return;
}
-
- state.resetTransform();
+ if (!mTransitionController.isShellTransitionsEnabled()) {
+ state.resetTransform();
+ } else {
+ // Remove all the leashes
+ for (int i = state.mAssociatedTokens.size() - 1; i >= 0; --i) {
+ state.mAssociatedTokens.get(i).removeFixedRotationLeash();
+ }
+ }
// Clear the flag so if the display will be updated to the same orientation, the transform
// won't take effect.
state.mIsTransforming = false;
@@ -554,6 +562,43 @@
}
/**
+ * Gets or creates a leash which can be treated as if this window is not-rotated. This is
+ * used to adapt mismatched-rotation surfaces into code that expects all windows to share
+ * the same rotation.
+ */
+ @Nullable
+ SurfaceControl getOrCreateFixedRotationLeash() {
+ if (!mTransitionController.isShellTransitionsEnabled()) return null;
+ final int rotation = getRelativeDisplayRotation();
+ if (rotation == Surface.ROTATION_0) return mFixedRotationTransformLeash;
+ if (mFixedRotationTransformLeash != null) return mFixedRotationTransformLeash;
+
+ final SurfaceControl.Transaction t = getSyncTransaction();
+ final SurfaceControl leash = makeSurface().setContainerLayer()
+ .setParent(getParentSurfaceControl())
+ .setName(getSurfaceControl() + " - rotation-leash")
+ .setHidden(false)
+ .setEffectLayer()
+ .setCallsite("WindowToken.getOrCreateFixedRotationLeash")
+ .build();
+ t.setPosition(leash, mLastSurfacePosition.x, mLastSurfacePosition.y);
+ t.show(leash);
+ t.reparent(getSurfaceControl(), leash);
+ t.setAlpha(getSurfaceControl(), 1.f);
+ mFixedRotationTransformLeash = leash;
+ updateSurfaceRotation(t, rotation, mFixedRotationTransformLeash);
+ return mFixedRotationTransformLeash;
+ }
+
+ void removeFixedRotationLeash() {
+ if (mFixedRotationTransformLeash == null) return;
+ final SurfaceControl.Transaction t = getSyncTransaction();
+ t.reparent(getSurfaceControl(), getParentSurfaceControl());
+ t.remove(mFixedRotationTransformLeash);
+ mFixedRotationTransformLeash = null;
+ }
+
+ /**
* It is called when the window is using fixed rotation transform, and before display applies
* the same rotation, the rotation change for display is canceled, e.g. the orientation from
* sensor is updated to previous direction.
@@ -575,7 +620,7 @@
@Override
void updateSurfacePosition(SurfaceControl.Transaction t) {
super.updateSurfacePosition(t);
- if (isFixedRotationTransforming()) {
+ if (!mTransitionController.isShellTransitionsEnabled() && isFixedRotationTransforming()) {
final ActivityRecord r = asActivityRecord();
final Task rootTask = r != null ? r.getRootTask() : null;
// Don't transform the activity in PiP because the PiP task organizer will handle it.
@@ -588,6 +633,20 @@
}
@Override
+ protected void updateSurfaceRotation(SurfaceControl.Transaction t,
+ @Surface.Rotation int deltaRotation, SurfaceControl positionLeash) {
+ final ActivityRecord r = asActivityRecord();
+ if (r != null) {
+ final Task rootTask = r.getRootTask();
+ // Don't transform the activity in PiP because the PiP task organizer will handle it.
+ if (rootTask != null && rootTask.inPinnedWindowingMode()) {
+ return;
+ }
+ }
+ super.updateSurfaceRotation(t, deltaRotation, positionLeash);
+ }
+
+ @Override
void resetSurfacePositionForAnimationLeash(SurfaceControl.Transaction t) {
// Keep the transformed position to animate because the surface will show in different
// rotation than the animator of leash.
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 95e1aec..0d49f5f 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -160,7 +160,7 @@
"android.hardware.graphics.common@1.2",
"android.hardware.graphics.common-V3-ndk",
"android.hardware.graphics.mapper@4.0",
- "android.hardware.input.classifier@1.0",
+ "android.hardware.input.processor-V1-ndk",
"android.hardware.ir@1.0",
"android.hardware.light@2.0",
"android.hardware.memtrack-V1-ndk",
diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
index adc91fc..8197b67 100644
--- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -243,22 +243,34 @@
xAbsSetup.code = ABS_MT_POSITION_X;
xAbsSetup.absinfo.maximum = screenWidth - 1;
xAbsSetup.absinfo.minimum = 0;
- ioctl(fd, UI_ABS_SETUP, xAbsSetup);
+ if (ioctl(fd, UI_ABS_SETUP, &xAbsSetup) != 0) {
+ ALOGE("Error creating touchscreen uinput x axis: %s", strerror(errno));
+ return -errno;
+ }
uinput_abs_setup yAbsSetup;
yAbsSetup.code = ABS_MT_POSITION_Y;
yAbsSetup.absinfo.maximum = screenHeight - 1;
yAbsSetup.absinfo.minimum = 0;
- ioctl(fd, UI_ABS_SETUP, yAbsSetup);
+ if (ioctl(fd, UI_ABS_SETUP, &yAbsSetup) != 0) {
+ ALOGE("Error creating touchscreen uinput y axis: %s", strerror(errno));
+ return -errno;
+ }
uinput_abs_setup majorAbsSetup;
majorAbsSetup.code = ABS_MT_TOUCH_MAJOR;
majorAbsSetup.absinfo.maximum = screenWidth - 1;
majorAbsSetup.absinfo.minimum = 0;
- ioctl(fd, UI_ABS_SETUP, majorAbsSetup);
+ if (ioctl(fd, UI_ABS_SETUP, &majorAbsSetup) != 0) {
+ ALOGE("Error creating touchscreen uinput major axis: %s", strerror(errno));
+ return -errno;
+ }
uinput_abs_setup pressureAbsSetup;
pressureAbsSetup.code = ABS_MT_PRESSURE;
pressureAbsSetup.absinfo.maximum = 255;
pressureAbsSetup.absinfo.minimum = 0;
- ioctl(fd, UI_ABS_SETUP, pressureAbsSetup);
+ if (ioctl(fd, UI_ABS_SETUP, &pressureAbsSetup) != 0) {
+ ALOGE("Error creating touchscreen uinput pressure axis: %s", strerror(errno));
+ return -errno;
+ }
}
if (ioctl(fd, UI_DEV_SETUP, &setup) != 0) {
ALOGE("Error creating uinput device: %s", strerror(errno));
@@ -266,6 +278,7 @@
}
} else {
// UI_DEV_SETUP was not introduced until version 5. Try setting up manually.
+ ALOGI("Falling back to version %d manual setup", version);
uinput_user_dev fallback;
memset(&fallback, 0, sizeof(fallback));
strlcpy(fallback.name, readableName, UINPUT_MAX_NAME_SIZE);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 80011d1..f8ace0c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9616,18 +9616,22 @@
"Cannot set the profile owner on a user which is already set-up");
if (!mIsWatch) {
- // Only the default supervision profile owner can be set as profile owner after SUW
+ final String supervisionRolePackage = mContext.getResources().getString(
+ com.android.internal.R.string.config_systemSupervision);
+ // Only the default supervision profile owner or supervision role holder
+ // can be set as profile owner after SUW
final String supervisor = mContext.getResources().getString(
com.android.internal.R.string
.config_defaultSupervisionProfileOwnerComponent);
- if (supervisor == null) {
+ if (supervisor == null && supervisionRolePackage == null) {
throw new IllegalStateException("Unable to set profile owner post-setup, no"
+ "default supervisor profile owner defined");
}
final ComponentName supervisorComponent = ComponentName.unflattenFromString(
supervisor);
- if (!owner.equals(supervisorComponent)) {
+ if (!owner.equals(supervisorComponent)
+ && !owner.getPackageName().equals(supervisionRolePackage)) {
throw new IllegalStateException("Unable to set non-default profile owner"
+ " post-setup " + owner);
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a7539b5..5c3721d 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -264,6 +264,10 @@
"com.android.server.companion.virtual.VirtualDeviceManagerService";
private static final String STATS_COMPANION_APEX_PATH =
"/apex/com.android.os.statsd/javalib/service-statsd.jar";
+ private static final String SCHEDULING_APEX_PATH =
+ "/apex/com.android.scheduling/javalib/service-scheduling.jar";
+ private static final String REBOOT_READINESS_LIFECYCLE_CLASS =
+ "com.android.server.scheduling.RebootReadinessManagerService$Lifecycle";
private static final String CONNECTIVITY_SERVICE_APEX_PATH =
"/apex/com.android.tethering/javalib/service-connectivity.jar";
private static final String STATS_COMPANION_LIFECYCLE_CLASS =
@@ -2575,6 +2579,12 @@
STATS_COMPANION_LIFECYCLE_CLASS, STATS_COMPANION_APEX_PATH);
t.traceEnd();
+ // Reboot Readiness
+ t.traceBegin("StartRebootReadinessManagerService");
+ mSystemServiceManager.startServiceFromJar(
+ REBOOT_READINESS_LIFECYCLE_CLASS, SCHEDULING_APEX_PATH);
+ t.traceEnd();
+
// Statsd pulled atoms
t.traceBegin("StartStatsPullAtomService");
mSystemServiceManager.startService(STATS_PULL_ATOM_SERVICE_CLASS);
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 cc663d9..11300ce 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
@@ -508,7 +508,8 @@
AndroidPackage::shouldInheritKeyStoreKeys,
ParsingPackage::setInheritKeyStoreKeys,
true
- )
+ ),
+ getter(AndroidPackage::getKnownActivityEmbeddingCerts, setOf("TESTEMBEDDINGCERT"))
)
override fun initialObject() = PackageImpl.forParsing(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
index a89b717..5180786 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
@@ -53,7 +53,7 @@
ParsedActivity::getTaskAffinity,
ParsedActivity::getTheme,
ParsedActivity::getUiOptions,
- ParsedActivity::isSupportsSizeChanges,
+ ParsedActivity::isSupportsSizeChanges
)
override fun mainComponentSubclassExtraParams() = listOf(
@@ -73,6 +73,7 @@
ActivityInfo.WindowLayout::minHeight
)
}
- )
+ ),
+ getter(ParsedActivity::getKnownActivityEmbeddingCerts, setOf("TESTEMBEDDINGCERT"))
)
}
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 75669d5..3e60af3 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -53,6 +53,7 @@
"service-jobscheduler",
"service-permission.impl",
"service-supplementalprocess.impl",
+ "services.companion",
"services.core",
"services.devicepolicy",
"services.net",
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
index a112baf..816dbdb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -23,6 +23,7 @@
import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
import static android.app.ActivityManager.RESTRICTION_LEVEL_EXEMPTED;
import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+import static android.app.ActivityManager.isLowRamDeviceStatic;
import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
@@ -46,9 +47,11 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.internal.notification.SystemNotificationChannels.ABUSIVE_BACKGROUND_APPS;
-import static com.android.server.am.AppBatteryTracker.BATT_DIMEN_BG;
-import static com.android.server.am.AppBatteryTracker.BATT_DIMEN_FG;
-import static com.android.server.am.AppBatteryTracker.BATT_DIMEN_FGS;
+import static com.android.server.am.AppBatteryTracker.AppBatteryPolicy.getFloatArray;
+import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATTERY_USAGE_INDEX_BACKGROUND;
+import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND;
+import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND_SERVICE;
+import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATT_DIMENS;
import static com.android.server.am.AppRestrictionController.STOCK_PM_FLAGS;
import static org.junit.Assert.assertEquals;
@@ -107,12 +110,14 @@
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.R;
import com.android.server.AppStateTracker;
import com.android.server.DeviceIdleInternal;
import com.android.server.am.AppBatteryExemptionTracker.AppBatteryExemptionPolicy;
import com.android.server.am.AppBatteryExemptionTracker.UidBatteryStates;
import com.android.server.am.AppBatteryExemptionTracker.UidStateEventWithBattery;
import com.android.server.am.AppBatteryTracker.AppBatteryPolicy;
+import com.android.server.am.AppBatteryTracker.ImmutableBatteryUsage;
import com.android.server.am.AppBindServiceEventsTracker.AppBindServiceEventsPolicy;
import com.android.server.am.AppBroadcastEventsTracker.AppBroadcastEventsPolicy;
import com.android.server.am.AppFGSTracker.AppFGSPolicy;
@@ -552,28 +557,34 @@
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED,
DeviceConfig::getBoolean,
- AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED);
+ mContext.getResources().getBoolean(
+ R.bool.config_bg_current_drain_monitor_enabled));
bgCurrentDrainMonitor.set(true);
bgCurrentDrainWindow = new DeviceConfigSession<>(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_WINDOW,
DeviceConfig::getLong,
- AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS);
+ (long) mContext.getResources().getInteger(
+ R.integer.config_bg_current_drain_window));
bgCurrentDrainWindow.set(windowMs);
bgCurrentDrainRestrictedBucketThreshold = new DeviceConfigSession<>(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET,
DeviceConfig::getFloat,
- AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+ getFloatArray(mContext.getResources().obtainTypedArray(
+ R.array.config_bg_current_drain_threshold_to_restricted_bucket))[
+ isLowRamDeviceStatic() ? 1 : 0]);
bgCurrentDrainRestrictedBucketThreshold.set(restrictBucketThreshold);
bgCurrentDrainBgRestrictedThreshold = new DeviceConfigSession<>(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED,
DeviceConfig::getFloat,
- AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+ getFloatArray(mContext.getResources().obtainTypedArray(
+ R.array.config_bg_current_drain_threshold_to_bg_restricted))[
+ isLowRamDeviceStatic() ? 1 : 0]);
bgCurrentDrainBgRestrictedThreshold.set(bgRestrictedThreshold);
mCurrentTimeMillis = 10_000L;
@@ -1280,64 +1291,76 @@
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED,
DeviceConfig::getBoolean,
- AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED);
+ mContext.getResources().getBoolean(
+ R.bool.config_bg_current_drain_monitor_enabled));
bgCurrentDrainMonitor.set(true);
bgCurrentDrainWindow = new DeviceConfigSession<>(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_WINDOW,
DeviceConfig::getLong,
- AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS);
+ (long) mContext.getResources().getInteger(
+ R.integer.config_bg_current_drain_window));
bgCurrentDrainWindow.set(windowMs);
bgCurrentDrainRestrictedBucketThreshold = new DeviceConfigSession<>(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET,
DeviceConfig::getFloat,
- AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+ getFloatArray(mContext.getResources().obtainTypedArray(
+ R.array.config_bg_current_drain_threshold_to_restricted_bucket))[
+ isLowRamDeviceStatic() ? 1 : 0]);
bgCurrentDrainRestrictedBucketThreshold.set(restrictBucketThreshold);
bgCurrentDrainBgRestrictedThreshold = new DeviceConfigSession<>(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED,
DeviceConfig::getFloat,
- AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+ getFloatArray(mContext.getResources().obtainTypedArray(
+ R.array.config_bg_current_drain_threshold_to_bg_restricted))[
+ isLowRamDeviceStatic() ? 1 : 0]);
bgCurrentDrainBgRestrictedThreshold.set(bgRestrictedThreshold);
bgCurrentDrainRestrictedBucketHighThreshold = new DeviceConfigSession<>(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET,
DeviceConfig::getFloat,
- AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_HIGH_THRESHOLD);
+ getFloatArray(mContext.getResources().obtainTypedArray(
+ R.array.config_bg_current_drain_high_threshold_to_restricted_bucket))[
+ isLowRamDeviceStatic() ? 1 : 0]);
bgCurrentDrainRestrictedBucketHighThreshold.set(restrictBucketHighThreshold);
bgCurrentDrainBgRestrictedHighThreshold = new DeviceConfigSession<>(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED,
DeviceConfig::getFloat,
- AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD);
+ getFloatArray(mContext.getResources().obtainTypedArray(
+ R.array.config_bg_current_drain_high_threshold_to_bg_restricted))[
+ isLowRamDeviceStatic() ? 1 : 0]);
bgCurrentDrainBgRestrictedHighThreshold.set(bgRestrictedHighThreshold);
bgMediaPlaybackMinDurationThreshold = new DeviceConfigSession<>(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION,
DeviceConfig::getLong,
- AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION);
+ (long) mContext.getResources().getInteger(
+ R.integer.config_bg_current_drain_media_playback_min_duration));
bgMediaPlaybackMinDurationThreshold.set(bgMediaPlaybackMinDuration);
bgLocationMinDurationThreshold = new DeviceConfigSession<>(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION,
DeviceConfig::getLong,
- AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION);
+ (long) mContext.getResources().getInteger(
+ R.integer.config_bg_current_drain_location_min_duration));
bgLocationMinDurationThreshold.set(bgLocationMinDuration);
bgCurrentDrainEventDurationBasedThresholdEnabled = new DeviceConfigSession<>(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED,
DeviceConfig::getBoolean,
- AppBatteryPolicy
- .DEFAULT_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED);
+ mContext.getResources().getBoolean(
+ R.bool.config_bg_current_drain_event_duration_based_threshold_enabled));
bgCurrentDrainEventDurationBasedThresholdEnabled.set(true);
bgBatteryExemptionEnabled = new DeviceConfigSession<>(
@@ -1932,9 +1955,12 @@
private UidBatteryConsumer mockUidBatteryConsumer(int uid, double bg, double fgs, double fg) {
UidBatteryConsumer uidConsumer = mock(UidBatteryConsumer.class);
doReturn(uid).when(uidConsumer).getUid();
- doReturn(bg).when(uidConsumer).getConsumedPower(eq(BATT_DIMEN_BG));
- doReturn(fgs).when(uidConsumer).getConsumedPower(eq(BATT_DIMEN_FGS));
- doReturn(fg).when(uidConsumer).getConsumedPower(eq(BATT_DIMEN_FG));
+ doReturn(bg).when(uidConsumer).getConsumedPower(
+ eq(BATT_DIMENS[BATTERY_USAGE_INDEX_BACKGROUND]));
+ doReturn(fgs).when(uidConsumer).getConsumedPower(
+ eq(BATT_DIMENS[BATTERY_USAGE_INDEX_FOREGROUND_SERVICE]));
+ doReturn(fg).when(uidConsumer).getConsumedPower(
+ eq(BATT_DIMENS[BATTERY_USAGE_INDEX_FOREGROUND]));
return uidConsumer;
}
@@ -2234,8 +2260,8 @@
boolean[] isStart, long[] timestamps, double[] batteryUsage) {
final LinkedList<UidStateEventWithBattery> result = new LinkedList<>();
for (int i = 0; i < isStart.length; i++) {
- result.add(new UidStateEventWithBattery(
- isStart[i], timestamps[i], batteryUsage[i], null));
+ result.add(new UidStateEventWithBattery(isStart[i], timestamps[i],
+ new ImmutableBatteryUsage(0.0d, 0.0d, batteryUsage[i], 0.0d), null));
}
return result;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java
new file mode 100644
index 0000000..1b9cb28
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java
@@ -0,0 +1,200 @@
+/*
+ * 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.companion.virtual;
+
+import static android.hardware.camera2.CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_UNSUPPORTED;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraInjectionSession;
+import android.hardware.camera2.CameraManager;
+import android.os.Process;
+import android.testing.TestableContext;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class CameraAccessControllerTest {
+ private static final String FRONT_CAMERA = "0";
+ private static final String REAR_CAMERA = "1";
+ private static final String TEST_APP_PACKAGE = "some.package";
+ private static final String OTHER_APP_PACKAGE = "other.package";
+
+ private CameraAccessController mController;
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getContext());
+
+ @Mock
+ private CameraManager mCameraManager;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private VirtualDeviceManagerInternal mDeviceManagerInternal;
+ @Mock
+ private CameraAccessController.CameraAccessBlockedCallback mBlockedCallback;
+
+ private ApplicationInfo mTestAppInfo = new ApplicationInfo();
+ private ApplicationInfo mOtherAppInfo = new ApplicationInfo();
+
+ @Captor
+ ArgumentCaptor<CameraInjectionSession.InjectionStatusCallback> mInjectionCallbackCaptor;
+
+
+ @Before
+ public void setUp() throws PackageManager.NameNotFoundException {
+ MockitoAnnotations.initMocks(this);
+ mContext.addMockSystemService(CameraManager.class, mCameraManager);
+ mContext.setMockPackageManager(mPackageManager);
+ LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+ LocalServices.addService(VirtualDeviceManagerInternal.class, mDeviceManagerInternal);
+ mController = new CameraAccessController(mContext, mDeviceManagerInternal,
+ mBlockedCallback);
+ mTestAppInfo.uid = Process.FIRST_APPLICATION_UID;
+ mOtherAppInfo.uid = Process.FIRST_APPLICATION_UID + 1;
+ when(mPackageManager.getApplicationInfo(eq(TEST_APP_PACKAGE), anyInt())).thenReturn(
+ mTestAppInfo);
+ when(mPackageManager.getApplicationInfo(eq(OTHER_APP_PACKAGE), anyInt())).thenReturn(
+ mOtherAppInfo);
+ mController.startObservingIfNeeded();
+ }
+
+ @Test
+ public void onCameraOpened_uidNotRunning_noCameraBlocking() throws CameraAccessException {
+ when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+ eq(mTestAppInfo.uid))).thenReturn(false);
+ mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+ verify(mCameraManager, never()).injectCamera(any(), any(), any(), any(), any());
+ }
+
+
+ @Test
+ public void onCameraOpened_uidRunning_cameraBlocked() throws CameraAccessException {
+ when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+ eq(mTestAppInfo.uid))).thenReturn(true);
+ mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+ verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
+ any(), any());
+ }
+
+ @Test
+ public void onCameraClosed_injectionWasActive_cameraUnblocked() throws CameraAccessException {
+ when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+ eq(mTestAppInfo.uid))).thenReturn(true);
+ mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+ verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
+ any(), mInjectionCallbackCaptor.capture());
+ CameraInjectionSession session = mock(CameraInjectionSession.class);
+ mInjectionCallbackCaptor.getValue().onInjectionSucceeded(session);
+
+ mController.onCameraClosed(FRONT_CAMERA);
+ verify(session).close();
+ }
+
+
+ @Test
+ public void onCameraClosed_otherCameraClosed_cameraNotUnblocked() throws CameraAccessException {
+ when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+ eq(mTestAppInfo.uid))).thenReturn(true);
+ mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+ verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
+ any(), mInjectionCallbackCaptor.capture());
+ CameraInjectionSession session = mock(CameraInjectionSession.class);
+ mInjectionCallbackCaptor.getValue().onInjectionSucceeded(session);
+
+ mController.onCameraClosed(REAR_CAMERA);
+ verify(session, never()).close();
+ }
+
+ @Test
+ public void onCameraClosed_twoCamerasBlocked_correctCameraUnblocked()
+ throws CameraAccessException {
+ when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+ eq(mTestAppInfo.uid))).thenReturn(true);
+ when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+ eq(mOtherAppInfo.uid))).thenReturn(true);
+
+ mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+ mController.onCameraOpened(REAR_CAMERA, OTHER_APP_PACKAGE);
+
+ verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
+ any(), mInjectionCallbackCaptor.capture());
+ CameraInjectionSession frontCameraSession = mock(CameraInjectionSession.class);
+ mInjectionCallbackCaptor.getValue().onInjectionSucceeded(frontCameraSession);
+
+ verify(mCameraManager).injectCamera(eq(OTHER_APP_PACKAGE), eq(REAR_CAMERA), anyString(),
+ any(), mInjectionCallbackCaptor.capture());
+ CameraInjectionSession rearCameraSession = mock(CameraInjectionSession.class);
+ mInjectionCallbackCaptor.getValue().onInjectionSucceeded(rearCameraSession);
+
+ mController.onCameraClosed(REAR_CAMERA);
+ verify(frontCameraSession, never()).close();
+ verify(rearCameraSession).close();
+ }
+
+ @Test
+ public void onInjectionError_callbackFires() throws CameraAccessException {
+ when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+ eq(mTestAppInfo.uid))).thenReturn(true);
+ mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+ verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
+ any(), mInjectionCallbackCaptor.capture());
+ CameraInjectionSession session = mock(CameraInjectionSession.class);
+ mInjectionCallbackCaptor.getValue().onInjectionSucceeded(session);
+ mInjectionCallbackCaptor.getValue().onInjectionError(ERROR_INJECTION_UNSUPPORTED);
+ verify(mBlockedCallback).onCameraAccessBlocked(eq(mTestAppInfo.uid));
+ }
+
+ @Test
+ public void twoCameraAccesses_onlyOneOnVirtualDisplay_callbackFiresForCorrectUid()
+ throws CameraAccessException {
+ when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+ eq(mTestAppInfo.uid))).thenReturn(true);
+ mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+ mController.onCameraOpened(REAR_CAMERA, OTHER_APP_PACKAGE);
+
+ verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
+ any(), mInjectionCallbackCaptor.capture());
+ CameraInjectionSession session = mock(CameraInjectionSession.class);
+ mInjectionCallbackCaptor.getValue().onInjectionSucceeded(session);
+ mInjectionCallbackCaptor.getValue().onInjectionError(ERROR_INJECTION_UNSUPPORTED);
+ verify(mBlockedCallback).onCameraAccessBlocked(eq(mTestAppInfo.uid));
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index f61d6ca..e4c9f97 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -23,6 +23,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -59,6 +60,10 @@
import android.os.Build;
import android.os.Looper;
import android.os.SystemClock;
+import android.telephony.CellSignalStrength;
+import android.telephony.SignalStrength;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
import android.util.DataUnit;
import com.android.server.LocalServices;
@@ -76,6 +81,7 @@
import java.time.Clock;
import java.time.ZoneOffset;
+import java.util.Set;
@RunWith(MockitoJUnitRunner.class)
public class ConnectivityControllerTest {
@@ -302,6 +308,427 @@
}
@Test
+ public void testStrongEnough_Cellular() {
+ mConstants.CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = 0;
+
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ final TelephonyManager telephonyManager = mock(TelephonyManager.class);
+ when(mContext.getSystemService(TelephonyManager.class))
+ .thenReturn(telephonyManager);
+ when(telephonyManager.createForSubscriptionId(anyInt()))
+ .thenReturn(telephonyManager);
+ final ArgumentCaptor<TelephonyCallback> signalStrengthsCaptor =
+ ArgumentCaptor.forClass(TelephonyCallback.class);
+ doNothing().when(telephonyManager)
+ .registerTelephonyCallback(any(), signalStrengthsCaptor.capture());
+ final ArgumentCaptor<NetworkCallback> callbackCaptor =
+ ArgumentCaptor.forClass(NetworkCallback.class);
+ doNothing().when(mConnManager).registerNetworkCallback(any(), callbackCaptor.capture());
+ final Network net = mock(Network.class);
+ final NetworkCapabilities caps = createCapabilitiesBuilder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .setSubscriptionIds(Set.of(7357))
+ .build();
+ final JobInfo.Builder baseJobBuilder = createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1),
+ DataUnit.MEBIBYTES.toBytes(1))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+ final JobStatus jobExp = createJobStatus(baseJobBuilder.setExpedited(true));
+ final JobStatus jobHigh = createJobStatus(
+ baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_HIGH));
+ final JobStatus jobDefEarly = createJobStatus(
+ baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT),
+ now - 1000, now + 100_000);
+ final JobStatus jobDefLate = createJobStatus(
+ baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT),
+ now - 100_000, now + 1000);
+ final JobStatus jobLow = createJobStatus(
+ baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_LOW));
+ final JobStatus jobMin = createJobStatus(
+ baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
+ final JobStatus jobMinRunner = createJobStatus(
+ baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
+
+ final ConnectivityController controller = new ConnectivityController(mService);
+
+ final NetworkCallback generalCallback = callbackCaptor.getValue();
+
+ when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L);
+
+ when(mService.isBatteryCharging()).thenReturn(false);
+ when(mService.isBatteryNotLow()).thenReturn(false);
+ answerNetwork(generalCallback, null, null, net, caps);
+
+ final TelephonyCallback.SignalStrengthsListener signalStrengthsListener =
+ (TelephonyCallback.SignalStrengthsListener) signalStrengthsCaptor.getValue();
+
+ controller.maybeStartTrackingJobLocked(jobMinRunner, null);
+ controller.prepareForExecutionLocked(jobMinRunner);
+
+ final SignalStrength signalStrength = mock(SignalStrength.class);
+
+ when(signalStrength.getLevel()).thenReturn(
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+ assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+ assertFalse(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+ assertFalse(controller.isSatisfied(jobLow, net, caps, mConstants));
+ assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants));
+ assertFalse(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+ when(signalStrength.getLevel()).thenReturn(
+ CellSignalStrength.SIGNAL_STRENGTH_POOR);
+ signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+ assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+ assertFalse(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+ assertFalse(controller.isSatisfied(jobLow, net, caps, mConstants));
+ assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants));
+ assertFalse(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+ when(mService.isBatteryCharging()).thenReturn(true);
+ when(mService.isBatteryNotLow()).thenReturn(false);
+
+ assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+ assertFalse(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+ assertFalse(controller.isSatisfied(jobLow, net, caps, mConstants));
+ assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants));
+ assertFalse(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+ when(mService.isBatteryCharging()).thenReturn(true);
+ when(mService.isBatteryNotLow()).thenReturn(true);
+
+ assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+ assertFalse(controller.isSatisfied(jobLow, net, caps, mConstants));
+ assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants));
+ assertFalse(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+ when(mService.isBatteryCharging()).thenReturn(false);
+ when(mService.isBatteryNotLow()).thenReturn(false);
+ when(signalStrength.getLevel()).thenReturn(
+ CellSignalStrength.SIGNAL_STRENGTH_MODERATE);
+ signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+ assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+ assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+ when(mService.isBatteryCharging()).thenReturn(false);
+ when(mService.isBatteryNotLow()).thenReturn(true);
+
+ assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+ assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+ when(mService.isBatteryCharging()).thenReturn(true);
+ when(mService.isBatteryNotLow()).thenReturn(true);
+
+ assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+ when(mService.isBatteryCharging()).thenReturn(false);
+ when(mService.isBatteryNotLow()).thenReturn(false);
+ when(signalStrength.getLevel()).thenReturn(
+ CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+ signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+ assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+ when(mService.isBatteryCharging()).thenReturn(false);
+ when(mService.isBatteryNotLow()).thenReturn(false);
+ when(signalStrength.getLevel()).thenReturn(
+ CellSignalStrength.SIGNAL_STRENGTH_GREAT);
+ signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+ assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+ }
+
+ @Test
+ public void testStrongEnough_Cellular_CheckDisabled() {
+ mConstants.CONN_USE_CELL_SIGNAL_STRENGTH = false;
+ mConstants.CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = 0;
+
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ final TelephonyManager telephonyManager = mock(TelephonyManager.class);
+ when(mContext.getSystemService(TelephonyManager.class))
+ .thenReturn(telephonyManager);
+ when(telephonyManager.createForSubscriptionId(anyInt()))
+ .thenReturn(telephonyManager);
+ final ArgumentCaptor<TelephonyCallback> signalStrengthsCaptor =
+ ArgumentCaptor.forClass(TelephonyCallback.class);
+ doNothing().when(telephonyManager)
+ .registerTelephonyCallback(any(), signalStrengthsCaptor.capture());
+ final ArgumentCaptor<NetworkCallback> callbackCaptor =
+ ArgumentCaptor.forClass(NetworkCallback.class);
+ doNothing().when(mConnManager).registerNetworkCallback(any(), callbackCaptor.capture());
+ final Network net = mock(Network.class);
+ final NetworkCapabilities caps = createCapabilitiesBuilder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .setSubscriptionIds(Set.of(7357))
+ .build();
+ final JobInfo.Builder baseJobBuilder = createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1),
+ DataUnit.MEBIBYTES.toBytes(1))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+ final JobStatus jobExp = createJobStatus(baseJobBuilder.setExpedited(true));
+ final JobStatus jobHigh = createJobStatus(
+ baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_HIGH));
+ final JobStatus jobDefEarly = createJobStatus(
+ baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT),
+ now - 1000, now + 100_000);
+ final JobStatus jobDefLate = createJobStatus(
+ baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT),
+ now - 100_000, now + 1000);
+ final JobStatus jobLow = createJobStatus(
+ baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_LOW));
+ final JobStatus jobMin = createJobStatus(
+ baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
+ final JobStatus jobMinRunner = createJobStatus(
+ baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
+
+ final ConnectivityController controller = new ConnectivityController(mService);
+
+ final NetworkCallback generalCallback = callbackCaptor.getValue();
+
+ when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L);
+
+ when(mService.isBatteryCharging()).thenReturn(false);
+ when(mService.isBatteryNotLow()).thenReturn(false);
+ answerNetwork(generalCallback, null, null, net, caps);
+
+ final TelephonyCallback.SignalStrengthsListener signalStrengthsListener =
+ (TelephonyCallback.SignalStrengthsListener) signalStrengthsCaptor.getValue();
+
+ controller.maybeStartTrackingJobLocked(jobMinRunner, null);
+ controller.prepareForExecutionLocked(jobMinRunner);
+
+ final SignalStrength signalStrength = mock(SignalStrength.class);
+
+ when(signalStrength.getLevel()).thenReturn(
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+ assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+ when(signalStrength.getLevel()).thenReturn(
+ CellSignalStrength.SIGNAL_STRENGTH_POOR);
+ signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+ assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+ when(mService.isBatteryCharging()).thenReturn(true);
+ when(mService.isBatteryNotLow()).thenReturn(false);
+
+ assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+ when(mService.isBatteryCharging()).thenReturn(true);
+ when(mService.isBatteryNotLow()).thenReturn(true);
+
+ assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+ when(mService.isBatteryCharging()).thenReturn(false);
+ when(mService.isBatteryNotLow()).thenReturn(false);
+ when(signalStrength.getLevel()).thenReturn(
+ CellSignalStrength.SIGNAL_STRENGTH_MODERATE);
+ signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+ assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+ when(mService.isBatteryCharging()).thenReturn(false);
+ when(mService.isBatteryNotLow()).thenReturn(true);
+
+ assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+ when(mService.isBatteryCharging()).thenReturn(true);
+ when(mService.isBatteryNotLow()).thenReturn(true);
+
+ assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+ when(mService.isBatteryCharging()).thenReturn(false);
+ when(mService.isBatteryNotLow()).thenReturn(false);
+ when(signalStrength.getLevel()).thenReturn(
+ CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+ signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+ assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+ when(mService.isBatteryCharging()).thenReturn(false);
+ when(mService.isBatteryNotLow()).thenReturn(false);
+ when(signalStrength.getLevel()).thenReturn(
+ CellSignalStrength.SIGNAL_STRENGTH_GREAT);
+ signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+ assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+ }
+
+ @Test
+ public void testStrongEnough_Cellular_VPN() {
+ mConstants.CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = 0;
+
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ final TelephonyManager telephonyManager = mock(TelephonyManager.class);
+ when(mContext.getSystemService(TelephonyManager.class))
+ .thenReturn(telephonyManager);
+ when(telephonyManager.createForSubscriptionId(anyInt()))
+ .thenReturn(telephonyManager);
+ final ArgumentCaptor<TelephonyCallback> signalStrengthsCaptor =
+ ArgumentCaptor.forClass(TelephonyCallback.class);
+ doNothing().when(telephonyManager)
+ .registerTelephonyCallback(any(), signalStrengthsCaptor.capture());
+ final ArgumentCaptor<NetworkCallback> callbackCaptor =
+ ArgumentCaptor.forClass(NetworkCallback.class);
+ doNothing().when(mConnManager).registerNetworkCallback(any(), callbackCaptor.capture());
+ final Network net = mock(Network.class);
+ final NetworkCapabilities caps = createCapabilitiesBuilder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addTransportType(TRANSPORT_VPN)
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .setSubscriptionIds(Set.of(7357))
+ .build();
+ final JobInfo.Builder baseJobBuilder = createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1),
+ DataUnit.MEBIBYTES.toBytes(1))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+ final JobStatus jobExp = createJobStatus(baseJobBuilder.setExpedited(true));
+ final JobStatus jobHigh = createJobStatus(
+ baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_HIGH));
+ final JobStatus jobDefEarly = createJobStatus(
+ baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT),
+ now - 1000, now + 100_000);
+ final JobStatus jobDefLate = createJobStatus(
+ baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT),
+ now - 100_000, now + 1000);
+ final JobStatus jobLow = createJobStatus(
+ baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_LOW));
+ final JobStatus jobMin = createJobStatus(
+ baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
+ final JobStatus jobMinRunner = createJobStatus(
+ baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
+
+ final ConnectivityController controller = new ConnectivityController(mService);
+
+ final NetworkCallback generalCallback = callbackCaptor.getValue();
+
+ when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L);
+
+ when(mService.isBatteryCharging()).thenReturn(false);
+ when(mService.isBatteryNotLow()).thenReturn(false);
+ answerNetwork(generalCallback, null, null, net, caps);
+
+ final TelephonyCallback.SignalStrengthsListener signalStrengthsListener =
+ (TelephonyCallback.SignalStrengthsListener) signalStrengthsCaptor.getValue();
+
+ controller.maybeStartTrackingJobLocked(jobMinRunner, null);
+ controller.prepareForExecutionLocked(jobMinRunner);
+
+ final SignalStrength signalStrength = mock(SignalStrength.class);
+
+ when(signalStrength.getLevel()).thenReturn(
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+ // We don't restrict data via VPN over cellular.
+ assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+ assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+ }
+
+ @Test
public void testRelaxed() throws Exception {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
final JobInfo.Builder job = createJob()
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index 0b488b2..5fb3a4e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -33,6 +33,7 @@
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -445,6 +446,48 @@
mService.getWallpaperDimAmount(), dimAmount, 0.0);
}
+ @Test
+ public void testGetAdjustedWallpaperColorsOnDimming() throws RemoteException {
+ final int testUserId = USER_SYSTEM;
+ mService.switchUser(testUserId, null);
+ mService.setWallpaperComponent(sDefaultWallpaperComponent);
+ WallpaperData wallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM, testUserId);
+
+ // Mock a wallpaper data with color hints that support dark text and dark theme
+ // but not HINT_FROM_BITMAP
+ wallpaper.primaryColors = new WallpaperColors(Color.valueOf(Color.WHITE), null, null,
+ WallpaperColors.HINT_SUPPORTS_DARK_TEXT | WallpaperColors.HINT_SUPPORTS_DARK_THEME);
+ mService.setWallpaperDimAmount(0.6f);
+ int colorHints = mService.getAdjustedWallpaperColorsOnDimming(wallpaper).getColorHints();
+ // Dimmed wallpaper not extracted from bitmap does not support dark text and dark theme
+ assertNotEquals(WallpaperColors.HINT_SUPPORTS_DARK_TEXT,
+ colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT);
+ assertNotEquals(WallpaperColors.HINT_SUPPORTS_DARK_THEME,
+ colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME);
+
+ // Remove dimming
+ mService.setWallpaperDimAmount(0f);
+ colorHints = mService.getAdjustedWallpaperColorsOnDimming(wallpaper).getColorHints();
+ // Undimmed wallpaper not extracted from bitmap does support dark text and dark theme
+ assertEquals(WallpaperColors.HINT_SUPPORTS_DARK_TEXT,
+ colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT);
+ assertEquals(WallpaperColors.HINT_SUPPORTS_DARK_THEME,
+ colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME);
+
+ // Mock a wallpaper data with color hints that support dark text and dark theme
+ // and was extracted from bitmap
+ wallpaper.primaryColors = new WallpaperColors(Color.valueOf(Color.WHITE), null, null,
+ WallpaperColors.HINT_SUPPORTS_DARK_TEXT | WallpaperColors.HINT_SUPPORTS_DARK_THEME
+ | WallpaperColors.HINT_FROM_BITMAP);
+ mService.setWallpaperDimAmount(0.6f);
+ colorHints = mService.getAdjustedWallpaperColorsOnDimming(wallpaper).getColorHints();
+ // Dimmed wallpaper should still support dark text and dark theme
+ assertEquals(WallpaperColors.HINT_SUPPORTS_DARK_TEXT,
+ colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT);
+ assertEquals(WallpaperColors.HINT_SUPPORTS_DARK_THEME,
+ colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME);
+ }
+
// Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for
// non-current user must not bind to wallpaper service.
private void verifyNoConnectionBeforeLastUser(int lastUserId) {
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index d9f73d9..53cab9e 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -130,6 +130,19 @@
android:resource="@xml/test_account_type2_authenticator"/>
</service>
+ <service
+ android:name="com.android.server.dreams.TestDreamService"
+ android:exported="false"
+ android:label="Test Dream" >
+ <intent-filter>
+ <action android:name="android.service.dreams.DreamService" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data
+ android:name="android.service.dream"
+ android:resource="@xml/test_dream_metadata" />
+ </service>
+
<receiver android:name="com.android.server.devicepolicy.ApplicationRestrictionsTest$AdminReceiver"
android:permission="android.permission.BIND_DEVICE_ADMIN"
android:exported="true">
diff --git a/packages/SystemUI/res/layout/qs_detail_switch.xml b/services/tests/servicestests/res/xml/test_dream_metadata.xml
similarity index 62%
copy from packages/SystemUI/res/layout/qs_detail_switch.xml
copy to services/tests/servicestests/res/xml/test_dream_metadata.xml
index abb2497..aa054f1 100644
--- a/packages/SystemUI/res/layout/qs_detail_switch.xml
+++ b/services/tests/servicestests/res/xml/test_dream_metadata.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright (C) 2019 The Android Open Source Project
+ ~ 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.
@@ -14,10 +14,6 @@
~ limitations under the License.
-->
-<Switch
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@android:id/toggle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:clickable="false"
- android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
\ No newline at end of file
+<dream xmlns:android="http://schemas.android.com/apk/res/android"
+ android:settingsActivity="com.android.server.dreams/.TestDreamSettingsActivity"
+ android:showClockAndComplications="false" />
diff --git a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java b/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
deleted file mode 100644
index 489e2f7..0000000
--- a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.test.AndroidTestCase;
-
-/**
- * Tests for {@link com.android.server.BootReceiver}
- */
-public class BootReceiverTest extends AndroidTestCase {
- public void testLogLinePotentiallySensitive() throws Exception {
- /*
- * Strings to be dropped from the log as potentially sensitive: register dumps, process
- * names, hardware info.
- */
- final String[] becomeNull = {
- "CPU: 4 PID: 120 Comm: kunit_try_catch Tainted: G W 5.8.0-rc6+ #7",
- "Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1 04/01/2014",
- "[ 0.083207] RSP: 0000:ffffffff8fe07ca8 EFLAGS: 00010046 ORIG_RAX: 0000000000000000",
- "[ 0.084709] RAX: 0000000000000000 RBX: ffffffffff240000 RCX: ffffffff815fcf01",
- "[ 0.086109] RDX: dffffc0000000000 RSI: 0000000000000001 RDI: ffffffffff240004",
- "[ 0.087509] RBP: ffffffff8fe07d60 R08: fffffbfff1fc0f21 R09: fffffbfff1fc0f21",
- "[ 0.088911] R10: ffffffff8fe07907 R11: fffffbfff1fc0f20 R12: ffffffff8fe07d38",
- "R13: 0000000000000001 R14: 0000000000000001 R15: ffffffff8fe07e80",
- "x29: ffff00003ce07150 x28: ffff80001aa29cc0",
- "x1 : 0000000000000000 x0 : ffff00000f628000",
- };
-
- /* Strings to be left unchanged, including non-sensitive registers and parts of reports. */
- final String[] leftAsIs = {
- "FS: 0000000000000000(0000) GS:ffffffff92409000(0000) knlGS:0000000000000000",
- "[ 69.2366] [ T6006]c7 6006 =======================================================",
- "[ 69.245688] [ T6006] BUG: KFENCE: out-of-bounds in kfence_handle_page_fault",
- "[ 69.257816] [ T6006]c7 6006 Out-of-bounds access at 0xffffffca75c45000 ",
- "[ 69.273536] [ T6006]c7 6006 __do_kernel_fault+0xa8/0x11c",
- "pc : __mutex_lock+0x428/0x99c ",
- "sp : ffff00003ce07150",
- "Call trace:",
- "",
- };
-
- final String[][] stripped = {
- { "Detected corrupted memory at 0xffffffffb6797ff9 [ 0xac . . . . . . ]:",
- "Detected corrupted memory at 0xffffffffb6797ff9" },
- };
- for (int i = 0; i < becomeNull.length; i++) {
- assertEquals(BootReceiver.stripSensitiveData(becomeNull[i]), null);
- }
-
- for (int i = 0; i < leftAsIs.length; i++) {
- assertEquals(BootReceiver.stripSensitiveData(leftAsIs[i]), leftAsIs[i]);
- }
-
- for (int i = 0; i < stripped.length; i++) {
- assertEquals(BootReceiver.stripSensitiveData(stripped[i][0]), stripped[i][1]);
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
index 7179c60..022c137 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
@@ -18,8 +18,12 @@
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
-import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS;
+import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS;
+import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST;
+import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST;
+import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS;
+import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_UNINTERRUPTIBLE;
import static android.view.accessibility.AccessibilityNodeInfo.ROOT_NODE_ID;
import static org.junit.Assert.assertEquals;
@@ -74,24 +78,37 @@
@Captor
private ArgumentCaptor<AccessibilityNodeInfo> mFindInfoCaptor;
+ @Captor
+ private ArgumentCaptor<List<AccessibilityNodeInfo>> mFindInfosCaptor;
@Captor private ArgumentCaptor<List<AccessibilityNodeInfo>> mPrefetchInfoListCaptor;
private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
private static final int MOCK_CLIENT_1_THREAD_AND_PROCESS_ID = 1;
private static final int MOCK_CLIENT_2_THREAD_AND_PROCESS_ID = 2;
- private static final String FRAME_LAYOUT_DESCRIPTION = "frameLayout";
+ private static final String ROOT_FRAME_LAYOUT_DESCRIPTION = "rootFrameLayout";
private static final String TEXT_VIEW_1_DESCRIPTION = "textView1";
private static final String TEXT_VIEW_2_DESCRIPTION = "textView2";
+ private static final String CHILD_FRAME_DESCRIPTION = "childFrameLayout";
+ private static final String TEXT_VIEW_3_DESCRIPTION = "textView3";
+ private static final String TEXT_VIEW_4_DESCRIPTION = "textView4";
+ private static final String VIRTUAL_VIEW_1_DESCRIPTION = "virtual descendant 1";
+ private static final String VIRTUAL_VIEW_2_DESCRIPTION = "virtual descendant 2";
+ private static final String VIRTUAL_VIEW_3_DESCRIPTION = "virtual descendant 3";
- private TestFrameLayout mFrameLayout;
+ private TestFrameLayout mRootFrameLayout;
+ private TestFrameLayout mChildFrameLayout;
private TestTextView mTextView1;
- private TestTextView2 mTextView2;
+ private TestTextView mTextView2;
+ private TestTextView mTextView3;
+ private TestTextView mTextView4;
private boolean mSendClient1RequestForTextAfterTextPrefetched;
private boolean mSendClient2RequestForTextAfterTextPrefetched;
private boolean mSendRequestForTextAndIncludeUnImportantViews;
private boolean mSendClient1RequestForRootAfterTextPrefetched;
+ private boolean mSendClient2RequestForTextAfterRootPrefetched;
+
private int mMockClient1InteractionId;
private int mMockClient2InteractionId;
@@ -103,23 +120,45 @@
final Context context = mInstrumentation.getTargetContext();
final ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay());
- mFrameLayout = new TestFrameLayout(context);
- mTextView1 = new TestTextView(context);
- mTextView2 = new TestTextView2(context);
+ mTextView1 = new TestTextView(context, 1, TEXT_VIEW_1_DESCRIPTION);
+ mTextView2 = new TestTextView(context, 2, TEXT_VIEW_2_DESCRIPTION);
+ mTextView3 = new TestTextView(context, 4, TEXT_VIEW_3_DESCRIPTION);
+ mTextView4 = new TestTextView(context, 5, TEXT_VIEW_4_DESCRIPTION);
- mFrameLayout.addView(mTextView1);
- mFrameLayout.addView(mTextView2);
+ mChildFrameLayout = new TestFrameLayout(context, 3,
+ CHILD_FRAME_DESCRIPTION, new ArrayList<>(List.of(mTextView4)));
+ mRootFrameLayout = new TestFrameLayout(context, 0, ROOT_FRAME_LAYOUT_DESCRIPTION,
+ new ArrayList<>(
+ List.of(mTextView1, mTextView2, mChildFrameLayout, mTextView3)));
+
+ mRootFrameLayout.addView(mTextView1);
+ mRootFrameLayout.addView(mTextView2);
+ mChildFrameLayout.addView(mTextView4);
+ mRootFrameLayout.addView(mChildFrameLayout);
+ mRootFrameLayout.addView(mTextView3);
+
+ // Layout
+ // mRootFrameLayout
+ // / | | \
+ // mTextView1 mTextView2 mChildFrameLayout mTextView3
+ // |
+ // mTextView4
// The controller retrieves views through this manager, and registration happens on
- // when attached to a window, which we don't have. We can simply reference FrameLayout
- // with ROOT_NODE_ID
+ // when attached to a window, which we don't have. We can simply reference
+ // RootFrameLayout with ROOT_NODE_ID.
AccessibilityNodeIdManager.getInstance().registerViewWithId(
mTextView1, mTextView1.getAccessibilityViewId());
AccessibilityNodeIdManager.getInstance().registerViewWithId(
mTextView2, mTextView2.getAccessibilityViewId());
-
+ AccessibilityNodeIdManager.getInstance().registerViewWithId(
+ mTextView3, mTextView3.getAccessibilityViewId());
+ AccessibilityNodeIdManager.getInstance().registerViewWithId(
+ mChildFrameLayout, mChildFrameLayout.getAccessibilityViewId());
+ AccessibilityNodeIdManager.getInstance().registerViewWithId(
+ mTextView4, mTextView4.getAccessibilityViewId());
try {
- viewRootImpl.setView(mFrameLayout, new WindowManager.LayoutParams(), null);
+ viewRootImpl.setView(mRootFrameLayout, new WindowManager.LayoutParams(), null);
} catch (WindowManager.BadTokenException e) {
// activity isn't running, we will ignore BadTokenException.
@@ -137,66 +176,79 @@
mTextView1.getAccessibilityViewId());
AccessibilityNodeIdManager.getInstance().unregisterViewWithId(
mTextView2.getAccessibilityViewId());
+ AccessibilityNodeIdManager.getInstance().unregisterViewWithId(
+ mTextView3.getAccessibilityViewId());
+ AccessibilityNodeIdManager.getInstance().unregisterViewWithId(
+ mTextView4.getAccessibilityViewId());
+ AccessibilityNodeIdManager.getInstance().unregisterViewWithId(
+ mChildFrameLayout.getAccessibilityViewId());
}
/**
* Tests a basic request for the root node with prefetch flag
- * {@link AccessibilityNodeInfo#FLAG_PREFETCH_DESCENDANTS}
+ * {@link AccessibilityNodeInfo#FLAG_PREFETCH_DESCENDANTS_HYBRID}
*
* @throws RemoteException
*/
@Test
public void testFindRootView_withOneClient_shouldReturnRootNodeAndPrefetchDescendants()
throws RemoteException {
- // Request for our FrameLayout
+ // Request for our RootFrameLayout.
sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
- mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
+ mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID);
mInstrumentation.waitForIdleSync();
- // Verify we get FrameLayout
+ // Verify we get RootFrameLayout.
verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
- assertEquals(FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription());
+ assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription());
verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(
mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId));
- // The descendants are our two TextViews
+ // The descendants are RootFrameLayout's 5 descendants.
List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
- assertEquals(2, prefetchedNodes.size());
+ assertEquals(5, prefetchedNodes.size());
assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription());
-
+ assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription());
+ assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNodes.get(3).getContentDescription());
+ assertEquals(TEXT_VIEW_4_DESCRIPTION, prefetchedNodes.get(4).getContentDescription());
}
/**
- * Tests a basic request for TestTextView1's node with prefetch flag
- * {@link AccessibilityNodeInfo#FLAG_PREFETCH_SIBLINGS}
+ * Tests a basic request for TextView1's node with prefetch flag.
+ * {@link AccessibilityNodeInfo#FLAG_PREFETCH_SIBLINGS} and
+ * {@link AccessibilityNodeInfo#FLAG_PREFETCH_ANCESTORS}.
*
* @throws RemoteException
*/
@Test
- public void testFindTextView_withOneClient_shouldReturnNodeAndPrefetchedSiblings()
+ public void testFindTextView_withOneClient_shouldReturnNodeAndPrefetchedSiblingsAndParent()
throws RemoteException {
- // Request for TextView1
+ // Request for TextView1.
sendNodeRequestToController(AccessibilityNodeInfo.makeNodeId(
mTextView1.getAccessibilityViewId(), AccessibilityNodeProvider.HOST_VIEW_ID),
- mMockClientCallback1, mMockClient1InteractionId, FLAG_PREFETCH_SIBLINGS);
+ mMockClientCallback1, mMockClient1InteractionId,
+ FLAG_PREFETCH_SIBLINGS | FLAG_PREFETCH_ANCESTORS);
mInstrumentation.waitForIdleSync();
- // Verify we get TextView1
+ // Verify we get TextView1.
verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription());
- // Verify the prefetched sibling of TextView1 is TextView2
+ // Verify the prefetched sibling of TextView1 is TextView2, ChildFrameLayout, and TextView3.
+ // The predecessor is RootFrameLayout.
verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(
mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId));
- // TextView2 is the prefetched sibling
List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
- assertEquals(1, prefetchedNodes.size());
- assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
+ assertEquals(4, prefetchedNodes.size());
+ assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
+ assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription());
+ assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription());
+ assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNodes.get(3).getContentDescription());
}
/**
@@ -213,7 +265,8 @@
@Test
public void testFindRootAndTextNodes_withTwoClients_shouldPreventClient1Prefetch()
throws RemoteException {
- mFrameLayout.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ mSendClient2RequestForTextAfterRootPrefetched = true;
+ mRootFrameLayout.setAccessibilityDelegate(new View.AccessibilityDelegate() {
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
@@ -221,41 +274,50 @@
mTextView1.getAccessibilityViewId(),
AccessibilityNodeProvider.HOST_VIEW_ID);
- // Enqueue a request when this node is found from a different service for
- // TextView1
+ if (mSendClient2RequestForTextAfterRootPrefetched) {
+ mSendClient2RequestForTextAfterRootPrefetched = false;
+
+ // Enqueue a request when this node is found from client 2 for TextView1.
sendNodeRequestToController(nodeId, mMockClientCallback2,
- mMockClient2InteractionId, FLAG_PREFETCH_SIBLINGS);
+ mMockClient2InteractionId,
+ FLAG_PREFETCH_SIBLINGS);
+ }
}
});
- // Client 1 request for FrameLayout
+ // Client 1 request for RootFrameLayout.
sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
- mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
+ mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID);
mInstrumentation.waitForIdleSync();
- // Verify client 1 gets FrameLayout
+ // Verify client 1 gets RootFrameLayout.
verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
- assertEquals(FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription());
+ assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription());
- // The second request is put in the queue in the FrameLayout's onInitializeA11yNodeInfo,
- // meaning prefetching is interrupted and does not even begin for the first request
+ // The second request is put in the queue in the RootFrameLayout's onInitializeA11yNodeInfo,
+ // meaning prefetching is does not occur for first request.
verify(mMockClientCallback1, never())
.setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt());
- // Verify client 2 gets TextView1
+ // Verify client 2 gets TextView1.
verify(mMockClientCallback2).setFindAccessibilityNodeInfoResult(
mFindInfoCaptor.capture(), eq(mMockClient2InteractionId));
infoSentToService = mFindInfoCaptor.getValue();
assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription());
- // Verify the prefetched sibling of TextView1 is TextView2 (FLAG_PREFETCH_SIBLINGS)
+ // Verify the prefetched sibling of TextView1 is TextView2 and ChildFrameLayout
+ // (FLAG_PREFETCH_SIBLINGS). The parent, RootFrameLayout, is also retrieved. Since
+ // predecessors takes priority over siblings, RootFrameLayout is the first node in the list.
verify(mMockClientCallback2).setPrefetchAccessibilityNodeInfoResult(
mPrefetchInfoListCaptor.capture(), eq(mMockClient2InteractionId));
List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
- assertEquals(1, prefetchedNodes.size());
- assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
+ assertEquals(4, prefetchedNodes.size());
+ assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
+ assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription());
+ assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription());
+ assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNodes.get(3).getContentDescription());
}
/**
@@ -282,43 +344,43 @@
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
- info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
final long nodeId = AccessibilityNodeInfo.makeNodeId(
mTextView1.getAccessibilityViewId(),
AccessibilityNodeProvider.HOST_VIEW_ID);
if (mSendClient1RequestForTextAfterTextPrefetched) {
- // Prevent a loop when processing second request
+ // Prevent a loop when processing this node's second request.
mSendClient1RequestForTextAfterTextPrefetched = false;
- // TextView1 is prefetched here after the FrameLayout is found. Now enqueue a
- // same-client request for TextView1
+ // TextView1 is prefetched here after the RootFrameLayout is found. Now enqueue
+ // a same-client request for TextView1.
sendNodeRequestToController(nodeId, mMockClientCallback1,
- ++mMockClient1InteractionId, FLAG_PREFETCH_SIBLINGS);
+ ++mMockClient1InteractionId,
+ FLAG_PREFETCH_SIBLINGS | FLAG_PREFETCH_ANCESTORS);
}
}
});
- // Client 1 requests FrameLayout
+ // Client 1 requests RootFrameLayout.
sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
- mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
+ mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID);
- // Flush out all messages
+ // Flush out all messages.
mInstrumentation.waitForIdleSync();
- // When TextView1 is prefetched for FrameLayout, we put a message on the queue in
- // TextView1's onInitializeA11yNodeInfo that requests for TextView1. The service thus get
+ // When TextView1 is prefetched for RootFrameLayout, we put a message on the queue in
+ // TextView1's onInitializeA11yNodeInfo that requests for TextView1. The service thus gets
// two node results for FrameLayout and TextView1.
verify(mMockClientCallback1, times(2))
.setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt());
List<AccessibilityNodeInfo> foundNodes = mFindInfoCaptor.getAllValues();
- assertEquals(FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription());
+ assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription());
assertEquals(TEXT_VIEW_1_DESCRIPTION, foundNodes.get(1).getContentDescription());
- // The controller will look at FrameLayout's prefetched nodes and find matching nodes in
- // pending requests. The prefetched TextView1 matches the second request. This is removed
- // from the first request's prefetch list, which is now empty. The second
- // request was removed from queue and prefetching for this request never occurred.
+ // The controller will look at RootFrameLayout's prefetched nodes and find matching nodes in
+ // pending requests. The prefetched TextView1 satisfied the second request. This is removed
+ // from the first request's prefetch list, which is now empty. The second request is removed
+ // from queue.
verify(mMockClientCallback1, never())
.setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
eq(mMockClient1InteractionId - 1));
@@ -347,42 +409,41 @@
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
- info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
final long nodeId = AccessibilityNodeInfo.makeNodeId(
- mFrameLayout.getAccessibilityViewId(),
+ mRootFrameLayout.getAccessibilityViewId(),
AccessibilityNodeProvider.HOST_VIEW_ID);
if (mSendClient1RequestForRootAfterTextPrefetched) {
- // Prevent a loop when processing second request
+ // Prevent a loop when processing this node's second request.
mSendClient1RequestForRootAfterTextPrefetched = false;
// TextView1 is prefetched here after the FrameLayout is found. Now enqueue a
- // same-client request for FrameLayout
+ // same-client request for FrameLayout.
sendNodeRequestToController(nodeId, mMockClientCallback1,
- ++mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
+ ++mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID);
}
}
});
- // Client 1 requests FrameLayout
+ // Client 1 requests RootFrameLayout.
sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
- mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
+ mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID);
- // Flush out all messages
+ // Flush out all messages.
mInstrumentation.waitForIdleSync();
- // When TextView1 is prefetched for FrameLayout, we put a message on the queue in
+ // When TextView1 is prefetched for RootFrameLayout, we put a message on the queue in
// TextView1's onInitializeA11yNodeInfo that requests for TextView1. The service thus gets
// two node results for FrameLayout and TextView1.
verify(mMockClientCallback1, times(2))
.setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt());
List<AccessibilityNodeInfo> foundNodes = mFindInfoCaptor.getAllValues();
- assertEquals(FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription());
- assertEquals(FRAME_LAYOUT_DESCRIPTION, foundNodes.get(1).getContentDescription());
+ assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription());
+ assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, foundNodes.get(1).getContentDescription());
- // The controller will look at FrameLayout's prefetched nodes and find matching nodes in
+ // The controller will look at RootFrameLayout's prefetched nodes and find matching nodes in
// pending requests. The first requested node (FrameLayout) is also checked, and this
- // satifies the second request. The second request is removed from queue and prefetching
+ // satisfies the second request. The second request is removed from queue and prefetching
// for this request never occurs.
verify(mMockClientCallback1, times(1))
.setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
@@ -406,7 +467,6 @@
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
- info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
final long nodeId = AccessibilityNodeInfo.makeNodeId(
mTextView1.getAccessibilityViewId(),
AccessibilityNodeProvider.HOST_VIEW_ID);
@@ -414,30 +474,30 @@
if (mSendClient2RequestForTextAfterTextPrefetched) {
mSendClient2RequestForTextAfterTextPrefetched = false;
// TextView1 is prefetched here. Now enqueue client 2's request for
- // TextView1
+ // TextView1.
sendNodeRequestToController(nodeId, mMockClientCallback2,
mMockClient2InteractionId, FLAG_PREFETCH_SIBLINGS);
}
}
});
- // Client 1 requests FrameLayout
+ // Client 1 requests RootFrameLayout.
sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
- mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
+ mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID);
mInstrumentation.waitForIdleSync();
- // Verify client 1 gets FrameLayout
+ // Verify client 1 gets RootFrameLayout.
verify(mMockClientCallback1, times(1))
.setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt());
- assertEquals(FRAME_LAYOUT_DESCRIPTION,
+ assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION,
mFindInfoCaptor.getValue().getContentDescription());
- // Verify client 1 doesn't have prefetched nodes
+ // Verify client 1 doesn't have prefetched nodes.
verify(mMockClientCallback1, never())
.setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
eq(mMockClient1InteractionId));
- // Verify client 2 gets TextView1
+ // Verify client 2 gets TextView1.
verify(mMockClientCallback2, times(1))
.setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt());
@@ -458,7 +518,6 @@
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
- info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
final long nodeId = AccessibilityNodeInfo.makeNodeId(
mTextView1.getAccessibilityViewId(),
AccessibilityNodeProvider.HOST_VIEW_ID);
@@ -466,17 +525,18 @@
if (mSendRequestForTextAndIncludeUnImportantViews) {
mSendRequestForTextAndIncludeUnImportantViews = false;
// TextView1 is prefetched here for client 1. Now enqueue a request from a
- // different client that holds different fetch flags for TextView1
+ // different client that holds different fetch flags for TextView1.
sendNodeRequestToController(nodeId, mMockClientCallback2,
mMockClient2InteractionId,
- FLAG_PREFETCH_SIBLINGS | FLAG_INCLUDE_NOT_IMPORTANT_VIEWS);
+ FLAG_PREFETCH_SIBLINGS | FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ | FLAG_PREFETCH_ANCESTORS);
}
}
});
// Mockito does not make copies of objects when called. It holds references, so
// the captor would point to client 2's results after all requests are processed. Verify
- // prefetched node immediately
+ // prefetched node immediately.
doAnswer(invocation -> {
List<AccessibilityNodeInfo> prefetched = invocation.getArgument(0);
assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetched.get(0).getContentDescription());
@@ -484,39 +544,290 @@
}).when(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(anyList(),
eq(mMockClient1InteractionId));
- // Client 1 requests FrameLayout
+ // Client 1 requests RootFrameLayout.
sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
- mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
+ mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID);
mInstrumentation.waitForIdleSync();
- // Verify client 1 gets FrameLayout
+ // Verify client 1 gets RootFrameLayout.
verify(mMockClientCallback1, times(1))
.setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(),
eq(mMockClient1InteractionId));
- assertEquals(FRAME_LAYOUT_DESCRIPTION,
+ assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION,
mFindInfoCaptor.getValue().getContentDescription());
// Verify client 1 has prefetched results. The only prefetched node is TextView1
- // (from above doAnswer)
+ // (from above doAnswer).
verify(mMockClientCallback1, times(1))
.setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
eq(mMockClient1InteractionId));
- // Verify client 2 gets TextView1
+ // Verify client 2 gets TextView1.
verify(mMockClientCallback2, times(1))
.setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(),
eq(mMockClient2InteractionId));
assertEquals(TEXT_VIEW_1_DESCRIPTION,
mFindInfoCaptor.getValue().getContentDescription());
- // Verify client 2 has TextView2 as a prefetched node
+ // Verify client 2 gets TextView1's siblings and its parent as prefetched nodes.
verify(mMockClientCallback2, times(1))
.setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
eq(mMockClient2InteractionId));
List<AccessibilityNodeInfo> prefetchedNode = mPrefetchInfoListCaptor.getValue();
- assertEquals(1, prefetchedNode.size());
- assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNode.get(0).getContentDescription());
+ assertEquals(4, prefetchedNode.size());
+ assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, prefetchedNode.get(0).getContentDescription());
+ assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNode.get(1).getContentDescription());
+ assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNode.get(2).getContentDescription());
+ assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNode.get(3).getContentDescription());
+ }
+
+ /**
+ * Tests a request for 4 nodes using depth first traversal.
+ * Request 1: Request the root node.
+ * Request 2: When TextView4 is prefetched, send a request for the root node. Depth first
+ * traversal completes here.
+ * Out of the 5 descendants, the root frame's 3rd child (TextView3) should not be prefetched,
+ * since this was not reached by the df-traversal.
+ *
+ * Layout
+ * mRootFrameLayout
+ * / | | \
+ * mTextView1 mTextView2 mChildFrameLayout *mTextView3*
+ * |
+ * mTextView4
+ * @throws RemoteException
+ */
+ @Test
+ public void testFindRootView_depthFirstStrategy_shouldReturnRootNodeAndPrefetch4Descendants()
+ throws RemoteException {
+ mTextView4.setAccessibilityDelegate(new View.AccessibilityDelegate(){
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ final long nodeId = AccessibilityNodeInfo.makeNodeId(
+ mRootFrameLayout.getAccessibilityViewId(),
+ AccessibilityNodeProvider.HOST_VIEW_ID);
+ // This request is satisfied by first request.
+ sendNodeRequestToController(nodeId, mMockClientCallback2,
+ mMockClient2InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID);
+
+ }
+ });
+ // Request for our RootFrameLayout.
+ sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
+ mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST);
+ mInstrumentation.waitForIdleSync();
+
+ // Verify we get RootFrameLayout.
+ verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
+ mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
+ AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
+ assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription());
+
+ verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(
+ mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId));
+ // Prefetch all the descendants besides TextView3.
+ List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
+ assertEquals(4, prefetchedNodes.size());
+ assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
+ assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription());
+ assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription());
+ assertEquals(TEXT_VIEW_4_DESCRIPTION, prefetchedNodes.get(3).getContentDescription());
+ }
+
+ /**
+ * Tests a request for 4 nodes using breadth first traversal.
+ * Request 1: Request the root node
+ * Request 2: When TextView3 is prefetched, send a request for the root node. Breadth first
+ * traversal completes here.
+ * Out of the 5 descendants, the child frame's child (TextView4) should not be prefetched, since
+ * this was not reached by the bf-traversal.
+ * Layout
+ * mRootFrameLayout
+ * / | | \
+ * mTextView1 mTextView2 mChildFrameLayout *mTextView3*
+ * |
+ * *mTextView4*
+ * @throws RemoteException
+ */
+ @Test
+ public void testFindRootView_breadthFirstStrategy_shouldReturnRootNodeAndPrefetch4Descendants()
+ throws RemoteException {
+
+ mTextView3.setAccessibilityDelegate(new View.AccessibilityDelegate(){
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ final long nodeId = AccessibilityNodeInfo.makeNodeId(
+ mRootFrameLayout.getAccessibilityViewId(),
+ AccessibilityNodeProvider.HOST_VIEW_ID);
+
+ // This request is satisfied by first request.
+ sendNodeRequestToController(nodeId, mMockClientCallback2,
+ mMockClient2InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID);
+ }
+ });
+ // Request for our RootFrameLayout.
+ sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
+ mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST);
+ mInstrumentation.waitForIdleSync();
+
+ // Verify we get RootFrameLayout.
+ verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
+ mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
+ AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
+ assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription());
+
+ verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(
+ mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId));
+ // Prefetch all the descendants besides TextView4.
+ List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
+ assertEquals(4, prefetchedNodes.size());
+ assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
+ assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription());
+ assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription());
+ assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNodes.get(3).getContentDescription());
+ }
+
+ /**
+ * Tests a request that should not have prefetching interrupted.
+ * Request 1: Client 1 requests the root node
+ * Request 2: When the root node is initialized in
+ * {@link TestFrameLayout#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)},
+ * Client 2 requests TextView1's node
+ *
+ * Request 1 is not interrupted during prefetch, and its prefetched node satisfies Request 2.
+ *
+ * @throws RemoteException
+ */
+ @Test
+ public void testFindRootAndTextNodes_withNoInterruptStrategy_shouldSatisfySecondRequest()
+ throws RemoteException {
+ mRootFrameLayout.setAccessibilityDelegate(new View.AccessibilityDelegate(){
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ final long nodeId = AccessibilityNodeInfo.makeNodeId(
+ mTextView1.getAccessibilityViewId(),
+ AccessibilityNodeProvider.HOST_VIEW_ID);
+
+ // TextView1 is prefetched here after the RootFrameLayout is found. Now enqueue a
+ // same-client request for RootFrameLayout.
+ sendNodeRequestToController(nodeId, mMockClientCallback2,
+ mMockClient2InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID);
+ }
+ });
+
+ // Client 1 request for RootFrameLayout.
+ sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
+ mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID
+ | FLAG_PREFETCH_UNINTERRUPTIBLE);
+
+ mInstrumentation.waitForIdleSync();
+
+ // When the controller returns the nodes, it clears the sent list. Check immediately since
+ // the captor will be cleared.
+ doAnswer(invocation -> {
+ List<AccessibilityNodeInfo> nodes = invocation.getArgument(0);
+ assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, nodes.get(0).getContentDescription());
+ assertEquals(TEXT_VIEW_2_DESCRIPTION, nodes.get(1).getContentDescription());
+ assertEquals(CHILD_FRAME_DESCRIPTION, nodes.get(2).getContentDescription());
+ assertEquals(TEXT_VIEW_3_DESCRIPTION, nodes.get(3).getContentDescription());
+ assertEquals(TEXT_VIEW_4_DESCRIPTION, nodes.get(4).getContentDescription());
+ return null;
+ }).when(mMockClientCallback1).setFindAccessibilityNodeInfosResult(
+ anyList(), eq(mMockClient1InteractionId));
+
+ verify(mMockClientCallback1, never())
+ .setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt());
+
+ // Verify client 2 gets TextView1.
+ verify(mMockClientCallback2).setFindAccessibilityNodeInfoResult(
+ mFindInfoCaptor.capture(), eq(mMockClient2InteractionId));
+ AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
+ assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription());
+ }
+
+ /**
+ * Tests a request for root node where a virtual hierarchy is prefetched.
+ *
+ * Layout
+ * mRootFrameLayout
+ * / | | \
+ * mTextView1 mTextView2 mChildFrameLayout *mTextView3*
+ * |
+ * *mTextView4*
+ * | \
+ * virtual view 1 virtual view 2
+ * |
+ * virtual view 3
+ * @throws RemoteException
+ */
+ @Test
+ public void testFindRootView_withVirtualView()
+ throws RemoteException {
+ mTextView4.setAccessibilityDelegate(new View.AccessibilityDelegate(){
+ @Override
+ public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
+ return new AccessibilityNodeProvider() {
+ @Override
+ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
+ if (virtualViewId == AccessibilityNodeProvider.HOST_VIEW_ID) {
+ AccessibilityNodeInfo node = new AccessibilityNodeInfo(host);
+ node.addChild(host, 1);
+ node.addChild(host, 2);
+ node.setContentDescription(TEXT_VIEW_4_DESCRIPTION);
+ return node;
+ } else if (virtualViewId == 1) {
+ AccessibilityNodeInfo node = new AccessibilityNodeInfo(
+ host, virtualViewId);
+ node.setParent(host);
+ node.setContentDescription(VIRTUAL_VIEW_1_DESCRIPTION);
+ node.addChild(host, 3);
+ return node;
+ } else if (virtualViewId == 2 || virtualViewId == 3) {
+ AccessibilityNodeInfo node = new AccessibilityNodeInfo(
+ host, virtualViewId);
+ node.setParent(host);
+ node.setContentDescription(virtualViewId == 2
+ ? VIRTUAL_VIEW_2_DESCRIPTION
+ : VIRTUAL_VIEW_3_DESCRIPTION);
+ return node;
+ }
+ return null;
+ }
+ };
+ }
+ });
+ // Request for our RootFrameLayout.
+ sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
+ mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST);
+ mInstrumentation.waitForIdleSync();
+
+ // Verify we get RootFrameLayout.
+ verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
+ mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
+ AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
+ assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription());
+
+ verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(
+ mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId));
+
+ List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
+ assertEquals(8, prefetchedNodes.size());
+ assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
+ assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription());
+ assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription());
+ assertEquals(TEXT_VIEW_4_DESCRIPTION, prefetchedNodes.get(3).getContentDescription());
+
+ assertEquals(VIRTUAL_VIEW_1_DESCRIPTION, prefetchedNodes.get(4).getContentDescription());
+ assertEquals(VIRTUAL_VIEW_3_DESCRIPTION, prefetchedNodes.get(5).getContentDescription());
+
+ assertEquals(VIRTUAL_VIEW_2_DESCRIPTION, prefetchedNodes.get(6).getContentDescription());
+ assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNodes.get(7).getContentDescription());
+
+
}
private void sendNodeRequestToController(long requestedNodeId,
@@ -536,9 +847,16 @@
}
private class TestFrameLayout extends FrameLayout {
+ private int mA11yId;
+ private String mContentDescription;
+ ArrayList<View> mChildren;
- TestFrameLayout(Context context) {
+ TestFrameLayout(Context context, int a11yId, String contentDescription,
+ ArrayList<View> children) {
super(context);
+ mA11yId = a11yId;
+ mContentDescription = contentDescription;
+ mChildren = children;
}
@Override
@@ -556,14 +874,15 @@
@Override
public int getAccessibilityViewId() {
// static id doesn't reset after tests so return the same one
- return 0;
+ return mA11yId;
}
@Override
public void addChildrenForAccessibility(ArrayList<View> outChildren) {
// ViewGroup#addChildrenForAccessbility sorting logic will switch these two
- outChildren.add(mTextView1);
- outChildren.add(mTextView2);
+ for (View view : mChildren) {
+ outChildren.add(view);
+ }
}
@Override
@@ -574,13 +893,17 @@
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
- info.setContentDescription(FRAME_LAYOUT_DESCRIPTION);
+ info.setContentDescription(mContentDescription);
}
}
private class TestTextView extends TextView {
- TestTextView(Context context) {
+ private int mA11yId;
+ private String mContentDescription;
+ TestTextView(Context context, int a11yId, String contentDescription) {
super(context);
+ mA11yId = a11yId;
+ mContentDescription = contentDescription;
}
@Override
@@ -595,7 +918,7 @@
@Override
public int getAccessibilityViewId() {
- return 1;
+ return mA11yId;
}
@Override
@@ -606,39 +929,7 @@
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
- info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
- }
- }
-
- private class TestTextView2 extends TextView {
- TestTextView2(Context context) {
- super(context);
- }
-
- @Override
- public int getWindowVisibility() {
- return VISIBLE;
- }
-
- @Override
- public boolean isShown() {
- return true;
- }
-
- @Override
- public int getAccessibilityViewId() {
- return 2;
- }
-
- @Override
- public boolean includeForAccessibility() {
- return true;
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setContentDescription(TEXT_VIEW_2_DESCRIPTION);
+ info.setContentDescription(mContentDescription);
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamServiceTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamServiceTest.java
new file mode 100644
index 0000000..74d2e0f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/dreams/DreamServiceTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.dreams;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.service.dreams.DreamService;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DreamServiceTest {
+ @Test
+ public void testMetadataParsing() throws PackageManager.NameNotFoundException {
+ final String testPackageName = "com.android.frameworks.servicestests";
+ final String testDreamClassName = "com.android.server.dreams.TestDreamService";
+ final String testSettingsActivity = "com.android.server.dreams/.TestDreamSettingsActivity";
+
+ final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ final ServiceInfo si = context.getPackageManager().getServiceInfo(
+ new ComponentName(testPackageName, testDreamClassName),
+ PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
+ final DreamService.DreamMetadata metadata = DreamService.getDreamMetadata(context, si);
+
+ assertEquals(0, metadata.settingsActivity.compareTo(
+ ComponentName.unflattenFromString(testSettingsActivity)));
+ assertFalse(metadata.showComplications);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/dreams/TestDreamService.java b/services/tests/servicestests/src/com/android/server/dreams/TestDreamService.java
new file mode 100644
index 0000000..3c99a98
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/dreams/TestDreamService.java
@@ -0,0 +1,25 @@
+/*
+ * 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.dreams;
+
+import android.service.dreams.DreamService;
+
+/**
+ * Dream service implementation for unit testing.
+ */
+public class TestDreamService extends DreamService {
+}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
index f26e094..096c80b 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
@@ -142,6 +142,7 @@
new AppInstallMetadata.Builder()
.setPackageName(packageName)
.setAppCertificates(Collections.singletonList(packageCert))
+ .setAppCertificateLineage(Collections.singletonList(packageCert))
.setVersionCode(version)
.setInstallerName("abc")
.setInstallerCertificates(Collections.singletonList("abc"))
@@ -183,6 +184,8 @@
new AppInstallMetadata.Builder()
.setPackageName(installedPackageName)
.setAppCertificates(Collections.singletonList(installedAppCertificate))
+ .setAppCertificateLineage(
+ Collections.singletonList(installedAppCertificate))
.setVersionCode(250)
.setInstallerName("abc")
.setInstallerCertificates(Collections.singletonList("abc"))
diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
index 441cd4b..1c860ca 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
@@ -183,6 +183,7 @@
return new AppInstallMetadata.Builder()
.setPackageName("abc")
.setAppCertificates(Collections.singletonList("abc"))
+ .setAppCertificateLineage(Collections.singletonList("abc"))
.setInstallerCertificates(Collections.singletonList("abc"))
.setInstallerName("abc")
.setVersionCode(-1)
diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
index b271a77..5089f74 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
@@ -49,6 +49,7 @@
new AppInstallMetadata.Builder()
.setPackageName(PACKAGE_NAME_1)
.setAppCertificates(Collections.singletonList(APP_CERTIFICATE))
+ .setAppCertificateLineage(Collections.singletonList(APP_CERTIFICATE))
.setVersionCode(2)
.build();
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
index 415e635..370bd80 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
@@ -54,6 +54,7 @@
new AppInstallMetadata.Builder()
.setPackageName("ddd")
.setAppCertificates(Collections.singletonList("777"))
+ .setAppCertificateLineage(Collections.singletonList("777"))
.build();
List<RuleIndexRange> resultingIndexes =
@@ -76,6 +77,7 @@
new AppInstallMetadata.Builder()
.setPackageName("ddd")
.setAppCertificates(Arrays.asList("777", "999"))
+ .setAppCertificateLineage(Arrays.asList("777", "999"))
.build();
List<RuleIndexRange> resultingIndexes =
@@ -99,6 +101,7 @@
new AppInstallMetadata.Builder()
.setPackageName("bbb")
.setAppCertificates(Collections.singletonList("999"))
+ .setAppCertificateLineage(Collections.singletonList("999"))
.build();
List<RuleIndexRange> resultingIndexes =
@@ -121,6 +124,7 @@
new AppInstallMetadata.Builder()
.setPackageName("ccc")
.setAppCertificates(Collections.singletonList("444"))
+ .setAppCertificateLineage(Collections.singletonList("444"))
.build();
List<RuleIndexRange> resultingIndexes =
@@ -153,6 +157,7 @@
new AppInstallMetadata.Builder()
.setPackageName("ccc")
.setAppCertificates(Collections.singletonList("444"))
+ .setAppCertificateLineage(Collections.singletonList("444"))
.build();
List<RuleIndexRange> resultingIndexes =
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
index b878779..9ed2e88 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
@@ -875,6 +875,11 @@
}
@Override
+ public boolean isAppCertificateLineageFormula() {
+ return false;
+ }
+
+ @Override
public boolean isInstallerFormula() {
return false;
}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
index ea9e6ff..6dccdf5 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
@@ -305,6 +305,11 @@
}
@Override
+ public boolean isAppCertificateLineageFormula() {
+ return false;
+ }
+
+ @Override
public boolean isInstallerFormula() {
return false;
}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index f865a50..af8ac6f 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -23,11 +23,13 @@
import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.net.INetd.FIREWALL_CHAIN_RESTRICTED;
import static android.net.INetd.FIREWALL_RULE_ALLOW;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -38,8 +40,10 @@
import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_FOREGROUND;
import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_SYSTEM;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_FOREGROUND;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_TOP;
import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
import static android.net.NetworkPolicyManager.POLICY_NONE;
@@ -200,6 +204,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
@@ -1877,55 +1882,99 @@
}
@Test
+ public void testLowPowerStandbyAllowlist() throws Exception {
+ callOnUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_TOP, 0);
+ callOnUidStateChanged(UID_B, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
+ callOnUidStateChanged(UID_C, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
+ expectHasInternetPermission(UID_A, true);
+ expectHasInternetPermission(UID_B, true);
+ expectHasInternetPermission(UID_C, true);
+
+ final NetworkPolicyManagerInternal internal = LocalServices
+ .getService(NetworkPolicyManagerInternal.class);
+
+ Map<Integer, Integer> firewallUidRules = new ArrayMap<>();
+ doAnswer(arg -> {
+ int[] uids = arg.getArgument(1);
+ int[] rules = arg.getArgument(2);
+ assertTrue(uids.length == rules.length);
+
+ for (int i = 0; i < uids.length; ++i) {
+ firewallUidRules.put(uids[i], rules[i]);
+ }
+ return null;
+ }).when(mNetworkManager).setFirewallUidRules(eq(FIREWALL_CHAIN_LOW_POWER_STANDBY),
+ any(int[].class), any(int[].class));
+
+ internal.setLowPowerStandbyAllowlist(new int[] { UID_B });
+ internal.setLowPowerStandbyActive(true);
+ assertEquals(FIREWALL_RULE_ALLOW, firewallUidRules.get(UID_A).intValue());
+ assertEquals(FIREWALL_RULE_ALLOW, firewallUidRules.get(UID_B).intValue());
+ assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+ assertFalse(mService.isUidNetworkingBlocked(UID_B, false));
+ assertTrue(mService.isUidNetworkingBlocked(UID_C, false));
+
+ internal.setLowPowerStandbyActive(false);
+ assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+ assertFalse(mService.isUidNetworkingBlocked(UID_B, false));
+ assertFalse(mService.isUidNetworkingBlocked(UID_C, false));
+ }
+
+ @Test
public void testUpdateEffectiveBlockedReasons() {
- final SparseArray<Pair<Integer, Integer>> effectiveBlockedReasons = new SparseArray<>();
- effectiveBlockedReasons.put(BLOCKED_REASON_NONE,
- Pair.create(BLOCKED_REASON_NONE, ALLOWED_REASON_NONE));
+ final Map<Pair<Integer, Integer>, Integer> effectiveBlockedReasons = new HashMap<>();
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_NONE, ALLOWED_REASON_NONE),
+ BLOCKED_REASON_NONE);
- effectiveBlockedReasons.put(BLOCKED_REASON_NONE,
- Pair.create(BLOCKED_REASON_BATTERY_SAVER, ALLOWED_REASON_SYSTEM));
- effectiveBlockedReasons.put(BLOCKED_REASON_NONE,
- Pair.create(BLOCKED_REASON_BATTERY_SAVER | BLOCKED_REASON_DOZE,
- ALLOWED_REASON_SYSTEM));
- effectiveBlockedReasons.put(BLOCKED_REASON_NONE,
- Pair.create(BLOCKED_METERED_REASON_DATA_SAVER,
- ALLOWED_METERED_REASON_SYSTEM));
- effectiveBlockedReasons.put(BLOCKED_REASON_NONE,
- Pair.create(BLOCKED_METERED_REASON_DATA_SAVER
- | BLOCKED_METERED_REASON_USER_RESTRICTED,
- ALLOWED_METERED_REASON_SYSTEM));
+ effectiveBlockedReasons.put(
+ Pair.create(BLOCKED_REASON_BATTERY_SAVER, ALLOWED_REASON_SYSTEM),
+ BLOCKED_REASON_NONE);
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_BATTERY_SAVER | BLOCKED_REASON_DOZE,
+ ALLOWED_REASON_SYSTEM), BLOCKED_REASON_NONE);
+ effectiveBlockedReasons.put(
+ Pair.create(BLOCKED_METERED_REASON_DATA_SAVER, ALLOWED_METERED_REASON_SYSTEM),
+ BLOCKED_REASON_NONE);
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_METERED_REASON_DATA_SAVER
+ | BLOCKED_METERED_REASON_USER_RESTRICTED,
+ ALLOWED_METERED_REASON_SYSTEM), BLOCKED_REASON_NONE);
- effectiveBlockedReasons.put(BLOCKED_METERED_REASON_DATA_SAVER,
+ effectiveBlockedReasons.put(
Pair.create(BLOCKED_REASON_BATTERY_SAVER | BLOCKED_METERED_REASON_DATA_SAVER,
- ALLOWED_REASON_SYSTEM));
- effectiveBlockedReasons.put(BLOCKED_REASON_APP_STANDBY,
+ ALLOWED_REASON_SYSTEM), BLOCKED_METERED_REASON_DATA_SAVER);
+ effectiveBlockedReasons.put(
Pair.create(BLOCKED_REASON_APP_STANDBY | BLOCKED_METERED_REASON_USER_RESTRICTED,
- ALLOWED_METERED_REASON_SYSTEM));
+ ALLOWED_METERED_REASON_SYSTEM), BLOCKED_REASON_APP_STANDBY);
- effectiveBlockedReasons.put(BLOCKED_REASON_NONE,
- Pair.create(BLOCKED_REASON_BATTERY_SAVER, ALLOWED_REASON_FOREGROUND));
- effectiveBlockedReasons.put(BLOCKED_REASON_NONE,
- Pair.create(BLOCKED_REASON_BATTERY_SAVER | BLOCKED_REASON_DOZE,
- ALLOWED_REASON_FOREGROUND));
- effectiveBlockedReasons.put(BLOCKED_REASON_NONE,
- Pair.create(BLOCKED_METERED_REASON_DATA_SAVER, ALLOWED_METERED_REASON_FOREGROUND));
- effectiveBlockedReasons.put(BLOCKED_REASON_NONE,
- Pair.create(BLOCKED_METERED_REASON_DATA_SAVER
- | BLOCKED_METERED_REASON_USER_RESTRICTED,
- ALLOWED_METERED_REASON_FOREGROUND));
- effectiveBlockedReasons.put(BLOCKED_METERED_REASON_DATA_SAVER,
+ effectiveBlockedReasons.put(
+ Pair.create(BLOCKED_REASON_BATTERY_SAVER, ALLOWED_REASON_FOREGROUND),
+ BLOCKED_REASON_NONE);
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_BATTERY_SAVER | BLOCKED_REASON_DOZE,
+ ALLOWED_REASON_FOREGROUND), BLOCKED_REASON_NONE);
+ effectiveBlockedReasons.put(
+ Pair.create(BLOCKED_METERED_REASON_DATA_SAVER, ALLOWED_METERED_REASON_FOREGROUND),
+ BLOCKED_REASON_NONE);
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_METERED_REASON_DATA_SAVER
+ | BLOCKED_METERED_REASON_USER_RESTRICTED,
+ ALLOWED_METERED_REASON_FOREGROUND), BLOCKED_REASON_NONE);
+ effectiveBlockedReasons.put(
Pair.create(BLOCKED_REASON_BATTERY_SAVER | BLOCKED_METERED_REASON_DATA_SAVER,
- ALLOWED_REASON_FOREGROUND));
- effectiveBlockedReasons.put(BLOCKED_REASON_BATTERY_SAVER,
- Pair.create(BLOCKED_REASON_BATTERY_SAVER
- | BLOCKED_METERED_REASON_USER_RESTRICTED,
- ALLOWED_METERED_REASON_FOREGROUND));
+ ALLOWED_REASON_FOREGROUND), BLOCKED_METERED_REASON_DATA_SAVER);
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_BATTERY_SAVER
+ | BLOCKED_METERED_REASON_USER_RESTRICTED,
+ ALLOWED_METERED_REASON_FOREGROUND), BLOCKED_REASON_BATTERY_SAVER);
+
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_LOW_POWER_STANDBY,
+ ALLOWED_REASON_FOREGROUND), BLOCKED_REASON_LOW_POWER_STANDBY);
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_LOW_POWER_STANDBY,
+ ALLOWED_REASON_TOP), BLOCKED_REASON_NONE);
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_LOW_POWER_STANDBY,
+ ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST), BLOCKED_REASON_NONE);
// TODO: test more combinations of blocked reasons.
- for (int i = 0; i < effectiveBlockedReasons.size(); ++i) {
- final int expectedEffectiveBlockedReasons = effectiveBlockedReasons.keyAt(i);
- final int blockedReasons = effectiveBlockedReasons.valueAt(i).first;
- final int allowedReasons = effectiveBlockedReasons.valueAt(i).second;
+ for (Map.Entry<Pair<Integer, Integer>, Integer> test : effectiveBlockedReasons.entrySet()) {
+ final int expectedEffectiveBlockedReasons = test.getValue();
+ final int blockedReasons = test.getKey().first;
+ final int allowedReasons = test.getKey().second;
final String errorMsg = "Expected="
+ blockedReasonsToString(expectedEffectiveBlockedReasons)
+ "; blockedReasons=" + blockedReasonsToString(blockedReasons)
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 31bdec1..952200d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -895,6 +895,7 @@
assertEquals(a.networkSecurityConfigRes, that.networkSecurityConfigRes);
assertEquals(a.taskAffinity, that.taskAffinity);
assertEquals(a.permission, that.permission);
+ assertEquals(a.getKnownActivityEmbeddingCerts(), that.getKnownActivityEmbeddingCerts());
assertEquals(a.processName, that.processName);
assertEquals(a.className, that.className);
assertEquals(a.manageSpaceActivityName, that.manageSpaceActivityName);
diff --git a/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java b/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java
index 4ae9613..00a7944 100644
--- a/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java
@@ -47,6 +47,7 @@
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
+import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.testutils.OffsettableClock;
import org.junit.After;
@@ -80,6 +81,8 @@
private IPowerManager mIPowerManagerMock;
@Mock
private PowerManagerInternal mPowerManagerInternalMock;
+ @Mock
+ private NetworkPolicyManagerInternal mNetworkPolicyManagerInternal;
@Before
public void setUp() throws Exception {
@@ -90,6 +93,7 @@
PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock, null, null);
when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
+ addLocalServiceMock(NetworkPolicyManagerInternal.class, mNetworkPolicyManagerInternal);
when(mIPowerManagerMock.isInteractive()).thenReturn(true);
@@ -121,6 +125,7 @@
public void tearDown() throws Exception {
LocalServices.removeServiceForTest(PowerManagerInternal.class);
LocalServices.removeServiceForTest(LowPowerStandbyControllerInternal.class);
+ LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
}
@Test
@@ -130,6 +135,7 @@
assertThat(mController.isActive()).isFalse();
verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+ verify(mNetworkPolicyManagerInternal, never()).setLowPowerStandbyActive(anyBoolean());
}
@Test
@@ -142,6 +148,7 @@
awaitStandbyTimeoutAlarm();
assertThat(mController.isActive()).isTrue();
verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(true);
+ verify(mNetworkPolicyManagerInternal, times(1)).setLowPowerStandbyActive(true);
}
private void awaitStandbyTimeoutAlarm() {
@@ -169,6 +176,7 @@
assertThat(mController.isActive()).isFalse();
verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+ verify(mNetworkPolicyManagerInternal, never()).setLowPowerStandbyActive(anyBoolean());
}
@Test
@@ -182,6 +190,7 @@
assertThat(mController.isActive()).isTrue();
verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(true);
+ verify(mNetworkPolicyManagerInternal, times(1)).setLowPowerStandbyActive(true);
}
@Test
@@ -197,6 +206,7 @@
assertThat(mController.isActive()).isFalse();
verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+ verify(mNetworkPolicyManagerInternal, never()).setLowPowerStandbyActive(anyBoolean());
}
private void verifyStandbyAlarmCancelled() {
@@ -221,6 +231,7 @@
assertThat(mController.isActive()).isFalse();
verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(false);
+ verify(mNetworkPolicyManagerInternal, times(1)).setLowPowerStandbyActive(false);
}
@Test
@@ -238,6 +249,7 @@
assertThat(mController.isActive()).isFalse();
verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(false);
+ verify(mNetworkPolicyManagerInternal, times(1)).setLowPowerStandbyActive(false);
}
@Test
@@ -255,6 +267,7 @@
assertThat(mController.isActive()).isTrue();
verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(false);
+ verify(mNetworkPolicyManagerInternal, never()).setLowPowerStandbyActive(false);
}
@Test
@@ -273,6 +286,7 @@
assertThat(mController.isActive()).isTrue();
verify(mPowerManagerInternalMock, times(2)).setLowPowerStandbyActive(true);
+ verify(mNetworkPolicyManagerInternal, times(2)).setLowPowerStandbyActive(true);
}
@Test
@@ -285,6 +299,7 @@
assertThat(mController.isActive()).isFalse();
verify(mAlarmManagerMock, never()).setExact(anyInt(), anyLong(), anyString(), any(), any());
verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+ verify(mNetworkPolicyManagerInternal, never()).setLowPowerStandbyActive(anyBoolean());
}
@Test
@@ -350,10 +365,12 @@
service.addToAllowlist(10);
mTestLooper.dispatchAll();
verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[] {10});
+ verify(mNetworkPolicyManagerInternal).setLowPowerStandbyAllowlist(new int[] {10});
service.removeFromAllowlist(10);
mTestLooper.dispatchAll();
verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[] {});
+ verify(mNetworkPolicyManagerInternal).setLowPowerStandbyAllowlist(new int[] {});
}
@Test
@@ -366,12 +383,14 @@
assertThat(mController.isActive()).isTrue();
verify(mPowerManagerInternalMock).setLowPowerStandbyActive(true);
+ verify(mNetworkPolicyManagerInternal).setLowPowerStandbyActive(true);
mController.forceActive(false);
mTestLooper.dispatchAll();
assertThat(mController.isActive()).isFalse();
verify(mPowerManagerInternalMock).setLowPowerStandbyActive(false);
+ verify(mNetworkPolicyManagerInternal).setLowPowerStandbyActive(false);
}
private void setLowPowerStandbySupportedConfig(boolean supported) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ef9494a..dfcab2b 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -102,6 +102,7 @@
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
@@ -241,6 +242,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
+@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
@RunWithLooper
public class NotificationManagerServiceTest extends UiServiceTestCase {
private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
@@ -1338,7 +1340,8 @@
mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment);
NotificationManagerService.PostNotificationRunnable runnable =
- mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+ mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), SystemClock.elapsedRealtime());
runnable.run();
waitForIdle();
@@ -1359,7 +1362,8 @@
when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(IMPORTANCE_NONE);
NotificationManagerService.PostNotificationRunnable runnable =
- mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+ mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), SystemClock.elapsedRealtime());
runnable.run();
waitForIdle();
@@ -3921,7 +3925,8 @@
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
mService.addEnqueuedNotification(r);
NotificationManagerService.PostNotificationRunnable runnable =
- mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+ mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), SystemClock.elapsedRealtime());
runnable.run();
waitForIdle();
@@ -3938,7 +3943,8 @@
r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
mService.addEnqueuedNotification(r);
NotificationManagerService.PostNotificationRunnable runnable =
- mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+ mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), SystemClock.elapsedRealtime());
runnable.run();
waitForIdle();
@@ -3954,7 +3960,8 @@
mService.addEnqueuedNotification(r);
NotificationManagerService.PostNotificationRunnable runnable =
- mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+ mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), SystemClock.elapsedRealtime());
runnable.run();
waitForIdle();
@@ -3967,12 +3974,14 @@
r.setCriticality(CriticalNotificationExtractor.CRITICAL_LOW);
mService.addEnqueuedNotification(r);
NotificationManagerService.PostNotificationRunnable runnable =
- mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+ mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), SystemClock.elapsedRealtime());
runnable.run();
r = generateNotificationRecord(mTestNotificationChannel, 1, null, false);
r.setCriticality(CriticalNotificationExtractor.CRITICAL);
- runnable = mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+ runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), SystemClock.elapsedRealtime());
mService.addEnqueuedNotification(r);
runnable.run();
@@ -4006,6 +4015,63 @@
}
@Test
+ public void testMediaStyleRemote_hasPermission() throws RemoteException {
+ String deviceName = "device";
+ when(mPackageManager.checkPermission(
+ eq(android.Manifest.permission.MEDIA_CONTENT_CONTROL), any(), anyInt()))
+ .thenReturn(PERMISSION_GRANTED);
+ Notification.MediaStyle style = new Notification.MediaStyle();
+ style.setRemotePlaybackInfo(deviceName, 0, null);
+ Notification.Builder nb = new Notification.Builder(mContext,
+ mTestNotificationChannel.getId())
+ .setStyle(style);
+
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "testMediaStyleRemoteHasPermission", mUid, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ NotificationRecord posted = mService.findNotificationLocked(
+ PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
+ Bundle extras = posted.getNotification().extras;
+
+ assertTrue(extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE));
+ assertEquals(deviceName, extras.getString(Notification.EXTRA_MEDIA_REMOTE_DEVICE));
+ }
+
+ @Test
+ public void testMediaStyleRemote_noPermission() throws RemoteException {
+ String deviceName = "device";
+ when(mPackageManager.checkPermission(
+ eq(android.Manifest.permission.MEDIA_CONTENT_CONTROL), any(), anyInt()))
+ .thenReturn(PERMISSION_DENIED);
+ Notification.MediaStyle style = new Notification.MediaStyle();
+ style.setRemotePlaybackInfo(deviceName, 0, null);
+ Notification.Builder nb = new Notification.Builder(mContext,
+ mTestNotificationChannel.getId())
+ .setStyle(style);
+
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "testMediaStyleRemoteNoPermission", mUid, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ NotificationRecord posted = mService.findNotificationLocked(
+ PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
+
+ assertFalse(posted.getNotification().extras
+ .containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE));
+ }
+
+ @Test
public void testGetNotificationCountLocked() {
String sampleTagToExclude = null;
int sampleIdToExclude = 0;
@@ -4416,6 +4482,8 @@
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(original.getKey(),
+ original.getSbn().getPackageName(),
+ original.getUid(),
SystemClock.elapsedRealtime());
runnable.run();
waitForIdle();
@@ -4438,6 +4506,8 @@
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(update.getKey(),
+ update.getSbn().getPackageName(),
+ update.getUid(),
SystemClock.elapsedRealtime());
runnable.run();
waitForIdle();
@@ -6533,7 +6603,8 @@
assertNull(update.getSbn().getNotification().getSmallIcon());
NotificationManagerService.PostNotificationRunnable runnable =
- mService.new PostNotificationRunnable(update.getKey(),
+ mService.new PostNotificationRunnable(update.getKey(), r.getSbn().getPackageName(),
+ r.getUid(),
SystemClock.elapsedRealtime());
runnable.run();
waitForIdle();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
index fec5405..d922f40 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
@@ -657,7 +657,8 @@
when(mPermissionHelper.hasPermission(anyInt())).thenReturn(false);
NotificationManagerService.PostNotificationRunnable runnable =
- mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+ mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), SystemClock.elapsedRealtime());
runnable.run();
waitForIdle();
@@ -790,7 +791,8 @@
mService.addEnqueuedNotification(r);
NotificationManagerService.PostNotificationRunnable runnable =
- mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+ mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), SystemClock.elapsedRealtime());
runnable.run();
waitForIdle();
@@ -806,7 +808,8 @@
r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
mService.addEnqueuedNotification(r);
- runnable = mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+ runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), SystemClock.elapsedRealtime());
runnable.run();
waitForIdle();
@@ -822,7 +825,8 @@
r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
mService.addEnqueuedNotification(r);
- runnable = mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+ runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), SystemClock.elapsedRealtime());
runnable.run();
waitForIdle();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
index 3b67182..a3440b4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -233,8 +233,8 @@
public void testSetNotificationPermission_grantReviewRequired() throws Exception {
mPermissionHelper.setNotificationPermission("pkg", 10, true, false, true);
- verify(mPermManager).grantRuntimePermission(
- "pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
+ verify(mPermManager).revokeRuntimePermission(
+ "pkg", Manifest.permission.POST_NOTIFICATIONS, 10, "PermissionHelper");
verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
FLAG_PERMISSION_REVIEW_REQUIRED, FLAG_PERMISSION_REVIEW_REQUIRED, true, 10);
}
@@ -245,8 +245,8 @@
"pkg", 10, true, false);
mPermissionHelper.setNotificationPermission(pkgPerm);
- verify(mPermManager).grantRuntimePermission(
- "pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
+ verify(mPermManager).revokeRuntimePermission(
+ "pkg", Manifest.permission.POST_NOTIFICATIONS, 10, "PermissionHelper");
verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
FLAG_PERMISSION_REVIEW_REQUIRED, FLAG_PERMISSION_REVIEW_REQUIRED, true, 10);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index c58bf3b..c8e48a4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -75,6 +75,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.SigningDetails;
import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
@@ -84,9 +85,11 @@
import android.service.voice.IVoiceInteractionSession;
import android.util.Pair;
import android.view.Gravity;
+import android.window.TaskFragmentOrganizerToken;
import androidx.test.filters.SmallTest;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
import com.android.server.wm.utils.MockTracker;
@@ -94,6 +97,10 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* Tests for the {@link ActivityStarter} class.
*
@@ -1109,7 +1116,7 @@
}
@Test
- public void testStartActivityInner_inTaskFragment() {
+ public void testStartActivityInner_inTaskFragment_failsByDefault() {
final ActivityStarter starter = prepareStarter(0, false);
final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build();
final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setCreateTask(true).build();
@@ -1130,6 +1137,97 @@
/* restrictedBgActivity */false,
/* intentGrants */null);
+ assertFalse(taskFragment.hasChild());
+ }
+
+ @Test
+ public void testStartActivityInner_inTaskFragment_allowedForSameUid() {
+ final ActivityStarter starter = prepareStarter(0, false);
+ final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build();
+ final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final TaskFragment taskFragment = new TaskFragment(mAtm, sourceRecord.token,
+ true /* createdByOrganizer */);
+ sourceRecord.getTask().addChild(taskFragment, POSITION_TOP);
+
+ taskFragment.setTaskFragmentOrganizer(mock(TaskFragmentOrganizerToken.class),
+ targetRecord.getUid(), "test_process_name");
+
+ starter.startActivityInner(
+ /* r */targetRecord,
+ /* sourceRecord */ sourceRecord,
+ /* voiceSession */null,
+ /* voiceInteractor */ null,
+ /* startFlags */ 0,
+ /* doResume */true,
+ /* options */null,
+ /* inTask */null,
+ /* inTaskFragment */ taskFragment,
+ /* restrictedBgActivity */false,
+ /* intentGrants */null);
+
+ assertTrue(taskFragment.hasChild());
+ }
+
+ @Test
+ public void testStartActivityInner_inTaskFragment_allowedTrustedCertUid() {
+ final ActivityStarter starter = prepareStarter(0, false);
+ final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build();
+ final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final TaskFragment taskFragment = new TaskFragment(mAtm, sourceRecord.token,
+ true /* createdByOrganizer */);
+ sourceRecord.getTask().addChild(taskFragment, POSITION_TOP);
+
+ taskFragment.setTaskFragmentOrganizer(mock(TaskFragmentOrganizerToken.class),
+ 12345, "test_process_name");
+ AndroidPackage androidPackage = mock(AndroidPackage.class);
+ doReturn(androidPackage).when(mMockPackageManager).getPackage(eq(12345));
+
+ Set<String> certs = new HashSet(Arrays.asList("test_cert1", "test_cert1"));
+ targetRecord.info.setKnownActivityEmbeddingCerts(certs);
+ SigningDetails signingDetails = mock(SigningDetails.class);
+ doReturn(true).when(signingDetails).hasAncestorOrSelfWithDigest(any());
+ doReturn(signingDetails).when(androidPackage).getSigningDetails();
+
+ starter.startActivityInner(
+ /* r */targetRecord,
+ /* sourceRecord */ sourceRecord,
+ /* voiceSession */null,
+ /* voiceInteractor */ null,
+ /* startFlags */ 0,
+ /* doResume */true,
+ /* options */null,
+ /* inTask */null,
+ /* inTaskFragment */ taskFragment,
+ /* restrictedBgActivity */false,
+ /* intentGrants */null);
+
+ assertTrue(taskFragment.hasChild());
+ }
+
+ @Test
+ public void testStartActivityInner_inTaskFragment_allowedForUntrustedEmbedding() {
+ final ActivityStarter starter = prepareStarter(0, false);
+ final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build();
+ final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final TaskFragment taskFragment = new TaskFragment(mAtm, sourceRecord.token,
+ true /* createdByOrganizer */);
+ sourceRecord.getTask().addChild(taskFragment, POSITION_TOP);
+
+ targetRecord.info.flags |= ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
+
+ starter.startActivityInner(
+ /* r */targetRecord,
+ /* sourceRecord */ sourceRecord,
+ /* voiceSession */null,
+ /* voiceInteractor */ null,
+ /* startFlags */ 0,
+ /* doResume */true,
+ /* options */null,
+ /* inTask */null,
+ /* inTaskFragment */ taskFragment,
+ /* restrictedBgActivity */false,
+ /* intentGrants */null);
+
assertTrue(taskFragment.hasChild());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index efc9a49..9f7130e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -447,25 +447,6 @@
}
/**
- * This tests override configuration updates for display content.
- */
- @Test
- public void testDisplayOverrideConfigUpdate() {
- final Configuration currentOverrideConfig =
- mDisplayContent.getRequestedOverrideConfiguration();
-
- // Create new, slightly changed override configuration and apply it to the display.
- final Configuration newOverrideConfig = new Configuration(currentOverrideConfig);
- newOverrideConfig.densityDpi += 120;
- newOverrideConfig.fontScale += 0.3;
-
- mWm.setNewDisplayOverrideConfiguration(newOverrideConfig, mDisplayContent);
-
- // Check that override config is applied.
- assertEquals(newOverrideConfig, mDisplayContent.getRequestedOverrideConfiguration());
- }
-
- /**
* This tests global configuration updates when default display config is updated.
*/
@Test
@@ -478,7 +459,8 @@
newOverrideConfig.densityDpi += 120;
newOverrideConfig.fontScale += 0.3;
- mWm.setNewDisplayOverrideConfiguration(newOverrideConfig, defaultDisplay);
+ defaultDisplay.updateDisplayOverrideConfigurationLocked(newOverrideConfig,
+ null /* starting */, false /* deferResume */, null /* result */);
// Check that global configuration is updated, as we've updated default display's config.
Configuration globalConfig = mWm.mRoot.getConfiguration();
@@ -486,7 +468,8 @@
assertEquals(newOverrideConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
// Return back to original values.
- mWm.setNewDisplayOverrideConfiguration(currentConfig, defaultDisplay);
+ defaultDisplay.updateDisplayOverrideConfigurationLocked(currentConfig,
+ null /* starting */, false /* deferResume */, null /* result */);
globalConfig = mWm.mRoot.getConfiguration();
assertEquals(currentConfig.densityDpi, globalConfig.densityDpi);
assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index 30ed7c2..8d92520 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -524,7 +524,10 @@
* defined in {@code RESOLVABLE_ERROR_}. A subclass should override this method. Otherwise,
* this method does nothing and returns null by default.
* @see android.telephony.euicc.EuiccManager#downloadSubscription
+ * @deprecated prefer {@link #onDownloadSubscription(int, int,
+ * DownloadableSubscription, boolean, boolean, Bundle)}
*/
+ @Deprecated
public DownloadSubscriptionResult onDownloadSubscription(int slotId,
@NonNull DownloadableSubscription subscription, boolean switchAfterDownload,
boolean forceDeactivateSim, @Nullable Bundle resolvedBundle) {
@@ -534,6 +537,37 @@
/**
* Download the given subscription.
*
+ * @param slotIndex Index of the SIM slot to use for the operation.
+ * @param portIndex Index of the port from the slot. portIndex is used when
+ * switchAfterDownload is set to {@code true}, otherwise download is port agnostic.
+ * @param subscription The subscription to download.
+ * @param switchAfterDownload If true, the subscription should be enabled upon successful
+ * download.
+ * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the
+ * eUICC, perform this action automatically. Otherwise, {@link #RESULT_MUST_DEACTIVATE_SIM}
+ * should be returned to allow the user to consent to this operation first.
+ * @param resolvedBundle The bundle containing information on resolved errors. It can contain
+ * a string of confirmation code for the key {@link #EXTRA_RESOLUTION_CONFIRMATION_CODE},
+ * and a boolean for key {@link #EXTRA_RESOLUTION_ALLOW_POLICY_RULES} indicating whether
+ * the user allows profile policy rules or not.
+ * @return a DownloadSubscriptionResult instance including a result code, a resolvable errors
+ * bit map, and original the card Id. The result code may be one of the predefined
+ * {@code RESULT_} constants or any implementation-specific code starting with
+ * {@link #RESULT_FIRST_USER}. The resolvable error bit map can be either 0 or values
+ * defined in {@code RESOLVABLE_ERROR_}.
+ * @see android.telephony.euicc.EuiccManager#downloadSubscription
+ */
+ @NonNull
+ public DownloadSubscriptionResult onDownloadSubscription(int slotIndex, int portIndex,
+ @NonNull DownloadableSubscription subscription, boolean switchAfterDownload,
+ boolean forceDeactivateSim, @NonNull Bundle resolvedBundle) {
+ // stub implementation, LPA needs to implement this
+ throw new UnsupportedOperationException("LPA must override onDownloadSubscription");
+ }
+
+ /**
+ * Download the given subscription.
+ *
* @param slotId ID of the SIM slot to use for the operation.
* @param subscription The subscription to download.
* @param switchAfterDownload If true, the subscription should be enabled upon successful
@@ -701,7 +735,8 @@
*/
private class IEuiccServiceWrapper extends IEuiccService.Stub {
@Override
- public void downloadSubscription(int slotId, DownloadableSubscription subscription,
+ public void downloadSubscription(int slotId, int portIndex,
+ DownloadableSubscription subscription,
boolean switchAfterDownload, boolean forceDeactivateSim, Bundle resolvedBundle,
IDownloadSubscriptionCallback callback) {
mExecutor.execute(new Runnable() {
diff --git a/telephony/java/android/service/euicc/IEuiccService.aidl b/telephony/java/android/service/euicc/IEuiccService.aidl
index 030e11a..6b0397d 100644
--- a/telephony/java/android/service/euicc/IEuiccService.aidl
+++ b/telephony/java/android/service/euicc/IEuiccService.aidl
@@ -35,9 +35,9 @@
/** @hide */
oneway interface IEuiccService {
- void downloadSubscription(int slotId, in DownloadableSubscription subscription,
- boolean switchAfterDownload, boolean forceDeactivateSim, in Bundle resolvedBundle,
- in IDownloadSubscriptionCallback callback);
+ void downloadSubscription(int slotId, int portIndex, in DownloadableSubscription subscription,
+ boolean switchAfterDownload, boolean forceDeactivateSim, in Bundle resolvedBundle,
+ in IDownloadSubscriptionCallback callback);
void getDownloadableSubscriptionMetadata(int slotId, in DownloadableSubscription subscription,
boolean forceDeactivateSim, in IGetDownloadableSubscriptionMetadataCallback callback);
void getEid(int slotId, in IGetEidCallback callback);
diff --git a/test-base/Android.bp b/test-base/Android.bp
index 8be7324..527159a 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -72,11 +72,16 @@
// Build the android.test.base_static library
// ==========================================
-// This is only intended for inclusion in the android.test.runner-minus-junit,
-// robolectric_android-all-stub and repackaged.android.test.* libraries.
+// This is only intended for use by the android.test.runner-minus-junit
+// library.
+//
// Must not be used elsewhere.
+//
java_library_static {
name: "android.test.base_static",
+ visibility: [
+ "//frameworks/base/test-runner",
+ ],
installable: false,
srcs: [":android-test-base-sources"],
@@ -91,28 +96,10 @@
sdk_version: "current",
}
-// Build the repackaged.android.test.base library
-// ==============================================
-// This contains repackaged versions of the classes from
-// android.test.base.
-java_library_static {
- name: "repackaged.android.test.base",
-
- sdk_version: "current",
- static_libs: ["android.test.base_static"],
-
- jarjar_rules: "jarjar-rules.txt",
- // Pin java_version until jarjar is certified to support later versions. http://b/72703434
- java_version: "1.8",
-}
-
// Build the android.test.base-minus-junit library
// ===============================================
// This contains the android.test classes from android.test.base plus
-// the com.android.internal.util.Predicate[s] classes. This is only
-// intended for inclusion in android.test.legacy and in
-// android.test.base-hiddenapi-annotations to avoid a dependency cycle and must
-// not be used elsewhere.
+// the com.android.internal.util.Predicate[s] classes.
java_library_static {
name: "android.test.base-minus-junit",
diff --git a/test-base/jarjar-rules.txt b/test-base/jarjar-rules.txt
deleted file mode 100644
index fd8555c..0000000
--- a/test-base/jarjar-rules.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-rule junit.** repackaged.junit.@1
-rule android.test.** repackaged.android.test.@1
-rule com.android.internal.util.** repackaged.com.android.internal.util.@1
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index 2a19af9..13a5dac 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -79,32 +79,6 @@
],
}
-// Build the repackaged.android.test.runner library
-// ================================================
-java_library_static {
- name: "repackaged.android.test.runner",
-
- srcs: [":android-test-runner-sources"],
- exclude_srcs: [
- "src/android/test/ActivityUnitTestCase.java",
- "src/android/test/ApplicationTestCase.java",
- "src/android/test/IsolatedContext.java",
- "src/android/test/ProviderTestCase.java",
- "src/android/test/ProviderTestCase2.java",
- "src/android/test/RenamingDelegatingContext.java",
- "src/android/test/ServiceTestCase.java",
- ],
-
- sdk_version: "current",
- libs: [
- "android.test.base_static",
- ],
-
- jarjar_rules: "jarjar-rules.txt",
- // Pin java_version until jarjar is certified to support later versions. http://b/72703434
- java_version: "1.8",
-}
-
// Make the current.txt available for use by the cts/tests/signature tests.
// ========================================================================
filegroup {
diff --git a/test-runner/jarjar-rules.txt b/test-runner/jarjar-rules.txt
deleted file mode 120000
index f6f7913..0000000
--- a/test-runner/jarjar-rules.txt
+++ /dev/null
@@ -1 +0,0 @@
-../test-base/jarjar-rules.txt
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
index ca735031..8d60466 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
@@ -18,7 +18,6 @@
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
-import androidx.test.filters.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
@@ -194,22 +193,4 @@
open fun launcherLayerReplacesApp() {
testSpec.replacesLayer(testApp.component, LAUNCHER_COMPONENT)
}
-
- @FlakyTest
- @Test
- fun runPresubmitAssertion() {
- flickerRule.checkPresubmitAssertions()
- }
-
- @FlakyTest
- @Test
- fun runPostsubmitAssertion() {
- flickerRule.checkPostsubmitAssertions()
- }
-
- @FlakyTest
- @Test
- fun runFlakyAssertion() {
- flickerRule.checkFlakyAssertions()
- }
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index c40dd5d..f603f6e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -96,24 +96,6 @@
}
}
- @FlakyTest
- @Test
- fun runPresubmitAssertion() {
- flickerRule.checkPresubmitAssertions()
- }
-
- @FlakyTest
- @Test
- fun runPostsubmitAssertion() {
- flickerRule.checkPostsubmitAssertions()
- }
-
- @FlakyTest
- @Test
- fun runFlakyAssertion() {
- flickerRule.checkFlakyAssertions()
- }
-
/**
* Windows maybe recreated when rotated. Checks that the focus does not change or if it does,
* focus returns to [testApp]
@@ -204,4 +186,4 @@
.getConfigRotationTests(repetitions = 3)
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/HandwritingIme/Android.bp b/tests/HandwritingIme/Android.bp
new file mode 100644
index 0000000..1f552bf
--- /dev/null
+++ b/tests/HandwritingIme/Android.bp
@@ -0,0 +1,35 @@
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "HandwritingIme",
+ srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
+ certificate: "platform",
+ platform_apis: true,
+ static_libs: [
+ "androidx.core_core",
+ "androidx.appcompat_appcompat",
+ "com.google.android.material_material",
+ ],
+}
diff --git a/tests/HandwritingIme/AndroidManifest.xml b/tests/HandwritingIme/AndroidManifest.xml
new file mode 100644
index 0000000..1445d95
--- /dev/null
+++ b/tests/HandwritingIme/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (018C) 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
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.test.handwritingime">
+
+ <application android:label="Handwriting IME">
+ <service android:name=".HandwritingIme"
+ android:process=":HandwritingIme"
+ android:label="Handwriting IME"
+ android:permission="android.permission.BIND_INPUT_METHOD"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.view.InputMethod"/>
+ </intent-filter>
+ <meta-data android:name="android.view.im"
+ android:resource="@xml/ime"/>
+ </service>
+
+ </application>
+</manifest>
diff --git a/packages/SystemUI/res/layout/qs_detail_switch.xml b/tests/HandwritingIme/res/xml/ime.xml
similarity index 62%
copy from packages/SystemUI/res/layout/qs_detail_switch.xml
copy to tests/HandwritingIme/res/xml/ime.xml
index abb2497..2e84a03 100644
--- a/packages/SystemUI/res/layout/qs_detail_switch.xml
+++ b/tests/HandwritingIme/res/xml/ime.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2019 The Android Open Source Project
+ ~ 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.
@@ -14,10 +15,6 @@
~ limitations under the License.
-->
-<Switch
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@android:id/toggle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:clickable="false"
- android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
\ No newline at end of file
+<!-- Configuration info for an input method -->
+<input-method xmlns:android="http://schemas.android.com/apk/res/android"
+ android:supportsStylusHandwriting="true"/>
\ No newline at end of file
diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
new file mode 100644
index 0000000..18f9623
--- /dev/null
+++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
@@ -0,0 +1,111 @@
+/*
+ * 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.google.android.test.handwritingime;
+
+import android.annotation.Nullable;
+import android.inputmethodservice.InputMethodService;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.FrameLayout;
+import android.widget.Toast;
+
+import java.util.Random;
+
+public class HandwritingIme extends InputMethodService {
+
+ public static final int HEIGHT_DP = 100;
+
+ private Window mInkWindow;
+ private InkView mInk;
+
+ static final String TAG = "HandwritingIme";
+
+ interface HandwritingFinisher {
+ void finish();
+ }
+
+ interface StylusListener {
+ void onStylusEvent(MotionEvent me);
+ }
+
+ final class StylusConsumer implements StylusListener {
+ @Override
+ public void onStylusEvent(MotionEvent me) {
+ HandwritingIme.this.onStylusEvent(me);
+ }
+ }
+
+ final class HandwritingFinisherImpl implements HandwritingFinisher {
+
+ HandwritingFinisherImpl() {}
+
+ @Override
+ public void finish() {
+ finishStylusHandwriting();
+ Log.d(TAG, "HandwritingIme called finishStylusHandwriting() ");
+ }
+ }
+
+ private void onStylusEvent(@Nullable MotionEvent event) {
+ // TODO Hookup recognizer here
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ sendKeyChar((char) (56 + new Random().nextInt(66)));
+ }
+ }
+
+ @Override
+ public View onCreateInputView() {
+ Log.d(TAG, "onCreateInputView");
+ final ViewGroup view = new FrameLayout(this);
+ final View inner = new View(this);
+ final float density = getResources().getDisplayMetrics().density;
+ final int height = (int) (HEIGHT_DP * density);
+ view.setPadding(0, 0, 0, 0);
+ view.addView(inner, new FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT, height));
+ inner.setBackgroundColor(0xff0110fe); // blue
+
+ return view;
+ }
+
+ public void onPrepareStylusHandwriting() {
+ Log.d(TAG, "onPrepareStylusHandwriting ");
+ if (mInk == null) {
+ mInk = new InkView(this, new HandwritingFinisherImpl(), new StylusConsumer());
+ }
+ }
+
+ @Override
+ public boolean onStartStylusHandwriting() {
+ Log.d(TAG, "onStartStylusHandwriting ");
+ Toast.makeText(this, "START HW", Toast.LENGTH_SHORT).show();
+ mInkWindow = getStylusHandwritingWindow();
+ mInkWindow.setContentView(mInk, mInk.getLayoutParams());
+ return true;
+ }
+
+ @Override
+ public void onFinishStylusHandwriting() {
+ Log.d(TAG, "onFinishStylusHandwriting ");
+ Toast.makeText(this, "Finish HW", Toast.LENGTH_SHORT).show();
+ // Free-up
+ ((ViewGroup) mInk.getParent()).removeView(mInk);
+ mInk = null;
+ }
+}
diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java
new file mode 100644
index 0000000..4ffdc92
--- /dev/null
+++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java
@@ -0,0 +1,164 @@
+/*
+ * 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.google.android.test.handwritingime;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Insets;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+class InkView extends View {
+ private static final long FINISH_TIMEOUT = 2500;
+ private final HandwritingIme.HandwritingFinisher mHwCanceller;
+ private final HandwritingIme.StylusConsumer mConsumer;
+ private Paint mPaint;
+ private Path mPath;
+ private float mX, mY;
+ private static final float STYLUS_MOVE_TOLERANCE = 1;
+ private Runnable mFinishRunnable;
+
+ InkView(Context context, HandwritingIme.HandwritingFinisher hwController,
+ HandwritingIme.StylusConsumer consumer) {
+ super(context);
+ mHwCanceller = hwController;
+ mConsumer = consumer;
+
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true);
+ mPaint.setDither(true);
+ mPaint.setColor(Color.GREEN);
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeJoin(Paint.Join.ROUND);
+ mPaint.setStrokeCap(Paint.Cap.ROUND);
+ mPaint.setStrokeWidth(14);
+
+ mPath = new Path();
+
+ WindowManager wm = context.getSystemService(WindowManager.class);
+ WindowMetrics metrics = wm.getCurrentWindowMetrics();
+ Insets insets = metrics.getWindowInsets()
+ .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars());
+ setLayoutParams(new ViewGroup.LayoutParams(
+ metrics.getBounds().width() - insets.left - insets.right,
+ metrics.getBounds().height() - insets.top - insets.bottom));
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ canvas.drawPath(mPath, mPaint);
+ canvas.drawARGB(20, 255, 50, 50);
+ }
+
+ private void stylusStart(float x, float y) {
+ mPath.moveTo(x, y);
+ mX = x;
+ mY = y;
+ }
+
+ private void stylusMove(float x, float y) {
+ float dx = Math.abs(x - mX);
+ float dy = Math.abs(y - mY);
+ if (mPath.isEmpty()) {
+ stylusStart(x, y);
+ }
+ if (dx >= STYLUS_MOVE_TOLERANCE || dy >= STYLUS_MOVE_TOLERANCE) {
+ mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
+ mX = x;
+ mY = y;
+ }
+ }
+
+ private void stylusFinish() {
+ mPath.lineTo(mX, mY);
+ // TODO: support offscreen? e.g. mCanvas.drawPath(mPath, mPaint);
+ mPath.reset();
+ mX = 0;
+ mY = 0;
+
+ }
+
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS) {
+ mConsumer.onStylusEvent(event);
+ android.util.Log.w(HandwritingIme.TAG, "INK touch onStylusEvent " + event);
+ float x = event.getX();
+ float y = event.getY();
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ cancelTimer();
+ stylusStart(x, y);
+ invalidate();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ stylusMove(x, y);
+ invalidate();
+ break;
+
+ case MotionEvent.ACTION_UP:
+ scheduleTimer();
+ break;
+
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void cancelTimer() {
+ if (mFinishRunnable != null) {
+ if (getHandler() != null) {
+ getHandler().removeCallbacks(mFinishRunnable);
+ }
+ mFinishRunnable = null;
+ }
+ if (getHandler() != null) {
+ getHandler().removeCallbacksAndMessages(null);
+ }
+ }
+
+ private void scheduleTimer() {
+ cancelTimer();
+ if (getHandler() != null) {
+ postDelayed(getFinishRunnable(), FINISH_TIMEOUT);
+ }
+ }
+
+ private Runnable getFinishRunnable() {
+ mFinishRunnable = () -> {
+ android.util.Log.e(HandwritingIme.TAG, "Hw view timer finishHandwriting ");
+ mHwCanceller.finish();
+ stylusFinish();
+ mPath.reset();
+ invalidate();
+ };
+
+ return mFinishRunnable;
+ }
+
+}
diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
index 824f91e..15a6afc 100644
--- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
+++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
@@ -62,6 +62,7 @@
"android.view.PendingInsetsControllerTest",
"android.window.", // all tests under the package.
"android.app.activity.ActivityThreadTest",
+ "android.app.activity.RegisterComponentCallbacksTest"
};
public FrameworksTestsFilter(Bundle testArgs) {
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index 1ec83a3..b18bdff 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -69,7 +69,6 @@
"test/**/*.proto",
],
proto: {
- plugin: "javastream",
+ type: "stream",
},
- static_libs: ["libprotobuf-java-lite"],
}
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index d3eb8e0..9604475 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -1269,18 +1269,19 @@
* support the NL80211_CMD_REG_CHANGED (otherwise it will find out on its own). The wificond
* updates in internal state in response to this Country Code update.
*
- * @return true on success, false otherwise.
+ * @param newCountryCode new country code. An ISO-3166-alpha2 country code which is 2-Character
+ * alphanumeric.
*/
- public boolean notifyCountryCodeChanged() {
- try {
- if (mWificond != null) {
- mWificond.notifyCountryCodeChanged();
- return true;
- }
- } catch (RemoteException e1) {
- Log.e(TAG, "Failed to notify country code changed due to remote exception");
+ public void notifyCountryCodeChanged(@Nullable String newCountryCode) {
+ if (mWificond == null) {
+ new RemoteException("Wificond service doesn't exist!").rethrowFromSystemServer();
}
- return false;
+ try {
+ mWificond.notifyCountryCodeChanged();
+ Log.i(TAG, "Receive country code change to " + newCountryCode);
+ } catch (RemoteException re) {
+ re.rethrowFromSystemServer();
+ }
}
/**
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index 4032a7b..a750696 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -1143,17 +1143,17 @@
@Test
public void testNotifyCountryCodeChanged() throws Exception {
doNothing().when(mWificond).notifyCountryCodeChanged();
- assertTrue(mWificondControl.notifyCountryCodeChanged());
+ mWificondControl.notifyCountryCodeChanged(TEST_COUNTRY_CODE);
verify(mWificond).notifyCountryCodeChanged();
}
/**
* Tests notifyCountryCodeChanged with RemoteException
*/
- @Test
+ @Test(expected = RuntimeException.class)
public void testNotifyCountryCodeChangedRemoteException() throws Exception {
doThrow(new RemoteException()).when(mWificond).notifyCountryCodeChanged();
- assertFalse(mWificondControl.notifyCountryCodeChanged());
+ mWificondControl.notifyCountryCodeChanged(TEST_COUNTRY_CODE);
verify(mWificond).notifyCountryCodeChanged();
}