Merge "Let top app can override insets behind notification shade"
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/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 7231047..2c085b4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -3068,6 +3068,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();
@@ -18198,6 +18199,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 +18208,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
@@ -51620,6 +51627,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();
@@ -51639,6 +51647,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();
@@ -51789,8 +51798,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
@@ -51955,6 +51971,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();
@@ -52012,6 +52029,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();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index dc63127..24b4f89 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -482,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 6da8933f0..321ebcf 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -914,7 +914,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 +3027,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 +3322,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";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index df09242..ff4ec31 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2061,7 +2061,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);
}
}
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 7c90b71..983dde3 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2696,6 +2696,7 @@
}
if (mDefaultBackCallback != null) {
getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mDefaultBackCallback);
+ mDefaultBackCallback = null;
}
if (mCallbacksController != null) {
mCallbacksController.clearCallbacks();
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..b838016 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3719,7 +3719,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>.
*
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index e8b5c0d..5f00342 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/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/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 62dd627..957cb24c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -180,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
@@ -200,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/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/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/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/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 df12d2c..18ec8f5 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -176,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.
@@ -247,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.
*
@@ -752,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/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/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/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/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/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/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 7e0d887..3fb0fe7 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.util.Log;
import android.view.accessibility.IAccessibilityEmbeddedConnection;
import android.view.InsetsState;
+import android.view.WindowManagerGlobal;
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,7 @@
public void release() {
// ViewRoot will release mSurfaceControl for us.
mViewRoot.die(true /* immediate */);
+ WindowManagerGlobal.getInstance().removeWindowlessRoot(mViewRoot);
+ mReleased = true;
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 39fc2c0..3ba305b 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;
@@ -4184,6 +4190,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 +4223,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 +4235,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 +4269,7 @@
return null;
}
- if (!useBlastSync && !reportNextDraw) {
+ if (!useBlastSync && !reportNextDraw && !hasPendingTransactions) {
return null;
}
@@ -10700,7 +10712,10 @@
if (mRemoved || !isHardwareEnabled()) {
t.apply();
} else {
- registerRtFrameCallback(frame -> mergeWithNextTransaction(t, frame));
+ mHasPendingTransactions = true;
+ registerRtFrameCallback(frame -> {
+ mergeWithNextTransaction(t, frame);
+ });
}
return true;
}
@@ -10841,6 +10856,7 @@
private void unregisterCompatOnBackInvokedCallback() {
if (mCompatOnBackInvokedCallback != null) {
mOnBackInvokedDispatcher.unregisterOnBackInvokedCallback(mCompatOnBackInvokedCallback);
+ mCompatOnBackInvokedCallback = null;
}
}
@@ -10854,4 +10870,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/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/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/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6297ed9..cb209ab1 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6266,4 +6266,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..e10ed24 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4724,5 +4724,8 @@
<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"/>
</resources>
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/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/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/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/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/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/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/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/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/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/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/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/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/drawable/ic_add_supervised_user.xml b/packages/SystemUI/res/drawable/ic_add_supervised_user.xml
new file mode 100644
index 0000000..627743e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_add_supervised_user.xml
@@ -0,0 +1,19 @@
+<!--
+ ~ 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.
+ -->
+<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/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/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/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/strings.xml b/packages/SystemUI/res/values/strings.xml
index d39e295..6a34ada 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2402,4 +2402,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/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/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/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/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..dd91f6f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -142,6 +142,7 @@
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);
// 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/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index bed254f..f972da5 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
@@ -80,8 +84,16 @@
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 +138,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 +155,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 +176,7 @@
controller?.unregisterCallback(this)
localMediaManager.stopScan()
localMediaManager.unregisterCallback(this)
+ muteAwaitConnectionManager?.stopListening()
}
fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
@@ -197,8 +215,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/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/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/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/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/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/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/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index f421d23..866f0d3 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,36 @@
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.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 +71,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 +88,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 +122,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 +213,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 */);
});
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/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/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..7a2eceb 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;
@@ -804,6 +803,7 @@
ControlsComponent controlsComponent,
InteractionJankMonitor interactionJankMonitor,
QsFrameTranslateController qsFrameTranslateController,
+ SysUiState sysUiState,
KeyguardUnlockAnimationController keyguardUnlockAnimationController) {
super(view,
falsingManager,
@@ -876,8 +876,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();
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/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 3ece240..7a7af4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -46,6 +46,7 @@
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;
@@ -159,6 +160,7 @@
private final AtomicBoolean mGuestCreationScheduled;
private FalsingManager mFalsingManager;
private View mView;
+ private String mCreateSupervisedUserPackage;
@Inject
public UserSwitcherController(Context context,
@@ -255,6 +257,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 +312,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 +323,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 +339,7 @@
}
records.add(new UserRecord(info, picture, false /* isGuest */,
isCurrent, false /* isAddUser */, false /* isRestricted */,
- switchToEnabled));
+ switchToEnabled, false /* isAddSupervisedUser */));
}
}
}
@@ -345,19 +347,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 +357,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 +372,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 +402,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 +518,9 @@
} else if (record.isAddUser) {
showAddUserDialog(dialogShower);
return;
+ } else if (record.isAddSupervisedUser) {
+ startSupervisedUserActivity();
+ return;
} else {
id = record.info.id;
}
@@ -561,6 +597,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 +993,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 +1009,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 +1056,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 +1064,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 +1073,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 +1102,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>");
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/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/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/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/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/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index 3d59497..64b5b86 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
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/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/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/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 419e84e..466d954 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -754,7 +754,7 @@
// WHEN asked to update the indication area
mController.setVisible(true);
- int runTasks = mExecutor.runAllReady();
+ mExecutor.runAllReady();
// THEN the owner info should be hidden
verifyHideIndication(INDICATION_TYPE_OWNER_INFO);
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/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..36d1629f 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,6 +106,7 @@
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;
@@ -366,6 +367,8 @@
private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock
private NotificationShadeWindowController mNotificationShadeWindowController;
+ @Mock
+ private SysUiState mSysUiState;
private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
private SysuiStatusBarStateController mStatusBarStateController;
private NotificationPanelViewController mNotificationPanelViewController;
@@ -555,6 +558,7 @@
mControlsComponent,
mInteractionJankMonitor,
mQsFrameTranslateController,
+ mSysUiState,
mKeyguardUnlockAnimationController);
mNotificationPanelViewController.initDependencies(
mStatusBar,
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..9dabf69 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
@@ -56,6 +56,7 @@
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -130,6 +131,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,
@@ -153,18 +169,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 +181,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 +201,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 +226,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 +247,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 +270,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 +292,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 +312,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 +334,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 +369,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 +402,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 +425,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/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/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 42bd926..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);
}
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..7b76de2 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;
@@ -32,6 +33,8 @@
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 +100,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 +124,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 +161,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 +174,8 @@
return;
}
addEvent(start, new UidStateEventWithBattery(start, now,
- batteryUsage - last.getBatteryUsage(), last), eventType);
+ batteryUsage.mutate().subtract(last.getBatteryUsage()).unmutate(), last),
+ eventType);
}
}
@@ -183,34 +189,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 +245,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 +254,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 +264,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 +290,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 +331,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 +365,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 +387,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 +419,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();
}
}
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
index b8f5c50..99808b6 100644
--- a/services/core/java/com/android/server/am/AppBatteryTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -28,8 +28,10 @@
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;
@@ -40,11 +42,13 @@
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.os.BatteryConsumer;
+import android.os.BatteryConsumer.Dimensions;
import android.os.BatteryStatsInternal;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
@@ -58,7 +62,6 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import android.util.SparseDoubleArray;
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
@@ -98,12 +101,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 +130,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 +165,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 +276,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 +314,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 +394,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 +463,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 +518,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 +537,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 +562,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 +602,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 +633,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 +651,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,6 +1001,30 @@
+ "current_drain_event_duration_based_threshold_enabled";
/**
+ * 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 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 {@link #mTrackerEnabled}.
*/
static final boolean DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED = true;
@@ -783,6 +1079,24 @@
false;
/**
+ * Default value to {@link #mBgCurrentDrainRestrictedBucketTypes}.
+ */
+ static final int DEFAULT_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET =
+ BATTERY_USAGE_TYPE_BACKGROUND;
+
+ /**
+ * Default value to {@link #mBgCurrentDrainBgRestrictedTypes}.
+ */
+ static final int DEFAULT_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED =
+ BATTERY_USAGE_TYPE_BACKGROUND | BATTERY_USAGE_TYPE_FOREGROUND_SERVICE;
+
+ /**
+ * Default value to {@link #mBgCurrentDrainPowerComponents}.
+ **/
+ @BatteryConsumer.PowerComponent
+ static final int DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS = POWER_COMPONENT_ANY;
+
+ /**
* The index to {@link #mBgCurrentDrainRestrictedBucketThreshold}
* and {@link #mBgCurrentDrainBgRestrictedThreshold}.
*/
@@ -830,6 +1144,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;
@@ -862,6 +1194,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:
@@ -912,14 +1247,33 @@
DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED,
DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD);
+ mBgCurrentDrainRestrictedBucketTypes =
+ DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET,
+ DEFAULT_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET);
+ mBgCurrentDrainBgRestrictedTypes =
+ DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED,
+ DEFAULT_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED);
+ mBgCurrentDrainPowerComponents =
+ DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS,
+ DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS);
+ 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);
+ DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS);
}
private void updateCurrentDrainMediaPlaybackMinDuration() {
@@ -970,18 +1324,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 +1386,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 +1401,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 +1410,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 +1435,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 +1446,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 +1518,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 +1568,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/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/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/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..240ed77 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -46,17 +46,19 @@
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;
@@ -70,11 +72,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 +92,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;
@@ -478,6 +483,8 @@
volatile boolean mRestrictBackgroundChangedInBsm;
@GuardedBy("mUidRulesFirstLock")
volatile boolean mRestrictedNetworkingMode;
+ @GuardedBy("mUidRulesFirstLock")
+ volatile boolean mLowPowerStandbyActive;
private final boolean mSuppressDefaultPolicy;
@@ -517,6 +524,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 +554,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.
@@ -3763,6 +3775,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 +3911,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 +4004,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 +4026,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 +4039,17 @@
}
updateRulesForPowerRestrictionsUL(uid);
}
+ if (mLowPowerStandbyActive) {
+ boolean allowedInLpsChanged =
+ isProcStateAllowedWhileInLowPowerStandby(oldUidState)
+ != isProcStateAllowedWhileInLowPowerStandby(newUidState);
+ if (allowedInLpsChanged) {
+ if (!allowedWhileIdleOrPowerSaveModeChanged) {
+ updateRulesForPowerRestrictionsUL(uid);
+ }
+ updateRuleForLowPowerStandbyUL(uid);
+ }
+ }
return true;
}
} finally {
@@ -4029,6 +4073,9 @@
updateRuleForRestrictPowerUL(uid);
}
updateRulesForPowerRestrictionsUL(uid);
+ if (mLowPowerStandbyActive) {
+ updateRuleForLowPowerStandbyUL(uid);
+ }
return true;
}
}
@@ -4232,6 +4279,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 +4352,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 +4697,7 @@
mPowerSaveTempWhitelistAppIds.delete(uid);
mAppIdleTempWhitelistAppIds.delete(uid);
mUidFirewallRestrictedModeRules.delete(uid);
+ mUidFirewallLowPowerStandbyModeRules.delete(uid);
synchronized (mUidStateCallbackInfos) {
mUidStateCallbackInfos.remove(uid);
}
@@ -4823,6 +4923,7 @@
}
final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid);
+ final boolean isTop = isUidTop(uid);
final boolean isWhitelisted = isWhitelistedFromPowerSaveUL(uid, mDeviceIdleMode);
@@ -4836,17 +4937,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 +4973,7 @@
+ ", mRestrictPower: " + mRestrictPower
+ ", mDeviceIdleMode: " + mDeviceIdleMode
+ ", isForeground=" + isForeground
+ + ", isTop=" + isTop
+ ", isWhitelisted=" + isWhitelisted
+ ", oldUidBlockedState=" + previousUidBlockedState.toString()
+ ", newUidBlockedState=" + uidBlockedState.toString());
@@ -5398,6 +5504,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 +5553,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 +5831,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) {
@@ -5888,6 +6060,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 +6078,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 +6106,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 +6115,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 +6137,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 +6159,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 +6235,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/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/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/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..49f759d 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;
}
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/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a25dc420..2c58d92 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();
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/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/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index d86382d..26871c3 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;
@@ -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/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 8a373bf..1bd305e 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -78,13 +78,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;
@@ -197,6 +198,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 +475,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 +647,7 @@
getSyncTransaction().remove(mSurfaceControl);
setSurfaceControl(null);
mLastSurfacePosition.set(0, 0);
+ mLastDeltaRotation = Surface.ROTATION_0;
scheduleAnimation();
}
if (mOverlayHost != null) {
@@ -3127,12 +3131,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 +3205,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 b865460..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
@@ -8342,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;
@@ -8352,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/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/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/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..fb8749e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -46,9 +46,10 @@
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.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;
@@ -113,6 +114,7 @@
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;
@@ -566,7 +568,7 @@
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET,
DeviceConfig::getFloat,
- AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+ AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_THRESHOLD);
bgCurrentDrainRestrictedBucketThreshold.set(restrictBucketThreshold);
bgCurrentDrainBgRestrictedThreshold = new DeviceConfigSession<>(
@@ -1294,7 +1296,7 @@
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET,
DeviceConfig::getFloat,
- AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+ AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_THRESHOLD);
bgCurrentDrainRestrictedBucketThreshold.set(restrictBucketThreshold);
bgCurrentDrainBgRestrictedThreshold = new DeviceConfigSession<>(
@@ -1932,9 +1934,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 +2239,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/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/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/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/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/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
+}