Merge "Enable flicker test group3 with Shell transition"
diff --git a/Android.bp b/Android.bp
index d64951c..e96c731 100644
--- a/Android.bp
+++ b/Android.bp
@@ -155,38 +155,6 @@
}
java_library_with_nonpublic_deps {
- name: "framework-updatable-stubs-module_libs_api",
- static_libs: [
- "android.net.ipsec.ike.stubs.module_lib",
- "framework-appsearch.stubs.module_lib",
- "framework-connectivity.stubs.module_lib",
- "framework-connectivity-tiramisu.stubs.module_lib",
- "framework-graphics.stubs.module_lib",
- "framework-media.stubs.module_lib",
- "framework-mediaprovider.stubs.module_lib",
- "framework-permission.stubs.module_lib",
- "framework-permission-s.stubs.module_lib",
- "framework-scheduling.stubs.module_lib",
- "framework-sdkextensions.stubs.module_lib",
- "framework-statsd.stubs.module_lib",
- "framework-supplementalprocess.stubs.module_lib",
- "framework-tethering.stubs.module_lib",
- "framework-uwb.stubs.module_lib",
- "framework-nearby.stubs.module_lib",
- "framework-wifi.stubs.module_lib",
- ],
- soong_config_variables: {
- include_nonpublic_framework_api: {
- static_libs: [
- "framework-supplementalapi.stubs.module_lib",
- ],
- },
- },
- sdk_version: "module_current",
- visibility: ["//visibility:private"],
-}
-
-java_library_with_nonpublic_deps {
name: "framework-all",
installable: false,
static_libs: [
@@ -212,7 +180,7 @@
soong_config_variables: {
include_nonpublic_framework_api: {
static_libs: [
- "framework-supplementalapi.stubs.module_lib",
+ "framework-supplementalapi.impl",
],
},
},
@@ -466,7 +434,6 @@
name: "framework-ike-shared-srcs",
visibility: ["//packages/modules/IPsec"],
srcs: [
- "core/java/android/net/annotations/PolicyDirection.java",
"core/java/com/android/internal/util/HexDump.java",
"core/java/com/android/internal/util/WakeupMessage.java",
"services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java",
diff --git a/ApiDocs.bp b/ApiDocs.bp
index feb43d1..7d4a5e5 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -190,46 +190,6 @@
},
}
-// This produces the same annotations.zip as framework-doc-stubs, but by using
-// outputs from individual modules instead of all the source code.
-genrule {
- name: "sdk-annotations.zip",
- srcs: [
- ":android-non-updatable-doc-stubs{.annotations.zip}",
-
- // Conscrypt and i18n currently do not enable annotations
- // ":conscrypt.module.public.api{.public.annotations.zip}",
- // ":i18n.module.public.api{.public.annotations.zip}",
-
- // Modules that enable annotations below
- ":android.net.ipsec.ike{.public.annotations.zip}",
- ":art.module.public.api{.public.annotations.zip}",
- ":framework-appsearch{.public.annotations.zip}",
- ":framework-connectivity{.public.annotations.zip}",
- ":framework-connectivity-tiramisu{.public.annotations.zip}",
- ":framework-graphics{.public.annotations.zip}",
- ":framework-media{.public.annotations.zip}",
- ":framework-mediaprovider{.public.annotations.zip}",
- ":framework-nearby{.public.annotations.zip}",
- ":framework-permission{.public.annotations.zip}",
- ":framework-permission-s{.public.annotations.zip}",
- ":framework-scheduling{.public.annotations.zip}",
- ":framework-sdkextensions{.public.annotations.zip}",
- ":framework-statsd{.public.annotations.zip}",
- ":framework-supplementalprocess{.public.annotations.zip}",
- ":framework-tethering{.public.annotations.zip}",
- ":framework-uwb{.public.annotations.zip}",
- ":framework-wifi{.public.annotations.zip}",
- ],
- out: ["annotations.zip"],
- tools: [
- "merge_annotation_zips",
- "soong_zip",
- ],
- cmd: "$(location merge_annotation_zips) $(genDir)/out $(in) && " +
- "$(location soong_zip) -o $(out) -C $(genDir)/out -D $(genDir)/out",
-}
-
/////////////////////////////////////////////////////////////////////
// API docs are created from the generated stub source files
// using droiddoc
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 1e16f94..77b26f8 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -185,6 +185,7 @@
last_released: {
api_file: ":android-non-updatable.api.module-lib.latest",
removed_api_file: ":android-non-updatable-removed.api.module-lib.latest",
+ baseline_file: ":android-non-updatable-incompatibilities.api.module-lib.latest",
},
api_lint: {
enabled: true,
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index a1a46af..161a317 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -235,7 +235,6 @@
public static final int REASON_LOCKED_BOOT_COMPLETED = 202;
/**
* All Bluetooth broadcasts.
- * @hide
*/
public static final int REASON_BLUETOOTH_BROADCAST = 203;
/**
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 3fb1fad..12a8654 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1556,7 +1556,7 @@
Slog.d(TAG, "UID " + uid + " bias changed from " + prevBias + " to " + newBias);
}
for (int c = 0; c < mControllers.size(); ++c) {
- mControllers.get(c).onUidBiasChangedLocked(uid, newBias);
+ mControllers.get(c).onUidBiasChangedLocked(uid, prevBias, newBias);
}
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
index f733849..63781ae 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
@@ -18,6 +18,14 @@
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import android.annotation.NonNull;
+import android.app.job.JobInfo;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.BatteryManagerInternal;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
@@ -27,6 +35,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;
@@ -42,10 +51,26 @@
private static final boolean DEBUG = JobSchedulerService.DEBUG
|| Log.isLoggable(TAG, Log.DEBUG);
+ @GuardedBy("mLock")
private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
+ /**
+ * List of jobs that started while the UID was in the TOP state.
+ */
+ @GuardedBy("mLock")
+ private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>();
+
+ private final PowerTracker mPowerTracker;
+
+ /**
+ * Helper set to avoid too much GC churn from frequent calls to
+ * {@link #maybeReportNewChargingStateLocked()}.
+ */
+ private final ArraySet<JobStatus> mChangedJobs = new ArraySet<>();
public BatteryController(JobSchedulerService service) {
super(service);
+ mPowerTracker = new PowerTracker();
+ mPowerTracker.startTracking();
}
@Override
@@ -54,8 +79,15 @@
final long nowElapsed = sElapsedRealtimeClock.millis();
mTrackedTasks.add(taskStatus);
taskStatus.setTrackingController(JobStatus.TRACKING_BATTERY);
- taskStatus.setChargingConstraintSatisfied(nowElapsed,
- mService.isBatteryCharging() && mService.isBatteryNotLow());
+ if (taskStatus.hasChargingConstraint()) {
+ if (hasTopExemptionLocked(taskStatus)) {
+ taskStatus.setChargingConstraintSatisfied(nowElapsed,
+ mPowerTracker.isPowerConnected());
+ } else {
+ taskStatus.setChargingConstraintSatisfied(nowElapsed,
+ mService.isBatteryCharging() && mService.isBatteryNotLow());
+ }
+ }
taskStatus.setBatteryNotLowConstraintSatisfied(nowElapsed, mService.isBatteryNotLow());
}
}
@@ -66,9 +98,32 @@
}
@Override
+ @GuardedBy("mLock")
+ public void prepareForExecutionLocked(JobStatus jobStatus) {
+ if (DEBUG) {
+ Slog.d(TAG, "Prepping for " + jobStatus.toShortString());
+ }
+
+ final int uid = jobStatus.getSourceUid();
+ if (mService.getUidBias(uid) == JobInfo.BIAS_TOP_APP) {
+ if (DEBUG) {
+ Slog.d(TAG, jobStatus.toShortString() + " is top started job");
+ }
+ mTopStartedJobs.add(jobStatus);
+ }
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ public void unprepareFromExecutionLocked(JobStatus jobStatus) {
+ mTopStartedJobs.remove(jobStatus);
+ }
+
+ @Override
public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
if (taskStatus.clearTrackingController(JobStatus.TRACKING_BATTERY)) {
mTrackedTasks.remove(taskStatus);
+ mTopStartedJobs.remove(taskStatus);
}
}
@@ -90,33 +145,124 @@
});
}
+ @Override
+ @GuardedBy("mLock")
+ public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
+ if (prevBias == JobInfo.BIAS_TOP_APP || newBias == JobInfo.BIAS_TOP_APP) {
+ maybeReportNewChargingStateLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean hasTopExemptionLocked(@NonNull JobStatus taskStatus) {
+ return mService.getUidBias(taskStatus.getSourceUid()) == JobInfo.BIAS_TOP_APP
+ || mTopStartedJobs.contains(taskStatus);
+ }
+
@GuardedBy("mLock")
private void maybeReportNewChargingStateLocked() {
+ final boolean powerConnected = mPowerTracker.isPowerConnected();
final boolean stablePower = mService.isBatteryCharging() && mService.isBatteryNotLow();
final boolean batteryNotLow = mService.isBatteryNotLow();
if (DEBUG) {
- Slog.d(TAG, "maybeReportNewChargingStateLocked: " + stablePower);
+ Slog.d(TAG, "maybeReportNewChargingStateLocked: "
+ + powerConnected + "/" + stablePower + "/" + batteryNotLow);
}
final long nowElapsed = sElapsedRealtimeClock.millis();
- boolean reportChange = false;
for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
final JobStatus ts = mTrackedTasks.valueAt(i);
- reportChange |= ts.setChargingConstraintSatisfied(nowElapsed, stablePower);
- reportChange |= ts.setBatteryNotLowConstraintSatisfied(nowElapsed, batteryNotLow);
+ if (ts.hasChargingConstraint()) {
+ if (hasTopExemptionLocked(ts)
+ && ts.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT) {
+ // If the job started while the app was on top or the app is currently on top,
+ // let the job run as long as there's power connected, even if the device isn't
+ // officially charging.
+ // For user requested/initiated jobs, users may be confused when the task stops
+ // running even though the device is plugged in.
+ // Low priority jobs don't need to be exempted.
+ if (ts.setChargingConstraintSatisfied(nowElapsed, powerConnected)) {
+ mChangedJobs.add(ts);
+ }
+ } else if (ts.setChargingConstraintSatisfied(nowElapsed, stablePower)) {
+ mChangedJobs.add(ts);
+ }
+ }
+ if (ts.hasBatteryNotLowConstraint()
+ && ts.setBatteryNotLowConstraintSatisfied(nowElapsed, batteryNotLow)) {
+ mChangedJobs.add(ts);
+ }
}
if (stablePower || batteryNotLow) {
// If one of our conditions has been satisfied, always schedule any newly ready jobs.
mStateChangedListener.onRunJobNow(null);
- } else if (reportChange) {
+ } else if (mChangedJobs.size() > 0) {
// Otherwise, just let the job scheduler know the state has changed and take care of it
// as it thinks is best.
- mStateChangedListener.onControllerStateChanged(mTrackedTasks);
+ mStateChangedListener.onControllerStateChanged(mChangedJobs);
+ }
+ mChangedJobs.clear();
+ }
+
+ private final class PowerTracker extends BroadcastReceiver {
+ /**
+ * Track whether there is power connected. It doesn't mean the device is charging.
+ * Use {@link JobSchedulerService#isBatteryCharging()} to determine if the device is
+ * charging.
+ */
+ private boolean mPowerConnected;
+
+ PowerTracker() {
+ }
+
+ void startTracking() {
+ IntentFilter filter = new IntentFilter();
+
+ filter.addAction(Intent.ACTION_POWER_CONNECTED);
+ filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
+ mContext.registerReceiver(this, filter);
+
+ // Initialize tracker state.
+ BatteryManagerInternal batteryManagerInternal =
+ LocalServices.getService(BatteryManagerInternal.class);
+ mPowerConnected = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
+ }
+
+ boolean isPowerConnected() {
+ return mPowerConnected;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized (mLock) {
+ final String action = intent.getAction();
+
+ if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Power connected @ " + sElapsedRealtimeClock.millis());
+ }
+ if (mPowerConnected) {
+ return;
+ }
+ mPowerConnected = true;
+ } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Power disconnected @ " + sElapsedRealtimeClock.millis());
+ }
+ if (!mPowerConnected) {
+ return;
+ }
+ mPowerConnected = false;
+ }
+
+ maybeReportNewChargingStateLocked();
+ }
}
}
@Override
public void dumpControllerStateLocked(IndentingPrintWriter pw,
Predicate<JobStatus> predicate) {
+ pw.println("Power connected: " + mPowerTracker.isPowerConnected());
pw.println("Stable power: " + (mService.isBatteryCharging() && mService.isBatteryNotLow()));
pw.println("Not low: " + mService.isBatteryNotLow());
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 9fb7ab5..c678755 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
@@ -517,7 +517,7 @@
@GuardedBy("mLock")
@Override
- public void onUidBiasChangedLocked(int uid, int newBias) {
+ public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
UidStats uidStats = mUidStats.get(uid);
if (uidStats != null && uidStats.baseBias != newBias) {
uidStats.baseBias = newBias;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
index 9749c80..428f2cb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
@@ -40,7 +40,6 @@
import android.util.Log;
import android.util.Slog;
import android.util.SparseArrayMap;
-import android.util.SparseBooleanArray;
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
@@ -81,9 +80,6 @@
*/
@GuardedBy("mLock")
private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>();
- /** Cached list of UIDs in the TOP state. */
- @GuardedBy("mLock")
- private final SparseBooleanArray mTopUids = new SparseBooleanArray();
private final ThresholdAlarmListener mThresholdAlarmListener;
/**
@@ -186,15 +182,9 @@
@GuardedBy("mLock")
@Override
- public void onUidBiasChangedLocked(int uid, int newBias) {
+ public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
final boolean isNowTop = newBias == JobInfo.BIAS_TOP_APP;
- final boolean wasTop = mTopUids.get(uid);
- if (isNowTop) {
- mTopUids.put(uid, true);
- } else {
- // Delete entries of non-top apps so the set doesn't get too large.
- mTopUids.delete(uid);
- }
+ final boolean wasTop = prevBias == JobInfo.BIAS_TOP_APP;
if (isNowTop != wasTop) {
mHandler.obtainMessage(MSG_PROCESS_TOP_STATE_CHANGE, uid, 0).sendToTarget();
}
@@ -314,7 +304,8 @@
// 3. The app is not open but has an active widget (we can't tell if a widget displays
// status/data, so this assumes the prefetch job is to update the data displayed on
// the widget).
- final boolean appIsOpen = mTopUids.get(jobStatus.getSourceUid());
+ final boolean appIsOpen =
+ mService.getUidBias(jobStatus.getSourceUid()) == JobInfo.BIAS_TOP_APP;
final boolean satisfied;
if (!appIsOpen) {
final int userId = jobStatus.getSourceUserId();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
index 509fb69..2a2d602 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
@@ -144,7 +144,7 @@
* important the UID is.
*/
@GuardedBy("mLock")
- public void onUidBiasChangedLocked(int uid, int newBias) {
+ public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
}
protected boolean wouldBeReadyWithConstraintLocked(JobStatus jobStatus, int constraint) {
diff --git a/api/Android.bp b/api/Android.bp
index 70f995a..362f39f 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -32,6 +32,7 @@
"soong",
"soong-android",
"soong-genrule",
+ "soong-java",
],
srcs: ["api.go"],
pluginFor: ["soong_build"],
@@ -102,47 +103,37 @@
visibility: ["//visibility:public"],
}
-genrule {
- name: "frameworks-base-api-current.txt",
- srcs: [
- ":android.net.ipsec.ike{.public.api.txt}",
- ":art.module.public.api{.public.api.txt}",
- ":conscrypt.module.public.api{.public.api.txt}",
- ":framework-appsearch{.public.api.txt}",
- ":framework-connectivity{.public.api.txt}",
- ":framework-connectivity-tiramisu{.public.api.txt}",
- ":framework-graphics{.public.api.txt}",
- ":framework-media{.public.api.txt}",
- ":framework-mediaprovider{.public.api.txt}",
- ":framework-nearby{.public.api.txt}",
- ":framework-permission{.public.api.txt}",
- ":framework-permission-s{.public.api.txt}",
- ":framework-scheduling{.public.api.txt}",
- ":framework-sdkextensions{.public.api.txt}",
- ":framework-statsd{.public.api.txt}",
- ":framework-supplementalprocess{.public.api.txt}",
- ":framework-tethering{.public.api.txt}",
- ":framework-uwb{.public.api.txt}",
- ":framework-wifi{.public.api.txt}",
- ":i18n.module.public.api{.public.api.txt}",
- ":non-updatable-current.txt",
+combined_apis {
+ name: "frameworks-base-api",
+ bootclasspath: [
+ "android.net.ipsec.ike",
+ "art.module.public.api",
+ "conscrypt.module.public.api",
+ "framework-appsearch",
+ "framework-connectivity",
+ "framework-connectivity-tiramisu",
+ "framework-graphics",
+ "framework-media",
+ "framework-mediaprovider",
+ "framework-nearby",
+ "framework-permission",
+ "framework-permission-s",
+ "framework-scheduling",
+ "framework-sdkextensions",
+ "framework-statsd",
+ "framework-supplementalprocess",
+ "framework-tethering",
+ "framework-uwb",
+ "framework-wifi",
+ "i18n.module.public.api",
],
- out: ["current.txt"],
- tools: ["metalava"],
- cmd: metalava_cmd + "$(in) --api $(out)",
- dists: [
- {
- targets: ["droidcore"],
- dir: "api",
- dest: "current.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/public/api",
- dest: "android.txt",
- },
+ conditional_bootclasspath: [
+ "framework-supplementalapi",
],
- visibility: ["//visibility:public"],
+ system_server_classpath: [
+ "service-media-s",
+ "service-permission",
+ ],
}
genrule {
@@ -162,120 +153,6 @@
}
genrule {
- name: "frameworks-base-api-current.srcjar",
- srcs: [
- ":android.net.ipsec.ike{.public.stubs.source}",
- ":api-stubs-docs-non-updatable",
- ":art.module.public.api{.public.stubs.source}",
- ":conscrypt.module.public.api{.public.stubs.source}",
- ":framework-appsearch{.public.stubs.source}",
- ":framework-connectivity{.public.stubs.source}",
- ":framework-connectivity-tiramisu{.public.stubs.source}",
- ":framework-graphics{.public.stubs.source}",
- ":framework-media{.public.stubs.source}",
- ":framework-mediaprovider{.public.stubs.source}",
- ":framework-nearby{.public.stubs.source}",
- ":framework-permission{.public.stubs.source}",
- ":framework-permission-s{.public.stubs.source}",
- ":framework-scheduling{.public.stubs.source}",
- ":framework-sdkextensions{.public.stubs.source}",
- ":framework-statsd{.public.stubs.source}",
- ":framework-supplementalprocess{.public.stubs.source}",
- ":framework-tethering{.public.stubs.source}",
- ":framework-uwb{.public.stubs.source}",
- ":framework-wifi{.public.stubs.source}",
- ":i18n.module.public.api{.public.stubs.source}",
- ],
- out: ["current.srcjar"],
- tools: ["merge_zips"],
- cmd: "$(location merge_zips) $(out) $(in)",
- visibility: ["//visibility:private"], // Used by make module in //development, mind.
-}
-
-genrule {
- name: "frameworks-base-api-removed.txt",
- srcs: [
- ":android.net.ipsec.ike{.public.removed-api.txt}",
- ":art.module.public.api{.public.removed-api.txt}",
- ":conscrypt.module.public.api{.public.removed-api.txt}",
- ":framework-appsearch{.public.removed-api.txt}",
- ":framework-connectivity{.public.removed-api.txt}",
- ":framework-connectivity-tiramisu{.public.removed-api.txt}",
- ":framework-graphics{.public.removed-api.txt}",
- ":framework-media{.public.removed-api.txt}",
- ":framework-mediaprovider{.public.removed-api.txt}",
- ":framework-nearby{.public.removed-api.txt}",
- ":framework-permission{.public.removed-api.txt}",
- ":framework-permission-s{.public.removed-api.txt}",
- ":framework-scheduling{.public.removed-api.txt}",
- ":framework-sdkextensions{.public.removed-api.txt}",
- ":framework-statsd{.public.removed-api.txt}",
- ":framework-supplementalprocess{.public.removed-api.txt}",
- ":framework-tethering{.public.removed-api.txt}",
- ":framework-uwb{.public.removed-api.txt}",
- ":framework-wifi{.public.removed-api.txt}",
- ":i18n.module.public.api{.public.removed-api.txt}",
- ":non-updatable-removed.txt",
- ],
- out: ["removed.txt"],
- tools: ["metalava"],
- cmd: metalava_cmd + "$(in) --api $(out)",
- dists: [
- {
- targets: ["droidcore"],
- dir: "api",
- dest: "removed.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/public/api",
- dest: "removed.txt",
- },
- ],
-}
-
-genrule {
- name: "frameworks-base-api-system-current.txt",
- srcs: [
- ":art.module.public.api{.system.api.txt}",
- ":android.net.ipsec.ike{.system.api.txt}",
- ":framework-appsearch{.system.api.txt}",
- ":framework-connectivity{.system.api.txt}",
- ":framework-connectivity-tiramisu{.system.api.txt}",
- ":framework-graphics{.system.api.txt}",
- ":framework-media{.system.api.txt}",
- ":framework-mediaprovider{.system.api.txt}",
- ":framework-nearby{.system.api.txt}",
- ":framework-permission{.system.api.txt}",
- ":framework-permission-s{.system.api.txt}",
- ":framework-scheduling{.system.api.txt}",
- ":framework-sdkextensions{.system.api.txt}",
- ":framework-statsd{.system.api.txt}",
- ":framework-supplementalprocess{.system.api.txt}",
- ":framework-tethering{.system.api.txt}",
- ":framework-uwb{.system.api.txt}",
- ":framework-wifi{.system.api.txt}",
- ":non-updatable-system-current.txt",
- ],
- out: ["system-current.txt"],
- tools: ["metalava"],
- cmd: metalava_cmd + "$(in) --api $(out)",
- dists: [
- {
- targets: ["droidcore"],
- dir: "api",
- dest: "system-current.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/system/api",
- dest: "android.txt",
- },
- ],
- visibility: ["//visibility:public"],
-}
-
-genrule {
name: "frameworks-base-api-system-current-compat",
srcs: [
":android.api.system.latest",
@@ -294,87 +171,6 @@
}
genrule {
- name: "frameworks-base-api-system-removed.txt",
- srcs: [
- ":art.module.public.api{.system.removed-api.txt}",
- ":android.net.ipsec.ike{.system.removed-api.txt}",
- ":framework-appsearch{.system.removed-api.txt}",
- ":framework-connectivity{.system.removed-api.txt}",
- ":framework-connectivity-tiramisu{.system.removed-api.txt}",
- ":framework-graphics{.system.removed-api.txt}",
- ":framework-media{.system.removed-api.txt}",
- ":framework-mediaprovider{.system.removed-api.txt}",
- ":framework-nearby{.system.removed-api.txt}",
- ":framework-permission{.system.removed-api.txt}",
- ":framework-permission-s{.system.removed-api.txt}",
- ":framework-scheduling{.system.removed-api.txt}",
- ":framework-sdkextensions{.system.removed-api.txt}",
- ":framework-statsd{.system.removed-api.txt}",
- ":framework-supplementalprocess{.system.removed-api.txt}",
- ":framework-tethering{.system.removed-api.txt}",
- ":framework-uwb{.system.removed-api.txt}",
- ":framework-wifi{.system.removed-api.txt}",
- ":non-updatable-system-removed.txt",
- ],
- out: ["system-removed.txt"],
- tools: ["metalava"],
- cmd: metalava_cmd + "$(in) --api $(out)",
- dists: [
- {
- targets: ["droidcore"],
- dir: "api",
- dest: "system-removed.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/system/api",
- dest: "removed.txt",
- },
- ],
- visibility: ["//visibility:public"],
-}
-
-genrule {
- name: "frameworks-base-api-module-lib-current.txt",
- srcs: [
- ":art.module.public.api{.module-lib.api.txt}",
- ":android.net.ipsec.ike{.module-lib.api.txt}",
- ":framework-appsearch{.module-lib.api.txt}",
- ":framework-connectivity{.module-lib.api.txt}",
- ":framework-connectivity-tiramisu{.module-lib.api.txt}",
- ":framework-graphics{.module-lib.api.txt}",
- ":framework-media{.module-lib.api.txt}",
- ":framework-mediaprovider{.module-lib.api.txt}",
- ":framework-nearby{.module-lib.api.txt}",
- ":framework-permission{.module-lib.api.txt}",
- ":framework-permission-s{.module-lib.api.txt}",
- ":framework-scheduling{.module-lib.api.txt}",
- ":framework-sdkextensions{.module-lib.api.txt}",
- ":framework-statsd{.module-lib.api.txt}",
- ":framework-supplementalprocess{.module-lib.api.txt}",
- ":framework-tethering{.module-lib.api.txt}",
- ":framework-uwb{.module-lib.api.txt}",
- ":framework-wifi{.module-lib.api.txt}",
- ":non-updatable-module-lib-current.txt",
- ],
- out: ["module-lib-current.txt"],
- tools: ["metalava"],
- cmd: metalava_cmd + "$(in) --api $(out)",
- dists: [
- {
- targets: ["droidcore"],
- dir: "api",
- dest: "module-lib-current.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/module-lib/api",
- dest: "android.txt",
- },
- ],
-}
-
-genrule {
name: "frameworks-base-api-module-lib-current-compat",
srcs: [
":android.api.module-lib.latest",
@@ -396,46 +192,6 @@
}
genrule {
- name: "frameworks-base-api-module-lib-removed.txt",
- srcs: [
- ":art.module.public.api{.module-lib.removed-api.txt}",
- ":android.net.ipsec.ike{.module-lib.removed-api.txt}",
- ":framework-appsearch{.module-lib.removed-api.txt}",
- ":framework-connectivity{.module-lib.removed-api.txt}",
- ":framework-connectivity-tiramisu{.module-lib.removed-api.txt}",
- ":framework-graphics{.module-lib.removed-api.txt}",
- ":framework-media{.module-lib.removed-api.txt}",
- ":framework-mediaprovider{.module-lib.removed-api.txt}",
- ":framework-nearby{.module-lib.removed-api.txt}",
- ":framework-permission{.module-lib.removed-api.txt}",
- ":framework-permission-s{.module-lib.removed-api.txt}",
- ":framework-scheduling{.module-lib.removed-api.txt}",
- ":framework-sdkextensions{.module-lib.removed-api.txt}",
- ":framework-statsd{.module-lib.removed-api.txt}",
- ":framework-supplementalprocess{.module-lib.removed-api.txt}",
- ":framework-tethering{.module-lib.removed-api.txt}",
- ":framework-uwb{.module-lib.removed-api.txt}",
- ":framework-wifi{.module-lib.removed-api.txt}",
- ":non-updatable-module-lib-removed.txt",
- ],
- out: ["module-lib-removed.txt"],
- tools: ["metalava"],
- cmd: metalava_cmd + "$(in) --api $(out)",
- dists: [
- {
- targets: ["droidcore"],
- dir: "api",
- dest: "module-lib-removed.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/module-lib/api",
- dest: "removed.txt",
- },
- ],
-}
-
-genrule {
name: "combined-removed-dex",
visibility: [
"//frameworks/base/boot",
@@ -451,90 +207,3 @@
out: ["combined-removed-dex.txt"],
cmd: "$(location gen_combined_removed_dex.sh) $(location metalava) $(genDir) $(in) > $(out)",
}
-
-genrule {
- name: "frameworks-base-api-system-server-current.txt",
- srcs: [
- ":service-media-s{.system-server.api.txt}",
- ":service-permission{.system-server.api.txt}",
- ":non-updatable-system-server-current.txt",
- ],
- out: ["system-server-current.txt"],
- tools: ["metalava"],
- cmd: metalava_cmd + "$(in) --api $(out)",
- dists: [
- {
- targets: ["droidcore"],
- dir: "api",
- dest: "system-server-current.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/system-server/api",
- dest: "android.txt",
- },
- ],
-}
-
-genrule {
- name: "frameworks-base-api-system-server-removed.txt",
- srcs: [
- ":service-media-s{.system-server.removed-api.txt}",
- ":service-permission{.system-server.removed-api.txt}",
- ":non-updatable-system-server-removed.txt",
- ],
- out: ["system-server-removed.txt"],
- tools: ["metalava"],
- cmd: metalava_cmd + "$(in) --api $(out)",
- dists: [
- {
- targets: ["droidcore"],
- dir: "api",
- dest: "system-server-removed.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/system-server/api",
- dest: "removed.txt",
- },
- ],
-}
-
-// This rule will filter classes present in the jar files of mainline modules
-// from the lint database in api-versions.xml.
-// This is done to reduce the number of false positive NewApi findings in
-// java libraries that compile against the module SDK
-genrule {
- name: "api-versions-xml-public-filtered",
- srcs: [
- // Note: order matters: first parameter is the full api-versions.xml
- // after that the stubs files in any order
- // stubs files are all modules that export API surfaces EXCEPT ART
- ":framework-doc-stubs{.api_versions.xml}",
- ":android.net.ipsec.ike.stubs{.jar}",
- ":conscrypt.module.public.api.stubs{.jar}",
- ":framework-appsearch.stubs{.jar}",
- ":framework-connectivity.stubs{.jar}",
- ":framework-connectivity-tiramisu.stubs{.jar}",
- ":framework-graphics.stubs{.jar}",
- ":framework-media.stubs{.jar}",
- ":framework-mediaprovider.stubs{.jar}",
- ":framework-nearby.stubs{.jar}",
- ":framework-permission.stubs{.jar}",
- ":framework-permission-s.stubs{.jar}",
- ":framework-scheduling.stubs{.jar}",
- ":framework-sdkextensions.stubs{.jar}",
- ":framework-statsd.stubs{.jar}",
- ":framework-supplementalprocess.stubs{.jar}",
- ":framework-tethering.stubs{.jar}",
- ":framework-uwb.stubs{.jar}",
- ":framework-wifi.stubs{.jar}",
- ":i18n.module.public.api.stubs{.jar}",
- ],
- out: ["api-versions-public-filtered.xml"],
- tools: ["api_versions_trimmer"],
- cmd: "$(location api_versions_trimmer) $(out) $(in)",
- dist: {
- targets: ["sdk"],
- },
-}
diff --git a/api/api.go b/api/api.go
index 976b140..3b0e300 100644
--- a/api/api.go
+++ b/api/api.go
@@ -15,12 +15,19 @@
package api
import (
+ "sort"
+
"github.com/google/blueprint/proptools"
"android/soong/android"
"android/soong/genrule"
+ "android/soong/java"
)
+const art = "art.module.public.api"
+const conscrypt = "conscrypt.module.public.api"
+const i18n = "i18n.module.public.api"
+
// The intention behind this soong plugin is to generate a number of "merged"
// API-related modules that would otherwise require a large amount of very
// similar Android.bp boilerplate to define. For example, the merged current.txt
@@ -30,22 +37,12 @@
// The properties of the combined_apis module type.
type CombinedApisProperties struct {
- // Module libraries that have public APIs
- Public []string
- // Module libraries that have system APIs
- System []string
- // Module libraries that have module_library APIs
- Module_lib []string
- // Module libraries that have system_server APIs
- System_server []string
- // ART module library. The only API library not removed from the filtered api database, because
- // 1) ART apis are available by default to all modules, while other module-to-module deps are
- // explicit and probably receive more scrutiny anyway
- // 2) The number of ART/libcore APIs is large, so not linting them would create a large gap
- // 3) It's a compromise. Ideally we wouldn't be filtering out any module APIs, and have
- // per-module lint databases that excludes just that module's APIs. Alas, that's more
- // difficult to achieve.
- Art_module string
+ // Module libraries in the bootclasspath
+ Bootclasspath []string
+ // Module libraries on the bootclasspath if include_nonpublic_framework_api is true.
+ Conditional_bootclasspath []string
+ // Module libraries in system server
+ System_server_classpath []string
}
type CombinedApis struct {
@@ -77,6 +74,13 @@
Visibility []string
}
+type libraryProps struct {
+ Name *string
+ Sdk_version *string
+ Static_libs []string
+ Visibility []string
+}
+
// Struct to pass parameters for the various merged [current|removed].txt file modules we create.
type MergedTxtDefinition struct {
// "current.txt" or "removed.txt"
@@ -105,7 +109,7 @@
props := genruleProps{}
props.Name = proptools.StringPtr(ctx.ModuleName() + "-" + filename)
props.Tools = []string{"metalava"}
- props.Out = []string{txt.TxtFilename}
+ props.Out = []string{filename}
props.Cmd = proptools.StringPtr(metalavaCmd + "$(in) --api $(out)")
props.Srcs = createSrcs(txt.BaseTxt, txt.Modules, txt.ModuleTag)
props.Dists = []android.Dist{
@@ -135,7 +139,31 @@
ctx.CreateModule(genrule.GenRuleFactory, &props)
}
+// This produces the same annotations.zip as framework-doc-stubs, but by using
+// outputs from individual modules instead of all the source code.
+func createMergedAnnotations(ctx android.LoadHookContext, modules []string) {
+ // Conscrypt and i18n currently do not enable annotations
+ modules = removeAll(modules, []string{conscrypt, i18n})
+ props := genruleProps{}
+ props.Name = proptools.StringPtr("sdk-annotations.zip")
+ props.Tools = []string{"merge_annotation_zips", "soong_zip"}
+ props.Out = []string{"annotations.zip"}
+ props.Cmd = proptools.StringPtr("$(location merge_annotation_zips) $(genDir)/out $(in) && " +
+ "$(location soong_zip) -o $(out) -C $(genDir)/out -D $(genDir)/out")
+ props.Srcs = createSrcs(":android-non-updatable-doc-stubs{.annotations.zip}", modules, "{.public.annotations.zip}")
+ ctx.CreateModule(genrule.GenRuleFactory, &props)
+}
+
func createFilteredApiVersions(ctx android.LoadHookContext, modules []string) {
+ // For the filtered api versions, we prune all APIs except art module's APIs. because
+ // 1) ART apis are available by default to all modules, while other module-to-module deps are
+ // explicit and probably receive more scrutiny anyway
+ // 2) The number of ART/libcore APIs is large, so not linting them would create a large gap
+ // 3) It's a compromise. Ideally we wouldn't be filtering out any module APIs, and have
+ // per-module lint databases that excludes just that module's APIs. Alas, that's more
+ // difficult to achieve.
+ modules = remove(modules, art)
+
props := genruleProps{}
props.Name = proptools.StringPtr("api-versions-xml-public-filtered")
props.Tools = []string{"api_versions_trimmer"}
@@ -149,6 +177,25 @@
ctx.CreateModule(genrule.GenRuleFactory, &props)
}
+func createMergedModuleLibStubs(ctx android.LoadHookContext, modules []string) {
+ // The user of this module compiles against the "core" SDK, so remove core libraries to avoid dupes.
+ modules = removeAll(modules, []string{art, conscrypt, i18n})
+ props := libraryProps{}
+ props.Name = proptools.StringPtr("framework-updatable-stubs-module_libs_api")
+ props.Static_libs = appendStr(modules, ".stubs.module_lib")
+ props.Sdk_version = proptools.StringPtr("module_current")
+ props.Visibility = []string{"//frameworks/base"}
+ ctx.CreateModule(java.LibraryFactory, &props)
+}
+
+func appendStr(modules []string, s string) []string {
+ a := make([]string, 0, len(modules))
+ for _, module := range modules {
+ a = append(a, module+s)
+ }
+ return a
+}
+
func createSrcs(base string, modules []string, tag string) []string {
a := make([]string, 0, len(modules)+1)
a = append(a, base)
@@ -158,6 +205,13 @@
return a
}
+func removeAll(s []string, vs []string) []string {
+ for _, v := range vs {
+ s = remove(s, v)
+ }
+ return s
+}
+
func remove(s []string, v string) []string {
s2 := make([]string, 0, len(s))
for _, sv := range s {
@@ -168,35 +222,38 @@
return s2
}
-func createMergedTxts(ctx android.LoadHookContext, props CombinedApisProperties) {
+func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) {
var textFiles []MergedTxtDefinition
+ // Two module libraries currently do not support @SystemApi so only have the public scope.
+ bcpWithSystemApi := removeAll(bootclasspath, []string{conscrypt, i18n})
+
tagSuffix := []string{".api.txt}", ".removed-api.txt}"}
for i, f := range []string{"current.txt", "removed.txt"} {
textFiles = append(textFiles, MergedTxtDefinition{
TxtFilename: f,
BaseTxt: ":non-updatable-" + f,
- Modules: props.Public,
+ Modules: bootclasspath,
ModuleTag: "{.public" + tagSuffix[i],
Scope: "public",
})
textFiles = append(textFiles, MergedTxtDefinition{
TxtFilename: f,
BaseTxt: ":non-updatable-system-" + f,
- Modules: props.System,
+ Modules: bcpWithSystemApi,
ModuleTag: "{.system" + tagSuffix[i],
Scope: "system",
})
textFiles = append(textFiles, MergedTxtDefinition{
TxtFilename: f,
BaseTxt: ":non-updatable-module-lib-" + f,
- Modules: props.Module_lib,
+ Modules: bcpWithSystemApi,
ModuleTag: "{.module-lib" + tagSuffix[i],
Scope: "module-lib",
})
textFiles = append(textFiles, MergedTxtDefinition{
TxtFilename: f,
BaseTxt: ":non-updatable-system-server-" + f,
- Modules: props.System_server,
+ Modules: system_server_classpath,
ModuleTag: "{.system-server" + tagSuffix[i],
Scope: "system-server",
})
@@ -207,12 +264,20 @@
}
func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) {
- createMergedTxts(ctx, a.properties)
+ bootclasspath := a.properties.Bootclasspath
+ if ctx.Config().VendorConfig("ANDROID").Bool("include_nonpublic_framework_api") {
+ bootclasspath = append(bootclasspath, a.properties.Conditional_bootclasspath...)
+ sort.Strings(bootclasspath)
+ }
+ createMergedTxts(ctx, bootclasspath, a.properties.System_server_classpath)
- createMergedStubsSrcjar(ctx, a.properties.Public)
+ createMergedStubsSrcjar(ctx, bootclasspath)
- // For the filtered api versions, we prune all APIs except art module's APIs.
- createFilteredApiVersions(ctx, remove(a.properties.Public, a.properties.Art_module))
+ createMergedModuleLibStubs(ctx, bootclasspath)
+
+ createMergedAnnotations(ctx, bootclasspath)
+
+ createFilteredApiVersions(ctx, bootclasspath)
}
func combinedApisModuleFactory() android.Module {
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index 13bf197..fbb99d2 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -43,7 +43,7 @@
#define DEFAULT_DELAY_NS (1000000000LL)
-#define DEFAULT_BYTES_SIZE_LIMIT (96 * 1024 * 1024) // 96MB
+#define DEFAULT_BYTES_SIZE_LIMIT (400 * 1024 * 1024) // 400MB
#define DEFAULT_REFACTORY_PERIOD_MS (24 * 60 * 60 * 1000) // 1 Day
// Skip these sections (for dumpstate only)
diff --git a/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp
index 23d80d7..dd33fdf 100644
--- a/cmds/incidentd/src/WorkDirectory.cpp
+++ b/cmds/incidentd/src/WorkDirectory.cpp
@@ -533,7 +533,7 @@
WorkDirectory::WorkDirectory()
:mDirectory("/data/misc/incidents"),
mMaxFileCount(100),
- mMaxDiskUsageBytes(100 * 1024 * 1024) { // Incident reports can take up to 100MB on disk.
+ mMaxDiskUsageBytes(400 * 1024 * 1024) { // Incident reports can take up to 400MB on disk.
// TODO: Should be a flag.
create_directory(mDirectory.c_str());
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 8557e90..385899a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -17,6 +17,7 @@
field public static final String ACCESS_MEDIA_LOCATION = "android.permission.ACCESS_MEDIA_LOCATION";
field public static final String ACCESS_NETWORK_STATE = "android.permission.ACCESS_NETWORK_STATE";
field public static final String ACCESS_NOTIFICATION_POLICY = "android.permission.ACCESS_NOTIFICATION_POLICY";
+ field public static final String ACCESS_SUPPLEMENTAL_APIS = "android.permission.ACCESS_SUPPLEMENTAL_APIS";
field public static final String ACCESS_WIFI_STATE = "android.permission.ACCESS_WIFI_STATE";
field public static final String ACCOUNT_MANAGER = "android.permission.ACCOUNT_MANAGER";
field public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION";
@@ -18330,10 +18331,12 @@
ctor public BiometricPrompt.CryptoObject(@NonNull java.security.Signature);
ctor public BiometricPrompt.CryptoObject(@NonNull javax.crypto.Cipher);
ctor public BiometricPrompt.CryptoObject(@NonNull javax.crypto.Mac);
- ctor public BiometricPrompt.CryptoObject(@NonNull android.security.identity.IdentityCredential);
+ ctor @Deprecated public BiometricPrompt.CryptoObject(@NonNull android.security.identity.IdentityCredential);
+ ctor public BiometricPrompt.CryptoObject(@NonNull android.security.identity.PresentationSession);
method public javax.crypto.Cipher getCipher();
- method @Nullable public android.security.identity.IdentityCredential getIdentityCredential();
+ method @Deprecated @Nullable public android.security.identity.IdentityCredential getIdentityCredential();
method public javax.crypto.Mac getMac();
+ method @Nullable public android.security.identity.PresentationSession getPresentationSession();
method public java.security.Signature getSignature();
}
@@ -19751,6 +19754,7 @@
@UiContext public class InputMethodService extends android.inputmethodservice.AbstractInputMethodService {
ctor public InputMethodService();
method @Deprecated public boolean enableHardwareAcceleration();
+ method public final void finishStylusHandwriting();
method public int getBackDisposition();
method public int getCandidatesHiddenVisibility();
method public android.view.inputmethod.InputBinding getCurrentInputBinding();
@@ -19760,6 +19764,7 @@
method @Deprecated public int getInputMethodWindowRecommendedHeight();
method public android.view.LayoutInflater getLayoutInflater();
method public int getMaxWidth();
+ method @Nullable public final android.view.Window getStylusHandwritingWindow();
method public CharSequence getTextForImeAction(int);
method public android.app.Dialog getWindow();
method public void hideStatusIcon();
@@ -19790,6 +19795,7 @@
method public void onFinishCandidatesView(boolean);
method public void onFinishInput();
method public void onFinishInputView(boolean);
+ method public void onFinishStylusHandwriting();
method public void onInitializeInterface();
method public boolean onInlineSuggestionsResponse(@NonNull android.view.inputmethod.InlineSuggestionsResponse);
method public boolean onKeyDown(int, android.view.KeyEvent);
@@ -19800,6 +19806,7 @@
method public void onStartCandidatesView(android.view.inputmethod.EditorInfo, boolean);
method public void onStartInput(android.view.inputmethod.EditorInfo, boolean);
method public void onStartInputView(android.view.inputmethod.EditorInfo, boolean);
+ method public boolean onStartStylusHandwriting();
method public void onUnbindInput();
method @Deprecated public void onUpdateCursor(android.graphics.Rect);
method public void onUpdateCursorAnchorInfo(android.view.inputmethod.CursorAnchorInfo);
@@ -20145,6 +20152,24 @@
field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssAntennaInfo.SphericalCorrections> CREATOR;
}
+ public final class GnssAutomaticGainControl implements android.os.Parcelable {
+ method public int describeContents();
+ method @IntRange(from=0) public long getCarrierFrequencyHz();
+ method public int getConstellationType();
+ method @FloatRange(from=0xffffd8f0, to=10000) public double getLevelDb();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssAutomaticGainControl> CREATOR;
+ }
+
+ public static final class GnssAutomaticGainControl.Builder {
+ ctor public GnssAutomaticGainControl.Builder();
+ ctor public GnssAutomaticGainControl.Builder(@NonNull android.location.GnssAutomaticGainControl);
+ method @NonNull public android.location.GnssAutomaticGainControl build();
+ method @NonNull public android.location.GnssAutomaticGainControl.Builder setCarrierFrequencyHz(@IntRange(from=0) long);
+ method @NonNull public android.location.GnssAutomaticGainControl.Builder setConstellationType(int);
+ method @NonNull public android.location.GnssAutomaticGainControl.Builder setLevelDb(@FloatRange(from=0xffffd8f0, to=10000) double);
+ }
+
public final class GnssCapabilities implements android.os.Parcelable {
method public int describeContents();
method public boolean hasAntennaInfo();
@@ -20201,7 +20226,7 @@
method public double getAccumulatedDeltaRangeMeters();
method public int getAccumulatedDeltaRangeState();
method public double getAccumulatedDeltaRangeUncertaintyMeters();
- method public double getAutomaticGainControlLevelDb();
+ method @Deprecated public double getAutomaticGainControlLevelDb();
method @FloatRange(from=0, to=63) public double getBasebandCn0DbHz();
method @Deprecated public long getCarrierCycles();
method public float getCarrierFrequencyHz();
@@ -20223,7 +20248,7 @@
method public int getState();
method public int getSvid();
method public double getTimeOffsetNanos();
- method public boolean hasAutomaticGainControlLevelDb();
+ method @Deprecated public boolean hasAutomaticGainControlLevelDb();
method public boolean hasBasebandCn0DbHz();
method @Deprecated public boolean hasCarrierCycles();
method public boolean hasCarrierFrequencyHz();
@@ -20285,11 +20310,21 @@
public final class GnssMeasurementsEvent implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.location.GnssClock getClock();
+ method @NonNull public java.util.Collection<android.location.GnssAutomaticGainControl> getGnssAutomaticGainControls();
method @NonNull public java.util.Collection<android.location.GnssMeasurement> getMeasurements();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssMeasurementsEvent> CREATOR;
}
+ public static final class GnssMeasurementsEvent.Builder {
+ ctor public GnssMeasurementsEvent.Builder();
+ ctor public GnssMeasurementsEvent.Builder(@NonNull android.location.GnssMeasurementsEvent);
+ method @NonNull public android.location.GnssMeasurementsEvent build();
+ method @NonNull public android.location.GnssMeasurementsEvent.Builder setClock(@NonNull android.location.GnssClock);
+ method @NonNull public android.location.GnssMeasurementsEvent.Builder setGnssAutomaticGainControls(@NonNull java.util.Collection<android.location.GnssAutomaticGainControl>);
+ method @NonNull public android.location.GnssMeasurementsEvent.Builder setMeasurements(@NonNull java.util.Collection<android.location.GnssMeasurement>);
+ }
+
public abstract static class GnssMeasurementsEvent.Callback {
ctor public GnssMeasurementsEvent.Callback();
method public void onGnssMeasurementsReceived(android.location.GnssMeasurementsEvent);
@@ -20961,6 +20996,7 @@
method @NonNull public java.util.List<android.media.AudioPlaybackConfiguration> getActivePlaybackConfigurations();
method @NonNull public java.util.List<android.media.AudioRecordingConfiguration> getActiveRecordingConfigurations();
method public int getAllowedCapturePolicy();
+ method @NonNull public java.util.List<android.media.AudioDeviceInfo> getAudioDevicesForAttributes(@NonNull android.media.AudioAttributes);
method public int getAudioHwSyncForSession(int);
method @NonNull public java.util.List<android.media.AudioDeviceInfo> getAvailableCommunicationDevices();
method @Nullable public android.media.AudioDeviceInfo getCommunicationDevice();
@@ -27720,6 +27756,8 @@
ctor public VcnCellUnderlyingNetworkTemplate.Builder();
method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate build();
method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMetered(int);
+ method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMinDownstreamBandwidthKbps(int, int);
+ method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMinUpstreamBandwidthKbps(int, int);
method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOperatorPlmnIds(@NonNull java.util.Set<java.lang.String>);
method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOpportunistic(int);
method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setRoaming(int);
@@ -27780,6 +27818,10 @@
public abstract class VcnUnderlyingNetworkTemplate {
method public int getMetered();
+ method public int getMinEntryDownstreamBandwidthKbps();
+ method public int getMinEntryUpstreamBandwidthKbps();
+ method public int getMinExitDownstreamBandwidthKbps();
+ method public int getMinExitUpstreamBandwidthKbps();
field public static final int MATCH_ANY = 0; // 0x0
field public static final int MATCH_FORBIDDEN = 2; // 0x2
field public static final int MATCH_REQUIRED = 1; // 0x1
@@ -27793,6 +27835,8 @@
ctor public VcnWifiUnderlyingNetworkTemplate.Builder();
method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate build();
method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMetered(int);
+ method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMinDownstreamBandwidthKbps(int, int);
+ method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMinUpstreamBandwidthKbps(int, int);
method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setSsids(@NonNull java.util.Set<java.lang.String>);
}
@@ -38025,6 +38069,51 @@
ctor public CipherSuiteNotSupportedException(@NonNull String, @NonNull Throwable);
}
+ public class CredentialDataRequest {
+ method @NonNull public java.util.Map<java.lang.String,java.util.Collection<java.lang.String>> getDeviceSignedEntriesToRequest();
+ method @NonNull public java.util.Map<java.lang.String,java.util.Collection<java.lang.String>> getIssuerSignedEntriesToRequest();
+ method @Nullable public byte[] getReaderSignature();
+ method @Nullable public byte[] getRequestMessage();
+ method public boolean isAllowUsingExhaustedKeys();
+ method public boolean isAllowUsingExpiredKeys();
+ method public boolean isIncrementUseCount();
+ }
+
+ public static final class CredentialDataRequest.Builder {
+ ctor public CredentialDataRequest.Builder();
+ method @NonNull public android.security.identity.CredentialDataRequest build();
+ method @NonNull public android.security.identity.CredentialDataRequest.Builder setAllowUsingExhaustedKeys(boolean);
+ method @NonNull public android.security.identity.CredentialDataRequest.Builder setAllowUsingExpiredKeys(boolean);
+ method @NonNull public android.security.identity.CredentialDataRequest.Builder setDeviceSignedEntriesToRequest(@NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>);
+ method @NonNull public android.security.identity.CredentialDataRequest.Builder setIncrementUseCount(boolean);
+ method @NonNull public android.security.identity.CredentialDataRequest.Builder setIssuerSignedEntriesToRequest(@NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>);
+ method @NonNull public android.security.identity.CredentialDataRequest.Builder setReaderSignature(@NonNull byte[]);
+ method @NonNull public android.security.identity.CredentialDataRequest.Builder setRequestMessage(@NonNull byte[]);
+ }
+
+ public abstract class CredentialDataResult {
+ method @Nullable public abstract byte[] getDeviceMac();
+ method @NonNull public abstract byte[] getDeviceNameSpaces();
+ method @NonNull public abstract android.security.identity.CredentialDataResult.Entries getDeviceSignedEntries();
+ method @NonNull public abstract android.security.identity.CredentialDataResult.Entries getIssuerSignedEntries();
+ method @NonNull public abstract byte[] getStaticAuthenticationData();
+ }
+
+ public static interface CredentialDataResult.Entries {
+ method @Nullable public byte[] getEntry(@NonNull String, @NonNull String);
+ method @NonNull public java.util.Collection<java.lang.String> getEntryNames(@NonNull String);
+ method @NonNull public java.util.Collection<java.lang.String> getNamespaces();
+ method @NonNull public java.util.Collection<java.lang.String> getRetrievedEntryNames(@NonNull String);
+ method public int getStatus(@NonNull String, @NonNull String);
+ field public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3; // 0x3
+ field public static final int STATUS_NOT_REQUESTED = 2; // 0x2
+ field public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6; // 0x6
+ field public static final int STATUS_NO_SUCH_ENTRY = 1; // 0x1
+ field public static final int STATUS_OK = 0; // 0x0
+ field public static final int STATUS_READER_AUTHENTICATION_FAILED = 5; // 0x5
+ field public static final int STATUS_USER_AUTHENTICATION_FAILED = 4; // 0x4
+ }
+
public class DocTypeNotSupportedException extends android.security.identity.IdentityCredentialException {
ctor public DocTypeNotSupportedException(@NonNull String);
ctor public DocTypeNotSupportedException(@NonNull String, @NonNull Throwable);
@@ -38036,19 +38125,19 @@
}
public abstract class IdentityCredential {
- method @NonNull public abstract java.security.KeyPair createEphemeralKeyPair();
- method @NonNull public abstract byte[] decryptMessageFromReader(@NonNull byte[]) throws android.security.identity.MessageDecryptionException;
+ method @Deprecated @NonNull public abstract java.security.KeyPair createEphemeralKeyPair();
+ method @Deprecated @NonNull public abstract byte[] decryptMessageFromReader(@NonNull byte[]) throws android.security.identity.MessageDecryptionException;
method @NonNull public byte[] delete(@NonNull byte[]);
- method @NonNull public abstract byte[] encryptMessageToReader(@NonNull byte[]);
+ method @Deprecated @NonNull public abstract byte[] encryptMessageToReader(@NonNull byte[]);
method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getAuthKeysNeedingCertification();
method @NonNull public abstract int[] getAuthenticationDataUsageCount();
method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getCredentialKeyCertificateChain();
- method @NonNull public abstract android.security.identity.ResultData getEntries(@Nullable byte[], @NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>, @Nullable byte[], @Nullable byte[]) throws android.security.identity.EphemeralPublicKeyNotFoundException, android.security.identity.InvalidReaderSignatureException, android.security.identity.InvalidRequestMessageException, android.security.identity.NoAuthenticationKeyAvailableException, android.security.identity.SessionTranscriptMismatchException;
+ method @Deprecated @NonNull public abstract android.security.identity.ResultData getEntries(@Nullable byte[], @NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>, @Nullable byte[], @Nullable byte[]) throws android.security.identity.EphemeralPublicKeyNotFoundException, android.security.identity.InvalidReaderSignatureException, android.security.identity.InvalidRequestMessageException, android.security.identity.NoAuthenticationKeyAvailableException, android.security.identity.SessionTranscriptMismatchException;
method @NonNull public byte[] proveOwnership(@NonNull byte[]);
- method public abstract void setAllowUsingExhaustedKeys(boolean);
- method public void setAllowUsingExpiredKeys(boolean);
+ method @Deprecated public abstract void setAllowUsingExhaustedKeys(boolean);
+ method @Deprecated public void setAllowUsingExpiredKeys(boolean);
method public abstract void setAvailableAuthenticationKeys(int, int);
- method public abstract void setReaderEphemeralPublicKey(@NonNull java.security.PublicKey) throws java.security.InvalidKeyException;
+ method @Deprecated public abstract void setReaderEphemeralPublicKey(@NonNull java.security.PublicKey) throws java.security.InvalidKeyException;
method @Deprecated public abstract void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException;
method public void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull java.time.Instant, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException;
method @NonNull public byte[] update(@NonNull android.security.identity.PersonalizationData);
@@ -38061,6 +38150,7 @@
public abstract class IdentityCredentialStore {
method @NonNull public abstract android.security.identity.WritableIdentityCredential createCredential(@NonNull String, @NonNull String) throws android.security.identity.AlreadyPersonalizedException, android.security.identity.DocTypeNotSupportedException;
+ method @NonNull public android.security.identity.PresentationSession createPresentationSession(int) throws android.security.identity.CipherSuiteNotSupportedException;
method @Deprecated @Nullable public abstract byte[] deleteCredentialByName(@NonNull String);
method @Nullable public abstract android.security.identity.IdentityCredential getCredentialByName(@NonNull String, int) throws android.security.identity.CipherSuiteNotSupportedException;
method @Nullable public static android.security.identity.IdentityCredentialStore getDirectAccessInstance(@NonNull android.content.Context);
@@ -38099,22 +38189,29 @@
method @NonNull public android.security.identity.PersonalizationData.Builder putEntry(@NonNull String, @NonNull String, @NonNull java.util.Collection<android.security.identity.AccessControlProfileId>, @NonNull byte[]);
}
- public abstract class ResultData {
- method @NonNull public abstract byte[] getAuthenticatedData();
- method @Nullable public abstract byte[] getEntry(@NonNull String, @NonNull String);
- method @Nullable public abstract java.util.Collection<java.lang.String> getEntryNames(@NonNull String);
- method @Nullable public abstract byte[] getMessageAuthenticationCode();
- method @NonNull public abstract java.util.Collection<java.lang.String> getNamespaces();
- method @Nullable public abstract java.util.Collection<java.lang.String> getRetrievedEntryNames(@NonNull String);
- method @NonNull public abstract byte[] getStaticAuthenticationData();
- method public abstract int getStatus(@NonNull String, @NonNull String);
- field public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3; // 0x3
- field public static final int STATUS_NOT_REQUESTED = 2; // 0x2
- field public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6; // 0x6
- field public static final int STATUS_NO_SUCH_ENTRY = 1; // 0x1
- field public static final int STATUS_OK = 0; // 0x0
- field public static final int STATUS_READER_AUTHENTICATION_FAILED = 5; // 0x5
- field public static final int STATUS_USER_AUTHENTICATION_FAILED = 4; // 0x4
+ public abstract class PresentationSession {
+ method @Nullable public abstract android.security.identity.CredentialDataResult getCredentialData(@NonNull String, @NonNull android.security.identity.CredentialDataRequest) throws android.security.identity.EphemeralPublicKeyNotFoundException, android.security.identity.InvalidReaderSignatureException, android.security.identity.InvalidRequestMessageException, android.security.identity.NoAuthenticationKeyAvailableException;
+ method @NonNull public abstract java.security.KeyPair getEphemeralKeyPair();
+ method public abstract void setReaderEphemeralPublicKey(@NonNull java.security.PublicKey) throws java.security.InvalidKeyException;
+ method public abstract void setSessionTranscript(@NonNull byte[]);
+ }
+
+ @Deprecated public abstract class ResultData {
+ method @Deprecated @NonNull public abstract byte[] getAuthenticatedData();
+ method @Deprecated @Nullable public abstract byte[] getEntry(@NonNull String, @NonNull String);
+ method @Deprecated @Nullable public abstract java.util.Collection<java.lang.String> getEntryNames(@NonNull String);
+ method @Deprecated @Nullable public abstract byte[] getMessageAuthenticationCode();
+ method @Deprecated @NonNull public abstract java.util.Collection<java.lang.String> getNamespaces();
+ method @Deprecated @Nullable public abstract java.util.Collection<java.lang.String> getRetrievedEntryNames(@NonNull String);
+ method @Deprecated @NonNull public abstract byte[] getStaticAuthenticationData();
+ method @Deprecated public abstract int getStatus(@NonNull String, @NonNull String);
+ field @Deprecated public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3; // 0x3
+ field @Deprecated public static final int STATUS_NOT_REQUESTED = 2; // 0x2
+ field @Deprecated public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6; // 0x6
+ field @Deprecated public static final int STATUS_NO_SUCH_ENTRY = 1; // 0x1
+ field @Deprecated public static final int STATUS_OK = 0; // 0x0
+ field @Deprecated public static final int STATUS_READER_AUTHENTICATION_FAILED = 5; // 0x5
+ field @Deprecated public static final int STATUS_USER_AUTHENTICATION_FAILED = 4; // 0x4
}
public class SessionTranscriptMismatchException extends android.security.identity.IdentityCredentialException {
@@ -41999,6 +42096,7 @@
field public static final int EPDG_ADDRESS_PCO = 2; // 0x2
field public static final int EPDG_ADDRESS_PLMN = 1; // 0x1
field public static final int EPDG_ADDRESS_STATIC = 0; // 0x0
+ field public static final int EPDG_ADDRESS_VISITED_COUNTRY = 4; // 0x4
field public static final int ID_TYPE_FQDN = 2; // 0x2
field public static final int ID_TYPE_KEY_ID = 11; // 0xb
field public static final int ID_TYPE_RFC822_ADDR = 3; // 0x3
@@ -47161,6 +47259,15 @@
field public float ydpi;
}
+ public interface Dumpable {
+ method public void dump(@NonNull java.io.PrintWriter, @Nullable String[]);
+ method @NonNull public default String getDumpableName();
+ }
+
+ public interface DumpableContainer {
+ method public boolean addDumpable(@NonNull android.util.Dumpable);
+ }
+
public class EventLog {
method public static int getTagCode(String);
method public static String getTagName(int);
@@ -49160,6 +49267,7 @@
field public static final int EDGE_LEFT = 4; // 0x4
field public static final int EDGE_RIGHT = 8; // 0x8
field public static final int EDGE_TOP = 1; // 0x1
+ field public static final int FLAG_CANCELED = 32; // 0x20
field public static final int FLAG_WINDOW_IS_OBSCURED = 1; // 0x1
field public static final int FLAG_WINDOW_IS_PARTIALLY_OBSCURED = 2; // 0x2
field public static final int INVALID_POINTER_ID = -1; // 0xffffffff
@@ -52262,6 +52370,7 @@
method public final float getFontScale();
method @Nullable public final java.util.Locale getLocale();
method @NonNull public android.view.accessibility.CaptioningManager.CaptionStyle getUserStyle();
+ method public boolean isCallCaptioningEnabled();
method public final boolean isEnabled();
method public void removeCaptioningChangeListener(@NonNull android.view.accessibility.CaptioningManager.CaptioningChangeListener);
}
@@ -53198,6 +53307,7 @@
method public boolean showSoftInput(android.view.View, int, android.os.ResultReceiver);
method @Deprecated public void showSoftInputFromInputMethod(android.os.IBinder, int);
method @Deprecated public void showStatusIcon(android.os.IBinder, String, @DrawableRes int);
+ method public void startStylusHandwriting(@NonNull android.view.View);
method @Deprecated public boolean switchToLastInputMethod(android.os.IBinder);
method @Deprecated public boolean switchToNextInputMethod(android.os.IBinder, boolean);
method @Deprecated public void toggleSoftInput(int, int);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 4d84537..c4aff2d0 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -9,6 +9,10 @@
package android.app {
+ @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
+ method public final boolean addDumpable(@NonNull android.util.Dumpable);
+ }
+
public class ActivityManager {
method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener);
method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void removeHomeVisibilityListener(@NonNull android.app.HomeVisibilityListener);
@@ -160,6 +164,7 @@
public final class BtProfileConnectionInfo implements android.os.Parcelable {
method @NonNull public static android.media.BtProfileConnectionInfo a2dpInfo(boolean, int);
+ method @NonNull public static android.media.BtProfileConnectionInfo a2dpSinkInfo(int);
method public int describeContents();
method public boolean getIsLeOutput();
method public int getProfile();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 3a7077d..17ea0858 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -149,6 +149,7 @@
field public static final String MANAGE_APP_PREDICTIONS = "android.permission.MANAGE_APP_PREDICTIONS";
field public static final String MANAGE_APP_TOKENS = "android.permission.MANAGE_APP_TOKENS";
field public static final String MANAGE_AUTO_FILL = "android.permission.MANAGE_AUTO_FILL";
+ field public static final String MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED = "android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED";
field public static final String MANAGE_CARRIER_OEM_UNLOCK_STATE = "android.permission.MANAGE_CARRIER_OEM_UNLOCK_STATE";
field public static final String MANAGE_CA_CERTIFICATES = "android.permission.MANAGE_CA_CERTIFICATES";
field public static final String MANAGE_CONTENT_CAPTURE = "android.permission.MANAGE_CONTENT_CAPTURE";
@@ -178,6 +179,7 @@
field public static final String MANAGE_USB = "android.permission.MANAGE_USB";
field public static final String MANAGE_USERS = "android.permission.MANAGE_USERS";
field public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE";
+ field public static final String MANAGE_WEAK_ESCROW_TOKEN = "android.permission.MANAGE_WEAK_ESCROW_TOKEN";
field public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN";
field public static final String MANAGE_WIFI_COUNTRY_CODE = "android.permission.MANAGE_WIFI_COUNTRY_CODE";
field public static final String MARK_DEVICE_ORGANIZATION_OWNED = "android.permission.MARK_DEVICE_ORGANIZATION_OWNED";
@@ -298,6 +300,7 @@
field public static final String SIGNAL_REBOOT_READINESS = "android.permission.SIGNAL_REBOOT_READINESS";
field public static final String SOUND_TRIGGER_RUN_IN_BATTERY_SAVER = "android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER";
field public static final String START_ACTIVITIES_FROM_BACKGROUND = "android.permission.START_ACTIVITIES_FROM_BACKGROUND";
+ field public static final String START_CROSS_PROFILE_ACTIVITIES = "android.permission.START_CROSS_PROFILE_ACTIVITIES";
field public static final String START_REVIEW_PERMISSION_DECISIONS = "android.permission.START_REVIEW_PERMISSION_DECISIONS";
field public static final String STATUS_BAR_SERVICE = "android.permission.STATUS_BAR_SERVICE";
field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
@@ -364,7 +367,6 @@
field public static final int config_showDefaultAssistant = 17891329; // 0x1110001
field public static final int config_showDefaultEmergency = 17891330; // 0x1110002
field public static final int config_showDefaultHome = 17891331; // 0x1110003
- field public static final int config_systemCaptionsServiceCallsEnabled;
}
public static final class R.color {
@@ -769,18 +771,32 @@
}
public class KeyguardManager {
+ method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public long addWeakEscrowToken(@NonNull byte[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.KeyguardManager.WeakEscrowTokenActivatedListener);
method public android.content.Intent createConfirmFactoryResetCredentialIntent(CharSequence, CharSequence, CharSequence);
method @RequiresPermission("android.permission.SET_INITIAL_LOCK") public int getMinLockLength(boolean, int);
method @RequiresPermission(android.Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS) public boolean getPrivateNotificationsAllowed();
method @RequiresPermission("android.permission.SET_INITIAL_LOCK") public boolean isValidLockPasswordComplexity(int, @NonNull byte[], int);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean isWeakEscrowTokenActive(long, @NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean isWeakEscrowTokenValid(long, @NonNull byte[], @NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean registerWeakEscrowTokenRemovedListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.KeyguardManager.WeakEscrowTokenRemovedListener);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean removeWeakEscrowToken(long, @NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.SHOW_KEYGUARD_MESSAGE) public void requestDismissKeyguard(@NonNull android.app.Activity, @Nullable CharSequence, @Nullable android.app.KeyguardManager.KeyguardDismissCallback);
method @RequiresPermission("android.permission.SET_INITIAL_LOCK") public boolean setLock(int, @NonNull byte[], int);
method @RequiresPermission(android.Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS) public void setPrivateNotificationsAllowed(boolean);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean unregisterWeakEscrowTokenRemovedListener(@NonNull android.app.KeyguardManager.WeakEscrowTokenRemovedListener);
field public static final int PASSWORD = 0; // 0x0
field public static final int PATTERN = 2; // 0x2
field public static final int PIN = 1; // 0x1
}
+ public static interface KeyguardManager.WeakEscrowTokenActivatedListener {
+ method public void onWeakEscrowTokenActivated(long, @NonNull android.os.UserHandle);
+ }
+
+ public static interface KeyguardManager.WeakEscrowTokenRemovedListener {
+ method public void onWeakEscrowTokenRemoved(long, @NonNull android.os.UserHandle);
+ }
+
public class LocaleManager {
method @NonNull @RequiresPermission(android.Manifest.permission.READ_APP_SPECIFIC_LOCALES) public android.os.LocaleList getApplicationLocales(@NonNull String);
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void setApplicationLocales(@NonNull String, @NonNull android.os.LocaleList);
@@ -1021,12 +1037,12 @@
field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE";
field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
field @RequiresPermission(android.Manifest.permission.MANAGE_FACTORY_RESET_PROTECTION) public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
- field public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION = "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION";
- field public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
- field public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE";
+ field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION = "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION";
+ field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
+ field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE";
field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
- field public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER";
+ field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER";
field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6
field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb
field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
@@ -2843,7 +2859,7 @@
}
public class CrossProfileApps {
- method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES) public void startActivity(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
+ method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_PROFILES, android.Manifest.permission.START_CROSS_PROFILE_ACTIVITIES}) public void startActivity(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
}
public class DataLoaderParams {
@@ -4152,7 +4168,7 @@
public class ContextHubClient implements java.io.Closeable {
method public void close();
method @NonNull public android.hardware.location.ContextHubInfo getAttachedHub();
- method public int getId();
+ method @IntRange(from=0, to=65535) public int getId();
method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int sendMessageToNanoApp(@NonNull android.hardware.location.NanoAppMessage);
}
@@ -6367,6 +6383,7 @@
public static final class TvInputManager.Hardware {
method public void overrideAudioSink(int, String, int, int, int);
+ method public void overrideAudioSink(@NonNull android.media.AudioDeviceInfo, @IntRange(from=0) int, int, int);
method public void setStreamVolume(float);
method public boolean setSurface(android.view.Surface, android.media.tv.TvStreamConfig);
}
@@ -9137,6 +9154,7 @@
field public static final int EVENT_UNSPECIFIED = 0; // 0x0
field public static final int REASON_ACCOUNT_TRANSFER = 104; // 0x68
field public static final int REASON_ACTIVITY_RECOGNITION = 103; // 0x67
+ field public static final int REASON_BLUETOOTH_BROADCAST = 203; // 0xcb
field public static final int REASON_GEOFENCING = 100; // 0x64
field public static final int REASON_LOCATION_PROVIDER = 312; // 0x138
field public static final int REASON_OTHER = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 66250fce..f1b4624 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1147,15 +1147,15 @@
public final class DisplayManager {
method public boolean areUserDisabledHdrTypesAllowed();
- method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void clearUserPreferredDisplayMode();
+ method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void clearGlobalUserPreferredDisplayMode();
+ method @Nullable public android.view.Display.Mode getGlobalUserPreferredDisplayMode();
method @NonNull public int[] getUserDisabledHdrTypes();
- method @Nullable public android.view.Display.Mode getUserPreferredDisplayMode();
method public boolean isMinimalPostProcessingRequested(int);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAreUserDisabledHdrTypesAllowed(boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setGlobalUserPreferredDisplayMode(@NonNull android.view.Display.Mode);
method @RequiresPermission(android.Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE) public void setRefreshRateSwitchingType(int);
method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public void setShouldAlwaysRespectAppRequestedMode(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setUserDisabledHdrTypes(@NonNull int[]);
- method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setUserPreferredDisplayMode(@NonNull android.view.Display.Mode);
method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public boolean shouldAlwaysRespectAppRequestedMode();
field public static final int SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS = 2; // 0x2
field public static final int SWITCHING_TYPE_NONE = 0; // 0x0
@@ -1319,7 +1319,7 @@
method public void setAccumulatedDeltaRangeMeters(double);
method public void setAccumulatedDeltaRangeState(int);
method public void setAccumulatedDeltaRangeUncertaintyMeters(double);
- method public void setAutomaticGainControlLevelInDb(double);
+ method @Deprecated public void setAutomaticGainControlLevelInDb(double);
method public void setBasebandCn0DbHz(double);
method @Deprecated public void setCarrierCycles(long);
method public void setCarrierFrequencyHz(float);
@@ -1346,10 +1346,6 @@
field public static final int ADR_STATE_ALL = 31; // 0x1f
}
- public final class GnssMeasurementsEvent implements android.os.Parcelable {
- ctor public GnssMeasurementsEvent(android.location.GnssClock, android.location.GnssMeasurement[]);
- }
-
public final class GnssNavigationMessage implements android.os.Parcelable {
ctor public GnssNavigationMessage();
method public void reset();
@@ -2714,11 +2710,14 @@
}
public final class Display {
+ method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void clearUserPreferredDisplayMode();
method @NonNull public android.view.Display.Mode getDefaultMode();
method @NonNull public int[] getReportedHdrTypes();
method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut();
method public int getType();
+ method @Nullable public android.view.Display.Mode getUserPreferredDisplayMode();
method public boolean hasAccess(int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setUserPreferredDisplayMode(@NonNull android.view.Display.Mode);
field public static final int FLAG_TRUSTED = 128; // 0x80
field public static final int TYPE_EXTERNAL = 2; // 0x2
field public static final int TYPE_INTERNAL = 1; // 0x1
@@ -2733,6 +2732,13 @@
method public boolean matches(int, int, float);
}
+ public static final class Display.Mode.Builder {
+ ctor public Display.Mode.Builder();
+ method @NonNull public android.view.Display.Mode build();
+ method @NonNull public android.view.Display.Mode.Builder setRefreshRate(float);
+ method @NonNull public android.view.Display.Mode.Builder setResolution(int, int);
+ }
+
public class FocusFinder {
method public static void sort(android.view.View[], int, int, android.view.ViewGroup, boolean);
}
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index e3690e5..01604e6 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -737,6 +737,8 @@
MissingGetterMatchingBuilder: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String):
+MissingGetterMatchingBuilder: android.view.Display.Mode.Builder#setResolution(int, int):
+ android.view.Display.Mode does not declare a `getResolution()` method matching method android.view.Display.Mode.Builder.setResolution(int,int)
MissingNullability: android.app.Activity#onMovedToDisplay(int, android.content.res.Configuration) parameter #1:
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index cf2b7ac..283345f 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -90,6 +90,7 @@
import android.transition.TransitionManager;
import android.util.ArrayMap;
import android.util.AttributeSet;
+import android.util.Dumpable;
import android.util.EventLog;
import android.util.Log;
import android.util.PrintWriterPrinter;
@@ -145,6 +146,7 @@
import com.android.internal.app.ToolbarActionBar;
import com.android.internal.app.WindowDecorActionBar;
import com.android.internal.policy.PhoneWindow;
+import com.android.internal.util.dump.DumpableContainerImpl;
import dalvik.system.VMRuntime;
@@ -954,6 +956,9 @@
private SplashScreen mSplashScreen;
+ @Nullable
+ private DumpableContainerImpl mDumpableContainer;
+
private final WindowControllerCallback mWindowControllerCallback =
new WindowControllerCallback() {
/**
@@ -7081,8 +7086,23 @@
dumpInner(prefix, fd, writer, args);
}
+ /**
+ * See {@link android.util.DumpableContainer#addDumpable(Dumpable)}.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public final boolean addDumpable(@NonNull Dumpable dumpable) {
+ if (mDumpableContainer == null) {
+ mDumpableContainer = new DumpableContainerImpl();
+ }
+ return mDumpableContainer.addDumpable(dumpable);
+ }
+
void dumpInner(@NonNull String prefix, @Nullable FileDescriptor fd,
@NonNull PrintWriter writer, @Nullable String[] args) {
+ String innerPrefix = prefix + " ";
+
if (args != null && args.length > 0) {
// Handle special cases
switch (args[0]) {
@@ -7095,12 +7115,33 @@
case "--translation":
dumpUiTranslation(prefix, writer);
return;
+ case "--list-dumpables":
+ if (mDumpableContainer == null) {
+ writer.print(prefix); writer.println("No dumpables");
+ return;
+ }
+ mDumpableContainer.listDumpables(prefix, writer);
+ return;
+ case "--dump-dumpable":
+ if (args.length == 1) {
+ writer.println("--dump-dumpable requires the dumpable name");
+ return;
+ }
+ if (mDumpableContainer == null) {
+ writer.println("no dumpables");
+ return;
+ }
+ // Strips --dump-dumpable NAME
+ String[] prunedArgs = new String[args.length - 2];
+ System.arraycopy(args, 2, prunedArgs, 0, prunedArgs.length);
+ mDumpableContainer.dumpOneDumpable(prefix, writer, args[1], prunedArgs);
+ return;
}
}
+
writer.print(prefix); writer.print("Local Activity ");
writer.print(Integer.toHexString(System.identityHashCode(this)));
writer.println(" State:");
- String innerPrefix = prefix + " ";
writer.print(innerPrefix); writer.print("mResumed=");
writer.print(mResumed); writer.print(" mStopped=");
writer.print(mStopped); writer.print(" mFinished=");
@@ -7138,6 +7179,10 @@
dumpUiTranslation(prefix, writer);
ResourcesManager.getInstance().dump(prefix, writer);
+
+ if (mDumpableContainer != null) {
+ mDumpableContainer.dumpAllDumpables(prefix, writer, args);
+ }
}
void dumpContentCaptureManager(String prefix, PrintWriter writer) {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index fe512ff..fa48730 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -312,6 +312,14 @@
@ContextType
private int mContextType;
+ /**
+ * {@code true} to indicate that the {@link Context} owns the {@link #getWindowContextToken()}
+ * and is responsible for detaching the token when the Context is released.
+ *
+ * @see #finalize()
+ */
+ private boolean mOwnsToken = false;
+
@GuardedBy("mSync")
private File mDatabasesDir;
@GuardedBy("mSync")
@@ -3015,7 +3023,7 @@
// WindowContainer. We should detach from WindowContainer when the Context is finalized
// if this Context is not a WindowContext. WindowContext finalization is handled in
// WindowContext class.
- if (mToken instanceof WindowTokenClient && mContextType != CONTEXT_TYPE_WINDOW_CONTEXT) {
+ if (mToken instanceof WindowTokenClient && mOwnsToken) {
((WindowTokenClient) mToken).detachFromWindowContainerIfNeeded();
}
super.finalize();
@@ -3046,6 +3054,7 @@
token.attachContext(context);
token.attachToDisplayContent(displayId);
context.mContextType = CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI;
+ context.mOwnsToken = true;
return context;
}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index fdcf99d..a82ecce 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -63,6 +63,7 @@
boolean isInInvalidMsgState(String pkg, int uid);
boolean hasUserDemotedInvalidMsgApp(String pkg, int uid);
void setInvalidMsgAppDemoted(String pkg, int uid, boolean isDemoted);
+ boolean hasSentValidBubble(String pkg, int uid);
void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
/**
* Updates the notification's enabled state. Additionally locks importance for all of the
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index dc71a32..14afd0f 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -17,6 +17,7 @@
package android.app;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -40,8 +41,10 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.UserHandle;
import android.provider.Settings;
import android.service.persistentdata.IPersistentDataBlockService;
+import android.util.ArrayMap;
import android.util.Log;
import android.view.IOnKeyguardExitResult;
import android.view.IWindowManager;
@@ -49,6 +52,9 @@
import android.view.WindowManagerGlobal;
import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.internal.util.Preconditions;
+import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
+import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
import com.android.internal.widget.LockscreenCredential;
@@ -57,6 +63,8 @@
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
/**
* Class that can be used to lock and unlock the keyguard. The
@@ -69,10 +77,13 @@
private static final String TAG = "KeyguardManager";
private final Context mContext;
+ private final LockPatternUtils mLockPatternUtils;
private final IWindowManager mWM;
private final IActivityManager mAm;
private final ITrustManager mTrustManager;
private final INotificationManager mNotificationManager;
+ private final ArrayMap<WeakEscrowTokenRemovedListener, IWeakEscrowTokenRemovedListener>
+ mListeners = new ArrayMap<>();
/**
* Intent used to prompt user for device credentials.
@@ -455,8 +466,42 @@
public void onDismissCancelled() { }
}
+ /**
+ * Callback passed to
+ * {@link KeyguardManager#addWeakEscrowToken}
+ * to notify caller of state change.
+ * @hide
+ */
+ @SystemApi
+ public interface WeakEscrowTokenActivatedListener {
+ /**
+ * The method to be called when the token is activated.
+ * @param handle 64 bit handle corresponding to the escrow token
+ * @param user user for whom the weak escrow token has been added
+ */
+ void onWeakEscrowTokenActivated(long handle, @NonNull UserHandle user);
+ }
+
+ /**
+ * Listener passed to
+ * {@link KeyguardManager#registerWeakEscrowTokenRemovedListener} and
+ * {@link KeyguardManager#unregisterWeakEscrowTokenRemovedListener}
+ * to notify caller of an weak escrow token has been removed.
+ * @hide
+ */
+ @SystemApi
+ public interface WeakEscrowTokenRemovedListener {
+ /**
+ * The method to be called when the token is removed.
+ * @param handle 64 bit handle corresponding to the escrow token
+ * @param user user for whom the escrow token has been added
+ */
+ void onWeakEscrowTokenRemoved(long handle, @NonNull UserHandle user);
+ }
+
KeyguardManager(Context context) throws ServiceNotFoundException {
mContext = context;
+ mLockPatternUtils = new LockPatternUtils(context);
mWM = WindowManagerGlobal.getWindowManagerService();
mAm = ActivityManager.getService();
mTrustManager = ITrustManager.Stub.asInterface(
@@ -785,7 +830,6 @@
return false;
}
- LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
int userId = mContext.getUserId();
if (isDeviceSecure(userId)) {
Log.e(TAG, "Password already set, rejecting call to setLock");
@@ -799,7 +843,7 @@
try {
LockscreenCredential credential = createLockscreenCredential(
lockType, password);
- success = lockPatternUtils.setLockCredential(
+ success = mLockPatternUtils.setLockCredential(
credential,
/* savedPassword= */ LockscreenCredential.createNone(),
userId);
@@ -813,6 +857,150 @@
}
/**
+ * Create a weak escrow token for the current user, which can later be used to unlock FBE
+ * or change user password.
+ *
+ * After adding, if the user currently has a secure lockscreen, they will need to perform a
+ * confirm credential operation in order to activate the token for future use. If the user
+ * has no secure lockscreen, then the token is activated immediately.
+ *
+ * If the user changes or removes the lockscreen password, any activated weak escrow token will
+ * be removed.
+ *
+ * @return a unique 64-bit token handle which is needed to refer to this token later.
+ * @hide
+ */
+ @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
+ @SystemApi
+ public long addWeakEscrowToken(@NonNull byte[] token, @NonNull UserHandle user,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull WeakEscrowTokenActivatedListener listener) {
+ Objects.requireNonNull(token, "Token cannot be null.");
+ Objects.requireNonNull(user, "User cannot be null.");
+ Objects.requireNonNull(executor, "Executor cannot be null.");
+ Objects.requireNonNull(listener, "Listener cannot be null.");
+ int userId = user.getIdentifier();
+ IWeakEscrowTokenActivatedListener internalListener =
+ new IWeakEscrowTokenActivatedListener.Stub() {
+ @Override
+ public void onWeakEscrowTokenActivated(long handle, int userId) {
+ UserHandle user = UserHandle.of(userId);
+ final long restoreToken = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> listener.onWeakEscrowTokenActivated(handle, user));
+ } finally {
+ Binder.restoreCallingIdentity(restoreToken);
+ }
+ Log.i(TAG, "Weak escrow token activated.");
+ }
+ };
+ return mLockPatternUtils.addWeakEscrowToken(token, userId, internalListener);
+ }
+
+ /**
+ * Remove a weak escrow token.
+ *
+ * @return true if the given handle refers to a valid weak token previously returned from
+ * {@link #addWeakEscrowToken}, whether it's active or not. return false otherwise.
+ * @hide
+ */
+ @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
+ @SystemApi
+ public boolean removeWeakEscrowToken(long handle, @NonNull UserHandle user) {
+ Objects.requireNonNull(user, "User cannot be null.");
+ return mLockPatternUtils.removeWeakEscrowToken(handle, user.getIdentifier());
+ }
+
+ /**
+ * Check if the given weak escrow token is active or not.
+ * @hide
+ */
+ @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
+ @SystemApi
+ public boolean isWeakEscrowTokenActive(long handle, @NonNull UserHandle user) {
+ Objects.requireNonNull(user, "User cannot be null.");
+ return mLockPatternUtils.isWeakEscrowTokenActive(handle, user.getIdentifier());
+ }
+
+ /**
+ * Check if the given weak escrow token is validate.
+ * @hide
+ */
+ @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
+ @SystemApi
+ public boolean isWeakEscrowTokenValid(long handle, @NonNull byte[] token,
+ @NonNull UserHandle user) {
+ Objects.requireNonNull(token, "Token cannot be null.");
+ Objects.requireNonNull(user, "User cannot be null.");
+ return mLockPatternUtils.isWeakEscrowTokenValid(handle, token, user.getIdentifier());
+ }
+
+ /**
+ * Register the given WeakEscrowTokenRemovedListener.
+ *
+ * @return true if the listener is registered successfully, return false otherwise.
+ * @hide
+ */
+ @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
+ @SystemApi
+ public boolean registerWeakEscrowTokenRemovedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull WeakEscrowTokenRemovedListener listener) {
+ Objects.requireNonNull(listener, "Listener cannot be null.");
+ Objects.requireNonNull(executor, "Executor cannot be null.");
+ Preconditions.checkArgument(!mListeners.containsKey(listener),
+ "Listener already registered: %s", listener);
+ IWeakEscrowTokenRemovedListener internalListener =
+ new IWeakEscrowTokenRemovedListener.Stub() {
+ @Override
+ public void onWeakEscrowTokenRemoved(long handle, int userId) {
+ UserHandle user = UserHandle.of(userId);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> listener.onWeakEscrowTokenRemoved(handle, user));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ };
+ if (mLockPatternUtils.registerWeakEscrowTokenRemovedListener(internalListener)) {
+ mListeners.put(listener, internalListener);
+ return true;
+ } else {
+ Log.e(TAG, "Listener failed to register");
+ return false;
+ }
+ }
+
+ /**
+ * Unregister the given WeakEscrowTokenRemovedListener.
+ *
+ * @return true if the listener is unregistered successfully, return false otherwise.
+ * @hide
+ */
+ @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
+ @SystemApi
+ public boolean unregisterWeakEscrowTokenRemovedListener(
+ @NonNull WeakEscrowTokenRemovedListener listener) {
+ Objects.requireNonNull(listener, "Listener cannot be null.");
+ IWeakEscrowTokenRemovedListener internalListener = mListeners.get(listener);
+ Preconditions.checkArgument(internalListener != null, "Listener was not registered");
+ if (mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(internalListener)) {
+ mListeners.remove(listener);
+ return true;
+ } else {
+ Log.e(TAG, "Listener failed to unregister.");
+ return false;
+ }
+ }
+
+ /**
* Set the lockscreen password to {@code newPassword} after validating the current password
* against {@code currentPassword}.
* <p>If no password is currently set, {@code currentPassword} should be set to {@code null}.
@@ -832,13 +1020,12 @@
})
public boolean setLock(@LockTypes int newLockType, @Nullable byte[] newPassword,
@LockTypes int currentLockType, @Nullable byte[] currentPassword) {
- final LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
final int userId = mContext.getUserId();
LockscreenCredential currentCredential = createLockscreenCredential(
currentLockType, currentPassword);
LockscreenCredential newCredential = createLockscreenCredential(
newLockType, newPassword);
- return lockPatternUtils.setLockCredential(newCredential, currentCredential, userId);
+ return mLockPatternUtils.setLockCredential(newCredential, currentCredential, userId);
}
/**
@@ -857,10 +1044,9 @@
Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE
})
public boolean checkLock(@LockTypes int lockType, @Nullable byte[] password) {
- final LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
final LockscreenCredential credential = createLockscreenCredential(
lockType, password);
- final VerifyCredentialResponse response = lockPatternUtils.verifyCredential(
+ final VerifyCredentialResponse response = mLockPatternUtils.verifyCredential(
credential, mContext.getUserId(), /* flags= */ 0);
if (response == null) {
return false;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 73ebdf6..a60de08 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -488,8 +488,7 @@
*
* @hide
*/
- // TODO(b/208628038): Uncomment when the permission is in place
- // @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+ @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP)
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@SystemApi
public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE =
@@ -535,8 +534,7 @@
*
* @hide
*/
- // TODO(b/208628038): Uncomment when the permission is in place
- // @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+ @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP)
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@SystemApi
public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE =
@@ -561,8 +559,7 @@
*
* @hide
*/
- // TODO(b/208628038): Uncomment when the permission is in place
- // @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+ @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP)
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@SystemApi
public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION =
@@ -2859,8 +2856,7 @@
*
* @hide
*/
- // TODO(b/208628038): Uncomment when the permission is in place
- // @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+ @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP)
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@SystemApi
public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER =
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index a695f6d..661291c 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -28,8 +28,7 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
-import android.app.PropertyInvalidatedCache;
+import android.annotation.SystemApi; //import android.app.PropertyInvalidatedCache;
import android.bluetooth.BluetoothDevice.Transport;
import android.bluetooth.BluetoothProfile.ConnectionPolicy;
import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission;
@@ -687,14 +686,15 @@
"android.bluetooth.adapter.action.BLE_ACL_DISCONNECTED";
/** The profile is in disconnected state */
- public static final int STATE_DISCONNECTED = BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTED;
+ public static final int STATE_DISCONNECTED =
+ 0; //BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTED;
/** The profile is in connecting state */
- public static final int STATE_CONNECTING = BluetoothProtoEnums.CONNECTION_STATE_CONNECTING;
+ public static final int STATE_CONNECTING = 1; //BluetoothProtoEnums.CONNECTION_STATE_CONNECTING;
/** The profile is in connected state */
- public static final int STATE_CONNECTED = BluetoothProtoEnums.CONNECTION_STATE_CONNECTED;
+ public static final int STATE_CONNECTED = 2; //BluetoothProtoEnums.CONNECTION_STATE_CONNECTED;
/** The profile is in disconnecting state */
public static final int STATE_DISCONNECTING =
- BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTING;
+ 3; //BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTING;
/** @hide */
public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager";
@@ -1055,6 +1055,7 @@
return false;
}
+ /*
private static final String BLUETOOTH_GET_STATE_CACHE_PROPERTY = "cache_key.bluetooth.get_state";
private final PropertyInvalidatedCache<Void, Integer> mBluetoothGetStateCache =
@@ -1070,17 +1071,22 @@
}
}
};
+ */
/** @hide */
+ /*
@RequiresNoPermission
public void disableBluetoothGetStateCache() {
mBluetoothGetStateCache.disableLocal();
}
+ */
/** @hide */
+ /*
public static void invalidateBluetoothGetStateCache() {
PropertyInvalidatedCache.invalidateCache(BLUETOOTH_GET_STATE_CACHE_PROPERTY);
}
+ */
/**
* Fetch the current bluetooth state. If the service is down, return
@@ -1092,14 +1098,12 @@
try {
mServiceLock.readLock().lock();
if (mService != null) {
- state = mBluetoothGetStateCache.query(null);
+ //state = mBluetoothGetStateCache.query(null);
+ state = mService.getState();
}
- } catch (RuntimeException e) {
- if (e.getCause() instanceof RemoteException) {
- Log.e(TAG, "", e.getCause());
- } else {
- throw e;
- }
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ e.rethrowFromSystemServer();
} finally {
mServiceLock.readLock().unlock();
}
@@ -2078,6 +2082,7 @@
}
}
+ /*
private static final String BLUETOOTH_FILTERING_CACHE_PROPERTY =
"cache_key.bluetooth.is_offloaded_filtering_supported";
private final PropertyInvalidatedCache<Void, Boolean> mBluetoothFilteringCache =
@@ -2100,17 +2105,22 @@
}
};
+ */
/** @hide */
+ /*
@RequiresNoPermission
public void disableIsOffloadedFilteringSupportedCache() {
mBluetoothFilteringCache.disableLocal();
}
+ */
/** @hide */
+ /*
public static void invalidateIsOffloadedFilteringSupportedCache() {
PropertyInvalidatedCache.invalidateCache(BLUETOOTH_FILTERING_CACHE_PROPERTY);
}
+ */
/**
* Return true if offloaded filters are supported
@@ -2123,7 +2133,18 @@
if (!getLeAccess()) {
return false;
}
- return mBluetoothFilteringCache.query(null);
+ //return mBluetoothFilteringCache.query(null);
+ try {
+ mServiceLock.readLock().lock();
+ if (mService != null) {
+ return mService.isOffloadedFilteringSupported();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to get isOffloadedFilteringSupported, error: ", e);
+ } finally {
+ mServiceLock.readLock().unlock();
+ }
+ return false;
}
/**
@@ -2530,15 +2551,13 @@
return supportedProfiles;
}
+ /*
private static final String BLUETOOTH_GET_ADAPTER_CONNECTION_STATE_CACHE_PROPERTY =
"cache_key.bluetooth.get_adapter_connection_state";
private final PropertyInvalidatedCache<Void, Integer>
mBluetoothGetAdapterConnectionStateCache =
new PropertyInvalidatedCache<Void, Integer> (
8, BLUETOOTH_GET_ADAPTER_CONNECTION_STATE_CACHE_PROPERTY) {
- /**
- * This method must not be called when mService is null.
- */
@Override
@SuppressLint("AndroidFrameworkRequiresPermission")
public Integer recompute(Void query) {
@@ -2549,18 +2568,23 @@
}
}
};
+ */
/** @hide */
+ /*
@RequiresNoPermission
public void disableGetAdapterConnectionStateCache() {
mBluetoothGetAdapterConnectionStateCache.disableLocal();
}
+ */
/** @hide */
+ /*
public static void invalidateGetAdapterConnectionStateCache() {
PropertyInvalidatedCache.invalidateCache(
BLUETOOTH_GET_ADAPTER_CONNECTION_STATE_CACHE_PROPERTY);
}
+ */
/**
* Get the current connection state of the local Bluetooth adapter.
@@ -2584,20 +2608,18 @@
try {
mServiceLock.readLock().lock();
if (mService != null) {
- return mBluetoothGetAdapterConnectionStateCache.query(null);
+ return mService.getAdapterConnectionState();
}
- } catch (RuntimeException e) {
- if (e.getCause() instanceof RemoteException) {
- Log.e(TAG, "getConnectionState:", e.getCause());
- } else {
- throw e;
- }
+ //return mBluetoothGetAdapterConnectionStateCache.query(null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to getConnectionState, error: ", e);
} finally {
mServiceLock.readLock().unlock();
}
return BluetoothAdapter.STATE_DISCONNECTED;
}
+ /*
private static final String BLUETOOTH_PROFILE_CACHE_PROPERTY =
"cache_key.bluetooth.get_profile_connection_state";
private final PropertyInvalidatedCache<Integer, Integer>
@@ -2625,17 +2647,22 @@
query);
}
};
+ */
/** @hide */
+ /*
@RequiresNoPermission
public void disableGetProfileConnectionStateCache() {
mGetProfileConnectionStateCache.disableLocal();
}
+ */
/** @hide */
+ /*
public static void invalidateGetProfileConnectionStateCache() {
PropertyInvalidatedCache.invalidateCache(BLUETOOTH_PROFILE_CACHE_PROPERTY);
}
+ */
/**
* Get the current connection state of a profile.
@@ -2657,7 +2684,18 @@
if (getState() != STATE_ON) {
return BluetoothProfile.STATE_DISCONNECTED;
}
- return mGetProfileConnectionStateCache.query(new Integer(profile));
+ try {
+ mServiceLock.readLock().lock();
+ if (mService != null) {
+ mService.getProfileConnectionState(profile);
+ }
+ //return mGetProfileConnectionStateCache.query(new Integer(profile));
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to getProfileConnectionState, error: ", e);
+ } finally {
+ mServiceLock.readLock().unlock();
+ }
+ return BluetoothProfile.STATE_DISCONNECTED;
}
/**
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index fc99942..984166d 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -23,8 +23,7 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
-import android.app.PropertyInvalidatedCache;
+import android.annotation.SystemApi; //import android.app.PropertyInvalidatedCache;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
import android.bluetooth.annotations.RequiresBluetoothScanPermission;
@@ -1597,6 +1596,7 @@
return false;
}
+ /*
private static final String BLUETOOTH_BONDING_CACHE_PROPERTY =
"cache_key.bluetooth.get_bond_state";
private final PropertyInvalidatedCache<BluetoothDevice, Integer> mBluetoothBondCache =
@@ -1612,16 +1612,19 @@
}
}
};
+ */
/** @hide */
- public void disableBluetoothGetBondStateCache() {
+ /* public void disableBluetoothGetBondStateCache() {
mBluetoothBondCache.disableLocal();
- }
+ } */
/** @hide */
+ /*
public static void invalidateBluetoothGetBondStateCache() {
PropertyInvalidatedCache.invalidateCache(BLUETOOTH_BONDING_CACHE_PROPERTY);
}
+ */
/**
* Get the bond state of the remote device.
@@ -1643,13 +1646,11 @@
return BOND_NONE;
}
try {
- return mBluetoothBondCache.query(this);
- } catch (RuntimeException e) {
- if (e.getCause() instanceof RemoteException) {
- Log.e(TAG, "", e);
- } else {
- throw e;
- }
+ //return mBluetoothBondCache.query(this);
+ return sService.getBondState(this, mAttributionSource);
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to ", e);
+ e.rethrowFromSystemServer();
}
return BOND_NONE;
}
diff --git a/core/java/android/bluetooth/OWNERS b/core/java/android/bluetooth/OWNERS
index fbee577..8e9d7b7 100644
--- a/core/java/android/bluetooth/OWNERS
+++ b/core/java/android/bluetooth/OWNERS
@@ -1,6 +1,4 @@
# Bug component: 27441
-rahulsabnis@google.com
sattiraju@google.com
-siyuanh@google.com
-zachoverflow@google.com
+baligh@google.com
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index f0566b8..373a8d9 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -54,13 +54,13 @@
private final @Nullable String mDeviceProfile;
private final boolean mSelfManaged;
- private boolean mNotifyOnDeviceNearby;
+ private final boolean mNotifyOnDeviceNearby;
private final long mTimeApprovedMs;
/**
* A long value indicates the last time connected reported by selfManaged devices
* Default value is Long.MAX_VALUE.
*/
- private long mLastTimeConnectedMs;
+ private final long mLastTimeConnectedMs;
/**
* Creates a new Association.
@@ -160,22 +160,6 @@
return mSelfManaged;
}
- /**
- * Should only be used by the CompanionDeviceManagerService.
- * @hide
- */
- public void setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby) {
- mNotifyOnDeviceNearby = notifyOnDeviceNearby;
- }
-
- /**
- * Should only be used by the CompanionDeviceManagerService.
- * @hide
- */
- public void setLastTimeConnected(long lastTimeConnectedMs) {
- mLastTimeConnectedMs = lastTimeConnectedMs;
- }
-
/** @hide */
public boolean isNotifyOnDeviceNearby() {
return mNotifyOnDeviceNearby;
@@ -330,4 +314,112 @@
return new AssociationInfo(in);
}
};
+
+ /**
+ * Use this method to obtain a builder that you can use to create a copy of the
+ * given {@link AssociationInfo} with modified values of {@code mLastTimeConnected}
+ * or {@code mNotifyOnDeviceNearby}.
+ * <p>
+ * Note that you <b>must</b> call either {@link Builder#setLastTimeConnected(long)
+ * setLastTimeConnected} or {@link Builder#setNotifyOnDeviceNearby(boolean)
+ * setNotifyOnDeviceNearby} before you will be able to call {@link Builder#build() build}.
+ *
+ * This is ensured statically at compile time.
+ *
+ * @hide
+ */
+ @NonNull
+ public static NonActionableBuilder builder(@NonNull AssociationInfo info) {
+ return new Builder(info);
+ }
+
+ /**
+ * @hide
+ */
+ public static final class Builder implements NonActionableBuilder {
+ @NonNull
+ private final AssociationInfo mOriginalInfo;
+ private boolean mNotifyOnDeviceNearby;
+ private long mLastTimeConnectedMs;
+
+ private Builder(@NonNull AssociationInfo info) {
+ mOriginalInfo = info;
+ mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby;
+ mLastTimeConnectedMs = info.mLastTimeConnectedMs;
+ }
+
+ /**
+ * Should only be used by the CompanionDeviceManagerService.
+ * @hide
+ */
+ @Override
+ @NonNull
+ public Builder setLastTimeConnected(long lastTimeConnectedMs) {
+ if (lastTimeConnectedMs < 0) {
+ throw new IllegalArgumentException(
+ "lastTimeConnectedMs must not be negative! (Given " + lastTimeConnectedMs
+ + " )");
+ }
+ mLastTimeConnectedMs = lastTimeConnectedMs;
+ return this;
+ }
+
+ /**
+ * Should only be used by the CompanionDeviceManagerService.
+ * @hide
+ */
+ @Override
+ @NonNull
+ public Builder setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby) {
+ mNotifyOnDeviceNearby = notifyOnDeviceNearby;
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public AssociationInfo build() {
+ return new AssociationInfo(
+ mOriginalInfo.mId,
+ mOriginalInfo.mUserId,
+ mOriginalInfo.mPackageName,
+ mOriginalInfo.mDeviceMacAddress,
+ mOriginalInfo.mDisplayName,
+ mOriginalInfo.mDeviceProfile,
+ mOriginalInfo.mSelfManaged,
+ mNotifyOnDeviceNearby,
+ mOriginalInfo.mTimeApprovedMs,
+ mLastTimeConnectedMs
+ );
+ }
+ }
+
+ /**
+ * This interface is returned from the
+ * {@link AssociationInfo#builder(android.companion.AssociationInfo) builder} entry point
+ * to indicate that this builder is not yet in a state that can produce a meaningful
+ * {@link AssociationInfo} object that is different from the one originally passed in.
+ *
+ * <p>
+ * Only by calling one of the setter methods is this builder turned into one where calling
+ * {@link Builder#build() build()} makes sense.
+ *
+ * @hide
+ */
+ public interface NonActionableBuilder {
+ /**
+ * Should only be used by the CompanionDeviceManagerService.
+ * @hide
+ */
+ @NonNull
+ Builder setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby);
+
+ /**
+ * Should only be used by the CompanionDeviceManagerService.
+ * @hide
+ */
+ @NonNull
+ Builder setLastTimeConnected(long lastTimeConnectedMs);
+ }
}
diff --git a/core/java/android/companion/IOnAssociationsChangedListener.aidl b/core/java/android/companion/IOnAssociationsChangedListener.aidl
index e6794b7..d369456 100644
--- a/core/java/android/companion/IOnAssociationsChangedListener.aidl
+++ b/core/java/android/companion/IOnAssociationsChangedListener.aidl
@@ -20,5 +20,22 @@
/** @hide */
interface IOnAssociationsChangedListener {
- oneway void onAssociationsChanged(in List<AssociationInfo> associations);
+
+ /*
+ * IMPORTANT: This method is intentionally NOT "oneway".
+ *
+ * The method is intentionally "blocking" to make sure that the clients of the
+ * addOnAssociationsChangedListener() API (@SystemAPI guarded by a "signature" permission) are
+ * able to prevent race conditions that may arise if their own clients (applications)
+ * effectively get notified about the changes before system services do.
+ *
+ * This is safe for 2 reasons:
+ * 1. The addOnAssociationsChangedListener() is only available to the system components
+ * (guarded by a "signature" permission).
+ * See android.permission.MANAGE_COMPANION_DEVICES.
+ * 2. On the Java side addOnAssociationsChangedListener() in CDM takes an Executor, and the
+ * proxy implementation of onAssociationsChanged() simply "post" a Runnable to it.
+ * See CompanionDeviceManager.OnAssociationsChangedListenerProxy class.
+ */
+ void onAssociationsChanged(in List<AssociationInfo> associations);
}
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 82ad150..85855be 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -16,12 +16,14 @@
package android.companion.virtual;
+import android.app.PendingIntent;
import android.graphics.Point;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualMouseButtonEvent;
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualTouchEvent;
+import android.os.ResultReceiver;
/**
* Interface for a virtual device.
@@ -41,6 +43,7 @@
* Closes the virtual device and frees all associated resources.
*/
void close();
+
void createVirtualKeyboard(
int displayId,
String inputDeviceName,
@@ -66,4 +69,10 @@
boolean sendRelativeEvent(IBinder token, in VirtualMouseRelativeEvent event);
boolean sendScrollEvent(IBinder token, in VirtualMouseScrollEvent event);
boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event);
+
+ /**
+ * Launches a pending intent on the given display that is owned by this virtual device.
+ */
+ void launchPendingIntent(
+ int displayId, in PendingIntent pendingIntent, in ResultReceiver resultReceiver);
}
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 2dfa202..d80bee6 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -17,6 +17,7 @@
package android.companion.virtual;
import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.VirtualDeviceParams;
/**
* Interface for communication between VirtualDeviceManager and VirtualDeviceManagerService.
@@ -33,6 +34,10 @@
* that this belongs to the calling UID.
* @param associationId The association ID as returned by {@link AssociationInfo#getId()} from
* CDM. Virtual devices must have a corresponding association with CDM in order to be created.
+ * @param params The parameters for creating this virtual device. See {@link
+ * VirtualDeviceManager.VirtualDeviceParams}.
*/
- IVirtualDevice createVirtualDevice(in IBinder token, String packageName, int associationId);
+ IVirtualDevice createVirtualDevice(
+ in IBinder token, String packageName, int associationId,
+ in VirtualDeviceParams params);
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index bace45b..8ab6688 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -23,6 +23,8 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.app.Activity;
+import android.app.PendingIntent;
import android.companion.AssociationInfo;
import android.content.Context;
import android.graphics.Point;
@@ -33,15 +35,19 @@
import android.hardware.input.VirtualMouse;
import android.hardware.input.VirtualTouchscreen;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.view.Surface;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.concurrent.Executor;
/**
* System level service for managing virtual devices.
@@ -100,11 +106,11 @@
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Nullable
- public VirtualDevice createVirtualDevice(int associationId) {
+ public VirtualDevice createVirtualDevice(int associationId, VirtualDeviceParams params) {
// TODO(b/194949534): Unhide this API
try {
IVirtualDevice virtualDevice = mService.createVirtualDevice(
- new Binder(), mContext.getPackageName(), associationId);
+ new Binder(), mContext.getPackageName(), associationId, params);
return new VirtualDevice(mContext, virtualDevice);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -129,6 +135,49 @@
}
/**
+ * Launches a given pending intent on the give display ID.
+ *
+ * @param displayId The display to launch the pending intent on. This display must be
+ * created from this virtual device.
+ * @param pendingIntent The pending intent to be launched. If the intent is an activity
+ * intent, the activity will be started on the virtual display using
+ * {@link android.app.ActivityOptions#setLaunchDisplayId}. If the intent is a service or
+ * broadcast intent, an attempt will be made to catch activities started as a result of
+ * sending the pending intent and move them to the given display.
+ * @param executor The executor to run {@code launchCallback} on.
+ * @param launchCallback Callback that is called when the pending intent launching is
+ * complete.
+ *
+ * @hide
+ */
+ public void launchPendingIntent(
+ int displayId,
+ @NonNull PendingIntent pendingIntent,
+ @NonNull Executor executor,
+ @NonNull LaunchCallback launchCallback) {
+ try {
+ mVirtualDevice.launchPendingIntent(
+ displayId,
+ pendingIntent,
+ new ResultReceiver(new Handler(Looper.myLooper())) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ super.onReceiveResult(resultCode, resultData);
+ executor.execute(() -> {
+ if (resultCode == Activity.RESULT_OK) {
+ launchCallback.onLaunchSuccess();
+ } else {
+ launchCallback.onLaunchFailed();
+ }
+ });
+ }
+ });
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Creates a virtual display for this virtual device. All displays created on the same
* device belongs to the same display group.
*
@@ -273,6 +322,12 @@
}
}
+ /**
+ * Returns the display flags that should be added to a particular virtual display.
+ * Additional device-level flags from {@link
+ * com.android.server.companion.virtual.VirtualDeviceImpl#getBaseVirtualDisplayFlags()} will
+ * be added by DisplayManagerService.
+ */
private int getVirtualDisplayFlags(@DisplayFlags int flags) {
int virtualDisplayFlags = DEFAULT_VIRTUAL_DISPLAY_FLAGS;
if ((flags & DISPLAY_FLAG_TRUSTED) != 0) {
@@ -293,4 +348,22 @@
}
}
}
+
+ /**
+ * Callback for launching pending intents on the virtual device.
+ *
+ * @hide
+ */
+ // TODO(b/194949534): Unhide this API
+ public interface LaunchCallback {
+ /**
+ * Called when the pending intent launched successfully.
+ */
+ void onLaunchSuccess();
+
+ /**
+ * Called when the pending intent failed to launch.
+ */
+ void onLaunchFailed();
+ }
}
diff --git a/media/java/android/media/tv/interactive/TvIAppInfo.aidl b/core/java/android/companion/virtual/VirtualDeviceParams.aidl
similarity index 89%
copy from media/java/android/media/tv/interactive/TvIAppInfo.aidl
copy to core/java/android/companion/virtual/VirtualDeviceParams.aidl
index 6041460..9b3974a 100644
--- a/media/java/android/media/tv/interactive/TvIAppInfo.aidl
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.media.tv.interactive;
+package android.companion.virtual;
-parcelable TvIAppInfo;
\ No newline at end of file
+parcelable VirtualDeviceParams;
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
new file mode 100644
index 0000000..d61d474
--- /dev/null
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual;
+
+import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.util.ArraySet;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Params that can be configured when creating virtual devices.
+ *
+ * @hide
+ */
+// TODO(b/194949534): Unhide this API
+public final class VirtualDeviceParams implements Parcelable {
+
+ /** @hide */
+ @IntDef(prefix = "LOCK_STATE_",
+ value = {LOCK_STATE_ALWAYS_LOCKED, LOCK_STATE_ALWAYS_UNLOCKED})
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ public @interface LockState {}
+
+ /**
+ * Indicates that the lock state of the virtual device should be always locked.
+ *
+ * @hide // TODO(b/194949534): Unhide this API
+ */
+ public static final int LOCK_STATE_ALWAYS_LOCKED = 0;
+
+ /**
+ * Indicates that the lock state of the virtual device should be always unlocked.
+ *
+ * @hide // TODO(b/194949534): Unhide this API
+ */
+ public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1;
+
+ private final int mLockState;
+ private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
+
+ private VirtualDeviceParams(
+ @LockState int lockState,
+ @NonNull Set<UserHandle> usersWithMatchingAccounts) {
+ mLockState = lockState;
+ mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts);
+ }
+
+ @SuppressWarnings("unchecked")
+ private VirtualDeviceParams(Parcel parcel) {
+ mLockState = parcel.readInt();
+ mUsersWithMatchingAccounts = (ArraySet<UserHandle>) parcel.readArraySet(null);
+ }
+
+ /**
+ * Returns the lock state of the virtual device.
+ */
+ @LockState
+ public int getLockState() {
+ return mLockState;
+ }
+
+ /**
+ * Returns the user handles with matching managed accounts on the remote device to which
+ * this virtual device is streaming.
+ *
+ * @see android.app.admin.DevicePolicyManager#NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY
+ */
+ @NonNull
+ public Set<UserHandle> getUsersWithMatchingAccounts() {
+ return Collections.unmodifiableSet(mUsersWithMatchingAccounts);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mLockState);
+ dest.writeArraySet(mUsersWithMatchingAccounts);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof VirtualDeviceParams)) {
+ return false;
+ }
+ VirtualDeviceParams that = (VirtualDeviceParams) o;
+ return mLockState == that.mLockState && mUsersWithMatchingAccounts.equals(
+ that.mUsersWithMatchingAccounts);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mLockState, mUsersWithMatchingAccounts);
+ }
+
+ @Override
+ public String toString() {
+ return "VirtualDeviceParams("
+ + " mLockState=" + mLockState
+ + " mUsersWithMatchingAccounts=" + mUsersWithMatchingAccounts
+ + ")";
+ }
+
+ public static final Parcelable.Creator<VirtualDeviceParams> CREATOR =
+ new Parcelable.Creator<VirtualDeviceParams>() {
+ public VirtualDeviceParams createFromParcel(Parcel in) {
+ return new VirtualDeviceParams(in);
+ }
+
+ public VirtualDeviceParams[] newArray(int size) {
+ return new VirtualDeviceParams[size];
+ }
+ };
+
+ /**
+ * Builder for {@link VirtualDeviceParams}.
+ */
+ public static final class Builder {
+
+ private @LockState int mLockState = LOCK_STATE_ALWAYS_LOCKED;
+ private Set<UserHandle> mUsersWithMatchingAccounts;
+
+ /**
+ * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
+ * is required if this is set to {@link #LOCK_STATE_ALWAYS_UNLOCKED}.
+ * The default is {@link #LOCK_STATE_ALWAYS_LOCKED}.
+ *
+ * @param lockState The lock state, either {@link #LOCK_STATE_ALWAYS_LOCKED} or
+ * {@link #LOCK_STATE_ALWAYS_UNLOCKED}.
+ */
+ @RequiresPermission(value = ADD_ALWAYS_UNLOCKED_DISPLAY, conditional = true)
+ @NonNull
+ public Builder setLockState(@LockState int lockState) {
+ mLockState = lockState;
+ return this;
+ }
+
+ /**
+ * Sets the user handles with matching managed accounts on the remote device to which
+ * this virtual device is streaming.
+ *
+ * @param usersWithMatchingAccounts A set of user handles with matching managed
+ * accounts on the remote device this is streaming to.
+ * @see android.app.admin.DevicePolicyManager#NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY
+ */
+ public Builder setUsersWithMatchingAccounts(
+ @NonNull Set<UserHandle> usersWithMatchingAccounts) {
+ mUsersWithMatchingAccounts = usersWithMatchingAccounts;
+ return this;
+ }
+
+ /**
+ * Builds the {@link VirtualDeviceParams} instance.
+ */
+ @NonNull
+ public VirtualDeviceParams build() {
+ if (mUsersWithMatchingAccounts == null) {
+ mUsersWithMatchingAccounts = Collections.emptySet();
+ }
+ return new VirtualDeviceParams(mLockState, mUsersWithMatchingAccounts);
+ }
+ }
+}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 74c326d..58a7d87 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2974,6 +2974,9 @@
* Broadcast Action: A uid has been removed from the system. The uid
* number is stored in the extra data under {@link #EXTRA_UID}.
*
+ * In certain instances, {@link #EXTRA_REPLACING} is set to true if the UID is not being
+ * fully removed.
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index 48b634e..11b2ea1 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -180,6 +180,7 @@
* {@link #startMainActivity}, this can start any activity of the caller package, not just
* the main activity.
* The caller must have the {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}
+ * or {@link android.Manifest.permission#START_CROSS_PROFILE_ACTIVITIES}
* permission and both the caller and target user profiles must be in the same profile group.
*
* @param component The ComponentName of the activity to launch. It must be exported.
@@ -189,7 +190,9 @@
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.INTERACT_ACROSS_PROFILES,
+ android.Manifest.permission.START_CROSS_PROFILE_ACTIVITIES})
public void startActivity(@NonNull ComponentName component, @NonNull UserHandle targetUser) {
try {
mService.startActivityAsUser(mContext.getIApplicationThread(),
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a6d846b..d817f1e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2734,6 +2734,8 @@
* API shipped in Android 11.
* <li><code>202101</code>: corresponds to the features included in the Identity Credential
* API shipped in Android 12.
+ * <li><code>202201</code>: corresponds to the features included in the Identity Credential
+ * API shipped in Android 13.
* </ul>
*/
@SdkConstant(SdkConstantType.FEATURE)
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index d04c97c..a503d14 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -25,6 +25,9 @@
"path": "cts/hostsidetests/packagemanager"
},
{
+ "path": "cts/hostsidetests/os/test_mappings/packagemanager"
+ },
+ {
"path": "system/apex/tests"
}
],
@@ -128,6 +131,23 @@
"exclude-annotation": "org.junit.Ignore"
}
]
+ },
+ {
+ "name": "CtsAppSecurityHostTestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.Postsubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
}
],
"postsubmit": [
diff --git a/core/java/android/content/pm/parsing/component/ParsedApexSystemService.java b/core/java/android/content/pm/parsing/component/ParsedApexSystemService.java
index fe821e0..c89d3b2 100644
--- a/core/java/android/content/pm/parsing/component/ParsedApexSystemService.java
+++ b/core/java/android/content/pm/parsing/component/ParsedApexSystemService.java
@@ -35,4 +35,6 @@
@Nullable
String getMaxSdkVersion();
+ int getInitOrder();
+
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java b/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java
index 54196fd..65d26b9 100644
--- a/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java
+++ b/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java
@@ -45,10 +45,11 @@
@Nullable
private String maxSdkVersion;
+ private int initOrder;
+
public ParsedApexSystemServiceImpl() {
}
-
// Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
@@ -67,13 +68,15 @@
@NonNull String name,
@Nullable String jarPath,
@Nullable String minSdkVersion,
- @Nullable String maxSdkVersion) {
+ @Nullable String maxSdkVersion,
+ int initOrder) {
this.name = name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, name);
this.jarPath = jarPath;
this.minSdkVersion = minSdkVersion;
this.maxSdkVersion = maxSdkVersion;
+ this.initOrder = initOrder;
// onConstructed(); // You can define this method to get a callback
}
@@ -99,6 +102,11 @@
}
@DataClass.Generated.Member
+ public int getInitOrder() {
+ return initOrder;
+ }
+
+ @DataClass.Generated.Member
public @NonNull ParsedApexSystemServiceImpl setName(@NonNull String value) {
name = value;
com.android.internal.util.AnnotationValidations.validate(
@@ -125,6 +133,12 @@
}
@DataClass.Generated.Member
+ public @NonNull ParsedApexSystemServiceImpl setInitOrder( int value) {
+ initOrder = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
static Parcelling<String> sParcellingForName =
Parcelling.Cache.get(
Parcelling.BuiltIn.ForInternedString.class);
@@ -183,6 +197,7 @@
sParcellingForJarPath.parcel(jarPath, dest, flags);
sParcellingForMinSdkVersion.parcel(minSdkVersion, dest, flags);
sParcellingForMaxSdkVersion.parcel(maxSdkVersion, dest, flags);
+ dest.writeInt(initOrder);
}
@Override
@@ -201,6 +216,7 @@
String _jarPath = sParcellingForJarPath.unparcel(in);
String _minSdkVersion = sParcellingForMinSdkVersion.unparcel(in);
String _maxSdkVersion = sParcellingForMaxSdkVersion.unparcel(in);
+ int _initOrder = in.readInt();
this.name = _name;
com.android.internal.util.AnnotationValidations.validate(
@@ -208,6 +224,7 @@
this.jarPath = _jarPath;
this.minSdkVersion = _minSdkVersion;
this.maxSdkVersion = _maxSdkVersion;
+ this.initOrder = _initOrder;
// onConstructed(); // You can define this method to get a callback
}
@@ -227,10 +244,10 @@
};
@DataClass.Generated(
- time = 1638903241144L,
+ time = 1641307133386L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java",
- inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedApexSystemService]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
+ inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nprivate int initOrder\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedApexSystemService]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceUtils.java b/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceUtils.java
index 26abf48..eca8976 100644
--- a/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceUtils.java
@@ -53,10 +53,13 @@
R.styleable.AndroidManifestApexSystemService_minSdkVersion);
String maxSdkVersion = sa.getString(
R.styleable.AndroidManifestApexSystemService_maxSdkVersion);
+ int initOrder = sa.getInt(R.styleable.AndroidManifestApexSystemService_initOrder, 0);
systemService.setName(className)
.setMinSdkVersion(minSdkVersion)
- .setMaxSdkVersion(maxSdkVersion);
+ .setMaxSdkVersion(maxSdkVersion)
+ .setInitOrder(initOrder);
+
if (!TextUtils.isEmpty(jarPath)) {
systemService.setJarPath(jarPath);
}
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index 08f5a8a..0e42b02 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -22,6 +22,8 @@
import android.hardware.input.InputSensorInfo;
import android.os.Build;
+import java.util.UUID;
+
/**
* Class representing a sensor. Use {@link SensorManager#getSensorList} to get
* the list of available sensors. For more information about Android sensors,
@@ -925,6 +927,7 @@
@UnsupportedAppUsage
private int mFlags;
private int mId;
+ private UUID mUuid;
Sensor() {
}
@@ -951,6 +954,8 @@
this.mMaxDelay = sensorInfo.getMaxDelay();
this.mFlags = sensorInfo.getFlags();
this.mId = sensorInfo.getId();
+ // The UUID is never specified when creating a sensor from Input manager
+ this.mUuid = new UUID((long) this.mId, 0);
}
/**
@@ -1040,11 +1045,9 @@
}
/**
- * Do not use.
- *
- * This method throws an UnsupportedOperationException.
- *
- * Use getId() if you want a unique ID.
+ * Reserved for system and audio servers.
+ * When called from an unauthorized context, the UUID will contain the
+ * sensor ID in the MSB and 0 in the LSB.
*
* @see getId
*
@@ -1052,7 +1055,7 @@
*/
@SystemApi
public java.util.UUID getUuid() {
- throw new UnsupportedOperationException();
+ return mUuid;
}
/**
@@ -1286,17 +1289,24 @@
}
/**
- * Sets the ID associated with the sensor.
+ * Sets the UUID associated with the sensor.
*
- * The method name is misleading; while this ID is based on the UUID,
- * we do not pass in the actual UUID.
+ * NOTE: to be used only by native bindings in SensorManager.
+ *
+ * @see #getUuid
+ */
+ private void setUuid(long msb, long lsb) {
+ mUuid = new UUID(msb, lsb);
+ }
+
+ /**
+ * Sets the ID associated with the sensor.
*
* NOTE: to be used only by native bindings in SensorManager.
*
* @see #getId
*/
- private void setUuid(long msb, long lsb) {
- // TODO(b/29547335): Rename this method to setId.
- mId = (int) msb;
+ private void setId(int id) {
+ mId = id;
}
}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 6b5bec9..dc65bef 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -38,6 +38,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.security.identity.IdentityCredential;
+import android.security.identity.PresentationSession;
import android.security.keystore.KeyProperties;
import android.text.TextUtils;
import android.util.Log;
@@ -666,8 +667,8 @@
/**
* A wrapper class for the cryptographic operations supported by BiometricPrompt.
*
- * <p>Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac}, and
- * {@link IdentityCredential}.
+ * <p>Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac},
+ * {@link IdentityCredential}, and {@link PresentationSession}.
*
* <p>Cryptographic operations in Android can be split into two categories: auth-per-use and
* time-based. This is specified during key creation via the timeout parameter of the
@@ -697,10 +698,21 @@
super(mac);
}
+ /**
+ * Create from a {@link IdentityCredential} object.
+ *
+ * @param credential a {@link IdentityCredential} object.
+ * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}.
+ */
+ @Deprecated
public CryptoObject(@NonNull IdentityCredential credential) {
super(credential);
}
+ public CryptoObject(@NonNull PresentationSession session) {
+ super(session);
+ }
+
/**
* Get {@link Signature} object.
* @return {@link Signature} object or null if this doesn't contain one.
@@ -728,10 +740,20 @@
/**
* Get {@link IdentityCredential} object.
* @return {@link IdentityCredential} object or null if this doesn't contain one.
+ * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}.
*/
+ @Deprecated
public @Nullable IdentityCredential getIdentityCredential() {
return super.getIdentityCredential();
}
+
+ /**
+ * Get {@link PresentationSession} object.
+ * @return {@link PresentationSession} object or null if this doesn't contain one.
+ */
+ public @Nullable PresentationSession getPresentationSession() {
+ return super.getPresentationSession();
+ }
}
/**
diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java
index 7648cf2..d415706 100644
--- a/core/java/android/hardware/biometrics/CryptoObject.java
+++ b/core/java/android/hardware/biometrics/CryptoObject.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.security.identity.IdentityCredential;
+import android.security.identity.PresentationSession;
import android.security.keystore2.AndroidKeyStoreProvider;
import java.security.Signature;
@@ -27,8 +28,8 @@
/**
* A wrapper class for the crypto objects supported by BiometricPrompt and FingerprintManager.
- * Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac} and
- * {@link IdentityCredential} objects.
+ * Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac},
+ * {@link IdentityCredential}, and {@link PresentationSession} objects.
* @hide
*/
public class CryptoObject {
@@ -46,10 +47,21 @@
mCrypto = mac;
}
+ /**
+ * Create from a {@link IdentityCredential} object.
+ *
+ * @param credential a {@link IdentityCredential} object.
+ * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}.
+ */
+ @Deprecated
public CryptoObject(@NonNull IdentityCredential credential) {
mCrypto = credential;
}
+ public CryptoObject(@NonNull PresentationSession session) {
+ mCrypto = session;
+ }
+
/**
* Get {@link Signature} object.
* @return {@link Signature} object or null if this doesn't contain one.
@@ -77,12 +89,22 @@
/**
* Get {@link IdentityCredential} object.
* @return {@link IdentityCredential} object or null if this doesn't contain one.
+ * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}.
*/
+ @Deprecated
public IdentityCredential getIdentityCredential() {
return mCrypto instanceof IdentityCredential ? (IdentityCredential) mCrypto : null;
}
/**
+ * Get {@link PresentationSession} object.
+ * @return {@link PresentationSession} object or null if this doesn't contain one.
+ */
+ public PresentationSession getPresentationSession() {
+ return mCrypto instanceof PresentationSession ? (PresentationSession) mCrypto : null;
+ }
+
+ /**
* @hide
* @return the opId associated with this object or 0 if none
*/
@@ -91,6 +113,8 @@
return 0;
} else if (mCrypto instanceof IdentityCredential) {
return ((IdentityCredential) mCrypto).getCredstoreOperationHandle();
+ } else if (mCrypto instanceof PresentationSession) {
+ return ((PresentationSession) mCrypto).getCredstoreOperationHandle();
}
return AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto);
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index de5c9ad..89ac8bf 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1132,41 +1132,48 @@
}
/**
- * Sets the default display mode, according to the refresh rate and the resolution chosen by the
- * user.
+ * Sets the global default {@link Display.Mode}. The display mode includes preference for
+ * resolution and refresh rate. The mode change is applied globally, i.e. to all the connected
+ * displays. If the mode specified is not supported by a connected display, then no mode change
+ * occurs for that display.
*
+ * @param mode The {@link Display.Mode} to set, which can include resolution and/or
+ * refresh-rate. It is created using {@link Display.Mode.Builder}.
+ *`
* @hide
*/
@TestApi
@RequiresPermission(Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE)
- public void setUserPreferredDisplayMode(@NonNull Display.Mode mode) {
+ public void setGlobalUserPreferredDisplayMode(@NonNull Display.Mode mode) {
// Create a new object containing default values for the unused fields like mode ID and
// alternative refresh rates.
Display.Mode preferredMode = new Display.Mode(mode.getPhysicalWidth(),
mode.getPhysicalHeight(), mode.getRefreshRate());
- mGlobal.setUserPreferredDisplayMode(preferredMode);
+ mGlobal.setUserPreferredDisplayMode(Display.INVALID_DISPLAY, preferredMode);
}
/**
- * Removes the user preferred display mode.
+ * Removes the global user preferred display mode.
+ * User preferred display mode is cleared for all the connected displays.
*
* @hide
*/
@TestApi
@RequiresPermission(Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE)
- public void clearUserPreferredDisplayMode() {
- mGlobal.setUserPreferredDisplayMode(null);
+ public void clearGlobalUserPreferredDisplayMode() {
+ mGlobal.setUserPreferredDisplayMode(Display.INVALID_DISPLAY, null);
}
/**
- * Returns the user preferred display mode.
+ * Returns the global user preferred display mode.
+ * If no user preferred mode has been set, or it has been cleared, this method returns null.
*
* @hide
*/
@TestApi
@Nullable
- public Display.Mode getUserPreferredDisplayMode() {
- return mGlobal.getUserPreferredDisplayMode();
+ public Display.Mode getGlobalUserPreferredDisplayMode() {
+ return mGlobal.getUserPreferredDisplayMode(Display.INVALID_DISPLAY);
}
/**
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index bf6e665..1a7a63ae 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -896,9 +896,9 @@
* Sets the default display mode, according to the refresh rate and the resolution chosen by the
* user.
*/
- public void setUserPreferredDisplayMode(Display.Mode mode) {
+ public void setUserPreferredDisplayMode(int displayId, Display.Mode mode) {
try {
- mDm.setUserPreferredDisplayMode(mode);
+ mDm.setUserPreferredDisplayMode(displayId, mode);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
@@ -907,9 +907,9 @@
/**
* Returns the user preferred display mode.
*/
- public Display.Mode getUserPreferredDisplayMode() {
+ public Display.Mode getUserPreferredDisplayMode(int displayId) {
try {
- return mDm.getUserPreferredDisplayMode();
+ return mDm.getUserPreferredDisplayMode(displayId);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index d38d388..35663af 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -166,8 +166,8 @@
// Sets the user preferred display mode.
// Requires MODIFY_USER_PREFERRED_DISPLAY_MODE permission.
- void setUserPreferredDisplayMode(in Mode mode);
- Mode getUserPreferredDisplayMode();
+ void setUserPreferredDisplayMode(int displayId, in Mode mode);
+ Mode getUserPreferredDisplayMode(int displayId);
// When enabled the app requested display resolution and refresh rate is always selected
// in DisplayModeDirector regardless of user settings and policies for low brightness, low
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 56f8142..b970559 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -306,22 +306,21 @@
throw new IllegalArgumentException("Must supply an enrollment callback");
}
- if (cancel != null) {
- if (cancel.isCanceled()) {
- Slog.w(TAG, "enrollment already canceled");
- return;
- } else {
- cancel.setOnCancelListener(new OnEnrollCancelListener());
- }
+ if (cancel != null && cancel.isCanceled()) {
+ Slog.w(TAG, "enrollment already canceled");
+ return;
}
if (mService != null) {
try {
mEnrollmentCallback = callback;
Trace.beginSection("FaceManager#enroll");
- mService.enroll(userId, mToken, hardwareAuthToken, mServiceReceiver,
- mContext.getOpPackageName(), disabledFeatures, previewSurface,
- debugConsent);
+ final long enrollId = mService.enroll(userId, mToken, hardwareAuthToken,
+ mServiceReceiver, mContext.getOpPackageName(), disabledFeatures,
+ previewSurface, debugConsent);
+ if (cancel != null) {
+ cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
+ }
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception in enroll: ", e);
// Though this may not be a hardware issue, it will cause apps to give up or
@@ -359,21 +358,20 @@
throw new IllegalArgumentException("Must supply an enrollment callback");
}
- if (cancel != null) {
- if (cancel.isCanceled()) {
- Slog.w(TAG, "enrollRemotely is already canceled.");
- return;
- } else {
- cancel.setOnCancelListener(new OnEnrollCancelListener());
- }
+ if (cancel != null && cancel.isCanceled()) {
+ Slog.w(TAG, "enrollRemotely is already canceled.");
+ return;
}
if (mService != null) {
try {
mEnrollmentCallback = callback;
Trace.beginSection("FaceManager#enrollRemotely");
- mService.enrollRemotely(userId, mToken, hardwareAuthToken, mServiceReceiver,
- mContext.getOpPackageName(), disabledFeatures);
+ final long enrolId = mService.enrollRemotely(userId, mToken, hardwareAuthToken,
+ mServiceReceiver, mContext.getOpPackageName(), disabledFeatures);
+ if (cancel != null) {
+ cancel.setOnCancelListener(new OnEnrollCancelListener(enrolId));
+ }
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception in enrollRemotely: ", e);
// Though this may not be a hardware issue, it will cause apps to give up or
@@ -713,10 +711,10 @@
}
}
- private void cancelEnrollment() {
+ private void cancelEnrollment(long requestId) {
if (mService != null) {
try {
- mService.cancelEnrollment(mToken);
+ mService.cancelEnrollment(mToken, requestId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1100,9 +1098,16 @@
}
private class OnEnrollCancelListener implements OnCancelListener {
+ private final long mAuthRequestId;
+
+ private OnEnrollCancelListener(long id) {
+ mAuthRequestId = id;
+ }
+
@Override
public void onCancel() {
- cancelEnrollment();
+ Slog.d(TAG, "Cancel face enrollment requested for: " + mAuthRequestId);
+ cancelEnrollment(mAuthRequestId);
}
}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index e919824..989b001 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -76,15 +76,16 @@
void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
// Start face enrollment
- void enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
- String opPackageName, in int [] disabledFeatures, in Surface previewSurface, boolean debugConsent);
+ long enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
+ String opPackageName, in int [] disabledFeatures,
+ in Surface previewSurface, boolean debugConsent);
// Start remote face enrollment
- void enrollRemotely(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
+ long enrollRemotely(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
String opPackageName, in int [] disabledFeatures);
// Cancel enrollment in progress
- void cancelEnrollment(IBinder token);
+ void cancelEnrollment(IBinder token, long requestId);
// Removes the specified face enrollment for the specified userId.
void remove(IBinder token, int faceId, int userId, IFaceServiceReceiver receiver,
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index fe04e5d..7e070bc 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -58,6 +58,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.security.identity.IdentityCredential;
+import android.security.identity.PresentationSession;
import android.util.Slog;
import android.view.Surface;
@@ -183,9 +184,16 @@
}
private class OnEnrollCancelListener implements OnCancelListener {
+ private final long mAuthRequestId;
+
+ private OnEnrollCancelListener(long id) {
+ mAuthRequestId = id;
+ }
+
@Override
public void onCancel() {
- cancelEnrollment();
+ Slog.d(TAG, "Cancel fingerprint enrollment requested for: " + mAuthRequestId);
+ cancelEnrollment(mAuthRequestId);
}
}
@@ -264,10 +272,21 @@
* Get {@link IdentityCredential} object.
* @return {@link IdentityCredential} object or null if this doesn't contain one.
* @hide
+ * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}.
*/
+ @Deprecated
public IdentityCredential getIdentityCredential() {
return super.getIdentityCredential();
}
+
+ /**
+ * Get {@link PresentationSession} object.
+ * @return {@link PresentationSession} object or null if this doesn't contain one.
+ * @hide
+ */
+ public PresentationSession getPresentationSession() {
+ return super.getPresentationSession();
+ }
}
/**
@@ -646,20 +665,19 @@
throw new IllegalArgumentException("Must supply an enrollment callback");
}
- if (cancel != null) {
- if (cancel.isCanceled()) {
- Slog.w(TAG, "enrollment already canceled");
- return;
- } else {
- cancel.setOnCancelListener(new OnEnrollCancelListener());
- }
+ if (cancel != null && cancel.isCanceled()) {
+ Slog.w(TAG, "enrollment already canceled");
+ return;
}
if (mService != null) {
try {
mEnrollmentCallback = callback;
- mService.enroll(mToken, hardwareAuthToken, userId, mServiceReceiver,
- mContext.getOpPackageName(), enrollReason);
+ final long enrollId = mService.enroll(mToken, hardwareAuthToken, userId,
+ mServiceReceiver, mContext.getOpPackageName(), enrollReason);
+ if (cancel != null) {
+ cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
+ }
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception in enroll: ", e);
// Though this may not be a hardware issue, it will cause apps to give up or try
@@ -1302,9 +1320,9 @@
return allSensors.isEmpty() ? null : allSensors.get(0);
}
- private void cancelEnrollment() {
+ private void cancelEnrollment(long requestId) {
if (mService != null) try {
- mService.cancelEnrollment(mToken);
+ mService.cancelEnrollment(mToken, requestId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index ba1dc6d..cbff8b1 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -84,11 +84,11 @@
void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
// Start fingerprint enrollment
- void enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver,
+ long enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver,
String opPackageName, int enrollReason);
// Cancel enrollment in progress
- void cancelEnrollment(IBinder token);
+ void cancelEnrollment(IBinder token, long requestId);
// Any errors resulting from this call will be returned to the listener
void remove(IBinder token, int fingerId, int userId, IFingerprintServiceReceiver receiver,
diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java
index 9468ca2..54060cc 100644
--- a/core/java/android/hardware/location/ContextHubClient.java
+++ b/core/java/android/hardware/location/ContextHubClient.java
@@ -15,6 +15,7 @@
*/
package android.hardware.location;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
@@ -118,13 +119,14 @@
* is newly generated (e.g. any regeneration of a callback client, or generation
* of a non-equal PendingIntent client), the ID will not be the same.
*
- * @return The ID of this ContextHubClient.
+ * @return The ID of this ContextHubClient, in the range [0, 65535].
*/
+ @IntRange(from = 0, to = 65535)
public int getId() {
if (mId == null) {
throw new IllegalStateException("ID was not set");
}
- return mId;
+ return (0x0000FFFF & mId);
}
/**
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index e30594f..6f15588 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -18,6 +18,7 @@
import android.annotation.BinderThread;
import android.annotation.MainThread;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -29,6 +30,7 @@
import android.os.ResultReceiver;
import android.util.Log;
import android.view.InputChannel;
+import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
@@ -50,6 +52,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -74,6 +77,8 @@
private static final int DO_HIDE_SOFT_INPUT = 70;
private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80;
private static final int DO_CREATE_INLINE_SUGGESTIONS_REQUEST = 90;
+ private static final int DO_CAN_START_STYLUS_HANDWRITING = 100;
+ private static final int DO_START_STYLUS_HANDWRITING = 110;
final WeakReference<InputMethodServiceInternal> mTarget;
final Context mContext;
@@ -169,7 +174,8 @@
SomeArgs args = (SomeArgs) msg.obj;
try {
inputMethod.initializeInternal((IBinder) args.arg1,
- (IInputMethodPrivilegedOperations) args.arg2, msg.arg1);
+ (IInputMethodPrivilegedOperations) args.arg2, msg.arg1,
+ (boolean) args.arg3);
} finally {
args.recycle();
}
@@ -229,13 +235,25 @@
case DO_CHANGE_INPUTMETHOD_SUBTYPE:
inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj);
return;
- case DO_CREATE_INLINE_SUGGESTIONS_REQUEST:
+ case DO_CREATE_INLINE_SUGGESTIONS_REQUEST: {
final SomeArgs args = (SomeArgs) msg.obj;
inputMethod.onCreateInlineSuggestionsRequest(
(InlineSuggestionsRequestInfo) args.arg1,
(IInlineSuggestionsRequestCallback) args.arg2);
args.recycle();
return;
+ }
+ case DO_CAN_START_STYLUS_HANDWRITING: {
+ inputMethod.canStartStylusHandwriting(msg.arg1);
+ return;
+ }
+ case DO_START_STYLUS_HANDWRITING: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ inputMethod.startStylusHandwriting((InputChannel) args.arg1,
+ (List<MotionEvent>) args.arg2);
+ args.recycle();
+ return;
+ }
}
Log.w(TAG, "Unhandled message code: " + msg.what);
@@ -272,9 +290,10 @@
@BinderThread
@Override
public void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
- int configChanges) {
+ int configChanges, boolean stylusHwSupported) {
mCaller.executeOrSendMessage(
- mCaller.obtainMessageIOO(DO_INITIALIZE_INTERNAL, configChanges, token, privOps));
+ mCaller.obtainMessageIOOO(
+ DO_INITIALIZE_INTERNAL, configChanges, token, privOps, stylusHwSupported));
}
@BinderThread
@@ -383,4 +402,21 @@
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE,
subtype));
}
+
+ @BinderThread
+ @Override
+ public void canStartStylusHandwriting(int requestId)
+ throws RemoteException {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageI(DO_CAN_START_STYLUS_HANDWRITING, requestId));
+ }
+
+ @BinderThread
+ @Override
+ public void startStylusHandwriting(@NonNull InputChannel channel,
+ @Nullable List<MotionEvent> stylusEvents)
+ throws RemoteException {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOO(DO_START_STYLUS_HANDWRITING, channel, stylusEvents));
+ }
}
diff --git a/core/java/android/inputmethodservice/InkWindow.java b/core/java/android/inputmethodservice/InkWindow.java
new file mode 100644
index 0000000..e11d635
--- /dev/null
+++ b/core/java/android/inputmethodservice/InkWindow.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.inputmethodservice;
+
+import static android.view.WindowManager.LayoutParams;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.IBinder;
+import android.util.Slog;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.internal.policy.PhoneWindow;
+
+/**
+ * Window of type {@code LayoutParams.TYPE_INPUT_METHOD_DIALOG} for drawing
+ * Handwriting Ink on screen.
+ * @hide
+ */
+final class InkWindow extends PhoneWindow {
+
+ private final WindowManager mWindowManager;
+
+ public InkWindow(@NonNull Context context) {
+ super(context);
+
+ setType(LayoutParams.TYPE_INPUT_METHOD);
+ final LayoutParams attrs = getAttributes();
+ attrs.layoutInDisplayCutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ attrs.setFitInsetsTypes(0);
+ setAttributes(attrs);
+ // Ink window is not touchable with finger.
+ addFlags(FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_NO_LIMITS | FLAG_NOT_TOUCHABLE
+ | FLAG_NOT_FOCUSABLE);
+ setBackgroundDrawableResource(android.R.color.transparent);
+ setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ mWindowManager = context.getSystemService(WindowManager.class);
+ }
+
+ /**
+ * Method to show InkWindow on screen.
+ * Emulates internal behavior similar to Dialog.show().
+ */
+ void show() {
+ if (getDecorView() == null) {
+ Slog.i(InputMethodService.TAG, "DecorView is not set for InkWindow. show() failed.");
+ return;
+ }
+ getDecorView().setVisibility(View.VISIBLE);
+ mWindowManager.addView(getDecorView(), getAttributes());
+ }
+
+ /**
+ * Method to hide InkWindow from screen.
+ * Emulates internal behavior similar to Dialog.hide().
+ * @param remove set {@code true} to remove InkWindow surface completely.
+ */
+ void hide(boolean remove) {
+ if (getDecorView() != null) {
+ getDecorView().setVisibility(remove ? View.GONE : View.INVISIBLE);
+ }
+ }
+
+ void setToken(@NonNull IBinder token) {
+ WindowManager.LayoutParams lp = getAttributes();
+ lp.token = token;
+ setAttributes(lp);
+ }
+}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index afcb6fc..09d5085 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -96,6 +96,7 @@
import android.util.Printer;
import android.util.proto.ProtoOutputStream;
import android.view.Gravity;
+import android.view.InputChannel;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -121,6 +122,7 @@
import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodServiceTraceProto;
+import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import android.widget.FrameLayout;
@@ -144,6 +146,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
/**
@@ -560,11 +563,15 @@
private boolean mAutomotiveHideNavBarForKeyboard;
private boolean mIsAutomotive;
+ private boolean mHandwritingStarted;
private Handler mHandler;
private boolean mImeSurfaceScheduledForRemoval;
private ImsConfigurationTracker mConfigTracker = new ImsConfigurationTracker();
private boolean mDestroyed;
+ /** Stylus handwriting Ink window. */
+ private InkWindow mInkWindow;
+
/**
* An opaque {@link Binder} token of window requesting {@link InputMethodImpl#showSoftInput}
* The original app window token is passed from client app window.
@@ -639,7 +646,8 @@
@MainThread
@Override
public final void initializeInternal(@NonNull IBinder token,
- IInputMethodPrivilegedOperations privilegedOperations, int configChanges) {
+ IInputMethodPrivilegedOperations privilegedOperations, int configChanges,
+ boolean stylusHwSupported) {
if (mDestroyed) {
Log.i(TAG, "The InputMethodService has already onDestroyed()."
+ "Ignore the initialization.");
@@ -649,6 +657,9 @@
mConfigTracker.onInitialize(configChanges);
mPrivOps.set(privilegedOperations);
InputMethodPrivilegedOperationsRegistry.put(token, mPrivOps);
+ if (stylusHwSupported) {
+ mInkWindow = new InkWindow(mWindow.getContext());
+ }
attachToken(token);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@@ -681,6 +692,9 @@
attachToWindowToken(token);
mToken = token;
mWindow.setToken(token);
+ if (mInkWindow != null) {
+ mInkWindow.setToken(token);
+ }
}
/**
@@ -864,6 +878,49 @@
/**
* {@inheritDoc}
+ * @hide
+ */
+ @Override
+ public void canStartStylusHandwriting(int requestId) {
+ if (DEBUG) Log.v(TAG, "canStartStylusHandwriting()");
+ if (mHandwritingStarted) {
+ Log.d(TAG, "There is an ongoing Handwriting session. ignoring.");
+ return;
+ }
+ if (!mInputStarted) {
+ Log.d(TAG, "Input should have started before starting Stylus handwriting.");
+ return;
+ }
+ if (onStartStylusHandwriting()) {
+ mPrivOps.onStylusHandwritingReady(requestId);
+ } else {
+ Log.i(TAG, "IME is not ready. Can't start Stylus Handwriting");
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @MainThread
+ @Override
+ public void startStylusHandwriting(
+ @NonNull InputChannel channel, @Nullable List<MotionEvent> stylusEvents) {
+ if (DEBUG) Log.v(TAG, "startStylusHandwriting()");
+ if (mHandwritingStarted) {
+ return;
+ }
+
+ mHandwritingStarted = true;
+ mShowInputRequested = false;
+
+ mInkWindow.show();
+ // TODO: deliver previous @param stylusEvents
+ // TODO: create spy receiver for @param channel
+ }
+
+ /**
+ * {@inheritDoc}
*/
@MainThread
@Override
@@ -2233,6 +2290,77 @@
}
/**
+ * Called when an app requests stylus handwriting
+ * {@link InputMethodManager#startStylusHandwriting(View)}.
+ *
+ * This will always be preceded by {@link #onStartInput(EditorInfo, boolean)} for the
+ * {@link EditorInfo} and {@link InputConnection} for which stylus handwriting is being
+ * requested.
+ *
+ * If the IME supports handwriting for the current input, it should return {@code true},
+ * ensure its inking views are attached to the {@link #getStylusHandwritingWindow()}, and handle
+ * stylus input received on the ink window via {@link #getCurrentInputConnection()}.
+ * @return {@code true} if IME can honor the request, {@code false} if IME cannot at this time.
+ */
+ public boolean onStartStylusHandwriting() {
+ // Intentionally empty
+ return false;
+ }
+
+ /**
+ * Called when the current stylus handwriting session was finished (either by the system or
+ * via {@link #finishStylusHandwriting()}.
+ *
+ * When this is called, the ink window has been made invisible, and the IME no longer
+ * intercepts handwriting-related {@code MotionEvent}s.
+ */
+ public void onFinishStylusHandwriting() {
+ // Intentionally empty
+ }
+
+ /**
+ * Returns the stylus handwriting inking window.
+ * IMEs supporting stylus input are expected to attach their inking views to this
+ * window (e.g. with {@link Window#setContentView(View)} )). Handwriting-related
+ * {@link MotionEvent}s are dispatched to the attached view hierarchy.
+ *
+ * Note: This returns {@code null} if IME doesn't support stylus handwriting
+ * i.e. if {@link InputMethodInfo#supportsStylusHandwriting()} is false.
+ * This method should be called after {@link #onStartStylusHandwriting()}.
+ * @see #onStartStylusHandwriting()
+ */
+ @Nullable
+ public final Window getStylusHandwritingWindow() {
+ return mInkWindow;
+ }
+
+ /**
+ * Finish the current stylus handwriting session.
+ *
+ * This dismisses the {@link #getStylusHandwritingWindow ink window} and stops intercepting
+ * stylus {@code MotionEvent}s.
+ *
+ * Note for IME developers: Call this method at any time to finish current handwriting session.
+ * Generally, this should be invoked after a short timeout, giving the user enough time
+ * to start the next stylus stroke, if any.
+ *
+ * Handwriting session will be finished by framework on next {@link #onFinishInput()}.
+ */
+ public final void finishStylusHandwriting() {
+ if (DEBUG) Log.v(TAG, "finishStylusHandwriting()");
+ if (mInkWindow == null) {
+ return;
+ }
+ if (!mHandwritingStarted) {
+ return;
+ }
+
+ mHandwritingStarted = false;
+ mInkWindow.hide(false /* remove */);
+ onFinishStylusHandwriting();
+ }
+
+ /**
* The system has decided that it may be time to show your input method.
* This is called due to a corresponding call to your
* {@link InputMethod#showSoftInput InputMethod.showSoftInput()}
@@ -2501,6 +2629,9 @@
mInputStarted = false;
mStartedInputConnection = null;
mCurCompletions = null;
+ if (mInkWindow != null) {
+ finishStylusHandwriting();
+ }
}
void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index f50aa99..147138e 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -69,6 +69,8 @@
int getMultipathPreference(in Network network);
+ SubscriptionPlan getSubscriptionPlan(in NetworkTemplate template);
+ void onStatsProviderWarningOrLimitReached();
SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage);
void setSubscriptionPlans(int subId, in SubscriptionPlan[] plans, String callingPackage);
String getSubscriptionPlansOwner(int subId);
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 7ebb646..426fc61 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -535,6 +535,46 @@
}
/**
+ * Get subscription plan for the given networkTemplate.
+ *
+ * @param template the networkTemplate to get the subscription plan for.
+ * @return the active {@link SubscriptionPlan} for the given template, or
+ * {@code null} if not found.
+ * @hide
+ */
+ @Nullable
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK})
+ // @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public SubscriptionPlan getSubscriptionPlan(@NonNull NetworkTemplate template) {
+ try {
+ return mService.getSubscriptionPlan(template);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Notifies that the specified {@link NetworkStatsProvider} has reached its quota
+ * which was set through {@link NetworkStatsProvider#onSetLimit(String, long)} or
+ * {@link NetworkStatsProvider#onSetWarningAndLimit(String, long, long)}.
+ *
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK})
+ // @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public void onStatsProviderWarningOrLimitReached() {
+ try {
+ mService.onStatsProviderWarningOrLimitReached();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Resets network policy settings back to factory defaults.
*
* @hide
diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
index 5c28553..3193826 100644
--- a/core/java/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -96,6 +96,141 @@
*/
public static final String NOTIFICATION_CHANNEL_VPN = "VPN";
+ /**
+ * Action sent in the intent when an error occurred.
+ *
+ * @hide
+ */
+ public static final String ACTION_VPN_MANAGER_ERROR = "android.net.action.VPN_MANAGER_ERROR";
+
+ /**
+ * An IKE protocol error. Codes are the codes from IkeProtocolException, RFC 7296.
+ *
+ * @hide
+ */
+ public static final String CATEGORY_ERROR_IKE = "android.net.category.ERROR_IKE";
+
+ /**
+ * User deactivated the VPN, either by turning it off or selecting a different VPN provider.
+ * The error code is always 0.
+ *
+ * @hide
+ */
+ public static final String CATEGORY_ERROR_USER_DEACTIVATED =
+ "android.net.category.ERROR_USER_DEACTIVATED";
+
+ /**
+ * Network error. Error codes are ERROR_CODE_NETWORK_*.
+ *
+ * @hide
+ */
+ public static final String CATEGORY_ERROR_NETWORK = "android.net.category.ERROR_NETWORK";
+
+ /**
+ * The key of the session that experienced this error, as returned by
+ * startProvisionedVpnProfileSession.
+ *
+ * @hide
+ */
+ public static final String EXTRA_SESSION_KEY = "android.net.extra.SESSION_KEY";
+
+ /**
+ * Extra for the Network object that was the underlying network at the time of the failure, or
+ * null if none.
+ *
+ * @hide
+ */
+ public static final String EXTRA_UNDERLYING_NETWORK = "android.net.extra.UNDERLYING_NETWORK";
+
+ /**
+ * The NetworkCapabilities of the underlying network.
+ *
+ * @hide
+ */
+ public static final String EXTRA_UNDERLYING_NETWORK_CAPABILITIES =
+ "android.net.extra.UNDERLYING_NETWORK_CAPABILITIES";
+
+ /**
+ * The LinkProperties of the underlying network.
+ *
+ * @hide
+ */
+ public static final String EXTRA_UNDERLYING_LINK_PROPERTIES =
+ "android.net.extra.UNDERLYING_LINK_PROPERTIES";
+
+ /**
+ * A long timestamp with SystemClock.elapsedRealtime base for when the event happened.
+ *
+ * @hide
+ */
+ public static final String EXTRA_TIMESTAMP = "android.net.extra.TIMESTAMP";
+
+ /**
+ * Extra for the error type. This is ERROR_NOT_RECOVERABLE or ERROR_RECOVERABLE.
+ *
+ * @hide
+ */
+ public static final String EXTRA_ERROR_TYPE = "android.net.extra.ERROR_TYPE";
+
+ /**
+ * Extra for the error code. The value will be 0 for CATEGORY_ERROR_USER_DEACTIVATED, one of
+ * ERROR_CODE_NETWORK_* for ERROR_CATEGORY_NETWORK or one of values defined in
+ * IkeProtocolException#ErrorType for CATEGORY_ERROR_IKE.
+ *
+ * @hide
+ */
+ public static final String EXTRA_ERROR_CODE = "android.net.extra.ERROR_CODE";
+
+ /**
+ * This error is fatal, e.g. the VPN was disabled or configuration error. The stack will not
+ * retry connection.
+ *
+ * @hide
+ */
+ public static final int ERROR_NOT_RECOVERABLE = 1;
+
+ /**
+ * The stack experienced an error but will retry with exponential backoff, e.g. network timeout.
+ *
+ * @hide
+ */
+ public static final int ERROR_RECOVERABLE = 2;
+
+ /**
+ * An error code to indicate that there was an UnknownHostException.
+ *
+ * @hide
+ */
+ public static final int ERROR_CODE_NETWORK_UNKNOWN_HOST = 0;
+
+ /**
+ * An error code to indicate that there is a SocketTimeoutException.
+ *
+ * @hide
+ */
+ public static final int ERROR_CODE_NETWORK_TIMEOUT = 1;
+
+ /**
+ * An error code to indicate that the connection is refused.
+ *
+ * @hide
+ */
+ public static final int ERROR_CODE_NETWORK_CONNECT = 2;
+
+ /**
+ * An error code to indicate the connection was reset. (e.g. SocketException)
+ *
+ * @hide
+ */
+ public static final int ERROR_CODE_NETWORK_CONNECTION_RESET = 3;
+
+ /**
+ * An error code to indicate that there is an IOException.
+ *
+ * @hide
+ */
+ public static final int ERROR_CODE_NETWORK_IO = 4;
+
/** @hide */
@IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY,
TYPE_VPN_OEM})
diff --git a/core/java/android/net/annotations/PolicyDirection.java b/core/java/android/net/annotations/PolicyDirection.java
index febd9b4..3f7521a 100644
--- a/core/java/android/net/annotations/PolicyDirection.java
+++ b/core/java/android/net/annotations/PolicyDirection.java
@@ -24,10 +24,6 @@
/**
* IPsec traffic direction.
- *
- * <p>Mainline modules cannot reference hidden @IntDef. Moving this annotation to a separate class
- * to allow others to statically include it.
- *
* @hide
*/
@IntDef(value = {IpSecManager.DIRECTION_IN, IpSecManager.DIRECTION_OUT})
diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
index 125b573..69e6313 100644
--- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
@@ -63,13 +63,22 @@
private final int mOpportunisticMatchCriteria;
private VcnCellUnderlyingNetworkTemplate(
- int networkQuality,
int meteredMatchCriteria,
+ int minEntryUpstreamBandwidthKbps,
+ int minExitUpstreamBandwidthKbps,
+ int minEntryDownstreamBandwidthKbps,
+ int minExitDownstreamBandwidthKbps,
Set<String> allowedNetworkPlmnIds,
Set<Integer> allowedSpecificCarrierIds,
int roamingMatchCriteria,
int opportunisticMatchCriteria) {
- super(NETWORK_PRIORITY_TYPE_CELL, networkQuality, meteredMatchCriteria);
+ super(
+ NETWORK_PRIORITY_TYPE_CELL,
+ meteredMatchCriteria,
+ minEntryUpstreamBandwidthKbps,
+ minExitUpstreamBandwidthKbps,
+ minEntryDownstreamBandwidthKbps,
+ minExitDownstreamBandwidthKbps);
mAllowedNetworkPlmnIds = new ArraySet<>(allowedNetworkPlmnIds);
mAllowedSpecificCarrierIds = new ArraySet<>(allowedSpecificCarrierIds);
mRoamingMatchCriteria = roamingMatchCriteria;
@@ -109,9 +118,17 @@
@NonNull PersistableBundle in) {
Objects.requireNonNull(in, "PersistableBundle is null");
- final int networkQuality = in.getInt(NETWORK_QUALITY_KEY);
final int meteredMatchCriteria = in.getInt(METERED_MATCH_KEY);
+ final int minEntryUpstreamBandwidthKbps =
+ in.getInt(MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+ final int minExitUpstreamBandwidthKbps =
+ in.getInt(MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+ final int minEntryDownstreamBandwidthKbps =
+ in.getInt(MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+ final int minExitDownstreamBandwidthKbps =
+ in.getInt(MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+
final PersistableBundle plmnIdsBundle =
in.getPersistableBundle(ALLOWED_NETWORK_PLMN_IDS_KEY);
Objects.requireNonNull(plmnIdsBundle, "plmnIdsBundle is null");
@@ -131,8 +148,11 @@
final int opportunisticMatchCriteria = in.getInt(OPPORTUNISTIC_MATCH_KEY);
return new VcnCellUnderlyingNetworkTemplate(
- networkQuality,
meteredMatchCriteria,
+ minEntryUpstreamBandwidthKbps,
+ minExitUpstreamBandwidthKbps,
+ minEntryDownstreamBandwidthKbps,
+ minExitDownstreamBandwidthKbps,
allowedNetworkPlmnIds,
allowedSpecificCarrierIds,
roamingMatchCriteria,
@@ -243,7 +263,6 @@
/** This class is used to incrementally build VcnCellUnderlyingNetworkTemplate objects. */
public static final class Builder {
- private int mNetworkQuality = NETWORK_QUALITY_ANY;
private int mMeteredMatchCriteria = MATCH_ANY;
@NonNull private final Set<String> mAllowedNetworkPlmnIds = new ArraySet<>();
@@ -252,29 +271,15 @@
private int mRoamingMatchCriteria = MATCH_ANY;
private int mOpportunisticMatchCriteria = MATCH_ANY;
+ private int mMinEntryUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+ private int mMinExitUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+ private int mMinEntryDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+ private int mMinExitDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+
/** Construct a Builder object. */
public Builder() {}
/**
- * Set the required network quality to match this template.
- *
- * <p>Network quality is a aggregation of multiple signals that reflect the network link
- * metrics. For example, the network validation bit (see {@link
- * NetworkCapabilities#NET_CAPABILITY_VALIDATED}), estimated first hop transport bandwidth
- * and signal strength.
- *
- * @param networkQuality the required network quality. Defaults to NETWORK_QUALITY_ANY
- * @hide
- */
- @NonNull
- public Builder setNetworkQuality(@NetworkQuality int networkQuality) {
- validateNetworkQuality(networkQuality);
-
- mNetworkQuality = networkQuality;
- return this;
- }
-
- /**
* Set the matching criteria for metered networks.
*
* <p>A template where setMetered(MATCH_REQUIRED) will only match metered networks (one
@@ -369,12 +374,92 @@
return this;
}
+ /**
+ * Set the minimum upstream bandwidths that this template will match.
+ *
+ * <p>This template will not match a network that does not provide at least the bandwidth
+ * passed as the entry bandwidth, except in the case that the network is selected as the VCN
+ * Gateway Connection's underlying network, where it will continue to match until the
+ * bandwidth drops under the exit bandwidth.
+ *
+ * <p>The entry criteria MUST be greater than, or equal to the exit criteria to avoid the
+ * invalid case where a network fulfills the entry criteria, but at the same time fails the
+ * exit criteria.
+ *
+ * <p>Estimated bandwidth of a network is provided by the transport layer, and reported in
+ * {@link NetworkCapabilities}. The provided estimates will be used without modification.
+ *
+ * @param minEntryUpstreamBandwidthKbps the minimum accepted upstream bandwidth for networks
+ * that ARE NOT the already-selected underlying network, or {@code 0} to disable this
+ * requirement. Disabled by default.
+ * @param minExitUpstreamBandwidthKbps the minimum accepted upstream bandwidth for a network
+ * that IS the already-selected underlying network, or {@code 0} to disable this
+ * requirement. Disabled by default.
+ * @return this {@link Builder} instance, for chaining
+ */
+ @NonNull
+ // The getter for the two integers are separated, and in the superclass. Please see {@link
+ // VcnUnderlyingNetworkTemplate#getMinEntryUpstreamBandwidthKbps()} and {@link
+ // VcnUnderlyingNetworkTemplate#getMinExitUpstreamBandwidthKbps()}
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public Builder setMinUpstreamBandwidthKbps(
+ int minEntryUpstreamBandwidthKbps, int minExitUpstreamBandwidthKbps) {
+ validateMinBandwidthKbps(minEntryUpstreamBandwidthKbps, minExitUpstreamBandwidthKbps);
+
+ mMinEntryUpstreamBandwidthKbps = minEntryUpstreamBandwidthKbps;
+ mMinExitUpstreamBandwidthKbps = minExitUpstreamBandwidthKbps;
+
+ return this;
+ }
+
+ /**
+ * Set the minimum upstream bandwidths that this template will match.
+ *
+ * <p>This template will not match a network that does not provide at least the bandwidth
+ * passed as the entry bandwidth, except in the case that the network is selected as the VCN
+ * Gateway Connection's underlying network, where it will continue to match until the
+ * bandwidth drops under the exit bandwidth.
+ *
+ * <p>The entry criteria MUST be greater than, or equal to the exit criteria to avoid the
+ * invalid case where a network fulfills the entry criteria, but at the same time fails the
+ * exit criteria.
+ *
+ * <p>Estimated bandwidth of a network is provided by the transport layer, and reported in
+ * {@link NetworkCapabilities}. The provided estimates will be used without modification.
+ *
+ * @param minEntryDownstreamBandwidthKbps the minimum accepted downstream bandwidth for
+ * networks that ARE NOT the already-selected underlying network, or {@code 0} to
+ * disable this requirement. Disabled by default.
+ * @param minExitDownstreamBandwidthKbps the minimum accepted downstream bandwidth for a
+ * network that IS the already-selected underlying network, or {@code 0} to disable this
+ * requirement. Disabled by default.
+ * @return this {@link Builder} instance, for chaining
+ */
+ @NonNull
+ // The getter for the two integers are separated, and in the superclass. Please see {@link
+ // VcnUnderlyingNetworkTemplate#getMinEntryDownstreamBandwidthKbps()} and {@link
+ // VcnUnderlyingNetworkTemplate#getMinExitDownstreamBandwidthKbps()}
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public Builder setMinDownstreamBandwidthKbps(
+ int minEntryDownstreamBandwidthKbps, int minExitDownstreamBandwidthKbps) {
+ validateMinBandwidthKbps(
+ minEntryDownstreamBandwidthKbps, minExitDownstreamBandwidthKbps);
+
+ mMinEntryDownstreamBandwidthKbps = minEntryDownstreamBandwidthKbps;
+ mMinExitDownstreamBandwidthKbps = minExitDownstreamBandwidthKbps;
+
+ return this;
+ }
+
/** Build the VcnCellUnderlyingNetworkTemplate. */
@NonNull
public VcnCellUnderlyingNetworkTemplate build() {
return new VcnCellUnderlyingNetworkTemplate(
- mNetworkQuality,
mMeteredMatchCriteria,
+ mMinEntryUpstreamBandwidthKbps,
+ mMinExitUpstreamBandwidthKbps,
+ mMinEntryDownstreamBandwidthKbps,
+ mMinExitDownstreamBandwidthKbps,
mAllowedNetworkPlmnIds,
mAllowedSpecificCarrierIds,
mRoamingMatchCriteria,
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index 92956e8..a6830b7 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -17,7 +17,6 @@
import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
import static com.android.internal.annotations.VisibleForTesting.Visibility;
@@ -169,18 +168,15 @@
static {
DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
new VcnCellUnderlyingNetworkTemplate.Builder()
- .setNetworkQuality(NETWORK_QUALITY_OK)
.setOpportunistic(MATCH_REQUIRED)
.build());
DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
new VcnWifiUnderlyingNetworkTemplate.Builder()
- .setNetworkQuality(NETWORK_QUALITY_OK)
.build());
DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
new VcnCellUnderlyingNetworkTemplate.Builder()
- .setNetworkQuality(NETWORK_QUALITY_OK)
.build());
}
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
index 60fc936..3a9ca3e 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
@@ -48,23 +48,6 @@
/** @hide */
static final int NETWORK_PRIORITY_TYPE_CELL = 2;
- /** Denotes that any network quality is acceptable. @hide */
- public static final int NETWORK_QUALITY_ANY = 0;
- /** Denotes that network quality needs to be OK. @hide */
- public static final int NETWORK_QUALITY_OK = 100000;
-
- private static final SparseArray<String> NETWORK_QUALITY_TO_STRING_MAP = new SparseArray<>();
-
- static {
- NETWORK_QUALITY_TO_STRING_MAP.put(NETWORK_QUALITY_ANY, "NETWORK_QUALITY_ANY");
- NETWORK_QUALITY_TO_STRING_MAP.put(NETWORK_QUALITY_OK, "NETWORK_QUALITY_OK");
- }
-
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({NETWORK_QUALITY_OK, NETWORK_QUALITY_ANY})
- public @interface NetworkQuality {}
-
/**
* Used to configure the matching criteria of a network characteristic. This may include network
* capabilities, or cellular subscription information. Denotes that networks with or without the
@@ -103,44 +86,73 @@
private final int mNetworkPriorityType;
/** @hide */
- static final String NETWORK_QUALITY_KEY = "mNetworkQuality";
-
- private final int mNetworkQuality;
-
- /** @hide */
static final String METERED_MATCH_KEY = "mMeteredMatchCriteria";
private final int mMeteredMatchCriteria;
/** @hide */
+ public static final int DEFAULT_MIN_BANDWIDTH_KBPS = 0;
+
+ /** @hide */
+ static final String MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS_KEY = "mMinEntryUpstreamBandwidthKbps";
+
+ private final int mMinEntryUpstreamBandwidthKbps;
+
+ /** @hide */
+ static final String MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS_KEY = "mMinExitUpstreamBandwidthKbps";
+
+ private final int mMinExitUpstreamBandwidthKbps;
+
+ /** @hide */
+ static final String MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS_KEY =
+ "mMinEntryDownstreamBandwidthKbps";
+
+ private final int mMinEntryDownstreamBandwidthKbps;
+
+ /** @hide */
+ static final String MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS_KEY = "mMinExitDownstreamBandwidthKbps";
+
+ private final int mMinExitDownstreamBandwidthKbps;
+
+ /** @hide */
VcnUnderlyingNetworkTemplate(
- int networkPriorityType, int networkQuality, int meteredMatchCriteria) {
+ int networkPriorityType,
+ int meteredMatchCriteria,
+ int minEntryUpstreamBandwidthKbps,
+ int minExitUpstreamBandwidthKbps,
+ int minEntryDownstreamBandwidthKbps,
+ int minExitDownstreamBandwidthKbps) {
mNetworkPriorityType = networkPriorityType;
- mNetworkQuality = networkQuality;
mMeteredMatchCriteria = meteredMatchCriteria;
+ mMinEntryUpstreamBandwidthKbps = minEntryUpstreamBandwidthKbps;
+ mMinExitUpstreamBandwidthKbps = minExitUpstreamBandwidthKbps;
+ mMinEntryDownstreamBandwidthKbps = minEntryDownstreamBandwidthKbps;
+ mMinExitDownstreamBandwidthKbps = minExitDownstreamBandwidthKbps;
}
/** @hide */
- static void validateNetworkQuality(int networkQuality) {
+ static void validateMatchCriteria(int matchCriteria, String matchingCapability) {
Preconditions.checkArgument(
- networkQuality == NETWORK_QUALITY_ANY || networkQuality == NETWORK_QUALITY_OK,
- "Invalid networkQuality:" + networkQuality);
+ MATCH_CRITERIA_TO_STRING_MAP.contains(matchCriteria),
+ "Invalid matching criteria: " + matchCriteria + " for " + matchingCapability);
}
/** @hide */
- static void validateMatchCriteria(int meteredMatchCriteria, String matchingCapability) {
+ static void validateMinBandwidthKbps(int minEntryBandwidth, int minExitBandwidth) {
Preconditions.checkArgument(
- MATCH_CRITERIA_TO_STRING_MAP.contains(meteredMatchCriteria),
- "Invalid matching criteria: "
- + meteredMatchCriteria
- + " for "
- + matchingCapability);
+ minEntryBandwidth >= 0, "Invalid minEntryBandwidth, must be >= 0");
+ Preconditions.checkArgument(
+ minExitBandwidth >= 0, "Invalid minExitBandwidth, must be >= 0");
+ Preconditions.checkArgument(
+ minEntryBandwidth >= minExitBandwidth,
+ "Minimum entry bandwidth must be >= exit bandwidth");
}
/** @hide */
protected void validate() {
- validateNetworkQuality(mNetworkQuality);
validateMatchCriteria(mMeteredMatchCriteria, "mMeteredMatchCriteria");
+ validateMinBandwidthKbps(mMinEntryUpstreamBandwidthKbps, mMinExitUpstreamBandwidthKbps);
+ validateMinBandwidthKbps(mMinEntryDownstreamBandwidthKbps, mMinExitDownstreamBandwidthKbps);
}
/** @hide */
@@ -168,15 +180,24 @@
final PersistableBundle result = new PersistableBundle();
result.putInt(NETWORK_PRIORITY_TYPE_KEY, mNetworkPriorityType);
- result.putInt(NETWORK_QUALITY_KEY, mNetworkQuality);
result.putInt(METERED_MATCH_KEY, mMeteredMatchCriteria);
+ result.putInt(MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS_KEY, mMinEntryUpstreamBandwidthKbps);
+ result.putInt(MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS_KEY, mMinExitUpstreamBandwidthKbps);
+ result.putInt(MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS_KEY, mMinEntryDownstreamBandwidthKbps);
+ result.putInt(MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS_KEY, mMinExitDownstreamBandwidthKbps);
return result;
}
@Override
public int hashCode() {
- return Objects.hash(mNetworkPriorityType, mNetworkQuality, mMeteredMatchCriteria);
+ return Objects.hash(
+ mNetworkPriorityType,
+ mMeteredMatchCriteria,
+ mMinEntryUpstreamBandwidthKbps,
+ mMinExitUpstreamBandwidthKbps,
+ mMinEntryDownstreamBandwidthKbps,
+ mMinExitDownstreamBandwidthKbps);
}
@Override
@@ -187,8 +208,11 @@
final VcnUnderlyingNetworkTemplate rhs = (VcnUnderlyingNetworkTemplate) other;
return mNetworkPriorityType == rhs.mNetworkPriorityType
- && mNetworkQuality == rhs.mNetworkQuality
- && mMeteredMatchCriteria == rhs.mMeteredMatchCriteria;
+ && mMeteredMatchCriteria == rhs.mMeteredMatchCriteria
+ && mMinEntryUpstreamBandwidthKbps == rhs.mMinEntryUpstreamBandwidthKbps
+ && mMinExitUpstreamBandwidthKbps == rhs.mMinExitUpstreamBandwidthKbps
+ && mMinEntryDownstreamBandwidthKbps == rhs.mMinEntryDownstreamBandwidthKbps
+ && mMinExitDownstreamBandwidthKbps == rhs.mMinExitDownstreamBandwidthKbps;
}
/** @hide */
@@ -197,8 +221,8 @@
}
/** @hide */
- static String getMatchCriteriaString(int meteredMatchCriteria) {
- return getNameString(MATCH_CRITERIA_TO_STRING_MAP, meteredMatchCriteria);
+ static String getMatchCriteriaString(int matchCriteria) {
+ return getNameString(MATCH_CRITERIA_TO_STRING_MAP, matchCriteria);
}
/** @hide */
@@ -213,34 +237,63 @@
pw.println(this.getClass().getSimpleName() + ":");
pw.increaseIndent();
- pw.println(
- "mNetworkQuality: "
- + getNameString(NETWORK_QUALITY_TO_STRING_MAP, mNetworkQuality));
pw.println("mMeteredMatchCriteria: " + getMatchCriteriaString(mMeteredMatchCriteria));
+ pw.println("mMinEntryUpstreamBandwidthKbps: " + mMinEntryUpstreamBandwidthKbps);
+ pw.println("mMinExitUpstreamBandwidthKbps: " + mMinExitUpstreamBandwidthKbps);
+ pw.println("mMinEntryDownstreamBandwidthKbps: " + mMinEntryDownstreamBandwidthKbps);
+ pw.println("mMinExitDownstreamBandwidthKbps: " + mMinExitDownstreamBandwidthKbps);
dumpTransportSpecificFields(pw);
pw.decreaseIndent();
}
/**
- * Retrieve the required network quality to match this template.
- *
- * @see Builder#setNetworkQuality(int)
- * @hide
- */
- @NetworkQuality
- public int getNetworkQuality() {
- return mNetworkQuality;
- }
-
- /**
* Return the matching criteria for metered networks.
*
* @see VcnWifiUnderlyingNetworkTemplate.Builder#setMetered(int)
* @see VcnCellUnderlyingNetworkTemplate.Builder#setMetered(int)
*/
- @MatchCriteria
public int getMetered() {
return mMeteredMatchCriteria;
}
+
+ /**
+ * Returns the minimum entry upstream bandwidth allowed by this template.
+ *
+ * @see VcnWifiUnderlyingNetworkTemplate.Builder#setMinUpstreamBandwidthKbps(int, int)
+ * @see VcnCellUnderlyingNetworkTemplate.Builder#setMinUpstreamBandwidthKbps(int, int)
+ */
+ public int getMinEntryUpstreamBandwidthKbps() {
+ return mMinEntryUpstreamBandwidthKbps;
+ }
+
+ /**
+ * Returns the minimum exit upstream bandwidth allowed by this template.
+ *
+ * @see VcnWifiUnderlyingNetworkTemplate.Builder#setMinUpstreamBandwidthKbps(int, int)
+ * @see VcnCellUnderlyingNetworkTemplate.Builder#setMinUpstreamBandwidthKbps(int, int)
+ */
+ public int getMinExitUpstreamBandwidthKbps() {
+ return mMinExitUpstreamBandwidthKbps;
+ }
+
+ /**
+ * Returns the minimum entry downstream bandwidth allowed by this template.
+ *
+ * @see VcnWifiUnderlyingNetworkTemplate.Builder#setMinDownstreamBandwidthKbps(int, int)
+ * @see VcnCellUnderlyingNetworkTemplate.Builder#setMinDownstreamBandwidthKbps(int, int)
+ */
+ public int getMinEntryDownstreamBandwidthKbps() {
+ return mMinEntryDownstreamBandwidthKbps;
+ }
+
+ /**
+ * Returns the minimum exit downstream bandwidth allowed by this template.
+ *
+ * @see VcnWifiUnderlyingNetworkTemplate.Builder#setMinDownstreamBandwidthKbps(int, int)
+ * @see VcnCellUnderlyingNetworkTemplate.Builder#setMinDownstreamBandwidthKbps(int, int)
+ */
+ public int getMinExitDownstreamBandwidthKbps() {
+ return mMinExitDownstreamBandwidthKbps;
+ }
}
diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
index 272ca9d..23a07ab 100644
--- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
@@ -46,8 +46,19 @@
@Nullable private final Set<String> mSsids;
private VcnWifiUnderlyingNetworkTemplate(
- int networkQuality, int meteredMatchCriteria, Set<String> ssids) {
- super(NETWORK_PRIORITY_TYPE_WIFI, networkQuality, meteredMatchCriteria);
+ int meteredMatchCriteria,
+ int minEntryUpstreamBandwidthKbps,
+ int minExitUpstreamBandwidthKbps,
+ int minEntryDownstreamBandwidthKbps,
+ int minExitDownstreamBandwidthKbps,
+ Set<String> ssids) {
+ super(
+ NETWORK_PRIORITY_TYPE_WIFI,
+ meteredMatchCriteria,
+ minEntryUpstreamBandwidthKbps,
+ minExitUpstreamBandwidthKbps,
+ minEntryDownstreamBandwidthKbps,
+ minExitDownstreamBandwidthKbps);
mSsids = new ArraySet<>(ssids);
validate();
@@ -75,15 +86,29 @@
@NonNull PersistableBundle in) {
Objects.requireNonNull(in, "PersistableBundle is null");
- final int networkQuality = in.getInt(NETWORK_QUALITY_KEY);
final int meteredMatchCriteria = in.getInt(METERED_MATCH_KEY);
+ final int minEntryUpstreamBandwidthKbps =
+ in.getInt(MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+ final int minExitUpstreamBandwidthKbps =
+ in.getInt(MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+ final int minEntryDownstreamBandwidthKbps =
+ in.getInt(MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+ final int minExitDownstreamBandwidthKbps =
+ in.getInt(MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+
final PersistableBundle ssidsBundle = in.getPersistableBundle(SSIDS_KEY);
Objects.requireNonNull(ssidsBundle, "ssidsBundle is null");
final Set<String> ssids =
new ArraySet<String>(
PersistableBundleUtils.toList(ssidsBundle, STRING_DESERIALIZER));
- return new VcnWifiUnderlyingNetworkTemplate(networkQuality, meteredMatchCriteria, ssids);
+ return new VcnWifiUnderlyingNetworkTemplate(
+ meteredMatchCriteria,
+ minEntryUpstreamBandwidthKbps,
+ minExitUpstreamBandwidthKbps,
+ minEntryDownstreamBandwidthKbps,
+ minExitDownstreamBandwidthKbps,
+ ssids);
}
/** @hide */
@@ -137,33 +162,18 @@
/** This class is used to incrementally build VcnWifiUnderlyingNetworkTemplate objects. */
public static final class Builder {
- private int mNetworkQuality = NETWORK_QUALITY_ANY;
private int mMeteredMatchCriteria = MATCH_ANY;
@NonNull private final Set<String> mSsids = new ArraySet<>();
+ private int mMinEntryUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+ private int mMinExitUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+ private int mMinEntryDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+ private int mMinExitDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+
/** Construct a Builder object. */
public Builder() {}
/**
- * Set the required network quality to match this template.
- *
- * <p>Network quality is a aggregation of multiple signals that reflect the network link
- * metrics. For example, the network validation bit (see {@link
- * NetworkCapabilities#NET_CAPABILITY_VALIDATED}), estimated first hop transport bandwidth
- * and signal strength.
- *
- * @param networkQuality the required network quality. Defaults to NETWORK_QUALITY_ANY
- * @hide
- */
- @NonNull
- public Builder setNetworkQuality(@NetworkQuality int networkQuality) {
- validateNetworkQuality(networkQuality);
-
- mNetworkQuality = networkQuality;
- return this;
- }
-
- /**
* Set the matching criteria for metered networks.
*
* <p>A template where setMetered(MATCH_REQUIRED) will only match metered networks (one
@@ -200,11 +210,93 @@
return this;
}
+ /**
+ * Set the minimum upstream bandwidths that this template will match.
+ *
+ * <p>This template will not match a network that does not provide at least the bandwidth
+ * passed as the entry bandwidth, except in the case that the network is selected as the VCN
+ * Gateway Connection's underlying network, where it will continue to match until the
+ * bandwidth drops under the exit bandwidth.
+ *
+ * <p>The entry criteria MUST be greater than, or equal to the exit criteria to avoid the
+ * invalid case where a network fulfills the entry criteria, but at the same time fails the
+ * exit criteria.
+ *
+ * <p>Estimated bandwidth of a network is provided by the transport layer, and reported in
+ * {@link NetworkCapabilities}. The provided estimates will be used without modification.
+ *
+ * @param minEntryUpstreamBandwidthKbps the minimum accepted upstream bandwidth for networks
+ * that ARE NOT the already-selected underlying network, or {@code 0} to disable this
+ * requirement. Disabled by default.
+ * @param minExitUpstreamBandwidthKbps the minimum accepted upstream bandwidth for a network
+ * that IS the already-selected underlying network, or {@code 0} to disable this
+ * requirement. Disabled by default.
+ * @return this {@link Builder} instance, for chaining
+ */
+ @NonNull
+ // The getter for the two integers are separated, and in the superclass. Please see {@link
+ // VcnUnderlyingNetworkTemplate#getMinEntryUpstreamBandwidthKbps()} and {@link
+ // VcnUnderlyingNetworkTemplate#getMinExitUpstreamBandwidthKbps()}
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public Builder setMinUpstreamBandwidthKbps(
+ int minEntryUpstreamBandwidthKbps, int minExitUpstreamBandwidthKbps) {
+ validateMinBandwidthKbps(minEntryUpstreamBandwidthKbps, minExitUpstreamBandwidthKbps);
+
+ mMinEntryUpstreamBandwidthKbps = minEntryUpstreamBandwidthKbps;
+ mMinExitUpstreamBandwidthKbps = minExitUpstreamBandwidthKbps;
+
+ return this;
+ }
+
+ /**
+ * Set the minimum upstream bandwidths that this template will match.
+ *
+ * <p>This template will not match a network that does not provide at least the bandwidth
+ * passed as the entry bandwidth, except in the case that the network is selected as the VCN
+ * Gateway Connection's underlying network, where it will continue to match until the
+ * bandwidth drops under the exit bandwidth.
+ *
+ * <p>The entry criteria MUST be greater than, or equal to the exit criteria to avoid the
+ * invalid case where a network fulfills the entry criteria, but at the same time fails the
+ * exit criteria.
+ *
+ * <p>Estimated bandwidth of a network is provided by the transport layer, and reported in
+ * {@link NetworkCapabilities}. The provided estimates will be used without modification.
+ *
+ * @param minEntryDownstreamBandwidthKbps the minimum accepted downstream bandwidth for
+ * networks that ARE NOT the already-selected underlying network, or {@code 0} to
+ * disable this requirement. Disabled by default.
+ * @param minExitDownstreamBandwidthKbps the minimum accepted downstream bandwidth for a
+ * network that IS the already-selected underlying network, or {@code 0} to disable this
+ * requirement. Disabled by default.
+ * @return this {@link Builder} instance, for chaining
+ */
+ @NonNull
+ // The getter for the two integers are separated, and in the superclass. Please see {@link
+ // VcnUnderlyingNetworkTemplate#getMinEntryDownstreamBandwidthKbps()} and {@link
+ // VcnUnderlyingNetworkTemplate#getMinExitDownstreamBandwidthKbps()}
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public Builder setMinDownstreamBandwidthKbps(
+ int minEntryDownstreamBandwidthKbps, int minExitDownstreamBandwidthKbps) {
+ validateMinBandwidthKbps(
+ minEntryDownstreamBandwidthKbps, minExitDownstreamBandwidthKbps);
+
+ mMinEntryDownstreamBandwidthKbps = minEntryDownstreamBandwidthKbps;
+ mMinExitDownstreamBandwidthKbps = minExitDownstreamBandwidthKbps;
+
+ return this;
+ }
+
/** Build the VcnWifiUnderlyingNetworkTemplate. */
@NonNull
public VcnWifiUnderlyingNetworkTemplate build() {
return new VcnWifiUnderlyingNetworkTemplate(
- mNetworkQuality, mMeteredMatchCriteria, mSsids);
+ mMeteredMatchCriteria,
+ mMinEntryUpstreamBandwidthKbps,
+ mMinExitUpstreamBandwidthKbps,
+ mMinEntryDownstreamBandwidthKbps,
+ mMinExitDownstreamBandwidthKbps,
+ mSsids);
}
}
}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 3f42164..fe86874 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -90,7 +90,7 @@
Bundle getApplicationRestrictionsForUser(in String packageName, int userId);
void setDefaultGuestRestrictions(in Bundle restrictions);
Bundle getDefaultGuestRestrictions();
- int removeUserOrSetEphemeral(int userId, boolean evenWhenDisallowed);
+ int removeUserWhenPossible(int userId, boolean overrideDevicePolicy);
boolean markGuestForDeletion(int userId);
UserInfo findCurrentGuestUser();
boolean isQuietModeEnabled(int userId);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index edf6280..190f5f1 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -4750,7 +4750,7 @@
public int removeUserWhenPossible(@NonNull UserHandle user,
boolean overrideDevicePolicy) {
try {
- return mService.removeUserOrSetEphemeral(user.getIdentifier(), overrideDevicePolicy);
+ return mService.removeUserWhenPossible(user.getIdentifier(), overrideDevicePolicy);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -4777,7 +4777,7 @@
public @RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId,
boolean evenWhenDisallowed) {
try {
- return mService.removeUserOrSetEphemeral(userId, evenWhenDisallowed);
+ return mService.removeUserWhenPossible(userId, evenWhenDisallowed);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java
index 61277e2..22ed1b8 100644
--- a/core/java/android/service/trust/TrustAgentService.java
+++ b/core/java/android/service/trust/TrustAgentService.java
@@ -114,15 +114,47 @@
*/
public static final int FLAG_GRANT_TRUST_DISMISS_KEYGUARD = 1 << 1;
+ /**
+ * Flag for {@link #grantTrust(CharSequence, long, int)} indicating the platform should
+ * automatically remove trust after some conditions are met (detailed below) with the option for
+ * the agent to renew the trust again later.
+ *
+ * <p>After this is called, the agent will grant trust until the platform thinks an active user
+ * is no longer using that trust. For example, if the user dismisses keyguard, the platform will
+ * remove trust (this does not automatically lock the device).
+ *
+ * <p>When the platform internally removes the agent's trust in this manner, an agent can
+ * re-grant it (via a call to grantTrust) without the user having to unlock the device through
+ * another method (e.g. PIN). This renewable state only persists for a limited time.
+ *
+ * TODO(b/213631675): Remove @hide
+ * @hide
+ */
+ public static final int FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE = 1 << 2;
+
+ /**
+ * Flag for {@link #grantTrust(CharSequence, long, int)} indicating that the message should
+ * be displayed to the user.
+ *
+ * Without this flag, the message passed to {@code grantTrust} is only used for debugging
+ * purposes. With the flag, it may be displayed to the user as the reason why the device is
+ * unlocked.
+ *
+ * TODO(b/213911325): Remove @hide
+ * @hide
+ */
+ public static final int FLAG_GRANT_TRUST_DISPLAY_MESSAGE = 1 << 3;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "FLAG_GRANT_TRUST_" }, value = {
FLAG_GRANT_TRUST_INITIATED_BY_USER,
FLAG_GRANT_TRUST_DISMISS_KEYGUARD,
+ FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE,
+ FLAG_GRANT_TRUST_DISPLAY_MESSAGE,
})
public @interface GrantTrustFlags {}
-
/**
* Int enum indicating that escrow token is active.
* See {@link #onEscrowTokenStateReceived(long, int)}
@@ -265,6 +297,22 @@
}
/**
+ * Called when the user has interacted with the locked device such that they likely want it
+ * to be unlocked. This approximates the timing when, for example, the platform would check for
+ * face authentication to unlock the device.
+ *
+ * To attempt to unlock the device, the agent needs to call
+ * {@link #grantTrust(CharSequence, long, int)}.
+ *
+ * @see #FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE
+ *
+ * TODO(b/213631672): Hook up call from system server & SystemUI, then un-hide
+ * @hide
+ */
+ public void onUserRequestedUnlock() {
+ }
+
+ /**
* Called when the timeout provided by the agent expires. Note that this may be called earlier
* than requested by the agent if the trust timeout is adjusted by the system or
* {@link DevicePolicyManager}. The agent is expected to re-evaluate the trust state and only
@@ -564,6 +612,22 @@
}
/**
+ * Locks the user.
+ *
+ * This revokes any trust granted by this agent and shows keyguard for the user if it is not
+ * currently shown for them. Other users are not affected. Note that this is in contrast to
+ * {@link #revokeTrust()} which does not show keyguard if it is not already shown.
+ *
+ * If the user has no auth method specified, then keyguard will still be shown but can be
+ * dismissed normally.
+ *
+ * TODO(b/213631675): Implement & make public
+ * @hide
+ */
+ public final void lockUser() {
+ }
+
+ /**
* Request showing a transient error message on the keyguard.
* The message will be visible on the lock screen or always on display if possible but can be
* overridden by other keyguard events of higher priority - eg. fingerprint auth error.
diff --git a/core/java/android/util/Dumpable.java b/core/java/android/util/Dumpable.java
new file mode 100644
index 0000000..79c576d
--- /dev/null
+++ b/core/java/android/util/Dumpable.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.PrintWriter;
+
+/**
+ * Represents an object whose state can be dumped into a {@link PrintWriter}.
+ */
+public interface Dumpable {
+
+ /**
+ * Gets the name of the {@link Dumpable}.
+ *
+ * @return class name, by default.
+ */
+ @NonNull
+ default String getDumpableName() {
+ return getClass().getName();
+ }
+
+ //TODO(b/149254050): decide whether it should take a ParcelFileDescription as well.
+
+ /**
+ * Dumps the internal state into the given {@code writer}.
+ *
+ * @param writer writer to be written to
+ * @param args optional list of arguments
+ */
+ void dump(@NonNull PrintWriter writer, @Nullable String[] args);
+}
diff --git a/core/java/android/util/DumpableContainer.java b/core/java/android/util/DumpableContainer.java
new file mode 100644
index 0000000..04d19dc
--- /dev/null
+++ b/core/java/android/util/DumpableContainer.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.util;
+
+import android.annotation.NonNull;
+
+/**
+ * Objects that contains a list of {@link Dumpable}, which will be dumped when the object itself
+ * is dumped.
+ */
+public interface DumpableContainer {
+
+ /**
+ * Adds the given {@link Dumpable dumpable} to the container.
+ *
+ * <p>If a dumpable with the same {@link Dumpable#getDumpableName() name} was added before, this
+ * call is ignored.
+ *
+ * @param dumpable dumpable to be added.
+ *
+ * @throws IllegalArgumentException if the {@link Dumpable#getDumpableName() dumpable name} is
+ * {@code null}.
+ *
+ * @return {@code true} if the dumpable was added, {@code false} if the call was ignored.
+ */
+ boolean addDumpable(@NonNull Dumpable dumpable);
+}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 3cc51c7..70266c1 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE;
import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS;
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -138,6 +139,24 @@
public static final int INVALID_DISPLAY = -1;
/**
+ * Invalid resolution width.
+ * @hide
+ */
+ public static final int INVALID_DISPLAY_WIDTH = -1;
+
+ /**
+ * Invalid resolution height.
+ * @hide
+ */
+ public static final int INVALID_DISPLAY_HEIGHT = -1;
+
+ /**
+ * Invalid refresh rate.
+ * @hide
+ */
+ public static final float INVALID_DISPLAY_REFRESH_RATE = 0.0f;
+
+ /**
* The default display group id, which is the display group id of the primary display assuming
* there is one.
* @hide
@@ -1170,6 +1189,49 @@
}
/**
+ * Sets the default {@link Display.Mode} to use for the display. The display mode includes
+ * preference for resolution and refresh rate.
+ * If the mode specified is not supported by the display, then no mode change occurs.
+ *
+ * @param mode The {@link Display.Mode} to set, which can include resolution and/or
+ * refresh-rate. It is created using {@link Display.Mode.Builder}.
+ *`
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE)
+ public void setUserPreferredDisplayMode(@NonNull Display.Mode mode) {
+ // Create a new object containing default values for the unused fields like mode ID and
+ // alternative refresh rates.
+ Display.Mode preferredMode = new Display.Mode(mode.getPhysicalWidth(),
+ mode.getPhysicalHeight(), mode.getRefreshRate());
+ mGlobal.setUserPreferredDisplayMode(mDisplayId, preferredMode);
+ }
+
+ /**
+ * Removes the display's user preferred display mode.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE)
+ public void clearUserPreferredDisplayMode() {
+ mGlobal.setUserPreferredDisplayMode(mDisplayId, null);
+ }
+
+ /**
+ * Returns the display's user preferred display mode.
+ *
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ public Display.Mode getUserPreferredDisplayMode() {
+ return mGlobal.getUserPreferredDisplayMode(mDisplayId);
+ }
+
+
+ /**
* Returns whether this display can be used to display wide color gamut content.
* This does not necessarily mean the device itself can render wide color gamut
* content. To ensure wide color gamut content can be produced, refer to
@@ -1710,6 +1772,30 @@
}
/**
+ * Returns true if the specified width is valid.
+ * @hide
+ */
+ public static boolean isWidthValid(int width) {
+ return width > 0;
+ }
+
+ /**
+ * Returns true if the specified height is valid.
+ * @hide
+ */
+ public static boolean isHeightValid(int height) {
+ return height > 0;
+ }
+
+ /**
+ * Returns true if the specified refresh-rate is valid.
+ * @hide
+ */
+ public static boolean isRefreshRateValid(float refreshRate) {
+ return refreshRate > 0.0f;
+ }
+
+ /**
* A mode supported by a given display.
*
* @see Display#getSupportedModes()
@@ -1846,6 +1932,30 @@
}
/**
+ * Returns {@code true} if this mode matches the given parameters, if those parameters are
+ * valid.<p>
+ * If resolution (width and height) is valid and refresh-rate is not, the method matches
+ * only resolution.
+ * If refresh-rate is valid and resolution (width and height) is not, the method matches
+ * only refresh-rate.</p>
+ *
+ * @hide
+ */
+ public boolean matchesIfValid(int width, int height, float refreshRate) {
+ if (!isWidthValid(width) && !isHeightValid(height)
+ && !isRefreshRateValid(refreshRate)) {
+ return false;
+ }
+ if (isWidthValid(width) != isHeightValid(height)) {
+ return false;
+ }
+ return (!isWidthValid(width) || mWidth == width)
+ && (!isHeightValid(height) || mHeight == height)
+ && (!isRefreshRateValid(refreshRate)
+ || Float.floatToIntBits(mRefreshRate) == Float.floatToIntBits(refreshRate));
+ }
+
+ /**
* Returns {@code true} if this mode equals to the other mode in all parameters except
* the refresh rate.
*
@@ -1855,6 +1965,24 @@
return mWidth == other.mWidth && mHeight == other.mHeight;
}
+ /**
+ * Returns {@code true} if refresh-rate is set for a display mode
+ *
+ * @hide
+ */
+ public boolean isRefreshRateSet() {
+ return mRefreshRate != INVALID_DISPLAY_REFRESH_RATE;
+ }
+
+ /**
+ * Returns {@code true} if refresh-rate is set for a display mode
+ *
+ * @hide
+ */
+ public boolean isResolutionSet() {
+ return mWidth != INVALID_DISPLAY_WIDTH && mHeight != INVALID_DISPLAY_HEIGHT;
+ }
+
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
@@ -1923,6 +2051,80 @@
return new Mode[size];
}
};
+
+ /**
+ * Builder is used to create {@link Display.Mode} objects
+ *
+ * @hide
+ */
+ @TestApi
+ public static final class Builder {
+ private int mWidth;
+ private int mHeight;
+ private float mRefreshRate;
+
+ public Builder() {
+ mWidth = Display.INVALID_DISPLAY_WIDTH;
+ mHeight = Display.INVALID_DISPLAY_HEIGHT;
+ mRefreshRate = Display.INVALID_DISPLAY_REFRESH_RATE;
+ }
+
+ /**
+ * Sets the resolution (width and height) of a {@link Display.Mode}
+ *
+ * @return Instance of {@link Builder}
+ */
+ @NonNull
+ public Builder setResolution(int width, int height) {
+ if (width > 0 && height > 0) {
+ mWidth = width;
+ mHeight = height;
+ }
+ return this;
+ }
+
+ /**
+ * Sets the refresh rate of a {@link Display.Mode}
+ *
+ * @return Instance of {@link Builder}
+ */
+ @NonNull
+ public Builder setRefreshRate(float refreshRate) {
+ if (refreshRate > 0.0f) {
+ mRefreshRate = refreshRate;
+ }
+ return this;
+ }
+
+ /**
+ * Creates the {@link Display.Mode} object.
+ *
+ * <p>
+ * If resolution needs to be set, but refresh-rate doesn't matter, create a mode with
+ * Builder and call setResolution.
+ * {@code
+ * Display.Mode mode =
+ * new Display.Mode.Builder()
+ * .setResolution(width, height)
+ * .build();
+ * }
+ * </p><p>
+ * If refresh-rate needs to be set, but resolution doesn't matter, create a mode with
+ * Builder and call setRefreshRate.
+ * {@code
+ * Display.Mode mode =
+ * new Display.Mode.Builder()
+ * .setRefreshRate(refreshRate)
+ * .build();
+ * }
+ * </p>
+ */
+ @NonNull
+ public Mode build() {
+ Display.Mode mode = new Mode(mWidth, mHeight, mRefreshRate);
+ return mode;
+ }
+ }
}
/**
diff --git a/core/java/android/view/ISurfaceControlViewHost.aidl b/core/java/android/view/ISurfaceControlViewHost.aidl
new file mode 100644
index 0000000..fc9661a
--- /dev/null
+++ b/core/java/android/view/ISurfaceControlViewHost.aidl
@@ -0,0 +1,28 @@
+/*
+** Copyright 2021, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.view;
+
+import android.content.res.Configuration;
+
+/**
+ * API from content embedder back to embedded content in SurfaceControlViewHost
+ * {@hide}
+ */
+oneway interface ISurfaceControlViewHost {
+ void onConfigurationChanged(in Configuration newConfig);
+ void onDispatchDetachedFromWindow();
+}
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index adb8b86..0ef5854 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -478,10 +478,15 @@
public static final int FLAG_IS_GENERATED_GESTURE = 0x8;
/**
- * This flag associated with {@link #ACTION_POINTER_UP}, this indicates that the pointer
- * has been canceled. Typically this is used for palm event when the user has accidental
- * touches.
- * @hide
+ * This flag is only set for events with {@link #ACTION_POINTER_UP} and {@link #ACTION_CANCEL}.
+ * It indicates that the pointer going up was an unintentional user touch. When FLAG_CANCELED
+ * is set, the typical actions that occur in response for a pointer going up (such as click
+ * handlers, end of drawing) should be aborted. This flag is typically set when the user was
+ * accidentally touching the screen, such as by gripping the device, or placing the palm on the
+ * screen.
+ *
+ * @see #ACTION_POINTER_UP
+ * @see #ACTION_CANCEL
*/
public static final int FLAG_CANCELED = 0x20;
diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS
index d160be5..43df294 100644
--- a/core/java/android/view/OWNERS
+++ b/core/java/android/view/OWNERS
@@ -80,6 +80,7 @@
per-file IPinnedStackListener.aidl = file:/services/core/java/com/android/server/wm/OWNERS
per-file IRecents*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
per-file IRemote*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
+per-file ISurfaceControlViewHost*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
per-file IWindow*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
per-file RemoteAnimation*.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file RemoteAnimation*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index a6c5042d..85a9dbd 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.os.Parcel;
@@ -45,6 +46,35 @@
private SurfaceControl mSurfaceControl;
private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
+ private final class ISurfaceControlViewHostImpl extends ISurfaceControlViewHost.Stub {
+ @Override
+ public void onConfigurationChanged(Configuration configuration) {
+ if (mViewRoot == null) {
+ return;
+ }
+ mViewRoot.mHandler.post(() -> {
+ if (mWm != null) {
+ mWm.setConfiguration(configuration);
+ }
+ if (mViewRoot != null) {
+ mViewRoot.forceWmRelayout();
+ }
+ });
+ }
+
+ @Override
+ public void onDispatchDetachedFromWindow() {
+ if (mViewRoot == null) {
+ return;
+ }
+ mViewRoot.mHandler.post(() -> {
+ release();
+ });
+ }
+ }
+
+ private ISurfaceControlViewHost mRemoteInterface = new ISurfaceControlViewHostImpl();
+
/**
* Package encapsulating a Surface hierarchy which contains interactive view
* elements. It's expected to get this object from
@@ -71,12 +101,14 @@
private SurfaceControl mSurfaceControl;
private final IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
private final IBinder mInputToken;
+ private final ISurfaceControlViewHost mRemoteInterface;
SurfacePackage(SurfaceControl sc, IAccessibilityEmbeddedConnection connection,
- IBinder inputToken) {
+ IBinder inputToken, ISurfaceControlViewHost ri) {
mSurfaceControl = sc;
mAccessibilityEmbeddedConnection = connection;
mInputToken = inputToken;
+ mRemoteInterface = ri;
}
/**
@@ -97,6 +129,7 @@
}
mAccessibilityEmbeddedConnection = other.mAccessibilityEmbeddedConnection;
mInputToken = other.mInputToken;
+ mRemoteInterface = other.mRemoteInterface;
}
private SurfacePackage(Parcel in) {
@@ -105,6 +138,8 @@
mAccessibilityEmbeddedConnection = IAccessibilityEmbeddedConnection.Stub.asInterface(
in.readStrongBinder());
mInputToken = in.readStrongBinder();
+ mRemoteInterface = ISurfaceControlViewHost.Stub.asInterface(
+ in.readStrongBinder());
}
/**
@@ -126,6 +161,13 @@
return mAccessibilityEmbeddedConnection;
}
+ /**
+ * @hide
+ */
+ public ISurfaceControlViewHost getRemoteInterface() {
+ return mRemoteInterface;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -136,6 +178,7 @@
mSurfaceControl.writeToParcel(out, flags);
out.writeStrongBinder(mAccessibilityEmbeddedConnection.asBinder());
out.writeStrongBinder(mInputToken);
+ out.writeStrongBinder(mRemoteInterface.asBinder());
}
/**
@@ -231,7 +274,7 @@
public @Nullable SurfacePackage getSurfacePackage() {
if (mSurfaceControl != null && mAccessibilityEmbeddedConnection != null) {
return new SurfacePackage(mSurfaceControl, mAccessibilityEmbeddedConnection,
- mViewRoot.getInputToken());
+ mViewRoot.getInputToken(), mRemoteInterface);
} else {
return null;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2307a03..9ce59bb 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -14248,34 +14248,34 @@
hideTooltip();
return true;
}
- }
-
- if (action == R.id.accessibilityActionDragDrop) {
- if (!canAcceptAccessibilityDrop()) {
- return false;
- }
- try {
- if (mAttachInfo != null && mAttachInfo.mSession != null) {
- final int[] location = new int[2];
- getLocationInWindow(location);
- final int centerX = location[0] + getWidth() / 2;
- final int centerY = location[1] + getHeight() / 2;
- return mAttachInfo.mSession.dropForAccessibility(mAttachInfo.mWindow,
- centerX, centerY);
+ case R.id.accessibilityActionDragDrop: {
+ if (!canAcceptAccessibilityDrop()) {
+ return false;
}
- } catch (RemoteException e) {
- Log.e(VIEW_LOG_TAG, "Unable to drop for accessibility", e);
- }
- return false;
- } else if (action == R.id.accessibilityActionDragCancel) {
- if (!startedSystemDragForAccessibility()) {
+ try {
+ if (mAttachInfo != null && mAttachInfo.mSession != null) {
+ final int[] location = new int[2];
+ getLocationInWindow(location);
+ final int centerX = location[0] + getWidth() / 2;
+ final int centerY = location[1] + getHeight() / 2;
+ return mAttachInfo.mSession.dropForAccessibility(mAttachInfo.mWindow,
+ centerX, centerY);
+ }
+ } catch (RemoteException e) {
+ Log.e(VIEW_LOG_TAG, "Unable to drop for accessibility", e);
+ }
return false;
}
- if (mAttachInfo != null && mAttachInfo.mDragToken != null) {
- cancelDragAndDrop();
- return true;
+ case R.id.accessibilityActionDragCancel: {
+ if (!startedSystemDragForAccessibility()) {
+ return false;
+ }
+ if (mAttachInfo != null && mAttachInfo.mDragToken != null) {
+ cancelDragAndDrop();
+ return true;
+ }
+ return false;
}
- return false;
}
return false;
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7718511..7412931 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -10639,4 +10639,9 @@
boolean wasRelayoutRequested() {
return mRelayoutRequested;
}
+
+ void forceWmRelayout() {
+ mForceNextWindowRelayout = true;
+ scheduleTraversals();
+ }
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index cd9f3eb6..5be3a57 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3581,7 +3581,8 @@
/**
* If specified, the insets provided by this window will be our window frame minus the
- * insets specified by providedInternalInsets.
+ * insets specified by providedInternalInsets. This should not be used together with
+ * {@link WindowState#mGivenContentInsets}. If both of them are set, both will be applied.
*
* @hide
*/
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index d699194d..5176f9b 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -87,7 +87,7 @@
mHostInputToken = hostInputToken;
}
- protected void setConfiguration(Configuration configuration) {
+ public void setConfiguration(Configuration configuration) {
mConfiguration.setTo(configuration);
}
@@ -330,6 +330,7 @@
public void setInsets(android.view.IWindow window, int touchableInsets,
android.graphics.Rect contentInsets, android.graphics.Rect visibleInsets,
android.graphics.Region touchableRegion) {
+ setTouchRegion(window.asBinder(), touchableRegion);
}
@Override
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index db7c663..0a33d6c 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -4321,15 +4321,13 @@
return "ACTION_PRESS_AND_HOLD";
case R.id.accessibilityActionImeEnter:
return "ACTION_IME_ENTER";
+ case R.id.accessibilityActionDragStart:
+ return "ACTION_DRAG";
+ case R.id.accessibilityActionDragCancel:
+ return "ACTION_CANCEL_DRAG";
+ case R.id.accessibilityActionDragDrop:
+ return "ACTION_DROP";
default:
- // TODO(197520937): Use finalized constants in switch
- if (action == R.id.accessibilityActionDragStart) {
- return "ACTION_DRAG";
- } else if (action == R.id.accessibilityActionDragCancel) {
- return "ACTION_CANCEL_DRAG";
- } else if (action == R.id.accessibilityActionDragDrop) {
- return "ACTION_DROP";
- }
return "ACTION_UNKNOWN";
}
}
diff --git a/core/java/android/view/accessibility/CaptioningManager.java b/core/java/android/view/accessibility/CaptioningManager.java
index 3d68692..3f6a871 100644
--- a/core/java/android/view/accessibility/CaptioningManager.java
+++ b/core/java/android/view/accessibility/CaptioningManager.java
@@ -22,6 +22,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Color;
import android.graphics.Typeface;
@@ -30,6 +31,8 @@
import android.provider.Settings.Secure;
import android.text.TextUtils;
+import com.android.internal.R;
+
import java.util.ArrayList;
import java.util.Locale;
@@ -51,6 +54,7 @@
private final ArrayList<CaptioningChangeListener> mListeners = new ArrayList<>();
private final ContentResolver mContentResolver;
private final ContentObserver mContentObserver;
+ private final Resources mResources;
/**
* Creates a new captioning manager for the specified context.
@@ -62,6 +66,7 @@
final Handler handler = new Handler(context.getMainLooper());
mContentObserver = new MyContentObserver(handler);
+ mResources = context.getResources();
}
/**
@@ -181,6 +186,13 @@
}
}
+ /**
+ * Returns true if system wide call captioning is enabled for this device.
+ */
+ public boolean isCallCaptioningEnabled() {
+ return mResources.getBoolean(R.bool.config_systemCaptionsServiceCallsEnabled);
+ }
+
private void notifyEnabledChanged() {
final boolean enabled = isEnabled();
synchronized (mListeners) {
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 5b2068f..fda72d5 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -26,12 +26,16 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.Log;
+import android.view.InputChannel;
+import android.view.MotionEvent;
import android.view.View;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.InlineSuggestionsRequestInfo;
+import java.util.List;
+
/**
* The InputMethod interface represents an input method which can generate key
* events and text, such as digital, email addresses, CJK characters, other
@@ -100,11 +104,13 @@
* operations that are allowed only to the
* current IME.
* @param configChanges {@link InputMethodInfo#getConfigChanges()} declared by IME.
+ * @param stylusHwSupported {@link InputMethodInfo#supportsStylusHandwriting()} declared by IME.
* @hide
*/
@MainThread
default void initializeInternal(IBinder token,
- IInputMethodPrivilegedOperations privilegedOperations, int configChanges) {
+ IInputMethodPrivilegedOperations privilegedOperations, int configChanges,
+ boolean stylusHwSupported) {
attachToken(token);
}
@@ -384,4 +390,23 @@
*/
public void setCurrentHideInputToken(IBinder hideInputToken);
+ /**
+ * Checks if IME is ready to start stylus handwriting session.
+ * If yes, {@link #startStylusHandwriting(InputChannel, List)} is called.
+ * @param requestId
+ * @hide
+ */
+ default void canStartStylusHandwriting(int requestId) {
+ // intentionally empty
+ }
+
+ /**
+ * Start stylus handwriting session.
+ * @hide
+ */
+ default void startStylusHandwriting(
+ @NonNull InputChannel channel, @Nullable List<MotionEvent> events) {
+ // intentionally empty
+ }
+
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 3583cd4..6fc246e 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1761,6 +1761,50 @@
}
/**
+ * Start stylus handwriting session.
+ *
+ * If supported by the current input method, a stylus handwriting session is started on the
+ * given View, capturing all stylus input and converting it to InputConnection commands.
+ *
+ * If handwriting mode is started successfully by the IME, any currently dispatched stylus
+ * pointers will be {@code android.view.MotionEvent#FLAG_CANCELED} cancelled.
+ *
+ * If Stylus handwriting mode is not supported or cannot be fulfilled for any reason by IME,
+ * request will be ignored and Stylus touch will continue as normal touch input.
+ *
+ * @param view the View for which stylus handwriting is requested. It and
+ * {@link View#hasWindowFocus its window} must be {@link View#hasFocus focused}.
+ */
+ public void startStylusHandwriting(@NonNull View view) {
+ // Re-dispatch if there is a context mismatch.
+ final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+ if (fallbackImm != null) {
+ fallbackImm.startStylusHandwriting(view);
+ }
+
+ checkFocus();
+ synchronized (mH) {
+ if (view == null || !hasServedByInputMethodLocked(view)) {
+ Log.w(TAG,
+ "Ignoring startStylusHandwriting() as view=" + view + " is not served.");
+ return;
+ }
+ if (view.getViewRootImpl() != mCurRootView) {
+ Log.w(TAG, "Ignoring startStylusHandwriting: View's window does not have focus.");
+ return;
+ }
+
+ try {
+ mService.startStylusHandwriting(mClient);
+ // TODO(b/210039666): do we need any extra work for supporting non-native
+ // UI toolkits?
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* This method toggles the input method window display.
* If the input window is already displayed, it gets hidden.
* If not the input window will be displayed.
diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java
index c3ef881..3359a41 100644
--- a/core/java/android/window/DisplayWindowPolicyController.java
+++ b/core/java/android/window/DisplayWindowPolicyController.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
+import android.util.ArraySet;
import java.io.PrintWriter;
import java.util.List;
@@ -81,8 +82,10 @@
/**
* This is called when the apps that contains running activities on the display has changed.
+ * The running activities refer to the non-finishing activities regardless of they are running
+ * in a process.
*/
- public void onRunningAppsChanged(int[] runningUids) {}
+ public void onRunningAppsChanged(ArraySet<Integer> runningUids) {}
/** Dump debug data */
public void dump(String prefix, final PrintWriter pw) {
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index 4ba7ef2..547535d 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -19,9 +19,10 @@
import static android.window.ConfigurationHelper.isDifferentDisplay;
import static android.window.ConfigurationHelper.shouldUpdateResources;
+import android.annotation.BinderThread;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityThread;
import android.app.IWindowToken;
import android.app.ResourcesManager;
import android.content.Context;
@@ -30,7 +31,9 @@
import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import android.view.IWindowManager;
@@ -71,6 +74,8 @@
private boolean mAttachToWindowContainer;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
/**
* Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient}
* can only attach one {@link Context}.
@@ -132,7 +137,8 @@
if (configuration == null) {
return false;
}
- onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */);
+ mHandler.post(() -> onConfigurationChanged(configuration, displayId,
+ false /* shouldReportConfigChange */));
mAttachToWindowContainer = true;
return true;
} catch (RemoteException e) {
@@ -179,9 +185,11 @@
* @param newConfig the updated {@link Configuration}
* @param newDisplayId the updated {@link android.view.Display} ID
*/
+ @BinderThread
@Override
public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
- onConfigurationChanged(newConfig, newDisplayId, true /* shouldReportConfigChange */);
+ mHandler.post(() -> onConfigurationChanged(newConfig, newDisplayId,
+ true /* shouldReportConfigChange */));
}
// TODO(b/192048581): rewrite this method based on WindowContext and WindowProviderService
@@ -192,6 +200,7 @@
* Similar to {@link #onConfigurationChanged(Configuration, int)}, but adds a flag to control
* whether to dispatch configuration update or not.
*/
+ @MainThread
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public void onConfigurationChanged(Configuration newConfig, int newDisplayId,
boolean shouldReportConfigChange) {
@@ -217,16 +226,14 @@
if (shouldReportConfigChange && context instanceof WindowContext) {
final WindowContext windowContext = (WindowContext) context;
- ActivityThread.currentActivityThread().getHandler().post(
- () -> windowContext.dispatchConfigurationChanged(newConfig));
+ windowContext.dispatchConfigurationChanged(newConfig);
}
final int diff = mConfiguration.diffPublicOnly(newConfig);
if (shouldReportConfigChange && diff != 0
&& context instanceof WindowProviderService) {
final WindowProviderService windowProviderService = (WindowProviderService) context;
- ActivityThread.currentActivityThread().getHandler().post(
- () -> windowProviderService.onConfigurationChanged(newConfig));
+ windowProviderService.onConfigurationChanged(newConfig);
}
freeTextLayoutCachesIfNeeded(diff);
if (mShouldDumpConfigForIme) {
@@ -248,12 +255,15 @@
}
}
+ @BinderThread
@Override
public void onWindowTokenRemoved() {
- final Context context = mContextRef.get();
- if (context != null) {
- context.destroy();
- mContextRef.clear();
- }
+ mHandler.post(() -> {
+ final Context context = mContextRef.get();
+ if (context != null) {
+ context.destroy();
+ mContextRef.clear();
+ }
+ });
}
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index f904610..13a39de 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -523,6 +523,12 @@
*/
public static final String TASK_MANAGER_ENABLED = "task_manager_enabled";
+
+ /**
+ * (boolean) Whether the clipboard overlay is enabled.
+ */
+ public static final String CLIPBOARD_OVERLAY_ENABLED = "clipboard_overlay_enabled";
+
private SystemUiDeviceConfigFlags() {
}
}
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index 9d0f209..08bc8c7 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -42,4 +42,5 @@
void shouldOfferSwitchingToNextInputMethod(in AndroidFuture future /* T=Boolean */);
void notifyUserActionAsync();
void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible);
+ void onStylusHandwritingReady(int requestId);
}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index d4cc376..7ebcc88 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -394,4 +394,20 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Calls {@link IInputMethodPrivilegedOperations#onStylusHandwritingReady()}
+ */
+ @AnyThread
+ public void onStylusHandwritingReady(int requestId) {
+ final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
+ if (ops == null) {
+ return;
+ }
+ try {
+ ops.onStylusHandwritingReady(requestId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index d14054d..e1a67d8 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -24,8 +24,6 @@
import static android.view.SurfaceControl.JankData.PREDICTION_ERROR;
import static android.view.SurfaceControl.JankData.SURFACE_FLINGER_SCHEDULING;
-import static com.android.internal.jank.InteractionJankMonitor.ACTION_METRICS_LOGGED;
-import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_BEGIN;
import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_CANCEL;
import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_END;
@@ -241,7 +239,6 @@
if (!mSurfaceOnly) {
mRendererWrapper.addObserver(mObserver);
}
- notifyCujEvent(ACTION_SESSION_BEGIN);
}
}
@@ -523,7 +520,6 @@
maxFrameTimeNanos, /* will be 0 if mSurfaceOnly == true */
missedSfFramesCount,
missedAppFramesCount);
- notifyCujEvent(ACTION_METRICS_LOGGED);
}
if (DEBUG) {
Log.i(TAG, "finish: CUJ=" + mSession.getName()
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index a33b2f1..5a66e9a 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -16,10 +16,7 @@
package com.android.internal.jank;
-import static android.content.Intent.FLAG_RECEIVER_REGISTERED_ONLY;
-
import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL;
-import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NOT_BEGUN;
import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN;
@@ -65,17 +62,16 @@
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.content.Context;
-import android.content.Intent;
import android.os.Build;
import android.os.HandlerExecutor;
import android.os.HandlerThread;
-import android.os.SystemProperties;
import android.provider.DeviceConfig;
import android.text.TextUtils;
import android.util.Log;
@@ -131,14 +127,8 @@
private static final int DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES = 3;
private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64;
- public static final String ACTION_SESSION_BEGIN = ACTION_PREFIX + ".ACTION_SESSION_BEGIN";
public static final String ACTION_SESSION_END = ACTION_PREFIX + ".ACTION_SESSION_END";
public static final String ACTION_SESSION_CANCEL = ACTION_PREFIX + ".ACTION_SESSION_CANCEL";
- public static final String ACTION_METRICS_LOGGED = ACTION_PREFIX + ".ACTION_METRICS_LOGGED";
- public static final String BUNDLE_KEY_CUJ_NAME = ACTION_PREFIX + ".CUJ_NAME";
- public static final String BUNDLE_KEY_TIMESTAMP = ACTION_PREFIX + ".TIMESTAMP";
- @VisibleForTesting
- public static final String PROP_NOTIFY_CUJ_EVENT = "debug.jank.notify_cuj_events";
// Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE.
public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0;
@@ -185,6 +175,7 @@
public static final int CUJ_SCREEN_OFF_SHOW_AOD = 41;
public static final int CUJ_ONE_HANDED_ENTER_TRANSITION = 42;
public static final int CUJ_ONE_HANDED_EXIT_TRANSITION = 43;
+ public static final int CUJ_UNFOLD_ANIM = 44;
private static final int NO_STATSD_LOGGING = -1;
@@ -237,6 +228,7 @@
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM,
};
private static volatile InteractionJankMonitor sInstance;
@@ -301,6 +293,7 @@
CUJ_SCREEN_OFF_SHOW_AOD,
CUJ_ONE_HANDED_ENTER_TRANSITION,
CUJ_ONE_HANDED_EXIT_TRANSITION,
+ CUJ_UNFOLD_ANIM,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -374,8 +367,7 @@
new ChoreographerWrapper(Choreographer.getInstance());
synchronized (mLock) {
- FrameTrackerListener eventsListener =
- (s, act) -> handleCujEvents(config.getContext(), act, s);
+ FrameTrackerListener eventsListener = (s, act) -> handleCujEvents(act, s);
return new FrameTracker(session, mWorker.getThreadHandler(),
threadedRenderer, viewRoot, surfaceControl, choreographer, mMetrics,
mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis,
@@ -383,24 +375,13 @@
}
}
- private void handleCujEvents(Context context, String action, Session session) {
+ private void handleCujEvents(String action, Session session) {
// Clear the running and timeout tasks if the end / cancel was fired within the tracker.
// Or we might have memory leaks.
if (needRemoveTasks(action, session)) {
removeTimeout(session.getCuj());
removeTracker(session.getCuj());
}
-
- // Notify the receivers if necessary.
- if (session.shouldNotify()) {
- if (context != null) {
- notifyEvents(context, action, session);
- } else {
- throw new IllegalArgumentException(
- "Can't notify cuj events due to lack of context: cuj="
- + session.getName() + ", action=" + action);
- }
- }
}
private boolean needRemoveTasks(String action, Session session) {
@@ -412,22 +393,6 @@
return badEnd || badCancel;
}
- /**
- * Notifies who may interest in some CUJ events.
- */
- @VisibleForTesting
- public void notifyEvents(Context context, String action, Session session) {
- if (action.equals(ACTION_SESSION_CANCEL)
- && session.getReason() == REASON_CANCEL_NOT_BEGUN) {
- return;
- }
- Intent intent = new Intent(action);
- intent.putExtra(BUNDLE_KEY_CUJ_NAME, getNameOfCuj(session.getCuj()));
- intent.putExtra(BUNDLE_KEY_TIMESTAMP, session.getTimeStamp());
- intent.addFlags(FLAG_RECEIVER_REGISTERED_ONLY);
- context.sendBroadcast(intent);
- }
-
private void removeTimeout(@CujType int cujType) {
synchronized (mLock) {
Runnable timeout = mTimeoutActions.get(cujType);
@@ -625,7 +590,17 @@
*/
public static String getNameOfInteraction(int interactionType) {
// There is an offset amount of 1 between cujType and interactionType.
- return getNameOfCuj(interactionType - 1);
+ return getNameOfCuj(getCujTypeFromInteraction(interactionType));
+ }
+
+ /**
+ * A helper method to translate interaction type to CUJ type.
+ *
+ * @param interactionType the interaction type defined in AtomsProto.java
+ * @return the integer in {@link CujType}
+ */
+ private static int getCujTypeFromInteraction(int interactionType) {
+ return interactionType - 1;
}
/**
@@ -724,6 +699,8 @@
return "ONE_HANDED_ENTER_TRANSITION";
case CUJ_ONE_HANDED_EXIT_TRANSITION:
return "ONE_HANDED_EXIT_TRANSITION";
+ case CUJ_UNFOLD_ANIM:
+ return "UNFOLD_ANIM";
}
return "UNKNOWN";
}
@@ -935,13 +912,11 @@
private final long mTimeStamp;
@Reasons
private int mReason = REASON_END_UNKNOWN;
- private final boolean mShouldNotify;
private final String mName;
public Session(@CujType int cujType, @NonNull String postfix) {
mCujType = cujType;
mTimeStamp = System.nanoTime();
- mShouldNotify = SystemProperties.getBoolean(PROP_NOTIFY_CUJ_EVENT, false);
mName = TextUtils.isEmpty(postfix)
? String.format("J<%s>", getNameOfCuj(mCujType))
: String.format("J<%s::%s>", getNameOfCuj(mCujType), postfix);
@@ -981,10 +956,5 @@
public @Reasons int getReason() {
return mReason;
}
-
- /** Determines if should notify the receivers of cuj events */
- public boolean shouldNotify() {
- return mShouldNotify;
- }
}
}
diff --git a/core/java/com/android/internal/util/dump/DumpableContainerImpl.java b/core/java/com/android/internal/util/dump/DumpableContainerImpl.java
new file mode 100644
index 0000000..d48b4b1
--- /dev/null
+++ b/core/java/com/android/internal/util/dump/DumpableContainerImpl.java
@@ -0,0 +1,139 @@
+/*
+ * 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.internal.util.dump;
+
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.Dumpable;
+import android.util.DumpableContainer;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+// TODO(b/149254050): add unit tests
+/**
+ * Helper class for {@link DumpableContainer} implementations - they can "implement it by
+ * association", i.e., by delegating the interface methods to a {@code DumpableContainerImpl}.
+ *
+ * @hide
+ */
+public final class DumpableContainerImpl implements DumpableContainer {
+
+ private static final String TAG = DumpableContainerImpl.class.getSimpleName();
+
+ private static final boolean DEBUG = false;
+
+ @Nullable
+ private final ArrayMap<String, Dumpable> mDumpables = new ArrayMap<>();
+
+ @Override
+ public boolean addDumpable(Dumpable dumpable) {
+ Objects.requireNonNull(dumpable, "dumpable");
+ String name = dumpable.getDumpableName();
+ Objects.requireNonNull(name, () -> "name of" + dumpable);
+
+ if (mDumpables.containsKey(name)) {
+ Log.e(TAG, "addDumpable(): ignoring " + dumpable + " as there is already a dumpable"
+ + " with that name (" + name + "): " + mDumpables.get(name));
+ return false;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Adding " + name + " -> " + dumpable);
+ }
+ mDumpables.put(name, dumpable);
+ return true;
+ }
+
+ /**
+ * Dumps the number of dumpable, without a newline.
+ */
+ private int dumpNumberDumpables(IndentingPrintWriter writer) {
+ int size = mDumpables == null ? 0 : mDumpables.size();
+ if (size == 0) {
+ writer.print("No dumpables");
+ } else {
+ writer.print(size); writer.print(" dumpables");
+ }
+ return size;
+ }
+
+ /**
+ * Lists the name of all dumpables to the given {@code writer}.
+ */
+ public void listDumpables(String prefix, PrintWriter writer) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(writer, prefix, prefix);
+
+ int size = dumpNumberDumpables(ipw);
+ if (size == 0) {
+ ipw.println();
+ return;
+ }
+ ipw.print(": ");
+ for (int i = 0; i < size; i++) {
+ ipw.print(mDumpables.keyAt(i));
+ if (i < size - 1) ipw.print(' ');
+ }
+ ipw.println();
+ }
+
+ /**
+ * Dumps the content of all dumpables to the given {@code writer}.
+ */
+ public void dumpAllDumpables(String prefix, PrintWriter writer, String[] args) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(writer, prefix, prefix);
+ int size = dumpNumberDumpables(ipw);
+ if (size == 0) {
+ ipw.println();
+ return;
+ }
+ ipw.println(": ");
+
+ for (int i = 0; i < size; i++) {
+ String dumpableName = mDumpables.keyAt(i);
+ ipw.print('#'); ipw.print(i); ipw.print(": "); ipw.println(dumpableName);
+ Dumpable dumpable = mDumpables.valueAt(i);
+ indentAndDump(ipw, dumpable, args);
+ }
+ }
+
+ private void indentAndDump(IndentingPrintWriter writer, Dumpable dumpable, String[] args) {
+ writer.increaseIndent();
+ try {
+ dumpable.dump(writer, args);
+ } finally {
+ writer.decreaseIndent();
+ }
+ }
+
+ /**
+ * Dumps the content of a specific dumpable to the given {@code writer}.
+ */
+ @SuppressWarnings("resource") // cannot close ipw as it would close writer
+ public void dumpOneDumpable(String prefix, PrintWriter writer, String dumpableName,
+ String[] args) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(writer, prefix, prefix);
+ Dumpable dumpable = mDumpables.get(dumpableName);
+ if (dumpable == null) {
+ ipw.print("No "); ipw.println(dumpableName);
+ return;
+ }
+ ipw.print(dumpableName); ipw.println(':');
+ indentAndDump(ipw, dumpable, args);
+ }
+}
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index 139660a..402fa64 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -19,6 +19,7 @@
import android.os.IBinder;
import android.os.ResultReceiver;
import android.view.InputChannel;
+import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputMethodSubtype;
@@ -36,7 +37,7 @@
*/
oneway interface IInputMethod {
void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
- int configChanges);
+ int configChanges, boolean stylusHwSupported);
void onCreateInlineSuggestionsRequest(in InlineSuggestionsRequestInfo requestInfo,
in IInlineSuggestionsRequestCallback cb);
@@ -59,4 +60,8 @@
void hideSoftInput(in IBinder hideInputToken, int flags, in ResultReceiver resultReceiver);
void changeInputMethodSubtype(in InputMethodSubtype subtype);
+
+ void canStartStylusHandwriting(int requestId);
+
+ void startStylusHandwriting(in InputChannel channel, in List<MotionEvent> events);
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 2dc7c42..0df3e87 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -81,4 +81,7 @@
void startImeTrace();
// Stops an ime trace.
void stopImeTrace();
+
+ /** Start Stylus handwriting session **/
+ void startStylusHandwriting(in IInputMethodClient client);
}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index d16d9c6..db4bc2c 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -24,6 +24,8 @@
import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.RecoveryCertPath;
import com.android.internal.widget.ICheckCredentialProgressCallback;
+import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
+import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
@@ -96,4 +98,10 @@
boolean tryUnlockWithCachedUnifiedChallenge(int userId);
void removeCachedUnifiedChallenge(int userId);
void updateEncryptionPassword(int type, in byte[] password);
+ boolean registerWeakEscrowTokenRemovedListener(in IWeakEscrowTokenRemovedListener listener);
+ boolean unregisterWeakEscrowTokenRemovedListener(in IWeakEscrowTokenRemovedListener listener);
+ long addWeakEscrowToken(in byte[] token, int userId, in IWeakEscrowTokenActivatedListener callback);
+ boolean removeWeakEscrowToken(long handle, int userId);
+ boolean isWeakEscrowTokenActive(long handle, int userId);
+ boolean isWeakEscrowTokenValid(long handle, in byte[] token, int userId);
}
diff --git a/media/java/android/media/tv/interactive/TvIAppInfo.aidl b/core/java/com/android/internal/widget/IWeakEscrowTokenActivatedListener.aidl
similarity index 71%
copy from media/java/android/media/tv/interactive/TvIAppInfo.aidl
copy to core/java/com/android/internal/widget/IWeakEscrowTokenActivatedListener.aidl
index 6041460..9c8d9d6 100644
--- a/media/java/android/media/tv/interactive/TvIAppInfo.aidl
+++ b/core/java/com/android/internal/widget/IWeakEscrowTokenActivatedListener.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,9 @@
* limitations under the License.
*/
-package android.media.tv.interactive;
+package com.android.internal.widget;
-parcelable TvIAppInfo;
\ No newline at end of file
+/** @hide */
+oneway interface IWeakEscrowTokenActivatedListener {
+ void onWeakEscrowTokenActivated(long handle, int userId);
+}
diff --git a/media/java/android/media/tv/interactive/TvIAppInfo.aidl b/core/java/com/android/internal/widget/IWeakEscrowTokenRemovedListener.aidl
similarity index 71%
copy from media/java/android/media/tv/interactive/TvIAppInfo.aidl
copy to core/java/com/android/internal/widget/IWeakEscrowTokenRemovedListener.aidl
index 6041460..7018048 100644
--- a/media/java/android/media/tv/interactive/TvIAppInfo.aidl
+++ b/core/java/com/android/internal/widget/IWeakEscrowTokenRemovedListener.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,9 @@
* limitations under the License.
*/
-package android.media.tv.interactive;
+package com.android.internal.widget;
-parcelable TvIAppInfo;
\ No newline at end of file
+/** @hide */
+oneway interface IWeakEscrowTokenRemovedListener {
+ void onWeakEscrowTokenRemoved(long handle, int userId);
+}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 5a03277..f91776e 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1236,6 +1236,28 @@
}
}
+ /** Register the given WeakEscrowTokenRemovedListener. */
+ public boolean registerWeakEscrowTokenRemovedListener(
+ @NonNull final IWeakEscrowTokenRemovedListener listener) {
+ try {
+ return getLockSettings().registerWeakEscrowTokenRemovedListener(listener);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not register WeakEscrowTokenRemovedListener.");
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Unregister the given WeakEscrowTokenRemovedListener. */
+ public boolean unregisterWeakEscrowTokenRemovedListener(
+ @NonNull final IWeakEscrowTokenRemovedListener listener) {
+ try {
+ return getLockSettings().unregisterWeakEscrowTokenRemovedListener(listener);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not register WeakEscrowTokenRemovedListener.");
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
public void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
try {
getLockSettings().reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
@@ -1355,15 +1377,38 @@
}
/**
+ * Create a weak escrow token for the current user, which can later be used to unlock FBE
+ * or change user password.
+ *
+ * After adding, if the user currently has lockscreen password, they will need to perform a
+ * confirm credential operation in order to activate the token for future use. If the user
+ * has no secure lockscreen, then the token is activated immediately.
+ *
+ * If the user changes or removes lockscreen password, activated weak escrow tokens will be
+ * removed.
+ *
+ * @return a unique 64-bit token handle which is needed to refer to this token later.
+ */
+ public long addWeakEscrowToken(byte[] token, int userId,
+ @NonNull IWeakEscrowTokenActivatedListener callback) {
+ try {
+ return getLockSettings().addWeakEscrowToken(token, userId, callback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not add weak token.");
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Callback interface to notify when an added escrow token has been activated.
*/
public interface EscrowTokenStateChangeCallback {
/**
* The method to be called when the token is activated.
* @param handle 64 bit handle corresponding to the escrow token
- * @param userid user for whom the escrow token has been added
+ * @param userId user for whom the escrow token has been added
*/
- void onEscrowTokenActivated(long handle, int userid);
+ void onEscrowTokenActivated(long handle, int userId);
}
/**
@@ -1379,6 +1424,21 @@
}
/**
+ * Remove a weak escrow token.
+ *
+ * @return true if the given handle refers to a valid weak token previously returned from
+ * {@link #addWeakEscrowToken}, whether it's active or not. return false otherwise.
+ */
+ public boolean removeWeakEscrowToken(long handle, int userId) {
+ try {
+ return getLockSettings().removeWeakEscrowToken(handle, userId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not remove the weak token.");
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Check if the given escrow token is active or not. Only active token can be used to call
* {@link #setLockCredentialWithToken} and {@link #unlockUserWithToken}
*
@@ -1389,6 +1449,29 @@
}
/**
+ * Check if the given weak escrow token is active or not. Only active token can be used to call
+ * {@link #setLockCredentialWithToken} and {@link #unlockUserWithToken}
+ */
+ public boolean isWeakEscrowTokenActive(long handle, int userId) {
+ try {
+ return getLockSettings().isWeakEscrowTokenActive(handle, userId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not check the weak token.");
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Check if the given weak escrow token is valid. */
+ public boolean isWeakEscrowTokenValid(long handle, byte[] token, int userId) {
+ try {
+ return getLockSettings().isWeakEscrowTokenValid(handle, token, userId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not validate the weak token.");
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Change a user's lock credential with a pre-configured escrow token.
*
* <p>This method is only available to code running in the system server process itself.
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index da62863..a3ac472 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -251,6 +251,7 @@
"spatializer-aidl-cpp",
"av-types-aidl-cpp",
"android.hardware.camera.device@3.2",
+ "libandroid_net",
"libandroidicu",
"libbattery",
"libbpf_android",
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index d0504fb..d039bcf 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -66,6 +66,7 @@
jfieldID flags;
//methods
jmethodID setType;
+ jmethodID setId;
jmethodID setUuid;
jmethodID init;
} gSensorOffsets;
@@ -112,6 +113,7 @@
sensorOffsets.flags = GetFieldIDOrDie(_env,sensorClass, "mFlags", "I");
sensorOffsets.setType = GetMethodIDOrDie(_env,sensorClass, "setType", "(I)Z");
+ sensorOffsets.setId = GetMethodIDOrDie(_env,sensorClass, "setId", "(I)V");
sensorOffsets.setUuid = GetMethodIDOrDie(_env,sensorClass, "setUuid", "(JJ)V");
sensorOffsets.init = GetMethodIDOrDie(_env,sensorClass, "<init>", "()V");
@@ -188,9 +190,10 @@
env->SetObjectField(sensor, sensorOffsets.stringType, stringType);
}
- // TODO(b/29547335): Rename "setUuid" method to "setId".
- int64_t id = nativeSensor.getId();
- env->CallVoidMethod(sensor, sensorOffsets.setUuid, id, 0);
+ int32_t id = nativeSensor.getId();
+ env->CallVoidMethod(sensor, sensorOffsets.setId, id);
+ Sensor::uuid_t uuid = nativeSensor.getUuid();
+ env->CallVoidMethod(sensor, sensorOffsets.setUuid, id, uuid.i64[0], uuid.i64[1]);
}
return sensor;
}
diff --git a/core/jni/android_server_NetworkManagementSocketTagger.cpp b/core/jni/android_server_NetworkManagementSocketTagger.cpp
index afad08a..1be1873 100644
--- a/core/jni/android_server_NetworkManagementSocketTagger.cpp
+++ b/core/jni/android_server_NetworkManagementSocketTagger.cpp
@@ -15,24 +15,23 @@
*/
#define LOG_TAG "NMST_QTagUidNative"
-#include <utils/Log.h>
-#include <nativehelper/JNIPlatformHelp.h>
-
-#include "jni.h"
-#include <utils/misc.h>
+#include <android/multinetwork.h>
#include <cutils/qtaguid.h>
-
#include <errno.h>
#include <fcntl.h>
-#include <sys/types.h>
+#include <nativehelper/JNIPlatformHelp.h>
#include <sys/socket.h>
+#include <sys/types.h>
+#include <utils/Log.h>
+#include <utils/misc.h>
+
+#include "jni.h"
namespace android {
-static jint QTagUid_tagSocketFd(JNIEnv* env, jclass,
- jobject fileDescriptor,
- jint tagNum, jint uid) {
+static jint tagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor,
+ jint tagNum, jint uid) {
int userFd = jniGetFDFromFileDescriptor(env, fileDescriptor);
if (env->ExceptionCheck()) {
@@ -40,15 +39,14 @@
return (jint)-1;
}
- int res = qtaguid_tagSocket(userFd, tagNum, uid);
+ int res = android_tag_socket_with_uid(userFd, tagNum, uid);
if (res < 0) {
return (jint)-errno;
}
return (jint)res;
}
-static jint QTagUid_untagSocketFd(JNIEnv* env, jclass,
- jobject fileDescriptor) {
+static jint untagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor) {
int userFd = jniGetFDFromFileDescriptor(env, fileDescriptor);
if (env->ExceptionCheck()) {
@@ -56,16 +54,14 @@
return (jint)-1;
}
- int res = qtaguid_untagSocket(userFd);
+ int res = android_untag_socket(userFd);
if (res < 0) {
return (jint)-errno;
}
return (jint)res;
}
-static jint QTagUid_setCounterSet(JNIEnv* env, jclass,
- jint setNum, jint uid) {
-
+static jint setCounterSet(JNIEnv* env, jclass, jint setNum, jint uid) {
int res = qtaguid_setCounterSet(setNum, uid);
if (res < 0) {
return (jint)-errno;
@@ -73,9 +69,7 @@
return (jint)res;
}
-static jint QTagUid_deleteTagData(JNIEnv* env, jclass,
- jint tagNum, jint uid) {
-
+static jint deleteTagData(JNIEnv* env, jclass, jint tagNum, jint uid) {
int res = qtaguid_deleteTagData(tagNum, uid);
if (res < 0) {
return (jint)-errno;
@@ -84,10 +78,10 @@
}
static const JNINativeMethod gQTagUidMethods[] = {
- { "native_tagSocketFd", "(Ljava/io/FileDescriptor;II)I", (void*)QTagUid_tagSocketFd},
- { "native_untagSocketFd", "(Ljava/io/FileDescriptor;)I", (void*)QTagUid_untagSocketFd},
- { "native_setCounterSet", "(II)I", (void*)QTagUid_setCounterSet},
- { "native_deleteTagData", "(II)I", (void*)QTagUid_deleteTagData},
+ { "native_tagSocketFd", "(Ljava/io/FileDescriptor;II)I", (void*)tagSocketFd},
+ { "native_untagSocketFd", "(Ljava/io/FileDescriptor;)I", (void*)untagSocketFd},
+ { "native_setCounterSet", "(II)I", (void*)setCounterSet},
+ { "native_deleteTagData", "(II)I", (void*)deleteTagData},
};
int register_android_server_NetworkManagementSocketTagger(JNIEnv* env) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ae5414d..d6bbe71 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2182,8 +2182,9 @@
<permission android:name="android.permission.NFC_HANDOVER_STATUS"
android:protectionLevel="signature|privileged" />
- <!-- @hide Allows internal management of Bluetooth state when on wireless consent mode.
- <p>Not for use by third-party applications. -->
+ <!-- @SystemApi Allows internal management of Bluetooth state when on wireless consent mode.
+ <p>Not for use by third-party applications.
+ @hide -->
<permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED"
android:protectionLevel="signature" />
@@ -2791,6 +2792,10 @@
<permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
android:protectionLevel="signature|appop" />
+ <!-- @SystemApi @hide Allows starting activities across profiles in the same profile group. -->
+ <permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"
+ android:protectionLevel="signature|role" />
+
<!-- @SystemApi Allows configuring apps to have the INTERACT_ACROSS_PROFILES permission so that
they can interact across profiles in the same profile group.
@hide -->
@@ -3590,6 +3595,18 @@
<permission android:name="android.permission.REQUEST_INCIDENT_REPORT_APPROVAL"
android:protectionLevel="signature|privileged" />
+ <!-- ========================================= -->
+ <!-- Permissions for SupplementalApi -->
+ <!-- ========================================= -->
+ <eat-comment />
+
+ <!-- TODO(b/213488783): Update with correct names. -->
+ <!-- Allows an application to access SupplementalApis. -->
+ <permission android:name="android.permission.ACCESS_SUPPLEMENTAL_APIS"
+ android:label="@string/permlab_accessSupplementalApi"
+ android:description="@string/permdesc_accessSupplementalApi"
+ android:protectionLevel="normal" />
+
<!-- ==================================== -->
<!-- Private permissions -->
<!-- ==================================== -->
@@ -5329,6 +5346,12 @@
<permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows an application to manage weak escrow token on the device. This permission
+ is not available to third party applications.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_WEAK_ESCROW_TOKEN"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows an application to listen to trust changes. Only allowed for system processes.
@hide -->
<permission android:name="android.permission.TRUST_LISTENER"
@@ -6521,6 +6544,11 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
+ <service android:name="com.android.server.SmartStorageMaintIdler"
+ android:exported="true"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
<service android:name="com.android.server.ZramWriteback"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" >
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 86c83c5..2267781 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3638,7 +3638,22 @@
to re-retrieve all resources (including view layouts, drawables, etc)
to correctly handle any configuration change.-->
<attr name="configChanges" />
- <!-- Specifies whether the IME supports Handwriting using stylus. Defaults to false. -->
+ <!-- Specifies whether the IME supports Handwriting using stylus. Defaults to false.
+ When IME implements support for stylus handwriting, on every ACTION_DOWN with stylus
+ on an editor,
+ {@link android.inputmethodservice.InputMethodService#onStartStylusHandwriting()}
+ is called.
+ If IME is ready for stylus input, it must return {@code true} for Handwriting sessions
+ to start. IME should attach it's View that renders Ink on screen to stylus handwriting
+ inking window
+ {@link android.inputmethodservice.InputMethodService#getStylusHandwritingWindow()}.
+ IME will then receive Stylus MotionEvent(s) on DecorView i.e. the Inking view
+ {@link android.view.View#onTouchEvent(MotionEvent)} attached by IME to Ink window.
+ Handwriting mode can be finished by calling
+ {@link android.inputmethodservice.InputMethodService#finishStylusHandwriting()} or will
+ be finished by framework on next
+ {@link android.inputmethodservice.InputMethodService#onFinishInput()}.
+ -->
<attr name="supportsStylusHandwriting" format="boolean" />
</declare-styleable>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index db24475..f7e0fcf 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2825,6 +2825,14 @@
<attr name="path" />
<attr name="minSdkVersion" />
<attr name="maxSdkVersion" />
+ <!-- The order in which the apex system services are initiated. When there are dependencies
+ among apex system services, setting this attribute for each of them ensures that they are
+ created in the order required by those dependencies. The apex-system-services that are
+ started manually within SystemServer ignore the initOrder and are not considered for
+ automatic starting of the other services.
+ The value is a simple integer, with higher number being initialized first. If not specified,
+ the default order is 0. -->
+ <attr name="initOrder" format="integer" />
</declare-styleable>
<!-- The <code>receiver</code> tag declares an
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2836c98..f4b7b73 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2695,10 +2695,27 @@
<!-- Configure mobile tcp buffer sizes in the form:
rat-name:rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max
If no value is found for the rat-name in use, the system default will be applied.
+
+ This is deprecated. Please use config_tcp_buffers.
-->
<string-array name="config_mobile_tcp_buffers">
</string-array>
+ <!-- Configure tcp buffer sizes in the form:
+ rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max
+ If this is configured as an empty string, the system default will be applied.
+
+ For now this config is used by mobile data only. In the future it should be
+ used by Wi-Fi as well.
+
+ Note that starting from Android 13, the TCP buffer size is fixed after boot up, and should
+ never be changed based on carriers or the network types. The value should be configured
+ appropriately based on the device's memory and performance. It is recommended to use lower
+ values if the device has low memory or doesn't support high-speed network such like LTE,
+ NR, or Wifi.
+ -->
+ <string name="config_tcp_buffers" translatable="false"></string>
+
<!-- Configure ethernet tcp buffersizes in the form:
rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max -->
<string name="config_ethernet_tcp_buffers" translatable="false">524288,1048576,3145728,524288,1048576,2097152</string>
@@ -2783,6 +2800,16 @@
<item>350</item>
</integer-array>
+ <!-- A vibration waveform for notifications that specify DEFAULT_VIBRATE.
+ This value is a float array with values grouped as
+ { targetAmplitude (within [0,1]), targetFrequency (in hertz), duration (in milliseconds) }
+ This is only applied on devices with vibration frequency control. If the device doesn't
+ support frequency control, then the vibration specified in
+ config_defaultNotificationVibePattern is used instead.
+ -->
+ <array name="config_defaultNotificationVibeWaveform">
+ </array>
+
<!-- Vibrator pattern to be used as the default for notifications
that do not specify vibration but vibrate anyway because the device
is in vibrate mode.
@@ -2794,6 +2821,16 @@
<item>100</item>
</integer-array>
+ <!-- A vibration waveform for notifications that do not specify vibration but vibrate anyway,
+ because the device is in vibrate mode. This value is a float array with values grouped as
+ { targetAmplitude (within [0,1]), targetFrequency (in hertz), duration (in milliseconds) }
+ This is only applied on devices with vibration frequency control. If the device doesn't
+ support frequency control, then the vibration specified in
+ config_notificationFallbackVibePattern is used instead.
+ -->
+ <array name="config_notificationFallbackVibeWaveform">
+ </array>
+
<!-- Flag indicating if the speed up audio on mt call code should be executed -->
<bool name="config_speed_up_audio_on_mt_calls">false</bool>
@@ -5567,4 +5604,7 @@
<!-- Determines whether SafetyCenter feature is enabled. -->
<bool name="config_enableSafetyCenter">true</bool>
+
+ <!-- Flag indicating if help links for Settings app should be enabled. -->
+ <bool name="config_settingsHelpLinksEnabled">false</bool>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 5fa7409..574c2f7 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3317,8 +3317,6 @@
</staging-public-group>
<staging-public-group type="bool" first-id="0x01cf0000">
- <!-- @hide @SystemApi -->
- <public name="config_systemCaptionsServiceCallsEnabled" />
<!-- @hide @TestApi -->
<public name="config_preventImeStartupUnlessTextEditor" />
</staging-public-group>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 2879759..6577ebc 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4046,11 +4046,16 @@
<!-- Description of an application permission that lets it ask user to ignore battery optimizations for that app-->
<string name="permdesc_requestIgnoreBatteryOptimizations">Allows an app to ask for permission to ignore battery optimizations for that app.</string>
- <!-- Title of an application permission that lets query all other packages. [CHAR LIMIT=NONE] -->
+ <!-- Title of an application permission that lets it query all other packages. [CHAR LIMIT=NONE] -->
<string name="permlab_queryAllPackages">query all packages</string>
<!-- Description of an application permission that lets it query all other packages. [CHAR LIMIT=NONE] -->
<string name="permdesc_queryAllPackages">Allows an app to see all installed packages.</string>
+ <!-- Title of an application permission that lets it access SupplementalApis. [CHAR LIMIT=NONE] -->
+ <string name="permlab_accessSupplementalApi">access SupplementalApis</string>
+ <!-- Description of an application permission that lets it access SupplementalApis. [CHAR LIMIT=NONE]-->
+ <string name="permdesc_accessSupplementalApi">Allows an application to access SupplementalApis.</string>
+
<!-- Shown in the tutorial for tap twice for zoom control. -->
<string name="tutorial_double_tap_to_zoom_message_short">Tap twice for zoom control</string>
diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml
index dcfca84..3b2f244 100644
--- a/core/res/res/values/styles_device_defaults.xml
+++ b/core/res/res/values/styles_device_defaults.xml
@@ -42,7 +42,6 @@
<item name="outlineSpotShadowColor">@color/btn_colored_background_material</item>
<item name="textAppearance">?attr/textAppearanceButton</item>
<item name="textColor">@color/btn_colored_text_material</item>
- <item name="drawableTint">@color/btn_colored_text_material</item>
</style>
<style name="Widget.DeviceDefault.TextView" parent="Widget.Material.TextView" />
<style name="Widget.DeviceDefault.CheckedTextView" parent="Widget.Material.CheckedTextView"/>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a4b5d3c..4e77563 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -481,6 +481,7 @@
<java-symbol type="integer" name="config_safe_media_volume_usb_mB" />
<java-symbol type="integer" name="config_mobile_mtu" />
<java-symbol type="array" name="config_mobile_tcp_buffers" />
+ <java-symbol type="string" name="config_tcp_buffers" />
<java-symbol type="integer" name="config_volte_replacement_rat"/>
<java-symbol type="integer" name="config_valid_wappush_index" />
<java-symbol type="integer" name="config_overrideHasPermanentMenuKey" />
@@ -1935,7 +1936,9 @@
<java-symbol type="array" name="config_locationExtraPackageNames" />
<java-symbol type="array" name="config_testLocationProviders" />
<java-symbol type="array" name="config_defaultNotificationVibePattern" />
+ <java-symbol type="array" name="config_defaultNotificationVibeWaveform" />
<java-symbol type="array" name="config_notificationFallbackVibePattern" />
+ <java-symbol type="array" name="config_notificationFallbackVibeWaveform" />
<java-symbol type="bool" name="config_enableServerNotificationEffectsForAutomotive" />
<java-symbol type="bool" name="config_useAttentionLight" />
<java-symbol type="bool" name="config_adaptive_sleep_available" />
@@ -2338,6 +2341,7 @@
<java-symbol type="string" name="nas_upgrade_notification_disable_action" />
<java-symbol type="string" name="nas_upgrade_notification_learn_more_action" />
<java-symbol type="string" name="nas_upgrade_notification_learn_more_content" />
+ <java-symbol type="bool" name="config_settingsHelpLinksEnabled" />
<!-- ImfTest -->
<java-symbol type="layout" name="auto_complete_list" />
@@ -3650,6 +3654,8 @@
<java-symbol type="string" name="config_retailDemoPackage" />
<java-symbol type="string" name="config_retailDemoPackageSignature" />
+ <java-symbol type="bool" name="config_systemCaptionsServiceCallsEnabled" />
+
<java-symbol type="string" name="notification_channel_foreground_service" />
<java-symbol type="string" name="foreground_service_app_in_background" />
<java-symbol type="string" name="foreground_service_apps_in_background" />
diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
index 0d2d047..a409129 100644
--- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
@@ -176,7 +176,6 @@
private InteractionJankMonitor createMockedInteractionJankMonitor() {
InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker));
doReturn(true).when(monitor).shouldMonitor(anyInt());
- doNothing().when(monitor).notifyEvents(any(), any(), any());
return monitor;
}
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
index 50e8474..b659f37 100644
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
@@ -21,13 +21,16 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.UserInfo;
+import android.os.RemoteException;
import android.os.UserManager;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
@@ -38,12 +41,16 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
+import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
import com.android.internal.widget.LockPatternUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
+import java.nio.charset.StandardCharsets;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class LockPatternUtilsTest {
@@ -102,4 +109,84 @@
configureTest(false, true, 0);
assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
}
+
+ @Test
+ public void testAddWeakEscrowToken() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
+ int testUserId = 10;
+ IWeakEscrowTokenActivatedListener listener = createWeakEscrowTokenListener();
+ mLockPatternUtils.addWeakEscrowToken(testToken, testUserId, listener);
+ verify(ils).addWeakEscrowToken(eq(testToken), eq(testUserId), eq(listener));
+ }
+
+ @Test
+ public void testRegisterWeakEscrowTokenRemovedListener() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
+ mLockPatternUtils.registerWeakEscrowTokenRemovedListener(testListener);
+ verify(ils).registerWeakEscrowTokenRemovedListener(eq(testListener));
+ }
+
+ @Test
+ public void testUnregisterWeakEscrowTokenRemovedListener() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
+ mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(testListener);
+ verify(ils).unregisterWeakEscrowTokenRemovedListener(eq(testListener));
+ }
+
+ @Test
+ public void testRemoveAutoEscrowToken() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ int testUserId = 10;
+ long testHandle = 100L;
+ mLockPatternUtils.removeWeakEscrowToken(testHandle, testUserId);
+ verify(ils).removeWeakEscrowToken(eq(testHandle), eq(testUserId));
+ }
+
+ @Test
+ public void testIsAutoEscrowTokenActive() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ int testUserId = 10;
+ long testHandle = 100L;
+ mLockPatternUtils.isWeakEscrowTokenActive(testHandle, testUserId);
+ verify(ils).isWeakEscrowTokenActive(eq(testHandle), eq(testUserId));
+ }
+
+ @Test
+ public void testIsAutoEscrowTokenValid() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ int testUserId = 10;
+ byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
+ long testHandle = 100L;
+ mLockPatternUtils.isWeakEscrowTokenValid(testHandle, testToken, testUserId);
+ verify(ils).isWeakEscrowTokenValid(eq(testHandle), eq(testToken), eq(testUserId));
+ }
+
+ private ILockSettings createTestLockSettings() {
+ final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+ mLockPatternUtils = spy(new LockPatternUtils(context));
+ final ILockSettings ils = Mockito.mock(ILockSettings.class);
+ when(mLockPatternUtils.getLockSettings()).thenReturn(ils);
+ return ils;
+ }
+
+ private IWeakEscrowTokenActivatedListener createWeakEscrowTokenListener() {
+ return new IWeakEscrowTokenActivatedListener.Stub() {
+ @Override
+ public void onWeakEscrowTokenActivated(long handle, int userId) {
+ // Do nothing.
+ }
+ };
+ }
+
+ private IWeakEscrowTokenRemovedListener createTestAutoEscrowTokenRemovedListener() {
+ return new IWeakEscrowTokenRemovedListener.Stub() {
+ @Override
+ public void onWeakEscrowTokenRemoved(long handle, int userId) {
+ // Do nothing.
+ }
+ };
+ }
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index ee0fb44..6f5951b 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -392,6 +392,7 @@
<permission name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"/>
<permission name="android.permission.SET_WALLPAPER" />
<permission name="android.permission.SET_WALLPAPER_COMPONENT" />
+ <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT" />
<permission name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" />
<!-- Permissions required for Incremental CTS tests -->
<permission name="com.android.permission.USE_INSTALLER_V2"/>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 9584994..535d656 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -3691,6 +3691,18 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1874559932": {
+ "message": "The TaskDisplayArea with %s does not exist.",
+ "level": "WARN",
+ "group": "WM_DEBUG_WINDOW_ORGANIZER",
+ "at": "com\/android\/server\/wm\/DisplayAreaPolicyBuilder.java"
+ },
+ "1884961873": {
+ "message": "Sleep still need to stop %d activities",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"1891501279": {
"message": "cancelAnimation(): reason=%s",
"level": "DEBUG",
diff --git a/identity/java/android/security/identity/CredentialDataRequest.java b/identity/java/android/security/identity/CredentialDataRequest.java
new file mode 100644
index 0000000..2a47a02
--- /dev/null
+++ b/identity/java/android/security/identity/CredentialDataRequest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * An object representing a request for credential data.
+ */
+public class CredentialDataRequest {
+ CredentialDataRequest() {}
+
+ /**
+ * Gets the device-signed entries to request.
+ *
+ * @return the device-signed entries to request.
+ */
+ public @NonNull Map<String, Collection<String>> getDeviceSignedEntriesToRequest() {
+ return mDeviceSignedEntriesToRequest;
+ }
+
+ /**
+ * Gets the issuer-signed entries to request.
+ *
+ * @return the issuer-signed entries to request.
+ */
+ public @NonNull Map<String, Collection<String>> getIssuerSignedEntriesToRequest() {
+ return mIssuerSignedEntriesToRequest;
+ }
+
+ /**
+ * Gets whether to allow using an authentication key which use count has been exceeded.
+ *
+ * <p>By default this is set to true.
+ *
+ * @return whether to allow using an authentication key which use
+ * count has been exceeded if no other key is available.
+ */
+ public boolean isAllowUsingExhaustedKeys() {
+ return mAllowUsingExhaustedKeys;
+ }
+
+ /**
+ * Gets whether to allow using an authentication key which is expired.
+ *
+ * <p>By default this is set to false.
+ *
+ * @return whether to allow using an authentication key which is
+ * expired if no other key is available.
+ */
+ public boolean isAllowUsingExpiredKeys() {
+ return mAllowUsingExpiredKeys;
+ }
+
+ /**
+ * Gets whether to increment the use-count for the authentication key used.
+ *
+ * <p>By default this is set to true.
+ *
+ * @return whether to increment the use count of the authentication key used.
+ */
+ public boolean isIncrementUseCount() {
+ return mIncrementUseCount;
+ }
+
+ /**
+ * Gets the request message CBOR.
+ *
+ * <p>This data structure is described in the documentation for the
+ * {@link PresentationSession#getCredentialData(String, CredentialDataRequest)} method.
+ *
+ * @return the request message CBOR as described above.
+ */
+ public @Nullable byte[] getRequestMessage() {
+ return mRequestMessage;
+ }
+
+ /**
+ * Gets the reader signature.
+ *
+ * <p>This data structure is described in the documentation for the
+ * {@link PresentationSession#getCredentialData(String, CredentialDataRequest)} method.
+ *
+ * @return a {@code COSE_Sign1} structure as described above.
+ */
+ public @Nullable byte[] getReaderSignature() {
+ return mReaderSignature;
+ }
+
+ Map<String, Collection<String>> mDeviceSignedEntriesToRequest = new LinkedHashMap<>();
+ Map<String, Collection<String>> mIssuerSignedEntriesToRequest = new LinkedHashMap<>();
+ boolean mAllowUsingExhaustedKeys = true;
+ boolean mAllowUsingExpiredKeys = false;
+ boolean mIncrementUseCount = true;
+ byte[] mRequestMessage = null;
+ byte[] mReaderSignature = null;
+
+ /**
+ * A builder for {@link CredentialDataRequest}.
+ */
+ public static final class Builder {
+ private CredentialDataRequest mData;
+
+ /**
+ * Creates a new builder.
+ */
+ public Builder() {
+ mData = new CredentialDataRequest();
+ }
+
+ /**
+ * Sets the device-signed entries to request.
+ *
+ * @param entriesToRequest the device-signed entries to request.
+ */
+ public @NonNull Builder setDeviceSignedEntriesToRequest(
+ @NonNull Map<String, Collection<String>> entriesToRequest) {
+ mData.mDeviceSignedEntriesToRequest = entriesToRequest;
+ return this;
+ }
+
+ /**
+ * Sets the issuer-signed entries to request.
+ *
+ * @param entriesToRequest the issuer-signed entries to request.
+ * @return the builder.
+ */
+ public @NonNull Builder setIssuerSignedEntriesToRequest(
+ @NonNull Map<String, Collection<String>> entriesToRequest) {
+ mData.mIssuerSignedEntriesToRequest = entriesToRequest;
+ return this;
+ }
+
+ /**
+ * Sets whether to allow using an authentication key which use count has been exceeded.
+ *
+ * By default this is set to true.
+ *
+ * @param allowUsingExhaustedKeys whether to allow using an authentication key which use
+ * count has been exceeded if no other key is available.
+ * @return the builder.
+ */
+ public @NonNull Builder setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys) {
+ mData.mAllowUsingExhaustedKeys = allowUsingExhaustedKeys;
+ return this;
+ }
+
+ /**
+ * Sets whether to allow using an authentication key which is expired.
+ *
+ * By default this is set to false.
+ *
+ * @param allowUsingExpiredKeys whether to allow using an authentication key which is
+ * expired if no other key is available.
+ * @return the builder.
+ */
+ public @NonNull Builder setAllowUsingExpiredKeys(boolean allowUsingExpiredKeys) {
+ mData.mAllowUsingExpiredKeys = allowUsingExpiredKeys;
+ return this;
+ }
+
+ /**
+ * Sets whether to increment the use-count for the authentication key used.
+ *
+ * By default this is set to true.
+ *
+ * @param incrementUseCount whether to increment the use count of the authentication
+ * key used.
+ * @return the builder.
+ */
+ public @NonNull Builder setIncrementUseCount(boolean incrementUseCount) {
+ mData.mIncrementUseCount = incrementUseCount;
+ return this;
+ }
+
+ /**
+ * Sets the request message CBOR.
+ *
+ * <p>This data structure is described in the documentation for the
+ * {@link PresentationSession#getCredentialData(String, CredentialDataRequest)} method.
+ *
+ * @param requestMessage the request message CBOR as described above.
+ * @return the builder.
+ */
+ public @NonNull Builder setRequestMessage(@NonNull byte[] requestMessage) {
+ mData.mRequestMessage = requestMessage;
+ return this;
+ }
+
+ /**
+ * Sets the reader signature.
+ *
+ * <p>This data structure is described in the documentation for the
+ * {@link PresentationSession#getCredentialData(String, CredentialDataRequest)} method.
+ *
+ * @param readerSignature a {@code COSE_Sign1} structure as described above.
+ * @return the builder.
+ */
+ public @NonNull Builder setReaderSignature(@NonNull byte[] readerSignature) {
+ mData.mReaderSignature = readerSignature;
+ return this;
+ }
+
+ /**
+ * Finishes building a {@link CredentialDataRequest}.
+ *
+ * @return the {@link CredentialDataRequest} object.
+ */
+ public @NonNull CredentialDataRequest build() {
+ return mData;
+ }
+ }
+}
diff --git a/identity/java/android/security/identity/CredentialDataResult.java b/identity/java/android/security/identity/CredentialDataResult.java
new file mode 100644
index 0000000..beb03af
--- /dev/null
+++ b/identity/java/android/security/identity/CredentialDataResult.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.identity;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+import java.util.Collection;
+
+
+/**
+ * An object that contains the result of retrieving data from a credential. This is used to return
+ * data requested in a {@link PresentationSession}.
+ */
+public abstract class CredentialDataResult {
+ /**
+ * @hide
+ */
+ protected CredentialDataResult() {}
+
+ /**
+ * Returns a CBOR structure containing the retrieved device-signed data.
+ *
+ * <p>This structure - along with the session transcript - may be cryptographically
+ * authenticated to prove to the reader that the data is from a trusted credential and
+ * {@link #getDeviceMac()} can be used to get a MAC.
+ *
+ * <p>The CBOR structure which is cryptographically authenticated is the
+ * {@code DeviceAuthenticationBytes} structure according to the following
+ * <a href="https://tools.ietf.org/html/rfc8610">CDDL</a> schema:
+ *
+ * <pre>
+ * DeviceAuthentication = [
+ * "DeviceAuthentication",
+ * SessionTranscript,
+ * DocType,
+ * DeviceNameSpacesBytes
+ * ]
+ *
+ * DocType = tstr
+ * SessionTranscript = any
+ * DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces)
+ * DeviceAuthenticationBytes = #6.24(bstr .cbor DeviceAuthentication)
+ * </pre>
+ *
+ * <p>where
+ *
+ * <pre>
+ * DeviceNameSpaces = {
+ * * NameSpace => DeviceSignedItems
+ * }
+ *
+ * DeviceSignedItems = {
+ * + DataItemName => DataItemValue
+ * }
+ *
+ * NameSpace = tstr
+ * DataItemName = tstr
+ * DataItemValue = any
+ * </pre>
+ *
+ * <p>The returned data is the binary encoding of the {@code DeviceNameSpaces} structure
+ * as defined above.
+ *
+ * @return The bytes of the {@code DeviceNameSpaces} CBOR structure.
+ */
+ public abstract @NonNull byte[] getDeviceNameSpaces();
+
+ /**
+ * Returns a message authentication code over the {@code DeviceAuthenticationBytes} CBOR
+ * specified in {@link #getDeviceNameSpaces()}, to prove to the reader that the data
+ * is from a trusted credential.
+ *
+ * <p>The MAC proves to the reader that the data is from a trusted credential. This code is
+ * produced by using the key agreement and key derivation function from the ciphersuite
+ * with the authentication private key and the reader ephemeral public key to compute a
+ * shared message authentication code (MAC) key, then using the MAC function from the
+ * ciphersuite to compute a MAC of the authenticated data. See section 9.2.3.5 of
+ * ISO/IEC 18013-5 for details of this operation.
+ *
+ * <p>If the session transcript or reader ephemeral key wasn't set on the {@link
+ * PresentationSession} used to obtain this data no message authencation code will be produced
+ * and this method will return {@code null}.
+ *
+ * @return A COSE_Mac0 structure with the message authentication code as described above
+ * or {@code null} if the conditions specified above are not met.
+ */
+ public abstract @Nullable byte[] getDeviceMac();
+
+ /**
+ * Returns the static authentication data associated with the dynamic authentication
+ * key used to MAC the data returned by {@link #getDeviceNameSpaces()}.
+ *
+ * @return The static authentication data associated with dynamic authentication key used to
+ * MAC the data.
+ */
+ public abstract @NonNull byte[] getStaticAuthenticationData();
+
+ /**
+ * Gets the device-signed entries that was returned.
+ *
+ * @return an object to examine the entries returned.
+ */
+ public abstract @NonNull Entries getDeviceSignedEntries();
+
+ /**
+ * Gets the issuer-signed entries that was returned.
+ *
+ * @return an object to examine the entries returned.
+ */
+ public abstract @NonNull Entries getIssuerSignedEntries();
+
+ /**
+ * A class for representing data elements returned.
+ */
+ public interface Entries {
+ /** Value was successfully retrieved. */
+ int STATUS_OK = 0;
+
+ /** The entry does not exist. */
+ int STATUS_NO_SUCH_ENTRY = 1;
+
+ /** The entry was not requested. */
+ int STATUS_NOT_REQUESTED = 2;
+
+ /** The entry wasn't in the request message. */
+ int STATUS_NOT_IN_REQUEST_MESSAGE = 3;
+
+ /** The entry was not retrieved because user authentication failed. */
+ int STATUS_USER_AUTHENTICATION_FAILED = 4;
+
+ /** The entry was not retrieved because reader authentication failed. */
+ int STATUS_READER_AUTHENTICATION_FAILED = 5;
+
+ /**
+ * The entry was not retrieved because it was configured without any access
+ * control profile.
+ */
+ int STATUS_NO_ACCESS_CONTROL_PROFILES = 6;
+
+ /**
+ * Gets the names of namespaces with retrieved entries.
+ *
+ * @return collection of name of namespaces containing retrieved entries. May be empty if no
+ * data was retrieved.
+ */
+ @NonNull Collection<String> getNamespaces();
+
+ /**
+ * Get the names of all requested entries in a name space.
+ *
+ * <p>This includes the name of entries that wasn't successfully retrieved.
+ *
+ * @param namespaceName the namespace name to get entries for.
+ * @return A collection of names for the given namespace or the empty collection if no
+ * entries was returned for the given name space.
+ */
+ @NonNull Collection<String> getEntryNames(@NonNull String namespaceName);
+
+ /**
+ * Get the names of all entries that was successfully retrieved from a name space.
+ *
+ * <p>This only return entries for which {@link #getStatus(String, String)} will return
+ * {@link #STATUS_OK}.
+ *
+ * @param namespaceName the namespace name to get entries for.
+ * @return The entries in the given namespace that were successfully rerieved or the
+ * empty collection if no entries was returned for the given name space.
+ */
+ @NonNull Collection<String> getRetrievedEntryNames(@NonNull String namespaceName);
+
+ /**
+ * Gets the status of an entry.
+ *
+ * <p>This returns {@link #STATUS_OK} if the value was retrieved, {@link
+ * #STATUS_NO_SUCH_ENTRY} if the given entry wasn't retrieved, {@link
+ * #STATUS_NOT_REQUESTED} if it wasn't requested, {@link #STATUS_NOT_IN_REQUEST_MESSAGE} if
+ * the request message was set but the entry wasn't present in the request message, {@link
+ * #STATUS_USER_AUTHENTICATION_FAILED} if the value wasn't retrieved because the necessary
+ * user authentication wasn't performed, {@link #STATUS_READER_AUTHENTICATION_FAILED} if
+ * the supplied reader certificate chain didn't match the set of certificates the entry was
+ * provisioned with, or {@link #STATUS_NO_ACCESS_CONTROL_PROFILES} if the entry was
+ * configured without any access control profiles.
+ *
+ * @param namespaceName the namespace name of the entry.
+ * @param name the name of the entry to get the value for.
+ * @return the status indicating whether the value was retrieved and if not, why.
+ */
+ @Status int getStatus(@NonNull String namespaceName, @NonNull String name);
+
+ /**
+ * Gets the raw CBOR data for the value of an entry.
+ *
+ * <p>This should only be called on an entry for which the {@link #getStatus(String,
+ * String)} method returns {@link #STATUS_OK}.
+ *
+ * @param namespaceName the namespace name of the entry.
+ * @param name the name of the entry to get the value for.
+ * @return the raw CBOR data or {@code null} if no entry with the given name exists.
+ */
+ @Nullable byte[] getEntry(@NonNull String namespaceName, @NonNull String name);
+
+ /**
+ * The type of the entry status.
+ * @hide
+ */
+ @Retention(SOURCE)
+ @IntDef({STATUS_OK, STATUS_NO_SUCH_ENTRY, STATUS_NOT_REQUESTED,
+ STATUS_NOT_IN_REQUEST_MESSAGE, STATUS_USER_AUTHENTICATION_FAILED,
+ STATUS_READER_AUTHENTICATION_FAILED, STATUS_NO_ACCESS_CONTROL_PROFILES})
+ @interface Status {}
+ }
+
+}
diff --git a/identity/java/android/security/identity/CredstoreCredentialDataResult.java b/identity/java/android/security/identity/CredstoreCredentialDataResult.java
new file mode 100644
index 0000000..7afe3d4
--- /dev/null
+++ b/identity/java/android/security/identity/CredstoreCredentialDataResult.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.LinkedList;
+
+class CredstoreCredentialDataResult extends CredentialDataResult {
+
+ ResultData mDeviceSignedResult;
+ ResultData mIssuerSignedResult;
+ CredstoreEntries mDeviceSignedEntries;
+ CredstoreEntries mIssuerSignedEntries;
+
+ CredstoreCredentialDataResult(ResultData deviceSignedResult, ResultData issuerSignedResult) {
+ mDeviceSignedResult = deviceSignedResult;
+ mIssuerSignedResult = issuerSignedResult;
+ mDeviceSignedEntries = new CredstoreEntries(deviceSignedResult);
+ mIssuerSignedEntries = new CredstoreEntries(issuerSignedResult);
+ }
+
+ @Override
+ public @NonNull byte[] getDeviceNameSpaces() {
+ return mDeviceSignedResult.getAuthenticatedData();
+ }
+
+ @Override
+ public @Nullable byte[] getDeviceMac() {
+ return mDeviceSignedResult.getMessageAuthenticationCode();
+ }
+
+ @Override
+ public @NonNull byte[] getStaticAuthenticationData() {
+ return mDeviceSignedResult.getStaticAuthenticationData();
+ }
+
+ @Override
+ public @NonNull CredentialDataResult.Entries getDeviceSignedEntries() {
+ return mDeviceSignedEntries;
+ }
+
+ @Override
+ public @NonNull CredentialDataResult.Entries getIssuerSignedEntries() {
+ return mIssuerSignedEntries;
+ }
+
+ static class CredstoreEntries implements CredentialDataResult.Entries {
+ ResultData mResultData;
+
+ CredstoreEntries(ResultData resultData) {
+ mResultData = resultData;
+ }
+
+ @Override
+ public @NonNull Collection<String> getNamespaces() {
+ return mResultData.getNamespaces();
+ }
+
+ @Override
+ public @NonNull Collection<String> getEntryNames(@NonNull String namespaceName) {
+ Collection<String> ret = mResultData.getEntryNames(namespaceName);
+ if (ret == null) {
+ ret = new LinkedList<String>();
+ }
+ return ret;
+ }
+
+ @Override
+ public @NonNull Collection<String> getRetrievedEntryNames(@NonNull String namespaceName) {
+ Collection<String> ret = mResultData.getRetrievedEntryNames(namespaceName);
+ if (ret == null) {
+ ret = new LinkedList<String>();
+ }
+ return ret;
+ }
+
+ @Override
+ @Status
+ public int getStatus(@NonNull String namespaceName, @NonNull String name) {
+ return mResultData.getStatus(namespaceName, name);
+ }
+
+ @Override
+ public @Nullable byte[] getEntry(@NonNull String namespaceName, @NonNull String name) {
+ return mResultData.getEntry(namespaceName, name);
+ }
+ }
+
+}
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredential.java b/identity/java/android/security/identity/CredstoreIdentityCredential.java
index 6398cee..8e01105 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredential.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredential.java
@@ -58,14 +58,17 @@
private @IdentityCredentialStore.Ciphersuite int mCipherSuite;
private Context mContext;
private ICredential mBinder;
+ private CredstorePresentationSession mSession;
CredstoreIdentityCredential(Context context, String credentialName,
@IdentityCredentialStore.Ciphersuite int cipherSuite,
- ICredential binder) {
+ ICredential binder,
+ @Nullable CredstorePresentationSession session) {
mContext = context;
mCredentialName = credentialName;
mCipherSuite = cipherSuite;
mBinder = binder;
+ mSession = session;
}
private KeyPair mEphemeralKeyPair = null;
@@ -239,6 +242,7 @@
private boolean mAllowUsingExhaustedKeys = true;
private boolean mAllowUsingExpiredKeys = false;
+ private boolean mIncrementKeyUsageCount = true;
@Override
public void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys) {
@@ -250,6 +254,11 @@
mAllowUsingExpiredKeys = allowUsingExpiredKeys;
}
+ @Override
+ public void setIncrementKeyUsageCount(boolean incrementKeyUsageCount) {
+ mIncrementKeyUsageCount = incrementKeyUsageCount;
+ }
+
private boolean mOperationHandleSet = false;
private long mOperationHandle = 0;
@@ -264,7 +273,8 @@
if (!mOperationHandleSet) {
try {
mOperationHandle = mBinder.selectAuthKey(mAllowUsingExhaustedKeys,
- mAllowUsingExpiredKeys);
+ mAllowUsingExpiredKeys,
+ mIncrementKeyUsageCount);
mOperationHandleSet = true;
} catch (android.os.RemoteException e) {
throw new RuntimeException("Unexpected RemoteException ", e);
@@ -315,7 +325,8 @@
sessionTranscript != null ? sessionTranscript : new byte[0],
readerSignature != null ? readerSignature : new byte[0],
mAllowUsingExhaustedKeys,
- mAllowUsingExpiredKeys);
+ mAllowUsingExpiredKeys,
+ mIncrementKeyUsageCount);
} catch (android.os.RemoteException e) {
throw new RuntimeException("Unexpected RemoteException ", e);
} catch (android.os.ServiceSpecificException e) {
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
index d8d4742..fb0880c 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
@@ -126,7 +126,8 @@
ICredential credstoreCredential;
credstoreCredential = mStore.getCredentialByName(credentialName, cipherSuite);
return new CredstoreIdentityCredential(mContext, credentialName, cipherSuite,
- credstoreCredential);
+ credstoreCredential,
+ null);
} catch (android.os.RemoteException e) {
throw new RuntimeException("Unexpected RemoteException ", e);
} catch (android.os.ServiceSpecificException e) {
@@ -162,4 +163,23 @@
+ e.errorCode, e);
}
}
+
+ @Override
+ public @NonNull PresentationSession createPresentationSession(@Ciphersuite int cipherSuite)
+ throws CipherSuiteNotSupportedException {
+ try {
+ ISession credstoreSession = mStore.createPresentationSession(cipherSuite);
+ return new CredstorePresentationSession(mContext, cipherSuite, this, credstoreSession);
+ } catch (android.os.RemoteException e) {
+ throw new RuntimeException("Unexpected RemoteException ", e);
+ } catch (android.os.ServiceSpecificException e) {
+ if (e.errorCode == ICredentialStore.ERROR_CIPHER_SUITE_NOT_SUPPORTED) {
+ throw new CipherSuiteNotSupportedException(e.getMessage(), e);
+ } else {
+ throw new RuntimeException("Unexpected ServiceSpecificException with code "
+ + e.errorCode, e);
+ }
+ }
+ }
+
}
diff --git a/identity/java/android/security/identity/CredstorePresentationSession.java b/identity/java/android/security/identity/CredstorePresentationSession.java
new file mode 100644
index 0000000..e3c6689
--- /dev/null
+++ b/identity/java/android/security/identity/CredstorePresentationSession.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.identity;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+class CredstorePresentationSession extends PresentationSession {
+ private static final String TAG = "CredstorePresentationSession";
+
+ private @IdentityCredentialStore.Ciphersuite int mCipherSuite;
+ private Context mContext;
+ private CredstoreIdentityCredentialStore mStore;
+ private ISession mBinder;
+ private Map<String, CredstoreIdentityCredential> mCredentialCache = new LinkedHashMap<>();
+ private KeyPair mEphemeralKeyPair = null;
+ private byte[] mSessionTranscript = null;
+ private boolean mOperationHandleSet = false;
+ private long mOperationHandle = 0;
+
+ CredstorePresentationSession(Context context,
+ @IdentityCredentialStore.Ciphersuite int cipherSuite,
+ CredstoreIdentityCredentialStore store,
+ ISession binder) {
+ mContext = context;
+ mCipherSuite = cipherSuite;
+ mStore = store;
+ mBinder = binder;
+ }
+
+ private void ensureEphemeralKeyPair() {
+ if (mEphemeralKeyPair != null) {
+ return;
+ }
+ try {
+ // This PKCS#12 blob is generated in credstore, using BoringSSL.
+ //
+ // The main reason for this convoluted approach and not just sending the decomposed
+ // key-pair is that this would require directly using (device-side) BouncyCastle which
+ // is tricky due to various API hiding efforts. So instead we have credstore generate
+ // this PKCS#12 blob. The blob is encrypted with no password (sadly, also, BoringSSL
+ // doesn't support not using encryption when building a PKCS#12 blob).
+ //
+ byte[] pkcs12 = mBinder.getEphemeralKeyPair();
+ String alias = "ephemeralKey";
+ char[] password = {};
+
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+ ByteArrayInputStream bais = new ByteArrayInputStream(pkcs12);
+ ks.load(bais, password);
+ PrivateKey privKey = (PrivateKey) ks.getKey(alias, password);
+
+ Certificate cert = ks.getCertificate(alias);
+ PublicKey pubKey = cert.getPublicKey();
+
+ mEphemeralKeyPair = new KeyPair(pubKey, privKey);
+ } catch (android.os.ServiceSpecificException e) {
+ throw new RuntimeException("Unexpected ServiceSpecificException with code "
+ + e.errorCode, e);
+ } catch (android.os.RemoteException
+ | KeyStoreException
+ | CertificateException
+ | UnrecoverableKeyException
+ | NoSuchAlgorithmException
+ | IOException e) {
+ throw new RuntimeException("Unexpected exception ", e);
+ }
+ }
+
+ @Override
+ public @NonNull KeyPair getEphemeralKeyPair() {
+ ensureEphemeralKeyPair();
+ return mEphemeralKeyPair;
+ }
+
+ @Override
+ public void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey)
+ throws InvalidKeyException {
+ try {
+ byte[] uncompressedForm =
+ Util.publicKeyEncodeUncompressedForm(readerEphemeralPublicKey);
+ mBinder.setReaderEphemeralPublicKey(uncompressedForm);
+ } catch (android.os.RemoteException e) {
+ throw new RuntimeException("Unexpected RemoteException ", e);
+ } catch (android.os.ServiceSpecificException e) {
+ throw new RuntimeException("Unexpected ServiceSpecificException with code "
+ + e.errorCode, e);
+ }
+ }
+
+ @Override
+ public void setSessionTranscript(@NonNull byte[] sessionTranscript) {
+ try {
+ mBinder.setSessionTranscript(sessionTranscript);
+ mSessionTranscript = sessionTranscript;
+ } catch (android.os.RemoteException e) {
+ throw new RuntimeException("Unexpected RemoteException ", e);
+ } catch (android.os.ServiceSpecificException e) {
+ throw new RuntimeException("Unexpected ServiceSpecificException with code "
+ + e.errorCode, e);
+ }
+ }
+
+ @Override
+ public @Nullable CredentialDataResult getCredentialData(@NonNull String credentialName,
+ @NonNull CredentialDataRequest request)
+ throws NoAuthenticationKeyAvailableException, InvalidReaderSignatureException,
+ InvalidRequestMessageException, EphemeralPublicKeyNotFoundException {
+ try {
+ // Cache the IdentityCredential to satisfy the property that AuthKey usage counts are
+ // incremented on only the _first_ getCredentialData() call.
+ //
+ CredstoreIdentityCredential credential = mCredentialCache.get(credentialName);
+ if (credential == null) {
+ ICredential credstoreCredential =
+ mBinder.getCredentialForPresentation(credentialName);
+ credential = new CredstoreIdentityCredential(mContext, credentialName,
+ mCipherSuite, credstoreCredential,
+ this);
+ mCredentialCache.put(credentialName, credential);
+
+ credential.setAllowUsingExhaustedKeys(request.isAllowUsingExhaustedKeys());
+ credential.setAllowUsingExpiredKeys(request.isAllowUsingExpiredKeys());
+ credential.setIncrementKeyUsageCount(request.isIncrementUseCount());
+ }
+
+ ResultData deviceSignedResult = credential.getEntries(
+ request.getRequestMessage(),
+ request.getDeviceSignedEntriesToRequest(),
+ mSessionTranscript,
+ request.getReaderSignature());
+
+ // By design this second getEntries() call consumes the same auth-key.
+
+ ResultData issuerSignedResult = credential.getEntries(
+ request.getRequestMessage(),
+ request.getIssuerSignedEntriesToRequest(),
+ mSessionTranscript,
+ request.getReaderSignature());
+
+ return new CredstoreCredentialDataResult(deviceSignedResult, issuerSignedResult);
+
+ } catch (SessionTranscriptMismatchException e) {
+ throw new RuntimeException("Unexpected ", e);
+ } catch (android.os.RemoteException e) {
+ throw new RuntimeException("Unexpected RemoteException ", e);
+ } catch (android.os.ServiceSpecificException e) {
+ if (e.errorCode == ICredentialStore.ERROR_NO_SUCH_CREDENTIAL) {
+ return null;
+ } else {
+ throw new RuntimeException("Unexpected ServiceSpecificException with code "
+ + e.errorCode, e);
+ }
+ }
+ }
+
+ /**
+ * Called by android.hardware.biometrics.CryptoObject#getOpId() to get an
+ * operation handle.
+ *
+ * @hide
+ */
+ @Override
+ public long getCredstoreOperationHandle() {
+ if (!mOperationHandleSet) {
+ try {
+ mOperationHandle = mBinder.getAuthChallenge();
+ mOperationHandleSet = true;
+ } catch (android.os.RemoteException e) {
+ throw new RuntimeException("Unexpected RemoteException ", e);
+ } catch (android.os.ServiceSpecificException e) {
+ if (e.errorCode == ICredentialStore.ERROR_NO_AUTHENTICATION_KEY_AVAILABLE) {
+ // The NoAuthenticationKeyAvailableException will be thrown when
+ // the caller proceeds to call getEntries().
+ }
+ throw new RuntimeException("Unexpected ServiceSpecificException with code "
+ + e.errorCode, e);
+ }
+ }
+ return mOperationHandle;
+ }
+
+}
diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java
index 1e68585..cdf746f 100644
--- a/identity/java/android/security/identity/IdentityCredential.java
+++ b/identity/java/android/security/identity/IdentityCredential.java
@@ -48,7 +48,9 @@
* encryption".
*
* @return ephemeral key pair to use to establish a secure channel with a reader.
+ * @deprecated Use {@link PresentationSession} instead.
*/
+ @Deprecated
public @NonNull abstract KeyPair createEphemeralKeyPair();
/**
@@ -58,7 +60,9 @@
* @param readerEphemeralPublicKey The ephemeral public key provided by the reader to
* establish a secure session.
* @throws InvalidKeyException if the given key is invalid.
+ * @deprecated Use {@link PresentationSession} instead.
*/
+ @Deprecated
public abstract void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey)
throws InvalidKeyException;
@@ -72,7 +76,10 @@
*
* @param messagePlaintext unencrypted message to encrypt.
* @return encrypted message.
+ * @deprecated Applications should use {@link PresentationSession} and
+ * implement encryption/decryption themselves.
*/
+ @Deprecated
public @NonNull abstract byte[] encryptMessageToReader(@NonNull byte[] messagePlaintext);
/**
@@ -86,7 +93,10 @@
* @param messageCiphertext encrypted message to decrypt.
* @return decrypted message.
* @throws MessageDecryptionException if the ciphertext couldn't be decrypted.
+ * @deprecated Applications should use {@link PresentationSession} and
+ * implement encryption/decryption themselves.
*/
+ @Deprecated
public @NonNull abstract byte[] decryptMessageFromReader(@NonNull byte[] messageCiphertext)
throws MessageDecryptionException;
@@ -111,7 +121,9 @@
*
* @param allowUsingExhaustedKeys whether to allow using an authentication key which use count
* has been exceeded if no other key is available.
+ * @deprecated Use {@link PresentationSession} instead.
*/
+ @Deprecated
public abstract void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys);
/**
@@ -128,12 +140,36 @@
*
* @param allowUsingExpiredKeys whether to allow using an authentication key which use count
* has been exceeded if no other key is available.
+ * @deprecated Use {@link PresentationSession} instead.
*/
+ @Deprecated
public void setAllowUsingExpiredKeys(boolean allowUsingExpiredKeys) {
throw new UnsupportedOperationException();
}
/**
+ * @hide
+ *
+ * Sets whether the usage count of an authentication key should be increased. This must be
+ * called prior to calling
+ * {@link #getEntries(byte[], Map, byte[], byte[])} or using a
+ * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which references this object.
+ *
+ * <p>By default this is set to true.
+ *
+ * <p>This is only implemented in feature version 202201 or later. If not implemented, the call
+ * fails with {@link UnsupportedOperationException}. See
+ * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known
+ * feature versions.
+ *
+ * @param incrementKeyUsageCount whether the usage count of the key should be increased.
+ * @deprecated Use {@link PresentationSession} instead.
+ */
+ public void setIncrementKeyUsageCount(boolean incrementKeyUsageCount) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* Called by android.hardware.biometrics.CryptoObject#getOpId() to get an
* operation handle.
*
@@ -149,15 +185,19 @@
* by using the {@link ResultData#getStatus(String, String)} method on each of the requested
* entries.
*
- * <p>It is the responsibility of the calling application to know if authentication is needed
- * and use e.g. {@link android.hardware.biometrics.BiometricPrompt} to make the user
- * authenticate using a {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which
- * references this object. If needed, this must be done before calling
- * {@link #getEntries(byte[], Map, byte[], byte[])}.
- *
* <p>It is permissible to call this method multiple times using the same instance but if this
* is done, the {@code sessionTranscript} parameter must be identical for each call. If this is
* not the case, the {@link SessionTranscriptMismatchException} exception is thrown.
+ * Additionally, if this is done the same auth-key will be used.
+ *
+ * <p>The application should not make any assumptions on whether user authentication is needed.
+ * Instead, the application should request the data elements values first and then examine
+ * the returned {@link ResultData}. If {@link ResultData#STATUS_USER_AUTHENTICATION_FAILED}
+ * is returned the application should get a
+ * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which references this
+ * object and use it with a {@link android.hardware.biometrics.BiometricPrompt}. Upon successful
+ * authentication the application may call {@link #getEntries(byte[], Map, byte[], byte[])}
+ * again.
*
* <p>If not {@code null} the {@code requestMessage} parameter must contain data for the request
* from the verifier. The content can be defined in the way appropriate for the credential, but
@@ -269,7 +309,9 @@
* @throws InvalidRequestMessageException if the requestMessage is malformed.
* @throws EphemeralPublicKeyNotFoundException if the ephemeral public key was not found in
* the session transcript.
+ * @deprecated Use {@link PresentationSession} instead.
*/
+ @Deprecated
public abstract @NonNull ResultData getEntries(
@Nullable byte[] requestMessage,
@NonNull Map<String, Collection<String>> entriesToRequest,
diff --git a/identity/java/android/security/identity/IdentityCredentialStore.java b/identity/java/android/security/identity/IdentityCredentialStore.java
index 6ccd0e8..dbb8aaa 100644
--- a/identity/java/android/security/identity/IdentityCredentialStore.java
+++ b/identity/java/android/security/identity/IdentityCredentialStore.java
@@ -209,6 +209,25 @@
@Deprecated
public abstract @Nullable byte[] deleteCredentialByName(@NonNull String credentialName);
+ /**
+ * Creates a new presentation session.
+ *
+ * <p>This method gets an object to be used for interaction with a remote verifier for
+ * presentation of one or more credentials.
+ *
+ * <p>This is only implemented in feature version 202201 or later. If not implemented, the call
+ * fails with {@link UnsupportedOperationException}. See
+ * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known
+ * feature versions.
+ *
+ * @param cipherSuite the cipher suite to use for communicating with the verifier.
+ * @return The presentation session.
+ */
+ public @NonNull PresentationSession createPresentationSession(@Ciphersuite int cipherSuite)
+ throws CipherSuiteNotSupportedException {
+ throw new UnsupportedOperationException();
+ }
+
/** @hide */
@IntDef(value = {CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256})
@Retention(RetentionPolicy.SOURCE)
diff --git a/identity/java/android/security/identity/PresentationSession.java b/identity/java/android/security/identity/PresentationSession.java
new file mode 100644
index 0000000..afaafce
--- /dev/null
+++ b/identity/java/android/security/identity/PresentationSession.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.PublicKey;
+
+/**
+ * Class for presenting multiple documents to a remote verifier.
+ *
+ * Use {@link IdentityCredentialStore#createPresentationSession(int)} to create a {@link
+ * PresentationSession} instance.
+ */
+public abstract class PresentationSession {
+ /**
+ * @hide
+ */
+ protected PresentationSession() {}
+
+ /**
+ * Gets the ephemeral key pair to use to establish a secure channel with the verifier.
+ *
+ * <p>Applications should use this key-pair for the communications channel with the verifier
+ * using a protocol / cipher-suite appropriate for the application. One example of such a
+ * protocol is the one used for Mobile Driving Licenses, see ISO 18013-5.
+ *
+ * <p>The ephemeral key pair is tied to the {@link PresentationSession} instance so subsequent
+ * calls to this method will return the same key-pair.
+ *
+ * @return ephemeral key pair to use to establish a secure channel with a reader.
+ */
+ public @NonNull abstract KeyPair getEphemeralKeyPair();
+
+ /**
+ * Set the ephemeral public key provided by the verifier.
+ *
+ * <p>If called, this must be called before any calls to
+ * {@link #getCredentialData(String, CredentialDataRequest)}.
+ *
+ * <p>This method can only be called once per {@link PresentationSession} instance.
+ *
+ * @param readerEphemeralPublicKey The ephemeral public key provided by the reader to
+ * establish a secure session.
+ * @throws InvalidKeyException if the given key is invalid.
+ */
+ public abstract void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey)
+ throws InvalidKeyException;
+
+ /**
+ * Set the session transcript.
+ *
+ * <p>If called, this must be called before any calls to
+ * {@link #getCredentialData(String, CredentialDataRequest)}.
+ *
+ * <p>The X and Y coordinates of the public part of the key-pair returned by {@link
+ * #getEphemeralKeyPair()} must appear somewhere in the bytes of the passed in CBOR. Each of
+ * these coordinates must appear encoded with the most significant bits first and use the exact
+ * amount of bits indicated by the key size of the ephemeral keys. For example, if the
+ * ephemeral key is using the P-256 curve then the 32 bytes for the X coordinate encoded with
+ * the most significant bits first must appear somewhere and ditto for the 32 bytes for the Y
+ * coordinate.
+ *
+ * <p>This method can only be called once per {@link PresentationSession} instance.
+ *
+ * @param sessionTranscript the session transcript.
+ */
+ public abstract void setSessionTranscript(@NonNull byte[] sessionTranscript);
+
+ /**
+ * Retrieves data from a named credential in the current presentation session.
+ *
+ * <p>If an access control check fails for one of the requested entries or if the entry
+ * doesn't exist, the entry is simply not returned. The application can detect this
+ * by using the {@link CredentialDataResult.Entries#getStatus(String, String)} method on
+ * each of the requested entries.
+ *
+ * <p>The application should not make any assumptions on whether user authentication is needed.
+ * Instead, the application should request the data elements values first and then examine
+ * the returned {@link CredentialDataResult.Entries}. If
+ * {@link CredentialDataResult.Entries#STATUS_USER_AUTHENTICATION_FAILED} is returned the
+ * application should get a
+ * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which references this
+ * object and use it with a {@link android.hardware.biometrics.BiometricPrompt}. Upon successful
+ * authentication the application may call
+ * {@link #getCredentialData(String, CredentialDataRequest)} again.
+ *
+ * <p>It is permissible to call this method multiple times using the same credential name.
+ * If this is done the same auth-key will be used.
+ *
+ * <p>If the reader signature is set in the request parameter (via the
+ * {@link CredentialDataRequest.Builder#setReaderSignature(byte[])} method) it must contain
+ * the bytes of a {@code COSE_Sign1} structure as defined in RFC 8152. For the payload
+ * {@code nil} shall be used and the detached payload is the {@code ReaderAuthenticationBytes}
+ * CBOR described below.
+ * <pre>
+ * ReaderAuthentication = [
+ * "ReaderAuthentication",
+ * SessionTranscript,
+ * ItemsRequestBytes
+ * ]
+ *
+ * ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest)
+ *
+ * ReaderAuthenticationBytes = #6.24(bstr .cbor ReaderAuthentication)
+ * </pre>
+ *
+ * <p>where {@code ItemsRequestBytes} are the bytes of the request message set in
+ * the request parameter (via the
+ * {@link CredentialDataRequest.Builder#setRequestMessage(byte[])} method).
+ *
+ * <p>The public key corresponding to the key used to make the signature, can be found in the
+ * {@code x5chain} unprotected header element of the {@code COSE_Sign1} structure (as as
+ * described in
+ * <a href="https://tools.ietf.org/html/draft-ietf-cose-x509-08">draft-ietf-cose-x509-08</a>).
+ * There will be at least one certificate in said element and there may be more (and if so,
+ * each certificate must be signed by its successor).
+ *
+ * <p>Data elements protected by reader authentication are returned if, and only if,
+ * {@code requestMessage} is signed by the top-most certificate in the reader's certificate
+ * chain, and the data element is configured with an {@link AccessControlProfile} configured
+ * with an X.509 certificate for a key which appear in the certificate chain.
+ *
+ * <p>Note that the request message CBOR is used only for enforcing reader authentication, it's
+ * not used for determining which entries this API will return. The application is expected to
+ * have parsed the request message and filtered it according to user preference and/or consent.
+ *
+ * @param credentialName the name of the credential to retrieve.
+ * @param request the data to retrieve from the credential
+ * @return If the credential wasn't found, returns null. Otherwise a
+ * {@link CredentialDataResult} object containing entry data organized by namespace and
+ * a cryptographically authenticated representation of the same data, bound to the
+ * current session.
+ * @throws NoAuthenticationKeyAvailableException if authentication keys were never
+ * provisioned for the credential or if they
+ * are expired or exhausted their use-count.
+ * @throws InvalidRequestMessageException if the requestMessage is malformed.
+ * @throws InvalidReaderSignatureException if the reader signature is invalid, or it
+ * doesn't contain a certificate chain, or if
+ * the signature failed to validate.
+ * @throws EphemeralPublicKeyNotFoundException if the ephemeral public key was not found in
+ * the session transcript.
+ */
+ public abstract @Nullable CredentialDataResult getCredentialData(
+ @NonNull String credentialName, @NonNull CredentialDataRequest request)
+ throws NoAuthenticationKeyAvailableException, InvalidReaderSignatureException,
+ InvalidRequestMessageException, EphemeralPublicKeyNotFoundException;
+
+ /**
+ * Called by android.hardware.biometrics.CryptoObject#getOpId() to get an
+ * operation handle.
+ *
+ * @hide
+ */
+ public abstract long getCredstoreOperationHandle();
+}
diff --git a/identity/java/android/security/identity/ResultData.java b/identity/java/android/security/identity/ResultData.java
index 71860d2..d46f985 100644
--- a/identity/java/android/security/identity/ResultData.java
+++ b/identity/java/android/security/identity/ResultData.java
@@ -28,7 +28,10 @@
/**
* An object that contains the result of retrieving data from a credential. This is used to return
* data requested from a {@link IdentityCredential}.
+ *
+ * @deprecated Use {@link PresentationSession} instead.
*/
+@Deprecated
public abstract class ResultData {
/** Value was successfully retrieved. */
diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_gain_animation.xml b/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_gain_animation.xml
deleted file mode 100644
index 29d9b25..0000000
--- a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_gain_animation.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
- android:propertyName="alpha"
- android:valueTo="1"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:duration="100" />
diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_in_animation.xml b/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_in_animation.xml
deleted file mode 100644
index 29d9b25..0000000
--- a/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_in_animation.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
- android:propertyName="alpha"
- android:valueTo="1"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:duration="100" />
diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_out_animation.xml b/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_out_animation.xml
deleted file mode 100644
index 70f553b..0000000
--- a/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_out_animation.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
- android:propertyName="alpha"
- android:valueTo="0"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:duration="100" />
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_down.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_down.xml
new file mode 100644
index 0000000..d8f3561
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/pip_ic_move_down.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<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="@color/tv_pip_menu_focus_border"
+ android:pathData="M7,10l5,5 5,-5H7z"/>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_left.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_left.xml
new file mode 100644
index 0000000..3e0011c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/pip_ic_move_left.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<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="@color/tv_pip_menu_focus_border"
+ android:pathData="M14,7l-5,5 5,5V7z"/>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_right.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_right.xml
new file mode 100644
index 0000000..f6b3c72
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/pip_ic_move_right.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<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="@color/tv_pip_menu_focus_border"
+ android:pathData="M10,17l5,-5 -5,-5v10z"/>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_up.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_up.xml
new file mode 100644
index 0000000..1a34462
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/pip_ic_move_up.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<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="@color/tv_pip_menu_focus_border"
+ android:pathData="M7,14l5,-5 5,5H7z"/>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_white.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_white.xml
new file mode 100644
index 0000000..37f4c87
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/pip_ic_move_white.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M11,5.83L11,10h2L13,5.83l1.83,1.83 1.41,-1.42L12,2 7.76,6.24l1.41,1.42zM17.76,7.76l-1.42,1.41L18.17,11L14,11v2h4.17l-1.83,1.83 1.42,1.41L22,12zM13,18.17L13,14h-2v4.17l-1.83,-1.83 -1.41,1.42L12,22l4.24,-4.24 -1.41,-1.42zM10,13v-2L5.83,11l1.83,-1.83 -1.42,-1.41L2,12l4.24,4.24 1.42,-1.41L5.83,13z"
+ android:fillColor="#FFFFFF" />
+
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index 5b90c99..b56b114 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -15,57 +15,101 @@
limitations under the License.
-->
<!-- Layout for TvPipMenuView -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv_pip_menu"
android:layout_width="match_parent"
android:layout_height="match_parent">
- <FrameLayout
+ <HorizontalScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_centerHorizontal="true"
+ android:gravity="center"
+ android:scrollbars="none"
+ android:layout_centerInParent="true"
+ android:layout_margin="@dimen/pip_menu_outer_space">
+
+ <LinearLayout
+ android:id="@+id/tv_pip_menu_action_buttons"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingStart="@dimen/pip_menu_button_wrapper_margin"
+ android:paddingEnd="@dimen/pip_menu_button_wrapper_margin"
+ android:gravity="center"
+ android:orientation="horizontal"
+ android:alpha="0">
+
+ <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ android:id="@+id/tv_pip_menu_fullscreen_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/pip_ic_fullscreen_white"
+ android:text="@string/pip_fullscreen" />
+
+ <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ android:id="@+id/tv_pip_menu_move_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/pip_ic_move_white"
+ android:text="@String/pip_move" />
+
+ <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ android:id="@+id/tv_pip_menu_close_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/pip_ic_close_white"
+ android:text="@string/pip_close" />
+
+ <!-- More TvPipMenuActionButtons may be added here at runtime. -->
+
+ </LinearLayout>
+ </HorizontalScrollView>
+
+ <View
android:id="@+id/tv_pip_menu_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:alpha="0" >
+ android:alpha="0"
+ android:layout_margin="@dimen/pip_menu_outer_space_frame"
+ android:background="@drawable/tv_pip_menu_border"/>
- <HorizontalScrollView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_centerHorizontal="true"
- android:gravity="center"
- android:scrollbars="none"
- android:requiresFadingEdge="vertical"
- android:fadingEdgeLength="30dp">
+ <ImageView
+ android:id="@+id/tv_pip_menu_arrow_up"
+ android:layout_width="@dimen/pip_menu_arrow_size"
+ android:layout_height="@dimen/pip_menu_arrow_size"
+ android:layout_centerHorizontal="true"
+ android:layout_alignParentTop="true"
+ android:alpha="0"
+ android:elevation="@dimen/pip_menu_arrow_elevation"
+ android:src="@drawable/pip_ic_move_up" />
- <LinearLayout
- android:id="@+id/tv_pip_menu_action_buttons"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:paddingStart="@dimen/pip_menu_button_wrapper_margin"
- android:paddingEnd="@dimen/pip_menu_button_wrapper_margin"
- android:gravity="center"
- android:orientation="horizontal">
+ <ImageView
+ android:id="@+id/tv_pip_menu_arrow_right"
+ android:layout_width="@dimen/pip_menu_arrow_size"
+ android:layout_height="@dimen/pip_menu_arrow_size"
+ android:layout_centerVertical="true"
+ android:layout_alignParentRight="true"
+ android:alpha="0"
+ android:elevation="@dimen/pip_menu_arrow_elevation"
+ android:src="@drawable/pip_ic_move_right" />
- <com.android.wm.shell.pip.tv.TvPipMenuActionButton
- android:id="@+id/tv_pip_menu_fullscreen_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/pip_ic_fullscreen_white"
- android:text="@string/pip_fullscreen" />
+ <ImageView
+ android:id="@+id/tv_pip_menu_arrow_down"
+ android:layout_width="@dimen/pip_menu_arrow_size"
+ android:layout_height="@dimen/pip_menu_arrow_size"
+ android:layout_centerHorizontal="true"
+ android:layout_alignParentBottom="true"
+ android:alpha="0"
+ android:elevation="@dimen/pip_menu_arrow_elevation"
+ android:src="@drawable/pip_ic_move_down" />
- <com.android.wm.shell.pip.tv.TvPipMenuActionButton
- android:id="@+id/tv_pip_menu_close_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/pip_ic_close_white"
- android:text="@string/pip_close" />
-
- <!-- More TvPipMenuActionButtons may be added here at runtime. -->
-
- </LinearLayout>
- </HorizontalScrollView>
-
- <View
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@drawable/tv_pip_menu_border"/>
- </FrameLayout>
-</FrameLayout>
+ <ImageView
+ android:id="@+id/tv_pip_menu_arrow_left"
+ android:layout_width="@dimen/pip_menu_arrow_size"
+ android:layout_height="@dimen/pip_menu_arrow_size"
+ android:layout_centerVertical="true"
+ android:layout_alignParentLeft="true"
+ android:alpha="0"
+ android:elevation="@dimen/pip_menu_arrow_elevation"
+ android:src="@drawable/pip_ic_move_left" />
+</RelativeLayout>
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index e41ebc4..558ec51 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -21,7 +21,14 @@
<dimen name="pip_menu_icon_size">20dp</dimen>
<dimen name="pip_menu_button_margin">4dp</dimen>
<dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
- <dimen name="pip_menu_border_width">2dp</dimen>
- <dimen name="pip_menu_border_radius">0dp</dimen>
+ <dimen name="pip_menu_border_width">4dp</dimen>
+ <dimen name="pip_menu_border_radius">4dp</dimen>
+ <dimen name="pip_menu_outer_space">24dp</dimen>
+
+ <!-- outer space minus border width -->
+ <dimen name="pip_menu_outer_space_frame">20dp</dimen>
+
+ <dimen name="pip_menu_arrow_size">24dp</dimen>
+ <dimen name="pip_menu_arrow_elevation">5dp</dimen>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml
index 17387fa..08d3cef 100644
--- a/libs/WindowManager/Shell/res/values/colors_tv.xml
+++ b/libs/WindowManager/Shell/res/values/colors_tv.xml
@@ -19,6 +19,6 @@
<color name="tv_pip_menu_icon_unfocused">#E8EAED</color>
<color name="tv_pip_menu_icon_disabled">#80868B</color>
<color name="tv_pip_menu_icon_bg_focused">#E8EAED</color>
- <color name="tv_pip_menu_icon_bg_unfocused">#777777</color>
- <color name="tv_pip_menu_focus_border">#CCE8EAED</color>
+ <color name="tv_pip_menu_icon_bg_unfocused">#990E0E0F</color>
+ <color name="tv_pip_menu_focus_border">#E8EAED</color>
</resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/strings_tv.xml b/libs/WindowManager/Shell/res/values/strings_tv.xml
index 2dfdcab..730d808 100644
--- a/libs/WindowManager/Shell/res/values/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values/strings_tv.xml
@@ -30,5 +30,8 @@
<!-- Button to move picture-in-picture (PIP) screen to the fullscreen in PIP menu [CHAR LIMIT=30] -->
<string name="pip_fullscreen">Full screen</string>
+
+ <!-- Button to move picture-in-picture (PIP) via DPAD in the PIP menu [CHAR LIMIT=30] -->
+ <string name="pip_move">Move PIP</string>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 22bec3d..6823639 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1348,7 +1348,7 @@
mStackView.updateContentDescription();
- mStackView.updateBubblesClickableStates();
+ mStackView.updateBubblesAcessibillityStates();
}
@VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index d5d8b71..a477bd7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1486,19 +1486,63 @@
}
/**
- * Update bubbles' icon views clickable states.
+ * Update bubbles' icon views accessibility states.
*/
- public void updateBubblesClickableStates() {
+ public void updateBubblesAcessibillityStates() {
for (int i = 0; i < mBubbleData.getBubbles().size(); i++) {
- final Bubble bubble = mBubbleData.getBubbles().get(i);
- if (bubble.getIconView() != null) {
- if (mIsExpanded) {
- // when stack is expanded all bubbles are clickable
- bubble.getIconView().setClickable(true);
- } else {
- // when stack is collapsed, only the top bubble needs to be clickable,
- // so that a11y ignores all the inaccessible bubbles in the stack
- bubble.getIconView().setClickable(i == 0);
+ Bubble prevBubble = i > 0 ? mBubbleData.getBubbles().get(i - 1) : null;
+ Bubble bubble = mBubbleData.getBubbles().get(i);
+
+ View bubbleIconView = bubble.getIconView();
+ if (bubbleIconView == null) {
+ continue;
+ }
+
+ if (mIsExpanded) {
+ // when stack is expanded
+ // all bubbles are important for accessibility
+ bubbleIconView
+ .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+
+ View prevBubbleIconView = prevBubble != null ? prevBubble.getIconView() : null;
+
+ if (prevBubbleIconView != null) {
+ bubbleIconView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View v,
+ AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(v, info);
+ info.setTraversalAfter(prevBubbleIconView);
+ }
+ });
+ }
+ } else {
+ // when stack is collapsed, only the top bubble is important for accessibility,
+ bubbleIconView.setImportantForAccessibility(
+ i == 0 ? View.IMPORTANT_FOR_ACCESSIBILITY_YES :
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ }
+ }
+
+ if (mIsExpanded) {
+ // make the overflow bubble last in the accessibility traversal order
+
+ View bubbleOverflowIconView =
+ mBubbleOverflow != null ? mBubbleOverflow.getIconView() : null;
+ if (bubbleOverflowIconView != null && !mBubbleData.getBubbles().isEmpty()) {
+ Bubble lastBubble =
+ mBubbleData.getBubbles().get(mBubbleData.getBubbles().size() - 1);
+ View lastBubbleIconView = lastBubble.getIconView();
+ if (lastBubbleIconView != null) {
+ bubbleOverflowIconView.setAccessibilityDelegate(
+ new View.AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View v,
+ AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(v, info);
+ info.setTraversalAfter(lastBubbleIconView);
+ }
+ });
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index 711a0ac..f91d7e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -30,7 +30,6 @@
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -39,6 +38,7 @@
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm;
import com.android.wm.shell.pip.tv.TvPipController;
import com.android.wm.shell.pip.tv.TvPipMenuController;
import com.android.wm.shell.pip.tv.TvPipNotificationController;
@@ -61,7 +61,7 @@
static Optional<Pip> providePip(
Context context,
PipBoundsState pipBoundsState,
- PipBoundsAlgorithm pipBoundsAlgorithm,
+ TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipTaskOrganizer pipTaskOrganizer,
TvPipMenuController tvPipMenuController,
PipMediaController pipMediaController,
@@ -74,7 +74,7 @@
TvPipController.create(
context,
pipBoundsState,
- pipBoundsAlgorithm,
+ tvPipBoundsAlgorithm,
pipTaskOrganizer,
pipTransitionController,
tvPipMenuController,
@@ -93,9 +93,9 @@
@WMSingleton
@Provides
- static PipBoundsAlgorithm providePipBoundsAlgorithm(Context context,
+ static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context,
PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) {
- return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm);
+ return new TvPipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm);
}
@WMSingleton
@@ -109,10 +109,11 @@
@Provides
static PipTransitionController provideTvPipTransition(
Transitions transitions, ShellTaskOrganizer shellTaskOrganizer,
- PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipAnimationController pipAnimationController,
+ TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipBoundsState pipBoundsState, TvPipMenuController pipMenuController) {
return new TvPipTransition(pipBoundsState, pipMenuController,
- pipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer);
+ tvPipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer);
}
@WMSingleton
@@ -156,7 +157,7 @@
SyncTransactionQueue syncTransactionQueue,
PipBoundsState pipBoundsState,
PipTransitionState pipTransitionState,
- PipBoundsAlgorithm pipBoundsAlgorithm,
+ TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipAnimationController pipAnimationController,
PipTransitionController pipTransitionController,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
@@ -166,7 +167,7 @@
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTaskOrganizer(context,
- syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm,
+ syncTransactionQueue, pipTransitionState, pipBoundsState, tvPipBoundsAlgorithm,
tvPipMenuController, pipAnimationController, pipSurfaceTransactionHelper,
pipTransitionController, splitScreenOptional, newSplitScreenOptional,
displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
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 8e6c05d..eda09e3 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
@@ -62,6 +62,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.InstanceId;
+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.splitscreen.SplitScreenController;
@@ -132,6 +133,8 @@
final Rect fullscreenHitRegion = new Rect(displayRegion);
final boolean inLandscape = mSession.displayLayout.isLandscape();
final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible();
+ final float dividerWidth = mContext.getResources().getDimensionPixelSize(
+ R.dimen.split_divider_bar_width);
// We allow splitting if we are already in split-screen or the running task is a standard
// task in fullscreen mode.
final boolean allowSplit = inSplitScreen
@@ -153,8 +156,11 @@
// If we have existing split regions use those bounds, otherwise split it 50/50
if (inSplitScreen) {
+ // Add the divider bounds to each side since that counts for the hit region.
leftHitRegion.set(topOrLeftBounds);
+ leftHitRegion.right += dividerWidth / 2;
rightHitRegion.set(bottomOrRightBounds);
+ rightHitRegion.left -= dividerWidth / 2;
} else {
displayRegion.splitVertically(leftHitRegion, rightHitRegion);
}
@@ -170,8 +176,11 @@
// If we have existing split regions use those bounds, otherwise split it 50/50
if (inSplitScreen) {
+ // Add the divider bounds to each side since that counts for the hit region.
topHitRegion.set(topOrLeftBounds);
+ topHitRegion.bottom += dividerWidth / 2;
bottomHitRegion.set(bottomOrRightBounds);
+ bottomHitRegion.top -= dividerWidth / 2;
} else {
displayRegion.splitHorizontally(topHitRegion, bottomHitRegion);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index a4b866a..1a3c51e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -44,7 +44,7 @@
private static final String TAG = PipBoundsAlgorithm.class.getSimpleName();
private static final float INVALID_SNAP_FRACTION = -1f;
- private final @NonNull PipBoundsState mPipBoundsState;
+ protected final @NonNull PipBoundsState mPipBoundsState;
private final PipSnapAlgorithm mSnapAlgorithm;
private float mDefaultSizePercent;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
new file mode 100644
index 0000000..33f3bfb
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -0,0 +1,69 @@
+/*
+ * 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.wm.shell.pip.tv;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.Gravity;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipSnapAlgorithm;
+
+/**
+ * Contains pip bounds calculations that are specific to TV.
+ */
+public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
+
+ private static final String TAG = TvPipBoundsAlgorithm.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ public TvPipBoundsAlgorithm(Context context,
+ @NonNull PipBoundsState pipBoundsState,
+ @NonNull PipSnapAlgorithm pipSnapAlgorithm) {
+ super(context, pipBoundsState, pipSnapAlgorithm);
+ }
+
+ /**
+ * The normal bounds at a different position on the screen.
+ */
+ public Rect getTvNormalBounds(int gravity) {
+ Rect normalBounds = getNormalBounds();
+ Rect insetBounds = new Rect();
+ getInsetBounds(insetBounds);
+
+ if (mPipBoundsState.isImeShowing()) {
+ if (DEBUG) Log.d(TAG, "IME showing, height: " + mPipBoundsState.getImeHeight());
+ insetBounds.bottom -= mPipBoundsState.getImeHeight();
+ }
+
+ Rect result = new Rect();
+ Gravity.apply(gravity, normalBounds.width(), normalBounds.height(), insetBounds, result);
+
+ if (DEBUG) {
+ Log.d(TAG, "normalBounds: " + normalBounds.toShortString());
+ Log.d(TAG, "insetBounds: " + insetBounds.toShortString());
+ Log.d(TAG, "gravity: " + Gravity.toString(gravity));
+ Log.d(TAG, "resultBounds: " + result.toShortString());
+ }
+
+ return result;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index b165706..de53939 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -18,6 +18,10 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.KeyEvent.KEYCODE_DPAD_DOWN;
+import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
+import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
+import static android.view.KeyEvent.KEYCODE_DPAD_UP;
import android.annotation.IntDef;
import android.app.ActivityManager;
@@ -33,6 +37,7 @@
import android.os.RemoteException;
import android.util.Log;
import android.view.DisplayInfo;
+import android.view.Gravity;
import com.android.wm.shell.R;
import com.android.wm.shell.WindowManagerShellWrapper;
@@ -42,7 +47,6 @@
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipTaskOrganizer;
@@ -85,10 +89,12 @@
*/
private static final int STATE_PIP_MENU = 2;
+ private static final int DEFAULT_GRAVITY = Gravity.BOTTOM | Gravity.RIGHT;
+
private final Context mContext;
private final PipBoundsState mPipBoundsState;
- private final PipBoundsAlgorithm mPipBoundsAlgorithm;
+ private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
private final PipTaskOrganizer mPipTaskOrganizer;
private final PipMediaController mPipMediaController;
private final TvPipNotificationController mPipNotificationController;
@@ -97,6 +103,7 @@
private final TvPipImpl mImpl = new TvPipImpl();
private @State int mState = STATE_NO_PIP;
+ private @Gravity.GravityFlags int mGravity = DEFAULT_GRAVITY;
private int mPinnedTaskId = NONEXISTENT_TASK_ID;
private int mResizeAnimationDuration;
@@ -104,7 +111,7 @@
public static Pip create(
Context context,
PipBoundsState pipBoundsState,
- PipBoundsAlgorithm pipBoundsAlgorithm,
+ TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipTaskOrganizer pipTaskOrganizer,
PipTransitionController pipTransitionController,
TvPipMenuController tvPipMenuController,
@@ -116,7 +123,7 @@
return new TvPipController(
context,
pipBoundsState,
- pipBoundsAlgorithm,
+ tvPipBoundsAlgorithm,
pipTaskOrganizer,
pipTransitionController,
tvPipMenuController,
@@ -130,7 +137,7 @@
private TvPipController(
Context context,
PipBoundsState pipBoundsState,
- PipBoundsAlgorithm pipBoundsAlgorithm,
+ TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipTaskOrganizer pipTaskOrganizer,
PipTransitionController pipTransitionController,
TvPipMenuController tvPipMenuController,
@@ -145,7 +152,7 @@
mPipBoundsState = pipBoundsState;
mPipBoundsState.setDisplayId(context.getDisplayId());
mPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay()));
- mPipBoundsAlgorithm = pipBoundsAlgorithm;
+ mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm;
mPipMediaController = pipMediaController;
@@ -192,24 +199,19 @@
public void showPictureInPictureMenu() {
if (DEBUG) Log.d(TAG, "showPictureInPictureMenu(), state=" + stateToName(mState));
- if (mState != STATE_PIP) {
+ if (mState == STATE_NO_PIP) {
if (DEBUG) Log.d(TAG, " > cannot open Menu from the current state.");
return;
}
setState(STATE_PIP_MENU);
- resizePinnedStack(STATE_PIP_MENU);
+ movePinnedStack();
}
- /**
- * Moves Pip window to its "normal" position.
- */
@Override
- public void movePipToNormalPosition() {
- if (DEBUG) Log.d(TAG, "movePipToNormalPosition(), state=" + stateToName(mState));
-
+ public void closeMenu() {
+ if (DEBUG) Log.d(TAG, "closeMenu(), state before=" + stateToName(mState));
setState(STATE_PIP);
- resizePinnedStack(STATE_PIP);
}
/**
@@ -223,6 +225,69 @@
onPipDisappeared();
}
+ @Override
+ public void movePip(int keycode) {
+ if (updatePosition(keycode)) {
+ if (DEBUG) Log.d(TAG, "New gravity: " + Gravity.toString(mGravity));
+ mTvPipMenuController.updateMenu(mGravity);
+ movePinnedStack();
+ } else {
+ if (DEBUG) Log.d(TAG, "Position hasn't changed");
+ }
+ }
+
+ @Override
+ public int getPipGravity() {
+ return mGravity;
+ }
+
+ /**
+ * @return true if position changed
+ */
+ private boolean updatePosition(int keycode) {
+ if (DEBUG) Log.d(TAG, "updatePosition, keycode: " + keycode);
+
+ int updatedGravity;
+ switch (keycode) {
+ case KEYCODE_DPAD_UP:
+ updatedGravity = (mGravity & (~Gravity.BOTTOM)) | Gravity.TOP;
+ break;
+ case KEYCODE_DPAD_DOWN:
+ updatedGravity = (mGravity & (~Gravity.TOP)) | Gravity.BOTTOM;
+ break;
+ case KEYCODE_DPAD_LEFT:
+ updatedGravity = (mGravity & (~Gravity.RIGHT)) | Gravity.LEFT;
+ break;
+ case KEYCODE_DPAD_RIGHT:
+ updatedGravity = (mGravity & (~Gravity.LEFT)) | Gravity.RIGHT;
+ break;
+ default:
+ updatedGravity = mGravity;
+ }
+
+ if (updatedGravity != mGravity) {
+ mGravity = updatedGravity;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Animate to the updated position of the PiP based on the state and position of the PiP.
+ */
+ private void movePinnedStack() {
+ if (mState == STATE_NO_PIP) {
+ return;
+ }
+
+ Rect bounds = mTvPipBoundsAlgorithm.getTvNormalBounds(mGravity);
+ if (DEBUG) Log.d(TAG, "movePinnedStack() - new pip bounds: " + bounds.toShortString());
+ mPipTaskOrganizer.scheduleAnimateResizePip(bounds,
+ mResizeAnimationDuration, rect -> {
+ if (DEBUG) Log.d(TAG, "movePinnedStack() animation done");
+ });
+ }
+
/**
* Closes Pip window.
*/
@@ -234,41 +299,6 @@
onPipDisappeared();
}
- /**
- * Resizes the Pip task/window to the appropriate size for the given state.
- * This is a legacy API. Now we expect that the state argument passed to it should always match
- * the current state of the Controller. If it does not match an {@link IllegalArgumentException}
- * will be thrown. However, if the passed state does match - we'll determine the right bounds
- * to the state and will move Pip task/window there.
- *
- * @param state the to determine the Pip bounds. IMPORTANT: should always match the current
- * state of the Controller.
- */
- private void resizePinnedStack(@State int state) {
- if (state != mState) {
- throw new IllegalArgumentException("The passed state should match the current state!");
- }
- if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + stateToName(mState));
-
- final Rect newBounds;
- switch (mState) {
- case STATE_PIP_MENU:
- case STATE_PIP:
- // Let PipBoundsAlgorithm figure out what the correct bounds are at the moment.
- // Internally, it will get the "default" bounds from PipBoundsState and adjust them
- // as needed to account for things like IME state (will query PipBoundsState for
- // this information as well, so it's important to keep PipBoundsState up to date).
- newBounds = mPipBoundsAlgorithm.getNormalBounds();
- break;
-
- case STATE_NO_PIP:
- default:
- return;
- }
-
- mPipTaskOrganizer.scheduleAnimateResizePip(newBounds, mResizeAnimationDuration, null);
- }
-
private void registerSessionListenerForCurrentUser() {
mPipMediaController.registerSessionListenerForCurrentUser();
}
@@ -298,6 +328,7 @@
mPipNotificationController.dismiss();
mTvPipMenuController.hideMenu();
+ mGravity = DEFAULT_GRAVITY;
setState(STATE_NO_PIP);
mPinnedTaskId = NONEXISTENT_TASK_ID;
}
@@ -384,10 +415,9 @@
return;
}
mPipBoundsState.setImeVisibility(imeVisible, imeHeight);
- // "Normal" Pip bounds may have changed, so if we are in the "normal" state,
- // let's update the bounds.
- if (mState == STATE_PIP) {
- resizePinnedStack(STATE_PIP);
+
+ if (mState != STATE_NO_PIP) {
+ movePinnedStack();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipInterpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipInterpolators.java
new file mode 100644
index 0000000..927c1ec
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipInterpolators.java
@@ -0,0 +1,47 @@
+/*
+ * 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.wm.shell.pip.tv;
+
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+/**
+ * All interpolators needed for TV specific Pip animations
+ */
+public class TvPipInterpolators {
+
+ /**
+ * A standard ease-in-out curve reserved for moments of interaction (button and card states).
+ */
+ public static final Interpolator STANDARD = new PathInterpolator(0.2f, 0.1f, 0f, 1f);
+
+ /**
+ * A sharp ease-out-expo curve created for snappy but fluid browsing between cards and clusters.
+ */
+ public static final Interpolator BROWSE = new PathInterpolator(0.18f, 1f, 0.22f, 1f);
+
+ /**
+ * A smooth ease-out-expo curve created for incoming elements (forward, back, overlay).
+ */
+ public static final Interpolator ENTER = new PathInterpolator(0.12f, 1f, 0.4f, 1f);
+
+ /**
+ * A smooth ease-in-out-expo curve created for outgoing elements (forward, back, overlay).
+ */
+ public static final Interpolator EXIT = new PathInterpolator(0.4f, 1f, 0.12f, 1f);
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 77bfa07..72ead00 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -24,13 +24,18 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ParceledListSlice;
+import android.graphics.Matrix;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Handler;
+import android.os.RemoteException;
import android.util.Log;
import android.view.SurfaceControl;
+import android.view.SyncRtSurfaceTransactionApplier;
import androidx.annotation.Nullable;
+import com.android.wm.shell.R;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
@@ -53,11 +58,33 @@
private Delegate mDelegate;
private SurfaceControl mLeash;
- private TvPipMenuView mMenuView;
+ private TvPipMenuView mPipMenuView;
+
+ // User can actively move the PiP via the DPAD.
+ private boolean mInMoveMode;
private final List<RemoteAction> mMediaActions = new ArrayList<>();
private final List<RemoteAction> mAppActions = new ArrayList<>();
+ private SyncRtSurfaceTransactionApplier mApplier;
+ RectF mTmpSourceRectF = new RectF();
+ RectF mTmpDestinationRectF = new RectF();
+ Matrix mMoveTransform = new Matrix();
+
+ private final float[] mTmpValues = new float[9];
+ private final Runnable mUpdateEmbeddedMatrix = () -> {
+ if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
+ return;
+ }
+ mMoveTransform.getValues(mTmpValues);
+ try {
+ mPipMenuView.getViewRootImpl().getAccessibilityEmbeddedConnection()
+ .setScreenMatrix(mTmpValues);
+ } catch (RemoteException e) {
+ if (DEBUG) e.printStackTrace();
+ }
+ };
+
public TvPipMenuController(Context context, PipBoundsState pipBoundsState,
SystemWindows systemWindows, PipMediaController pipMediaController,
Handler mainHandler) {
@@ -107,13 +134,13 @@
private void attachPipMenuView() {
if (DEBUG) Log.d(TAG, "attachPipMenuView()");
- if (mMenuView != null) {
+ if (mPipMenuView != null) {
detachPipMenuView();
}
- mMenuView = new TvPipMenuView(mContext);
- mMenuView.setListener(this);
- mSystemWindows.addView(mMenuView,
+ mPipMenuView = new TvPipMenuView(mContext);
+ mPipMenuView.setListener(this);
+ mSystemWindows.addView(mPipMenuView,
getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
0, SHELL_ROOT_LAYER_PIP);
}
@@ -122,38 +149,76 @@
public void showMenu() {
if (DEBUG) Log.d(TAG, "showMenu()");
- if (mMenuView != null) {
- Rect pipBounds = mPipBoundsState.getBounds();
- mSystemWindows.updateViewLayout(mMenuView, getPipMenuLayoutParams(
- MENU_WINDOW_TITLE, pipBounds.width(), pipBounds.height()));
+ if (mPipMenuView != null) {
+ Rect menuBounds = getMenuBounds(mPipBoundsState.getBounds());
+ mSystemWindows.updateViewLayout(mPipMenuView, getPipMenuLayoutParams(
+ MENU_WINDOW_TITLE, menuBounds.width(), menuBounds.height()));
maybeUpdateMenuViewActions();
- SurfaceControl menuSurfaceControl = mSystemWindows.getViewSurface(mMenuView);
+ SurfaceControl menuSurfaceControl = mSystemWindows.getViewSurface(mPipMenuView);
if (menuSurfaceControl != null) {
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- t.setRelativeLayer(mMenuView.getWindowSurfaceControl(), mLeash, 1);
- t.setPosition(menuSurfaceControl, pipBounds.left, pipBounds.top);
+ t.setRelativeLayer(mPipMenuView.getWindowSurfaceControl(), mLeash, 1);
+ t.setPosition(menuSurfaceControl, menuBounds.left, menuBounds.top);
t.apply();
}
- mMenuView.show();
+ mPipMenuView.show(mInMoveMode, mDelegate.getPipGravity());
}
}
+ void updateMenu(int gravity) {
+ mPipMenuView.showMovementHints(gravity);
+ }
+
+ private Rect getMenuBounds(Rect pipBounds) {
+ int extraSpaceInPx = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.pip_menu_outer_space);
+ Rect menuBounds = new Rect(pipBounds);
+ menuBounds.inset(-extraSpaceInPx, -extraSpaceInPx);
+ return menuBounds;
+ }
+
void hideMenu() {
- hideMenu(true);
+ if (!isMenuVisible()) {
+ if (DEBUG) Log.d(TAG, "hideMenu() - Menu isn't visible, so don't hide");
+ return;
+ } else {
+ if (DEBUG) Log.d(TAG, "hideMenu()");
+ }
+
+ mPipMenuView.hide(mInMoveMode);
+ if (!mInMoveMode) {
+ mDelegate.closeMenu();
+ }
}
- void hideMenu(boolean movePipWindow) {
- if (DEBUG) Log.d(TAG, "hideMenu(), movePipWindow=" + movePipWindow);
+ @Override
+ public void onEnterMoveMode() {
+ if (DEBUG) Log.d(TAG, "onEnterMoveMode - " + mInMoveMode);
+ mInMoveMode = true;
+ mPipMenuView.showMenuButtons(false);
+ mPipMenuView.showMovementHints(mDelegate.getPipGravity());
+ }
- if (!isMenuVisible()) {
- return;
+ @Override
+ public boolean onExitMoveMode() {
+ if (DEBUG) Log.d(TAG, "onExitMoveMode - " + mInMoveMode);
+ if (mInMoveMode) {
+ mInMoveMode = false;
+ mPipMenuView.showMenuButtons(true);
+ mPipMenuView.hideMovementHints();
+ return true;
}
+ return false;
+ }
- mMenuView.hide();
- if (movePipWindow) {
- mDelegate.movePipToNormalPosition();
+ @Override
+ public boolean onPipMovement(int keycode) {
+ if (DEBUG) Log.d(TAG, "onPipMovement - " + mInMoveMode);
+ if (mInMoveMode) {
+ mDelegate.movePip(keycode);
}
+ return mInMoveMode;
}
@Override
@@ -163,17 +228,6 @@
mLeash = null;
}
- private void detachPipMenuView() {
- if (DEBUG) Log.d(TAG, "detachPipMenuView()");
-
- if (mMenuView == null) {
- return;
- }
-
- mSystemWindows.removeView(mMenuView);
- mMenuView = null;
- }
-
@Override
public void setAppActions(ParceledListSlice<RemoteAction> actions) {
if (DEBUG) Log.d(TAG, "setAppActions()");
@@ -209,24 +263,146 @@
}
private void maybeUpdateMenuViewActions() {
- if (mMenuView == null) {
+ if (mPipMenuView == null) {
return;
}
if (!mAppActions.isEmpty()) {
- mMenuView.setAdditionalActions(mAppActions, mMainHandler);
+ mPipMenuView.setAdditionalActions(mAppActions, mMainHandler);
} else {
- mMenuView.setAdditionalActions(mMediaActions, mMainHandler);
+ mPipMenuView.setAdditionalActions(mMediaActions, mMainHandler);
}
}
@Override
public boolean isMenuVisible() {
- return mMenuView != null && mMenuView.isVisible();
+ boolean isVisible = mPipMenuView != null && mPipMenuView.isVisible();
+ if (DEBUG) Log.d(TAG, "isMenuVisible: " + isVisible);
+ return isVisible;
+ }
+
+ /**
+ * Does an immediate window crop of the PiP menu.
+ */
+ @Override
+ public void resizePipMenu(@android.annotation.Nullable SurfaceControl pipLeash,
+ @android.annotation.Nullable SurfaceControl.Transaction t,
+ Rect destinationBounds) {
+ if (DEBUG) Log.d(TAG, "resizePipMenu: " + destinationBounds.toShortString());
+ if (destinationBounds.isEmpty()) {
+ return;
+ }
+
+ if (!maybeCreateSyncApplier()) {
+ return;
+ }
+
+ SurfaceControl surfaceControl = getSurfaceControl();
+ SyncRtSurfaceTransactionApplier.SurfaceParams
+ params = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(surfaceControl)
+ .withWindowCrop(getMenuBounds(destinationBounds))
+ .build();
+ if (pipLeash != null && t != null) {
+ SyncRtSurfaceTransactionApplier.SurfaceParams
+ pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
+ .withMergeTransaction(t)
+ .build();
+ mApplier.scheduleApply(params, pipParams);
+ } else {
+ mApplier.scheduleApply(params);
+ }
+ }
+
+ private SurfaceControl getSurfaceControl() {
+ return mSystemWindows.getViewSurface(mPipMenuView);
+ }
+
+ @Override
+ public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction transaction,
+ Rect pipDestBounds) {
+ if (DEBUG) Log.d(TAG, "movePipMenu: " + pipDestBounds.toShortString());
+
+ if (pipDestBounds.isEmpty()) {
+ if (transaction == null && DEBUG) Log.d(TAG, "no transaction given");
+ return;
+ }
+ if (!maybeCreateSyncApplier()) {
+ return;
+ }
+
+ Rect menuDestBounds = getMenuBounds(pipDestBounds);
+ Rect mTmpSourceBounds = new Rect();
+ // If there is no pip leash supplied, that means the PiP leash is already finalized
+ // resizing and the PiP menu is also resized. We then want to do a scale from the current
+ // new menu bounds.
+ if (pipLeash != null && transaction != null) {
+ if (DEBUG) Log.d(TAG, "mTmpSourceBounds based on mPipMenuView.getBoundsOnScreen()");
+ mPipMenuView.getBoundsOnScreen(mTmpSourceBounds);
+ } else {
+ if (DEBUG) Log.d(TAG, "mTmpSourceBounds based on menu width and height");
+ mTmpSourceBounds.set(0, 0, menuDestBounds.width(), menuDestBounds.height());
+ }
+
+ mTmpSourceRectF.set(mTmpSourceBounds);
+ mTmpDestinationRectF.set(menuDestBounds);
+ mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
+
+ SurfaceControl surfaceControl = getSurfaceControl();
+ SyncRtSurfaceTransactionApplier.SurfaceParams params =
+ new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+ surfaceControl).withMatrix(mMoveTransform).build();
+
+ if (pipLeash != null && transaction != null) {
+ SyncRtSurfaceTransactionApplier.SurfaceParams
+ pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
+ .withMergeTransaction(transaction)
+ .build();
+ mApplier.scheduleApply(params, pipParams);
+ } else {
+ mApplier.scheduleApply(params);
+ }
+
+ if (mPipMenuView.getViewRootImpl() != null) {
+ mPipMenuView.getHandler().removeCallbacks(mUpdateEmbeddedMatrix);
+ mPipMenuView.getHandler().post(mUpdateEmbeddedMatrix);
+ }
+ }
+
+ private boolean maybeCreateSyncApplier() {
+ if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
+ Log.v(TAG, "Not going to move PiP, either menu or its parent is not created.");
+ return false;
+ }
+
+ if (mApplier == null) {
+ mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView);
+ }
+ return true;
+ }
+
+ private void detachPipMenuView() {
+ if (mPipMenuView == null) {
+ return;
+ }
+
+ mApplier = null;
+ mSystemWindows.removeView(mPipMenuView);
+ mPipMenuView = null;
+ }
+
+ @Override
+ public void updateMenuBounds(Rect destinationBounds) {
+ Rect menuBounds = getMenuBounds(destinationBounds);
+ if (DEBUG) Log.d(TAG, "updateMenuBounds: " + menuBounds.toShortString());
+ mSystemWindows.updateViewLayout(mPipMenuView,
+ getPipMenuLayoutParams(MENU_WINDOW_TITLE, menuBounds.width(),
+ menuBounds.height()));
}
@Override
public void onBackPress() {
- hideMenu();
+ if (!onExitMoveMode()) {
+ hideMenu();
+ }
}
@Override
@@ -240,8 +416,14 @@
}
interface Delegate {
- void movePipToNormalPosition();
void movePipToFullscreen();
+
+ void movePip(int keycode);
+
+ int getPipGravity();
+
+ void closeMenu();
+
void closePip();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 4327f15..0141b6a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -16,17 +16,21 @@
package com.android.wm.shell.pip.tv;
-import static android.animation.AnimatorInflater.loadAnimator;
import static android.view.KeyEvent.ACTION_UP;
import static android.view.KeyEvent.KEYCODE_BACK;
+import static android.view.KeyEvent.KEYCODE_DPAD_CENTER;
+import static android.view.KeyEvent.KEYCODE_DPAD_DOWN;
+import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
+import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
+import static android.view.KeyEvent.KEYCODE_DPAD_UP;
-import android.animation.Animator;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
@@ -34,6 +38,7 @@
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
import android.widget.FrameLayout;
+import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
@@ -45,16 +50,14 @@
import java.util.List;
/**
- * A View that represents Pip Menu on TV. It's responsible for displaying 2 ever-present Pip Menu
- * actions: Fullscreen and Close, but could also display "additional" actions, that may be set via
- * a {@link #setAdditionalActions(List, Handler)} call.
+ * A View that represents Pip Menu on TV. It's responsible for displaying 3 ever-present Pip Menu
+ * actions: Fullscreen, Move and Close, but could also display "additional" actions, that may be set
+ * via a {@link #setAdditionalActions(List, Handler)} call.
*/
public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
private static final String TAG = "TvPipMenuView";
private static final boolean DEBUG = TvPipController.DEBUG;
- private final Animator mFadeInAnimation;
- private final Animator mFadeOutAnimation;
@Nullable
private Listener mListener;
@@ -62,6 +65,11 @@
private final View mMenuFrameView;
private final List<TvPipMenuActionButton> mAdditionalButtons = new ArrayList<>();
+ private final ImageView mArrowUp;
+ private final ImageView mArrowRight;
+ private final ImageView mArrowDown;
+ private final ImageView mArrowLeft;
+
public TvPipMenuView(@NonNull Context context) {
this(context, null);
}
@@ -85,35 +93,68 @@
.setOnClickListener(this);
mActionButtonsContainer.findViewById(R.id.tv_pip_menu_close_button)
.setOnClickListener(this);
+ mActionButtonsContainer.findViewById(R.id.tv_pip_menu_move_button)
+ .setOnClickListener(this);
mMenuFrameView = findViewById(R.id.tv_pip_menu_frame);
- mFadeInAnimation = loadAnimator(mContext, R.anim.tv_pip_menu_fade_in_animation);
- mFadeInAnimation.setTarget(mMenuFrameView);
- mFadeOutAnimation = loadAnimator(mContext, R.anim.tv_pip_menu_fade_out_animation);
- mFadeOutAnimation.setTarget(mMenuFrameView);
+ mArrowUp = findViewById(R.id.tv_pip_menu_arrow_up);
+ mArrowRight = findViewById(R.id.tv_pip_menu_arrow_right);
+ mArrowDown = findViewById(R.id.tv_pip_menu_arrow_down);
+ mArrowLeft = findViewById(R.id.tv_pip_menu_arrow_left);
}
void setListener(@Nullable Listener listener) {
mListener = listener;
}
- void show() {
- if (DEBUG) Log.d(TAG, "show()");
-
- mFadeInAnimation.start();
+ void show(boolean inMoveMode, int gravity) {
+ if (DEBUG) Log.d(TAG, "show(), inMoveMode: " + inMoveMode);
grantWindowFocus(true);
+
+ if (inMoveMode) {
+ showMovementHints(gravity);
+ } else {
+ animateAlphaTo(1, mActionButtonsContainer);
+ }
+ animateAlphaTo(1, mMenuFrameView);
}
- void hide() {
+ void hide(boolean isInMoveMode) {
if (DEBUG) Log.d(TAG, "hide()");
+ animateAlphaTo(0, mActionButtonsContainer);
+ animateAlphaTo(0, mMenuFrameView);
+ hideMovementHints();
- mFadeOutAnimation.start();
- grantWindowFocus(false);
+ if (!isInMoveMode) {
+ grantWindowFocus(false);
+ }
+ }
+
+ private void animateAlphaTo(float alpha, View view) {
+ view.animate()
+ .alpha(alpha)
+ .setInterpolator(alpha == 0f ? TvPipInterpolators.EXIT : TvPipInterpolators.ENTER)
+ .setDuration(500)
+ .withStartAction(() -> {
+ if (alpha != 0) {
+ view.setVisibility(VISIBLE);
+ }
+ })
+ .withEndAction(() -> {
+ if (alpha == 0) {
+ view.setVisibility(GONE);
+ }
+ });
}
boolean isVisible() {
- return mMenuFrameView != null && mMenuFrameView.getAlpha() != 0.0f;
+ return mMenuFrameView.getAlpha() != 0f
+ || mActionButtonsContainer.getAlpha() != 0f
+ || mArrowUp.getAlpha() != 0f
+ || mArrowRight.getAlpha() != 0f
+ || mArrowDown.getAlpha() != 0f
+ || mArrowLeft.getAlpha() != 0f;
}
private void grantWindowFocus(boolean grantFocus) {
@@ -188,6 +229,8 @@
final int id = v.getId();
if (id == R.id.tv_pip_menu_fullscreen_button) {
mListener.onFullscreenButtonClick();
+ } else if (id == R.id.tv_pip_menu_move_button) {
+ mListener.onEnterMoveMode();
} else if (id == R.id.tv_pip_menu_close_button) {
mListener.onCloseButtonClick();
} else {
@@ -207,17 +250,79 @@
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
- if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK
- && mListener != null) {
- mListener.onBackPress();
- return true;
+ if (DEBUG) {
+ Log.d(TAG, "dispatchKeyEvent, action: " + event.getAction()
+ + ", keycode: " + event.getKeyCode());
+ }
+ if (mListener != null && event.getAction() == ACTION_UP) {
+ switch (event.getKeyCode()) {
+ case KEYCODE_BACK:
+ mListener.onBackPress();
+ return true;
+ case KEYCODE_DPAD_UP:
+ case KEYCODE_DPAD_DOWN:
+ case KEYCODE_DPAD_LEFT:
+ case KEYCODE_DPAD_RIGHT:
+ return mListener.onPipMovement(event.getKeyCode()) || super.dispatchKeyEvent(
+ event);
+ case KEYCODE_DPAD_CENTER:
+ return mListener.onExitMoveMode() || super.dispatchKeyEvent(event);
+ default:
+ break;
+ }
}
return super.dispatchKeyEvent(event);
}
+ /**
+ * Shows user hints for moving the PiP, e.g. arrows.
+ */
+ public void showMovementHints(int gravity) {
+ if (DEBUG) Log.d(TAG, "showMovementHints(), position: " + Gravity.toString(gravity));
+
+ animateAlphaTo((gravity & Gravity.BOTTOM) == Gravity.BOTTOM ? 1f : 0f, mArrowUp);
+ animateAlphaTo((gravity & Gravity.TOP) == Gravity.TOP ? 1f : 0f, mArrowDown);
+ animateAlphaTo((gravity & Gravity.RIGHT) == Gravity.RIGHT ? 1f : 0f, mArrowLeft);
+ animateAlphaTo((gravity & Gravity.LEFT) == Gravity.LEFT ? 1f : 0f, mArrowRight);
+ }
+
+ /**
+ * Hides user hints for moving the PiP, e.g. arrows.
+ */
+ public void hideMovementHints() {
+ if (DEBUG) Log.d(TAG, "hideMovementHints()");
+ animateAlphaTo(0, mArrowUp);
+ animateAlphaTo(0, mArrowRight);
+ animateAlphaTo(0, mArrowDown);
+ animateAlphaTo(0, mArrowLeft);
+ }
+
+ /**
+ * Show or hide the pip user actions.
+ */
+ public void showMenuButtons(boolean show) {
+ if (DEBUG) Log.d(TAG, "showMenuButtons: " + show);
+ animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
+ }
+
interface Listener {
+
void onBackPress();
+
+ void onEnterMoveMode();
+
+ /**
+ * @return whether move mode was exited
+ */
+ boolean onExitMoveMode();
+
+ /**
+ * @return whether pip movement was handled.
+ */
+ boolean onPipMovement(int keycode);
+
void onCloseButtonClick();
+
void onFullscreenButtonClick();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index 551476d..5062cc4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -29,7 +29,6 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMenuController;
import com.android.wm.shell.pip.PipTransitionController;
@@ -42,11 +41,11 @@
public class TvPipTransition extends PipTransitionController {
public TvPipTransition(PipBoundsState pipBoundsState,
PipMenuController pipMenuController,
- PipBoundsAlgorithm pipBoundsAlgorithm,
+ TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipAnimationController pipAnimationController,
Transitions transitions,
@NonNull ShellTaskOrganizer shellTaskOrganizer) {
- super(pipBoundsState, pipMenuController, pipBoundsAlgorithm, pipAnimationController,
+ super(pipBoundsState, pipMenuController, tvPipBoundsAlgorithm, pipAnimationController,
transitions, shellTaskOrganizer);
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
index 2c02d2c..fb1004b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
@@ -27,7 +27,6 @@
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
import com.android.server.wm.flicker.helpers.setRotation
@@ -41,7 +40,6 @@
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.dockedStackDividerBecomesInvisible
import com.android.wm.shell.flicker.helpers.SimpleAppHelper
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -112,11 +110,7 @@
@FlakyTest(bugId = 206753786)
@Test
- fun statusBarLayerRotatesScales() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
- testSpec.statusBarLayerRotatesScales()
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
index 7d7add4..264d482 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
@@ -29,7 +29,6 @@
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.resizeSplitScreen
import com.android.server.wm.flicker.helpers.setRotation
@@ -45,7 +44,6 @@
import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.helpers.SimpleAppHelper
import com.android.wm.shell.flicker.testapp.Components
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -137,11 +135,7 @@
@FlakyTest(bugId = 206753786)
@Test
- fun statusBarLayerRotatesScales() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
- testSpec.statusBarLayerRotatesScales()
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Test
fun topAppLayerIsAlwaysVisible() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
index 5678899..d703ea0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
@@ -25,7 +25,6 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
@@ -35,7 +34,6 @@
import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -79,11 +77,7 @@
@FlakyTest(bugId = 206753786)
@Test
- fun statusBarLayerRotatesScales() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
- testSpec.statusBarLayerRotatesScales()
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
index c2edf9d..6b18839 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
@@ -25,7 +25,6 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
@@ -35,7 +34,6 @@
import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -78,11 +76,7 @@
@FlakyTest(bugId = 206753786)
@Test
- fun statusBarLayerRotatesScales() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
- testSpec.statusBarLayerRotatesScales()
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
index 777998c..acd658b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
@@ -25,7 +25,6 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
@@ -37,7 +36,6 @@
import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -88,11 +86,7 @@
@FlakyTest(bugId = 206753786)
@Test
- fun statusBarLayerRotatesScales() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
- testSpec.statusBarLayerRotatesScales()
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
index 914b11d..b40be8b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
@@ -25,7 +25,6 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
@@ -37,7 +36,6 @@
import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -93,11 +91,7 @@
@FlakyTest(bugId = 206753786)
@Test
- fun statusBarLayerRotatesScales() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
- testSpec.statusBarLayerRotatesScales()
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@FlakyTest
@Test
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 6755b7c..d862377 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -119,7 +119,7 @@
AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(cbData);
int64_t vsyncId = AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
cbData, preferredFrameTimelineIndex);
- int64_t frameDeadline = AChoreographerFrameCallbackData_getFrameTimelineDeadline(
+ int64_t frameDeadline = AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos(
cbData, preferredFrameTimelineIndex);
int64_t frameTimeNanos = AChoreographerFrameCallbackData_getFrameTimeNanos(cbData);
// TODO(b/193273294): Remove when shared memory in use w/ expected present time always current.
diff --git a/location/java/android/location/Geocoder.java b/location/java/android/location/Geocoder.java
index e4a0d0c..a158344 100644
--- a/location/java/android/location/Geocoder.java
+++ b/location/java/android/location/Geocoder.java
@@ -298,6 +298,7 @@
* @param lowerLeftLongitude the longitude of the lower left corner of the bounding box
* @param upperRightLatitude the latitude of the upper right corner of the bounding box
* @param upperRightLongitude the longitude of the upper right corner of the bounding box
+ * @param listener a listener for receiving results
*
* @throws IllegalArgumentException if locationName is null
* @throws IllegalArgumentException if any latitude or longitude is invalid
diff --git a/media/java/android/media/tv/interactive/TvIAppInfo.aidl b/location/java/android/location/GnssAutomaticGainControl.aidl
similarity index 82%
copy from media/java/android/media/tv/interactive/TvIAppInfo.aidl
copy to location/java/android/location/GnssAutomaticGainControl.aidl
index 6041460..8298cb71 100644
--- a/media/java/android/media/tv/interactive/TvIAppInfo.aidl
+++ b/location/java/android/location/GnssAutomaticGainControl.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.media.tv.interactive;
+package android.location;
-parcelable TvIAppInfo;
\ No newline at end of file
+parcelable GnssAutomaticGainControl;
diff --git a/location/java/android/location/GnssAutomaticGainControl.java b/location/java/android/location/GnssAutomaticGainControl.java
new file mode 100644
index 0000000..e4f7304
--- /dev/null
+++ b/location/java/android/location/GnssAutomaticGainControl.java
@@ -0,0 +1,215 @@
+/*
+ * 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.location;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * A class that contains GNSS Automatic Gain Control (AGC) information.
+ *
+ * <p> AGC acts as a variable gain amplifier adjusting the power of the incoming signal. The AGC
+ * level may be used to indicate potential interference. Higher gain (and/or lower input power)
+ * shall be output as a positive number. Hence in cases of strong jamming, in the band of this
+ * signal, this value will go more negative. This value must be consistent given the same level
+ * of the incoming signal power.
+ *
+ * <p> Note: Different hardware designs (e.g. antenna, pre-amplification, or other RF HW
+ * components) may also affect the typical output of this value on any given hardware design
+ * in an open sky test - the important aspect of this output is that changes in this value are
+ * indicative of changes on input signal power in the frequency band for this measurement.
+ */
+public final class GnssAutomaticGainControl implements Parcelable {
+ private final double mLevelDb;
+ private final int mConstellationType;
+ private final long mCarrierFrequencyHz;
+
+ /**
+ * Creates a {@link GnssAutomaticGainControl} with a full list of parameters.
+ */
+ private GnssAutomaticGainControl(double levelDb, int constellationType,
+ long carrierFrequencyHz) {
+ mLevelDb = levelDb;
+ mConstellationType = constellationType;
+ mCarrierFrequencyHz = carrierFrequencyHz;
+ }
+
+ /**
+ * Gets the Automatic Gain Control level in dB.
+ */
+ @FloatRange(from = -10000, to = 10000)
+ public double getLevelDb() {
+ return mLevelDb;
+ }
+
+ /**
+ * Gets the constellation type.
+ *
+ * <p>The return value is one of those constants with {@code CONSTELLATION_} prefix in
+ * {@link GnssStatus}.
+ */
+ @GnssStatus.ConstellationType
+ public int getConstellationType() {
+ return mConstellationType;
+ }
+
+ /**
+ * Gets the carrier frequency of the tracked signal.
+ *
+ * <p>For example it can be the GPS central frequency for L1 = 1575.45 MHz, or L2 = 1227.60 MHz,
+ * L5 = 1176.45 MHz, varying GLO channels, etc.
+ *
+ * @return the carrier frequency of the signal tracked in Hz.
+ */
+ @IntRange(from = 0)
+ public long getCarrierFrequencyHz() {
+ return mCarrierFrequencyHz;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flag) {
+ parcel.writeDouble(mLevelDb);
+ parcel.writeInt(mConstellationType);
+ parcel.writeLong(mCarrierFrequencyHz);
+ }
+
+ @NonNull
+ public static final Creator<GnssAutomaticGainControl> CREATOR =
+ new Creator<GnssAutomaticGainControl>() {
+ @Override
+ @NonNull
+ public GnssAutomaticGainControl createFromParcel(@NonNull Parcel parcel) {
+ return new GnssAutomaticGainControl(parcel.readDouble(), parcel.readInt(),
+ parcel.readLong());
+ }
+
+ @Override
+ public GnssAutomaticGainControl[] newArray(int i) {
+ return new GnssAutomaticGainControl[i];
+ }
+ };
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder s = new StringBuilder();
+ s.append("GnssAutomaticGainControl[");
+ s.append("Level=").append(mLevelDb).append(" dB");
+ s.append(" Constellation=").append(
+ GnssStatus.constellationTypeToString(mConstellationType));
+ s.append(" CarrierFrequency=").append(mCarrierFrequencyHz).append(" Hz");
+ s.append(']');
+ return s.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof GnssAutomaticGainControl)) {
+ return false;
+ }
+
+ GnssAutomaticGainControl other = (GnssAutomaticGainControl) obj;
+ if (Double.compare(mLevelDb, other.mLevelDb)
+ != 0) {
+ return false;
+ }
+ if (mConstellationType != other.mConstellationType) {
+ return false;
+ }
+ if (mCarrierFrequencyHz != other.mCarrierFrequencyHz) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mLevelDb, mConstellationType, mCarrierFrequencyHz);
+ }
+
+ /** Builder for {@link GnssAutomaticGainControl} */
+ public static final class Builder {
+ private double mLevelDb;
+ private int mConstellationType;
+ private long mCarrierFrequencyHz;
+
+ /**
+ * Constructs a {@link GnssAutomaticGainControl.Builder} instance.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Constructs a {@link GnssAutomaticGainControl.Builder} instance by copying a
+ * {@link GnssAutomaticGainControl}.
+ */
+ public Builder(@NonNull GnssAutomaticGainControl agc) {
+ mLevelDb = agc.getLevelDb();
+ mConstellationType = agc.getConstellationType();
+ mCarrierFrequencyHz = agc.getCarrierFrequencyHz();
+ }
+
+ /**
+ * Sets the Automatic Gain Control level in dB.
+ */
+ @NonNull
+ public Builder setLevelDb(@FloatRange(from = -10000, to = 10000) double levelDb) {
+ Preconditions.checkArgument(levelDb >= -10000 && levelDb <= 10000);
+ mLevelDb = levelDb;
+ return this;
+ }
+
+ /**
+ * Sets the constellation type.
+ */
+ @NonNull
+ public Builder setConstellationType(@GnssStatus.ConstellationType int constellationType) {
+ mConstellationType = constellationType;
+ return this;
+ }
+
+ /**
+ * Sets the Carrier frequency in Hz.
+ */
+ @NonNull public Builder setCarrierFrequencyHz(@IntRange(from = 0) long carrierFrequencyHz) {
+ Preconditions.checkArgumentNonnegative(carrierFrequencyHz);
+ mCarrierFrequencyHz = carrierFrequencyHz;
+ return this;
+ }
+
+ /** Builds a {@link GnssAutomaticGainControl} instance as specified by this builder. */
+ @NonNull
+ public GnssAutomaticGainControl build() {
+ return new GnssAutomaticGainControl(mLevelDb, mConstellationType, mCarrierFrequencyHz);
+ }
+ }
+}
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index ecdd4b6..cdfa02c 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -1381,7 +1381,10 @@
/**
* Returns {@code true} if {@link #getAutomaticGainControlLevelDb()} is available,
* {@code false} otherwise.
+ *
+ * @deprecated Use {@link GnssMeasurementsEvent#getGnssAutomaticGainControls()} instead.
*/
+ @Deprecated
public boolean hasAutomaticGainControlLevelDb() {
return isFlagSet(HAS_AUTOMATIC_GAIN_CONTROL);
}
@@ -1401,7 +1404,10 @@
* indicative of changes on input signal power in the frequency band for this measurement.
*
* <p> The value is only available if {@link #hasAutomaticGainControlLevelDb()} is {@code true}
+ *
+ * @deprecated Use {@link GnssMeasurementsEvent#getGnssAutomaticGainControls()} instead.
*/
+ @Deprecated
public double getAutomaticGainControlLevelDb() {
return mAutomaticGainControlLevelInDb;
}
@@ -1409,7 +1415,9 @@
/**
* Sets the Automatic Gain Control level in dB.
* @hide
+ * @deprecated Use {@link GnssMeasurementsEvent.Builder#setGnssAutomaticGainControls()} instead.
*/
+ @Deprecated
@TestApi
public void setAutomaticGainControlLevelInDb(double agcLevelDb) {
setFlag(HAS_AUTOMATIC_GAIN_CONTROL);
diff --git a/location/java/android/location/GnssMeasurementsEvent.java b/location/java/android/location/GnssMeasurementsEvent.java
index a07a64a..075ddeb 100644
--- a/location/java/android/location/GnssMeasurementsEvent.java
+++ b/location/java/android/location/GnssMeasurementsEvent.java
@@ -18,16 +18,19 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.TestApi;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.Preconditions;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.security.InvalidParameterException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.List;
/**
* A class implementing a container for data associated with a measurement event.
@@ -35,7 +38,8 @@
*/
public final class GnssMeasurementsEvent implements Parcelable {
private final GnssClock mClock;
- private final Collection<GnssMeasurement> mReadOnlyMeasurements;
+ private final List<GnssMeasurement> mMeasurements;
+ private final List<GnssAutomaticGainControl> mGnssAgcs;
/**
* Used for receiving GNSS satellite measurements from the GNSS engine.
@@ -116,20 +120,13 @@
}
/**
- * @hide
+ * Create a {@link GnssMeasurementsEvent} instance with a full list of parameters.
*/
- @TestApi
- public GnssMeasurementsEvent(GnssClock clock, GnssMeasurement[] measurements) {
- if (clock == null) {
- throw new InvalidParameterException("Parameter 'clock' must not be null.");
- }
- if (measurements == null || measurements.length == 0) {
- mReadOnlyMeasurements = Collections.emptyList();
- } else {
- Collection<GnssMeasurement> measurementCollection = Arrays.asList(measurements);
- mReadOnlyMeasurements = Collections.unmodifiableCollection(measurementCollection);
- }
-
+ private GnssMeasurementsEvent(@NonNull GnssClock clock,
+ @NonNull List<GnssMeasurement> measurements,
+ @NonNull List<GnssAutomaticGainControl> agcs) {
+ mMeasurements = measurements;
+ mGnssAgcs = agcs;
mClock = clock;
}
@@ -143,26 +140,31 @@
}
/**
- * Gets a read-only collection of measurements associated with the current event.
+ * Gets the collection of measurements associated with the current event.
*/
@NonNull
public Collection<GnssMeasurement> getMeasurements() {
- return mReadOnlyMeasurements;
+ return mMeasurements;
+ }
+
+ /**
+ * Gets the collection of {@link GnssAutomaticGainControl} associated with the
+ * current event.
+ */
+ @NonNull
+ public Collection<GnssAutomaticGainControl> getGnssAutomaticGainControls() {
+ return mGnssAgcs;
}
public static final @android.annotation.NonNull Creator<GnssMeasurementsEvent> CREATOR =
new Creator<GnssMeasurementsEvent>() {
@Override
public GnssMeasurementsEvent createFromParcel(Parcel in) {
- ClassLoader classLoader = getClass().getClassLoader();
-
- GnssClock clock = in.readParcelable(classLoader);
-
- int measurementsLength = in.readInt();
- GnssMeasurement[] measurementsArray = new GnssMeasurement[measurementsLength];
- in.readTypedArray(measurementsArray, GnssMeasurement.CREATOR);
-
- return new GnssMeasurementsEvent(clock, measurementsArray);
+ GnssClock clock = in.readParcelable(getClass().getClassLoader());
+ List<GnssMeasurement> measurements = in.createTypedArrayList(GnssMeasurement.CREATOR);
+ List<GnssAutomaticGainControl> agcs = in.createTypedArrayList(
+ GnssAutomaticGainControl.CREATOR);
+ return new GnssMeasurementsEvent(clock, measurements, agcs);
}
@Override
@@ -179,28 +181,105 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeParcelable(mClock, flags);
-
- int measurementsCount = mReadOnlyMeasurements.size();
- GnssMeasurement[] measurementsArray =
- mReadOnlyMeasurements.toArray(new GnssMeasurement[measurementsCount]);
- parcel.writeInt(measurementsArray.length);
- parcel.writeTypedArray(measurementsArray, flags);
+ parcel.writeTypedList(mMeasurements);
+ parcel.writeTypedList(mGnssAgcs);
}
@Override
public String toString() {
- StringBuilder builder = new StringBuilder("[ GnssMeasurementsEvent:\n\n");
+ StringBuilder builder = new StringBuilder("GnssMeasurementsEvent[");
+ builder.append(mClock);
+ builder.append(' ').append(mMeasurements.toString());
+ builder.append(' ').append(mGnssAgcs.toString());
+ builder.append("]");
+ return builder.toString();
+ }
- builder.append(mClock.toString());
- builder.append("\n");
+ /** Builder for {@link GnssMeasurementsEvent} */
+ public static final class Builder {
+ private GnssClock mClock;
+ private List<GnssMeasurement> mMeasurements;
+ private List<GnssAutomaticGainControl> mGnssAgcs;
- for (GnssMeasurement measurement : mReadOnlyMeasurements) {
- builder.append(measurement.toString());
- builder.append("\n");
+ /**
+ * Constructs a {@link GnssMeasurementsEvent.Builder} instance.
+ */
+ public Builder() {
+ mClock = new GnssClock();
+ mMeasurements = new ArrayList<>();
+ mGnssAgcs = new ArrayList<>();
}
- builder.append("]");
+ /**
+ * Constructs a {@link GnssMeasurementsEvent.Builder} instance by copying a
+ * {@link GnssMeasurementsEvent}.
+ */
+ public Builder(@NonNull GnssMeasurementsEvent event) {
+ mClock = event.getClock();
+ mMeasurements = (List<GnssMeasurement>) event.getMeasurements();
+ mGnssAgcs = (List<GnssAutomaticGainControl>) event.getGnssAutomaticGainControls();
+ }
- return builder.toString();
+ /**
+ * Sets the {@link GnssClock}.
+ */
+ @NonNull
+ public Builder setClock(@NonNull GnssClock clock) {
+ Preconditions.checkNotNull(clock);
+ mClock = clock;
+ return this;
+ }
+
+ /**
+ * Sets the collection of {@link GnssMeasurement}.
+ *
+ * This API exists for JNI since it is easier for JNI to work with an array than a
+ * collection.
+ * @hide
+ */
+ @NonNull
+ public Builder setMeasurements(@Nullable GnssMeasurement... measurements) {
+ mMeasurements = measurements == null ? Collections.emptyList() : Arrays.asList(
+ measurements);
+ return this;
+ }
+
+ /**
+ * Sets the collection of {@link GnssMeasurement}.
+ */
+ @NonNull
+ public Builder setMeasurements(@NonNull Collection<GnssMeasurement> measurements) {
+ mMeasurements = new ArrayList<>(measurements);
+ return this;
+ }
+
+ /**
+ * Sets the collection of {@link GnssAutomaticGainControl}.
+ *
+ * This API exists for JNI since it is easier for JNI to work with an array than a
+ * collection.
+ * @hide
+ */
+ @NonNull
+ public Builder setGnssAutomaticGainControls(@Nullable GnssAutomaticGainControl... agcs) {
+ mGnssAgcs = agcs == null ? Collections.emptyList() : Arrays.asList(agcs);
+ return this;
+ }
+
+ /**
+ * Sets the collection of {@link GnssAutomaticGainControl}.
+ */
+ @NonNull
+ public Builder setGnssAutomaticGainControls(
+ @NonNull Collection<GnssAutomaticGainControl> agcs) {
+ mGnssAgcs = new ArrayList<>(agcs);
+ return this;
+ }
+
+ /** Builds a {@link GnssMeasurementsEvent} instance as specified by this builder. */
+ @NonNull
+ public GnssMeasurementsEvent build() {
+ return new GnssMeasurementsEvent(mClock, mMeasurements, mGnssAgcs);
+ }
}
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 21fc6ec..68e5d94 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -79,6 +79,7 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -2995,19 +2996,17 @@
void onModeChanged(@AudioMode int mode);
}
- private final Object mModeListenerLock = new Object();
/**
- * List of listeners for audio mode and their associated Executor.
- * List is lazy-initialized on first registration
+ * manages the OnModeChangedListener listeners and the ModeDispatcherStub
*/
- @GuardedBy("mModeListenerLock")
- private @Nullable ArrayList<ListenerInfo<OnModeChangedListener>> mModeListeners;
+ private final CallbackUtil.LazyListenerManager<OnModeChangedListener> mModeChangedListenerMgr =
+ new CallbackUtil.LazyListenerManager();
- @GuardedBy("mModeListenerLock")
- private ModeDispatcherStub mModeDispatcherStub;
- private final class ModeDispatcherStub extends IAudioModeDispatcher.Stub {
+ final class ModeDispatcherStub extends IAudioModeDispatcher.Stub
+ implements CallbackUtil.DispatcherStub {
+ @Override
public void register(boolean register) {
try {
if (register) {
@@ -3021,10 +3020,8 @@
}
@Override
- @SuppressLint("GuardedBy") // lock applied inside callListeners method
public void dispatchAudioModeChanged(int mode) {
- CallbackUtil.callListeners(mModeListeners, mModeListenerLock,
- (listener) -> listener.onModeChanged(mode));
+ mModeChangedListenerMgr.callListeners((listener) -> listener.onModeChanged(mode));
}
}
@@ -3037,15 +3034,8 @@
public void addOnModeChangedListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull OnModeChangedListener listener) {
- synchronized (mModeListenerLock) {
- final Pair<ArrayList<ListenerInfo<OnModeChangedListener>>, ModeDispatcherStub> res =
- CallbackUtil.addListener("addOnModeChangedListener",
- executor, listener, mModeListeners, mModeDispatcherStub,
- () -> new ModeDispatcherStub(),
- stub -> stub.register(true));
- mModeListeners = res.first;
- mModeDispatcherStub = res.second;
- }
+ mModeChangedListenerMgr.addListener(executor, listener, "addOnModeChangedListener",
+ () -> new ModeDispatcherStub());
}
/**
@@ -3054,14 +3044,7 @@
* @param listener
*/
public void removeOnModeChangedListener(@NonNull OnModeChangedListener listener) {
- synchronized (mModeListenerLock) {
- final Pair<ArrayList<ListenerInfo<OnModeChangedListener>>, ModeDispatcherStub> res =
- CallbackUtil.removeListener("removeOnModeChangedListener",
- listener, mModeListeners, mModeDispatcherStub,
- stub -> stub.register(false));
- mModeListeners = res.first;
- mModeDispatcherStub = res.second;
- }
+ mModeChangedListenerMgr.removeListener(listener, "removeOnModeChangedListener");
}
/**
@@ -5666,6 +5649,43 @@
}
/**
+ * Get the audio devices that would be used for the routing of the given audio attributes.
+ * These are the devices anticipated to play sound from an {@link AudioTrack} created with
+ * the specified {@link AudioAttributes}.
+ * The audio routing can change if audio devices are physically connected or disconnected or
+ * concurrently through {@link AudioRouting} or {@link MediaRouter}.
+ * @param attributes the {@link AudioAttributes} for which the routing is being queried
+ * @return an empty list if there was an issue with the request, a list of
+ * {@link AudioDeviceInfo} otherwise (typically one device, except for duplicated paths).
+ */
+ public @NonNull List<AudioDeviceInfo> getAudioDevicesForAttributes(
+ @NonNull AudioAttributes attributes) {
+ final List<AudioDeviceAttributes> devicesForAttributes;
+ try {
+ Objects.requireNonNull(attributes);
+ final IAudioService service = getService();
+ devicesForAttributes = service.getDevicesForAttributesUnprotected(attributes);
+ } catch (Exception e) {
+ Log.i(TAG, "No audio devices available for specified attributes.");
+ return Collections.emptyList();
+ }
+
+ // Map from AudioDeviceAttributes to AudioDeviceInfo
+ AudioDeviceInfo[] outputDeviceInfos = getDevicesStatic(GET_DEVICES_OUTPUTS);
+ List<AudioDeviceInfo> deviceInfosForAttributes = new ArrayList<>();
+ for (AudioDeviceAttributes deviceForAttributes : devicesForAttributes) {
+ for (AudioDeviceInfo deviceInfo : outputDeviceInfos) {
+ if (deviceForAttributes.getType() == deviceInfo.getType()
+ && TextUtils.equals(deviceForAttributes.getAddress(),
+ deviceInfo.getAddress())) {
+ deviceInfosForAttributes.add(deviceInfo);
+ }
+ }
+ }
+ return Collections.unmodifiableList(deviceInfosForAttributes);
+ }
+
+ /**
* @hide
* Volume behavior for an audio device that has no particular volume behavior set. Invalid as
* an argument to {@link #setDeviceVolumeBehavior(AudioDeviceAttributes, int)} and should not
@@ -7718,6 +7738,12 @@
}
/**
+ * manages the OnCommunicationDeviceChangedListener listeners and the
+ * CommunicationDeviceDispatcherStub
+ */
+ private final CallbackUtil.LazyListenerManager<OnCommunicationDeviceChangedListener>
+ mCommDeviceChangedListenerMgr = new CallbackUtil.LazyListenerManager();
+ /**
* Adds a listener for being notified of changes to the communication audio device.
* See {@link #setCommunicationDevice(AudioDeviceInfo)}.
* @param executor
@@ -7726,16 +7752,9 @@
public void addOnCommunicationDeviceChangedListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull OnCommunicationDeviceChangedListener listener) {
- synchronized (mCommDevListenerLock) {
- final Pair<ArrayList<ListenerInfo<OnCommunicationDeviceChangedListener>>,
- CommunicationDeviceDispatcherStub> res =
- CallbackUtil.addListener("addOnCommunicationDeviceChangedListener",
- executor, listener, mCommDevListeners, mCommDevDispatcherStub,
- () -> new CommunicationDeviceDispatcherStub(),
- stub -> stub.register(true));
- mCommDevListeners = res.first;
- mCommDevDispatcherStub = res.second;
- }
+ mCommDeviceChangedListenerMgr.addListener(
+ executor, listener, "addOnCommunicationDeviceChangedListener",
+ () -> new CommunicationDeviceDispatcherStub());
}
/**
@@ -7745,32 +7764,14 @@
*/
public void removeOnCommunicationDeviceChangedListener(
@NonNull OnCommunicationDeviceChangedListener listener) {
- synchronized (mCommDevListenerLock) {
- final Pair<ArrayList<ListenerInfo<OnCommunicationDeviceChangedListener>>,
- CommunicationDeviceDispatcherStub> res =
- CallbackUtil.removeListener("removeOnCommunicationDeviceChangedListener",
- listener, mCommDevListeners, mCommDevDispatcherStub,
- stub -> stub.register(false));
- mCommDevListeners = res.first;
- mCommDevDispatcherStub = res.second;
- }
+ mCommDeviceChangedListenerMgr.removeListener(listener,
+ "removeOnCommunicationDeviceChangedListener");
}
- private final Object mCommDevListenerLock = new Object();
- /**
- * List of listeners for preferred device for strategy and their associated Executor.
- * List is lazy-initialized on first registration
- */
- @GuardedBy("mCommDevListenerLock")
- private @Nullable
- ArrayList<ListenerInfo<OnCommunicationDeviceChangedListener>> mCommDevListeners;
-
- @GuardedBy("mCommDevListenerLock")
- private CommunicationDeviceDispatcherStub mCommDevDispatcherStub;
-
private final class CommunicationDeviceDispatcherStub
- extends ICommunicationDeviceDispatcher.Stub {
+ extends ICommunicationDeviceDispatcher.Stub implements CallbackUtil.DispatcherStub {
+ @Override
public void register(boolean register) {
try {
if (register) {
@@ -7784,10 +7785,9 @@
}
@Override
- @SuppressLint("GuardedBy") // lock applied inside callListeners method
public void dispatchCommunicationDeviceChanged(int portId) {
AudioDeviceInfo device = getDeviceForPortId(portId, GET_DEVICES_OUTPUTS);
- CallbackUtil.callListeners(mCommDevListeners, mCommDevListenerLock,
+ mCommDeviceChangedListenerMgr.callListeners(
(listener) -> listener.onCommunicationDeviceChanged(device));
}
}
diff --git a/media/java/android/media/BtProfileConnectionInfo.java b/media/java/android/media/BtProfileConnectionInfo.java
index 19ea2de..d1bb41e 100644
--- a/media/java/android/media/BtProfileConnectionInfo.java
+++ b/media/java/android/media/BtProfileConnectionInfo.java
@@ -34,7 +34,7 @@
/** @hide */
@IntDef({
BluetoothProfile.A2DP,
- BluetoothProfile.A2DP_SINK, // Can only be set by BtHelper
+ BluetoothProfile.A2DP_SINK,
BluetoothProfile.HEADSET, // Can only be set by BtHelper
BluetoothProfile.HEARING_AID,
BluetoothProfile.LE_AUDIO,
@@ -105,6 +105,16 @@
}
/**
+ * Constructor for A2dp sink info
+ * The {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent will not be sent.
+ *
+ * @param volume of device -1 to ignore value
+ */
+ public static @NonNull BtProfileConnectionInfo a2dpSinkInfo(int volume) {
+ return new BtProfileConnectionInfo(BluetoothProfile.A2DP_SINK, true, volume, false);
+ }
+
+ /**
* Constructor for hearing aid info
*
* @param suppressNoisyIntent if true the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY}
diff --git a/media/java/android/media/CallbackUtil.java b/media/java/android/media/CallbackUtil.java
index ac39317..2b5fd25 100644
--- a/media/java/android/media/CallbackUtil.java
+++ b/media/java/android/media/CallbackUtil.java
@@ -18,11 +18,14 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.media.permission.ClearCallingIdentityContext;
import android.media.permission.SafeCloseable;
import android.util.Log;
import android.util.Pair;
+import com.android.internal.annotations.GuardedBy;
+
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -221,4 +224,87 @@
}
}
+
+ /**
+ * Interface to be implemented by stub implementation for the events received from a server
+ * to the class managing the listener API.
+ * For an example see {@link AudioManager#ModeDispatcherStub} which registers with AudioService.
+ */
+ interface DispatcherStub {
+ /**
+ * Register/unregister the stub as a listener of the events to be forwarded to the listeners
+ * managed by LazyListenerManager.
+ * @param register true for registering, false to unregister
+ */
+ void register(boolean register);
+ }
+
+ /**
+ * Class to manage a list of listeners and their callback, and the associated stub which
+ * receives the events to be forwarded to the listeners.
+ * The list of listeners and the stub and its registration are lazily initialized and registered
+ * @param <T> the listener class
+ */
+ static class LazyListenerManager<T> {
+ private final Object mListenerLock = new Object();
+
+ @GuardedBy("mListenerLock")
+ private @Nullable ArrayList<ListenerInfo<T>> mListeners;
+
+ @GuardedBy("mListenerLock")
+ private @Nullable DispatcherStub mDispatcherStub;
+
+ LazyListenerManager() {
+ // nothing to initialize as instances of dispatcher and list of listeners
+ // are lazily initialized
+ }
+
+ /**
+ * Add a new listener / executor pair for the configured listener
+ * @param executor Executor for the callback
+ * @param listener the listener to register
+ * @param methodName the name of the method calling this utility method for easier to read
+ * exception messages
+ * @param newStub how to build a new instance of the stub receiving the events when the
+ * number of listeners goes from 0 to 1, not called until then.
+ */
+ void addListener(@NonNull Executor executor, @NonNull T listener, String methodName,
+ @NonNull java.util.function.Supplier<DispatcherStub> newStub) {
+ synchronized (mListenerLock) {
+ final Pair<ArrayList<ListenerInfo<T>>, DispatcherStub> res =
+ CallbackUtil.addListener(methodName,
+ executor, listener, mListeners, mDispatcherStub,
+ newStub,
+ stub -> stub.register(true));
+ mListeners = res.first;
+ mDispatcherStub = res.second;
+ }
+ }
+
+ /**
+ * Remove a previously registered listener
+ * @param listener the listener to unregister
+ * @param methodName the name of the method calling this utility method for easier to read
+ * exception messages
+ */
+ void removeListener(@NonNull T listener, String methodName) {
+ synchronized (mListenerLock) {
+ final Pair<ArrayList<ListenerInfo<T>>, DispatcherStub> res =
+ CallbackUtil.removeListener(methodName,
+ listener, mListeners, mDispatcherStub,
+ stub -> stub.register(false));
+ mListeners = res.first;
+ mDispatcherStub = res.second;
+ }
+ }
+
+ /**
+ * Call the registered listeners with the given callback method
+ * @param callback the listener method to invoke
+ */
+ @SuppressLint("GuardedBy") // lock applied inside callListeners method
+ void callListeners(CallbackMethod<T> callback) {
+ CallbackUtil.callListeners(mListeners, mListenerLock, callback);
+ }
+ }
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 7f6fb90..96199a9 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -308,6 +308,8 @@
List<AudioDeviceAttributes> getDevicesForAttributes(in AudioAttributes attributes);
+ List<AudioDeviceAttributes> getDevicesForAttributesUnprotected(in AudioAttributes attributes);
+
int setAllowedCapturePolicy(in int capturePolicy);
int getAllowedCapturePolicy();
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index 69ce8d2..09d7fbd 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -1124,6 +1124,8 @@
private class SurfaceImage extends android.media.Image {
public SurfaceImage(int format) {
mFormat = format;
+ mHardwareBufferFormat = ImageReader.this.mHardwareBufferFormat;
+ mDataSpace = ImageReader.this.mDataSpace;
}
SurfaceImage(int hardwareBufferFormat, long dataSpace) {
@@ -1229,6 +1231,12 @@
}
@Override
+ public long getDataSpace() {
+ throwISEIfImageIsInvalid();
+ return mDataSpace;
+ }
+
+ @Override
public void setTimestamp(long timestampNs) {
throwISEIfImageIsInvalid();
mTimestamp = timestampNs;
diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java
index c0793ec..f3f8bbe 100644
--- a/media/java/android/media/Spatializer.java
+++ b/media/java/android/media/Spatializer.java
@@ -29,7 +29,6 @@
import android.media.permission.SafeCloseable;
import android.os.RemoteException;
import android.util.Log;
-import android.util.Pair;
import com.android.internal.annotations.GuardedBy;
@@ -376,16 +375,8 @@
public void addOnSpatializerStateChangedListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull OnSpatializerStateChangedListener listener) {
- synchronized (mStateListenerLock) {
- final Pair<ArrayList<ListenerInfo<OnSpatializerStateChangedListener>>,
- SpatializerInfoDispatcherStub> res =
- CallbackUtil.addListener("addOnSpatializerStateChangedListener",
- executor, listener, mStateListeners, mInfoDispatcherStub,
- () -> new SpatializerInfoDispatcherStub(),
- stub -> stub.register(true));
- mStateListeners = res.first;
- mInfoDispatcherStub = res.second;
- }
+ mStateListenerMgr.addListener(executor, listener, "addOnSpatializerStateChangedListener",
+ () -> new SpatializerInfoDispatcherStub());
}
/**
@@ -396,15 +387,7 @@
*/
public void removeOnSpatializerStateChangedListener(
@NonNull OnSpatializerStateChangedListener listener) {
- synchronized (mStateListenerLock) {
- final Pair<ArrayList<ListenerInfo<OnSpatializerStateChangedListener>>,
- SpatializerInfoDispatcherStub> res =
- CallbackUtil.removeListener("removeOnSpatializerStateChangedListener",
- listener, mStateListeners, mInfoDispatcherStub,
- stub -> stub.register(false));
- mStateListeners = res.first;
- mInfoDispatcherStub = res.second;
- }
+ mStateListenerMgr.removeListener(listener, "removeOnSpatializerStateChangedListener");
}
/**
@@ -459,18 +442,16 @@
}
}
- private final Object mStateListenerLock = new Object();
/**
- * List of listeners for state listener and their associated Executor.
- * List is lazy-initialized on first registration
+ * manages the OnSpatializerStateChangedListener listeners and the
+ * SpatializerInfoDispatcherStub
*/
- @GuardedBy("mStateListenerLock")
- private @Nullable ArrayList<ListenerInfo<OnSpatializerStateChangedListener>> mStateListeners;
+ private final CallbackUtil.LazyListenerManager<OnSpatializerStateChangedListener>
+ mStateListenerMgr = new CallbackUtil.LazyListenerManager();
- @GuardedBy("mStateListenerLock")
- private @Nullable SpatializerInfoDispatcherStub mInfoDispatcherStub;
-
- private final class SpatializerInfoDispatcherStub extends ISpatializerCallback.Stub {
+ private final class SpatializerInfoDispatcherStub extends ISpatializerCallback.Stub
+ implements CallbackUtil.DispatcherStub {
+ @Override
public void register(boolean register) {
try {
if (register) {
@@ -486,7 +467,7 @@
@Override
@SuppressLint("GuardedBy") // lock applied inside callListeners method
public void dispatchSpatializerEnabledChanged(boolean enabled) {
- CallbackUtil.callListeners(mStateListeners, mStateListenerLock,
+ mStateListenerMgr.callListeners(
(listener) -> listener.onSpatializerEnabledChanged(
Spatializer.this, enabled));
}
@@ -494,7 +475,7 @@
@Override
@SuppressLint("GuardedBy") // lock applied inside callListeners method
public void dispatchSpatializerAvailableChanged(boolean available) {
- CallbackUtil.callListeners(mStateListeners, mStateListenerLock,
+ mStateListenerMgr.callListeners(
(listener) -> listener.onSpatializerAvailableChanged(
Spatializer.this, available));
}
@@ -612,16 +593,9 @@
public void addOnHeadTrackingModeChangedListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull OnHeadTrackingModeChangedListener listener) {
- synchronized (mHeadTrackingListenerLock) {
- final Pair<ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>,
- SpatializerHeadTrackingDispatcherStub> res = CallbackUtil.addListener(
- "addOnHeadTrackingModeChangedListener", executor, listener,
- mHeadTrackingListeners, mHeadTrackingDispatcherStub,
- () -> new SpatializerHeadTrackingDispatcherStub(),
- stub -> stub.register(true));
- mHeadTrackingListeners = res.first;
- mHeadTrackingDispatcherStub = res.second;
- }
+ mHeadTrackingListenerMgr.addListener(executor, listener,
+ "addOnHeadTrackingModeChangedListener",
+ () -> new SpatializerHeadTrackingDispatcherStub());
}
/**
@@ -634,15 +608,8 @@
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
public void removeOnHeadTrackingModeChangedListener(
@NonNull OnHeadTrackingModeChangedListener listener) {
- synchronized (mHeadTrackingListenerLock) {
- final Pair<ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>,
- SpatializerHeadTrackingDispatcherStub> res = CallbackUtil.removeListener(
- "removeOnHeadTrackingModeChangedListener", listener,
- mHeadTrackingListeners, mHeadTrackingDispatcherStub,
- stub -> stub.register(false));
- mHeadTrackingListeners = res.first;
- mHeadTrackingDispatcherStub = res.second;
- }
+ mHeadTrackingListenerMgr.removeListener(listener,
+ "removeOnHeadTrackingModeChangedListener");
}
/**
@@ -828,20 +795,17 @@
//-----------------------------------------------------------------------------
// head tracking callback management and stub
- private final Object mHeadTrackingListenerLock = new Object();
/**
- * List of listeners for head tracking mode listener and their associated Executor.
- * List is lazy-initialized on first registration
+ * manages the OnHeadTrackingModeChangedListener listeners and the
+ * SpatializerHeadTrackingDispatcherStub
*/
- @GuardedBy("mHeadTrackingListenerLock")
- private @Nullable ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>
- mHeadTrackingListeners;
-
- @GuardedBy("mHeadTrackingListenerLock")
- private @Nullable SpatializerHeadTrackingDispatcherStub mHeadTrackingDispatcherStub;
+ private final CallbackUtil.LazyListenerManager<OnHeadTrackingModeChangedListener>
+ mHeadTrackingListenerMgr = new CallbackUtil.LazyListenerManager();
private final class SpatializerHeadTrackingDispatcherStub
- extends ISpatializerHeadTrackingModeCallback.Stub {
+ extends ISpatializerHeadTrackingModeCallback.Stub
+ implements CallbackUtil.DispatcherStub {
+ @Override
public void register(boolean register) {
try {
if (register) {
@@ -857,14 +821,14 @@
@Override
@SuppressLint("GuardedBy") // lock applied inside callListeners method
public void dispatchSpatializerActualHeadTrackingModeChanged(int mode) {
- CallbackUtil.callListeners(mHeadTrackingListeners, mHeadTrackingListenerLock,
+ mHeadTrackingListenerMgr.callListeners(
(listener) -> listener.onHeadTrackingModeChanged(Spatializer.this, mode));
}
@Override
@SuppressLint("GuardedBy") // lock applied inside callListeners method
public void dispatchSpatializerDesiredHeadTrackingModeChanged(int mode) {
- CallbackUtil.callListeners(mHeadTrackingListeners, mHeadTrackingListenerLock,
+ mHeadTrackingListenerMgr.callListeners(
(listener) -> listener.onDesiredHeadTrackingModeChanged(
Spatializer.this, mode));
}
diff --git a/media/java/android/media/tv/BroadcastInfoRequest.java b/media/java/android/media/tv/BroadcastInfoRequest.java
index 85ad3cd..6ba9133 100644
--- a/media/java/android/media/tv/BroadcastInfoRequest.java
+++ b/media/java/android/media/tv/BroadcastInfoRequest.java
@@ -49,8 +49,10 @@
return StreamEventRequest.createFromParcelBody(source);
case TvInputManager.BROADCAST_INFO_TYPE_DSMCC:
return DsmccRequest.createFromParcelBody(source);
- case TvInputManager.BROADCAST_INFO_TYPE_TV_PROPRIETARY_FUNCTION:
+ case TvInputManager.BROADCAST_INFO_TYPE_COMMAND:
return CommandRequest.createFromParcelBody(source);
+ case TvInputManager.BROADCAST_INFO_TYPE_TIMELINE:
+ return TimelineRequest.createFromParcelBody(source);
default:
throw new IllegalStateException(
"Unexpected broadcast info request type (value "
diff --git a/media/java/android/media/tv/BroadcastInfoResponse.java b/media/java/android/media/tv/BroadcastInfoResponse.java
index e423aba..67bdedc 100644
--- a/media/java/android/media/tv/BroadcastInfoResponse.java
+++ b/media/java/android/media/tv/BroadcastInfoResponse.java
@@ -50,8 +50,10 @@
return StreamEventResponse.createFromParcelBody(source);
case TvInputManager.BROADCAST_INFO_TYPE_DSMCC:
return DsmccResponse.createFromParcelBody(source);
- case TvInputManager.BROADCAST_INFO_TYPE_TV_PROPRIETARY_FUNCTION:
+ case TvInputManager.BROADCAST_INFO_TYPE_COMMAND:
return CommandResponse.createFromParcelBody(source);
+ case TvInputManager.BROADCAST_INFO_TYPE_TIMELINE:
+ return TimelineResponse.createFromParcelBody(source);
default:
throw new IllegalStateException(
"Unexpected broadcast info response type (value "
diff --git a/media/java/android/media/tv/CommandRequest.java b/media/java/android/media/tv/CommandRequest.java
index 2391fa3..d61c858 100644
--- a/media/java/android/media/tv/CommandRequest.java
+++ b/media/java/android/media/tv/CommandRequest.java
@@ -23,7 +23,7 @@
/** @hide */
public final class CommandRequest extends BroadcastInfoRequest implements Parcelable {
public static final @TvInputManager.BroadcastInfoType int requestType =
- TvInputManager.BROADCAST_INFO_TYPE_TV_PROPRIETARY_FUNCTION;
+ TvInputManager.BROADCAST_INFO_TYPE_COMMAND;
public static final @NonNull Parcelable.Creator<CommandRequest> CREATOR =
new Parcelable.Creator<CommandRequest>() {
diff --git a/media/java/android/media/tv/CommandResponse.java b/media/java/android/media/tv/CommandResponse.java
index d34681f..af3d00c 100644
--- a/media/java/android/media/tv/CommandResponse.java
+++ b/media/java/android/media/tv/CommandResponse.java
@@ -23,7 +23,7 @@
/** @hide */
public final class CommandResponse extends BroadcastInfoResponse implements Parcelable {
public static final @TvInputManager.BroadcastInfoType int responseType =
- TvInputManager.BROADCAST_INFO_TYPE_TV_PROPRIETARY_FUNCTION;
+ TvInputManager.BROADCAST_INFO_TYPE_COMMAND;
public static final @NonNull Parcelable.Creator<CommandResponse> CREATOR =
new Parcelable.Creator<CommandResponse>() {
diff --git a/media/java/android/media/tv/DsmccResponse.java b/media/java/android/media/tv/DsmccResponse.java
index e43d31a..4d49620 100644
--- a/media/java/android/media/tv/DsmccResponse.java
+++ b/media/java/android/media/tv/DsmccResponse.java
@@ -21,6 +21,9 @@
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
+import java.util.ArrayList;
+import java.util.List;
+
/** @hide */
public final class DsmccResponse extends BroadcastInfoResponse implements Parcelable {
public static final @TvInputManager.BroadcastInfoType int responseType =
@@ -41,20 +44,27 @@
};
private final ParcelFileDescriptor mFileDescriptor;
+ private final boolean mIsDirectory;
+ private final List<String> mChildren;
public static DsmccResponse createFromParcelBody(Parcel in) {
return new DsmccResponse(in);
}
public DsmccResponse(int requestId, int sequence, @ResponseResult int responseResult,
- ParcelFileDescriptor file) {
+ ParcelFileDescriptor file, boolean isDirectory, List<String> children) {
super(responseType, requestId, sequence, responseResult);
mFileDescriptor = file;
+ mIsDirectory = isDirectory;
+ mChildren = children;
}
protected DsmccResponse(Parcel source) {
super(responseType, source);
mFileDescriptor = source.readFileDescriptor();
+ mIsDirectory = (source.readInt() == 1);
+ mChildren = new ArrayList<>();
+ source.readStringList(mChildren);
}
public ParcelFileDescriptor getFile() {
@@ -65,5 +75,7 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
mFileDescriptor.writeToParcel(dest, flags);
+ dest.writeInt(mIsDirectory ? 1 : 0);
+ dest.writeStringList(mChildren);
}
}
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index f4f55e4..49148ce 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -47,6 +47,7 @@
void onTimeShiftStartPositionChanged(long timeMs, int seq);
void onTimeShiftCurrentPositionChanged(long timeMs, int seq);
void onAitInfoUpdated(in AitInfo aitInfo, int seq);
+ void onSignalStrength(int stength, int seq);
void onTuned(in Uri channelUri, int seq);
// For the recording session
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 770b8aa..2a33ee6 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -77,7 +77,7 @@
void setCaptionEnabled(in IBinder sessionToken, boolean enabled, int userId);
void selectTrack(in IBinder sessionToken, int type, in String trackId, int userId);
- void setIAppNotificationEnabled(in IBinder sessionToken, boolean enabled, int userId);
+ void setInteractiveAppNotificationEnabled(in IBinder sessionToken, boolean enabled, int userId);
void sendAppPrivateCommand(in IBinder sessionToken, in String action, in Bundle data,
int userId);
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index f427501..9820034 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -42,7 +42,7 @@
void setCaptionEnabled(boolean enabled);
void selectTrack(int type, in String trackId);
- void setIAppNotificationEnabled(boolean enable);
+ void setInteractiveAppNotificationEnabled(boolean enable);
void appPrivateCommand(in String action, in Bundle data);
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
index 9830e78..9dfdb78 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -44,6 +44,7 @@
void onTimeShiftStartPositionChanged(long timeMs);
void onTimeShiftCurrentPositionChanged(long timeMs);
void onAitInfoUpdated(in AitInfo aitInfo);
+ void onSignalStrength(int strength);
// For the recording session
void onTuned(in Uri channelUri);
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 418ab2c..8911f6c 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -247,7 +247,7 @@
break;
}
case DO_SET_IAPP_NOTIFICATION_ENABLED: {
- mTvInputSessionImpl.setIAppNotificationEnabled((Boolean) msg.obj);
+ mTvInputSessionImpl.setInteractiveAppNotificationEnabled((Boolean) msg.obj);
break;
}
case DO_REQUEST_AD: {
@@ -322,7 +322,7 @@
}
@Override
- public void setIAppNotificationEnabled(boolean enabled) {
+ public void setInteractiveAppNotificationEnabled(boolean enabled) {
mCaller.executeOrSendMessage(
mCaller.obtainMessageO(DO_SET_IAPP_NOTIFICATION_ENABLED, enabled));
}
diff --git a/media/java/android/media/tv/OWNERS b/media/java/android/media/tv/OWNERS
index 33acd0d..fa04293 100644
--- a/media/java/android/media/tv/OWNERS
+++ b/media/java/android/media/tv/OWNERS
@@ -1,6 +1,6 @@
-nchalko@google.com
quxiangfang@google.com
shubang@google.com
+hgchen@google.com
# For android remote service
per-file ITvRemoteServiceInput.aidl = file:/media/lib/tvremote/OWNERS
diff --git a/media/java/android/media/tv/TableResponse.java b/media/java/android/media/tv/TableResponse.java
index 912cbce..68d5f8a 100644
--- a/media/java/android/media/tv/TableResponse.java
+++ b/media/java/android/media/tv/TableResponse.java
@@ -17,9 +17,9 @@
package android.media.tv;
import android.annotation.NonNull;
+import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
-import android.net.Uri;
/** @hide */
public final class TableResponse extends BroadcastInfoResponse implements Parcelable {
diff --git a/media/java/android/media/tv/TimelineRequest.java b/media/java/android/media/tv/TimelineRequest.java
new file mode 100644
index 0000000..0714972
--- /dev/null
+++ b/media/java/android/media/tv/TimelineRequest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public final class TimelineRequest extends BroadcastInfoRequest implements Parcelable {
+ private static final @TvInputManager.BroadcastInfoType int REQUEST_TYPE =
+ TvInputManager.BROADCAST_INFO_TYPE_TIMELINE;
+
+ public static final @NonNull Parcelable.Creator<TimelineRequest> CREATOR =
+ new Parcelable.Creator<TimelineRequest>() {
+ @Override
+ public TimelineRequest createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public TimelineRequest[] newArray(int size) {
+ return new TimelineRequest[size];
+ }
+ };
+
+ private final int mIntervalMs;
+
+ static TimelineRequest createFromParcelBody(Parcel in) {
+ return new TimelineRequest(in);
+ }
+
+ public TimelineRequest(int requestId, @RequestOption int option, int intervalMs) {
+ super(REQUEST_TYPE, requestId, option);
+ mIntervalMs = intervalMs;
+ }
+
+ protected TimelineRequest(Parcel source) {
+ super(REQUEST_TYPE, source);
+ mIntervalMs = source.readInt();
+ }
+
+ public int getIntervalMs() {
+ return mIntervalMs;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mIntervalMs);
+ }
+}
diff --git a/media/java/android/media/tv/TimelineResponse.java b/media/java/android/media/tv/TimelineResponse.java
new file mode 100644
index 0000000..fee10b4
--- /dev/null
+++ b/media/java/android/media/tv/TimelineResponse.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public final class TimelineResponse extends BroadcastInfoResponse implements Parcelable {
+ private static final @TvInputManager.BroadcastInfoType int RESPONSE_TYPE =
+ TvInputManager.BROADCAST_INFO_TYPE_TIMELINE;
+
+ public static final @NonNull Parcelable.Creator<TimelineResponse> CREATOR =
+ new Parcelable.Creator<TimelineResponse>() {
+ @Override
+ public TimelineResponse createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public TimelineResponse[] newArray(int size) {
+ return new TimelineResponse[size];
+ }
+ };
+
+ private final String mSelector;
+ private final int mUnitsPerTick;
+ private final int mUnitsPerSecond;
+ private final long mWallClock;
+ private final long mTicks;
+
+ static TimelineResponse createFromParcelBody(Parcel in) {
+ return new TimelineResponse(in);
+ }
+
+ public TimelineResponse(int requestId, int sequence,
+ @ResponseResult int responseResult, String selector, int unitsPerTick,
+ int unitsPerSecond, long wallClock, long ticks) {
+ super(RESPONSE_TYPE, requestId, sequence, responseResult);
+ mSelector = selector;
+ mUnitsPerTick = unitsPerTick;
+ mUnitsPerSecond = unitsPerSecond;
+ mWallClock = wallClock;
+ mTicks = ticks;
+ }
+
+ protected TimelineResponse(Parcel source) {
+ super(RESPONSE_TYPE, source);
+ mSelector = source.readString();
+ mUnitsPerTick = source.readInt();
+ mUnitsPerSecond = source.readInt();
+ mWallClock = source.readLong();
+ mTicks = source.readLong();
+ }
+
+ public String getSelector() {
+ return mSelector;
+ }
+
+ public int getUnitsPerTick() {
+ return mUnitsPerTick;
+ }
+
+ public int getUnitsPerSecond() {
+ return mUnitsPerSecond;
+ }
+
+ public long getWallClock() {
+ return mWallClock;
+ }
+
+ public long getTicks() {
+ return mTicks;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mSelector);
+ dest.writeInt(mUnitsPerTick);
+ dest.writeInt(mUnitsPerSecond);
+ dest.writeLong(mWallClock);
+ dest.writeLong(mTicks);
+ }
+}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 52036b0..98d1599 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -18,6 +18,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -27,6 +28,8 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
+import android.media.AudioDeviceInfo;
+import android.media.AudioFormat.Encoding;
import android.media.PlaybackParams;
import android.media.tv.interactive.TvIAppManager;
import android.net.Uri;
@@ -60,6 +63,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -359,9 +363,10 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef({BROADCAST_INFO_TYPE_TS, BROADCAST_INFO_TYPE_TABLE, BROADCAST_INFO_TYPE_SECTION,
+ @IntDef(prefix = "BROADCAST_INFO_TYPE_", value =
+ {BROADCAST_INFO_TYPE_TS, BROADCAST_INFO_TYPE_TABLE, BROADCAST_INFO_TYPE_SECTION,
BROADCAST_INFO_TYPE_PES, BROADCAST_INFO_STREAM_EVENT, BROADCAST_INFO_TYPE_DSMCC,
- BROADCAST_INFO_TYPE_TV_PROPRIETARY_FUNCTION})
+ BROADCAST_INFO_TYPE_COMMAND, BROADCAST_INFO_TYPE_TIMELINE})
public @interface BroadcastInfoType {}
/** @hide */
@@ -377,7 +382,31 @@
/** @hide */
public static final int BROADCAST_INFO_TYPE_DSMCC = 6;
/** @hide */
- public static final int BROADCAST_INFO_TYPE_TV_PROPRIETARY_FUNCTION = 7;
+ public static final int BROADCAST_INFO_TYPE_COMMAND = 7;
+ /** @hide */
+ public static final int BROADCAST_INFO_TYPE_TIMELINE = 8;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "SIGNAL_STRENGTH_",
+ value = {SIGNAL_STRENGTH_LOST, SIGNAL_STRENGTH_WEAK, SIGNAL_STRENGTH_STRONG})
+ public @interface SignalStrength {}
+
+ /**
+ * Signal lost.
+ * @hide
+ */
+ public static final int SIGNAL_STRENGTH_LOST = 1;
+ /**
+ * Weak signal.
+ * @hide
+ */
+ public static final int SIGNAL_STRENGTH_WEAK = 2;
+ /**
+ * Strong signal.
+ * @hide
+ */
+ public static final int SIGNAL_STRENGTH_STRONG = 3;
/**
* An unknown state of the client pid gets from the TvInputManager. Client gets this value when
@@ -658,6 +687,14 @@
}
/**
+ * This is called when signal strength is updated.
+ * @param session A {@link TvInputManager.Session} associated with this callback.
+ * @param strength The current signal strength.
+ */
+ public void onSignalStrength(Session session, @SignalStrength int strength) {
+ }
+
+ /**
* This is called when the session has been tuned to the given channel.
*
* @param channelUri The URI of a channel.
@@ -731,8 +768,9 @@
@Override
public void run() {
mSessionCallback.onTracksChanged(mSession, tracks);
- if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
- mSession.getIAppSession().notifyTracksChanged(tracks);
+ if (mSession.mIAppNotificationEnabled
+ && mSession.getInteractiveAppSession() != null) {
+ mSession.getInteractiveAppSession().notifyTracksChanged(tracks);
}
}
});
@@ -743,8 +781,9 @@
@Override
public void run() {
mSessionCallback.onTrackSelected(mSession, type, trackId);
- if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
- mSession.getIAppSession().notifyTrackSelected(type, trackId);
+ if (mSession.mIAppNotificationEnabled
+ && mSession.getInteractiveAppSession() != null) {
+ mSession.getInteractiveAppSession().notifyTrackSelected(type, trackId);
}
}
});
@@ -764,8 +803,9 @@
@Override
public void run() {
mSessionCallback.onVideoAvailable(mSession);
- if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
- mSession.getIAppSession().notifyVideoAvailable();
+ if (mSession.mIAppNotificationEnabled
+ && mSession.getInteractiveAppSession() != null) {
+ mSession.getInteractiveAppSession().notifyVideoAvailable();
}
}
});
@@ -776,8 +816,9 @@
@Override
public void run() {
mSessionCallback.onVideoUnavailable(mSession, reason);
- if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
- mSession.getIAppSession().notifyVideoUnavailable(reason);
+ if (mSession.mIAppNotificationEnabled
+ && mSession.getInteractiveAppSession() != null) {
+ mSession.getInteractiveAppSession().notifyVideoUnavailable(reason);
}
}
});
@@ -788,8 +829,9 @@
@Override
public void run() {
mSessionCallback.onContentAllowed(mSession);
- if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
- mSession.getIAppSession().notifyContentAllowed();
+ if (mSession.mIAppNotificationEnabled
+ && mSession.getInteractiveAppSession() != null) {
+ mSession.getInteractiveAppSession().notifyContentAllowed();
}
}
});
@@ -800,8 +842,9 @@
@Override
public void run() {
mSessionCallback.onContentBlocked(mSession, rating);
- if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
- mSession.getIAppSession().notifyContentBlocked(rating);
+ if (mSession.mIAppNotificationEnabled
+ && mSession.getInteractiveAppSession() != null) {
+ mSession.getInteractiveAppSession().notifyContentBlocked(rating);
}
}
});
@@ -862,13 +905,27 @@
});
}
+ void postSignalStrength(final int strength) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onSignalStrength(mSession, strength);
+ if (mSession.mIAppNotificationEnabled
+ && mSession.getInteractiveAppSession() != null) {
+ mSession.getInteractiveAppSession().notifySignalStrength(strength);
+ }
+ }
+ });
+ }
+
void postTuned(final Uri channelUri) {
mHandler.post(new Runnable() {
@Override
public void run() {
mSessionCallback.onTuned(mSession, channelUri);
- if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
- mSession.getIAppSession().notifyTuned(channelUri);
+ if (mSession.mIAppNotificationEnabled
+ && mSession.getInteractiveAppSession() != null) {
+ mSession.getInteractiveAppSession().notifyTuned(channelUri);
}
}
});
@@ -899,8 +956,9 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- if (mSession.getIAppSession() != null) {
- mSession.getIAppSession().notifyBroadcastInfoResponse(response);
+ if (mSession.getInteractiveAppSession() != null) {
+ mSession.getInteractiveAppSession()
+ .notifyBroadcastInfoResponse(response);
}
}
});
@@ -912,8 +970,8 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- if (mSession.getIAppSession() != null) {
- mSession.getIAppSession().notifyAdResponse(response);
+ if (mSession.getInteractiveAppSession() != null) {
+ mSession.getInteractiveAppSession().notifyAdResponse(response);
}
}
});
@@ -1295,6 +1353,18 @@
}
@Override
+ public void onSignalStrength(int strength, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postSignalStrength(strength);
+ }
+ }
+
+ @Override
public void onTuned(Uri channelUri, int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
@@ -2261,12 +2331,12 @@
mSessionCallbackRecordMap = sessionCallbackRecordMap;
}
- public TvIAppManager.Session getIAppSession() {
+ public TvIAppManager.Session getInteractiveAppSession() {
return mIAppSession;
}
- public void setIAppSession(TvIAppManager.Session IAppSession) {
- this.mIAppSession = IAppSession;
+ public void setInteractiveAppSession(TvIAppManager.Session iAppSession) {
+ this.mIAppSession = iAppSession;
}
/**
@@ -2527,13 +2597,13 @@
* {@code false} otherwise.
* @hide
*/
- public void setIAppNotificationEnabled(boolean enabled) {
+ public void setInteractiveAppNotificationEnabled(boolean enabled) {
if (mToken == null) {
Log.w(TAG, "The session has been already released");
return;
}
try {
- mService.setIAppNotificationEnabled(mToken, enabled, mUserId);
+ mService.setInteractiveAppNotificationEnabled(mToken, enabled, mUserId);
mIAppNotificationEnabled = enabled;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -3221,6 +3291,16 @@
return false;
}
+ /**
+ * Override default audio sink from audio policy.
+ *
+ * @param audioType device type of the audio sink to override with.
+ * @param audioAddress device address of the audio sink to override with.
+ * @param samplingRate desired sampling rate. Use default when it's 0.
+ * @param channelMask desired channel mask. Use default when it's
+ * AudioFormat.CHANNEL_OUT_DEFAULT.
+ * @param format desired format. Use default when it's AudioFormat.ENCODING_DEFAULT.
+ */
public void overrideAudioSink(int audioType, String audioAddress, int samplingRate,
int channelMask, int format) {
try {
@@ -3230,5 +3310,27 @@
throw new RuntimeException(e);
}
}
+
+ /**
+ * Override default audio sink from audio policy.
+ *
+ * @param device {@link android.media.AudioDeviceInfo} to use.
+ * @param samplingRate desired sampling rate. Use default when it's 0.
+ * @param channelMask desired channel mask. Use default when it's
+ * AudioFormat.CHANNEL_OUT_DEFAULT.
+ * @param format desired format. Use default when it's AudioFormat.ENCODING_DEFAULT.
+ */
+ public void overrideAudioSink(@NonNull AudioDeviceInfo device,
+ @IntRange(from = 0) int samplingRate,
+ int channelMask, @Encoding int format) {
+ Objects.requireNonNull(device);
+ try {
+ mInterface.overrideAudioSink(
+ AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType()),
+ device.getAddress(), samplingRate, channelMask, format);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
}
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 3a40d6f..524ba34 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -966,6 +966,27 @@
}
/**
+ * Notifies signal strength.
+ * @hide
+ */
+ public void notifySignalStrength(@TvInputManager.SignalStrength final int strength) {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) Log.d(TAG, "notifySignalStrength");
+ if (mSessionCallback != null) {
+ mSessionCallback.onSignalStrength(strength);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in notifySignalStrength", e);
+ }
+ }
+ });
+ }
+
+ /**
* Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
* is relative to the overlay view that sits on top of this surface.
*
@@ -1180,7 +1201,7 @@
* @param enabled {@code true} to enable, {@code false} to disable.
* @hide
*/
- public void onSetIAppNotificationEnabled(boolean enabled) {
+ public void onSetInteractiveAppNotificationEnabled(boolean enabled) {
}
/**
@@ -1532,10 +1553,10 @@
}
/**
- * Calls {@link #onSetIAppNotificationEnabled}.
+ * Calls {@link #onSetInteractiveAppNotificationEnabled}.
*/
- void setIAppNotificationEnabled(boolean enabled) {
- onSetIAppNotificationEnabled(enabled);
+ void setInteractiveAppNotificationEnabled(boolean enabled) {
+ onSetInteractiveAppNotificationEnabled(enabled);
}
/**
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 4a12cd7..71f6ad6 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -486,9 +486,9 @@
* {@code false} otherwise.
* @hide
*/
- public void setIAppNotificationEnabled(boolean enabled) {
+ public void setInteractiveAppNotificationEnabled(boolean enabled) {
if (mSession != null) {
- mSession.setIAppNotificationEnabled(enabled);
+ mSession.setInteractiveAppNotificationEnabled(enabled);
}
}
@@ -1071,6 +1071,16 @@
}
/**
+ * This is called when signal strength is updated.
+ * @param inputId The ID of the TV input bound to this view.
+ * @param strength The current signal strength.
+ *
+ * @hide
+ */
+ public void onSignalStrength(String inputId, @TvInputManager.SignalStrength int strength) {
+ }
+
+ /**
* This is called when the session has been tuned to the given channel.
*
* @param channelUri The URI of a channel.
@@ -1390,6 +1400,20 @@
}
@Override
+ public void onSignalStrength(Session session, int strength) {
+ if (DEBUG) {
+ Log.d(TAG, "onSignalStrength(strength=" + strength + ")");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onSignalStrength - session not created");
+ return;
+ }
+ if (mCallback != null) {
+ mCallback.onSignalStrength(mInputId, strength);
+ }
+ }
+
+ @Override
public void onTuned(Session session, Uri channelUri) {
if (DEBUG) {
Log.d(TAG, "onTuned(channelUri=" + channelUri + ")");
diff --git a/media/java/android/media/tv/interactive/ITvIAppManager.aidl b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
index 2e04359..a19a2d2 100644
--- a/media/java/android/media/tv/interactive/ITvIAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
@@ -20,9 +20,9 @@
import android.media.tv.AdResponse;
import android.media.tv.BroadcastInfoResponse;
import android.media.tv.TvTrackInfo;
-import android.media.tv.interactive.ITvIAppClient;
-import android.media.tv.interactive.ITvIAppManagerCallback;
-import android.media.tv.interactive.TvIAppInfo;
+import android.media.tv.interactive.ITvInteractiveAppClient;
+import android.media.tv.interactive.ITvInteractiveAppManagerCallback;
+import android.media.tv.interactive.TvInteractiveAppInfo;
import android.net.Uri;
import android.os.Bundle;
import android.view.Surface;
@@ -32,21 +32,25 @@
* @hide
*/
interface ITvIAppManager {
- List<TvIAppInfo> getTvIAppServiceList(int userId);
+ List<TvInteractiveAppInfo> getTvInteractiveAppServiceList(int userId);
void prepare(String tiasId, int type, int userId);
- void notifyAppLinkInfo(String tiasId, in Bundle info, int userId);
+ void registerAppLinkInfo(String tiasId, in Bundle info, int userId);
+ void unregisterAppLinkInfo(String tiasId, in Bundle info, int userId);
void sendAppLinkCommand(String tiasId, in Bundle command, int userId);
- void startIApp(in IBinder sessionToken, int userId);
- void stopIApp(in IBinder sessionToken, int userId);
+ void startInteractiveApp(in IBinder sessionToken, int userId);
+ void stopInteractiveApp(in IBinder sessionToken, int userId);
+ void resetInteractiveApp(in IBinder sessionToken, int userId);
void createBiInteractiveApp(
in IBinder sessionToken, in Uri biIAppUri, in Bundle params, int userId);
void destroyBiInteractiveApp(in IBinder sessionToken, in String biIAppId, int userId);
+ void setTeletextAppEnabled(in IBinder sessionToken, boolean enable, int userId);
void sendCurrentChannelUri(in IBinder sessionToken, in Uri channelUri, int userId);
void sendCurrentChannelLcn(in IBinder sessionToken, int lcn, int userId);
void sendStreamVolume(in IBinder sessionToken, float volume, int userId);
void sendTrackInfoList(in IBinder sessionToken, in List<TvTrackInfo> tracks, int userId);
- void createSession(
- in ITvIAppClient client, in String iAppServiceId, int type, int seq, int userId);
+ void sendCurrentTvInputId(in IBinder sessionToken, in String inputId, int userId);
+ void createSession(in ITvInteractiveAppClient client, in String iAppServiceId, int type,
+ int seq, int userId);
void releaseSession(in IBinder sessionToken, int userId);
void notifyTuned(in IBinder sessionToken, in Uri channelUri, int userId);
void notifyTrackSelected(in IBinder sessionToken, int type, in String trackId, int userId);
@@ -55,6 +59,7 @@
void notifyVideoUnavailable(in IBinder sessionToken, int reason, int userId);
void notifyContentAllowed(in IBinder sessionToken, int userId);
void notifyContentBlocked(in IBinder sessionToken, in String rating, int userId);
+ void notifySignalStrength(in IBinder sessionToken, int stength, int userId);
void setSurface(in IBinder sessionToken, in Surface surface, int userId);
void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
int userId);
@@ -67,6 +72,6 @@
void relayoutMediaView(in IBinder sessionToken, in Rect frame, int userId);
void removeMediaView(in IBinder sessionToken, int userId);
- void registerCallback(in ITvIAppManagerCallback callback, int userId);
- void unregisterCallback(in ITvIAppManagerCallback callback, int userId);
+ void registerCallback(in ITvInteractiveAppManagerCallback callback, int userId);
+ void unregisterCallback(in ITvInteractiveAppManagerCallback callback, int userId);
}
diff --git a/media/java/android/media/tv/interactive/ITvIAppService.aidl b/media/java/android/media/tv/interactive/ITvIAppService.aidl
deleted file mode 100644
index 8acb75f..0000000
--- a/media/java/android/media/tv/interactive/ITvIAppService.aidl
+++ /dev/null
@@ -1,37 +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 android.media.tv.interactive;
-
-import android.media.tv.interactive.ITvIAppServiceCallback;
-import android.media.tv.interactive.ITvIAppSessionCallback;
-import android.os.Bundle;
-import android.view.InputChannel;
-
-/**
- * Top-level interface to a TV IApp component (implemented in a Service). It's used for
- * TvIAppManagerService to communicate with TvIAppService.
- * @hide
- */
-oneway interface ITvIAppService {
- void registerCallback(in ITvIAppServiceCallback callback);
- void unregisterCallback(in ITvIAppServiceCallback callback);
- void createSession(in InputChannel channel, in ITvIAppSessionCallback callback,
- in String iAppServiceId, int type);
- void prepare(int type);
- void notifyAppLinkInfo(in Bundle info);
- void sendAppLinkCommand(in Bundle command);
-}
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
similarity index 83%
rename from media/java/android/media/tv/interactive/ITvIAppClient.aidl
rename to media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index 892a800..1a8fc46 100644
--- a/media/java/android/media/tv/interactive/ITvIAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -24,11 +24,11 @@
import android.view.InputChannel;
/**
- * Interface a client of the ITvIAppManager implements, to identify itself and receive information
- * about changes to the state of each TV interactive application service.
+ * Interface a client of the ITvInteractiveAppManager implements, to identify itself and receive
+ * information about changes to the state of each TV interactive application service.
* @hide
*/
-oneway interface ITvIAppClient {
+oneway interface ITvInteractiveAppClient {
void onSessionCreated(in String iAppServiceId, IBinder token, in InputChannel channel, int seq);
void onSessionReleased(int seq);
void onLayoutSurface(int left, int top, int right, int bottom, int seq);
@@ -36,11 +36,13 @@
void onRemoveBroadcastInfo(int id, int seq);
void onSessionStateChanged(int state, int seq);
void onBiInteractiveAppCreated(in Uri biIAppUri, in String biIAppId, int seq);
+ void onTeletextAppStateChanged(int state, int seq);
void onCommandRequest(in String cmdType, in Bundle parameters, int seq);
void onSetVideoBounds(in Rect rect, int seq);
void onRequestCurrentChannelUri(int seq);
void onRequestCurrentChannelLcn(int seq);
void onRequestStreamVolume(int seq);
void onRequestTrackInfoList(int seq);
+ void onRequestCurrentTvInputId(int seq);
void onAdRequest(in AdRequest request, int Seq);
}
diff --git a/media/java/android/media/tv/interactive/ITvIAppManagerCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManagerCallback.aidl
similarity index 62%
rename from media/java/android/media/tv/interactive/ITvIAppManagerCallback.aidl
rename to media/java/android/media/tv/interactive/ITvInteractiveAppManagerCallback.aidl
index d5e0c63..f4510f6 100644
--- a/media/java/android/media/tv/interactive/ITvIAppManagerCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManagerCallback.aidl
@@ -16,16 +16,16 @@
package android.media.tv.interactive;
-import android.media.tv.interactive.TvIAppInfo;
+import android.media.tv.interactive.TvInteractiveAppInfo;
/**
- * Interface to receive callbacks from ITvIAppManager regardless of sessions.
+ * Interface to receive callbacks from ITvInteractiveAppManager regardless of sessions.
* @hide
*/
-interface ITvIAppManagerCallback {
- void onIAppServiceAdded(in String iAppServiceId);
- void onIAppServiceRemoved(in String iAppServiceId);
- void onIAppServiceUpdated(in String iAppServiceId);
- void onTvIAppInfoUpdated(in TvIAppInfo tvIAppInfo);
+interface ITvInteractiveAppManagerCallback {
+ void onInteractiveAppServiceAdded(in String iAppServiceId);
+ void onInteractiveAppServiceRemoved(in String iAppServiceId);
+ void onInteractiveAppServiceUpdated(in String iAppServiceId);
+ void onTvInteractiveAppInfoUpdated(in TvInteractiveAppInfo tvIAppInfo);
void onStateChanged(in String iAppServiceId, int type, int state);
}
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl
new file mode 100644
index 0000000..c1e6622
--- /dev/null
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.interactive;
+
+import android.media.tv.interactive.ITvInteractiveAppServiceCallback;
+import android.media.tv.interactive.ITvInteractiveAppSessionCallback;
+import android.os.Bundle;
+import android.view.InputChannel;
+
+/**
+ * Top-level interface to a TV Interactive App component (implemented in a Service). It's used for
+ * TvIAppManagerService to communicate with TvIAppService.
+ * @hide
+ */
+oneway interface ITvInteractiveAppService {
+ void registerCallback(in ITvInteractiveAppServiceCallback callback);
+ void unregisterCallback(in ITvInteractiveAppServiceCallback callback);
+ void createSession(in InputChannel channel, in ITvInteractiveAppSessionCallback callback,
+ in String iAppServiceId, int type);
+ void prepare(int type);
+ void registerAppLinkInfo(in Bundle info);
+ void unregisterAppLinkInfo(in Bundle info);
+ void sendAppLinkCommand(in Bundle command);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppServiceCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppServiceCallback.aidl
similarity index 84%
rename from media/java/android/media/tv/interactive/ITvIAppServiceCallback.aidl
rename to media/java/android/media/tv/interactive/ITvInteractiveAppServiceCallback.aidl
index fec7d78..f56d3bd 100644
--- a/media/java/android/media/tv/interactive/ITvIAppServiceCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppServiceCallback.aidl
@@ -17,10 +17,10 @@
package android.media.tv.interactive;
/**
- * Helper interface for ITvIAppService to allow the TvIAppService to notify the
+ * Helper interface for ITvInteractiveAppService to allow the TvIAppService to notify the
* TvIAppManagerService.
* @hide
*/
-oneway interface ITvIAppServiceCallback {
+oneway interface ITvInteractiveAppServiceCallback {
void onStateChanged(int type, int state);
}
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
similarity index 83%
rename from media/java/android/media/tv/interactive/ITvIAppSession.aidl
rename to media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 2788ff6..c449d2475 100644
--- a/media/java/android/media/tv/interactive/ITvIAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -26,18 +26,22 @@
import android.view.Surface;
/**
- * Sub-interface of ITvIAppService.aidl which is created per session and has its own context.
+ * Sub-interface of ITvInteractiveAppService.aidl which is created per session and has its own
+ * context.
* @hide
*/
-oneway interface ITvIAppSession {
- void startIApp();
- void stopIApp();
+oneway interface ITvInteractiveAppSession {
+ void startInteractiveApp();
+ void stopInteractiveApp();
+ void resetInteractiveApp();
void createBiInteractiveApp(in Uri biIAppUri, in Bundle params);
void destroyBiInteractiveApp(in String biIAppId);
+ void setTeletextAppEnabled(boolean enable);
void sendCurrentChannelUri(in Uri channelUri);
void sendCurrentChannelLcn(int lcn);
void sendStreamVolume(float volume);
void sendTrackInfoList(in List<TvTrackInfo> tracks);
+ void sendCurrentTvInputId(in String inputId);
void release();
void notifyTuned(in Uri channelUri);
void notifyTrackSelected(int type, in String trackId);
@@ -46,6 +50,7 @@
void notifyVideoUnavailable(int reason);
void notifyContentAllowed();
void notifyContentBlocked(in String rating);
+ void notifySignalStrength(int strength);
void setSurface(in Surface surface);
void dispatchSurfaceChanged(int format, int width, int height);
void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
diff --git a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
similarity index 78%
rename from media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
rename to media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index 9b9e6af..c270424 100644
--- a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -19,27 +19,29 @@
import android.graphics.Rect;
import android.media.tv.AdRequest;
import android.media.tv.BroadcastInfoRequest;
-import android.media.tv.interactive.ITvIAppSession;
+import android.media.tv.interactive.ITvInteractiveAppSession;
import android.net.Uri;
import android.os.Bundle;
/**
- * Helper interface for ITvIAppSession to allow TvIAppService to notify the system service when
- * there is a related event.
+ * Helper interface for ITvInteractiveAppSession to allow TvIAppService to notify the
+ * system service when there is a related event.
* @hide
*/
-oneway interface ITvIAppSessionCallback {
- void onSessionCreated(in ITvIAppSession session);
+oneway interface ITvInteractiveAppSessionCallback {
+ void onSessionCreated(in ITvInteractiveAppSession session);
void onLayoutSurface(int left, int top, int right, int bottom);
void onBroadcastInfoRequest(in BroadcastInfoRequest request);
void onRemoveBroadcastInfo(int id);
void onSessionStateChanged(int state);
void onBiInteractiveAppCreated(in Uri biIAppUri, in String biIAppId);
+ void onTeletextAppStateChanged(int state);
void onCommandRequest(in String cmdType, in Bundle parameters);
void onSetVideoBounds(in Rect rect);
void onRequestCurrentChannelUri();
void onRequestCurrentChannelLcn();
void onRequestStreamVolume();
void onRequestTrackInfoList();
+ void onRequestCurrentTvInputId();
void onAdRequest(in AdRequest request);
}
diff --git a/media/java/android/media/tv/interactive/TvIAppInfo.java b/media/java/android/media/tv/interactive/TvIAppInfo.java
deleted file mode 100644
index b5245fc..0000000
--- a/media/java/android/media/tv/interactive/TvIAppInfo.java
+++ /dev/null
@@ -1,227 +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 android.media.tv.interactive;
-
-import android.annotation.NonNull;
-import android.annotation.StringDef;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.util.Xml;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * This class is used to specify meta information of a TV interactive app.
- * @hide
- */
-public final class TvIAppInfo implements Parcelable {
- private static final boolean DEBUG = false;
- private static final String TAG = "TvIAppInfo";
-
- @Retention(RetentionPolicy.SOURCE)
- @StringDef(prefix = { "INTERACTIVE_APP_TYPE_" }, value = {
- INTERACTIVE_APP_TYPE_HBBTV,
- INTERACTIVE_APP_TYPE_ATSC,
- INTERACTIVE_APP_TYPE_GINGA,
- })
- @interface InteractiveAppType {}
-
- /** HbbTV interactive app type */
- public static final String INTERACTIVE_APP_TYPE_HBBTV = "hbbtv";
- /** ATSC interactive app type */
- public static final String INTERACTIVE_APP_TYPE_ATSC = "atsc";
- /** Ginga interactive app type */
- public static final String INTERACTIVE_APP_TYPE_GINGA = "ginga";
-
- private final ResolveInfo mService;
- private final String mId;
- private List<String> mTypes = new ArrayList<>();
-
- private TvIAppInfo(ResolveInfo service, String id, List<String> types) {
- mService = service;
- mId = id;
- mTypes = types;
- }
-
- private TvIAppInfo(@NonNull Parcel in) {
- mService = ResolveInfo.CREATOR.createFromParcel(in);
- mId = in.readString();
- in.readStringList(mTypes);
- }
-
- public static final @NonNull Creator<TvIAppInfo> CREATOR = new Creator<TvIAppInfo>() {
- @Override
- public TvIAppInfo createFromParcel(Parcel in) {
- return new TvIAppInfo(in);
- }
-
- @Override
- public TvIAppInfo[] newArray(int size) {
- return new TvIAppInfo[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- mService.writeToParcel(dest, flags);
- dest.writeString(mId);
- dest.writeStringList(mTypes);
- }
-
- @NonNull
- public String getId() {
- return mId;
- }
-
- /**
- * Returns the component of the TV IApp service.
- * @hide
- */
- public ComponentName getComponent() {
- return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name);
- }
-
- /**
- * Returns the information of the service that implements this TV IApp service.
- */
- public ServiceInfo getServiceInfo() {
- return mService.serviceInfo;
- }
-
- /**
- * Gets supported interactive app types
- */
- @NonNull
- public List<String> getSupportedTypes() {
- return new ArrayList<>(mTypes);
- }
-
- /**
- * A convenience builder for creating {@link TvIAppInfo} objects.
- */
- public static final class Builder {
- private static final String XML_START_TAG_NAME = "tv-iapp";
- private final Context mContext;
- private final ResolveInfo mResolveInfo;
- private final List<String> mTypes = new ArrayList<>();
-
- /**
- * Constructs a new builder for {@link TvIAppInfo}.
- *
- * @param context A Context of the application package implementing this class.
- * @param component The name of the application component to be used for the
- * {@link TvIAppService}.
- */
- public Builder(@NonNull Context context, @NonNull ComponentName component) {
- if (context == null) {
- throw new IllegalArgumentException("context cannot be null.");
- }
- Intent intent = new Intent(TvIAppService.SERVICE_INTERFACE).setComponent(component);
- mResolveInfo = context.getPackageManager().resolveService(intent,
- PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
- if (mResolveInfo == null) {
- throw new IllegalArgumentException("Invalid component. Can't find the service.");
- }
- mContext = context;
- }
-
- /**
- * Creates a {@link TvIAppInfo} instance with the specified fields. Most of the information
- * is obtained by parsing the AndroidManifest and {@link TvIAppService#SERVICE_META_DATA}
- * for the {@link TvIAppService} this TV IApp implements.
- *
- * @return TvIAppInfo containing information about this TV IApp service.
- */
- @NonNull
- public TvIAppInfo build() {
- ComponentName componentName = new ComponentName(mResolveInfo.serviceInfo.packageName,
- mResolveInfo.serviceInfo.name);
- String id;
- id = generateIAppServiceId(componentName);
- parseServiceMetadata();
- return new TvIAppInfo(mResolveInfo, id, mTypes);
- }
-
- private static String generateIAppServiceId(ComponentName name) {
- return name.flattenToShortString();
- }
-
- private void parseServiceMetadata() {
- ServiceInfo si = mResolveInfo.serviceInfo;
- PackageManager pm = mContext.getPackageManager();
- try (XmlResourceParser parser =
- si.loadXmlMetaData(pm, TvIAppService.SERVICE_META_DATA)) {
- if (parser == null) {
- throw new IllegalStateException("No " + TvIAppService.SERVICE_META_DATA
- + " meta-data found for " + si.name);
- }
-
- Resources res = pm.getResourcesForApplication(si.applicationInfo);
- AttributeSet attrs = Xml.asAttributeSet(parser);
-
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && type != XmlPullParser.START_TAG) {
- // move to the START_TAG
- }
-
- String nodeName = parser.getName();
- if (!XML_START_TAG_NAME.equals(nodeName)) {
- throw new IllegalStateException("Meta-data does not start with "
- + XML_START_TAG_NAME + " tag for " + si.name);
- }
-
- TypedArray sa = res.obtainAttributes(attrs,
- com.android.internal.R.styleable.TvIAppService);
- CharSequence[] types = sa.getTextArray(
- com.android.internal.R.styleable.TvIAppService_supportedTypes);
- for (CharSequence cs : types) {
- mTypes.add(cs.toString().toLowerCase());
- }
-
- sa.recycle();
- } catch (IOException | XmlPullParserException e) {
- throw new IllegalStateException(
- "Failed reading meta-data for " + si.packageName, e);
- } catch (PackageManager.NameNotFoundException e) {
- throw new IllegalStateException("No resources found for " + si.packageName, e);
- }
- }
- }
-}
diff --git a/media/java/android/media/tv/interactive/TvIAppManager.java b/media/java/android/media/tv/interactive/TvIAppManager.java
old mode 100644
new mode 100755
index 9685e3a..f819438
--- a/media/java/android/media/tv/interactive/TvIAppManager.java
+++ b/media/java/android/media/tv/interactive/TvIAppManager.java
@@ -64,39 +64,63 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = false, prefix = "TV_IAPP_RTE_STATE_", value = {
- TV_IAPP_RTE_STATE_UNREALIZED,
- TV_IAPP_RTE_STATE_PREPARING,
- TV_IAPP_RTE_STATE_READY,
- TV_IAPP_RTE_STATE_ERROR})
- public @interface TvIAppRteState {}
+ @IntDef(flag = false, prefix = "TV_INTERACTIVE_APP_RTE_STATE_", value = {
+ TV_INTERACTIVE_APP_RTE_STATE_UNREALIZED,
+ TV_INTERACTIVE_APP_RTE_STATE_PREPARING,
+ TV_INTERACTIVE_APP_RTE_STATE_READY,
+ TV_INTERACTIVE_APP_RTE_STATE_ERROR})
+ public @interface TvInteractiveAppRteState {}
/**
* Unrealized state of interactive app RTE.
* @hide
*/
- public static final int TV_IAPP_RTE_STATE_UNREALIZED = 1;
+ public static final int TV_INTERACTIVE_APP_RTE_STATE_UNREALIZED = 1;
/**
* Preparing state of interactive app RTE.
* @hide
*/
- public static final int TV_IAPP_RTE_STATE_PREPARING = 2;
+ public static final int TV_INTERACTIVE_APP_RTE_STATE_PREPARING = 2;
/**
* Ready state of interactive app RTE.
* @hide
*/
- public static final int TV_IAPP_RTE_STATE_READY = 3;
+ public static final int TV_INTERACTIVE_APP_RTE_STATE_READY = 3;
/**
* Error state of interactive app RTE.
* @hide
*/
- public static final int TV_IAPP_RTE_STATE_ERROR = 4;
+ public static final int TV_INTERACTIVE_APP_RTE_STATE_ERROR = 4;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = false, prefix = "TELETEXT_APP_STATE_", value = {
+ TELETEXT_APP_STATE_SHOW,
+ TELETEXT_APP_STATE_HIDE,
+ TELETEXT_APP_STATE_ERROR})
+ public @interface TeletextAppState {}
+
+ /**
+ * Show state of Teletext app.
+ * @hide
+ */
+ public static final int TELETEXT_APP_STATE_SHOW = 1;
+ /**
+ * Hide state of Teletext app.
+ * @hide
+ */
+ public static final int TELETEXT_APP_STATE_HIDE = 2;
+ /**
+ * Error state of Teletext app.
+ * @hide
+ */
+ public static final int TELETEXT_APP_STATE_ERROR = 3;
/**
* Key for package name in app link.
* <p>Type: String
*
- * @see #notifyAppLinkInfo(String, Bundle)
+ * @see #registerAppLinkInfo(String, Bundle)
* @see #sendAppLinkCommand(String, Bundle)
* @hide
*/
@@ -106,7 +130,7 @@
* Key for class name in app link.
* <p>Type: String
*
- * @see #notifyAppLinkInfo(String, Bundle)
+ * @see #registerAppLinkInfo(String, Bundle)
* @see #sendAppLinkCommand(String, Bundle)
* @hide
*/
@@ -116,7 +140,7 @@
* Key for URI scheme in app link.
* <p>Type: String
*
- * @see #notifyAppLinkInfo(String, Bundle)
+ * @see #registerAppLinkInfo(String, Bundle)
* @hide
*/
public static final String KEY_URI_SCHEME = "uri_scheme";
@@ -125,7 +149,7 @@
* Key for URI host in app link.
* <p>Type: String
*
- * @see #notifyAppLinkInfo(String, Bundle)
+ * @see #registerAppLinkInfo(String, Bundle)
* @hide
*/
public static final String KEY_URI_HOST = "uri_host";
@@ -134,7 +158,7 @@
* Key for URI prefix in app link.
* <p>Type: String
*
- * @see #notifyAppLinkInfo(String, Bundle)
+ * @see #registerAppLinkInfo(String, Bundle)
* @hide
*/
public static final String KEY_URI_PREFIX = "uri_prefix";
@@ -174,7 +198,7 @@
new SparseArray<>();
// @GuardedBy("mLock")
- private final List<TvIAppCallbackRecord> mCallbackRecords = new LinkedList<>();
+ private final List<TvInteractiveAppCallbackRecord> mCallbackRecords = new LinkedList<>();
// A sequence number for the next session to be created. Should be protected by a lock
// {@code mSessionCallbackRecordMap}.
@@ -182,13 +206,13 @@
private final Object mLock = new Object();
- private final ITvIAppClient mClient;
+ private final ITvInteractiveAppClient mClient;
/** @hide */
public TvIAppManager(ITvIAppManager service, int userId) {
mService = service;
mUserId = userId;
- mClient = new ITvIAppClient.Stub() {
+ mClient = new ITvInteractiveAppClient.Stub() {
@Override
public void onSessionCreated(String iAppServiceId, IBinder token, InputChannel channel,
int seq) {
@@ -260,8 +284,10 @@
}
@Override
- public void onCommandRequest(@TvIAppService.IAppServiceCommandType String cmdType,
- Bundle parameters, int seq) {
+ public void onCommandRequest(
+ @TvIAppService.InteractiveAppServiceCommandType String cmdType,
+ Bundle parameters,
+ int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
if (record == null) {
@@ -345,6 +371,18 @@
}
@Override
+ public void onRequestCurrentTvInputId(int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestCurrentTvInputId();
+ }
+ }
+
+ @Override
public void onSessionStateChanged(int state, int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
@@ -367,41 +405,54 @@
record.postBiInteractiveAppCreated(biIAppUri, biIAppId);
}
}
+
+ @Override
+ public void onTeletextAppStateChanged(int state, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postTeletextAppStateChanged(state);
+ }
+ }
};
- ITvIAppManagerCallback managerCallback = new ITvIAppManagerCallback.Stub() {
+ ITvInteractiveAppManagerCallback managerCallback =
+ new ITvInteractiveAppManagerCallback.Stub() {
@Override
- public void onIAppServiceAdded(String iAppServiceId) {
+ public void onInteractiveAppServiceAdded(String iAppServiceId) {
synchronized (mLock) {
- for (TvIAppCallbackRecord record : mCallbackRecords) {
- record.postIAppServiceAdded(iAppServiceId);
+ for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
+ record.postInteractiveAppServiceAdded(iAppServiceId);
}
}
}
@Override
- public void onIAppServiceRemoved(String iAppServiceId) {
+ public void onInteractiveAppServiceRemoved(String iAppServiceId) {
synchronized (mLock) {
- for (TvIAppCallbackRecord record : mCallbackRecords) {
- record.postIAppServiceRemoved(iAppServiceId);
+ for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
+ record.postInteractiveAppServiceRemoved(iAppServiceId);
}
}
}
@Override
- public void onIAppServiceUpdated(String iAppServiceId) {
+ public void onInteractiveAppServiceUpdated(String iAppServiceId) {
synchronized (mLock) {
- for (TvIAppCallbackRecord record : mCallbackRecords) {
- record.postIAppServiceUpdated(iAppServiceId);
+ for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
+ record.postInteractiveAppServiceUpdated(iAppServiceId);
}
}
}
@Override
- public void onTvIAppInfoUpdated(TvIAppInfo iAppInfo) {
- // TODO: add public API updateIAppInfo()
+ public void onTvInteractiveAppInfoUpdated(TvInteractiveAppInfo iAppInfo) {
+ // TODO: add public API updateInteractiveAppInfo()
synchronized (mLock) {
- for (TvIAppCallbackRecord record : mCallbackRecords) {
- record.postTvIAppInfoUpdated(iAppInfo);
+ for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
+ record.postTvInteractiveAppInfoUpdated(iAppInfo);
}
}
}
@@ -409,7 +460,7 @@
@Override
public void onStateChanged(String iAppServiceId, int type, int state) {
synchronized (mLock) {
- for (TvIAppCallbackRecord record : mCallbackRecords) {
+ for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
record.postStateChanged(iAppServiceId, type, state);
}
}
@@ -425,110 +476,112 @@
}
/**
- * Callback used to monitor status of the TV IApp.
+ * Callback used to monitor status of the TV Interactive App.
* @hide
*/
- public abstract static class TvIAppCallback {
+ public abstract static class TvInteractiveAppCallback {
/**
- * This is called when a TV IApp service is added to the system.
+ * This is called when a TV Interactive App service is added to the system.
*
- * <p>Normally it happens when the user installs a new TV IApp service package that
- * implements {@link TvIAppService} interface.
+ * <p>Normally it happens when the user installs a new TV Interactive App service package
+ * that implements {@link TvIAppService} interface.
*
- * @param iAppServiceId The ID of the TV IApp service.
+ * @param iAppServiceId The ID of the TV Interactive App service.
*/
- public void onIAppServiceAdded(@NonNull String iAppServiceId) {
+ public void onInteractiveAppServiceAdded(@NonNull String iAppServiceId) {
}
/**
- * This is called when a TV IApp service is removed from the system.
+ * This is called when a TV Interactive App service is removed from the system.
*
- * <p>Normally it happens when the user uninstalls the previously installed TV IApp service
- * package.
+ * <p>Normally it happens when the user uninstalls the previously installed TV Interactive
+ * App service package.
*
- * @param iAppServiceId The ID of the TV IApp service.
+ * @param iAppServiceId The ID of the TV Interactive App service.
*/
- public void onIAppServiceRemoved(@NonNull String iAppServiceId) {
+ public void onInteractiveAppServiceRemoved(@NonNull String iAppServiceId) {
}
/**
- * This is called when a TV IApp service is updated on the system.
+ * This is called when a TV Interactive App service is updated on the system.
*
- * <p>Normally it happens when a previously installed TV IApp service package is
+ * <p>Normally it happens when a previously installed TV Interactive App service package is
* re-installed or a newer version of the package exists becomes available/unavailable.
*
- * @param iAppServiceId The ID of the TV IApp service.
+ * @param iAppServiceId The ID of the TV Interactive App service.
*/
- public void onIAppServiceUpdated(@NonNull String iAppServiceId) {
+ public void onInteractiveAppServiceUpdated(@NonNull String iAppServiceId) {
}
/**
- * This is called when the information about an existing TV IApp service has been updated.
+ * This is called when the information about an existing TV Interactive App service has been
+ * updated.
*
- * <p>Because the system automatically creates a <code>TvIAppInfo</code> object for each TV
- * IApp service based on the information collected from the
+ * <p>Because the system automatically creates a <code>TvInteractiveAppInfo</code> object
+ * for each TV Interactive App service based on the information collected from the
* <code>AndroidManifest.xml</code>, this method is only called back when such information
* has changed dynamically.
*
- * @param iAppInfo The <code>TvIAppInfo</code> object that contains new information.
+ * @param iAppInfo The <code>TvInteractiveAppInfo</code> object that contains new
+ * information.
*/
- public void onTvIAppInfoUpdated(@NonNull TvIAppInfo iAppInfo) {
+ public void onTvInteractiveAppInfoUpdated(@NonNull TvInteractiveAppInfo iAppInfo) {
}
/**
* This is called when the state of the interactive app service is changed.
* @hide
*/
- public void onTvIAppServiceStateChanged(
- @NonNull String iAppServiceId, int type, @TvIAppRteState int state) {
+ public void onTvInteractiveAppServiceStateChanged(
+ @NonNull String iAppServiceId, int type, @TvInteractiveAppRteState int state) {
}
}
- private static final class TvIAppCallbackRecord {
- private final TvIAppCallback mCallback;
+ private static final class TvInteractiveAppCallbackRecord {
+ private final TvInteractiveAppCallback mCallback;
private final Handler mHandler;
- TvIAppCallbackRecord(TvIAppCallback callback, Handler handler) {
+ TvInteractiveAppCallbackRecord(TvInteractiveAppCallback callback, Handler handler) {
mCallback = callback;
mHandler = handler;
}
- public TvIAppCallback getCallback() {
+ public TvInteractiveAppCallback getCallback() {
return mCallback;
}
- public void postIAppServiceAdded(final String iAppServiceId) {
+ public void postInteractiveAppServiceAdded(final String iAppServiceId) {
mHandler.post(new Runnable() {
@Override
public void run() {
- mCallback.onIAppServiceAdded(iAppServiceId);
+ mCallback.onInteractiveAppServiceAdded(iAppServiceId);
}
});
}
- public void postIAppServiceRemoved(final String iAppServiceId) {
+ public void postInteractiveAppServiceRemoved(final String iAppServiceId) {
mHandler.post(new Runnable() {
@Override
public void run() {
- mCallback.onIAppServiceRemoved(iAppServiceId);
+ mCallback.onInteractiveAppServiceRemoved(iAppServiceId);
}
});
}
- public void postIAppServiceUpdated(final String iAppServiceId) {
+ public void postInteractiveAppServiceUpdated(final String iAppServiceId) {
mHandler.post(new Runnable() {
@Override
public void run() {
- mCallback.onIAppServiceUpdated(iAppServiceId);
+ mCallback.onInteractiveAppServiceUpdated(iAppServiceId);
}
});
}
- public void postTvIAppInfoUpdated(final TvIAppInfo iAppInfo) {
+ public void postTvInteractiveAppInfoUpdated(final TvInteractiveAppInfo iAppInfo) {
mHandler.post(new Runnable() {
@Override
public void run() {
- mCallback.onTvIAppInfoUpdated(iAppInfo);
+ mCallback.onTvInteractiveAppInfoUpdated(iAppInfo);
}
});
}
@@ -537,7 +590,7 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- mCallback.onTvIAppServiceStateChanged(iAppServiceId, type, state);
+ mCallback.onTvInteractiveAppServiceStateChanged(iAppServiceId, type, state);
}
});
}
@@ -578,23 +631,23 @@
}
/**
- * Returns the complete list of TV IApp service on the system.
+ * Returns the complete list of TV Interactive App service on the system.
*
- * @return List of {@link TvIAppInfo} for each TV IApp service that describes its meta
- * information.
+ * @return List of {@link TvInteractiveAppInfo} for each TV Interactive App service that
+ * describes its meta information.
* @hide
*/
@NonNull
- public List<TvIAppInfo> getTvIAppServiceList() {
+ public List<TvInteractiveAppInfo> getTvInteractiveAppServiceList() {
try {
- return mService.getTvIAppServiceList(mUserId);
+ return mService.getTvInteractiveAppServiceList(mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Prepares TV IApp service for the given type.
+ * Prepares TV Interactive App service for the given type.
* @hide
*/
public void prepare(@NonNull String tvIAppServiceId, int type) {
@@ -606,12 +659,25 @@
}
/**
- * Notifies app link info.
+ * Registers app link info.
* @hide
*/
- public void notifyAppLinkInfo(@NonNull String tvIAppServiceId, @NonNull Bundle appLinkInfo) {
+ public void registerAppLinkInfo(@NonNull String tvIAppServiceId, @NonNull Bundle appLinkInfo) {
try {
- mService.notifyAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId);
+ mService.registerAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregisters app link info.
+ * @hide
+ */
+ public void unregisterAppLinkInfo(
+ @NonNull String tvIAppServiceId, @NonNull Bundle appLinkInfo) {
+ try {
+ mService.unregisterAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -630,32 +696,33 @@
}
/**
- * Registers a {@link TvIAppManager.TvIAppCallback}.
+ * Registers a {@link TvInteractiveAppCallback}.
*
- * @param callback A callback used to monitor status of the TV IApp services.
+ * @param callback A callback used to monitor status of the TV Interactive App services.
* @param handler A {@link Handler} that the status change will be delivered to.
* @hide
*/
- public void registerCallback(@NonNull TvIAppCallback callback, @NonNull Handler handler) {
+ public void registerCallback(
+ @NonNull TvInteractiveAppCallback callback, @NonNull Handler handler) {
Preconditions.checkNotNull(callback);
Preconditions.checkNotNull(handler);
synchronized (mLock) {
- mCallbackRecords.add(new TvIAppCallbackRecord(callback, handler));
+ mCallbackRecords.add(new TvInteractiveAppCallbackRecord(callback, handler));
}
}
/**
- * Unregisters the existing {@link TvIAppManager.TvIAppCallback}.
+ * Unregisters the existing {@link TvInteractiveAppCallback}.
*
* @param callback The existing callback to remove.
* @hide
*/
- public void unregisterCallback(@NonNull final TvIAppCallback callback) {
+ public void unregisterCallback(@NonNull final TvInteractiveAppCallback callback) {
Preconditions.checkNotNull(callback);
synchronized (mLock) {
- for (Iterator<TvIAppCallbackRecord> it = mCallbackRecords.iterator();
+ for (Iterator<TvInteractiveAppCallbackRecord> it = mCallbackRecords.iterator();
it.hasNext(); ) {
- TvIAppCallbackRecord record = it.next();
+ TvInteractiveAppCallbackRecord record = it.next();
if (record.getCallback() == callback) {
it.remove();
break;
@@ -692,8 +759,8 @@
private TvInputEventSender mSender;
private InputChannel mInputChannel;
- private Session(IBinder token, InputChannel channel, ITvIAppManager service, int userId,
- int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
+ private Session(IBinder token, InputChannel channel, ITvIAppManager service,
+ int userId, int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
mToken = token;
mInputChannel = channel;
mService = service;
@@ -710,25 +777,37 @@
mInputSession = inputSession;
}
- void startIApp() {
+ void startInteractiveApp() {
if (mToken == null) {
Log.w(TAG, "The session has been already released");
return;
}
try {
- mService.startIApp(mToken, mUserId);
+ mService.startInteractiveApp(mToken, mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
- void stopIApp() {
+ void stopInteractiveApp() {
if (mToken == null) {
Log.w(TAG, "The session has been already released");
return;
}
try {
- mService.stopIApp(mToken, mUserId);
+ mService.stopInteractiveApp(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void resetInteractiveApp() {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.resetInteractiveApp(mToken, mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -758,6 +837,18 @@
}
}
+ void setTeletextAppEnabled(boolean enable) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.setTeletextAppEnabled(mToken, enable, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
void sendCurrentChannelUri(@Nullable Uri channelUri) {
if (mToken == null) {
Log.w(TAG, "The session has been already released");
@@ -806,6 +897,18 @@
}
}
+ void sendCurrentTvInputId(@Nullable String inputId) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.sendCurrentTvInputId(mToken, inputId, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Sets the {@link android.view.Surface} for this session.
*
@@ -994,7 +1097,7 @@
}
/**
- * Notifies IAPP session when a channel is tuned.
+ * Notifies Interactive APP session when a channel is tuned.
*/
public void notifyTuned(Uri channelUri) {
if (mToken == null) {
@@ -1009,7 +1112,7 @@
}
/**
- * Notifies IAPP session when a track is selected.
+ * Notifies Interactive APP session when a track is selected.
*/
public void notifyTrackSelected(int type, String trackId) {
if (mToken == null) {
@@ -1024,7 +1127,7 @@
}
/**
- * Notifies IAPP session when tracks are changed.
+ * Notifies Interactive APP session when tracks are changed.
*/
public void notifyTracksChanged(List<TvTrackInfo> tracks) {
if (mToken == null) {
@@ -1098,6 +1201,21 @@
}
}
+ /**
+ * Notifies Interactive APP session when signal strength is changed.
+ */
+ public void notifySignalStrength(int strength) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.notifySignalStrength(mToken, strength, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private void flushPendingEventsLocked() {
mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
@@ -1359,7 +1477,8 @@
});
}
- void postCommandRequest(final @TvIAppService.IAppServiceCommandType String cmdType,
+ void postCommandRequest(
+ final @TvIAppService.InteractiveAppServiceCommandType String cmdType,
final Bundle parameters) {
mHandler.post(new Runnable() {
@Override
@@ -1414,6 +1533,15 @@
});
}
+ void postRequestCurrentTvInputId() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestCurrentTvInputId(mSession);
+ }
+ });
+ }
+
void postAdRequest(final AdRequest request) {
mHandler.post(new Runnable() {
@Override
@@ -1442,6 +1570,15 @@
}
});
}
+
+ void postTeletextAppStateChanged(int state) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onTeletextAppStateChanged(mSession, state);
+ }
+ });
+ }
}
/**
@@ -1452,8 +1589,8 @@
/**
* This is called after {@link TvIAppManager#createSession} has been processed.
*
- * @param session A {@link TvIAppManager.Session} instance created. This can be {@code null}
- * if the creation request failed.
+ * @param session A {@link TvIAppManager.Session} instance created. This can be
+ * {@code null} if the creation request failed.
*/
public void onSessionCreated(@Nullable Session session) {
}
@@ -1468,8 +1605,8 @@
}
/**
- * This is called when {@link TvIAppService.Session#layoutSurface} is called to change the
- * layout of surface.
+ * This is called when {@link TvIAppService.Session#layoutSurface} is called to
+ * change the layout of surface.
*
* @param session A {@link TvIAppManager.Session} associated with this callback.
* @param left Left position.
@@ -1487,8 +1624,10 @@
* @param cmdType type of the command.
* @param parameters parameters of the command.
*/
- public void onCommandRequest(Session session,
- @TvIAppService.IAppServiceCommandType String cmdType, Bundle parameters) {
+ public void onCommandRequest(
+ Session session,
+ @TvIAppService.InteractiveAppServiceCommandType String cmdType,
+ Bundle parameters) {
}
/**
@@ -1500,7 +1639,8 @@
}
/**
- * This is called when {@link TvIAppService.Session#RequestCurrentChannelUri} is called.
+ * This is called when {@link TvIAppService.Session#RequestCurrentChannelUri} is
+ * called.
*
* @param session A {@link TvIAppManager.Session} associated with this callback.
*/
@@ -1508,7 +1648,8 @@
}
/**
- * This is called when {@link TvIAppService.Session#RequestCurrentChannelLcn} is called.
+ * This is called when {@link TvIAppService.Session#RequestCurrentChannelLcn} is
+ * called.
*
* @param session A {@link TvIAppManager.Session} associated with this callback.
*/
@@ -1516,7 +1657,8 @@
}
/**
- * This is called when {@link TvIAppService.Session#RequestStreamVolume} is called.
+ * This is called when {@link TvIAppService.Session#RequestStreamVolume} is
+ * called.
*
* @param session A {@link TvIAppManager.Session} associated with this callback.
*/
@@ -1524,7 +1666,8 @@
}
/**
- * This is called when {@link TvIAppService.Session#RequestTrackInfoList} is called.
+ * This is called when {@link TvIAppService.Session#RequestTrackInfoList} is
+ * called.
*
* @param session A {@link TvIAppManager.Session} associated with this callback.
*/
@@ -1532,6 +1675,15 @@
}
/**
+ * This is called when {@link TvIAppService.Session#RequestCurrentTvInputId} is called.
+ *
+ * @param session A {@link TvIAppManager.Session} associated with this callback.
+ * @hide
+ */
+ public void onRequestCurrentTvInputId(Session session) {
+ }
+
+ /**
* This is called when {@link TvIAppService.Session#notifySessionStateChanged} is called.
*
* @param session A {@link TvIAppManager.Session} associated with this callback.
@@ -1541,8 +1693,8 @@
}
/**
- * This is called when {@link TvIAppService.Session#notifyBiInteractiveAppCreated} is
- * called.
+ * This is called when {@link TvIAppService.Session#notifyBiInteractiveAppCreated}
+ * is called.
*
* @param session A {@link TvIAppManager.Session} associated with this callback.
* @param biIAppUri URI associated this BI interactive app. This is the same URI in
@@ -1552,5 +1704,16 @@
*/
public void onBiInteractiveAppCreated(Session session, Uri biIAppUri, String biIAppId) {
}
+
+ /**
+ * This is called when {@link TvIAppService.Session#notifyTeletextAppStateChanged} is
+ * called.
+ *
+ * @param session A {@link TvIAppManager.Session} associated with this callback.
+ * @param state the current state.
+ */
+ public void onTeletextAppStateChanged(
+ Session session, @TvIAppManager.TeletextAppState int state) {
+ }
}
}
diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvIAppService.java
old mode 100644
new mode 100755
index 4993bc3..c0ec76b
--- a/media/java/android/media/tv/interactive/TvIAppService.java
+++ b/media/java/android/media/tv/interactive/TvIAppService.java
@@ -32,6 +32,7 @@
import android.media.tv.BroadcastInfoRequest;
import android.media.tv.BroadcastInfoResponse;
import android.media.tv.TvContentRating;
+import android.media.tv.TvInputManager;
import android.media.tv.TvTrackInfo;
import android.net.Uri;
import android.os.AsyncTask;
@@ -75,45 +76,48 @@
// TODO: cleanup and unhide APIs.
/**
- * This is the interface name that a service implementing a TV IApp service should say that it
- * supports -- that is, this is the action it uses for its intent filter. To be supported, the
- * service must also require the android.Manifest.permission#BIND_TV_IAPP permission so
- * that other applications cannot abuse it.
+ * This is the interface name that a service implementing a TV Interactive App service should
+ * say that it supports -- that is, this is the action it uses for its intent filter. To be
+ * supported, the service must also require the
+ * android.Manifest.permission#BIND_TV_INTERACTIVE_APP permission so that other applications
+ * cannot abuse it.
*/
- public static final String SERVICE_INTERFACE = "android.media.tv.interactive.TvIAppService";
+ public static final String SERVICE_INTERFACE =
+ "android.media.tv.interactive.TvIAppService";
/**
- * Name under which a TvIAppService component publishes information about itself. This meta-data
- * must reference an XML resource containing an
- * <code><{@link android.R.styleable#TvIAppService tv-iapp}></code>
+ * Name under which a TvIAppService component publishes information about itself. This
+ * meta-data must reference an XML resource containing an
+ * <code><{@link android.R.styleable#TvIAppService tv-interactive-app}></code>
* tag.
*/
public static final String SERVICE_META_DATA = "android.media.tv.interactive.app";
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @StringDef(prefix = "IAPP_SERVICE_COMMAND_TYPE_", value = {
- IAPP_SERVICE_COMMAND_TYPE_TUNE,
- IAPP_SERVICE_COMMAND_TYPE_TUNE_NEXT,
- IAPP_SERVICE_COMMAND_TYPE_TUNE_PREV,
- IAPP_SERVICE_COMMAND_TYPE_STOP,
- IAPP_SERVICE_COMMAND_TYPE_SET_STREAM_VOLUME,
- IAPP_SERVICE_COMMAND_TYPE_SELECT_TRACK
+ @StringDef(prefix = "INTERACTIVE_APP_SERVICE_COMMAND_TYPE_", value = {
+ INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE,
+ INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE_NEXT,
+ INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE_PREV,
+ INTERACTIVE_APP_SERVICE_COMMAND_TYPE_STOP,
+ INTERACTIVE_APP_SERVICE_COMMAND_TYPE_SET_STREAM_VOLUME,
+ INTERACTIVE_APP_SERVICE_COMMAND_TYPE_SELECT_TRACK
})
- public @interface IAppServiceCommandType {}
+ public @interface InteractiveAppServiceCommandType {}
/** @hide */
- public static final String IAPP_SERVICE_COMMAND_TYPE_TUNE = "tune";
+ public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE = "tune";
/** @hide */
- public static final String IAPP_SERVICE_COMMAND_TYPE_TUNE_NEXT = "tune_next";
+ public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE_NEXT = "tune_next";
/** @hide */
- public static final String IAPP_SERVICE_COMMAND_TYPE_TUNE_PREV = "tune_previous";
+ public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE_PREV = "tune_previous";
/** @hide */
- public static final String IAPP_SERVICE_COMMAND_TYPE_STOP = "stop";
+ public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_STOP = "stop";
/** @hide */
- public static final String IAPP_SERVICE_COMMAND_TYPE_SET_STREAM_VOLUME = "set_stream_volume";
+ public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_SET_STREAM_VOLUME =
+ "set_stream_volume";
/** @hide */
- public static final String IAPP_SERVICE_COMMAND_TYPE_SELECT_TRACK = "select_track";
+ public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_SELECT_TRACK = "select_track";
/** @hide */
public static final String COMMAND_PARAMETER_KEY_CHANNEL_URI = "command_channel_uri";
/** @hide */
@@ -129,29 +133,29 @@
"command_track_select_mode";
private final Handler mServiceHandler = new ServiceHandler();
- private final RemoteCallbackList<ITvIAppServiceCallback> mCallbacks =
+ private final RemoteCallbackList<ITvInteractiveAppServiceCallback> mCallbacks =
new RemoteCallbackList<>();
/** @hide */
@Override
public final IBinder onBind(Intent intent) {
- ITvIAppService.Stub tvIAppServiceBinder = new ITvIAppService.Stub() {
+ ITvInteractiveAppService.Stub tvIAppServiceBinder = new ITvInteractiveAppService.Stub() {
@Override
- public void registerCallback(ITvIAppServiceCallback cb) {
+ public void registerCallback(ITvInteractiveAppServiceCallback cb) {
if (cb != null) {
mCallbacks.register(cb);
}
}
@Override
- public void unregisterCallback(ITvIAppServiceCallback cb) {
+ public void unregisterCallback(ITvInteractiveAppServiceCallback cb) {
if (cb != null) {
mCallbacks.unregister(cb);
}
}
@Override
- public void createSession(InputChannel channel, ITvIAppSessionCallback cb,
+ public void createSession(InputChannel channel, ITvInteractiveAppSessionCallback cb,
String iAppServiceId, int type) {
if (cb == null) {
return;
@@ -171,8 +175,13 @@
}
@Override
- public void notifyAppLinkInfo(Bundle appLinkInfo) {
- onAppLinkInfo(appLinkInfo);
+ public void registerAppLinkInfo(Bundle appLinkInfo) {
+ onRegisterAppLinkInfo(appLinkInfo);
+ }
+
+ @Override
+ public void unregisterAppLinkInfo(Bundle appLinkInfo) {
+ onUnregisterAppLinkInfo(appLinkInfo);
}
@Override
@@ -184,7 +193,7 @@
}
/**
- * Prepares TV IApp service for the given type.
+ * Prepares TV Interactive App service for the given type.
* @hide
*/
public void onPrepare(int type) {
@@ -195,7 +204,15 @@
* Registers App link info.
* @hide
*/
- public void onAppLinkInfo(Bundle appLinkInfo) {
+ public void onRegisterAppLinkInfo(Bundle appLinkInfo) {
+ // TODO: make it abstract when unhide
+ }
+
+ /**
+ * Unregisters App link info.
+ * @hide
+ */
+ public void onUnregisterAppLinkInfo(Bundle appLinkInfo) {
// TODO: make it abstract when unhide
}
@@ -211,11 +228,11 @@
/**
* Returns a concrete implementation of {@link Session}.
*
- * <p>May return {@code null} if this TV IApp service fails to create a session for some
- * reason.
+ * <p>May return {@code null} if this TV Interactive App service fails to create a session for
+ * some reason.
*
- * @param iAppServiceId The ID of the TV IApp associated with the session.
- * @param type The type of the TV IApp associated with the session.
+ * @param iAppServiceId The ID of the TV Interactive App associated with the session.
+ * @param type The type of the TV Interactive App associated with the session.
* @hide
*/
@Nullable
@@ -229,7 +246,8 @@
* @param state the current state
* @hide
*/
- public final void notifyStateChanged(int type, @TvIAppManager.TvIAppRteState int state) {
+ public final void notifyStateChanged(
+ int type, @TvIAppManager.TvInteractiveAppRteState int state) {
mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_RTE_STATE_CHANGED,
type, state).sendToTarget();
}
@@ -243,7 +261,7 @@
private final Object mLock = new Object();
// @GuardedBy("mLock")
- private ITvIAppSessionCallback mSessionCallback;
+ private ITvInteractiveAppSessionCallback mSessionCallback;
// @GuardedBy("mLock")
private final List<Runnable> mPendingActions = new ArrayList<>();
@@ -276,7 +294,7 @@
* <p>By default, the media view is disabled. Must be called explicitly after the
* session is created to enable the media view.
*
- * <p>The TV IApp service can disable its media view when needed.
+ * <p>The TV Interactive App service can disable its media view when needed.
*
* @param enable {@code true} if you want to enable the media view. {@code false}
* otherwise.
@@ -304,14 +322,21 @@
* Starts TvIAppService session.
* @hide
*/
- public void onStartIApp() {
+ public void onStartInteractiveApp() {
}
/**
* Stops TvIAppService session.
* @hide
*/
- public void onStopIApp() {
+ public void onStopInteractiveApp() {
+ }
+
+ /**
+ * Resets TvIAppService session.
+ * @hide
+ */
+ public void onResetInteractiveApp() {
}
/**
@@ -337,6 +362,13 @@
}
/**
+ * To toggle Digital Teletext Application if there is one in AIT app list.
+ * @param enable
+ */
+ public void onSetTeletextAppEnabled(boolean enable) {
+ }
+
+ /**
* Receives current channel URI.
* @hide
*/
@@ -365,11 +397,18 @@
}
/**
+ * Receives current TV input ID.
+ * @hide
+ */
+ public void onCurrentTvInputId(@Nullable String inputId) {
+ }
+
+ /**
* Called when the application sets the surface.
*
- * <p>The TV IApp service should render interactive app UI onto the given surface. When
- * called with {@code null}, the IApp service should immediately free any references to the
- * currently set surface and stop using it.
+ * <p>The TV Interactive App service should render interactive app UI onto the given
+ * surface. When called with {@code null}, the Interactive App service should immediately
+ * free any references to the currently set surface and stop using it.
*
* @param surface The surface to be used for interactive app UI rendering. Can be
* {@code null}.
@@ -394,8 +433,8 @@
*
* <p>This is always called at least once when the session is created regardless of whether
* the media view is enabled or not. The media view container size is the same as the
- * containing {@link TvIAppView}. Note that the size of the underlying surface can be
- * different if the surface was changed by calling {@link #layoutSurface}.
+ * containing {@link TvInteractiveAppView}. Note that the size of the underlying surface can
+ * be different if the surface was changed by calling {@link #layoutSurface}.
*
* @param width The width of the media view.
* @param height The height of the media view.
@@ -471,6 +510,13 @@
}
/**
+ * Called when signal strength is changed.
+ * @hide
+ */
+ public void onSignalStrength(@TvInputManager.SignalStrength int strength) {
+ }
+
+ /**
* Called when a broadcast info response is received.
* @hide
*/
@@ -624,7 +670,8 @@
* @param cmdType type of the specific command
* @param parameters parameters of the specific command
*/
- public void requestCommand(@IAppServiceCommandType String cmdType, Bundle parameters) {
+ public void requestCommand(
+ @InteractiveAppServiceCommandType String cmdType, Bundle parameters) {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@Override
@@ -755,6 +802,31 @@
}
/**
+ * Requests current TV input ID.
+ *
+ * @see android.media.tv.TvInputInfo
+ * @hide
+ */
+ public void requestCurrentTvInputId() {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestCurrentTvInputId");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestCurrentTvInputId();
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestCurrentTvInputId", e);
+ }
+ }
+ });
+ }
+
+ /**
* requests an advertisement request to be processed by the related TV input.
* @param request advertisement request
*/
@@ -777,12 +849,16 @@
});
}
- void startIApp() {
- onStartIApp();
+ void startInteractiveApp() {
+ onStartInteractiveApp();
}
- void stopIApp() {
- onStopIApp();
+ void stopInteractiveApp() {
+ onStopInteractiveApp();
+ }
+
+ void resetInteractiveApp() {
+ onResetInteractiveApp();
}
void createBiInteractiveApp(@NonNull Uri biIAppUri, @Nullable Bundle params) {
@@ -793,6 +869,10 @@
onDestroyBiInteractiveApp(biIAppId);
}
+ void setTeletextAppEnabled(boolean enable) {
+ onSetTeletextAppEnabled(enable);
+ }
+
void sendCurrentChannelUri(@Nullable Uri channelUri) {
onCurrentChannelUri(channelUri);
}
@@ -809,6 +889,10 @@
onTrackInfoList(tracks);
}
+ void sendCurrentTvInputId(@Nullable String inputId) {
+ onCurrentTvInputId(inputId);
+ }
+
void release() {
onRelease();
if (mSurface != null) {
@@ -873,6 +957,13 @@
onContentBlocked(rating);
}
+ void notifySignalStrength(int strength) {
+ if (DEBUG) {
+ Log.d(TAG, "notifySignalStrength (strength=" + strength + ")");
+ }
+ onSignalStrength(strength);
+ }
+
/**
* Calls {@link #onBroadcastInfoResponse}.
*/
@@ -898,7 +989,8 @@
* Notifies when the session state is changed.
* @param state the current state.
*/
- public void notifySessionStateChanged(@TvIAppManager.TvIAppRteState int state) {
+ public void notifySessionStateChanged(
+ @TvIAppManager.TvInteractiveAppRteState int state) {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@Override
@@ -945,6 +1037,30 @@
}
/**
+ * Notifies when the digital teletext app state is changed.
+ * @param state the current state.
+ */
+ public final void notifyTeletextAppStateChanged(@TvIAppManager.TeletextAppState int state) {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "notifyTeletextAppState (state="
+ + state + ")");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onTeletextAppStateChanged(state);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in notifyTeletextAppState", e);
+ }
+ }
+ });
+ }
+
+ /**
* Takes care of dispatching incoming input events and tells whether the event was handled.
*/
int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
@@ -977,7 +1093,7 @@
return TvIAppManager.Session.DISPATCH_NOT_HANDLED;
}
- private void initialize(ITvIAppSessionCallback callback) {
+ private void initialize(ITvInteractiveAppSessionCallback callback) {
synchronized (mLock) {
mSessionCallback = callback;
for (Runnable runnable : mPendingActions) {
@@ -1087,8 +1203,8 @@
if (DEBUG) Log.d(TAG, "relayoutMediaView(" + frame + ")");
if (mMediaFrame == null || mMediaFrame.width() != frame.width()
|| mMediaFrame.height() != frame.height()) {
- // Note: relayoutMediaView is called whenever TvIAppView's layout is changed
- // regardless of setMediaViewEnabled.
+ // Note: relayoutMediaView is called whenever TvInteractiveAppView's layout is
+ // changed regardless of setMediaViewEnabled.
onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
}
mMediaFrame = frame;
@@ -1159,31 +1275,37 @@
}
/**
- * Implements the internal ITvIAppSession interface.
+ * Implements the internal ITvInteractiveAppSession interface.
* @hide
*/
- public static class ITvIAppSessionWrapper extends ITvIAppSession.Stub {
- // TODO: put ITvIAppSessionWrapper in a separate Java file
+ public static class ITvInteractiveAppSessionWrapper extends ITvInteractiveAppSession.Stub {
+ // TODO: put ITvInteractiveAppSessionWrapper in a separate Java file
private final Session mSessionImpl;
private InputChannel mChannel;
- private TvIAppEventReceiver mReceiver;
+ private TvInteractiveAppEventReceiver mReceiver;
- public ITvIAppSessionWrapper(Context context, Session mSessionImpl, InputChannel channel) {
+ public ITvInteractiveAppSessionWrapper(
+ Context context, Session mSessionImpl, InputChannel channel) {
this.mSessionImpl = mSessionImpl;
mChannel = channel;
if (channel != null) {
- mReceiver = new TvIAppEventReceiver(channel, context.getMainLooper());
+ mReceiver = new TvInteractiveAppEventReceiver(channel, context.getMainLooper());
}
}
@Override
- public void startIApp() {
- mSessionImpl.startIApp();
+ public void startInteractiveApp() {
+ mSessionImpl.startInteractiveApp();
}
@Override
- public void stopIApp() {
- mSessionImpl.stopIApp();
+ public void stopInteractiveApp() {
+ mSessionImpl.stopInteractiveApp();
+ }
+
+ @Override
+ public void resetInteractiveApp() {
+ mSessionImpl.resetInteractiveApp();
}
@Override
@@ -1192,6 +1314,11 @@
}
@Override
+ public void setTeletextAppEnabled(boolean enable) {
+ mSessionImpl.setTeletextAppEnabled(enable);
+ }
+
+ @Override
public void destroyBiInteractiveApp(@NonNull String biIAppId) {
mSessionImpl.destroyBiInteractiveApp(biIAppId);
}
@@ -1217,6 +1344,11 @@
}
@Override
+ public void sendCurrentTvInputId(@Nullable String inputId) {
+ mSessionImpl.sendCurrentTvInputId(inputId);
+ }
+
+ @Override
public void release() {
mSessionImpl.scheduleMediaViewCleanup();
mSessionImpl.release();
@@ -1258,6 +1390,11 @@
}
@Override
+ public void notifySignalStrength(int strength) {
+ mSessionImpl.notifySignalStrength(strength);
+ }
+
+ @Override
public void setSurface(Surface surface) {
mSessionImpl.setSurface(surface);
}
@@ -1292,8 +1429,8 @@
mSessionImpl.removeMediaView(true);
}
- private final class TvIAppEventReceiver extends InputEventReceiver {
- TvIAppEventReceiver(InputChannel inputChannel, Looper looper) {
+ private final class TvInteractiveAppEventReceiver extends InputEventReceiver {
+ TvInteractiveAppEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}
@@ -1307,7 +1444,8 @@
int handled = mSessionImpl.dispatchInputEvent(event, this);
if (handled != TvIAppManager.Session.DISPATCH_IN_PROGRESS) {
- finishInputEvent(event, handled == TvIAppManager.Session.DISPATCH_HANDLED);
+ finishInputEvent(
+ event, handled == TvIAppManager.Session.DISPATCH_HANDLED);
}
}
}
@@ -1337,7 +1475,8 @@
case DO_CREATE_SESSION: {
SomeArgs args = (SomeArgs) msg.obj;
InputChannel channel = (InputChannel) args.arg1;
- ITvIAppSessionCallback cb = (ITvIAppSessionCallback) args.arg2;
+ ITvInteractiveAppSessionCallback cb =
+ (ITvInteractiveAppSessionCallback) args.arg2;
String iAppServiceId = (String) args.arg3;
int type = (int) args.arg4;
args.recycle();
@@ -1351,8 +1490,8 @@
}
return;
}
- ITvIAppSession stub = new ITvIAppSessionWrapper(
- TvIAppService.this, sessionImpl, channel);
+ ITvInteractiveAppSession stub = new ITvInteractiveAppSessionWrapper(
+ android.media.tv.interactive.TvIAppService.this, sessionImpl, channel);
SomeArgs someArgs = SomeArgs.obtain();
someArgs.arg1 = sessionImpl;
@@ -1365,8 +1504,9 @@
case DO_NOTIFY_SESSION_CREATED: {
SomeArgs args = (SomeArgs) msg.obj;
Session sessionImpl = (Session) args.arg1;
- ITvIAppSession stub = (ITvIAppSession) args.arg2;
- ITvIAppSessionCallback cb = (ITvIAppSessionCallback) args.arg3;
+ ITvInteractiveAppSession stub = (ITvInteractiveAppSession) args.arg2;
+ ITvInteractiveAppSessionCallback cb =
+ (ITvInteractiveAppSessionCallback) args.arg3;
try {
cb.onSessionCreated(stub);
} catch (RemoteException e) {
diff --git a/media/java/android/media/tv/interactive/TvIAppInfo.aidl b/media/java/android/media/tv/interactive/TvInteractiveAppInfo.aidl
similarity index 95%
rename from media/java/android/media/tv/interactive/TvIAppInfo.aidl
rename to media/java/android/media/tv/interactive/TvInteractiveAppInfo.aidl
index 6041460..5e15016 100644
--- a/media/java/android/media/tv/interactive/TvIAppInfo.aidl
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppInfo.aidl
@@ -16,4 +16,4 @@
package android.media.tv.interactive;
-parcelable TvIAppInfo;
\ No newline at end of file
+parcelable TvInteractiveAppInfo;
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java b/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java
new file mode 100644
index 0000000..2f96552
--- /dev/null
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.interactive;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class is used to specify meta information of a TV interactive app.
+ * @hide
+ */
+public final class TvInteractiveAppInfo implements Parcelable {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "TvInteractiveAppInfo";
+
+ private static final String XML_START_TAG_NAME = "tv-interactive-app";
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "INTERACTIVE_APP_TYPE_" }, value = {
+ INTERACTIVE_APP_TYPE_HBBTV,
+ INTERACTIVE_APP_TYPE_ATSC,
+ INTERACTIVE_APP_TYPE_GINGA,
+ })
+ @interface InteractiveAppType {}
+
+ /** HbbTV interactive app type */
+ public static final int INTERACTIVE_APP_TYPE_HBBTV = 0x1;
+ /** ATSC interactive app type */
+ public static final int INTERACTIVE_APP_TYPE_ATSC = 0x2;
+ /** Ginga interactive app type */
+ public static final int INTERACTIVE_APP_TYPE_GINGA = 0x4;
+
+ private final ResolveInfo mService;
+ private final String mId;
+ private int mTypes;
+
+ public TvInteractiveAppInfo(@NonNull Context context, @NonNull ComponentName component) {
+ if (context == null) {
+ throw new IllegalArgumentException("context cannot be null.");
+ }
+ Intent intent =
+ new Intent(TvIAppService.SERVICE_INTERFACE).setComponent(component);
+ ResolveInfo resolveInfo = context.getPackageManager().resolveService(intent,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+ if (resolveInfo == null) {
+ throw new IllegalArgumentException("Invalid component. Can't find the service.");
+ }
+
+ ComponentName componentName = new ComponentName(resolveInfo.serviceInfo.packageName,
+ resolveInfo.serviceInfo.name);
+ String id;
+ id = generateInteractiveAppServiceId(componentName);
+ List<String> types = new ArrayList<>();
+ parseServiceMetadata(resolveInfo, context, types);
+
+ mService = resolveInfo;
+ mId = id;
+ mTypes = toTypesFlag(types);
+ }
+ private TvInteractiveAppInfo(ResolveInfo service, String id, int types) {
+ mService = service;
+ mId = id;
+ mTypes = types;
+ }
+
+ private TvInteractiveAppInfo(@NonNull Parcel in) {
+ mService = ResolveInfo.CREATOR.createFromParcel(in);
+ mId = in.readString();
+ mTypes = in.readInt();
+ }
+
+ public static final @NonNull Creator<TvInteractiveAppInfo> CREATOR =
+ new Creator<TvInteractiveAppInfo>() {
+ @Override
+ public TvInteractiveAppInfo createFromParcel(Parcel in) {
+ return new TvInteractiveAppInfo(in);
+ }
+
+ @Override
+ public TvInteractiveAppInfo[] newArray(int size) {
+ return new TvInteractiveAppInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mService.writeToParcel(dest, flags);
+ dest.writeString(mId);
+ dest.writeInt(mTypes);
+ }
+
+ @NonNull
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the component of the TV Interactive App service.
+ * @hide
+ */
+ public ComponentName getComponent() {
+ return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name);
+ }
+
+ /**
+ * Returns the information of the service that implements this TV Interactive App service.
+ */
+ @Nullable
+ public ServiceInfo getServiceInfo() {
+ return mService.serviceInfo;
+ }
+
+ /**
+ * Gets supported interactive app types
+ */
+ @InteractiveAppType
+ @NonNull
+ public int getSupportedTypes() {
+ return mTypes;
+ }
+
+ private static String generateInteractiveAppServiceId(ComponentName name) {
+ return name.flattenToShortString();
+ }
+
+ private static void parseServiceMetadata(
+ ResolveInfo resolveInfo, Context context, List<String> types) {
+ ServiceInfo si = resolveInfo.serviceInfo;
+ PackageManager pm = context.getPackageManager();
+ try (XmlResourceParser parser =
+ si.loadXmlMetaData(pm, TvIAppService.SERVICE_META_DATA)) {
+ if (parser == null) {
+ throw new IllegalStateException(
+ "No " + TvIAppService.SERVICE_META_DATA
+ + " meta-data found for " + si.name);
+ }
+
+ Resources res = pm.getResourcesForApplication(si.applicationInfo);
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ // move to the START_TAG
+ }
+
+ String nodeName = parser.getName();
+ if (!XML_START_TAG_NAME.equals(nodeName)) {
+ throw new IllegalStateException("Meta-data does not start with "
+ + XML_START_TAG_NAME + " tag for " + si.name);
+ }
+
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.TvIAppService);
+ CharSequence[] textArr = sa.getTextArray(
+ com.android.internal.R.styleable.TvIAppService_supportedTypes);
+ for (CharSequence cs : textArr) {
+ types.add(cs.toString().toLowerCase());
+ }
+
+ sa.recycle();
+ } catch (IOException | XmlPullParserException e) {
+ throw new IllegalStateException(
+ "Failed reading meta-data for " + si.packageName, e);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalStateException("No resources found for " + si.packageName, e);
+ }
+ }
+
+ private static int toTypesFlag(List<String> types) {
+ int flag = 0;
+ for (String type : types) {
+ switch (type) {
+ case "hbbtv":
+ flag |= INTERACTIVE_APP_TYPE_HBBTV;
+ break;
+ case "atsc":
+ flag |= INTERACTIVE_APP_TYPE_ATSC;
+ break;
+ case "ginga":
+ flag |= INTERACTIVE_APP_TYPE_GINGA;
+ break;
+ default:
+ break;
+ }
+ }
+ return flag;
+ }
+}
diff --git a/media/java/android/media/tv/interactive/TvIAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
old mode 100644
new mode 100755
similarity index 72%
rename from media/java/android/media/tv/interactive/TvIAppView.java
rename to media/java/android/media/tv/interactive/TvInteractiveAppView.java
index b295055..6f99d51
--- a/media/java/android/media/tv/interactive/TvIAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -16,6 +16,7 @@
package android.media.tv.interactive;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -45,13 +46,14 @@
import android.view.ViewRootImpl;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* Displays contents of interactive TV applications.
* @hide
*/
-public class TvIAppView extends ViewGroup {
- private static final String TAG = "TvIAppView";
+public class TvInteractiveAppView extends ViewGroup {
+ private static final String TAG = "TvInteractiveAppView";
private static final boolean DEBUG = false;
private static final int SET_TVVIEW_SUCCESS = 1;
@@ -59,11 +61,13 @@
private static final int UNSET_TVVIEW_SUCCESS = 3;
private static final int UNSET_TVVIEW_FAIL = 4;
- private final TvIAppManager mTvIAppManager;
+ private final TvIAppManager mTvInteractiveAppManager;
private final Handler mHandler = new Handler();
+ private final Object mCallbackLock = new Object();
private Session mSession;
private MySessionCallback mSessionCallback;
- private TvIAppCallback mCallback;
+ private TvInteractiveAppCallback mCallback;
+ private Executor mCallbackExecutor;
private SurfaceView mSurfaceView;
private Surface mSurface;
@@ -114,15 +118,16 @@
}
};
- public TvIAppView(@NonNull Context context) {
+ public TvInteractiveAppView(@NonNull Context context) {
this(context, null, 0);
}
- public TvIAppView(@NonNull Context context, @Nullable AttributeSet attrs) {
+ public TvInteractiveAppView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
- public TvIAppView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ public TvInteractiveAppView(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
super(context, attrs, defStyleAttr);
int sourceResId = Resources.getAttributeSetSourceResId(attrs);
if (sourceResId != Resources.ID_NULL) {
@@ -136,31 +141,50 @@
}
mDefStyleAttr = defStyleAttr;
resetSurfaceView();
- mTvIAppManager = (TvIAppManager) getContext().getSystemService(Context.TV_IAPP_SERVICE);
+ mTvInteractiveAppManager = (TvIAppManager) getContext().getSystemService(
+ Context.TV_IAPP_SERVICE);
}
/**
- * Sets the callback to be invoked when an event is dispatched to this TvIAppView.
+ * Sets the callback to be invoked when an event is dispatched to this TvInteractiveAppView.
*
* @param callback The callback to receive events. A value of {@code null} removes the existing
- * callback.
+ * callback.
*/
- public void setCallback(@Nullable TvIAppCallback callback) {
- mCallback = callback;
+ public void setCallback(
+ @NonNull TvInteractiveAppCallback callback,
+ @NonNull @CallbackExecutor Executor executor) {
+ synchronized (mCallbackLock) {
+ mCallbackExecutor = executor;
+ mCallback = callback;
+ }
}
+ /**
+ * Clears the callback.
+ */
+ public void clearCallback() {
+ synchronized (mCallbackLock) {
+ mCallback = null;
+ mCallbackExecutor = null;
+ }
+ }
+
+ /** @hide */
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
createSessionMediaView();
}
+ /** @hide */
@Override
protected void onDetachedFromWindow() {
removeSessionMediaView();
super.onDetachedFromWindow();
}
+ /** @hide */
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (DEBUG) {
@@ -175,6 +199,7 @@
}
}
+ /** @hide */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mSurfaceView.measure(widthMeasureSpec, heightMeasureSpec);
@@ -186,6 +211,7 @@
childState << MEASURED_HEIGHT_STATE_SHIFT));
}
+ /** @hide */
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
@@ -216,7 +242,8 @@
}
/**
- * Resets this TvIAppView.
+ * Resets this TvInteractiveAppView.
+ * @hide
*/
public void reset() {
if (DEBUG) Log.d(TAG, "reset()");
@@ -302,6 +329,7 @@
/**
* Dispatches an unhandled input event to the next receiver.
+ * @hide
*/
public boolean dispatchUnhandledInputEvent(@NonNull InputEvent event) {
if (mOnUnhandledInputEventListener != null) {
@@ -314,11 +342,13 @@
/**
* Called when an unhandled input event also has not been handled by the user provided
- * callback. This is the last chance to handle the unhandled input event in the TvIAppView.
+ * callback. This is the last chance to handle the unhandled input event in the
+ * TvInteractiveAppView.
*
* @param event The input event.
* @return If you handled the event, return {@code true}. If you want to allow the event to be
* handled by the next receiver, return {@code false}.
+ * @hide
*/
public boolean onUnhandledInputEvent(@NonNull InputEvent event) {
return false;
@@ -329,6 +359,7 @@
* by the TV Interactive App.
*
* @param listener The callback to be invoked when the unhandled input event is received.
+ * @hide
*/
public void setOnUnhandledInputEventListener(@NonNull OnUnhandledInputEventListener listener) {
mOnUnhandledInputEventListener = listener;
@@ -350,44 +381,60 @@
/**
* Prepares the interactive application.
+ * @hide
*/
- public void prepareIApp(@NonNull String iAppServiceId, int type) {
+ public void prepareInteractiveApp(@NonNull String iAppServiceId, int type) {
// TODO: document and handle the cases that this method is called multiple times.
if (DEBUG) {
- Log.d(TAG, "prepareIApp");
+ Log.d(TAG, "prepareInteractiveApp");
}
mSessionCallback = new MySessionCallback(iAppServiceId, type);
- if (mTvIAppManager != null) {
- mTvIAppManager.createSession(iAppServiceId, type, mSessionCallback, mHandler);
+ if (mTvInteractiveAppManager != null) {
+ mTvInteractiveAppManager.createSession(iAppServiceId, type, mSessionCallback, mHandler);
}
}
/**
* Starts the interactive application.
*/
- public void startIApp() {
+ public void startInteractiveApp() {
if (DEBUG) {
- Log.d(TAG, "startIApp");
+ Log.d(TAG, "startInteractiveApp");
}
if (mSession != null) {
- mSession.startIApp();
+ mSession.startInteractiveApp();
}
}
/**
* Stops the interactive application.
+ * @hide
*/
- public void stopIApp() {
+ public void stopInteractiveApp() {
if (DEBUG) {
- Log.d(TAG, "stopIApp");
+ Log.d(TAG, "stopInteractiveApp");
}
if (mSession != null) {
- mSession.stopIApp();
+ mSession.stopInteractiveApp();
+ }
+ }
+
+ /**
+ * Resets the interactive application.
+ * @hide
+ */
+ public void resetInteractiveApp() {
+ if (DEBUG) {
+ Log.d(TAG, "resetInteractiveApp");
+ }
+ if (mSession != null) {
+ mSession.resetInteractiveApp();
}
}
/**
* Sends current channel URI to related TV interactive app.
+ * @hide
*/
public void sendCurrentChannelUri(Uri channelUri) {
if (DEBUG) {
@@ -400,6 +447,7 @@
/**
* Sends current channel logical channel number (LCN) to related TV interactive app.
+ * @hide
*/
public void sendCurrentChannelLcn(int lcn) {
if (DEBUG) {
@@ -412,6 +460,7 @@
/**
* Sends stream volume to related TV interactive app.
+ * @hide
*/
public void sendStreamVolume(float volume) {
if (DEBUG) {
@@ -424,6 +473,7 @@
/**
* Sends track info list to related TV interactive app.
+ * @hide
*/
public void sendTrackInfoList(List<TvTrackInfo> tracks) {
if (DEBUG) {
@@ -434,6 +484,23 @@
}
}
+ /**
+ * Sends current TV input ID to related TV interactive app.
+ *
+ * @param inputId The current TV input ID whose channel is tuned. {@code null} if no channel is
+ * tuned.
+ * @see android.media.tv.TvInputInfo
+ * @hide
+ */
+ public void sendCurrentTvInputId(@Nullable String inputId) {
+ if (DEBUG) {
+ Log.d(TAG, "sendCurrentTvInputId");
+ }
+ if (mSession != null) {
+ mSession.sendCurrentTvInputId(inputId);
+ }
+ }
+
private void resetInternal() {
mSessionCallback = null;
if (mSession != null) {
@@ -478,16 +545,18 @@
}
}
- public Session getIAppSession() {
+ /** @hide */
+ public Session getInteractiveAppSession() {
return mSession;
}
/**
- * Sets the TvIAppView to receive events from TIS. This method links the session of
+ * Sets the TvInteractiveAppView to receive events from TIS. This method links the session of
* TvIAppManager to TvInputManager session, so the TIAS can get the TIS events.
*
- * @param tvView the TvView to be linked to this TvIAppView via linking of Sessions.
+ * @param tvView the TvView to be linked to this TvInteractiveAppView via linking of Sessions.
* @return to be added
+ * @hide
*/
public int setTvView(@Nullable TvView tvView) {
if (tvView == null) {
@@ -498,7 +567,7 @@
return SET_TVVIEW_FAIL;
}
mSession.setInputSession(inputSession);
- inputSession.setIAppSession(mSession);
+ inputSession.setInteractiveAppSession(mSession);
return SET_TVVIEW_SUCCESS;
}
@@ -506,15 +575,29 @@
if (mSession == null || mSession.getInputSession() == null) {
return UNSET_TVVIEW_FAIL;
}
- mSession.getInputSession().setIAppSession(null);
+ mSession.getInputSession().setInteractiveAppSession(null);
mSession.setInputSession(null);
return UNSET_TVVIEW_SUCCESS;
}
/**
- * Callback used to receive various status updates on the {@link TvIAppView}.
+ * To toggle Digital Teletext Application if there is one in AIT app list.
+ * @param enable
*/
- public abstract static class TvIAppCallback {
+ public void setTeletextAppEnabled(boolean enable) {
+ if (DEBUG) {
+ Log.d(TAG, "setTeletextAppEnabled enable=" + enable);
+ }
+ if (mSession != null) {
+ mSession.setTeletextAppEnabled(enable);
+ }
+ }
+
+ /**
+ * Callback used to receive various status updates on the {@link TvInteractiveAppView}.
+ */
+ public abstract static class TvInteractiveAppCallback {
+ // TODO: unhide the following public APIs
/**
* This is called when a command is requested to be processed by the related TV input.
@@ -522,10 +605,11 @@
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
* @param cmdType type of the command
* @param parameters parameters of the command
+ * @hide
*/
public void onCommandRequest(
@NonNull String iAppServiceId,
- @NonNull @TvIAppService.IAppServiceCommandType String cmdType,
+ @NonNull @TvIAppService.InteractiveAppServiceCommandType String cmdType,
@Nullable Bundle parameters) {
}
@@ -534,6 +618,7 @@
*
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
* @param state current session state.
+ * @hide
*/
public void onSessionStateChanged(@NonNull String iAppServiceId, int state) {
}
@@ -546,55 +631,84 @@
* {@link Session#createBiInteractiveApp(Uri, Bundle)}
* @param biIAppId BI interactive app ID, which can be used to destroy the BI interactive
* app.
+ * @hide
*/
public void onBiInteractiveAppCreated(@NonNull String iAppServiceId, @NonNull Uri biIAppUri,
@Nullable String biIAppId) {
}
/**
+ * This is called when the digital teletext app state is changed.
+ *
+ * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+ * @param state digital teletext app current state.
+ */
+ public void onTeletextAppStateChanged(
+ @NonNull String iAppServiceId, @TvIAppManager.TeletextAppState int state) {
+ }
+
+ /**
* This is called when {@link TvIAppService.Session#SetVideoBounds} is called.
*
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
+ * @hide
*/
public void onSetVideoBounds(@NonNull String iAppServiceId, @NonNull Rect rect) {
}
/**
- * This is called when {@link TvIAppService.Session#RequestCurrentChannelUri} is called.
+ * This is called when {@link TvIAppService.Session#RequestCurrentChannelUri} is
+ * called.
*
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
+ * @hide
*/
public void onRequestCurrentChannelUri(@NonNull String iAppServiceId) {
}
/**
- * This is called when {@link TvIAppService.Session#RequestCurrentChannelLcn} is called.
+ * This is called when {@link TvIAppService.Session#RequestCurrentChannelLcn} is
+ * called.
*
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
+ * @hide
*/
public void onRequestCurrentChannelLcn(@NonNull String iAppServiceId) {
}
/**
- * This is called when {@link TvIAppService.Session#RequestStreamVolume} is called.
+ * This is called when {@link TvIAppService.Session#RequestStreamVolume} is
+ * called.
*
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
+ * @hide
*/
public void onRequestStreamVolume(@NonNull String iAppServiceId) {
}
/**
- * This is called when {@link TvIAppService.Session#RequestTrackInfoList} is called.
+ * This is called when {@link TvIAppService.Session#RequestTrackInfoList} is
+ * called.
+ *
+ * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+ * @hide
+ */
+ public void onRequestTrackInfoList(@NonNull String iAppServiceId) {
+ }
+
+ /**
+ * This is called when {@link TvIAppService.Session#RequestCurrentTvInputId} is called.
*
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
*/
- public void onRequestTrackInfoList(@NonNull String iAppServiceId) {
+ public void onRequestCurrentTvInputId(@NonNull String iAppServiceId) {
}
}
/**
* Interface definition for a callback to be invoked when the unhandled input event is received.
+ * @hide
*/
public interface OnUnhandledInputEventListener {
/**
@@ -685,8 +799,10 @@
}
@Override
- public void onCommandRequest(Session session,
- @TvIAppService.IAppServiceCommandType String cmdType, Bundle parameters) {
+ public void onCommandRequest(
+ Session session,
+ @TvIAppService.InteractiveAppServiceCommandType String cmdType,
+ Bundle parameters) {
if (DEBUG) {
Log.d(TAG, "onCommandRequest (cmdType=" + cmdType + ", parameters="
+ parameters.toString() + ")");
@@ -695,8 +811,16 @@
Log.w(TAG, "onCommandRequest - session not created");
return;
}
- if (mCallback != null) {
- mCallback.onCommandRequest(mIAppServiceId, cmdType, parameters);
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onCommandRequest(mIAppServiceId, cmdType, parameters);
+ }
+ }
+ });
+ }
}
}
@@ -709,8 +833,16 @@
Log.w(TAG, "onSessionStateChanged - session not created");
return;
}
- if (mCallback != null) {
- mCallback.onSessionStateChanged(mIAppServiceId, state);
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onSessionStateChanged(mIAppServiceId, state);
+ }
+ }
+ });
+ }
}
}
@@ -724,8 +856,31 @@
Log.w(TAG, "onBiInteractiveAppCreated - session not created");
return;
}
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onBiInteractiveAppCreated(
+ mIAppServiceId, biIAppUri, biIAppId);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ public void onTeletextAppStateChanged(Session session, int state) {
+ if (DEBUG) {
+ Log.d(TAG, "onTeletextAppStateChanged (state=" + state + ")");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onTeletextAppStateChanged - session not created");
+ return;
+ }
if (mCallback != null) {
- mCallback.onBiInteractiveAppCreated(mIAppServiceId, biIAppUri, biIAppId);
+ mCallback.onTeletextAppStateChanged(mIAppServiceId, state);
}
}
@@ -738,8 +893,16 @@
Log.w(TAG, "onSetVideoBounds - session not created");
return;
}
- if (mCallback != null) {
- mCallback.onSetVideoBounds(mIAppServiceId, rect);
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onSetVideoBounds(mIAppServiceId, rect);
+ }
+ }
+ });
+ }
}
}
@@ -752,8 +915,16 @@
Log.w(TAG, "onRequestCurrentChannelUri - session not created");
return;
}
- if (mCallback != null) {
- mCallback.onRequestCurrentChannelUri(mIAppServiceId);
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestCurrentChannelUri(mIAppServiceId);
+ }
+ }
+ });
+ }
}
}
@@ -766,8 +937,16 @@
Log.w(TAG, "onRequestCurrentChannelLcn - session not created");
return;
}
- if (mCallback != null) {
- mCallback.onRequestCurrentChannelLcn(mIAppServiceId);
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestCurrentChannelLcn(mIAppServiceId);
+ }
+ }
+ });
+ }
}
}
@@ -780,8 +959,16 @@
Log.w(TAG, "onRequestStreamVolume - session not created");
return;
}
- if (mCallback != null) {
- mCallback.onRequestStreamVolume(mIAppServiceId);
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestStreamVolume(mIAppServiceId);
+ }
+ }
+ });
+ }
}
}
@@ -794,8 +981,30 @@
Log.w(TAG, "onRequestTrackInfoList - session not created");
return;
}
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestTrackInfoList(mIAppServiceId);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ public void onRequestCurrentTvInputId(Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestCurrentTvInputId");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestCurrentTvInputId - session not created");
+ return;
+ }
if (mCallback != null) {
- mCallback.onRequestTrackInfoList(mIAppServiceId);
+ mCallback.onRequestCurrentTvInputId(mIAppServiceId);
}
}
}
diff --git a/media/java/android/media/tv/tunerresourcemanager/OWNER b/media/java/android/media/tv/tunerresourcemanager/OWNER
index 76b84d9..0eb1c31 100644
--- a/media/java/android/media/tv/tunerresourcemanager/OWNER
+++ b/media/java/android/media/tv/tunerresourcemanager/OWNER
@@ -1,4 +1,3 @@
-amyjojo@google.com
-nchalko@google.com
quxiangfang@google.com
-shubang@google.com
\ No newline at end of file
+shubang@google.com
+kemiyagi@google.com
\ No newline at end of file
diff --git a/media/tests/TunerTest/OWNERS b/media/tests/TunerTest/OWNERS
index 73ea663..7554889 100644
--- a/media/tests/TunerTest/OWNERS
+++ b/media/tests/TunerTest/OWNERS
@@ -1,4 +1,4 @@
-amyjojo@google.com
-nchalko@google.com
quxiangfang@google.com
shubang@google.com
+hgchen@google.com
+kemiyagi@google.com
\ No newline at end of file
diff --git a/native/android/choreographer.cpp b/native/android/choreographer.cpp
index deee5b1..fbd4b2e 100644
--- a/native/android/choreographer.cpp
+++ b/native/android/choreographer.cpp
@@ -71,11 +71,12 @@
const AChoreographerFrameCallbackData* data, size_t index) {
return AChoreographerFrameCallbackData_routeGetFrameTimelineVsyncId(data, index);
}
-int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTime(
+int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTimeNanos(
const AChoreographerFrameCallbackData* data, size_t index) {
- return AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentTime(data, index);
+ return AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentTimeNanos(data,
+ index);
}
-int64_t AChoreographerFrameCallbackData_getFrameTimelineDeadline(
+int64_t AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos(
const AChoreographerFrameCallbackData* data, size_t index) {
- return AChoreographerFrameCallbackData_routeGetFrameTimelineDeadline(data, index);
+ return AChoreographerFrameCallbackData_routeGetFrameTimelineDeadlineNanos(data, index);
}
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 3c1aa44..35c794e 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -34,8 +34,8 @@
AChoreographerFrameCallbackData_getFrameTimelinesLength; # introduced=33
AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex; # introduced=33
AChoreographerFrameCallbackData_getFrameTimelineVsyncId; # introduced=33
- AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTime; # introduced=33
- AChoreographerFrameCallbackData_getFrameTimelineDeadline; # introduced=33
+ AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTimeNanos; # introduced=33
+ AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos; # introduced=33
AConfiguration_copy;
AConfiguration_delete;
AConfiguration_diff;
diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java
index f684a4d..d33666d 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java
@@ -545,6 +545,15 @@
}
/**
+ * Collects tagged summary results and sets summary enumeration mode.
+ * @throws RemoteException
+ */
+ void startTaggedSummaryEnumeration() throws RemoteException {
+ mSummary = mSession.getTaggedSummaryForAllUid(mTemplate, mStartTimeStamp, mEndTimeStamp);
+ mEnumerationIndex = 0;
+ }
+
+ /**
* Collects history results for uid and resets history enumeration index.
*/
void startHistoryEnumeration(int uid, int tag, int state) {
diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
index 583f7ba..cc7b2a5 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -369,7 +369,7 @@
* @return Statistics which is described above.
* @hide
*/
- @Nullable
+ @NonNull
// @SystemApi(client = MODULE_LIBRARIES)
@WorkerThread
public NetworkStats querySummary(@NonNull NetworkTemplate template, long startTime,
@@ -386,6 +386,39 @@
}
/**
+ * Query tagged network usage statistics summaries.
+ *
+ * The results will only include tagged traffic made by UIDs belonging to the calling user
+ * profile. The results are aggregated over time, so that all buckets will have the same
+ * start and end timestamps as the passed arguments. Not aggregated over state, uid,
+ * default network, metered, or roaming.
+ * This may take a long time, and apps should avoid calling this on their main thread.
+ *
+ * @param template Template used to match networks. See {@link NetworkTemplate}.
+ * @param startTime Start of period, in milliseconds since the Unix epoch, see
+ * {@link System#currentTimeMillis}.
+ * @param endTime End of period, in milliseconds since the Unix epoch, see
+ * {@link System#currentTimeMillis}.
+ * @return Statistics which is described above.
+ * @hide
+ */
+ @NonNull
+ // @SystemApi(client = MODULE_LIBRARIES)
+ @WorkerThread
+ public NetworkStats queryTaggedSummary(@NonNull NetworkTemplate template, long startTime,
+ long endTime) throws SecurityException {
+ try {
+ NetworkStats result =
+ new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
+ result.startTaggedSummaryEnumeration();
+ return result;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return null; // To make the compiler happy.
+ }
+
+ /**
* Query network usage statistics details for a given uid.
* This may take a long time, and apps should avoid calling this on their main thread.
*
diff --git a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl
index dfedf66..babe0bf 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl
@@ -46,6 +46,10 @@
*/
@UnsupportedAppUsage
NetworkStats getSummaryForAllUid(in NetworkTemplate template, long start, long end, boolean includeTags);
+
+ /** Return network layer usage summary per UID for tagged traffic that matches template. */
+ NetworkStats getTaggedSummaryForAllUid(in NetworkTemplate template, long start, long end);
+
/** Return historical network layer stats for specific UID traffic that matches template. */
@UnsupportedAppUsage
NetworkStatsHistory getHistoryForUid(in NetworkTemplate template, int uid, int set, int tag, int fields);
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
index 0d15dff..49aa99b 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
@@ -17,6 +17,7 @@
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
@@ -25,7 +26,6 @@
import android.annotation.TestApi;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.net.annotations.PolicyDirection;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -41,6 +41,8 @@
import java.io.FileDescriptor;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
@@ -88,6 +90,11 @@
@SystemApi(client = MODULE_LIBRARIES)
public static final int DIRECTION_FWD = 2;
+ /** @hide */
+ @IntDef(value = {DIRECTION_IN, DIRECTION_OUT})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PolicyDirection {}
+
/**
* The Security Parameter Index (SPI) 0 indicates an unknown or invalid index.
*
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
index 7935d28..9f9d73f 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
@@ -46,8 +46,6 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FastDataInput;
-import com.android.internal.util.FastDataOutput;
import com.android.internal.util.FileRotator;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.NetworkStatsUtils;
@@ -58,6 +56,7 @@
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
+import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -83,9 +82,6 @@
/** File header magic number: "ANET" */
private static final int FILE_MAGIC = 0x414E4554;
- /** Default buffer size from BufferedInputStream */
- private static final int BUFFER_SIZE = 8192;
-
private static final int VERSION_NETWORK_INIT = 1;
private static final int VERSION_UID_INIT = 1;
@@ -439,8 +435,7 @@
@Override
public void read(InputStream in) throws IOException {
- final FastDataInput dataIn = new FastDataInput(in, BUFFER_SIZE);
- read(dataIn);
+ read((DataInput) new DataInputStream(in));
}
private void read(DataInput in) throws IOException {
@@ -479,9 +474,8 @@
@Override
public void write(OutputStream out) throws IOException {
- final FastDataOutput dataOut = new FastDataOutput(out, BUFFER_SIZE);
- write(dataOut);
- dataOut.flush();
+ write((DataOutput) new DataOutputStream(out));
+ out.flush();
}
private void write(DataOutput out) throws IOException {
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
index 74f3156..efc8c55 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -96,6 +96,7 @@
import android.net.NetworkCapabilities;
import android.net.NetworkIdentity;
import android.net.NetworkIdentitySet;
+import android.net.NetworkPolicyManager;
import android.net.NetworkSpecifier;
import android.net.NetworkStack;
import android.net.NetworkStateSnapshot;
@@ -155,7 +156,6 @@
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.NetworkStatsUtils;
import com.android.net.module.util.PermissionUtils;
-import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import java.io.File;
@@ -209,6 +209,14 @@
private static final String TAG_NETSTATS_ERROR = "netstats_error";
+ /**
+ * EventLog tags used when logging into the event log. Note the values must be sync with
+ * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct
+ * name translation.
+ */
+ private static final int LOG_TAG_NETSTATS_MOBILE_SAMPLE = 51100;
+ private static final int LOG_TAG_NETSTATS_WIFI_SAMPLE = 51101;
+
private final Context mContext;
private final NetworkStatsFactory mStatsFactory;
private final AlarmManager mAlarmManager;
@@ -789,8 +797,18 @@
}
return stats;
} catch (NullPointerException e) {
- // TODO: Track down and fix the cause of this crash and remove this catch block.
- Log.wtf(TAG, "NullPointerException in getSummaryForAllUid", e);
+ throw e;
+ }
+ }
+
+ @Override
+ public NetworkStats getTaggedSummaryForAllUid(
+ NetworkTemplate template, long start, long end) {
+ try {
+ final NetworkStats tagStats = getUidTagComplete()
+ .getSummary(template, start, end, mAccessLevel, mCallingUid);
+ return tagStats;
+ } catch (NullPointerException e) {
throw e;
}
}
@@ -850,7 +868,7 @@
if (LOGD) Log.d(TAG, "Resolving plan for " + template);
final long token = Binder.clearCallingIdentity();
try {
- plan = LocalServices.getService(NetworkPolicyManagerInternal.class)
+ plan = mContext.getSystemService(NetworkPolicyManager.class)
.getSubscriptionPlan(template);
} finally {
Binder.restoreCallingIdentity(token);
@@ -1614,7 +1632,7 @@
xtTotal = mXtRecorder.getTotalSinceBootLocked(template);
uidTotal = mUidRecorder.getTotalSinceBootLocked(template);
- EventLogTags.writeNetstatsMobileSample(
+ EventLog.writeEvent(LOG_TAG_NETSTATS_MOBILE_SAMPLE,
devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets,
xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets,
@@ -1626,7 +1644,7 @@
xtTotal = mXtRecorder.getTotalSinceBootLocked(template);
uidTotal = mUidRecorder.getTotalSinceBootLocked(template);
- EventLogTags.writeNetstatsWifiSample(
+ EventLog.writeEvent(LOG_TAG_NETSTATS_WIFI_SAMPLE,
devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets,
xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets,
@@ -2021,10 +2039,12 @@
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
Objects.requireNonNull(provider, "provider is null");
Objects.requireNonNull(tag, "tag is null");
+ final NetworkPolicyManager netPolicyManager = mContext
+ .getSystemService(NetworkPolicyManager.class);
try {
NetworkStatsProviderCallbackImpl callback = new NetworkStatsProviderCallbackImpl(
tag, provider, mStatsProviderSem, mAlertObserver,
- mStatsProviderCbList);
+ mStatsProviderCbList, netPolicyManager);
mStatsProviderCbList.add(callback);
Log.d(TAG, "registerNetworkStatsProvider from " + callback.mTag + " uid/pid="
+ getCallingUid() + "/" + getCallingPid());
@@ -2066,6 +2086,7 @@
@NonNull private final Semaphore mSemaphore;
@NonNull final AlertObserver mAlertObserver;
@NonNull final CopyOnWriteArrayList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList;
+ @NonNull final NetworkPolicyManager mNetworkPolicyManager;
@NonNull private final Object mProviderStatsLock = new Object();
@@ -2079,7 +2100,8 @@
@NonNull String tag, @NonNull INetworkStatsProvider provider,
@NonNull Semaphore semaphore,
@NonNull AlertObserver alertObserver,
- @NonNull CopyOnWriteArrayList<NetworkStatsProviderCallbackImpl> cbList)
+ @NonNull CopyOnWriteArrayList<NetworkStatsProviderCallbackImpl> cbList,
+ @NonNull NetworkPolicyManager networkPolicyManager)
throws RemoteException {
mTag = tag;
mProvider = provider;
@@ -2087,6 +2109,7 @@
mSemaphore = semaphore;
mAlertObserver = alertObserver;
mStatsProviderCbList = cbList;
+ mNetworkPolicyManager = networkPolicyManager;
}
@NonNull
@@ -2133,8 +2156,7 @@
public void notifyWarningOrLimitReached() {
Log.d(TAG, mTag + ": notifyWarningOrLimitReached");
BinderUtils.withCleanCallingIdentity(() ->
- LocalServices.getService(NetworkPolicyManagerInternal.class)
- .onStatsProviderWarningOrLimitReached(mTag));
+ mNetworkPolicyManager.onStatsProviderWarningOrLimitReached());
}
@Override
diff --git a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
index 7a239af..068074a 100644
--- a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
+++ b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
@@ -44,6 +44,7 @@
import java.io.PrintWriter;
import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
/** Basic fused location provider implementation. */
public class FusedLocationProvider extends LocationProviderBase {
@@ -69,6 +70,12 @@
private final BroadcastReceiver mUserChangeReceiver;
@GuardedBy("mLock")
+ boolean mGpsPresent;
+
+ @GuardedBy("mLock")
+ boolean mNlpPresent;
+
+ @GuardedBy("mLock")
private ProviderRequest mRequest;
@GuardedBy("mLock")
@@ -119,19 +126,28 @@
@Override
public void onFlush(OnFlushCompleteCallback callback) {
- OnFlushCompleteCallback wrapper = new OnFlushCompleteCallback() {
- private int mFlushCount = 2;
+ synchronized (mLock) {
+ AtomicInteger flushCount = new AtomicInteger(0);
+ if (mGpsPresent) {
+ flushCount.incrementAndGet();
+ }
+ if (mNlpPresent) {
+ flushCount.incrementAndGet();
+ }
- @Override
- public void onFlushComplete() {
- if (--mFlushCount == 0) {
+ OnFlushCompleteCallback wrapper = () -> {
+ if (flushCount.decrementAndGet() == 0) {
callback.onFlushComplete();
}
- }
- };
+ };
- mGpsListener.flush(wrapper);
- mNetworkListener.flush(wrapper);
+ if (mGpsPresent) {
+ mGpsListener.flush(wrapper);
+ }
+ if (mNlpPresent) {
+ mNetworkListener.flush(wrapper);
+ }
+ }
}
@Override
@@ -139,9 +155,19 @@
@GuardedBy("mLock")
private void updateRequirementsLocked() {
- long gpsInterval = mRequest.getQuality() < QUALITY_LOW_POWER ? mRequest.getIntervalMillis()
- : INTERVAL_DISABLED;
- long networkInterval = mRequest.getIntervalMillis();
+ // it's possible there might be race conditions on device start where a provider doesn't
+ // appear to be present yet, but once a provider is present it shouldn't go away.
+ if (!mGpsPresent) {
+ mGpsPresent = mLocationManager.hasProvider(GPS_PROVIDER);
+ }
+ if (!mNlpPresent) {
+ mNlpPresent = mLocationManager.hasProvider(NETWORK_PROVIDER);
+ }
+
+ long gpsInterval =
+ mGpsPresent && (!mNlpPresent || mRequest.getQuality() < QUALITY_LOW_POWER)
+ ? mRequest.getIntervalMillis() : INTERVAL_DISABLED;
+ long networkInterval = mNlpPresent ? mRequest.getIntervalMillis() : INTERVAL_DISABLED;
mGpsListener.resetProviderRequest(gpsInterval);
mNetworkListener.resetProviderRequest(networkInterval);
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index b266df5..fcf2282 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -49,6 +49,7 @@
"SettingsLibTwoTargetPreference",
"SettingsLibSettingsTransition",
"SettingsLibActivityEmbedding",
+ "SettingsLibButtonPreference",
],
// ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES
diff --git a/packages/SettingsLib/ButtonPreference/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp
new file mode 100644
index 0000000..39f804f
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/Android.bp
@@ -0,0 +1,23 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+ name: "SettingsLibButtonPreference",
+
+ srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
+
+ static_libs: [
+ "androidx.preference_preference",
+ "SettingsLibSettingsTheme",
+ ],
+
+ sdk_version: "system_current",
+ min_sdk_version: "21",
+}
diff --git a/packages/SettingsLib/ButtonPreference/AndroidManifest.xml b/packages/SettingsLib/ButtonPreference/AndroidManifest.xml
new file mode 100644
index 0000000..2d35c33
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.widget">
+
+ <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_background_material.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_background_material.xml
new file mode 100644
index 0000000..51ca4ac
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_background_material.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<!-- Used for the text of a bordered colored button. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="?android:attr/disabledAlpha"
+ android:color="?android:attr/colorButtonNormal" />
+ <item android:color="?android:attr/colorAccent" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_text_material.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_text_material.xml
new file mode 100644
index 0000000..8dca4db
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_text_material.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<!-- Used for the text of a bordered colored button. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="?android:attr/disabledAlpha"
+ android:color="?android:attr/textColorPrimary" />
+ <item android:color="?android:attr/textColorPrimaryInverse" />
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_dark.xml
similarity index 67%
copy from libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml
copy to packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_dark.xml
index 70f553b..1e930ea 100644
--- a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml
+++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_dark.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
+<!--
+ Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,8 +14,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
- android:propertyName="alpha"
- android:valueTo="0"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:duration="100" />
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:alpha="@dimen/settingslib_highlight_alpha_material_dark"
+ android:color="@android:color/white" />
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_light.xml
similarity index 67%
copy from libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml
copy to packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_light.xml
index 70f553b..378fc16 100644
--- a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml
+++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_light.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
+<!--
+ Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,8 +14,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
- android:propertyName="alpha"
- android:valueTo="0"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:duration="100" />
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:alpha="@dimen/settingslib_highlight_alpha_material_light"
+ android:color="@android:color/black" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_btn_colored_material.xml b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_btn_colored_material.xml
new file mode 100644
index 0000000..bb0597d
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_btn_colored_material.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetLeft="4dp"
+ android:insetTop="6dp"
+ android:insetRight="4dp"
+ android:insetBottom="6dp">
+ <ripple android:color="?attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle"
+ android:tint="@color/settingslib_btn_colored_background_material">
+ <corners android:radius="2dp" />
+ <solid android:color="@android:color/white" />
+ <padding android:left="8dp"
+ android:top="4dp"
+ android:right="8dp"
+ android:bottom="4dp" />
+ </shape>
+ </item>
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml
new file mode 100644
index 0000000..1ff0990
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <Button
+ android:id="@+id/settingslib_button"
+ android:drawablePadding="8dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:layout_marginStart="-4dp"
+ style="@style/SettingsLibButtonStyle" />
+
+</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml b/packages/SettingsLib/ButtonPreference/res/values-night/colors.xml
similarity index 67%
copy from libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml
copy to packages/SettingsLib/ButtonPreference/res/values-night/colors.xml
index 70f553b..6be7b93 100644
--- a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml
+++ b/packages/SettingsLib/ButtonPreference/res/values-night/colors.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
+<!--
+ Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,8 +14,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
- android:propertyName="alpha"
- android:valueTo="0"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:duration="100" />
+
+<resources>
+ <!-- Material inverse ripple color, useful for inverted backgrounds. -->
+ <color name="settingslib_button_ripple">@color/settingslib_ripple_material_light</color>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/values-v23/styles.xml b/packages/SettingsLib/ButtonPreference/res/values-v23/styles.xml
new file mode 100644
index 0000000..202645f
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values-v23/styles.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<resources>
+
+ <style name="SettingsLibButtonStyle" parent="android:Widget.Material.Button.Colored">
+ <item name="android:theme">@style/SettingsLibRoundedCornerThemeOverlay</item>
+ <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">@color/settingslib_btn_colored_text_material</item>
+ </style>
+</resources>
diff --git a/packages/SettingsLib/ButtonPreference/res/values-v28/styles.xml b/packages/SettingsLib/ButtonPreference/res/values-v28/styles.xml
new file mode 100644
index 0000000..d8c6ac3
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values-v28/styles.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<resources>
+
+ <style name="SettingsLibButtonStyle" parent="android:Widget.DeviceDefault.Button.Colored">
+ <item name="android:theme">@style/SettingsLibRoundedCornerThemeOverlay</item>
+ <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
+ <item name="android:textSize">16sp</item>
+ </style>
+</resources>
diff --git a/packages/SettingsLib/ButtonPreference/res/values-v31/styles.xml b/packages/SettingsLib/ButtonPreference/res/values-v31/styles.xml
new file mode 100644
index 0000000..12dcbbf
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values-v31/styles.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<resources>
+
+ <style name="SettingsLibRoundedCornerThemeOverlay">
+ <item name="android:buttonCornerRadius">24dp</item>
+ <item name="android:paddingStart">16dp</item>
+ <item name="android:paddingEnd">16dp</item>
+ <item name="android:colorControlHighlight">@color/settingslib_button_ripple</item>
+ </style>
+</resources>
diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml b/packages/SettingsLib/ButtonPreference/res/values/attrs.xml
similarity index 67%
copy from libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml
copy to packages/SettingsLib/ButtonPreference/res/values/attrs.xml
index 70f553b..9a4312a 100644
--- a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml
+++ b/packages/SettingsLib/ButtonPreference/res/values/attrs.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
+<!--
+ Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,8 +14,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
- android:propertyName="alpha"
- android:valueTo="0"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:duration="100" />
+
+<resources>
+ <declare-styleable name="ButtonPreference">
+ <attr name="android:gravity" />
+ </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml b/packages/SettingsLib/ButtonPreference/res/values/colors.xml
similarity index 67%
rename from libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml
rename to packages/SettingsLib/ButtonPreference/res/values/colors.xml
index 70f553b..45baeeb 100644
--- a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml
+++ b/packages/SettingsLib/ButtonPreference/res/values/colors.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
+<!--
+ Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,8 +14,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
- android:propertyName="alpha"
- android:valueTo="0"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:duration="100" />
+
+<resources>
+ <!-- Material inverse ripple color, useful for inverted backgrounds. -->
+ <color name="settingslib_button_ripple">@color/settingslib_ripple_material_dark</color>
+</resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml b/packages/SettingsLib/ButtonPreference/res/values/dimens.xml
similarity index 67%
copy from libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml
copy to packages/SettingsLib/ButtonPreference/res/values/dimens.xml
index 70f553b..3d7831e 100644
--- a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml
+++ b/packages/SettingsLib/ButtonPreference/res/values/dimens.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
+<!--
+ Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,8 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
- android:propertyName="alpha"
- android:valueTo="0"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:duration="100" />
+<resources>
+ <item name="settingslib_highlight_alpha_material_light" format="float" type="dimen">0.10</item>
+ <item name="settingslib_highlight_alpha_material_dark" format="float" type="dimen">0.10</item>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/values/styles.xml b/packages/SettingsLib/ButtonPreference/res/values/styles.xml
new file mode 100644
index 0000000..3963732
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values/styles.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+
+<resources>
+
+ <style name="SettingsLibRoundedCornerThemeOverlay">
+ <item name="android:paddingStart">16dp</item>
+ <item name="android:paddingEnd">16dp</item>
+ <item name="android:colorControlHighlight">@color/settingslib_button_ripple</item>
+ </style>
+
+ <style name="SettingsLibButtonStyle" parent="android:Widget.Material.Button">
+ <item name="android:theme">@style/SettingsLibRoundedCornerThemeOverlay</item>
+ <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">@color/settingslib_btn_colored_text_material</item>
+ <item name="android:background">@drawable/settingslib_btn_colored_material</item>
+ </style>
+</resources>
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
new file mode 100644
index 0000000..56d2967
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
@@ -0,0 +1,197 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import androidx.annotation.GravityInt;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+/**
+ * A preference handled a button
+ */
+public class ButtonPreference extends Preference {
+
+ private static final int ICON_SIZE = 24;
+
+ private View.OnClickListener mClickListener;
+ private Button mButton;
+ private CharSequence mTitle;
+ private Drawable mIcon;
+ @GravityInt
+ private int mGravity;
+
+ /**
+ * Constructs a new LayoutPreference with the given context's theme, the supplied
+ * attribute set, and default style attribute.
+ *
+ * @param context The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default
+ * values for the view. Can be 0 to not look for
+ * defaults.
+ */
+ public ButtonPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context, attrs, defStyleAttr);
+ }
+
+ /**
+ * Constructs a new LayoutPreference with the given context's theme and the supplied
+ * attribute set.
+ *
+ * @param context The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ */
+ public ButtonPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, 0 /* defStyleAttr */);
+ }
+
+ /**
+ * Constructs a new LayoutPreference with the given context's theme and a customized view.
+ *
+ * @param context The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ */
+ public ButtonPreference(Context context) {
+ this(context, null);
+ }
+
+ private void init(Context context, AttributeSet attrs, int defStyleAttr) {
+ setLayoutResource(R.layout.settingslib_button_layout);
+
+ if (attrs != null) {
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ androidx.preference.R.styleable.Preference, defStyleAttr,
+ 0 /*defStyleRes*/);
+ mTitle = a.getText(
+ androidx.preference.R.styleable.Preference_android_title);
+ mIcon = a.getDrawable(
+ androidx.preference.R.styleable.Preference_android_icon);
+ a.recycle();
+
+ a = context.obtainStyledAttributes(attrs,
+ R.styleable.ButtonPreference, defStyleAttr,
+ 0 /*defStyleRes*/);
+ mGravity = a.getInt(R.styleable.ButtonPreference_android_gravity, Gravity.START);
+ a.recycle();
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ mButton = (Button) holder.findViewById(R.id.settingslib_button);
+ setTitle(mTitle);
+ setIcon(mIcon);
+ setGravity(mGravity);
+ setOnClickListener(mClickListener);
+
+ if (mButton != null) {
+ final boolean selectable = isSelectable();
+ mButton.setFocusable(selectable);
+ mButton.setClickable(selectable);
+
+ mButton.setEnabled(isEnabled());
+ }
+
+ holder.setDividerAllowedAbove(false);
+ holder.setDividerAllowedBelow(false);
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ mTitle = title;
+ if (mButton != null) {
+ mButton.setText(title);
+ }
+ }
+
+ @Override
+ public void setIcon(Drawable icon) {
+ mIcon = icon;
+ if (mButton == null || icon == null) {
+ return;
+ }
+ //get pixel from dp
+ int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ICON_SIZE,
+ getContext().getResources().getDisplayMetrics());
+ icon.setBounds(0, 0, size, size);
+
+ //set drawableStart
+ mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null/* top */, null/* end */,
+ null/* bottom */);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ if (mButton != null) {
+ mButton.setEnabled(enabled);
+ }
+ }
+
+ /**
+ * Return Button
+ */
+ public Button getButton() {
+ return mButton;
+ }
+
+
+ /**
+ * Set a listener for button click
+ */
+ public void setOnClickListener(View.OnClickListener listener) {
+ mClickListener = listener;
+ if (mButton != null) {
+ mButton.setOnClickListener(listener);
+ }
+ }
+
+ /**
+ * Set the gravity of button
+ *
+ * @param gravity The {@link Gravity} supported CENTER_HORIZONTAL
+ * and the default value is START
+ */
+ public void setGravity(@GravityInt int gravity) {
+ if (gravity == Gravity.CENTER_HORIZONTAL || gravity == Gravity.CENTER_VERTICAL
+ || gravity == Gravity.CENTER) {
+ mGravity = Gravity.CENTER_HORIZONTAL;
+ } else {
+ mGravity = Gravity.START;
+ }
+
+ if (mButton != null) {
+ LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mButton.getLayoutParams();
+ lp.gravity = mGravity;
+ mButton.setLayoutParams(lp);
+ }
+ }
+}
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 4cdcca7..5222d8a 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -250,7 +250,7 @@
<string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Konektatuta daudenak"</string>
<string name="adb_wireless_device_details_title" msgid="7129369670526565786">"Gailuaren xehetasunak"</string>
<string name="adb_device_forget" msgid="193072400783068417">"Ahaztu"</string>
- <string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"Gailuaren erreferentzia-gako digitala: <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string>
+ <string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"Gailuaren aztarna digitala: <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string>
<string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"Ezin izan da konektatu"</string>
<string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Ziurtatu <xliff:g id="DEVICE_NAME">%1$s</xliff:g> sare berera konektatuta dagoela"</string>
<string name="adb_pairing_device_dialog_title" msgid="7141739231018530210">"Parekatu gailuarekin"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 9490ede..4103a9f 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -209,7 +209,7 @@
<string name="tts_status_checking" msgid="8026559918948285013">"Tarkistetaan…"</string>
<string name="tts_engine_settings_title" msgid="7849477533103566291">"Asetukset: <xliff:g id="TTS_ENGINE_NAME">%s</xliff:g>"</string>
<string name="tts_engine_settings_button" msgid="477155276199968948">"Käynnistä moottorin asetukset"</string>
- <string name="tts_engine_preference_section_title" msgid="3861562305498624904">"Ensisijainen kone"</string>
+ <string name="tts_engine_preference_section_title" msgid="3861562305498624904">"Ensisijainen moottori"</string>
<string name="tts_general_section_title" msgid="8919671529502364567">"Yleiset"</string>
<string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"Palauta äänenkorkeus"</string>
<string name="tts_reset_speech_pitch_summary" msgid="6822904157021406449">"Palauta tekstin lukemisen oletusäänenkorkeus"</string>
@@ -220,8 +220,8 @@
<item msgid="1158955023692670059">"Nopea"</item>
<item msgid="5664310435707146591">"Nopeampi"</item>
<item msgid="5491266922147715962">"Hyvin nopea"</item>
- <item msgid="7659240015901486196">"Nopea"</item>
- <item msgid="7147051179282410945">"Erittäin nopea"</item>
+ <item msgid="7659240015901486196">"Vauhdikas"</item>
+ <item msgid="7147051179282410945">"Erittäin vauhdikas"</item>
<item msgid="581904787661470707">"Nopein"</item>
</string-array>
<string name="choose_profile" msgid="343803890897657450">"Valitse profiili"</string>
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ButtonPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ButtonPreferenceTest.java
new file mode 100644
index 0000000..625b214
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ButtonPreferenceTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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.widget;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import androidx.preference.PreferenceViewHolder;
+import androidx.preference.R;
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.shadows.ShadowDrawable;
+
+@RunWith(RobolectricTestRunner.class)
+public class ButtonPreferenceTest {
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private ButtonPreference mPreference;
+ private PreferenceViewHolder mHolder;
+
+ private boolean mClickListenerCalled;
+ private final View.OnClickListener mClickListener = v -> mClickListenerCalled = true;
+
+ @Before
+ public void setUp() {
+ mClickListenerCalled = false;
+ mPreference = new ButtonPreference(mContext);
+ setUpViewHolder();
+ }
+
+ @Test
+ public void onBindViewHolder_whenTitleSet_shouldSetButtonText() {
+ final String testTitle = "Test title";
+ mPreference.setTitle(testTitle);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ assertThat(button.getText().toString()).isEqualTo(testTitle);
+ }
+
+ @Test
+ public void onBindViewHolder_whenIconSet_shouldSetIcon() {
+ mPreference.setIcon(R.drawable.settingslib_ic_cross);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ final Drawable icon = button.getCompoundDrawablesRelative()[0];
+ final ShadowDrawable shadowDrawable = shadowOf(icon);
+ assertThat(shadowDrawable.getCreatedFromResId()).isEqualTo(R.drawable.settingslib_ic_cross);
+ }
+
+ @Test
+ public void onBindViewHolder_setEnable_shouldSetButtonEnabled() {
+ mPreference.setEnabled(true);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ assertThat(button.isEnabled()).isTrue();
+ }
+
+ @Test
+ public void onBindViewHolder_setDisable_shouldSetButtonDisabled() {
+ mPreference.setEnabled(false);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ assertThat(button.isEnabled()).isFalse();
+ }
+
+ @Test
+ public void onBindViewHolder_default_shouldReturnButtonGravityStart() {
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) button.getLayoutParams();
+ assertThat(lp.gravity).isEqualTo(Gravity.START);
+ }
+
+ @Test
+ public void onBindViewHolder_setGravityStart_shouldReturnButtonGravityStart() {
+ mPreference.setGravity(Gravity.START);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) button.getLayoutParams();
+ assertThat(lp.gravity).isEqualTo(Gravity.START);
+ }
+
+ @Test
+ public void onBindViewHolder_setGravityCenter_shouldReturnButtonGravityCenterHorizontal() {
+ mPreference.setGravity(Gravity.CENTER_HORIZONTAL);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) button.getLayoutParams();
+ assertThat(lp.gravity).isEqualTo(Gravity.CENTER_HORIZONTAL);
+
+ mPreference.setGravity(Gravity.CENTER_VERTICAL);
+ mPreference.onBindViewHolder(mHolder);
+ assertThat(lp.gravity).isEqualTo(Gravity.CENTER_HORIZONTAL);
+
+ mPreference.setGravity(Gravity.CENTER);
+ mPreference.onBindViewHolder(mHolder);
+ assertThat(lp.gravity).isEqualTo(Gravity.CENTER_HORIZONTAL);
+ }
+
+ @Test
+ public void onBindViewHolder_setUnsupportedGravity_shouldReturnButtonGravityStart() {
+ mPreference.setGravity(Gravity.END);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) button.getLayoutParams();
+ assertThat(lp.gravity).isEqualTo(Gravity.START);
+ }
+
+ @Test
+ public void setButtonOnClickListener_setsClickListener() {
+ mPreference.setOnClickListener(mClickListener);
+
+ mPreference.onBindViewHolder(mHolder);
+ final Button button = mPreference.getButton();
+ button.callOnClick();
+
+ assertThat(mClickListenerCalled).isTrue();
+ }
+
+ private void setUpViewHolder() {
+ final View rootView =
+ View.inflate(mContext, mPreference.getLayoutResource(), null /* parent */);
+ mHolder = PreferenceViewHolder.createInstanceForTests(rootView);
+ }
+}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 0c70821..8546b16 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -341,6 +341,9 @@
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.SET_WALLPAPER_COMPONENT" />
+ <!-- Permission needed to test wallpaper dimming -->
+ <uses-permission android:name="android.permission.SET_WALLPAPER_DIM_AMOUNT" />
+
<!-- Permission required to test ContentResolver caching. -->
<uses-permission android:name="android.permission.CACHE_CONTENT" />
@@ -350,6 +353,9 @@
<!-- Permission required for CTS test - CrossProfileAppsHostSideTest -->
<uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
+ <!-- Permission required for CTS test - CrossProfileAppsHostSideTest -->
+ <uses-permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"/>
+
<!-- permissions required for CTS test - PhoneStateListenerTest -->
<uses-permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e907efb..e9e85f1 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -299,6 +299,9 @@
<uses-permission android:name="android.permission.BIND_APPWIDGET" />
+ <!-- For clipboard overlay -->
+ <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
+
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
@@ -348,6 +351,7 @@
<!-- started from PhoneWindowManager
TODO: Should have an android:permission attribute -->
<service android:name=".screenshot.TakeScreenshotService"
+ android:permission="com.android.systemui.permission.SELF"
android:process=":screenshot"
android:exported="false" />
@@ -760,6 +764,12 @@
</intent-filter>
</activity>
+ <activity android:name=".clipboardoverlay.EditTextActivity"
+ android:theme="@style/EditTextActivity"
+ android:exported="false"
+ android:excludeFromRecents="true"
+ />
+
<activity android:name=".controls.management.ControlsProviderSelectorActivity"
android:label="@string/controls_providers_title"
android:theme="@style/Theme.ControlsManagement"
@@ -845,6 +855,12 @@
android:singleUser="true"
android:permission="android.permission.BIND_DREAM_SERVICE" />
+ <!-- Service for external clients to do media transfer -->
+ <!-- TODO(b/203800643): Export and guard with a permission. -->
+ <service
+ android:name=".media.taptotransfer.sender.MediaTttSenderService"
+ />
+
<receiver
android:name=".tuner.TunerService$ClearReceiver"
android:exported="false">
diff --git a/packages/SystemUI/res/drawable/ic_baseline_devices_24.xml b/packages/SystemUI/res/drawable/ic_baseline_devices_24.xml
new file mode 100644
index 0000000..61c32b2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_baseline_devices_24.xml
@@ -0,0 +1,27 @@
+<!--
+ 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
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M4,6h18L22,4L4,4c-1.1,0 -2,0.9 -2,2v11L0,17v3h14v-3L4,17L4,6zM23,8h-6c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h6c0.55,0 1,-0.45 1,-1L24,9c0,-0.55 -0.45,-1 -1,-1zM22,17h-4v-7h4v7z"/>
+</vector>
+
diff --git a/packages/SystemUI/res/layout/clipboard_content_preview.xml b/packages/SystemUI/res/layout/clipboard_content_preview.xml
new file mode 100644
index 0000000..7317a94
--- /dev/null
+++ b/packages/SystemUI/res/layout/clipboard_content_preview.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/preview_border"
+ android:elevation="9dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/screenshot_offset_x"
+ android:layout_marginBottom="@dimen/screenshot_offset_y"
+ android:layout_gravity="bottom|start"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ android:clipToPadding="false"
+ android:clipChildren="false"
+ android:padding="4dp"
+ android:background="@drawable/screenshot_border"
+ >
+ <FrameLayout
+ android:elevation="0dp"
+ android:background="@drawable/screenshot_preview_background"
+ android:clipChildren="true"
+ android:clipToOutline="true"
+ android:clipToPadding="true"
+ android:layout_width="@dimen/screenshot_x_scale"
+ android:layout_height="wrap_content">
+ <TextView android:id="@+id/text_preview"
+ android:textFontWeight="500"
+ android:padding="8dp"
+ android:gravity="center|start"
+ android:ellipsize="end"
+ android:autoSizeTextType="uniform"
+ android:autoSizeMinTextSize="10sp"
+ android:autoSizeMaxTextSize="200sp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:layout_width="@dimen/screenshot_x_scale"
+ android:layout_height="@dimen/screenshot_x_scale"/>
+ <ImageView
+ android:id="@+id/image_preview"
+ android:scaleType="fitCenter"
+ android:adjustViewBounds="true"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ </FrameLayout>
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml
new file mode 100644
index 0000000..8f6753a
--- /dev/null
+++ b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ android:id="@+id/copy_button"
+ style="@android:style/Widget.DeviceDefault.Button.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:layout_marginTop="8dp"
+ android:text="@string/clipboard_edit_text_copy"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <TextView
+ android:id="@+id/attribution"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ app:layout_constraintStart_toStartOf="@+id/copy_button"
+ app:layout_constraintTop_toBottomOf="@+id/copy_button" />
+
+ <ImageButton
+ android:id="@+id/share"
+ style="@android:style/Widget.Material.Button.Borderless"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginEnd="8dp"
+ android:padding="12dp"
+ android:scaleType="fitCenter"
+ android:contentDescription="@*android:string/share"
+ android:tooltipText="@*android:string/share"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="@+id/copy_button"
+ android:src="@drawable/ic_screenshot_share" />
+
+ <ScrollView
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginTop="8dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintStart_toStartOf="@+id/copy_button"
+ app:layout_constraintTop_toBottomOf="@+id/attribution">
+
+ <EditText
+ android:id="@+id/edit_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="start|top"
+ android:textSize="24sp" />
+ </ScrollView>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
new file mode 100644
index 0000000..76280d8
--- /dev/null
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<com.android.systemui.clipboardoverlay.DraggableConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_gravity="bottom"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <ImageView
+ android:id="@+id/actions_container_background"
+ android:visibility="gone"
+ android:layout_height="0dp"
+ android:layout_width="0dp"
+ android:elevation="1dp"
+ android:background="@drawable/action_chip_container_background"
+ android:layout_marginStart="@dimen/screenshot_action_container_margin_horizontal"
+ app:layout_constraintBottom_toBottomOf="@+id/actions_container"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="@+id/actions_container"
+ app:layout_constraintEnd_toEndOf="@+id/actions_container"/>
+ <HorizontalScrollView
+ android:id="@+id/actions_container"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/screenshot_action_container_margin_horizontal"
+ android:paddingEnd="@dimen/screenshot_action_container_padding_right"
+ android:paddingVertical="@dimen/screenshot_action_container_padding_vertical"
+ android:elevation="1dp"
+ android:scrollbars="none"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintWidth_percent="1.0"
+ app:layout_constraintWidth_max="wrap"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/preview_border"
+ app:layout_constraintEnd_toEndOf="parent">
+ <LinearLayout
+ android:id="@+id/actions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <include layout="@layout/screenshot_action_chip"
+ android:id="@+id/remote_copy_chip"/>
+ <include layout="@layout/screenshot_action_chip"
+ android:id="@+id/edit_chip"/>
+ </LinearLayout>
+ </HorizontalScrollView>
+ <include layout="@layout/clipboard_content_preview" />
+</com.android.systemui.clipboardoverlay.DraggableConstraintLayout>
diff --git a/packages/SystemUI/res/layout/contaminant_dialog.xml b/packages/SystemUI/res/layout/contaminant_dialog.xml
index ea6d900..5f8c305 100644
--- a/packages/SystemUI/res/layout/contaminant_dialog.xml
+++ b/packages/SystemUI/res/layout/contaminant_dialog.xml
@@ -63,7 +63,8 @@
<TextView
android:id="@+id/learnMore"
- style="@style/USBContaminant.UserAction" />
+ style="@style/USBContaminant.UserAction"
+ android:visibility="gone" />
<View
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index cb963e6..1f815b7 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -38,6 +38,15 @@
<item name="android:textColorSecondary">?android:attr/textColorPrimaryInverse</item>
</style>
+ <!-- Clipboard overlay's edit text activity. -->
+ <style name="EditTextActivity" parent="@android:style/Theme.DeviceDefault.DayNight">
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowLightStatusBar">false</item>
+ <item name="android:windowLightNavigationBar">false</item>
+ <item name="android:navigationBarColor">?android:attr/colorBackgroundFloating</item>
+ <item name="android:textColorSecondary">?android:attr/textColorPrimaryInverse</item>
+ </style>
+
<style name="Screenshot" parent="@android:style/Theme.DeviceDefault.DayNight">
<item name="android:textColorPrimary">?android:attr/textColorPrimaryInverse</item>
</style>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 56517cc..9c35fea 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -316,6 +316,7 @@
<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
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7600eb1..08fb2c6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2152,8 +2152,8 @@
<!--- ****** Media tap-to-transfer ****** -->
<!-- Text for a button to undo the media transfer. [CHAR LIMIT=20] -->
<string name="media_transfer_undo">Undo</string>
- <!-- Text to ask the user to move their device closer to a different device (deviceName) in order to play music on the different device. [CHAR LIMIT=75] -->
- <string name="media_move_closer_to_transfer">Move closer to play on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
+ <!-- Text to ask the user to move their device closer to a different device (deviceName) in order to play media on the different device. [CHAR LIMIT=75] -->
+ <string name="media_move_closer_to_start_cast">Move closer to play on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
<!-- Text informing the user that their media is now playing on a different device (deviceName). [CHAR LIMIT=50] -->
<string name="media_transfer_playing">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
@@ -2349,4 +2349,9 @@
<string name="fgs_manager_dialog_title">Apps running in the background</string>
<!-- Label of the button to stop the app from running in the background [CHAR LIMIT=12]-->
<string name="fgs_manager_app_item_stop_button_label">Stop</string>
+
+ <!-- Label for button to copy edited text back to the clipboard [CHAR LIMIT=20] -->
+ <string name="clipboard_edit_text_copy">Copy</string>
+ <!-- Text informing user that content has been copied to the system clipboard [CHAR LIMIT=NONE] -->
+ <string name="clipboard_overlay_text_copied">Copied</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 18bfb52..1b52811 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -666,6 +666,14 @@
<style name="Screenshot" parent="@android:style/Theme.DeviceDefault.DayNight"/>
+ <!-- Clipboard overlay's edit text activity. -->
+ <style name="EditTextActivity" parent="@android:style/Theme.DeviceDefault.DayNight">
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowLightStatusBar">true</item>
+ <item name="android:windowLightNavigationBar">true</item>
+ <item name="android:navigationBarColor">?android:attr/colorBackgroundFloating</item>
+ </style>
+
<!-- Privacy dialog -->
<style name="PrivacyDialog" parent="Theme.SystemUI.QuickSettings.Dialog">
<item name="android:windowIsTranslucent">true</item>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index d172006..3cf5bc1 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -49,9 +49,12 @@
"PluginCoreLib",
"androidx.dynamicanimation_dynamicanimation",
"androidx.concurrent_concurrent-futures",
+ "dagger2",
+ "jsr330",
],
java_version: "1.8",
min_sdk_version: "current",
+ plugins: ["dagger2-compiler"],
}
java_library {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Main.java b/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Main.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Main.java
rename to packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Main.java
diff --git a/media/java/android/media/tv/interactive/TvIAppInfo.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
similarity index 81%
copy from media/java/android/media/tv/interactive/TvIAppInfo.aidl
copy to packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
index 6041460..861a4ed 100644
--- a/media/java/android/media/tv/interactive/TvIAppInfo.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.media.tv.interactive;
+package com.android.systemui.shared.mediattt;
-parcelable TvIAppInfo;
\ No newline at end of file
+parcelable DeviceInfo;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt
new file mode 100644
index 0000000..d41aaf3
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.shared.mediattt
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * Represents a device that can send or receive media. Includes any device information necessary for
+ * SysUI to display an informative chip to the user.
+ */
+class DeviceInfo(val name: String) : Parcelable {
+ constructor(parcel: Parcel) : this(parcel.readString())
+
+ override fun writeToParcel(dest: Parcel?, flags: Int) {
+ dest?.writeString(name)
+ }
+
+ override fun describeContents() = 0
+
+ override fun toString() = "name: $name"
+
+ companion object CREATOR : Parcelable.Creator<DeviceInfo> {
+ override fun createFromParcel(parcel: Parcel) = DeviceInfo(parcel)
+ override fun newArray(size: Int) = arrayOfNulls<DeviceInfo?>(size)
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl
new file mode 100644
index 0000000..484791d
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl
@@ -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.shared.mediattt;
+
+import android.media.MediaRoute2Info;
+import com.android.systemui.shared.mediattt.DeviceInfo;
+
+/**
+ * A callback interface that can be invoked to trigger media transfer events on System UI.
+ *
+ * This interface is for the *sender* device, which is the device currently playing media. This
+ * sender device can transfer the media to a different device, called the receiver.
+ *
+ * System UI will implement this interface and other services will invoke it.
+ */
+interface IDeviceSenderCallback {
+ /**
+ * Invoke to notify System UI that this device (the sender) is close to a receiver device, so
+ * the user can potentially *start* a cast to the receiver device if the user moves their device
+ * a bit closer.
+ *
+ * Important notes:
+ * - When this callback triggers, the device is close enough to inform the user that
+ * transferring is an option, but the device is *not* close enough to actually initiate a
+ * transfer yet.
+ * - This callback is for *starting* a cast. It should be used when this device is currently
+ * playing media locally and the media should be transferred to be played on the receiver
+ * device instead.
+ */
+ oneway void closeToReceiverToStartCast(
+ in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index d182399..54c798c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -60,8 +60,6 @@
// See IRecentTasks.aidl
public static final String KEY_EXTRA_RECENT_TASKS = "recent_tasks";
- public static final String NAV_BAR_MODE_2BUTTON_OVERLAY =
- WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
public static final String NAV_BAR_MODE_3BUTTON_OVERLAY =
WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
public static final String NAV_BAR_MODE_GESTURAL_OVERLAY =
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
index 8d26ddd..618d2d2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
@@ -183,8 +183,8 @@
}
// Make wallpaper visible immediately since launcher apparently won't do this.
for (int i = wallpapersCompat.length - 1; i >= 0; --i) {
- t.show(wallpapersCompat[i].leash.getSurfaceControl());
- t.setAlpha(wallpapersCompat[i].leash.getSurfaceControl(), 1.f);
+ t.show(wallpapersCompat[i].leash);
+ t.setAlpha(wallpapersCompat[i].leash, 1.f);
}
} else {
if (launcherTask != null) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 4ec65d8..72e3e18 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -57,7 +57,7 @@
public final int activityType;
public final int taskId;
- public final SurfaceControlCompat leash;
+ public final SurfaceControl leash;
public final boolean isTranslucent;
public final Rect clipRect;
public final int prefixOrderIndex;
@@ -82,7 +82,7 @@
public RemoteAnimationTargetCompat(RemoteAnimationTarget app) {
taskId = app.taskId;
mode = app.mode;
- leash = new SurfaceControlCompat(app.leash);
+ leash = app.leash;
isTranslucent = app.isTranslucent;
clipRect = app.clipRect;
position = app.position;
@@ -119,7 +119,7 @@
public RemoteAnimationTarget unwrap() {
return new RemoteAnimationTarget(
- taskId, mode, leash.getSurfaceControl(), isTranslucent, clipRect, contentInsets,
+ taskId, mode, leash, isTranslucent, clipRect, contentInsets,
prefixOrderIndex, position, localBounds, screenSpaceBounds, windowConfiguration,
isNotInRecents, mStartLeash, startBounds, taskInfo, allowEnterPip, windowType
);
@@ -211,7 +211,7 @@
mode = newModeToLegacyMode(change.getMode());
// TODO: once we can properly sync transactions across process, then get rid of this leash.
- leash = new SurfaceControlCompat(createLeash(info, change, order, t));
+ leash = createLeash(info, change, order, t);
isTranslucent = (change.getFlags() & TransitionInfo.FLAG_TRANSLUCENT) != 0
|| (change.getFlags() & TransitionInfo.FLAG_SHOW_WALLPAPER) != 0;
@@ -273,7 +273,7 @@
info.getChanges().size() - i, info, t));
if (leashMap == null) continue;
leashMap.put(info.getChanges().get(i).getLeash(),
- out.get(out.size() - 1).leash.mSurfaceControl);
+ out.get(out.size() - 1).leash);
}
return out.toArray(new RemoteAnimationTargetCompat[out.size()]);
}
@@ -282,8 +282,8 @@
* @see SurfaceControl#release()
*/
public void release() {
- if (leash.mSurfaceControl != null) {
- leash.mSurfaceControl.release();
+ if (leash != null) {
+ leash.release();
}
if (mStartLeash != null) {
mStartLeash.release();
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 2d5080e..2ae32c7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -149,7 +149,7 @@
}
// Also make all the wallpapers opaque since we want the visible from the start
for (int i = wallpapers.length - 1; i >= 0; --i) {
- t.setAlpha(wallpapers[i].leash.mSurfaceControl, 1);
+ t.setAlpha(wallpapers[i].leash, 1);
}
t.apply();
mRecentsSession.setup(controller, info, finishedCallback, pausingTasks, pipTask,
@@ -267,10 +267,10 @@
// We are receiving new opening tasks, so convert to onTasksAppeared.
final RemoteAnimationTargetCompat target = new RemoteAnimationTargetCompat(
openingTasks.get(i), layer, info, t);
- mLeashMap.put(mOpeningLeashes.get(i), target.leash.mSurfaceControl);
- t.reparent(target.leash.mSurfaceControl, mInfo.getRootLeash());
- t.setLayer(target.leash.mSurfaceControl, layer);
- t.hide(target.leash.mSurfaceControl);
+ mLeashMap.put(mOpeningLeashes.get(i), target.leash);
+ t.reparent(target.leash, mInfo.getRootLeash());
+ t.setLayer(target.leash, layer);
+ t.hide(target.leash);
targets[i] = target;
}
t.apply();
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceControlCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceControlCompat.java
deleted file mode 100644
index acc6913..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceControlCompat.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2018 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.shared.system;
-
-import android.view.SurfaceControl;
-import android.view.View;
-import android.view.ViewRootImpl;
-
-/**
- * TODO: Remove this class
- */
-public class SurfaceControlCompat {
- final SurfaceControl mSurfaceControl;
-
- public SurfaceControlCompat(SurfaceControl surfaceControl) {
- mSurfaceControl = surfaceControl;
- }
-
- public SurfaceControlCompat(View v) {
- ViewRootImpl viewRootImpl = v.getViewRootImpl();
- mSurfaceControl = viewRootImpl != null
- ? viewRootImpl.getSurfaceControl()
- : null;
- }
-
- public boolean isValid() {
- return mSurfaceControl != null && mSurfaceControl.isValid();
- }
-
- public SurfaceControl getSurfaceControl() {
- return mSurfaceControl;
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
index e281914..30c062b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
@@ -205,13 +205,6 @@
/**
* @param surface The surface to modify.
*/
- public Builder(SurfaceControlCompat surface) {
- this(surface.mSurfaceControl);
- }
-
- /**
- * @param surface The surface to modify.
- */
public Builder(SurfaceControl surface) {
this.surface = surface;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
index c043fba..43a882a5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
@@ -35,70 +35,69 @@
mTransaction.apply();
}
- public TransactionCompat show(SurfaceControlCompat surfaceControl) {
- mTransaction.show(surfaceControl.mSurfaceControl);
+ public TransactionCompat show(SurfaceControl surfaceControl) {
+ mTransaction.show(surfaceControl);
return this;
}
- public TransactionCompat hide(SurfaceControlCompat surfaceControl) {
- mTransaction.hide(surfaceControl.mSurfaceControl);
+ public TransactionCompat hide(SurfaceControl surfaceControl) {
+ mTransaction.hide(surfaceControl);
return this;
}
- public TransactionCompat setPosition(SurfaceControlCompat surfaceControl, float x, float y) {
- mTransaction.setPosition(surfaceControl.mSurfaceControl, x, y);
+ public TransactionCompat setPosition(SurfaceControl surfaceControl, float x, float y) {
+ mTransaction.setPosition(surfaceControl, x, y);
return this;
}
- public TransactionCompat setSize(SurfaceControlCompat surfaceControl, int w, int h) {
- mTransaction.setBufferSize(surfaceControl.mSurfaceControl, w, h);
+ public TransactionCompat setSize(SurfaceControl surfaceControl, int w, int h) {
+ mTransaction.setBufferSize(surfaceControl, w, h);
return this;
}
- public TransactionCompat setLayer(SurfaceControlCompat surfaceControl, int z) {
- mTransaction.setLayer(surfaceControl.mSurfaceControl, z);
+ public TransactionCompat setLayer(SurfaceControl surfaceControl, int z) {
+ mTransaction.setLayer(surfaceControl, z);
return this;
}
- public TransactionCompat setAlpha(SurfaceControlCompat surfaceControl, float alpha) {
- mTransaction.setAlpha(surfaceControl.mSurfaceControl, alpha);
+ public TransactionCompat setAlpha(SurfaceControl surfaceControl, float alpha) {
+ mTransaction.setAlpha(surfaceControl, alpha);
return this;
}
- public TransactionCompat setOpaque(SurfaceControlCompat surfaceControl, boolean opaque) {
- mTransaction.setOpaque(surfaceControl.mSurfaceControl, opaque);
+ public TransactionCompat setOpaque(SurfaceControl surfaceControl, boolean opaque) {
+ mTransaction.setOpaque(surfaceControl, opaque);
return this;
}
- public TransactionCompat setMatrix(SurfaceControlCompat surfaceControl, float dsdx, float dtdx,
+ public TransactionCompat setMatrix(SurfaceControl surfaceControl, float dsdx, float dtdx,
float dtdy, float dsdy) {
- mTransaction.setMatrix(surfaceControl.mSurfaceControl, dsdx, dtdx, dtdy, dsdy);
+ mTransaction.setMatrix(surfaceControl, dsdx, dtdx, dtdy, dsdy);
return this;
}
- public TransactionCompat setMatrix(SurfaceControlCompat surfaceControl, Matrix matrix) {
- mTransaction.setMatrix(surfaceControl.mSurfaceControl, matrix, mTmpValues);
+ public TransactionCompat setMatrix(SurfaceControl surfaceControl, Matrix matrix) {
+ mTransaction.setMatrix(surfaceControl, matrix, mTmpValues);
return this;
}
- public TransactionCompat setWindowCrop(SurfaceControlCompat surfaceControl, Rect crop) {
- mTransaction.setWindowCrop(surfaceControl.mSurfaceControl, crop);
+ public TransactionCompat setWindowCrop(SurfaceControl surfaceControl, Rect crop) {
+ mTransaction.setWindowCrop(surfaceControl, crop);
return this;
}
- public TransactionCompat setCornerRadius(SurfaceControlCompat surfaceControl, float radius) {
- mTransaction.setCornerRadius(surfaceControl.mSurfaceControl, radius);
+ public TransactionCompat setCornerRadius(SurfaceControl surfaceControl, float radius) {
+ mTransaction.setCornerRadius(surfaceControl, radius);
return this;
}
- public TransactionCompat setBackgroundBlurRadius(SurfaceControlCompat surfaceControl,
- int radius) {
- mTransaction.setBackgroundBlurRadius(surfaceControl.mSurfaceControl, radius);
+ public TransactionCompat setBackgroundBlurRadius(SurfaceControl surfaceControl, int radius) {
+ mTransaction.setBackgroundBlurRadius(surfaceControl, radius);
return this;
}
- public TransactionCompat setColor(SurfaceControlCompat surfaceControl, float[] color) {
- mTransaction.setColor(surfaceControl.mSurfaceControl, color);
+ public TransactionCompat setColor(SurfaceControl surfaceControl, float[] color) {
+ mTransaction.setColor(surfaceControl, color);
return this;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
new file mode 100644
index 0000000..ac62cf9
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.unfold
+
+import android.content.ContentResolver
+import android.content.Context
+import android.hardware.SensorManager
+import android.hardware.devicestate.DeviceStateManager
+import android.os.Handler
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
+import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix
+import dagger.BindsInstance
+import dagger.Component
+import java.util.Optional
+import java.util.concurrent.Executor
+import javax.inject.Singleton
+
+/**
+ * Provides [UnfoldTransitionProgressProvider]. The [Optional] is empty when the transition
+ * animation is disabled.
+ *
+ * This component is meant to be used for places that don't use dagger. By providing those
+ * parameters to the factory, all dagger objects are correctly instantiated. See
+ * [createUnfoldTransitionProgressProvider] for an example.
+ */
+@Singleton
+@Component(modules = [UnfoldSharedModule::class])
+internal interface UnfoldSharedComponent {
+
+ @Component.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance context: Context,
+ @BindsInstance config: UnfoldTransitionConfig,
+ @BindsInstance screenStatusProvider: ScreenStatusProvider,
+ @BindsInstance deviceStateManager: DeviceStateManager,
+ @BindsInstance sensorManager: SensorManager,
+ @BindsInstance @Main handler: Handler,
+ @BindsInstance @Main executor: Executor,
+ @BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String,
+ @BindsInstance contentResolver: ContentResolver = context.contentResolver
+ ): UnfoldSharedComponent
+ }
+
+ val unfoldTransitionProvider: Optional<UnfoldTransitionProgressProvider>
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedModule.kt
new file mode 100644
index 0000000..23e4c97
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedModule.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.unfold
+
+import android.hardware.SensorManager
+import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
+import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
+import com.android.systemui.unfold.updates.DeviceFoldStateProvider
+import com.android.systemui.unfold.updates.FoldStateProvider
+import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider
+import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
+import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider
+import com.android.systemui.unfold.util.ATraceLoggerTransitionProgressListener
+import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider
+import dagger.Module
+import dagger.Provides
+import java.util.Optional
+import javax.inject.Singleton
+
+@Module
+class UnfoldSharedModule {
+ @Provides
+ @Singleton
+ fun unfoldTransitionProgressProvider(
+ config: UnfoldTransitionConfig,
+ scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory,
+ tracingListener: ATraceLoggerTransitionProgressListener,
+ foldStateProvider: FoldStateProvider
+ ): Optional<UnfoldTransitionProgressProvider> =
+ if (!config.isEnabled) {
+ Optional.empty()
+ } else {
+ val baseProgressProvider =
+ if (config.isHingeAngleEnabled) {
+ PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider)
+ } else {
+ FixedTimingTransitionProgressProvider(foldStateProvider)
+ }
+ Optional.of(
+ scaleAwareProviderFactory.wrap(baseProgressProvider).apply {
+ // Always present callback that logs animation beginning and end.
+ addCallback(tracingListener)
+ })
+ }
+
+ @Provides
+ @Singleton
+ fun provideFoldStateProvider(
+ deviceFoldStateProvider: DeviceFoldStateProvider
+ ): FoldStateProvider = deviceFoldStateProvider
+
+ @Provides
+ fun hingeAngleProvider(
+ config: UnfoldTransitionConfig,
+ sensorManager: SensorManager
+ ): HingeAngleProvider =
+ if (config.isHingeAngleEnabled) {
+ HingeSensorAngleProvider(sensorManager)
+ } else {
+ EmptyHingeAngleProvider
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index 953b0e0..d5d6362 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -23,22 +23,16 @@
import android.os.Handler
import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
import com.android.systemui.unfold.config.UnfoldTransitionConfig
-import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
-import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
-import com.android.systemui.unfold.updates.DeviceFoldStateProvider
-import com.android.systemui.unfold.updates.FoldStateProvider
-import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider
-import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
-import com.android.systemui.unfold.util.ATraceLoggerTransitionProgressListener
-import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider
import java.util.concurrent.Executor
/**
* Factory for [UnfoldTransitionProgressProvider].
*
- * This is needed as Launcher has to create the object manually. Sysui create it using dagger (see
- * [UnfoldTransitionModule]).
+ * This is needed as Launcher has to create the object manually. If dagger is available, this object
+ * is provided in [UnfoldSharedModule].
+ *
+ * This should **never** be called from sysui, as the object is already provided in that process.
*/
fun createUnfoldTransitionProgressProvider(
context: Context,
@@ -49,62 +43,21 @@
mainHandler: Handler,
mainExecutor: Executor,
tracingTagPrefix: String
-): UnfoldTransitionProgressProvider {
-
- if (!config.isEnabled) {
- throw IllegalStateException(
- "Trying to create " +
- "UnfoldTransitionProgressProvider when the transition is disabled")
- }
-
- val foldStateProvider =
- createFoldStateProvider(
+): UnfoldTransitionProgressProvider =
+ DaggerUnfoldSharedComponent.factory()
+ .create(
context,
config,
screenStatusProvider,
deviceStateManager,
sensorManager,
mainHandler,
- mainExecutor)
-
- val unfoldTransitionProgressProvider =
- if (config.isHingeAngleEnabled) {
- PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider)
- } else {
- FixedTimingTransitionProgressProvider(foldStateProvider)
- }
-
- return ScaleAwareTransitionProgressProvider(
- unfoldTransitionProgressProvider, context.contentResolver)
- .apply {
- // Always present callback that logs animation beginning and end.
- addCallback(ATraceLoggerTransitionProgressListener(tracingTagPrefix))
- }
-}
-
-fun createFoldStateProvider(
- context: Context,
- config: UnfoldTransitionConfig,
- screenStatusProvider: ScreenStatusProvider,
- deviceStateManager: DeviceStateManager,
- sensorManager: SensorManager,
- mainHandler: Handler,
- mainExecutor: Executor
-): FoldStateProvider {
- val hingeAngleProvider =
- if (config.isHingeAngleEnabled) {
- HingeSensorAngleProvider(sensorManager)
- } else {
- EmptyHingeAngleProvider()
- }
-
- return DeviceFoldStateProvider(
- context,
- hingeAngleProvider,
- screenStatusProvider,
- deviceStateManager,
- mainExecutor,
- mainHandler)
-}
+ mainExecutor,
+ tracingTagPrefix)
+ .unfoldTransitionProvider
+ .orElse(null)
+ ?: throw IllegalStateException(
+ "Trying to create " +
+ "UnfoldTransitionProgressProvider when the transition is disabled")
fun createConfig(context: Context): UnfoldTransitionConfig = ResourceUnfoldTransitionConfig(context)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index cd1ea21..204ae09 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -22,6 +22,7 @@
import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.core.util.Consumer
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
import com.android.systemui.unfold.updates.hinge.FULLY_CLOSED_DEGREES
@@ -29,14 +30,17 @@
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import java.util.concurrent.Executor
+import javax.inject.Inject
-class DeviceFoldStateProvider(
+class DeviceFoldStateProvider
+@Inject
+constructor(
context: Context,
private val hingeAngleProvider: HingeAngleProvider,
private val screenStatusProvider: ScreenStatusProvider,
private val deviceStateManager: DeviceStateManager,
- private val mainExecutor: Executor,
- private val handler: Handler
+ @Main private val mainExecutor: Executor,
+ @Main private val handler: Handler
) : FoldStateProvider {
private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt
index 9b58b1f..4ca1a53 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt
@@ -2,7 +2,7 @@
import androidx.core.util.Consumer
-internal class EmptyHingeAngleProvider : HingeAngleProvider {
+internal object EmptyHingeAngleProvider : HingeAngleProvider {
override fun start() {
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt
similarity index 76%
rename from packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressProvider.kt
rename to packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt
index f3eeb32..1574c8d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt
@@ -2,6 +2,8 @@
import android.os.Trace
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import javax.inject.Inject
+import javax.inject.Qualifier
/**
* Listener that logs start and end of the fold-unfold transition.
@@ -9,7 +11,10 @@
* [tracePrefix] arg helps in differentiating those. Currently, this is expected to be logged twice
* for each fold/unfold: in (1) systemui and (2) launcher process.
*/
-class ATraceLoggerTransitionProgressListener(tracePrefix: String) : TransitionProgressListener {
+class ATraceLoggerTransitionProgressListener
+@Inject
+internal constructor(@UnfoldTransitionATracePrefix tracePrefix: String) :
+ TransitionProgressListener {
private val traceName = "$tracePrefix#$UNFOLD_TRANSITION_TRACE_NAME"
@@ -27,3 +32,5 @@
}
private const val UNFOLD_TRANSITION_TRACE_NAME = "FoldUnfoldTransitionInProgress"
+
+@Qualifier annotation class UnfoldTransitionATracePrefix
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/JankMonitorTransitionProgressListener.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/JankMonitorTransitionProgressListener.kt
new file mode 100644
index 0000000..2b38f3d
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/JankMonitorTransitionProgressListener.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.unfold.util
+
+import android.view.View
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.jank.InteractionJankMonitor.CUJ_UNFOLD_ANIM
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import java.util.function.Supplier
+
+class JankMonitorTransitionProgressListener(private val attachedViewProvider: Supplier<View>) :
+ TransitionProgressListener {
+
+ private val interactionJankMonitor = InteractionJankMonitor.getInstance()
+
+ override fun onTransitionStarted() {
+ interactionJankMonitor.begin(attachedViewProvider.get(), CUJ_UNFOLD_ANIM)
+ }
+
+ override fun onTransitionFinished() {
+ interactionJankMonitor.end(CUJ_UNFOLD_ANIM)
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
index df9078a..ee79b87 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
@@ -6,15 +6,20 @@
import android.provider.Settings
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
/** Wraps [UnfoldTransitionProgressProvider] to disable transitions when animations are disabled. */
-class ScaleAwareTransitionProgressProvider(
- unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
+class ScaleAwareTransitionProgressProvider
+@AssistedInject
+constructor(
+ @Assisted progressProviderToWrap: UnfoldTransitionProgressProvider,
private val contentResolver: ContentResolver
) : UnfoldTransitionProgressProvider {
private val scopedUnfoldTransitionProgressProvider =
- ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider)
+ ScopedUnfoldTransitionProgressProvider(progressProviderToWrap)
private val animatorDurationScaleObserver = object : ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
@@ -47,4 +52,11 @@
contentResolver.unregisterContentObserver(animatorDurationScaleObserver)
scopedUnfoldTransitionProgressProvider.destroy()
}
+
+ @AssistedFactory
+ interface Factory {
+ fun wrap(
+ progressProvider: UnfoldTransitionProgressProvider
+ ): ScaleAwareTransitionProgressProvider
+ }
}
diff --git a/media/java/android/media/tv/interactive/TvIAppInfo.aidl b/packages/SystemUI/src-debug/com/android/systemui/util/Compile.java
similarity index 70%
copy from media/java/android/media/tv/interactive/TvIAppInfo.aidl
copy to packages/SystemUI/src-debug/com/android/systemui/util/Compile.java
index 6041460..dc804ca 100644
--- a/media/java/android/media/tv/interactive/TvIAppInfo.aidl
+++ b/packages/SystemUI/src-debug/com/android/systemui/util/Compile.java
@@ -14,6 +14,10 @@
* limitations under the License.
*/
-package android.media.tv.interactive;
+package com.android.systemui.util;
-parcelable TvIAppInfo;
\ No newline at end of file
+/** Constants that vary by compilation configuration. */
+public class Compile {
+ /** Whether SystemUI was compiled in debug mode, and supports debug features */
+ public static final boolean IS_DEBUG = true;
+}
diff --git a/media/java/android/media/tv/interactive/TvIAppInfo.aidl b/packages/SystemUI/src-release/com/android/systemui/util/Compile.java
similarity index 70%
copy from media/java/android/media/tv/interactive/TvIAppInfo.aidl
copy to packages/SystemUI/src-release/com/android/systemui/util/Compile.java
index 6041460..8a63763 100644
--- a/media/java/android/media/tv/interactive/TvIAppInfo.aidl
+++ b/packages/SystemUI/src-release/com/android/systemui/util/Compile.java
@@ -14,6 +14,10 @@
* limitations under the License.
*/
-package android.media.tv.interactive;
+package com.android.systemui.util;
-parcelable TvIAppInfo;
\ No newline at end of file
+/** Constants that vary by compilation configuration. */
+public class Compile {
+ /** Whether SystemUI was compiled in debug mode, and supports debug features */
+ public static final boolean IS_DEBUG = false;
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index 099dd5d..75579b0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -30,6 +30,8 @@
import android.view.ViewGroup;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
import com.android.internal.policy.SystemBarUtils;
import com.android.settingslib.Utils;
import com.android.systemui.R;
@@ -57,6 +59,11 @@
private ColorStateList mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR);
private boolean mBouncerVisible;
private boolean mAltBouncerShowing;
+ /**
+ * Container that wraps the KeyguardMessageArea - may be null if current view hierarchy doesn't
+ * contain {@link R.id.keyguard_message_area_container}.
+ */
+ @Nullable
private ViewGroup mContainer;
private int mTopMargin;
@@ -75,6 +82,9 @@
}
void onConfigChanged() {
+ if (mContainer == null) {
+ return;
+ }
final int newTopMargin = SystemBarUtils.getStatusBarHeight(getContext());
if (mTopMargin == newTopMargin) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index a100cb8..9c2971c 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -96,6 +96,8 @@
import com.android.systemui.util.concurrency.ThreadFactory;
import com.android.systemui.util.settings.SecureSettings;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
@@ -107,7 +109,7 @@
* for antialiasing and emulation purposes.
*/
@SysUISingleton
-public class ScreenDecorations extends CoreStartable implements Tunable {
+public class ScreenDecorations extends CoreStartable implements Tunable , Dumpable{
private static final boolean DEBUG = false;
private static final String TAG = "ScreenDecorations";
@@ -677,6 +679,20 @@
});
}
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("ScreenDecorations state:");
+ pw.println(" DEBUG_DISABLE_SCREEN_DECORATIONS:" + DEBUG_DISABLE_SCREEN_DECORATIONS);
+ pw.println(" mIsRoundedCornerMultipleRadius:" + mIsRoundedCornerMultipleRadius);
+ pw.println(" mIsPrivacyDotEnabled:" + mIsPrivacyDotEnabled);
+ pw.println(" mPendingRotationChange:" + mPendingRotationChange);
+ pw.println(" mRoundedDefault(x,y)=(" + mRoundedDefault.x + "," + mRoundedDefault.y + ")");
+ pw.println(" mRoundedDefaultTop(x,y)=(" + mRoundedDefaultTop.x + "," + mRoundedDefaultTop.y
+ + ")");
+ pw.println(" mRoundedDefaultBottom(x,y)=(" + mRoundedDefaultBottom.x + ","
+ + mRoundedDefaultBottom.y + ")");
+ }
+
private void updateOrientation() {
Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(),
"must call on " + mHandler.getLooper().getThread()
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 63962fa..daca918 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -32,6 +32,9 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Dumpable;
+import android.util.DumpableContainer;
import android.util.Log;
import android.util.TimingsTraceLog;
import android.view.SurfaceControl;
@@ -53,13 +56,19 @@
* Application class for SystemUI.
*/
public class SystemUIApplication extends Application implements
- SystemUIAppComponentFactory.ContextInitializer {
+ SystemUIAppComponentFactory.ContextInitializer, DumpableContainer {
public static final String TAG = "SystemUIService";
private static final boolean DEBUG = false;
private ContextComponentHelper mComponentHelper;
private BootCompleteCacheImpl mBootCompleteCache;
+ private DumpManager mDumpManager;
+
+ /**
+ * Map of dumpables added externally.
+ */
+ private final ArrayMap<String, Dumpable> mDumpables = new ArrayMap<>();
/**
* Hold a reference on the stuff we start.
@@ -214,7 +223,7 @@
}
}
- final DumpManager dumpManager = mSysUIComponent.createDumpManager();
+ mDumpManager = mSysUIComponent.createDumpManager();
Log.v(TAG, "Starting SystemUI services for user " +
Process.myUserHandle().getIdentifier() + ".");
@@ -255,7 +264,7 @@
mServices[i].onBootCompleted();
}
- dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
+ mDumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
}
mSysUIComponent.getInitController().executePostInitTasks();
log.traceEnd();
@@ -263,6 +272,29 @@
mServicesStarted = true;
}
+ // TODO(b/149254050): add unit tests? There doesn't seem to be a SystemUiApplicationTest...
+ @Override
+ public boolean addDumpable(Dumpable dumpable) {
+ String name = dumpable.getDumpableName();
+ if (mDumpables.containsKey(name)) {
+ // This is normal because SystemUIApplication is an application context that is shared
+ // among multiple components
+ if (DEBUG) {
+ Log.d(TAG, "addDumpable(): ignoring " + dumpable + " as there is already a dumpable"
+ + " with that name (" + name + "): " + mDumpables.get(name));
+ }
+ return false;
+ }
+ if (DEBUG) Log.d(TAG, "addDumpable(): adding '" + name + "' = " + dumpable);
+ mDumpables.put(name, dumpable);
+
+ // TODO(b/149254050): replace com.android.systemui.dump.Dumpable by
+ // com.android.util.Dumpable and get rid of the intermediate lambda
+ mDumpManager.registerDumpable(dumpable.getDumpableName(),
+ (fd, pw, args) -> dumpable.dump(pw, args));
+ return true;
+ }
+
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (mServicesStarted) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
index 7bb4708..4c00735 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
@@ -26,6 +26,7 @@
import android.hardware.biometrics.BiometricOverlayConstants
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
+import android.hardware.biometrics.SensorLocationInternal
import android.hardware.display.DisplayManager
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
@@ -113,6 +114,7 @@
orientationListener.enable()
}
}
+ private var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT
private val overlayViewParams = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
@@ -158,11 +160,19 @@
val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
val display = context.display!!
+ val offsets = sensorProps.getLocation(display.uniqueId).let { location ->
+ if (location == null) {
+ Log.w(TAG, "No location specified for display: ${display.uniqueId}")
+ }
+ location ?: sensorProps.location
+ }
+ overlayOffsets = offsets
+
val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
- lottie.setAnimation(display.asSideFpsAnimation())
- view.rotation = display.asSideFpsAnimationRotation()
+ view.rotation = display.asSideFpsAnimationRotation(offsets.isYAligned())
updateOverlayParams(display, lottie.composition?.bounds ?: Rect())
+ lottie.setAnimation(display.asSideFpsAnimation(offsets.isYAligned()))
lottie.addLottieOnCompositionLoadedListener {
if (overlayView == view) {
updateOverlayParams(display, it.bounds)
@@ -179,24 +189,37 @@
val size = windowManager.maximumWindowMetrics.bounds
val displayWidth = if (isPortrait) size.width() else size.height()
val displayHeight = if (isPortrait) size.height() else size.width()
- val offsets = sensorProps.getLocation(display.uniqueId).let { location ->
- if (location == null) {
- Log.w(TAG, "No location specified for display: ${display.uniqueId}")
- }
- location ?: sensorProps.location
- }
- // ignore sensorLocationX and sensorRadius since it's assumed to be on the side
- // of the device and centered at sensorLocationY
- val (x, y) = when (display.rotation) {
- Surface.ROTATION_90 ->
- Pair(offsets.sensorLocationY, 0)
- Surface.ROTATION_270 ->
- Pair(displayHeight - offsets.sensorLocationY - bounds.width(), displayWidth)
- Surface.ROTATION_180 ->
- Pair(0, displayHeight - offsets.sensorLocationY - bounds.height())
- else ->
- Pair(displayWidth, offsets.sensorLocationY)
+ // ignore sensorRadius since it's assumed that the sensor is on the side and centered at
+ // either sensorLocationX or sensorLocationY (both should not be set)
+ val (x, y) = if (overlayOffsets.isYAligned()) {
+ when (display.rotation) {
+ Surface.ROTATION_90 ->
+ Pair(overlayOffsets.sensorLocationY, 0)
+ Surface.ROTATION_270 ->
+ Pair(
+ displayHeight - overlayOffsets.sensorLocationY - bounds.width(),
+ displayWidth + bounds.height()
+ )
+ Surface.ROTATION_180 ->
+ Pair(0, displayHeight - overlayOffsets.sensorLocationY - bounds.height())
+ else ->
+ Pair(displayWidth, overlayOffsets.sensorLocationY)
+ }
+ } else {
+ when (display.rotation) {
+ Surface.ROTATION_90 ->
+ Pair(0, displayWidth - overlayOffsets.sensorLocationX - bounds.height())
+ Surface.ROTATION_270 ->
+ Pair(displayWidth, overlayOffsets.sensorLocationX - bounds.height())
+ Surface.ROTATION_180 ->
+ Pair(
+ displayWidth - overlayOffsets.sensorLocationX - bounds.width(),
+ displayHeight
+ )
+ else ->
+ Pair(overlayOffsets.sensorLocationX, 0)
+ }
}
overlayViewParams.x = x
overlayViewParams.y = y
@@ -209,8 +232,10 @@
// hide after a few seconds if the sensor is oriented down and there are
// large overlapping system bars
- if ((context.display?.rotation == Surface.ROTATION_270) &&
- windowManager.currentWindowMetrics.windowInsets.hasBigNavigationBar()) {
+ val rotation = context.display?.rotation
+ if (windowManager.currentWindowMetrics.windowInsets.hasBigNavigationBar() &&
+ ((rotation == Surface.ROTATION_270 && overlayOffsets.isYAligned()) ||
+ (rotation == Surface.ROTATION_180 && !overlayOffsets.isYAligned()))) {
overlayHideAnimator = view.animate()
.alpha(0f)
.setStartDelay(3_000)
@@ -245,18 +270,21 @@
getTasks(1).firstOrNull()?.topActivity?.className ?: ""
@RawRes
-private fun Display.asSideFpsAnimation(): Int = when (rotation) {
- Surface.ROTATION_0 -> R.raw.sfps_pulse
- Surface.ROTATION_180 -> R.raw.sfps_pulse
- else -> R.raw.sfps_pulse_landscape
+private fun Display.asSideFpsAnimation(yAligned: Boolean): Int = when (rotation) {
+ Surface.ROTATION_0 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
+ Surface.ROTATION_180 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
+ else -> if (yAligned) R.raw.sfps_pulse_landscape else R.raw.sfps_pulse
}
-private fun Display.asSideFpsAnimationRotation(): Float = when (rotation) {
+private fun Display.asSideFpsAnimationRotation(yAligned: Boolean): Float = when (rotation) {
+ Surface.ROTATION_90 -> if (yAligned) 0f else 180f
Surface.ROTATION_180 -> 180f
- Surface.ROTATION_270 -> 180f
+ Surface.ROTATION_270 -> if (yAligned) 180f else 0f
else -> 0f
}
+private fun SensorLocationInternal.isYAligned(): Boolean = sensorLocationY != 0
+
private fun Display.isPortrait(): Boolean =
rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
new file mode 100644
index 0000000..41a4963
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -0,0 +1,72 @@
+/*
+ * 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.clipboardoverlay;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED;
+
+import static java.util.Objects.requireNonNull;
+
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.provider.DeviceConfig;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.screenshot.TimeoutHandler;
+
+import javax.inject.Inject;
+
+/**
+ * ClipboardListener brings up a clipboard overlay when something is copied to the clipboard.
+ */
+@SysUISingleton
+public class ClipboardListener extends CoreStartable
+ implements ClipboardManager.OnPrimaryClipChangedListener {
+
+ private ClipboardOverlayController mClipboardOverlayController;
+ private ClipboardManager mClipboardManager;
+
+ @Inject
+ public ClipboardListener(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void start() {
+ if (DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, false)) {
+ mClipboardManager = requireNonNull(mContext.getSystemService(ClipboardManager.class));
+ mClipboardManager.addPrimaryClipChangedListener(this);
+ }
+ }
+
+ @Override
+ public void onPrimaryClipChanged() {
+ if (!mClipboardManager.hasPrimaryClip()) {
+ return;
+ }
+ if (mClipboardOverlayController == null) {
+ mClipboardOverlayController = new ClipboardOverlayController(mContext,
+ new TimeoutHandler(mContext));
+ }
+ mClipboardOverlayController.setClipData(mClipboardManager.getPrimaryClip());
+ mClipboardOverlayController.setOnSessionCompleteListener(() -> {
+ // Session is complete, free memory until it's needed again.
+ mClipboardOverlayController = null;
+ });
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
new file mode 100644
index 0000000..ae0702c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -0,0 +1,471 @@
+/*
+ * 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.clipboardoverlay;
+
+import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+
+import static java.util.Objects.requireNonNull;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.MainThread;
+import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.graphics.drawable.Icon;
+import android.hardware.display.DisplayManager;
+import android.hardware.input.InputManager;
+import android.net.Uri;
+import android.os.Looper;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Size;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.Gravity;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.InputMonitor;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.policy.PhoneWindow;
+import com.android.systemui.R;
+import com.android.systemui.screenshot.FloatingWindowUtil;
+import com.android.systemui.screenshot.ScreenshotActionChip;
+import com.android.systemui.screenshot.TimeoutHandler;
+
+import java.io.IOException;
+
+/**
+ * Controls state and UI for the overlay that appears when something is added to the clipboard
+ */
+public class ClipboardOverlayController {
+ private static final String TAG = "ClipboardOverlayCtrlr";
+ private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY";
+
+ /** Constants for screenshot/copy deconflicting */
+ public static final String SCREENSHOT_ACTION = "com.android.systemui.SCREENSHOT";
+ public static final String SELF_PERMISSION = "com.android.systemui.permission.SELF";
+ public static final String COPY_OVERLAY_ACTION = "com.android.systemui.COPY";
+
+ private static final int CLIPBOARD_DEFAULT_TIMEOUT_MILLIS = 6000;
+
+ private final Context mContext;
+ private final DisplayManager mDisplayManager;
+ private final WindowManager mWindowManager;
+ private final WindowManager.LayoutParams mWindowLayoutParams;
+ private final PhoneWindow mWindow;
+ private final TimeoutHandler mTimeoutHandler;
+
+ private final DraggableConstraintLayout mView;
+ private final ImageView mImagePreview;
+ private final TextView mTextPreview;
+ private final ScreenshotActionChip mEditChip;
+ private final ScreenshotActionChip mRemoteCopyChip;
+ private final View mActionContainerBackground;
+
+ private Runnable mOnSessionCompleteListener;
+
+ private InputEventReceiver mInputEventReceiver;
+
+ private BroadcastReceiver mCloseDialogsReceiver;
+ private BroadcastReceiver mScreenshotReceiver;
+
+ private boolean mBlockAttach = false;
+
+ public ClipboardOverlayController(Context context, TimeoutHandler timeoutHandler) {
+ mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
+ final Context displayContext = context.createDisplayContext(getDefaultDisplay());
+ mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
+
+ mWindowManager = mContext.getSystemService(WindowManager.class);
+
+ mTimeoutHandler = timeoutHandler;
+ mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
+
+ // Setup the window that we are going to use
+ mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
+ mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ mWindowLayoutParams.height = WRAP_CONTENT;
+ mWindowLayoutParams.gravity = Gravity.BOTTOM;
+ mWindowLayoutParams.setTitle("ClipboardOverlay");
+ mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
+ mWindow.setWindowManager(mWindowManager, null, null);
+
+ mView = (DraggableConstraintLayout)
+ LayoutInflater.from(mContext).inflate(R.layout.clipboard_overlay, null);
+ mActionContainerBackground = requireNonNull(
+ mView.findViewById(R.id.actions_container_background));
+ mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
+ mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
+ mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
+ mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
+
+ mView.setOnDismissCallback(this::hideImmediate);
+ mView.setOnInteractionCallback(() -> mTimeoutHandler.resetTimeout());
+
+ mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
+ mRemoteCopyChip.setIcon(
+ Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
+
+ // Only show remote copy if it's available.
+ PackageManager packageManager = mContext.getPackageManager();
+ if (packageManager.resolveActivity(getRemoteCopyIntent(), 0) != null) {
+ mRemoteCopyChip.setOnClickListener((v) -> {
+ showNearby();
+ });
+ mRemoteCopyChip.setAlpha(1f);
+ } else {
+ mRemoteCopyChip.setVisibility(View.GONE);
+ }
+
+ attachWindow();
+ withWindowAttached(() -> {
+ mWindow.setContentView(mView);
+ updateInsets(mWindowManager.getCurrentWindowMetrics().getWindowInsets());
+ getEnterAnimation().start();
+ });
+
+ mTimeoutHandler.setOnTimeoutRunnable(() -> animateOut());
+
+ mCloseDialogsReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+ animateOut();
+ }
+ }
+ };
+ mContext.registerReceiver(mCloseDialogsReceiver,
+ new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS));
+
+ mScreenshotReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (SCREENSHOT_ACTION.equals(intent.getAction())) {
+ animateOut();
+ }
+ }
+ };
+ mContext.registerReceiver(mScreenshotReceiver, new IntentFilter(SCREENSHOT_ACTION),
+ SELF_PERMISSION, null);
+ monitorOutsideTouches();
+
+ mContext.sendBroadcast(new Intent(COPY_OVERLAY_ACTION), SELF_PERMISSION);
+ }
+
+ void setClipData(ClipData clipData) {
+ reset();
+
+ if (clipData == null || clipData.getItemCount() == 0) {
+ showTextPreview(
+ mContext.getResources().getString(R.string.clipboard_overlay_text_copied));
+ } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
+ showEditableText(clipData.getItemAt(0).getText());
+ } else if (clipData.getItemAt(0).getUri() != null) {
+ // How to handle non-image URIs?
+ showEditableImage(clipData.getItemAt(0).getUri());
+ } else {
+ showTextPreview(
+ mContext.getResources().getString(R.string.clipboard_overlay_text_copied));
+ }
+
+ mTimeoutHandler.resetTimeout();
+ }
+
+ void setOnSessionCompleteListener(Runnable runnable) {
+ mOnSessionCompleteListener = runnable;
+ }
+
+ private void monitorOutsideTouches() {
+ InputManager inputManager = mContext.getSystemService(InputManager.class);
+ InputMonitor monitor = inputManager.monitorGestureInput("clipboard overlay", 0);
+ mInputEventReceiver = new InputEventReceiver(monitor.getInputChannel(),
+ Looper.getMainLooper()) {
+ @Override
+ public void onInputEvent(InputEvent event) {
+ if (event instanceof MotionEvent) {
+ MotionEvent motionEvent = (MotionEvent) event;
+ if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ int[] pt = new int[2];
+ mView.getLocationOnScreen(pt);
+ Rect rect = new Rect(pt[0], pt[1], pt[0] + mView.getWidth(),
+ pt[1] + mView.getHeight());
+ if (!rect.contains((int) motionEvent.getRawX(),
+ (int) motionEvent.getRawY())) {
+ animateOut();
+ }
+ }
+ }
+ finishInputEvent(event, true /* handled */);
+ }
+ };
+ }
+
+ private void editImage(Uri uri) {
+ String editorPackage = mContext.getString(R.string.config_screenshotEditor);
+ Intent editIntent = new Intent(Intent.ACTION_EDIT);
+ if (!TextUtils.isEmpty(editorPackage)) {
+ editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
+ }
+ editIntent.setDataAndType(uri, "image/*");
+ editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivity(editIntent);
+ animateOut();
+ }
+
+ private void editText() {
+ Intent editIntent = new Intent(mContext, EditTextActivity.class);
+ editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivity(editIntent);
+ animateOut();
+ }
+
+ private void showNearby() {
+ mContext.startActivity(getRemoteCopyIntent());
+ animateOut();
+ }
+
+ private void showTextPreview(CharSequence text) {
+ mTextPreview.setVisibility(View.VISIBLE);
+ mImagePreview.setVisibility(View.GONE);
+ mTextPreview.setText(text);
+ mEditChip.setVisibility(View.GONE);
+ }
+
+ private void showEditableText(CharSequence text) {
+ showTextPreview(text);
+ mEditChip.setVisibility(View.VISIBLE);
+ mEditChip.setAlpha(1f);
+ View.OnClickListener listener = v -> editText();
+ mEditChip.setOnClickListener(listener);
+ mTextPreview.setOnClickListener(listener);
+ }
+
+ private void showEditableImage(Uri uri) {
+ mTextPreview.setVisibility(View.GONE);
+ mImagePreview.setVisibility(View.VISIBLE);
+ mEditChip.setAlpha(1f);
+ ContentResolver resolver = mContext.getContentResolver();
+ try {
+ int size = mContext.getResources().getDimensionPixelSize(R.dimen.screenshot_x_scale);
+ // The width of the view is capped, height maintains aspect ratio, so allow it to be
+ // taller if needed.
+ Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
+ mImagePreview.setImageBitmap(thumbnail);
+ } catch (IOException e) {
+ Log.e(TAG, "Thumbnail loading failed", e);
+ }
+ View.OnClickListener listener = v -> editImage(uri);
+ mEditChip.setOnClickListener(listener);
+ mImagePreview.setOnClickListener(listener);
+ }
+
+ private Intent getRemoteCopyIntent() {
+ Intent nearbyIntent = new Intent(REMOTE_COPY_ACTION);
+ nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ return nearbyIntent;
+ }
+
+ private void animateOut() {
+ getExitAnimation().start();
+ }
+
+ private ValueAnimator getEnterAnimation() {
+ ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+
+ mView.setAlpha(0);
+ final View previewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
+ final View actionBackground = requireNonNull(
+ mView.findViewById(R.id.actions_container_background));
+ mImagePreview.setVisibility(View.VISIBLE);
+ mActionContainerBackground.setVisibility(View.VISIBLE);
+
+ anim.addUpdateListener(animation -> {
+ mView.setAlpha(animation.getAnimatedFraction());
+ float scale = 0.6f + 0.4f * animation.getAnimatedFraction();
+ mView.setPivotY(mView.getHeight() - previewBorder.getHeight() / 2f);
+ mView.setPivotX(actionBackground.getWidth() / 2f);
+ mView.setScaleX(scale);
+ mView.setScaleY(scale);
+ });
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mView.setAlpha(1);
+ mTimeoutHandler.resetTimeout();
+ }
+ });
+ return anim;
+ }
+
+ private ValueAnimator getExitAnimation() {
+ ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+
+ anim.addUpdateListener(animation -> {
+ mView.setAlpha(1 - animation.getAnimatedFraction());
+ final View actionBackground = requireNonNull(
+ mView.findViewById(R.id.actions_container_background));
+ mView.setTranslationX(
+ -animation.getAnimatedFraction() * actionBackground.getWidth() / 2);
+ });
+
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ hideImmediate();
+ }
+ });
+
+ return anim;
+ }
+
+ private void hideImmediate() {
+ // Note this may be called multiple times if multiple dismissal events happen at the same
+ // time.
+ mTimeoutHandler.cancelTimeout();
+ final View decorView = mWindow.peekDecorView();
+ if (decorView != null && decorView.isAttachedToWindow()) {
+ mWindowManager.removeViewImmediate(decorView);
+ }
+ if (mCloseDialogsReceiver != null) {
+ mContext.unregisterReceiver(mCloseDialogsReceiver);
+ mCloseDialogsReceiver = null;
+ }
+ if (mScreenshotReceiver != null) {
+ mContext.unregisterReceiver(mScreenshotReceiver);
+ mScreenshotReceiver = null;
+ }
+ if (mInputEventReceiver != null) {
+ mInputEventReceiver.dispose();
+ mInputEventReceiver = null;
+ }
+ if (mOnSessionCompleteListener != null) {
+ mOnSessionCompleteListener.run();
+ }
+ }
+
+ private void reset() {
+ mView.setTranslationX(0);
+ mView.setAlpha(1);
+ mTimeoutHandler.cancelTimeout();
+ }
+
+ @MainThread
+ private void attachWindow() {
+ View decorView = mWindow.getDecorView();
+ if (decorView.isAttachedToWindow() || mBlockAttach) {
+ return;
+ }
+ mBlockAttach = true;
+ mWindowManager.addView(decorView, mWindowLayoutParams);
+ decorView.requestApplyInsets();
+ mView.requestApplyInsets();
+ decorView.getViewTreeObserver().addOnWindowAttachListener(
+ new ViewTreeObserver.OnWindowAttachListener() {
+ @Override
+ public void onWindowAttached() {
+ mBlockAttach = false;
+ }
+
+ @Override
+ public void onWindowDetached() {
+ }
+ }
+ );
+ }
+
+ private void withWindowAttached(Runnable action) {
+ View decorView = mWindow.getDecorView();
+ if (decorView.isAttachedToWindow()) {
+ action.run();
+ } else {
+ decorView.getViewTreeObserver().addOnWindowAttachListener(
+ new ViewTreeObserver.OnWindowAttachListener() {
+ @Override
+ public void onWindowAttached() {
+ mBlockAttach = false;
+ decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
+ action.run();
+ }
+
+ @Override
+ public void onWindowDetached() {
+ }
+ });
+ }
+ }
+
+ private void updateInsets(WindowInsets insets) {
+ int orientation = mContext.getResources().getConfiguration().orientation;
+ FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mView.getLayoutParams();
+ if (p == null) {
+ return;
+ }
+ DisplayCutout cutout = insets.getDisplayCutout();
+ Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
+ if (cutout == null) {
+ p.setMargins(0, 0, 0, navBarInsets.bottom);
+ } else {
+ Insets waterfall = cutout.getWaterfallInsets();
+ if (orientation == ORIENTATION_PORTRAIT) {
+ p.setMargins(
+ waterfall.left,
+ Math.max(cutout.getSafeInsetTop(), waterfall.top),
+ waterfall.right,
+ Math.max(cutout.getSafeInsetBottom(),
+ Math.max(navBarInsets.bottom, waterfall.bottom)));
+ } else {
+ p.setMargins(
+ Math.max(cutout.getSafeInsetLeft(), waterfall.left),
+ waterfall.top,
+ Math.max(cutout.getSafeInsetRight(), waterfall.right),
+ Math.max(navBarInsets.bottom, waterfall.bottom));
+ }
+ }
+ mView.setLayoutParams(p);
+ mView.requestLayout();
+ }
+
+ private Display getDefaultDisplay() {
+ return mDisplayManager.getDisplay(DEFAULT_DISPLAY);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java
new file mode 100644
index 0000000..6a4be6e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java
@@ -0,0 +1,108 @@
+/*
+ * 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.clipboardoverlay;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+import com.android.systemui.R;
+import com.android.systemui.screenshot.SwipeDismissHandler;
+
+/**
+ * ConstraintLayout that is draggable when touched in a specific region
+ */
+public class DraggableConstraintLayout extends ConstraintLayout {
+ private final SwipeDismissHandler mSwipeDismissHandler;
+ private final GestureDetector mSwipeDetector;
+ private Runnable mOnDismiss;
+ private Runnable mOnInteraction;
+
+ public DraggableConstraintLayout(Context context) {
+ this(context, null);
+ }
+
+ public DraggableConstraintLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public DraggableConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ mSwipeDismissHandler = new SwipeDismissHandler(mContext, this,
+ new SwipeDismissHandler.SwipeDismissCallbacks() {
+ @Override
+ public void onInteraction() {
+ if (mOnInteraction != null) {
+ mOnInteraction.run();
+ }
+ }
+
+ @Override
+ public void onDismiss() {
+ if (mOnDismiss != null) {
+ mOnDismiss.run();
+ }
+ }
+ });
+ setOnTouchListener(mSwipeDismissHandler);
+
+ mSwipeDetector = new GestureDetector(mContext,
+ new GestureDetector.SimpleOnGestureListener() {
+ final Rect mActionsRect = new Rect();
+
+ @Override
+ public boolean onScroll(
+ MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
+ View actionsContainer = findViewById(R.id.actions_container);
+ actionsContainer.getBoundsOnScreen(mActionsRect);
+ // return true if we aren't in the actions bar, or if we are but it isn't
+ // scrollable in the direction of movement
+ return !mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY())
+ || !actionsContainer.canScrollHorizontally((int) distanceX);
+ }
+ });
+ mSwipeDetector.setIsLongpressEnabled(false);
+ }
+
+ @Override // View
+ protected void onFinishInflate() {
+
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mSwipeDismissHandler.onTouch(this, ev);
+ }
+
+ return mSwipeDetector.onTouchEvent(ev);
+ }
+
+ public void setOnDismissCallback(Runnable callback) {
+ mOnDismiss = callback;
+ }
+
+ public void setOnInteractionCallback(Runnable callback) {
+ mOnInteraction = callback;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
new file mode 100644
index 0000000..be10c35
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
@@ -0,0 +1,82 @@
+/*
+ * 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.clipboardoverlay;
+
+import static java.util.Objects.requireNonNull;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+import com.android.systemui.R;
+
+/**
+ * Lightweight activity for editing text clipboard contents
+ */
+public class EditTextActivity extends Activity {
+ private EditText mEditText;
+ private ClipboardManager mClipboardManager;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.clipboard_edit_text_activity);
+ findViewById(R.id.copy_button).setOnClickListener((v) -> saveToClipboard());
+ findViewById(R.id.share).setOnClickListener((v) -> share());
+ mEditText = findViewById(R.id.edit_text);
+ mClipboardManager = requireNonNull(getSystemService(ClipboardManager.class));
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ ClipData clip = mClipboardManager.getPrimaryClip();
+ if (clip == null) {
+ finish();
+ return;
+ }
+ // TODO: put clip attribution in R.id.attribution TextView
+ mEditText.setText(clip.getItemAt(0).getText());
+ mEditText.requestFocus();
+ }
+
+ private void saveToClipboard() {
+ ClipData clip = ClipData.newPlainText("text", mEditText.getText());
+ mClipboardManager.setPrimaryClip(clip);
+ hideImeAndFinish();
+ }
+
+ private void share() {
+ Intent sendIntent = new Intent();
+ sendIntent.setAction(Intent.ACTION_SEND);
+ sendIntent.putExtra(Intent.EXTRA_TEXT, mEditText.getText());
+ sendIntent.setType("text/plain");
+
+ Intent shareIntent = Intent.createChooser(sendIntent, null);
+ startActivity(shareIntent);
+ }
+
+ private void hideImeAndFinish() {
+ InputMethodManager imm = getSystemService(InputMethodManager.class);
+ imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
+ finish();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index c0da57f..00491da 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -24,6 +24,7 @@
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.communal.CommunalManagerUpdater;
import com.android.systemui.dreams.DreamOverlayRegistrant;
import com.android.systemui.dreams.appwidgets.ComplicationPrimer;
@@ -73,6 +74,12 @@
@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
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 094b192..5ca2539 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1688,6 +1688,21 @@
|| mUpdateMonitor.isSimPinSecure();
}
+ /**
+ * Whether any of the SIMs on the device are secured with a PIN. If so, the keyguard should not
+ * be dismissable until the PIN is entered, even if the device itself has no lock set.
+ */
+ public boolean isAnySimPinSecure() {
+ for (int i = 0; i < mLastSimStates.size(); i++) {
+ final int key = mLastSimStates.keyAt(i);
+ if (KeyguardUpdateMonitor.isSimPinSecure(mLastSimStates.get(key))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
public void setSwitchingUser(boolean switching) {
mUpdateMonitor.setSwitchingUser(switching);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
index 2e1c9fa..474a81b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
@@ -138,6 +138,7 @@
}
setWakefulness(WAKEFULNESS_AWAKE);
dispatch(Observer::onFinishedWakingUp);
+ dispatch(Observer::onPostFinishedWakingUp);
}
public void dispatchStartedGoingToSleep(@PowerManager.GoToSleepReason int pmSleepReason) {
@@ -236,6 +237,12 @@
public interface Observer {
default void onStartedWakingUp() {}
default void onFinishedWakingUp() {}
+
+ /**
+ * Called after the finished waking up call, ensuring it's after all the other listeners,
+ * reacting to {@link #onFinishedWakingUp()}
+ */
+ default void onPostFinishedWakingUp() {}
default void onStartedGoingToSleep() {}
default void onFinishedGoingToSleep() {}
}
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 558f0e6..d1fe7d4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -16,6 +16,7 @@
package com.android.systemui.media.dagger;
+import android.app.Service;
import android.content.Context;
import android.view.WindowManager;
@@ -30,6 +31,7 @@
import com.android.systemui.media.taptotransfer.MediaTttFlags;
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService;
import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -38,8 +40,11 @@
import javax.inject.Named;
+import dagger.Binds;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
/** Dagger module for the media package. */
@Module
@@ -128,4 +133,10 @@
mediaTttChipControllerReceiver,
mainExecutor));
}
+
+ /** Inject into MediaTttSenderService. */
+ @Binds
+ @IntoMap
+ @ClassKey(MediaTttSenderService.class)
+ Service bindMediaTttSenderService(MediaTttSenderService service);
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 83d581f..4e039279 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -539,8 +539,7 @@
}
boolean isVolumeControlEnabled(@NonNull MediaDevice device) {
- // TODO(b/202500642): Also enable volume control for remote non-group sessions.
- return !isActiveRemoteDevice(device)
+ return !device.getFeatures().contains(MediaRoute2Info.FEATURE_REMOTE_GROUP_PLAYBACK)
|| mVolumeAdjustmentForRemoteGroupSessions;
}
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 5a86723..460d38f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -16,9 +16,14 @@
package com.android.systemui.media.taptotransfer
+import android.content.ComponentName
import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
import android.graphics.Color
import android.graphics.drawable.Icon
+import android.media.MediaRoute2Info
+import android.os.IBinder
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.systemui.R
@@ -27,9 +32,12 @@
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender
-import com.android.systemui.media.taptotransfer.sender.MoveCloserToTransfer
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService
+import com.android.systemui.media.taptotransfer.sender.MoveCloserToStartCast
import com.android.systemui.media.taptotransfer.sender.TransferInitiated
import com.android.systemui.media.taptotransfer.sender.TransferSucceeded
+import com.android.systemui.shared.mediattt.DeviceInfo
+import com.android.systemui.shared.mediattt.IDeviceSenderCallback
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -44,11 +52,14 @@
@SysUISingleton
class MediaTttCommandLineHelper @Inject constructor(
commandRegistry: CommandRegistry,
- context: Context,
+ private val context: Context,
private val mediaTttChipControllerSender: MediaTttChipControllerSender,
private val mediaTttChipControllerReceiver: MediaTttChipControllerReceiver,
@Main private val mainExecutor: DelayableExecutor,
) {
+ private var senderCallback: IDeviceSenderCallback? = null
+ private val senderServiceConnection = SenderServiceConnection()
+
private val appIconDrawable =
Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also {
it.setTint(Color.YELLOW)
@@ -68,14 +79,20 @@
inner class AddChipCommandSender : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
val otherDeviceName = args[0]
+ val mediaInfo = MediaRoute2Info.Builder("id", "Test Name")
+ .addFeature("feature")
+ .build()
+ val otherDeviceInfo = DeviceInfo(otherDeviceName)
+
when (args[1]) {
- MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME -> {
- mediaTttChipControllerSender.displayChip(
- MoveCloserToTransfer(
- appIconDrawable, APP_ICON_CONTENT_DESCRIPTION, otherDeviceName
- )
- )
+ MOVE_CLOSER_TO_START_CAST_COMMAND_NAME -> {
+ runOnService { senderCallback ->
+ senderCallback.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo)
+ }
}
+
+ // TODO(b/203800643): Migrate other commands to invoke the service instead of the
+ // controller.
TRANSFER_INITIATED_COMMAND_NAME -> {
val futureTask = FutureTask { fakeUndoRunnable }
mediaTttChipControllerSender.displayChip(
@@ -101,7 +118,7 @@
}
else -> {
pw.println("Chip type must be one of " +
- "$MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME, " +
+ "$MOVE_CLOSER_TO_START_CAST_COMMAND_NAME, " +
"$TRANSFER_INITIATED_COMMAND_NAME, " +
TRANSFER_SUCCEEDED_COMMAND_NAME
)
@@ -114,19 +131,40 @@
"$ADD_CHIP_COMMAND_SENDER_TAG <deviceName> <chipStatus>"
)
}
+
+ private fun runOnService(command: SenderCallbackCommand) {
+ val currentServiceCallback = senderCallback
+ if (currentServiceCallback != null) {
+ command.run(currentServiceCallback)
+ } else {
+ bindService(command)
+ }
+ }
+
+ private fun bindService(command: SenderCallbackCommand) {
+ senderServiceConnection.pendingCommand = command
+ val binding = context.bindService(
+ Intent(context, MediaTttSenderService::class.java),
+ senderServiceConnection,
+ Context.BIND_AUTO_CREATE
+ )
+ Log.i(TAG, "Starting service binding? $binding")
+ }
}
/** A command to REMOVE the media ttt chip on the SENDER device. */
inner class RemoveChipCommandSender : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
mediaTttChipControllerSender.removeChip()
+ if (senderCallback != null) {
+ context.unbindService(senderServiceConnection)
+ }
}
override fun help(pw: PrintWriter) {
pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_SENDER_TAG")
}
}
-
/** A command to DISPLAY the media ttt chip on the RECEIVER device. */
inner class AddChipCommandReceiver : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
@@ -149,6 +187,29 @@
}
}
+ /** A service connection for [IDeviceSenderCallback]. */
+ private inner class SenderServiceConnection : ServiceConnection {
+ // A command that should be run when the service gets connected.
+ var pendingCommand: SenderCallbackCommand? = null
+
+ override fun onServiceConnected(className: ComponentName, service: IBinder) {
+ val newCallback = IDeviceSenderCallback.Stub.asInterface(service)
+ senderCallback = newCallback
+ pendingCommand?.run(newCallback)
+ pendingCommand = null
+ }
+
+ override fun onServiceDisconnected(className: ComponentName) {
+ senderCallback = null
+ }
+ }
+
+ /** An interface defining a command that should be run on the sender callback. */
+ private fun interface SenderCallbackCommand {
+ /** Runs the command on the provided [senderCallback]. */
+ fun run(senderCallback: IDeviceSenderCallback)
+ }
+
private val fakeUndoRunnable = Runnable {
Log.i(TAG, "Undo runnable triggered")
}
@@ -163,7 +224,7 @@
@VisibleForTesting
const val REMOVE_CHIP_COMMAND_RECEIVER_TAG = "media-ttt-chip-remove-receiver"
@VisibleForTesting
-val MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME = MoveCloserToTransfer::class.simpleName!!
+val MOVE_CLOSER_TO_START_CAST_COMMAND_NAME = MoveCloserToStartCast::class.simpleName!!
@VisibleForTesting
val TRANSFER_INITIATED_COMMAND_NAME = TransferInitiated::class.simpleName!!
@VisibleForTesting
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 b1f6faa..dd434e7 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
@@ -40,17 +40,18 @@
) : MediaTttChipState(appIconDrawable, appIconContentDescription)
/**
- * A state representing that the two devices are close but not close enough to initiate a transfer.
- * The chip will instruct the user to move closer in order to initiate the transfer.
+ * A state representing that the two devices are close but not close enough to *start* a cast to
+ * the receiver device. The chip will instruct the user to move closer in order to initiate the
+ * transfer to the receiver.
*/
-class MoveCloserToTransfer(
+class MoveCloserToStartCast(
appIconDrawable: Drawable,
appIconContentDescription: String,
otherDeviceName: String,
) : ChipStateSender(
appIconDrawable,
appIconContentDescription,
- R.string.media_move_closer_to_transfer,
+ R.string.media_move_closer_to_start_cast,
otherDeviceName
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt
new file mode 100644
index 0000000..b56a699
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.taptotransfer.sender
+
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.graphics.Color
+import android.graphics.drawable.Icon
+import android.media.MediaRoute2Info
+import android.os.IBinder
+import com.android.systemui.R
+import com.android.systemui.shared.mediattt.DeviceInfo
+import com.android.systemui.shared.mediattt.IDeviceSenderCallback
+import javax.inject.Inject
+
+/**
+ * Service that allows external handlers to trigger the media chip on the sender device.
+ */
+class MediaTttSenderService @Inject constructor(
+ context: Context,
+ val controller: MediaTttChipControllerSender
+) : Service() {
+
+ // TODO(b/203800643): Add logging when callbacks trigger.
+ private val binder: IBinder = object : IDeviceSenderCallback.Stub() {
+ override fun closeToReceiverToStartCast(
+ mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
+ ) {
+ this@MediaTttSenderService.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo)
+ }
+ }
+
+ // TODO(b/203800643): Use the app icon from the media info instead of a fake one.
+ private val fakeAppIconDrawable =
+ Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also {
+ it.setTint(Color.YELLOW)
+ }
+
+ override fun onBind(intent: Intent?): IBinder = binder
+
+ private fun closeToReceiverToStartCast(
+ mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
+ ) {
+ val chipState = MoveCloserToStartCast(
+ appIconDrawable = fakeAppIconDrawable,
+ appIconContentDescription = mediaInfo.name.toString(),
+ otherDeviceName = otherDeviceInfo.name
+ )
+ controller.displayChip(chipState)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 866b1b8..d3f8db3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -247,10 +247,9 @@
boolean shouldUseSplitShade =
resources.getBoolean(R.bool.config_use_split_notification_shade);
- mStatusIconsView.setVisibility(
- shouldUseSplitShade || mUseCombinedQSHeader ? View.GONE : View.VISIBLE);
- mDatePrivacyView.setVisibility(
- shouldUseSplitShade || mUseCombinedQSHeader ? View.GONE : View.VISIBLE);
+ boolean gone = shouldUseSplitShade || mUseCombinedQSHeader || mQsDisabled;
+ mStatusIconsView.setVisibility(gone ? View.GONE : View.VISIBLE);
+ mDatePrivacyView.setVisibility(gone ? View.GONE : View.VISIBLE);
mConfigShowBatteryEstimate = resources.getBoolean(R.bool.config_showBatteryEstimateQSBH);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/FloatingWindowUtil.java b/packages/SystemUI/src/com/android/systemui/screenshot/FloatingWindowUtil.java
new file mode 100644
index 0000000..3dec387
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/FloatingWindowUtil.java
@@ -0,0 +1,75 @@
+/*
+ * 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.screenshot;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.util.DisplayMetrics;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.android.internal.policy.PhoneWindow;
+
+/**
+ * Utility methods for setting up a floating window
+ */
+public class FloatingWindowUtil {
+
+ /**
+ * Convert input dp to pixels given DisplayMetrics
+ */
+ public static float dpToPx(DisplayMetrics metrics, float dp) {
+ return dp * metrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT;
+ }
+
+ /**
+ * Sets up window params for a floating window
+ */
+ public static WindowManager.LayoutParams getFloatingWindowParams() {
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ MATCH_PARENT, MATCH_PARENT, /* xpos */ 0, /* ypos */ 0, TYPE_SCREENSHOT,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+ PixelFormat.TRANSLUCENT);
+ params.layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ params.setFitInsetsTypes(0);
+ // This is needed to let touches pass through outside the touchable areas
+ params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+ return params;
+ }
+
+ /**
+ * Constructs a transparent floating window
+ */
+ public static PhoneWindow getFloatingWindow(Context context) {
+ PhoneWindow window = new PhoneWindow(context);
+ window.requestFeature(Window.FEATURE_NO_TITLE);
+ window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);
+ window.setBackgroundDrawableResource(android.R.color.transparent);
+ return window;
+ }
+
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
index 4a1aa16..6c01f0e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
@@ -70,19 +70,28 @@
super.setPressed(mIsPending || pressed);
}
- void setIcon(Icon icon, boolean tint) {
+ /**
+ * Set chip icon and whether to tint with theme color
+ */
+ public void setIcon(Icon icon, boolean tint) {
mIconView.setImageIcon(icon);
if (!tint) {
mIconView.setImageTintList(null);
}
}
- void setText(CharSequence text) {
+ /**
+ * Set chip text
+ */
+ public void setText(CharSequence text) {
mTextView.setText(text);
updatePadding(text.length() > 0);
}
- void setPendingIntent(PendingIntent intent, Runnable finisher) {
+ /**
+ * Set PendingIntent to be sent and Runnable to be run, when chip is clicked
+ */
+ public void setPendingIntent(PendingIntent intent, Runnable finisher) {
setOnClickListener(v -> {
try {
intent.send();
@@ -93,7 +102,10 @@
});
}
- void setIsPending(boolean isPending) {
+ /**
+ * Set pressed state of chip (to be used when chip is clicked before underlying intent is ready)
+ */
+ public void setIsPending(boolean isPending) {
mIsPending = isPending;
setPressed(mIsPending);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index ce571e5..83d8d19 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -18,7 +18,6 @@
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
@@ -41,23 +40,21 @@
import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks;
import android.app.ICompatCameraControlCallback;
import android.app.Notification;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Insets;
-import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.media.MediaActionSound;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
import android.os.RemoteException;
import android.provider.Settings;
import android.util.DisplayMetrics;
@@ -76,7 +73,6 @@
import android.view.View;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
-import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
@@ -90,6 +86,7 @@
import com.android.internal.policy.PhoneWindow;
import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.R;
+import com.android.systemui.clipboardoverlay.ClipboardOverlayController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
@@ -235,13 +232,11 @@
static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
-
- private static final int MESSAGE_CORNER_TIMEOUT = 2;
- private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
-
// From WizardManagerHelper.java
private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
+ private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
+
private final WindowContext mContext;
private final ScreenshotNotificationsController mNotificationsController;
private final ScreenshotSmartActions mScreenshotSmartActions;
@@ -260,6 +255,7 @@
private final ScrollCaptureController mScrollCaptureController;
private final LongScreenshotData mLongScreenshotHolder;
private final boolean mIsLowRamDevice;
+ private final TimeoutHandler mScreenshotHandler;
private ScreenshotView mScreenshotView;
private Bitmap mScreenBitmap;
@@ -270,24 +266,8 @@
private Animator mScreenshotAnimation;
private RequestCallback mCurrentRequestCallback;
private String mPackageName = "";
+ private BroadcastReceiver mCopyBroadcastReceiver;
- private final Handler mScreenshotHandler = new Handler(Looper.getMainLooper()) {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MESSAGE_CORNER_TIMEOUT:
- if (DEBUG_UI) {
- Log.d(TAG, "Corner timeout hit");
- }
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT, 0,
- mPackageName);
- ScreenshotController.this.dismissScreenshot(false);
- break;
- default:
- break;
- }
- }
- };
/** Tracks config changes that require re-creating UI */
private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
@@ -309,7 +289,8 @@
@Main Executor mainExecutor,
ScrollCaptureController scrollCaptureController,
LongScreenshotData longScreenshotHolder,
- ActivityManager activityManager) {
+ ActivityManager activityManager,
+ TimeoutHandler timeoutHandler) {
mScreenshotSmartActions = screenshotSmartActions;
mNotificationsController = screenshotNotificationsController;
mScrollCaptureClient = scrollCaptureClient;
@@ -321,6 +302,17 @@
mIsLowRamDevice = activityManager.isLowRamDevice();
mBgExecutor = Executors.newSingleThreadExecutor();
+ mScreenshotHandler = timeoutHandler;
+ mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
+ mScreenshotHandler.setOnTimeoutRunnable(() -> {
+ if (DEBUG_UI) {
+ Log.d(TAG, "Corner timeout hit");
+ }
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT, 0,
+ mPackageName);
+ ScreenshotController.this.dismissScreenshot(false);
+ });
+
mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
final Context displayContext = context.createDisplayContext(getDefaultDisplay());
mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
@@ -329,27 +321,11 @@
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
// Setup the window that we are going to use
- mWindowLayoutParams = new WindowManager.LayoutParams(
- MATCH_PARENT, MATCH_PARENT, /* xpos */ 0, /* ypos */ 0, TYPE_SCREENSHOT,
- WindowManager.LayoutParams.FLAG_FULLSCREEN
- | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
- PixelFormat.TRANSLUCENT);
+ mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
mWindowLayoutParams.setTitle("ScreenshotAnimation");
- mWindowLayoutParams.layoutInDisplayCutoutMode =
- WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- mWindowLayoutParams.setFitInsetsTypes(0);
- // This is needed to let touches pass through outside the touchable areas
- mWindowLayoutParams.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
- mWindow = new PhoneWindow(mContext);
+ mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
mWindow.setWindowManager(mWindowManager, null, null);
- mWindow.requestFeature(Window.FEATURE_NO_TITLE);
- mWindow.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);
- mWindow.setBackgroundDrawableResource(android.R.color.transparent);
mConfigChanges.applyNewConfig(context.getResources());
reloadAssets();
@@ -357,6 +333,18 @@
// Setup the Camera shutter sound
mCameraSound = new MediaActionSound();
mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
+
+ mCopyBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ClipboardOverlayController.COPY_OVERLAY_ACTION.equals(intent.getAction())) {
+ dismissScreenshot(false);
+ }
+ }
+ };
+ mContext.registerReceiver(mCopyBroadcastReceiver, new IntentFilter(
+ ClipboardOverlayController.COPY_OVERLAY_ACTION),
+ ClipboardOverlayController.SELF_PERMISSION, null);
}
void takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher,
@@ -424,7 +412,7 @@
}
return;
}
- cancelTimeout();
+ mScreenshotHandler.cancelTimeout();
if (immediate) {
finishDismiss();
} else {
@@ -440,6 +428,7 @@
* Release the constructed window context.
*/
void releaseContext() {
+ mContext.unregisterReceiver(mCopyBroadcastReceiver);
mContext.release();
mCameraSound.release();
mBgExecutor.shutdownNow();
@@ -462,7 +451,7 @@
if (DEBUG_INPUT) {
Log.d(TAG, "onUserInteraction");
}
- resetTimeout();
+ mScreenshotHandler.resetTimeout();
}
@Override
@@ -517,6 +506,9 @@
}
saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true);
+
+ mContext.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION),
+ ClipboardOverlayController.SELF_PERMISSION);
}
private Bitmap captureScreenshot(Rect crop) {
@@ -651,7 +643,7 @@
// ignore system bar insets for the purpose of window layout
mWindow.getDecorView().setOnApplyWindowInsetsListener(
(v, insets) -> WindowInsets.CONSUMED);
- cancelTimeout(); // restarted after animation
+ mScreenshotHandler.cancelTimeout(); // restarted after animation
}
private void requestScrollCapture() {
@@ -878,7 +870,7 @@
}
mScreenshotView.reset();
removeWindow();
- cancelTimeout();
+ mScreenshotHandler.cancelTimeout();
}
/**
@@ -905,30 +897,6 @@
mSaveInBgTask.execute();
}
- private void cancelTimeout() {
- if (DEBUG_DISMISS) {
- Log.d(TAG, "cancel timeout");
- }
- mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
- }
-
- private void resetTimeout() {
- cancelTimeout();
-
- AccessibilityManager accessibilityManager = (AccessibilityManager)
- mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
- long timeoutMs = accessibilityManager.getRecommendedTimeoutMillis(
- SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS,
- AccessibilityManager.FLAG_CONTENT_CONTROLS);
-
- mScreenshotHandler.sendMessageDelayed(
- mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
- timeoutMs);
- if (DEBUG_DISMISS) {
- Log.d(TAG, "dismiss timeout: " + timeoutMs + " ms");
- }
-
- }
/**
* Sets up the action shade and its entrance animation, once we get the screenshot URI.
@@ -939,7 +907,7 @@
Log.d(TAG, "Showing UI actions");
}
- resetTimeout();
+ mScreenshotHandler.resetTimeout();
if (imageData.uri != null) {
mScreenshotHandler.post(() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 7bcaf5f..e5649a1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -222,7 +222,6 @@
}
});
mSwipeDetector.setIsLongpressEnabled(false);
- mSwipeDismissHandler = new SwipeDismissHandler();
addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -244,7 +243,7 @@
* Called to display the scroll action chip when support is detected.
*
* @param packageName the owning package of the window to be captured
- * @param onClick the action to take when the chip is clicked.
+ * @param onClick the action to take when the chip is clicked.
*/
public void showScrollChip(String packageName, Runnable onClick) {
if (DEBUG_SCROLL) {
@@ -273,10 +272,12 @@
final Rect tmpRect = new Rect();
mScreenshotPreview.getBoundsOnScreen(tmpRect);
- tmpRect.inset((int) dpToPx(-SWIPE_PADDING_DP), (int) dpToPx(-SWIPE_PADDING_DP));
+ tmpRect.inset((int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
touchRegion.op(tmpRect, Region.Op.UNION);
mActionsContainerBackground.getBoundsOnScreen(tmpRect);
- tmpRect.inset((int) dpToPx(-SWIPE_PADDING_DP), (int) dpToPx(-SWIPE_PADDING_DP));
+ tmpRect.inset((int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
touchRegion.op(tmpRect, Region.Op.UNION);
mDismissButton.getBoundsOnScreen(tmpRect);
touchRegion.op(tmpRect, Region.Op.UNION);
@@ -365,7 +366,7 @@
mEditChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_edit_chip));
mScrollChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_scroll_chip));
- int swipePaddingPx = (int) dpToPx(SWIPE_PADDING_DP);
+ int swipePaddingPx = (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, SWIPE_PADDING_DP);
TouchDelegate previewDelegate = new TouchDelegate(
new Rect(swipePaddingPx, swipePaddingPx, swipePaddingPx, swipePaddingPx),
mScreenshotPreview);
@@ -390,6 +391,24 @@
// Get focus so that the key events go to the layout.
setFocusableInTouchMode(true);
requestFocus();
+
+ mSwipeDismissHandler = new SwipeDismissHandler(mContext, mScreenshotStatic,
+ new SwipeDismissHandler.SwipeDismissCallbacks() {
+ @Override
+ public void onInteraction() {
+ mCallbacks.onUserInteraction();
+ }
+
+ @Override
+ public void onDismiss() {
+ if (DEBUG_DISMISS) {
+ Log.d(ScreenshotView.TAG, "dismiss triggered via swipe gesture");
+ }
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, 0,
+ mPackageName);
+ mCallbacks.onDismiss();
+ }
+ });
}
View getScreenshotPreview() {
@@ -859,8 +878,8 @@
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- mCallbacks.onDismiss();
- }
+ mCallbacks.onDismiss();
+ }
});
animSet.start();
}
@@ -934,38 +953,7 @@
}
void animateDismissal() {
- animateDismissal(createScreenshotTranslateDismissAnimation());
- }
-
- private void animateDismissal(Animator dismissAnimation) {
- mDismissAnimation = dismissAnimation;
- mDismissAnimation.addListener(new AnimatorListenerAdapter() {
- private boolean mCancelled = false;
-
- @Override
- public void onAnimationCancel(Animator animation) {
- super.onAnimationCancel(animation);
- if (DEBUG_ANIM) {
- Log.d(TAG, "Cancelled dismiss animation");
- }
- mCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- if (!mCancelled) {
- if (DEBUG_ANIM) {
- Log.d(TAG, "after dismiss animation, calling onDismissRunnable.run()");
- }
- mCallbacks.onDismiss();
- }
- }
- });
- if (DEBUG_ANIM) {
- Log.d(TAG, "Starting dismiss animation");
- }
- mDismissAnimation.start();
+ mSwipeDismissHandler.dismiss();
}
void reset() {
@@ -979,6 +967,7 @@
}
mDismissAnimation.cancel();
}
+ mSwipeDismissHandler.cancel();
if (DEBUG_WINDOW) {
Log.d(TAG, "removing OnComputeInternalInsetsListener");
}
@@ -1042,8 +1031,8 @@
xAnim.setInterpolator(mAccelerateInterpolator);
xAnim.setDuration(SCREENSHOT_DISMISS_X_DURATION_MS);
float deltaX = mDirectionLTR
- ? -1 * (mScreenshotPreviewBorder.getX() + mScreenshotPreviewBorder.getWidth())
- : (mDisplayMetrics.widthPixels - mScreenshotPreviewBorder.getX());
+ ? -1 * (mScreenshotPreviewBorder.getX() + mScreenshotPreviewBorder.getWidth())
+ : (mDisplayMetrics.widthPixels - mScreenshotPreviewBorder.getX());
xAnim.addUpdateListener(animation -> {
float currXDelta = MathUtils.lerp(0, deltaX, animation.getAnimatedFraction());
mScreenshotStatic.setTranslationX(currXDelta);
@@ -1100,130 +1089,4 @@
return insetDrawable;
}
}
-
- private float dpToPx(float dp) {
- return dp * mDisplayMetrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT;
- }
-
- class SwipeDismissHandler implements OnTouchListener {
- // distance needed to register a dismissal
- private static final float DISMISS_DISTANCE_THRESHOLD_DP = 20;
-
- private final GestureDetector mGestureDetector;
-
- private float mStartX;
- // Keeps track of the most recent direction (between the last two move events).
- // -1 for left; +1 for right.
- private int mDirectionX;
- private float mPreviousX;
-
- SwipeDismissHandler() {
- GestureDetector.OnGestureListener gestureListener = new SwipeDismissGestureListener();
- mGestureDetector = new GestureDetector(mContext, gestureListener);
- }
-
- @Override
- public boolean onTouch(View view, MotionEvent event) {
- boolean gestureResult = mGestureDetector.onTouchEvent(event);
- mCallbacks.onUserInteraction();
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mStartX = event.getRawX();
- mPreviousX = mStartX;
- return true;
- } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
- if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
- return true;
- }
- if (isPastDismissThreshold()) {
- if (DEBUG_DISMISS) {
- Log.d(TAG, "dismiss triggered via swipe gesture");
- }
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, 0, mPackageName);
- animateDismissal(createSwipeDismissAnimation());
- } else {
- // if we've moved, but not past the threshold, start the return animation
- if (DEBUG_DISMISS) {
- Log.d(TAG, "swipe gesture abandoned");
- }
- createSwipeReturnAnimation().start();
- }
- return true;
- }
- return gestureResult;
- }
-
- class SwipeDismissGestureListener extends GestureDetector.SimpleOnGestureListener {
- @Override
- public boolean onScroll(
- MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
- mScreenshotStatic.setTranslationX(ev2.getRawX() - mStartX);
- mDirectionX = (ev2.getRawX() < mPreviousX) ? -1 : 1;
- mPreviousX = ev2.getRawX();
- return true;
- }
-
- @Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
- float velocityY) {
- if (mScreenshotStatic.getTranslationX() * velocityX > 0
- && (mDismissAnimation == null || !mDismissAnimation.isRunning())) {
- animateDismissal(createSwipeDismissAnimation(velocityX / (float) 1000));
- return true;
- }
- return false;
- }
- }
-
- private boolean isPastDismissThreshold() {
- float translationX = mScreenshotStatic.getTranslationX();
- // Determines whether the absolute translation from the start is in the same direction
- // as the current movement. For example, if the user moves most of the way to the right,
- // but then starts dragging back left, we do not dismiss even though the absolute
- // distance is greater than the threshold.
- if (translationX * mDirectionX > 0) {
- return Math.abs(translationX) >= dpToPx(DISMISS_DISTANCE_THRESHOLD_DP);
- }
- return false;
- }
-
- private ValueAnimator createSwipeDismissAnimation() {
- return createSwipeDismissAnimation(1);
- }
-
- private ValueAnimator createSwipeDismissAnimation(float velocity) {
- // velocity is measured in pixels per millisecond
- velocity = Math.min(3, Math.max(1, velocity));
- ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
- float startX = mScreenshotStatic.getTranslationX();
- // make sure the UI gets all the way off the screen in the direction of movement
- // (the actions container background is guaranteed to be both the leftmost and
- // rightmost UI element in LTR and RTL)
- float finalX = startX < 0
- ? -1 * mActionsContainerBackground.getRight()
- : mDisplayMetrics.widthPixels;
- float distance = Math.abs(finalX - startX);
-
- anim.addUpdateListener(animation -> {
- float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction());
- mScreenshotStatic.setTranslationX(translation);
- setAlpha(1 - animation.getAnimatedFraction());
- });
- anim.setDuration((long) (distance / Math.abs(velocity)));
- return anim;
- }
-
- private ValueAnimator createSwipeReturnAnimation() {
- ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
- float startX = mScreenshotStatic.getTranslationX();
- float finalX = 0;
-
- anim.addUpdateListener(animation -> {
- float translation = MathUtils.lerp(
- startX, finalX, animation.getAnimatedFraction());
- mScreenshotStatic.setTranslationX(translation);
- });
-
- return anim;
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java
new file mode 100644
index 0000000..4e96003
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java
@@ -0,0 +1,210 @@
+/*
+ * 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.screenshot;
+
+import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.MathUtils;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * Allows a view to be swipe-dismissed, or returned to its location if distance threshold is not met
+ */
+public class SwipeDismissHandler implements View.OnTouchListener {
+ private static final String TAG = "SwipeDismissHandler";
+
+ // distance needed to register a dismissal
+ private static final float DISMISS_DISTANCE_THRESHOLD_DP = 20;
+
+ /**
+ * Stores the callbacks when the view is interacted with or dismissed.
+ */
+ public interface SwipeDismissCallbacks {
+ /**
+ * Run when the view is interacted with (touched)
+ */
+ void onInteraction();
+
+ /**
+ * Run when the view is dismissed (the distance threshold is met), post-dismissal animation
+ */
+ void onDismiss();
+ }
+
+ private final View mView;
+ private final SwipeDismissCallbacks mCallbacks;
+ private final GestureDetector mGestureDetector;
+ private DisplayMetrics mDisplayMetrics;
+ private ValueAnimator mDismissAnimation;
+
+
+ private float mStartX;
+ // Keeps track of the most recent direction (between the last two move events).
+ // -1 for left; +1 for right.
+ private int mDirectionX;
+ private float mPreviousX;
+
+ public SwipeDismissHandler(Context context, View view, SwipeDismissCallbacks callbacks) {
+ mView = view;
+ mCallbacks = callbacks;
+ GestureDetector.OnGestureListener gestureListener = new SwipeDismissGestureListener();
+ mGestureDetector = new GestureDetector(context, gestureListener);
+ mDisplayMetrics = new DisplayMetrics();
+ context.getDisplay().getRealMetrics(mDisplayMetrics);
+ }
+
+ @Override
+ public boolean onTouch(View view, MotionEvent event) {
+ boolean gestureResult = mGestureDetector.onTouchEvent(event);
+ mCallbacks.onInteraction();
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mStartX = event.getRawX();
+ mPreviousX = mStartX;
+ return true;
+ } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+ if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
+ return true;
+ }
+ if (isPastDismissThreshold()) {
+ dismiss();
+ } else {
+ // if we've moved, but not past the threshold, start the return animation
+ if (DEBUG_DISMISS) {
+ Log.d(TAG, "swipe gesture abandoned");
+ }
+ createSwipeReturnAnimation().start();
+ }
+ return true;
+ }
+ return gestureResult;
+ }
+
+ class SwipeDismissGestureListener extends GestureDetector.SimpleOnGestureListener {
+ @Override
+ public boolean onScroll(
+ MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
+ mView.setTranslationX(ev2.getRawX() - mStartX);
+ mDirectionX = (ev2.getRawX() < mPreviousX) ? -1 : 1;
+ mPreviousX = ev2.getRawX();
+ return true;
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ if (mView.getTranslationX() * velocityX > 0
+ && (mDismissAnimation == null || !mDismissAnimation.isRunning())) {
+ dismiss(velocityX / (float) 1000);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private boolean isPastDismissThreshold() {
+ float translationX = mView.getTranslationX();
+ // Determines whether the absolute translation from the start is in the same direction
+ // as the current movement. For example, if the user moves most of the way to the right,
+ // but then starts dragging back left, we do not dismiss even though the absolute
+ // distance is greater than the threshold.
+ if (translationX * mDirectionX > 0) {
+ return Math.abs(translationX) >= FloatingWindowUtil.dpToPx(mDisplayMetrics,
+ DISMISS_DISTANCE_THRESHOLD_DP);
+ }
+ return false;
+ }
+
+ /**
+ * Cancel the currently-running dismissal animation, if any.
+ */
+ public void cancel() {
+ if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
+ mDismissAnimation.cancel();
+ }
+ }
+
+ /**
+ * Start dismissal animation (will run onDismiss callback when animation complete)
+ */
+ public void dismiss() {
+ dismiss(1);
+ }
+
+ private void dismiss(float velocity) {
+ mDismissAnimation = createSwipeDismissAnimation(velocity);
+ mDismissAnimation.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ super.onAnimationCancel(animation);
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (!mCancelled) {
+ mCallbacks.onDismiss();
+ }
+ }
+ });
+ mDismissAnimation.start();
+ }
+
+ private ValueAnimator createSwipeDismissAnimation(float velocity) {
+ // velocity is measured in pixels per millisecond
+ velocity = Math.min(3, Math.max(1, velocity));
+ ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+ float startX = mView.getTranslationX();
+ // make sure the UI gets all the way off the screen in the direction of movement
+ // (the actions container background is guaranteed to be both the leftmost and
+ // rightmost UI element in LTR and RTL)
+ float finalX = startX <= 0 ? -1 * mView.getRight() : mDisplayMetrics.widthPixels;
+ float distance = Math.abs(finalX - startX);
+
+ anim.addUpdateListener(animation -> {
+ float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction());
+ mView.setTranslationX(translation);
+ mView.setAlpha(1 - animation.getAnimatedFraction());
+ });
+ anim.setDuration((long) (distance / Math.abs(velocity)));
+ return anim;
+ }
+
+ private ValueAnimator createSwipeReturnAnimation() {
+ ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+ float startX = mView.getTranslationX();
+ float finalX = 0;
+
+ anim.addUpdateListener(animation -> {
+ float translation = MathUtils.lerp(
+ startX, finalX, animation.getAnimatedFraction());
+ mView.setTranslationX(translation);
+ });
+
+ return anim;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 84e21e4..98e6bd1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -83,7 +83,7 @@
/** Informs about coarse grained state of the Controller. */
interface RequestCallback {
- /** Respond to the current request indicating the screenshot request failed.*/
+ /** Respond to the current request indicating the screenshot request failed. */
void reportError();
/** The controller has completed handling this request UI has been removed */
@@ -230,7 +230,7 @@
return false;
}
return true;
- };
+ }
private static void sendComplete(Messenger target) {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
new file mode 100644
index 0000000..9156601
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
@@ -0,0 +1,102 @@
+/*
+ * 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.screenshot;
+
+import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.accessibility.AccessibilityManager;
+
+import javax.inject.Inject;
+
+/**
+ * Starts a configurable runnable on timeout. Can be cancelled. Used for automatically dismissing
+ * floating overlays.
+ */
+public class TimeoutHandler extends Handler {
+ private static final String TAG = "TimeoutHandler";
+
+ private static final int MESSAGE_CORNER_TIMEOUT = 2;
+ private static final int DEFAULT_TIMEOUT_MILLIS = 6000;
+
+ private final Context mContext;
+
+ private Runnable mOnTimeout;
+ private int mDefaultTimeout = DEFAULT_TIMEOUT_MILLIS;
+
+ @Inject
+ public TimeoutHandler(Context context) {
+ super(Looper.getMainLooper());
+ mContext = context;
+ mOnTimeout = () -> {
+ };
+ }
+
+ public void setOnTimeoutRunnable(Runnable onTimeout) {
+ mOnTimeout = onTimeout;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_CORNER_TIMEOUT:
+ mOnTimeout.run();
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Set the default timeout (if not overridden by accessibility)
+ */
+ public void setDefaultTimeoutMillis(int timeout) {
+ mDefaultTimeout = timeout;
+ }
+
+ /**
+ * Cancel the current timeout, if any. To reset the delayed runnable use resetTimeout instead.
+ */
+ public void cancelTimeout() {
+ if (DEBUG_DISMISS) {
+ Log.d(TAG, "cancel timeout");
+ }
+ removeMessages(MESSAGE_CORNER_TIMEOUT);
+ }
+
+ /**
+ * Reset the timeout.
+ */
+ public void resetTimeout() {
+ cancelTimeout();
+
+ AccessibilityManager accessibilityManager = (AccessibilityManager)
+ mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ long timeoutMs = accessibilityManager.getRecommendedTimeoutMillis(
+ mDefaultTimeout,
+ AccessibilityManager.FLAG_CONTENT_CONTROLS);
+
+ sendMessageDelayed(obtainMessage(MESSAGE_CORNER_TIMEOUT), timeoutMs);
+ if (DEBUG_DISMISS) {
+ Log.d(TAG, "dismiss timeout: " + timeoutMs + " ms");
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 648e14c..c136d9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -7,7 +7,6 @@
import android.content.Context
import android.content.res.Configuration
import android.os.SystemClock
-import android.util.DisplayMetrics
import android.util.IndentingPrintWriter
import android.util.MathUtils
import android.view.MotionEvent
@@ -24,6 +23,7 @@
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.media.MediaHierarchyManager
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.plugins.FalsingManager
@@ -64,6 +64,7 @@
private val scrimController: ScrimController,
private val depthController: NotificationShadeDepthController,
private val context: Context,
+ wakefulnessLifecycle: WakefulnessLifecycle,
configurationController: ConfigurationController,
falsingManager: FalsingManager,
dumpManager: DumpManager,
@@ -120,6 +121,12 @@
private var nextHideKeyguardNeedsNoAnimation = false
/**
+ * Are we currently waking up to the shade locked
+ */
+ var isWakingToShadeLocked: Boolean = false
+ private set
+
+ /**
* The distance until we're showing the notifications when pulsing
*/
val distanceUntilShowingPulsingNotifications
@@ -160,6 +167,13 @@
}
}
})
+ wakefulnessLifecycle.addObserver(object : WakefulnessLifecycle.Observer {
+ override fun onPostFinishedWakingUp() {
+ // when finishing waking up, the UnlockedScreenOffAnimation has another attempt
+ // to reset keyguard. Let's do it in post
+ isWakingToShadeLocked = false
+ }
+ })
}
private fun updateResources() {
@@ -494,6 +508,10 @@
draggedDownEntry = entry
} else {
logger.logGoingToLockedShade(animationHandler != null)
+ if (statusBarStateController.isDozing) {
+ // Make sure we don't go back to keyguard immediately again after waking up
+ isWakingToShadeLocked = true
+ }
statusBarStateController.setState(StatusBarState.SHADE_LOCKED)
// This call needs to be after updating the shade state since otherwise
// the scrimstate resets too early
@@ -605,6 +623,7 @@
it.println("qSDragProgress: $qSDragProgress")
it.println("isDragDownAnywhereEnabled: $isDragDownAnywhereEnabled")
it.println("isFalsingCheckNeeded: $isFalsingCheckNeeded")
+ it.println("isWakingToShadeLocked: $isWakingToShadeLocked")
it.println("hasPendingHandlerOnKeyguardDismiss: " +
"${animationHandlerOnKeyguardDismiss != null}")
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index ea51bd8..3fe108f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -107,8 +107,6 @@
private var mDraggedFarEnough: Boolean = false
private var mStartingChild: ExpandableView? = null
private var mPulsing: Boolean = false
- var isWakingToShadeLocked: Boolean = false
- private set
private var velocityTracker: VelocityTracker? = null
@@ -235,7 +233,6 @@
mStartingChild = null
}
if (statusBarStateController.isDozing) {
- isWakingToShadeLocked = true
wakeUpCoordinator.willWakeUp = true
mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), WAKE_REASON_GESTURE,
"com.android.systemui:PULSEDRAG")
@@ -333,10 +330,6 @@
mPulsing = pulsing
}
- fun onStartedWakingUp() {
- isWakingToShadeLocked = false
- }
-
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
IndentingPrintWriter(pw, " ").let {
it.println("PulseExpansionHandler:")
@@ -344,7 +337,6 @@
it.println("isExpanding: $isExpanding")
it.println("leavingLockscreen: $leavingLockscreen")
it.println("mPulsing: $mPulsing")
- it.println("isWakingToShadeLocked: $isWakingToShadeLocked")
it.println("qsExpanded: $qsExpanded")
it.println("bouncerShowing: $bouncerShowing")
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 5272a16..300c3a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -334,6 +334,8 @@
setUserSetupComplete(deviceProvisionedController.isCurrentUserSetup());
}
});
+ // Get initial user setup state
+ setUserSetupComplete(deviceProvisionedController.isCurrentUserSetup());
WifiManager.ScanResultsCallback scanResultsCallback =
new WifiManager.ScanResultsCallback() {
@@ -994,6 +996,11 @@
}
@VisibleForTesting
+ boolean isUserSetup() {
+ return mUserSetup;
+ }
+
+ @VisibleForTesting
boolean hasCorrectMobileControllers(List<SubscriptionInfo> allSubscriptions) {
if (allSubscriptions.size() != mMobileSignalControllers.size()) {
return false;
@@ -1137,6 +1144,7 @@
/** */
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("NetworkController state:");
+ pw.println(" mUserSetup=" + mUserSetup);
pw.println(" - telephony ------");
pw.print(" hasVoiceCallingFeature()=");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index b0d41f1..3449bd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -32,6 +32,8 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationContentView
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
@@ -132,12 +134,15 @@
/**
* Tracks state related to conversation notifications, and updates the UI of existing notifications
* when necessary.
+ * TODO(b/214083332) Refactor this class to use the right coordinators and controllers
*/
@SysUISingleton
class ConversationNotificationManager @Inject constructor(
private val notificationEntryManager: NotificationEntryManager,
private val notificationGroupManager: NotificationGroupManagerLegacy,
private val context: Context,
+ private val notifCollection: CommonNotifCollection,
+ private val featureFlags: NotifPipelineFlags,
@Main private val mainHandler: Handler
) {
// Need this state to be thread safe, since it's accessed from the ui thread
@@ -146,76 +151,93 @@
private var notifPanelCollapsed = true
+ private val entryManagerListener = object : NotificationEntryListener {
+ override fun onNotificationRankingUpdated(rankingMap: RankingMap) =
+ updateNotificationRanking(rankingMap)
+ override fun onEntryInflated(entry: NotificationEntry) =
+ onEntryViewBound(entry)
+ override fun onEntryReinflated(entry: NotificationEntry) = onEntryInflated(entry)
+ override fun onEntryRemoved(
+ entry: NotificationEntry,
+ visibility: NotificationVisibility?,
+ removedByUser: Boolean,
+ reason: Int
+ ) = removeTrackedEntry(entry)
+ }
+
+ private val notifCollectionListener = object : NotifCollectionListener {
+ override fun onRankingUpdate(ranking: RankingMap) =
+ updateNotificationRanking(ranking)
+
+ override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+ removeTrackedEntry(entry)
+ }
+ }
+
+ private fun updateNotificationRanking(rankingMap: RankingMap) {
+ fun getLayouts(view: NotificationContentView) =
+ sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild)
+ val ranking = Ranking()
+ val activeConversationEntries = states.keys.asSequence()
+ .mapNotNull { notificationEntryManager.getActiveNotificationUnfiltered(it) }
+ for (entry in activeConversationEntries) {
+ if (rankingMap.getRanking(entry.sbn.key, ranking) && ranking.isConversation) {
+ val important = ranking.channel.isImportantConversation
+ var changed = false
+ entry.row?.layouts?.asSequence()
+ ?.flatMap(::getLayouts)
+ ?.mapNotNull { it as? ConversationLayout }
+ ?.filterNot { it.isImportantConversation == important }
+ ?.forEach { layout ->
+ changed = true
+ if (important && entry.isMarkedForUserTriggeredMovement) {
+ // delay this so that it doesn't animate in until after
+ // the notif has been moved in the shade
+ mainHandler.postDelayed(
+ {
+ layout.setIsImportantConversation(
+ important,
+ true)
+ },
+ IMPORTANCE_ANIMATION_DELAY.toLong())
+ } else {
+ layout.setIsImportantConversation(important, false)
+ }
+ }
+ if (changed) {
+ notificationGroupManager.updateIsolation(entry)
+ }
+ }
+ }
+ }
+ fun onEntryViewBound(entry: NotificationEntry) {
+ if (!entry.ranking.isConversation) {
+ return
+ }
+ fun updateCount(isExpanded: Boolean) {
+ if (isExpanded && (!notifPanelCollapsed || entry.isPinnedAndExpanded)) {
+ resetCount(entry.key)
+ entry.row?.let(::resetBadgeUi)
+ }
+ }
+ entry.row?.setOnExpansionChangedListener { isExpanded ->
+ if (entry.row?.isShown == true && isExpanded) {
+ entry.row.performOnIntrinsicHeightReached {
+ updateCount(isExpanded)
+ }
+ } else {
+ updateCount(isExpanded)
+ }
+ }
+ updateCount(entry.row?.isExpanded == true)
+ }
+
init {
- notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener {
- override fun onNotificationRankingUpdated(rankingMap: RankingMap) {
- fun getLayouts(view: NotificationContentView) =
- sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild)
- val ranking = Ranking()
- val activeConversationEntries = states.keys.asSequence()
- .mapNotNull { notificationEntryManager.getActiveNotificationUnfiltered(it) }
- for (entry in activeConversationEntries) {
- if (rankingMap.getRanking(entry.sbn.key, ranking) && ranking.isConversation) {
- val important = ranking.channel.isImportantConversation
- var changed = false
- entry.row?.layouts?.asSequence()
- ?.flatMap(::getLayouts)
- ?.mapNotNull { it as? ConversationLayout }
- ?.filterNot { it.isImportantConversation == important }
- ?.forEach { layout ->
- changed = true
- if (important && entry.isMarkedForUserTriggeredMovement) {
- // delay this so that it doesn't animate in until after
- // the notif has been moved in the shade
- mainHandler.postDelayed(
- {
- layout.setIsImportantConversation(
- important,
- true)
- },
- IMPORTANCE_ANIMATION_DELAY.toLong())
- } else {
- layout.setIsImportantConversation(important, false)
- }
- }
- if (changed) {
- notificationGroupManager.updateIsolation(entry)
- }
- }
- }
- }
-
- override fun onEntryInflated(entry: NotificationEntry) {
- if (!entry.ranking.isConversation) {
- return
- }
- fun updateCount(isExpanded: Boolean) {
- if (isExpanded && (!notifPanelCollapsed || entry.isPinnedAndExpanded)) {
- resetCount(entry.key)
- entry.row?.let(::resetBadgeUi)
- }
- }
- entry.row?.setOnExpansionChangedListener { isExpanded ->
- if (entry.row?.isShown == true && isExpanded) {
- entry.row.performOnIntrinsicHeightReached {
- updateCount(isExpanded)
- }
- } else {
- updateCount(isExpanded)
- }
- }
- updateCount(entry.row?.isExpanded == true)
- }
-
- override fun onEntryReinflated(entry: NotificationEntry) = onEntryInflated(entry)
-
- override fun onEntryRemoved(
- entry: NotificationEntry,
- visibility: NotificationVisibility?,
- removedByUser: Boolean,
- reason: Int
- ) = removeTrackedEntry(entry)
- })
+ if (featureFlags.isNewPipelineEnabled()) {
+ notifCollection.addCollectionListener(notifCollectionListener)
+ } else {
+ notificationEntryManager.addNotificationEntryListener(entryManagerListener)
+ }
}
private fun ConversationState.shouldIncrementUnread(newBuilder: Notification.Builder) =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 09c608d..062e239 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -59,6 +59,7 @@
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.util.Assert;
+import com.android.systemui.util.Compile;
import com.android.systemui.util.leak.LeakDetector;
import java.io.FileDescriptor;
@@ -1011,7 +1012,7 @@
}
private static final String TAG = "NotificationEntryMgr";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
/**
* Used when a notification is removed and it doesn't have a reason that maps to one of the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index ec4e039..195f367 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -31,6 +31,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -98,6 +99,7 @@
/** How long we can delay a group while waiting for all children to inflate */
private final long mMaxGroupInflationDelay;
+ private final ConversationNotificationManager mConversationManager;
@Inject
public PreparationCoordinator(
@@ -106,7 +108,8 @@
NotifInflationErrorManager errorManager,
NotifViewBarn viewBarn,
NotifUiAdjustmentProvider adjustmentProvider,
- IStatusBarService service) {
+ IStatusBarService service,
+ ConversationNotificationManager conversationManager) {
this(
logger,
notifInflater,
@@ -114,6 +117,7 @@
viewBarn,
adjustmentProvider,
service,
+ conversationManager,
CHILD_BIND_CUTOFF,
MAX_GROUP_INFLATION_DELAY);
}
@@ -126,6 +130,7 @@
NotifViewBarn viewBarn,
NotifUiAdjustmentProvider adjustmentProvider,
IStatusBarService service,
+ ConversationNotificationManager conversationManager,
int childBindCutoff,
long maxGroupInflationDelay) {
mLogger = logger;
@@ -136,6 +141,7 @@
mStatusBarService = service;
mChildBindCutoff = childBindCutoff;
mMaxGroupInflationDelay = maxGroupInflationDelay;
+ mConversationManager = conversationManager;
}
@Override
@@ -363,6 +369,9 @@
mInflatingNotifs.remove(entry);
mViewBarn.registerViewForEntry(entry, controller);
mInflationStates.put(entry, STATE_INFLATED);
+ // NOTE: under the new pipeline there's no way to register for an inflation callback,
+ // so this one method is called by the PreparationCoordinator directly.
+ mConversationManager.onEntryViewBound(entry);
mNotifInflatingFilter.invalidateList();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
index 5993f1d..3b93020 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
@@ -34,9 +34,9 @@
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.util.Compile;
import com.android.wm.shell.bubbles.Bubbles;
import java.io.FileDescriptor;
@@ -69,8 +69,8 @@
Dumpable {
private static final String TAG = "NotifGroupManager";
- private static final boolean DEBUG = StatusBar.DEBUG;
- private static final boolean SPEW = StatusBar.SPEW;
+ private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean SPEW = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
/**
* The maximum amount of time (in ms) between the posting of notifications that can be
* considered part of the same update batch.
@@ -384,9 +384,9 @@
// * Only necessary when all notifications in the group use GROUP_ALERT_SUMMARY
// * Only necessary when at least one notification in the group is on a priority channel
if (group.summary.getSbn().getNotification().getGroupAlertBehavior()
- != Notification.GROUP_ALERT_SUMMARY) {
+ == Notification.GROUP_ALERT_CHILDREN) {
if (SPEW) {
- Log.d(TAG, "getPriorityConversationAlertOverride: summary != GROUP_ALERT_SUMMARY");
+ Log.d(TAG, "getPriorityConversationAlertOverride: summary == GROUP_ALERT_CHILDREN");
}
return null;
}
@@ -529,8 +529,10 @@
mIsolatedEntries.put(entry.getKey(), entry.getSbn());
if (groupKeysChanged) {
updateSuppression(mGroupMap.get(oldGroupKey));
- updateSuppression(mGroupMap.get(newGroupKey));
}
+ // Always update the suppression of the group from which you're isolated, in case
+ // this entry was or now is the alertOverride for that group.
+ updateSuppression(mGroupMap.get(newGroupKey));
} else if (!wasGroupChild && isGroupChild) {
onEntryBecomingChild(entry);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 433d5e1..2b4bc91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -40,6 +40,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.util.Compile;
import java.util.ArrayList;
import java.util.List;
@@ -52,8 +53,8 @@
@SysUISingleton
public class NotificationInterruptStateProviderImpl implements NotificationInterruptStateProvider {
private static final String TAG = "InterruptionStateProvider";
- private static final boolean DEBUG = true; //false;
- private static final boolean DEBUG_HEADS_UP = true;
+ private static final boolean DEBUG = Compile.IS_DEBUG;
+ private static final boolean DEBUG_HEADS_UP = Compile.IS_DEBUG;
private static final boolean ENABLE_HEADS_UP = true;
private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 9e8200b..dc39413 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -50,6 +50,7 @@
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.util.Compile;
import java.util.Collection;
import java.util.Collections;
@@ -65,7 +66,7 @@
*/
public class NotificationLogger implements StateListener {
private static final String TAG = "NotificationLogger";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
/** The minimum delay in ms between reports of notification visibility. */
private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 08a230b..dbd22db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -115,6 +115,7 @@
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
+import com.android.systemui.util.Compile;
import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.wmshell.BubblesManager;
@@ -136,11 +137,11 @@
implements PluginListener<NotificationMenuRowPlugin>, SwipeableView,
NotificationFadeAware.FadeOptimizedNotification {
- private static final boolean DEBUG = false;
+ private static final String TAG = "ExpandableNotifRow";
+ private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
private static final int COLORED_DIVIDER_ALPHA = 0x7B;
private static final int MENU_VIEW_INDEX = 0;
- private static final String TAG = "ExpandableNotifRow";
public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f;
private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
index c0bafb7..4893490 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
@@ -48,11 +48,12 @@
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.Compile;
public class FeedbackInfo extends LinearLayout implements NotificationGuts.GutsContent {
private static final String TAG = "FeedbackInfo";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private NotificationGuts mGutsContainer;
private NotificationListenerService.Ranking mRanking;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
index ab78d19..6abfee9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
@@ -32,6 +32,7 @@
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.logging.NotificationCounters;
+import com.android.systemui.util.Compile;
import java.util.Collections;
import java.util.HashSet;
@@ -43,8 +44,9 @@
*/
public class NotificationBlockingHelperManager {
/** Enables debug logging and always makes the blocking helper show up after a dismiss. */
- private static final boolean DEBUG = false;
private static final String TAG = "BlockingHelper";
+ private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean DEBUG_ALWAYS_SHOW = false;
private final Context mContext;
private final NotificationGutsManager mNotificationGutsManager;
@@ -98,7 +100,7 @@
// - The dismissed row is a valid group (>1 or 0 children from the same channel)
// or the only child in the group
final NotificationEntry entry = row.getEntry();
- if ((entry.getUserSentiment() == USER_SENTIMENT_NEGATIVE || DEBUG)
+ if ((entry.getUserSentiment() == USER_SENTIMENT_NEGATIVE || DEBUG_ALWAYS_SHOW)
&& mIsShadeExpanded
&& !row.getIsNonblockable()
&& ((!row.isChildInGroup() || mGroupMembershipManager.isOnlyChildInGroup(entry))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 4dec1f1..9cb5dc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -61,6 +61,7 @@
import com.android.systemui.statusbar.policy.SmartReplyStateInflaterKt;
import com.android.systemui.statusbar.policy.SmartReplyView;
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
+import com.android.systemui.util.Compile;
import com.android.systemui.wmshell.BubblesManager;
import java.io.FileDescriptor;
@@ -77,7 +78,7 @@
public class NotificationContentView extends FrameLayout implements NotificationFadeAware {
private static final String TAG = "NotificationContentView";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
public static final int VISIBLE_TYPE_CONTRACTED = 0;
public static final int VISIBLE_TYPE_EXPANDED = 1;
public static final int VISIBLE_TYPE_HEADSUP = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index 9d599cb..d0fb416 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -57,9 +57,6 @@
public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnClickListener,
ExpandableNotificationRow.LayoutListener {
- private static final boolean DEBUG = false;
- private static final String TAG = "swipe";
-
// Notification must be swiped at least this fraction of a single menu item to show menu
private static final float SWIPED_FAR_ENOUGH_MENU_FRACTION = 0.25f;
private static final float SWIPED_FAR_ENOUGH_MENU_UNCLEARABLE_FRACTION = 0.15f;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 915a85d..90f5179 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -132,6 +132,7 @@
public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
private static final String TAG = "StackScroller";
+ private static final boolean SPEW = Log.isLoggable(TAG, Log.VERBOSE);
// Delay in milli-seconds before shade closes for clear all.
private final int DELAY_BEFORE_SHADE_CLOSE = 200;
@@ -3143,6 +3144,13 @@
AnimationEvent event = new AnimationEvent(row, type);
event.headsUpFromBottom = onBottom;
mAnimationEvents.add(event);
+ if (SPEW) {
+ Log.v(TAG, "Generating HUN animation event: "
+ + " isHeadsUp=" + isHeadsUp
+ + " type=" + type
+ + " onBottom=" + onBottom
+ + " row=" + row.getEntry().getKey());
+ }
}
mHeadsUpChangeAnimations.clear();
mAddedHeadsUpChildren.clear();
@@ -4677,7 +4685,22 @@
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
- if (mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed)) {
+ final boolean add = mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed);
+ if (SPEW) {
+ Log.v(TAG, "generateHeadsUpAnimation:"
+ + " willAdd=" + add
+ + " isHeadsUp=" + isHeadsUp
+ + " row=" + row.getEntry().getKey());
+ }
+ if (add) {
+ // If we're hiding a HUN we just started showing THIS FRAME, then remove that event,
+ // and do not add the disappear event either.
+ if (!isHeadsUp && mHeadsUpChangeAnimations.remove(new Pair<>(row, true))) {
+ if (SPEW) {
+ Log.v(TAG, "generateHeadsUpAnimation: previous hun appear animation cancelled");
+ }
+ return;
+ }
mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
mNeedsAnimation = true;
if (!mIsExpanded && !mWillExpand && !isHeadsUp) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index ff75eef..7c3399d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -127,6 +127,7 @@
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.Compile;
import java.util.ArrayList;
import java.util.List;
@@ -144,7 +145,7 @@
@StatusBarComponent.StatusBarScope
public class NotificationStackScrollLayoutController {
private static final String TAG = "StackScrollerController";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private final boolean mAllowLongPress;
private final NotificationGutsManager mNotificationGutsManager;
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 9787a944..6632c9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -39,6 +39,7 @@
import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.util.Compile;
import java.util.ArrayList;
import java.util.List;
@@ -54,8 +55,8 @@
private static final long ALERT_TRANSFER_TIMEOUT = 300;
private static final String TAG = "NotifGroupAlertTransfer";
- private static final boolean DEBUG = StatusBar.DEBUG;
- private static final boolean SPEW = StatusBar.SPEW;
+ private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean SPEW = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
/**
* The list of entries containing group alert metadata for each group. Keyed by group key.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index a9b3927..137c519 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1359,10 +1359,14 @@
// Things that mean we're not dismissing the keyguard, and should ignore this expansion:
// - Keyguard isn't even visible.
// - Keyguard is visible, but can't be dismissed (swiping up will show PIN/password prompt).
+ // - The SIM is locked, you can't swipe to unlock. If the SIM is locked but there is no
+ // device lock set, canDismissLockScreen returns true even though you should not be able
+ // to dismiss the lock screen until entering the SIM PIN.
// - QS is expanded and we're swiping - swiping up now will hide QS, not dismiss the
// keyguard.
if (!isKeyguardShowing()
|| !mKeyguardStateController.canDismissLockScreen()
+ || mKeyguardViewMediator.isAnySimPinSecure()
|| (mNotificationPanelViewController.isQsExpanded() && trackingTouch)) {
return;
}
@@ -2959,7 +2963,7 @@
mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
if (mUserSwitcherController != null && mUserSwitcherController.useFullscreenUserSwitcher()) {
mStatusBarStateController.setState(StatusBarState.FULLSCREEN_USER_SWITCHER);
- } else if (!mPulseExpansionHandler.isWakingToShadeLocked()) {
+ } else if (!mLockscreenShadeTransitionController.isWakingToShadeLocked()) {
mStatusBarStateController.setState(StatusBarState.KEYGUARD);
}
updatePanelExpansionForKeyguard();
@@ -3565,7 +3569,6 @@
// once we fully woke up.
updateRevealEffect(true /* wakingUp */);
updateNotificationPanelTouchState();
- mPulseExpansionHandler.onStartedWakingUp();
// If we are waking up during the screen off animation, we should undo making the
// expanded visible (we did that so the LightRevealScrim would be visible).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 0ba7134..ea0dd72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -99,6 +99,7 @@
override fun onAnimationEnd(animation: Animator?) {
lightRevealAnimationPlaying = false
+ interactionJankMonitor.end(CUJ_SCREEN_OFF)
}
override fun onAnimationStart(animation: Animator?) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index bd84520..31c7006 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -47,6 +47,8 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+import com.android.systemui.unfold.util.JankMonitorTransitionProgressListener;
import java.util.Optional;
@@ -81,7 +83,8 @@
WindowManager windowManager,
IWindowManager iWindowManager,
StatusBarContentInsetsProvider contentInsetsProvider,
- @Main Resources resources) {
+ @Main Resources resources,
+ Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider) {
mContext = context;
mWindowManager = windowManager;
mIWindowManager = iWindowManager;
@@ -94,6 +97,10 @@
if (mBarHeight < 0) {
mBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
}
+ unfoldTransitionProgressProvider.ifPresent(
+ unfoldProgressProvider -> unfoldProgressProvider.addCallback(
+ new JankMonitorTransitionProgressListener(
+ /* attachedViewProvider=*/ () -> mStatusBarWindowView)));
}
public int getStatusBarHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index c2fd34c..178d014 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -17,87 +17,38 @@
package com.android.systemui.unfold
import android.content.Context
-import android.hardware.SensorManager
-import android.hardware.devicestate.DeviceStateManager
-import android.os.Handler
import android.view.IWindowManager
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.LifecycleScreenStatusProvider
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.updates.FoldStateProvider
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix
import com.android.systemui.util.time.SystemClockImpl
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider
import dagger.Lazy
import dagger.Module
import dagger.Provides
import java.util.Optional
-import java.util.concurrent.Executor
import javax.inject.Named
import javax.inject.Singleton
-@Module
+@Module(includes = [UnfoldSharedModule::class])
class UnfoldTransitionModule {
- @Provides
- @Singleton
- fun provideUnfoldTransitionProgressProvider(
- context: Context,
- config: UnfoldTransitionConfig,
- screenStatusProvider: Lazy<LifecycleScreenStatusProvider>,
- deviceStateManager: DeviceStateManager,
- sensorManager: SensorManager,
- @Main executor: Executor,
- @Main handler: Handler
- ): Optional<UnfoldTransitionProgressProvider> =
- if (config.isEnabled) {
- Optional.of(
- createUnfoldTransitionProgressProvider(
- context,
- config,
- screenStatusProvider.get(),
- deviceStateManager,
- sensorManager,
- handler,
- executor,
- tracingTagPrefix = "systemui"))
- } else {
- Optional.empty()
- }
-
- @Provides
- @Singleton
- fun provideFoldStateProvider(
- context: Context,
- config: UnfoldTransitionConfig,
- screenStatusProvider: Lazy<LifecycleScreenStatusProvider>,
- deviceStateManager: DeviceStateManager,
- sensorManager: SensorManager,
- @Main executor: Executor,
- @Main handler: Handler
- ): Optional<FoldStateProvider> =
- if (!config.isHingeAngleEnabled) {
- Optional.empty()
- } else {
- Optional.of(
- createFoldStateProvider(
- context,
- config,
- screenStatusProvider.get(),
- deviceStateManager,
- sensorManager,
- handler,
- executor))
- }
+ @Provides @UnfoldTransitionATracePrefix fun tracingTagPrefix() = "systemui"
@Provides
@Singleton
fun providesFoldStateLoggingProvider(
- optionalFoldStateProvider: Optional<FoldStateProvider>
+ config: UnfoldTransitionConfig,
+ foldStateProvider: Lazy<FoldStateProvider>
): Optional<FoldStateLoggingProvider> =
- optionalFoldStateProvider.map { foldStateProvider ->
- FoldStateLoggingProviderImpl(foldStateProvider, SystemClockImpl())
+ if (config.isHingeAngleEnabled) {
+ Optional.of(FoldStateLoggingProviderImpl(foldStateProvider.get(), SystemClockImpl()))
+ } else {
+ Optional.empty()
}
@Provides
@@ -144,6 +95,9 @@
} else {
ShellUnfoldProgressProvider.NO_PROVIDER
}
+
+ @Provides
+ fun screenStatusProvider(impl: LifecycleScreenStatusProvider): ScreenStatusProvider = impl
}
const val UNFOLD_STATUS_BAR = "unfold_status_bar"
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java
index aec14be..d10e890 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java
@@ -70,6 +70,10 @@
mEnableUsb.setText(getString(R.string.usb_disable_contaminant_detection));
mGotIt.setText(getString(R.string.got_it));
mLearnMore.setText(getString(R.string.learn_more));
+ if (getResources().getBoolean(
+ com.android.internal.R.bool.config_settingsHelpLinksEnabled)) {
+ mLearnMore.setVisibility(View.VISIBLE);
+ }
mEnableUsb.setOnClickListener(this);
mGotIt.setOnClickListener(this);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
index 2d51092..254fc59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
@@ -25,6 +25,7 @@
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN
+import android.hardware.biometrics.SensorLocationInternal
import android.hardware.biometrics.SensorProperties
import android.hardware.display.DisplayManager
import android.hardware.display.DisplayManagerGlobal
@@ -52,6 +53,7 @@
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -75,6 +77,12 @@
private const val DISPLAY_ID = 2
private const val SENSOR_ID = 1
+private const val DISPLAY_SIZE_X = 800
+private const val DISPLAY_SIZE_Y = 900
+
+private val X_LOCATION = SensorLocationInternal("", 540, 0, 20)
+private val Y_LOCATION = SensorLocationInternal("", 0, 1500, 22)
+
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
@@ -101,6 +109,8 @@
lateinit var handler: Handler
@Captor
lateinit var overlayCaptor: ArgumentCaptor<View>
+ @Captor
+ lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
private val executor = FakeExecutor(FakeSystemClock())
private lateinit var overlayController: ISidefpsController
@@ -125,6 +135,17 @@
this
}
}
+ `when`(windowManager.maximumWindowMetrics).thenReturn(
+ WindowMetrics(Rect(0, 0, DISPLAY_SIZE_X, DISPLAY_SIZE_Y), WindowInsets.CONSUMED)
+ )
+ }
+
+ private fun testWithDisplay(
+ initInfo: DisplayInfo.() -> Unit = {},
+ locations: List<SensorLocationInternal> = listOf(X_LOCATION),
+ windowInsets: WindowInsets = insetsForSmallNavbar(),
+ block: () -> Unit
+ ) {
`when`(fingerprintManager.sensorPropertiesInternal).thenReturn(
listOf(
FingerprintSensorPropertiesInternal(
@@ -133,22 +154,21 @@
5 /* maxEnrollmentsPerUser */,
listOf() /* componentInfo */,
FingerprintSensorProperties.TYPE_POWER_BUTTON,
- true /* resetLockoutRequiresHardwareAuthToken */
+ true /* resetLockoutRequiresHardwareAuthToken */,
+ locations
)
)
)
- `when`(windowManager.maximumWindowMetrics).thenReturn(
- WindowMetrics(Rect(0, 0, 800, 800), WindowInsets.CONSUMED)
- )
- }
- private fun testWithDisplay(initInfo: DisplayInfo.() -> Unit = {}, block: () -> Unit) {
val displayInfo = DisplayInfo()
displayInfo.initInfo()
val dmGlobal = mock(DisplayManagerGlobal::class.java)
val display = Display(dmGlobal, DISPLAY_ID, displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS)
`when`(dmGlobal.getDisplayInfo(eq(DISPLAY_ID))).thenReturn(displayInfo)
`when`(windowManager.defaultDisplay).thenReturn(display)
+ `when`(windowManager.currentWindowMetrics).thenReturn(
+ WindowMetrics(Rect(0, 0, DISPLAY_SIZE_X, DISPLAY_SIZE_Y), windowInsets)
+ )
sideFpsController = SidefpsController(
context.createDisplayContext(display), layoutInflater, fingerprintManager,
@@ -245,28 +265,71 @@
}
@Test
+ fun showsWithTaskbarOnY() = testWithDisplay(
+ { rotation = Surface.ROTATION_0 },
+ locations = listOf(Y_LOCATION)
+ ) {
+ hidesWithTaskbar(visible = true)
+ }
+
+ @Test
fun showsWithTaskbar90() = testWithDisplay({ rotation = Surface.ROTATION_90 }) {
hidesWithTaskbar(visible = true)
}
@Test
+ fun showsWithTaskbar90OnY() = testWithDisplay(
+ { rotation = Surface.ROTATION_90 },
+ locations = listOf(Y_LOCATION)
+ ) {
+ hidesWithTaskbar(visible = true)
+ }
+
+ @Test
fun showsWithTaskbar180() = testWithDisplay({ rotation = Surface.ROTATION_180 }) {
hidesWithTaskbar(visible = true)
}
@Test
- fun showsWithTaskbarCollapsedDown() = testWithDisplay({ rotation = Surface.ROTATION_270 }) {
- `when`(windowManager.currentWindowMetrics).thenReturn(
- WindowMetrics(Rect(0, 0, 800, 800), insetsForSmallNavbar())
- )
+ fun showsWithTaskbar270OnY() = testWithDisplay(
+ { rotation = Surface.ROTATION_270 },
+ locations = listOf(Y_LOCATION)
+ ) {
hidesWithTaskbar(visible = true)
}
@Test
- fun hidesWithTaskbarDown() = testWithDisplay({ rotation = Surface.ROTATION_270 }) {
- `when`(windowManager.currentWindowMetrics).thenReturn(
- WindowMetrics(Rect(0, 0, 800, 800), insetsForLargeNavbar())
- )
+ fun showsWithTaskbarCollapsedDown() = testWithDisplay(
+ { rotation = Surface.ROTATION_270 },
+ windowInsets = insetsForSmallNavbar()
+ ) {
+ hidesWithTaskbar(visible = true)
+ }
+
+ @Test
+ fun showsWithTaskbarCollapsedDownOnY() = testWithDisplay(
+ { rotation = Surface.ROTATION_180 },
+ locations = listOf(Y_LOCATION),
+ windowInsets = insetsForSmallNavbar()
+ ) {
+ hidesWithTaskbar(visible = true)
+ }
+
+ @Test
+ fun hidesWithTaskbarDown() = testWithDisplay(
+ { rotation = Surface.ROTATION_180 },
+ locations = listOf(X_LOCATION),
+ windowInsets = insetsForLargeNavbar()
+ ) {
+ hidesWithTaskbar(visible = false)
+ }
+
+ @Test
+ fun hidesWithTaskbarDownOnY() = testWithDisplay(
+ { rotation = Surface.ROTATION_270 },
+ locations = listOf(Y_LOCATION),
+ windowInsets = insetsForLargeNavbar()
+ ) {
hidesWithTaskbar(visible = false)
}
@@ -281,6 +344,28 @@
verify(windowManager, never()).removeView(any())
verify(sidefpsView).visibility = if (visible) View.VISIBLE else View.GONE
}
+
+ @Test
+ fun setsXAlign() = testWithDisplay {
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ executor.runAllReady()
+
+ verify(windowManager).addView(any(), overlayViewParamsCaptor.capture())
+
+ assertThat(overlayViewParamsCaptor.value.x).isEqualTo(X_LOCATION.sensorLocationX)
+ assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
+ }
+
+ @Test
+ fun setYAlign() = testWithDisplay(locations = listOf(Y_LOCATION)) {
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ executor.runAllReady()
+
+ verify(windowManager).addView(any(), overlayViewParamsCaptor.capture())
+
+ assertThat(overlayViewParamsCaptor.value.x).isEqualTo(DISPLAY_SIZE_X)
+ assertThat(overlayViewParamsCaptor.value.y).isEqualTo(Y_LOCATION.sensorLocationY)
+ }
}
private fun insetsForSmallNavbar() = insetsWithBottom(60)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
index e453ff2..fd282cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
@@ -80,6 +80,7 @@
assertEquals(WakefulnessLifecycle.WAKEFULNESS_AWAKE, mWakefulness.getWakefulness());
verify(mWakefulnessObserver).onFinishedWakingUp();
+ verify(mWakefulnessObserver).onPostFinishedWakingUp();
}
@Test
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 dec5a10..4839bde 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
@@ -16,23 +16,28 @@
package com.android.systemui.media.taptotransfer
+import android.content.ComponentName
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
-import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender
-import com.android.systemui.media.taptotransfer.sender.MoveCloserToTransfer
-import com.android.systemui.media.taptotransfer.sender.TransferInitiated
-import com.android.systemui.media.taptotransfer.sender.TransferSucceeded
+import com.android.systemui.media.taptotransfer.sender.*
+import com.android.systemui.shared.mediattt.DeviceInfo
+import com.android.systemui.shared.mediattt.IDeviceSenderCallback
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
+import org.mockito.Mockito.anyString
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
import java.io.PrintWriter
import java.io.StringWriter
@@ -51,10 +56,19 @@
private lateinit var mediaTttChipControllerSender: MediaTttChipControllerSender
@Mock
private lateinit var mediaTttChipControllerReceiver: MediaTttChipControllerReceiver
+ @Mock
+ private lateinit var mediaSenderService: IDeviceSenderCallback.Stub
+ private lateinit var mediaSenderServiceComponentName: ComponentName
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+
+ mediaSenderServiceComponentName = ComponentName(context, MediaTttSenderService::class.java)
+ context.addMockService(mediaSenderServiceComponentName, mediaSenderService)
+ whenever(mediaSenderService.queryLocalInterface(anyString())).thenReturn(mediaSenderService)
+ whenever(mediaSenderService.asBinder()).thenReturn(mediaSenderService)
+
mediaTttCommandLineHelper =
MediaTttCommandLineHelper(
commandRegistry,
@@ -102,10 +116,14 @@
}
@Test
- fun sender_moveCloserToTransfer_chipDisplayWithCorrectState() {
- commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
+ fun sender_moveCloserToStartCast_serviceCallbackCalled() {
+ commandRegistry.onShellCommand(pw, getMoveCloserToStartCastCommand())
- verify(mediaTttChipControllerSender).displayChip(any(MoveCloserToTransfer::class.java))
+ assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
+
+ val deviceInfoCaptor = argumentCaptor<DeviceInfo>()
+ verify(mediaSenderService).closeToReceiverToStartCast(any(), capture(deviceInfoCaptor))
+ assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
}
@Test
@@ -143,11 +161,11 @@
verify(mediaTttChipControllerReceiver).removeChip()
}
- private fun getMoveCloserToTransferCommand(): Array<String> =
+ private fun getMoveCloserToStartCastCommand(): Array<String> =
arrayOf(
ADD_CHIP_COMMAND_SENDER_TAG,
DEVICE_NAME,
- MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME
+ MOVE_CLOSER_TO_START_CAST_COMMAND_NAME
)
private fun getTransferInitiatedCommand(): Array<String> =
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 caef5b9..ecc4c46 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
@@ -66,8 +66,8 @@
}
@Test
- fun moveCloserToTransfer_appIcon_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
- controllerSender.displayChip(moveCloserToTransfer())
+ fun moveCloserToStartCast_appIcon_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
+ controllerSender.displayChip(moveCloserToStartCast())
val chipView = getChipView()
assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
@@ -192,8 +192,8 @@
}
@Test
- fun changeFromCloserToTransferToTransferInitiated_loadingIconAppears() {
- controllerSender.displayChip(moveCloserToTransfer())
+ fun changeFromCloserToStartToTransferInitiated_loadingIconAppears() {
+ controllerSender.displayChip(moveCloserToStartCast())
controllerSender.displayChip(transferInitiated())
assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
@@ -216,9 +216,9 @@
}
@Test
- fun changeFromTransferSucceededToMoveCloser_undoButtonDisappears() {
+ fun changeFromTransferSucceededToMoveCloserToStart_undoButtonDisappears() {
controllerSender.displayChip(transferSucceeded())
- controllerSender.displayChip(moveCloserToTransfer())
+ controllerSender.displayChip(moveCloserToStartCast())
assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE)
}
@@ -240,8 +240,8 @@
}
/** Helper method providing default parameters to not clutter up the tests. */
- private fun moveCloserToTransfer() =
- MoveCloserToTransfer(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
+ private fun moveCloserToStartCast() =
+ MoveCloserToStartCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
/** Helper method providing default parameters to not clutter up the tests. */
private fun transferInitiated(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt
new file mode 100644
index 0000000..8f64698
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt
@@ -0,0 +1,48 @@
+package com.android.systemui.media.taptotransfer.sender
+
+import android.media.MediaRoute2Info
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.mediattt.DeviceInfo
+import com.android.systemui.shared.mediattt.IDeviceSenderCallback
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MediaTttSenderServiceTest : SysuiTestCase() {
+
+ private lateinit var service: MediaTttSenderService
+ private lateinit var callback: IDeviceSenderCallback
+
+ @Mock
+ private lateinit var controller: MediaTttChipControllerSender
+
+ private val mediaInfo = MediaRoute2Info.Builder("id", "Test Name")
+ .addFeature("feature")
+ .build()
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ service = MediaTttSenderService(context, controller)
+ callback = IDeviceSenderCallback.Stub.asInterface(service.onBind(null))
+ }
+
+ @Test
+ fun closeToReceiverToStartCast_controllerTriggeredWithMoveCloserToStartCastState() {
+ val name = "Fake name"
+ callback.closeToReceiverToStartCast(mediaInfo, DeviceInfo(name))
+
+ val chipStateCaptor = argumentCaptor<MoveCloserToStartCast>()
+ verify(controller).displayChip(capture(chipStateCaptor))
+
+ val chipState = chipStateCaptor.value!!
+ assertThat(chipState.otherDeviceName).isEqualTo(name)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 13b8e81..42647f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -4,13 +4,12 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
-import android.util.DisplayMetrics
import com.android.systemui.ExpandHelper
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dump.DumpManager
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.media.MediaHierarchyManager
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QS
@@ -64,12 +63,11 @@
@Mock lateinit var lockScreenUserManager: NotificationLockscreenUserManager
@Mock lateinit var falsingCollector: FalsingCollector
@Mock lateinit var ambientState: AmbientState
- @Mock lateinit var displayMetrics: DisplayMetrics
+ @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle
@Mock lateinit var mediaHierarchyManager: MediaHierarchyManager
@Mock lateinit var scrimController: ScrimController
@Mock lateinit var configurationController: ConfigurationController
@Mock lateinit var falsingManager: FalsingManager
- @Mock lateinit var buffer: LogBuffer
@Mock lateinit var notificationPanelController: NotificationPanelViewController
@Mock lateinit var nsslController: NotificationStackScrollLayoutController
@Mock lateinit var depthController: NotificationShadeDepthController
@@ -98,6 +96,7 @@
mediaHierarchyManager = mediaHierarchyManager,
scrimController = scrimController,
depthController = depthController,
+ wakefulnessLifecycle = wakefulnessLifecycle,
context = context,
configurationController = configurationController,
falsingManager = falsingManager,
@@ -148,6 +147,23 @@
}
@Test
+ fun testWakingToShadeLockedWhenDozing() {
+ whenever(statusbarStateController.isDozing).thenReturn(true)
+ transitionController.goToLockedShade(null)
+ verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
+ assertTrue("Not waking to shade locked", transitionController.isWakingToShadeLocked)
+ }
+
+ @Test
+ fun testNotWakingToShadeLockedWhenNotDozing() {
+ whenever(statusbarStateController.isDozing).thenReturn(false)
+ transitionController.goToLockedShade(null)
+ verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
+ assertFalse("Waking to shade locked when not dozing",
+ transitionController.isWakingToShadeLocked)
+ }
+
+ @Test
fun testGoToLockedShadeOnlyOnKeyguard() {
whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
transitionController.goToLockedShade(null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index b31dd3c..eef9dd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -23,7 +23,7 @@
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.Matchers.any;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
@@ -209,8 +209,6 @@
when(mMockProvisionController.isCurrentUserSetup()).thenReturn(true);
doAnswer(invocation -> {
mUserCallback = (DeviceProvisionedListener) invocation.getArguments()[0];
- mUserCallback.onUserSetupChanged();
- mUserCallback.onDeviceProvisionedChanged();
TestableLooper.get(this).processAllMessages();
return null;
}).when(mMockProvisionController).addCallback(any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index 73eddd1..6262a9b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -35,6 +35,7 @@
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import com.android.settingslib.graph.SignalDrawable;
@@ -60,6 +61,72 @@
public class NetworkControllerSignalTest extends NetworkControllerBaseTest {
@Test
+ public void testDeviceProvisioned_userNotSetUp() {
+ // GIVEN - user is not setup
+ when(mMockProvisionController.isCurrentUserSetup()).thenReturn(false);
+
+ // WHEN - a NetworkController is created
+ mNetworkController = new NetworkControllerImpl(mContext,
+ mMockCm,
+ mMockTm,
+ mTelephonyListenerManager,
+ mMockWm,
+ mMockNsm,
+ mMockSm,
+ mConfig,
+ TestableLooper.get(this).getLooper(),
+ mFakeExecutor,
+ mCallbackHandler,
+ mock(AccessPointControllerImpl.class),
+ mock(DataUsageController.class),
+ mMockSubDefaults,
+ mMockProvisionController,
+ mMockBd,
+ mDemoModeController,
+ mCarrierConfigTracker,
+ mFeatureFlags,
+ mock(DumpManager.class)
+ );
+ TestableLooper.get(this).processAllMessages();
+
+ // THEN - NetworkController claims the user is not setup
+ assertFalse("User has not been set up", mNetworkController.isUserSetup());
+ }
+
+ @Test
+ public void testDeviceProvisioned_userSetUp() {
+ // GIVEN - user is not setup
+ when(mMockProvisionController.isCurrentUserSetup()).thenReturn(true);
+
+ // WHEN - a NetworkController is created
+ mNetworkController = new NetworkControllerImpl(mContext,
+ mMockCm,
+ mMockTm,
+ mTelephonyListenerManager,
+ mMockWm,
+ mMockNsm,
+ mMockSm,
+ mConfig,
+ TestableLooper.get(this).getLooper(),
+ mFakeExecutor,
+ mCallbackHandler,
+ mock(AccessPointControllerImpl.class),
+ mock(DataUsageController.class),
+ mMockSubDefaults,
+ mMockProvisionController,
+ mMockBd,
+ mDemoModeController,
+ mCarrierConfigTracker,
+ mFeatureFlags,
+ mock(DumpManager.class)
+ );
+ TestableLooper.get(this).processAllMessages();
+
+ // THEN - NetworkController claims the user is not setup
+ assertTrue("User has been set up", mNetworkController.isUserSetup());
+ }
+
+ @Test
public void testNoIconWithoutMobile() {
// Turn off mobile network support.
when(mMockTm.isDataCapable()).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index f70330d..bde6734 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -40,6 +40,7 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.SectionClassifier;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
@@ -92,6 +93,7 @@
@Mock private NotifSection mNotifSection;
@Mock private NotifPipeline mNotifPipeline;
@Mock private IStatusBarService mService;
+ @Mock private ConversationNotificationManager mConvoManager;
@Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater();
private final SectionClassifier mSectionClassifier = new SectionClassifier();
private final NotifUiAdjustmentProvider mAdjustmentProvider =
@@ -119,6 +121,7 @@
mock(NotifViewBarn.class),
mAdjustmentProvider,
mService,
+ mConvoManager,
TEST_CHILD_BIND_CUTOFF,
TEST_MAX_GROUP_DELAY);
@@ -405,6 +408,13 @@
}
@Test
+ public void testCallConversationManagerBindWhenInflated() {
+ mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
+ mNotifInflater.getInflateCallback(mEntry).onInflationFinished(mEntry, null);
+ verify(mConvoManager, times(1)).onEntryViewBound(eq(mEntry));
+ }
+
+ @Test
public void testPartiallyInflatedGroupsAreReleasedAfterTimeout() {
// GIVEN a newly-posted group with a summary and two children
final GroupEntry group = new GroupEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
index 1be27da..6d170b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
@@ -178,20 +178,68 @@
}
/**
+ * Helper for testing various sibling counts
+ */
+ private void helpTestAlertOverrideWithSiblings(int numSiblings) {
+ helpTestAlertOverride(
+ /* numSiblings */ numSiblings,
+ /* summaryAlert */ Notification.GROUP_ALERT_SUMMARY,
+ /* childAlert */ Notification.GROUP_ALERT_SUMMARY,
+ /* siblingAlert */ Notification.GROUP_ALERT_SUMMARY,
+ /* expectAlertOverride */ true);
+ }
+
+ @Test
+ public void testAlertOverrideWithParentAlertAll() {
+ // tests that summary can have GROUP_ALERT_ALL and this still works
+ helpTestAlertOverride(
+ /* numSiblings */ 1,
+ /* summaryAlert */ Notification.GROUP_ALERT_ALL,
+ /* childAlert */ Notification.GROUP_ALERT_SUMMARY,
+ /* siblingAlert */ Notification.GROUP_ALERT_SUMMARY,
+ /* expectAlertOverride */ true);
+ }
+
+ @Test
+ public void testAlertOverrideWithParentAlertChild() {
+ // Tests that if the summary alerts CHILDREN, there's no alertOverride
+ helpTestAlertOverride(
+ /* numSiblings */ 1,
+ /* summaryAlert */ Notification.GROUP_ALERT_CHILDREN,
+ /* childAlert */ Notification.GROUP_ALERT_SUMMARY,
+ /* siblingAlert */ Notification.GROUP_ALERT_SUMMARY,
+ /* expectAlertOverride */ false);
+ }
+
+ @Test
+ public void testAlertOverrideWithChildrenAlertAll() {
+ // Tests that if the children alert ALL, there's no alertOverride
+ helpTestAlertOverride(
+ /* numSiblings */ 1,
+ /* summaryAlert */ Notification.GROUP_ALERT_SUMMARY,
+ /* childAlert */ Notification.GROUP_ALERT_ALL,
+ /* siblingAlert */ Notification.GROUP_ALERT_ALL,
+ /* expectAlertOverride */ false);
+ }
+
+ /**
* This tests, for a group with a priority entry and the given number of siblings, that:
* 1) the priority entry is identified as the alertOverride for the group
* 2) the onAlertOverrideChanged method is called at that time
* 3) when the priority entry is removed, these are reversed
*/
- private void helpTestAlertOverrideWithSiblings(int numSiblings) {
- int groupAlert = Notification.GROUP_ALERT_SUMMARY;
+ private void helpTestAlertOverride(int numSiblings,
+ @Notification.GroupAlertBehavior int summaryAlert,
+ @Notification.GroupAlertBehavior int childAlert,
+ @Notification.GroupAlertBehavior int siblingAlert,
+ boolean expectAlertOverride) {
// Create entries in an order so that the priority entry can be deemed the newest child.
NotificationEntry[] siblings = new NotificationEntry[numSiblings];
for (int i = 0; i < numSiblings; i++) {
- siblings[i] = mGroupTestHelper.createChildNotification(groupAlert);
+ siblings[i] = mGroupTestHelper.createChildNotification(siblingAlert);
}
- NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(groupAlert);
- NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(groupAlert);
+ NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(childAlert);
+ NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(summaryAlert);
// The priority entry is an important conversation.
when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry)))
@@ -208,6 +256,14 @@
}
mGroupManager.onEntryAdded(priorityEntry);
+ if (!expectAlertOverride) {
+ // Test expectation is that there will NOT be an alert, so verify that!
+ NotificationGroup summaryGroup =
+ mGroupManager.getGroupForSummary(summaryEntry.getSbn());
+ assertNull(summaryGroup.alertOverride);
+ return;
+ }
+
// Verify that the summary group has the priority child as its alertOverride
NotificationGroup summaryGroup = mGroupManager.getGroupForSummary(summaryEntry.getSbn());
assertEquals(priorityEntry, summaryGroup.alertOverride);
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 3047c90..196c6aa 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -276,6 +276,12 @@
// Package: android
NOTE_UNBLOCK_CAM_TOGGLE = 66;
+ // Notify the user that a CA certificate is pending for the wifi connection.
+ NOTE_SERVER_CA_CERTIFICATE = 67;
+
+ // Notify the user to set up dream
+ NOTE_SETUP_DREAM = 68;
+
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
// Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index aba32ec..59c1461 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -667,10 +667,6 @@
return null;
}
- // Don't need to add the embedded hierarchy windows into the accessibility windows list.
- if (mHostEmbeddedMap.size() > 0 && isEmbeddedHierarchyWindowsLocked(windowId)) {
- return null;
- }
final AccessibilityWindowInfo reportedWindow = AccessibilityWindowInfo.obtain();
reportedWindow.setId(windowId);
@@ -703,21 +699,6 @@
return reportedWindow;
}
- private boolean isEmbeddedHierarchyWindowsLocked(int windowId) {
- final IBinder leashToken = mWindowIdMap.get(windowId);
- if (leashToken == null) {
- return false;
- }
-
- for (int i = 0; i < mHostEmbeddedMap.size(); i++) {
- if (mHostEmbeddedMap.keyAt(i).equals(leashToken)) {
- return true;
- }
- }
-
- return false;
- }
-
private int getTypeForWindowManagerWindowType(int windowType) {
switch (windowType) {
case WindowManager.LayoutParams.TYPE_APPLICATION:
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index 1914164..93fc0e72 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -23,7 +23,7 @@
import static android.content.ComponentName.createRelative;
import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
-import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
+import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature;
import static com.android.server.companion.PermissionsUtils.enforcePermissionsForAssociation;
import static com.android.server.companion.RolesUtils.isRoleHolder;
@@ -31,6 +31,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.companion.AssociationInfo;
@@ -102,8 +103,9 @@
* @see #processAssociationRequestApproval(AssociationRequest, IAssociationRequestCallback,
* ResultReceiver, MacAddress)
*/
+@SuppressLint("LongLogTag")
class AssociationRequestsProcessor {
- private static final String TAG = LOG_TAG + ".AssociationRequestsProcessor";
+ private static final String TAG = "CompanionDevice_AssociationRequestsProcessor";
private static final ComponentName ASSOCIATION_REQUEST_APPROVAL_ACTIVITY =
createRelative(COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, ".CompanionDeviceActivity");
@@ -161,7 +163,7 @@
// 1. Enforce permissions and other requirements.
enforcePermissionsForAssociation(mContext, request, packageUid);
- mService.checkUsesFeature(packageName, userId);
+ enforceUsesCompanionDeviceFeature(mContext, userId, packageName);
// 2. Check if association can be created without launching UI (i.e. CDM needs NEITHER
// to perform discovery NOR to collect user consent).
diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
index 3f0200e..5b318d3 100644
--- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
+++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
@@ -136,6 +136,8 @@
// Update the ID-to-Association map.
mIdMap.put(id, updated);
+ // Invalidate the corresponding user cache entry.
+ invalidateCacheForUserLocked(current.getUserId());
// Update the MacAddress-to-List<Association> map if needed.
final MacAddress updatedAddress = updated.getDeviceMacAddress();
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 94a97d8..1c98347 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -600,11 +600,13 @@
return;
}
- association.setLastTimeConnected(System.currentTimeMillis());
- mAssociationStore.updateAssociation(association);
+ AssociationInfo updatedAssociationInfo = AssociationInfo.builder(association)
+ .setLastTimeConnected(System.currentTimeMillis())
+ .build();
+ mAssociationStore.updateAssociation(updatedAssociationInfo);
mCompanionDevicePresenceController.onDeviceNotifyAppeared(
- association, getContext(), mMainHandler);
+ updatedAssociationInfo, getContext(), mMainHandler);
}
@Override
@@ -651,8 +653,10 @@
+ " for user " + userId));
}
- association.setNotifyOnDeviceNearby(active);
- mAssociationStore.updateAssociation(association);
+ AssociationInfo updatedAssociationInfo = AssociationInfo.builder(association)
+ .setNotifyOnDeviceNearby(active)
+ .build();
+ mAssociationStore.updateAssociation(updatedAssociationInfo);
}
@Override
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
new file mode 100644
index 0000000..777917c
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
@@ -0,0 +1,119 @@
+/*
+ * 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;
+
+import static android.content.Context.BIND_IMPORTANT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
+import android.companion.AssociationInfo;
+import android.companion.CompanionDeviceService;
+import android.companion.ICompanionDeviceService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.internal.infra.ServiceConnector;
+
+/**
+ * Manages a connection (binding) to an instance of {@link CompanionDeviceService} running in the
+ * application process.
+ */
+@SuppressLint("LongLogTag")
+class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDeviceService> {
+ private static final String TAG = "CompanionDevice_ServiceConnector";
+ private static final boolean DEBUG = false;
+ private static final int BINDING_FLAGS = BIND_IMPORTANT;
+
+ /** Listener for changes to the state of the {@link CompanionDeviceServiceConnector} */
+ interface Listener {
+ void onBindingDied(@UserIdInt int userId, @NonNull String packageName);
+ }
+
+ private final @UserIdInt int mUserId;
+ private final @NonNull ComponentName mComponentName;
+ private @Nullable Listener mListener;
+
+ CompanionDeviceServiceConnector(@NonNull Context context, @UserIdInt int userId,
+ @NonNull ComponentName componentName) {
+ super(context, buildIntent(componentName), BINDING_FLAGS, userId, null);
+ mUserId = userId;
+ mComponentName = componentName;
+ }
+
+ void setListener(@Nullable Listener listener) {
+ mListener = listener;
+ }
+
+ void postOnDeviceAppeared(@NonNull AssociationInfo associationInfo) {
+ post(companionService -> companionService.onDeviceAppeared(associationInfo));
+ }
+
+ void postOnDeviceDisappeared(@NonNull AssociationInfo associationInfo) {
+ post(companionService -> companionService.onDeviceDisappeared(associationInfo));
+ }
+
+ /**
+ * Post "unbind" job, which will run *after* all previously posted jobs complete.
+ *
+ * IMPORTANT: use this method instead of invoking {@link ServiceConnector#unbind()} directly,
+ * because the latter may cause previously posted callback, such as
+ * {@link ICompanionDeviceService#onDeviceDisappeared(AssociationInfo)} to be dropped.
+ */
+ void postUnbind() {
+ post(it -> unbind());
+ }
+
+ @Override
+ protected void onServiceConnectionStatusChanged(
+ @NonNull ICompanionDeviceService service, boolean isConnected) {
+ if (DEBUG) {
+ Log.d(TAG, "onServiceConnection_StatusChanged() " + mComponentName.toShortString()
+ + " connected=" + isConnected);
+ }
+ }
+
+ @Override
+ public void onBindingDied(@NonNull ComponentName name) {
+ // IMPORTANT: call super!
+ super.onBindingDied(name);
+
+ if (DEBUG) Log.d(TAG, "onBindingDied() " + mComponentName.toShortString());
+
+ mListener.onBindingDied(mUserId, mComponentName.getPackageName());
+ }
+
+ @Override
+ protected ICompanionDeviceService binderAsInterface(@NonNull IBinder service) {
+ return ICompanionDeviceService.Stub.asInterface(service);
+ }
+
+ @Override
+ protected long getAutoDisconnectTimeoutMs() {
+ // Do NOT auto-disconnect.
+ return -1;
+ }
+
+ private static @NonNull Intent buildIntent(@NonNull ComponentName componentName) {
+ return new Intent(CompanionDeviceService.SERVICE_INTERFACE)
+ .setComponent(componentName);
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/PackageUtils.java
new file mode 100644
index 0000000..985daa3
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/PackageUtils.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.server.companion;
+
+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.LOG_TAG;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.companion.CompanionDeviceService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.PackageInfoFlags;
+import android.content.pm.PackageManager.ResolveInfoFlags;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Binder;
+import android.util.Slog;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Utility methods for working with {@link PackageInfo}-s.
+ */
+final class PackageUtils {
+ private static final Intent COMPANION_SERVICE_INTENT =
+ new Intent(CompanionDeviceService.SERVICE_INTERFACE);
+ private static final String META_DATA_KEY_PRIMARY = "primary";
+
+ static @Nullable PackageInfo getPackageInfo(@NonNull Context context,
+ @UserIdInt int userId, @NonNull String packageName) {
+ final PackageManager pm = context.getPackageManager();
+ final PackageInfoFlags flags = PackageInfoFlags.of(GET_PERMISSIONS | GET_CONFIGURATIONS);
+ return Binder.withCleanCallingIdentity(() ->
+ pm.getPackageInfoAsUser(packageName, flags , userId));
+ }
+
+ static void enforceUsesCompanionDeviceFeature(@NonNull Context context,
+ @UserIdInt int userId, @NonNull String packageName) {
+ final boolean requested = ArrayUtils.contains(
+ getPackageInfo(context, userId, packageName).reqFeatures,
+ FEATURE_COMPANION_DEVICE_SETUP);
+
+ if (requested) {
+ throw new IllegalStateException("Must declare uses-feature "
+ + FEATURE_COMPANION_DEVICE_SETUP
+ + " in manifest to use this API");
+ }
+ }
+
+ /**
+ * @return list of {@link CompanionDeviceService}-s per package for a given user.
+ * Services marked as "primary" would always appear at the head of the lists, *before*
+ * all non-primary services.
+ */
+ 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 Map<String, List<ComponentName>> packageNameToServiceInfoList = new HashMap<>();
+
+ for (ResolveInfo resolveInfo : companionServices) {
+ final ServiceInfo service = resolveInfo.serviceInfo;
+
+ final boolean requiresPermission = Manifest.permission.BIND_COMPANION_DEVICE_SERVICE
+ .equals(resolveInfo.serviceInfo.permission);
+ if (!requiresPermission) {
+ Slog.w(LOG_TAG, "CompanionDeviceService "
+ + service.getComponentName().flattenToShortString() + " must require "
+ + "android.permission.BIND_COMPANION_DEVICE_SERVICE");
+ continue;
+ }
+
+ // Use LinkedList, because we'll need to prepend "primary" services, while appending the
+ // other (non-primary) services to the list.
+ final LinkedList<ComponentName> services =
+ (LinkedList<ComponentName>) packageNameToServiceInfoList.computeIfAbsent(
+ service.packageName, it -> new LinkedList<>());
+
+ final ComponentName componentName = service.getComponentName();
+ if (isPrimaryCompanionDeviceService(service)) {
+ // "Primary" service should be at the head of the list.
+ services.addFirst(componentName);
+ } else {
+ services.addLast(componentName);
+ }
+ }
+
+ return packageNameToServiceInfoList;
+ }
+
+ private static boolean isPrimaryCompanionDeviceService(ServiceInfo service) {
+ return service.metaData != null && service.metaData.getBoolean(META_DATA_KEY_PRIMARY);
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index e98b63e..734e5c3 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -16,6 +16,7 @@
package com.android.server.companion.virtual;
+import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -27,9 +28,10 @@
import android.content.pm.ActivityInfo;
import android.os.Build;
import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Slog;
import android.window.DisplayWindowPolicyController;
-import java.util.HashSet;
import java.util.List;
@@ -38,6 +40,8 @@
*/
class GenericWindowPolicyController extends DisplayWindowPolicyController {
+ private static final String TAG = "VirtualDeviceManager";
+
/**
* If required, allow the secure activity to display on remote device since
* {@link android.os.Build.VERSION_CODES#TIRAMISU}.
@@ -45,10 +49,13 @@
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
public static final long ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE = 201712607L;
+ @NonNull private final ArraySet<UserHandle> mAllowedUsers;
- @NonNull final HashSet<Integer> mRunningUids = new HashSet<>();
+ @NonNull final ArraySet<Integer> mRunningUids = new ArraySet<>();
- GenericWindowPolicyController(int windowFlags, int systemWindowFlags) {
+ GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
+ @NonNull ArraySet<UserHandle> allowedUsers) {
+ mAllowedUsers = allowedUsers;
setInterestedWindowFlags(windowFlags, systemWindowFlags);
}
@@ -58,7 +65,7 @@
final int activityCount = activities.size();
for (int i = 0; i < activityCount; i++) {
final ActivityInfo aInfo = activities.get(i);
- if ((aInfo.flags & ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
+ if (!canContainActivity(aInfo, /* windowFlags= */ 0, /* systemWindowFlags= */ 0)) {
return false;
}
}
@@ -68,12 +75,41 @@
@Override
public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
int systemWindowFlags) {
- if ((activityInfo.flags & ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
+ return canContainActivity(activityInfo, windowFlags, systemWindowFlags);
+ }
+
+ @Override
+ public void onTopActivityChanged(ComponentName topActivity, int uid) {
+
+ }
+
+ @Override
+ public void onRunningAppsChanged(ArraySet<Integer> runningUids) {
+ mRunningUids.clear();
+ mRunningUids.addAll(runningUids);
+ }
+
+ /**
+ * Returns true if an app with the given UID has an activity running on the virtual display for
+ * this controller.
+ */
+ boolean containsUid(int uid) {
+ return mRunningUids.contains(uid);
+ }
+
+ private boolean canContainActivity(ActivityInfo activityInfo, int windowFlags,
+ int systemWindowFlags) {
+ if ((activityInfo.flags & FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
+ return false;
+ }
+ final UserHandle activityUser =
+ UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid);
+ if (!mAllowedUsers.contains(activityUser)) {
+ Slog.d(TAG, "Virtual device activity not allowed from user " + activityUser);
return false;
}
if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE,
- activityInfo.packageName,
- UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid))) {
+ activityInfo.packageName, activityUser)) {
// TODO(b/201712607): Add checks for the apps that use SurfaceView#setSecure.
if ((windowFlags & FLAG_SECURE) != 0) {
return false;
@@ -84,25 +120,4 @@
}
return true;
}
-
- @Override
- public void onTopActivityChanged(ComponentName topActivity, int uid) {
-
- }
-
- @Override
- public void onRunningAppsChanged(int[] runningUids) {
- mRunningUids.clear();
- for (int i = 0; i < runningUids.length; i++) {
- mRunningUids.add(runningUids[i]);
- }
- }
-
- /**
- * Returns true if an app with the given UID has an activity running on the virtual display for
- * this controller.
- */
- boolean containsUid(int uid) {
- return mRunningUids.contains(uid);
- }
}
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 ca35e03..0c0ee52 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -16,14 +16,23 @@
package com.android.server.companion.virtual;
+import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_ENABLED;
+import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
+import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import android.annotation.NonNull;
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.VirtualDeviceParams;
import android.content.Context;
import android.graphics.Point;
+import android.hardware.display.DisplayManager;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualMouseButtonEvent;
import android.hardware.input.VirtualMouseRelativeEvent;
@@ -32,6 +41,11 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArraySet;
+import android.util.Slog;
import android.util.SparseArray;
import android.window.DisplayWindowPolicyController;
@@ -46,16 +60,19 @@
final class VirtualDeviceImpl extends IVirtualDevice.Stub
implements IBinder.DeathRecipient {
+ private static final String TAG = "VirtualDeviceImpl";
private final Object mVirtualDeviceLock = new Object();
private final Context mContext;
private final AssociationInfo mAssociationInfo;
+ private final PendingTrampolineCallback mPendingTrampolineCallback;
private final int mOwnerUid;
private final InputController mInputController;
@VisibleForTesting
final List<Integer> mVirtualDisplayIds = new ArrayList<>();
private final OnDeviceCloseListener mListener;
private final IBinder mAppToken;
+ private final VirtualDeviceParams mParams;
/**
* A mapping from the virtual display ID to its corresponding
@@ -65,17 +82,22 @@
new SparseArray<>();
VirtualDeviceImpl(Context context, AssociationInfo associationInfo,
- IBinder token, int ownerUid, OnDeviceCloseListener listener) {
- this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener);
+ IBinder token, int ownerUid, OnDeviceCloseListener listener,
+ PendingTrampolineCallback pendingTrampolineCallback, VirtualDeviceParams params) {
+ this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener,
+ pendingTrampolineCallback, params);
}
@VisibleForTesting
VirtualDeviceImpl(Context context, AssociationInfo associationInfo, IBinder token,
- int ownerUid, InputController inputController, OnDeviceCloseListener listener) {
+ int ownerUid, InputController inputController, OnDeviceCloseListener listener,
+ PendingTrampolineCallback pendingTrampolineCallback, VirtualDeviceParams params) {
mContext = context;
mAssociationInfo = associationInfo;
+ mPendingTrampolineCallback = pendingTrampolineCallback;
mOwnerUid = ownerUid;
mAppToken = token;
+ mParams = params;
if (inputController == null) {
mInputController = new InputController(mVirtualDeviceLock);
} else {
@@ -89,12 +111,67 @@
}
}
- @Override
+ /**
+ * Returns the flags that should be added to any virtual displays created on this virtual
+ * device.
+ */
+ int getBaseVirtualDisplayFlags() {
+ int flags = 0;
+ if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) {
+ flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
+ }
+ return flags;
+ }
+
+ @Override // Binder call
public int getAssociationId() {
return mAssociationInfo.getId();
}
@Override // Binder call
+ public void launchPendingIntent(int displayId, PendingIntent pendingIntent,
+ ResultReceiver resultReceiver) {
+ if (!mVirtualDisplayIds.contains(displayId)) {
+ throw new SecurityException("Display ID " + displayId
+ + " not found for this virtual device");
+ }
+ if (pendingIntent.isActivity()) {
+ try {
+ sendPendingIntent(displayId, pendingIntent);
+ resultReceiver.send(Activity.RESULT_OK, null);
+ } catch (PendingIntent.CanceledException e) {
+ Slog.w(TAG, "Pending intent canceled", e);
+ resultReceiver.send(Activity.RESULT_CANCELED, null);
+ }
+ } else {
+ PendingTrampoline pendingTrampoline = new PendingTrampoline(pendingIntent,
+ resultReceiver, displayId);
+ mPendingTrampolineCallback.startWaitingForPendingTrampoline(pendingTrampoline);
+ try {
+ sendPendingIntent(displayId, pendingIntent);
+ } catch (PendingIntent.CanceledException e) {
+ Slog.w(TAG, "Pending intent canceled", e);
+ resultReceiver.send(Activity.RESULT_CANCELED, null);
+ mPendingTrampolineCallback.stopWaitingForPendingTrampoline(pendingTrampoline);
+ }
+ }
+ }
+
+ private void sendPendingIntent(int displayId, PendingIntent pendingIntent)
+ throws PendingIntent.CanceledException {
+ pendingIntent.send(
+ mContext,
+ /* code= */ 0,
+ /* intent= */ null,
+ /* onFinished= */ null,
+ /* handler= */ null,
+ /* requiredPermission= */ null,
+ ActivityOptions.makeBasic()
+ .setLaunchDisplayId(displayId)
+ .toBundle());
+ }
+
+ @Override // Binder call
public void close() {
mListener.onClose(mAssociationInfo.getId());
mAppToken.unlinkToDeath(this, 0);
@@ -250,6 +327,7 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
fout.println(" VirtualDevice: ");
+ fout.println(" mAssociationId: " + mAssociationInfo.getId());
fout.println(" mVirtualDisplayIds: ");
synchronized (mVirtualDeviceLock) {
for (int id : mVirtualDisplayIds) {
@@ -267,11 +345,29 @@
mVirtualDisplayIds.add(displayId);
final GenericWindowPolicyController dwpc =
new GenericWindowPolicyController(FLAG_SECURE,
- SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, getAllowedUserHandles());
mWindowPolicyControllers.put(displayId, dwpc);
return dwpc;
}
+ private ArraySet<UserHandle> getAllowedUserHandles() {
+ ArraySet<UserHandle> result = new ArraySet<>();
+ DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ UserManager userManager = mContext.getSystemService(UserManager.class);
+ for (UserHandle profile : userManager.getAllProfiles()) {
+ int nearbyAppStreamingPolicy = dpm.getNearbyAppStreamingPolicy(profile.getIdentifier());
+ if (nearbyAppStreamingPolicy == NEARBY_STREAMING_ENABLED
+ || nearbyAppStreamingPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY) {
+ result.add(profile);
+ } else if (nearbyAppStreamingPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY) {
+ if (mParams.getUsersWithMatchingAccounts().contains(profile)) {
+ result.add(profile);
+ }
+ }
+ }
+ return result;
+ }
+
void onVirtualDisplayRemovedLocked(int displayId) {
if (!mVirtualDisplayIds.contains(displayId)) {
throw new IllegalStateException(
@@ -302,4 +398,58 @@
interface OnDeviceCloseListener {
void onClose(int associationId);
}
+
+ interface PendingTrampolineCallback {
+ /**
+ * Called when the callback should start waiting for the given pending trampoline.
+ * Implementations should try to listen for activity starts associated with the given
+ * {@code pendingTrampoline}, and launch the activity on the display with
+ * {@link PendingTrampoline#mDisplayId}.
+ */
+ void startWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline);
+
+ /**
+ * Called when the callback should stop waiting for the given pending trampoline. This can
+ * happen, for example, when the pending intent failed to send.
+ */
+ void stopWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline);
+ }
+
+ /**
+ * A data class storing a pending trampoline this device is expecting.
+ */
+ static class PendingTrampoline {
+
+ /**
+ * The original pending intent sent, for which a trampoline activity launch is expected.
+ */
+ final PendingIntent mPendingIntent;
+
+ /**
+ * The result receiver associated with this pending call. {@link Activity#RESULT_OK} will
+ * be sent to the receiver if the trampoline activity was captured successfully.
+ * {@link Activity#RESULT_CANCELED} is sent otherwise.
+ */
+ final ResultReceiver mResultReceiver;
+
+ /**
+ * The display ID to send the captured trampoline activity launch to.
+ */
+ final int mDisplayId;
+
+ private PendingTrampoline(PendingIntent pendingIntent, ResultReceiver resultReceiver,
+ int displayId) {
+ mPendingIntent = pendingIntent;
+ mResultReceiver = resultReceiver;
+ mDisplayId = displayId;
+ }
+
+ @Override
+ public String toString() {
+ return "PendingTrampoline{"
+ + "pendingIntent=" + mPendingIntent
+ + ", resultReceiver=" + mResultReceiver
+ + ", displayId=" + mDisplayId + "}";
+ }
+ }
}
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 0db670e..7e0c2fc 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -16,16 +16,23 @@
package com.android.server.companion.virtual;
+import static com.android.server.wm.ActivityInterceptorCallback.VIRTUAL_DEVICE_SERVICE_ORDERED_ID;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.ActivityOptions;
import android.companion.AssociationInfo;
import android.companion.CompanionDeviceManager;
import android.companion.CompanionDeviceManager.OnAssociationsChangedListener;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
import android.content.Context;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.ExceptionUtils;
@@ -36,6 +43,9 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
import com.android.server.SystemService;
+import com.android.server.companion.virtual.VirtualDeviceImpl.PendingTrampoline;
+import com.android.server.wm.ActivityInterceptorCallback;
+import com.android.server.wm.ActivityTaskManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -47,10 +57,12 @@
public class VirtualDeviceManagerService extends SystemService {
private static final boolean DEBUG = false;
- private static final String LOG_TAG = "VirtualDeviceManagerService";
+ private static final String TAG = "VirtualDeviceManagerService";
private final Object mVirtualDeviceManagerLock = new Object();
private final VirtualDeviceManagerImpl mImpl;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler);
/**
* Mapping from CDM association IDs to virtual devices. Only one virtual device is allowed for
@@ -79,10 +91,39 @@
mImpl = new VirtualDeviceManagerImpl();
}
+ private final ActivityInterceptorCallback mActivityInterceptorCallback =
+ new ActivityInterceptorCallback() {
+
+ @Nullable
+ @Override
+ public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
+ if (info.callingPackage == null) {
+ return null;
+ }
+ PendingTrampoline pt = mPendingTrampolines.remove(info.callingPackage);
+ if (pt == null) {
+ return null;
+ }
+ pt.mResultReceiver.send(Activity.RESULT_OK, null);
+ ActivityOptions options = info.checkedOptions;
+ if (options == null) {
+ options = ActivityOptions.makeBasic();
+ }
+ return new ActivityInterceptResult(
+ info.intent, options.setLaunchDisplayId(pt.mDisplayId));
+ }
+ };
+
@Override
public void onStart() {
publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl);
publishLocalService(VirtualDeviceManagerInternal.class, new LocalService());
+
+ ActivityTaskManagerInternal activityTaskManagerInternal = getLocalService(
+ ActivityTaskManagerInternal.class);
+ activityTaskManagerInternal.registerActivityStartInterceptor(
+ VIRTUAL_DEVICE_SERVICE_ORDERED_ID,
+ mActivityInterceptorCallback);
}
@GuardedBy("mVirtualDeviceManagerLock")
@@ -127,11 +168,15 @@
}
}
- class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub {
+ class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements
+ VirtualDeviceImpl.PendingTrampolineCallback {
@Override // Binder call
public IVirtualDevice createVirtualDevice(
- IBinder token, String packageName, int associationId) {
+ IBinder token,
+ String packageName,
+ int associationId,
+ @NonNull VirtualDeviceParams params) {
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"createVirtualDevice");
@@ -160,7 +205,8 @@
mVirtualDevices.remove(associationId);
}
}
- });
+ },
+ this, params);
mVirtualDevices.put(associationInfo.getId(), virtualDevice);
return virtualDevice;
}
@@ -181,7 +227,7 @@
}
}
} else {
- Slog.w(LOG_TAG, "No associations for user " + callingUserId);
+ Slog.w(TAG, "No associations for user " + callingUserId);
}
return null;
}
@@ -192,7 +238,7 @@
try {
return super.onTransact(code, data, reply, flags);
} catch (Throwable e) {
- Slog.e(LOG_TAG, "Error during IPC", e);
+ Slog.e(TAG, "Error during IPC", e);
throw ExceptionUtils.propagate(e, RemoteException.class);
}
}
@@ -201,7 +247,7 @@
public void dump(@NonNull FileDescriptor fd,
@NonNull PrintWriter fout,
@Nullable String[] args) {
- if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), LOG_TAG, fout)) {
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, fout)) {
return;
}
fout.println("Created virtual devices: ");
@@ -211,10 +257,24 @@
}
}
}
+
+ @Override
+ public void startWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
+ PendingTrampoline existing = mPendingTrampolines.put(
+ pendingTrampoline.mPendingIntent.getCreatorPackage(),
+ pendingTrampoline);
+ if (existing != null) {
+ existing.mResultReceiver.send(Activity.RESULT_CANCELED, null);
+ }
+ }
+
+ @Override
+ public void stopWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
+ mPendingTrampolines.remove(pendingTrampoline.mPendingIntent.getCreatorPackage());
+ }
}
private final class LocalService extends VirtualDeviceManagerInternal {
-
@Override
public boolean isValidVirtualDevice(IVirtualDevice virtualDevice) {
synchronized (mVirtualDeviceManagerLock) {
@@ -238,6 +298,11 @@
}
@Override
+ public int getBaseVirtualDisplayFlags(IVirtualDevice virtualDevice) {
+ return ((VirtualDeviceImpl) virtualDevice).getBaseVirtualDisplayFlags();
+ }
+
+ @Override
public boolean isAppOwnerOfAnyVirtualDevice(int uid) {
synchronized (mVirtualDeviceManagerLock) {
int size = mVirtualDevices.size();
@@ -263,4 +328,42 @@
return false;
}
}
+
+ private static final class PendingTrampolineMap {
+ /**
+ * The maximum duration, in milliseconds, to wait for a trampoline activity launch after
+ * invoking a pending intent.
+ */
+ private static final int TRAMPOLINE_WAIT_MS = 5000;
+
+ private final ConcurrentHashMap<String, PendingTrampoline> mMap = new ConcurrentHashMap<>();
+ private final Handler mHandler;
+
+ PendingTrampolineMap(Handler handler) {
+ mHandler = handler;
+ }
+
+ PendingTrampoline put(
+ @NonNull String packageName, @NonNull PendingTrampoline pendingTrampoline) {
+ PendingTrampoline existing = mMap.put(packageName, pendingTrampoline);
+ mHandler.removeCallbacksAndMessages(existing);
+ mHandler.postDelayed(
+ () -> {
+ final String creatorPackage =
+ pendingTrampoline.mPendingIntent.getCreatorPackage();
+ if (creatorPackage != null) {
+ remove(creatorPackage);
+ }
+ },
+ pendingTrampoline,
+ TRAMPOLINE_WAIT_MS);
+ return existing;
+ }
+
+ PendingTrampoline remove(@NonNull String packageName) {
+ PendingTrampoline pendingTrampoline = mMap.remove(packageName);
+ mHandler.removeCallbacksAndMessages(pendingTrampoline);
+ return pendingTrampoline;
+ }
+ }
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 7b17162..95ec9e9 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -100,7 +100,7 @@
name: "services.core.unboosted",
defaults: ["platform_service_defaults"],
srcs: [
- ":android.hardware.biometrics.face-V1-java-source",
+ ":android.hardware.biometrics.face-V2-java-source",
":statslog-art-java-gen",
":statslog-contexthub-java-gen",
":services.core-sources",
diff --git a/services/core/java/com/android/server/Dumpable.java b/services/core/java/com/android/server/Dumpable.java
index 866f81c..004f923 100644
--- a/services/core/java/com/android/server/Dumpable.java
+++ b/services/core/java/com/android/server/Dumpable.java
@@ -24,6 +24,8 @@
*
* <p>See {@link SystemServer.SystemServerDumper} for usage example.
*/
+// TODO(b/149254050): replace / merge with package android.util.Dumpable (it would require
+// exporting IndentingPrintWriter as @SystemApi) and/or changing the method to use a prefix
public interface Dumpable {
/**
diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java
index 513d86e..62dc2733 100644
--- a/services/core/java/com/android/server/MasterClearReceiver.java
+++ b/services/core/java/com/android/server/MasterClearReceiver.java
@@ -126,8 +126,8 @@
private boolean wipeUser(Context context, @UserIdInt int userId, String wipeReason) {
final UserManager userManager = context.getSystemService(UserManager.class);
- final int result = userManager.removeUserOrSetEphemeral(
- userId, /* evenWhenDisallowed= */ false);
+ final int result = userManager.removeUserWhenPossible(
+ UserHandle.of(userId), /* overrideDevicePolicy= */ false);
if (result == UserManager.REMOVE_RESULT_ERROR) {
Slogf.e(TAG, "Can't remove user %d", userId);
return false;
diff --git a/services/core/java/com/android/server/SmartStorageMaintIdler.java b/services/core/java/com/android/server/SmartStorageMaintIdler.java
new file mode 100644
index 0000000..2dff72f
--- /dev/null
+++ b/services/core/java/com/android/server/SmartStorageMaintIdler.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.Slog;
+
+import java.util.concurrent.TimeUnit;
+
+public class SmartStorageMaintIdler extends JobService {
+ private static final String TAG = "SmartStorageMaintIdler";
+
+ private static final ComponentName SMART_STORAGE_MAINT_SERVICE =
+ new ComponentName("android", SmartStorageMaintIdler.class.getName());
+
+ private static final int SMART_MAINT_JOB_ID = 2808;
+
+ private boolean mStarted;
+ private JobParameters mJobParams;
+ private final Runnable mFinishCallback = new Runnable() {
+ @Override
+ public void run() {
+ Slog.i(TAG, "Got smart storage maintenance service completion callback");
+ if (mStarted) {
+ jobFinished(mJobParams, false);
+ mStarted = false;
+ }
+ // ... and try again in a next period
+ scheduleSmartIdlePass(SmartStorageMaintIdler.this,
+ StorageManagerService.SMART_IDLE_MAINT_PERIOD);
+ }
+ };
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ mJobParams = params;
+ StorageManagerService ms = StorageManagerService.sSelf;
+ if (ms != null) {
+ mStarted = true;
+ ms.runSmartIdleMaint(mFinishCallback);
+ }
+ return ms != null;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ mStarted = false;
+ return false;
+ }
+
+ /**
+ * Schedule the smart storage idle maintenance job
+ */
+ public static void scheduleSmartIdlePass(Context context, int nHours) {
+ StorageManagerService ms = StorageManagerService.sSelf;
+ if ((ms == null) || ms.isPassedLifetimeThresh()) {
+ return;
+ }
+
+ JobScheduler tm = context.getSystemService(JobScheduler.class);
+
+ long nextScheduleTime = TimeUnit.HOURS.toMillis(nHours);
+
+ JobInfo.Builder builder = new JobInfo.Builder(SMART_MAINT_JOB_ID,
+ SMART_STORAGE_MAINT_SERVICE);
+
+ builder.setMinimumLatency(nextScheduleTime);
+ tm.schedule(builder.build());
+ }
+}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index ec018de..98764e0 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -79,6 +79,7 @@
import android.content.res.ObbInfo;
import android.database.ContentObserver;
import android.net.Uri;
+import android.os.BatteryManager;
import android.os.Binder;
import android.os.Build;
import android.os.DropBoxManager;
@@ -159,6 +160,8 @@
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal.ScreenObserver;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
import libcore.io.IoUtils;
import libcore.util.EmptyArray;
@@ -340,7 +343,44 @@
@Nullable public static String sMediaStoreAuthorityProcessName;
+ // Run period in hour for smart idle maintenance
+ static final int SMART_IDLE_MAINT_PERIOD = 1;
+
private final AtomicFile mSettingsFile;
+ private final AtomicFile mHourlyWriteFile;
+
+ private static final int MAX_HOURLY_WRITE_RECORDS = 72;
+
+ /**
+ * Default config values for smart idle maintenance
+ * Actual values will be controlled by DeviceConfig
+ */
+ // Decide whether smart idle maintenance is enabled or not
+ private static final boolean DEFAULT_SMART_IDLE_MAINT_ENABLED = false;
+ // Storage lifetime percentage threshold to decide to turn off the feature
+ private static final int DEFAULT_LIFETIME_PERCENT_THRESHOLD = 70;
+ // Minimum required number of dirty + free segments to trigger GC
+ private static final int DEFAULT_MIN_SEGMENTS_THRESHOLD = 512;
+ // Determine how much portion of current dirty segments will be GCed
+ private static final float DEFAULT_DIRTY_RECLAIM_RATE = 0.5F;
+ // Multiplier to amplify the target segment number for GC
+ private static final float DEFAULT_SEGMENT_RECLAIM_WEIGHT = 1.0F;
+ // Low battery level threshold to decide to turn off the feature
+ private static final float DEFAULT_LOW_BATTERY_LEVEL = 20F;
+ // Decide whether charging is required to turn on the feature
+ private static final boolean DEFAULT_CHARGING_REQUIRED = true;
+
+ private volatile int mLifetimePercentThreshold;
+ private volatile int mMinSegmentsThreshold;
+ private volatile float mDirtyReclaimRate;
+ private volatile float mSegmentReclaimWeight;
+ private volatile float mLowBatteryLevel;
+ private volatile boolean mChargingRequired;
+ private volatile boolean mNeedGC;
+
+ private volatile boolean mPassedLifetimeThresh;
+ // Tracking storage hourly write amounts
+ private volatile int[] mStorageHourlyWrites;
/**
* <em>Never</em> hold the lock while performing downcalls into vold, since
@@ -896,6 +936,10 @@
}
private void handleSystemReady() {
+ if (prepareSmartIdleMaint()) {
+ SmartStorageMaintIdler.scheduleSmartIdlePass(mContext, SMART_IDLE_MAINT_PERIOD);
+ }
+
// Start scheduling nominally-daily fstrim operations
MountServiceIdler.scheduleIdlePass(mContext);
@@ -1912,6 +1956,10 @@
mSettingsFile = new AtomicFile(
new File(Environment.getDataSystemDirectory(), "storage.xml"), "storage-settings");
+ mHourlyWriteFile = new AtomicFile(
+ new File(Environment.getDataSystemDirectory(), "storage-hourly-writes"));
+
+ mStorageHourlyWrites = new int[MAX_HOURLY_WRITE_RECORDS];
synchronized (mLock) {
readSettingsLocked();
@@ -2574,7 +2622,7 @@
// fstrim time is still updated. If file based checkpoints are used, we run
// idle maintenance (GC + fstrim) regardless of checkpoint status.
if (!needsCheckpoint() || !supportsBlockCheckpoint()) {
- mVold.runIdleMaint(new IVoldTaskListener.Stub() {
+ mVold.runIdleMaint(mNeedGC, new IVoldTaskListener.Stub() {
@Override
public void onStatus(int status, PersistableBundle extras) {
// Not currently used
@@ -2625,6 +2673,176 @@
abortIdleMaint(null);
}
+ private boolean prepareSmartIdleMaint() {
+ /**
+ * We can choose whether going with a new storage smart idle maintenance job
+ * or falling back to the traditional way using DeviceConfig
+ */
+ boolean smartIdleMaintEnabled = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+ "smart_idle_maint_enabled",
+ DEFAULT_SMART_IDLE_MAINT_ENABLED);
+ if (smartIdleMaintEnabled) {
+ mLifetimePercentThreshold = DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+ "lifetime_threshold", DEFAULT_LIFETIME_PERCENT_THRESHOLD);
+ mMinSegmentsThreshold = DeviceConfig.getInt(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+ "min_segments_threshold", DEFAULT_MIN_SEGMENTS_THRESHOLD);
+ mDirtyReclaimRate = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+ "dirty_reclaim_rate", DEFAULT_DIRTY_RECLAIM_RATE);
+ mSegmentReclaimWeight = DeviceConfig.getFloat(
+ DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+ "segment_reclaim_weight", DEFAULT_SEGMENT_RECLAIM_WEIGHT);
+ mLowBatteryLevel = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+ "low_battery_level", DEFAULT_LOW_BATTERY_LEVEL);
+ mChargingRequired = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+ "charging_required", DEFAULT_CHARGING_REQUIRED);
+
+ // If we use the smart idle maintenance, we need to turn off GC in the traditional idle
+ // maintenance to avoid the conflict
+ mNeedGC = false;
+
+ loadStorageHourlyWrites();
+ try {
+ mVold.refreshLatestWrite();
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ refreshLifetimeConstraint();
+ }
+ return smartIdleMaintEnabled;
+ }
+
+ // Return whether storage lifetime exceeds the threshold
+ public boolean isPassedLifetimeThresh() {
+ return mPassedLifetimeThresh;
+ }
+
+ private void loadStorageHourlyWrites() {
+ FileInputStream fis = null;
+
+ try {
+ fis = mHourlyWriteFile.openRead();
+ ObjectInputStream ois = new ObjectInputStream(fis);
+ mStorageHourlyWrites = (int[])ois.readObject();
+ } catch (FileNotFoundException e) {
+ // Missing data is okay, probably first boot
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Failed reading write records", e);
+ } finally {
+ IoUtils.closeQuietly(fis);
+ }
+ }
+
+ private int getAverageHourlyWrite() {
+ return Arrays.stream(mStorageHourlyWrites).sum() / MAX_HOURLY_WRITE_RECORDS;
+ }
+
+ private void updateStorageHourlyWrites(int latestWrite) {
+ FileOutputStream fos = null;
+
+ System.arraycopy(mStorageHourlyWrites,0, mStorageHourlyWrites, 1,
+ MAX_HOURLY_WRITE_RECORDS - 1);
+ mStorageHourlyWrites[0] = latestWrite;
+ try {
+ fos = mHourlyWriteFile.startWrite();
+ ObjectOutputStream oos = new ObjectOutputStream(fos);
+ oos.writeObject(mStorageHourlyWrites);
+ mHourlyWriteFile.finishWrite(fos);
+ } catch (IOException e) {
+ if (fos != null) {
+ mHourlyWriteFile.failWrite(fos);
+ }
+ }
+ }
+
+ private boolean checkChargeStatus() {
+ IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+ Intent batteryStatus = mContext.registerReceiver(null, ifilter);
+
+ if (mChargingRequired) {
+ int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
+ if (status != BatteryManager.BATTERY_STATUS_CHARGING &&
+ status != BatteryManager.BATTERY_STATUS_FULL) {
+ Slog.w(TAG, "Battery is not being charged");
+ return false;
+ }
+ }
+
+ int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+ int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
+ float chargePercent = level * 100f / (float)scale;
+
+ if (chargePercent < mLowBatteryLevel) {
+ Slog.w(TAG, "Battery level is " + chargePercent + ", which is lower than threshold: " +
+ mLowBatteryLevel);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean refreshLifetimeConstraint() {
+ int storageLifeTime = 0;
+
+ try {
+ storageLifeTime = mVold.getStorageLifeTime();
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return false;
+ }
+
+ if (storageLifeTime == -1) {
+ Slog.w(TAG, "Failed to get storage lifetime");
+ return false;
+ } else if (storageLifeTime > mLifetimePercentThreshold) {
+ Slog.w(TAG, "Ended smart idle maintenance, because of lifetime(" + storageLifeTime +
+ ")" + ", lifetime threshold(" + mLifetimePercentThreshold + ")");
+ mPassedLifetimeThresh = true;
+ return false;
+ }
+ Slog.i(TAG, "Storage lifetime: " + storageLifeTime);
+ return true;
+ }
+
+ void runSmartIdleMaint(Runnable callback) {
+ enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+
+ try {
+ // Block based checkpoint process runs fstrim. So, if checkpoint is in progress
+ // (first boot after OTA), We skip the smart idle maintenance
+ if (!needsCheckpoint() || !supportsBlockCheckpoint()) {
+ if (!refreshLifetimeConstraint() || !checkChargeStatus()) {
+ return;
+ }
+
+ int latestHourlyWrite = mVold.getWriteAmount();
+ if (latestHourlyWrite == -1) {
+ Slog.w(TAG, "Failed to get storage hourly write");
+ return;
+ }
+
+ updateStorageHourlyWrites(latestHourlyWrite);
+ int avgHourlyWrite = getAverageHourlyWrite();
+
+ Slog.i(TAG, "Set smart idle maintenance: " + "latest hourly write: " +
+ latestHourlyWrite + ", average hourly write: " + avgHourlyWrite +
+ ", min segment threshold: " + mMinSegmentsThreshold +
+ ", dirty reclaim rate: " + mDirtyReclaimRate +
+ ", segment reclaim weight:" + mSegmentReclaimWeight);
+ mVold.setGCUrgentPace(avgHourlyWrite, mMinSegmentsThreshold, mDirtyReclaimRate,
+ mSegmentReclaimWeight);
+ } else {
+ Slog.i(TAG, "Skipping smart idle maintenance - block based checkpoint in progress");
+ }
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ } finally {
+ if (callback != null) {
+ callback.run();
+ }
+ }
+ }
+
@Override
public void setDebugFlags(int flags, int mask) {
enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 023a11e..0961fcb3 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1124,6 +1124,12 @@
private void makeLeAudioDeviceAvailable(String address, String name, int streamType, int device,
String eventSource) {
if (device != AudioSystem.DEVICE_NONE) {
+
+ /* Audio Policy sees Le Audio similar to A2DP. Let's make sure
+ * AUDIO_POLICY_FORCE_NO_BT_A2DP is not set
+ */
+ mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource);
+
AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_AVAILABLE,
address, name, AudioSystem.AUDIO_FORMAT_DEFAULT);
mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 805e45d..346ae0f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2611,6 +2611,16 @@
return getDevicesForAttributesInt(attributes);
}
+ /** @see AudioManager#getAudioDevicesForAttributes(AudioAttributes)
+ * This method is similar with AudioService#getDevicesForAttributes,
+ * only it doesn't enforce permissions because it is used by an unprivileged public API
+ * instead of the system API.
+ */
+ public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributesUnprotected(
+ @NonNull AudioAttributes attributes) {
+ return getDevicesForAttributesInt(attributes);
+ }
+
/**
* @see AudioManager#isMusicActive()
* @param remotely true if query is for remote playback (cast), false for local playback.
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 406b2dd2..74c8999 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -252,6 +252,9 @@
AudioAttributes.FLAG_BYPASS_MUTE;
private void checkVolumeForPrivilegedAlarm(AudioPlaybackConfiguration apc, int event) {
+ if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_DEVICE_ID) {
+ return;
+ }
if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED ||
apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
if ((apc.getAudioAttributes().getAllFlags() & FLAGS_FOR_SILENCE_OVERRIDE)
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index b73e911..26bbb40 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors;
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -48,7 +50,6 @@
* Interface that ClientMonitor holders should use to receive callbacks.
*/
public interface Callback {
-
/**
* Invoked when the ClientMonitor operation has been started (e.g. reached the head of
* the queue and becomes the current operation).
@@ -203,7 +204,8 @@
}
/** Signals this operation has completed its lifecycle and should no longer be used. */
- void destroy() {
+ @VisibleForTesting(visibility = Visibility.PACKAGE)
+ public void destroy() {
mAlreadyDone = true;
if (mToken != null) {
try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index a358bc2..39c5944 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -17,10 +17,10 @@
package com.android.server.biometrics.sensors;
import android.annotation.IntDef;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.IBiometricService;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Handler;
@@ -55,6 +55,7 @@
* We currently assume (and require) that each biometric sensor have its own instance of a
* {@link BiometricScheduler}. See {@link CoexCoordinator}.
*/
+@MainThread
public class BiometricScheduler {
private static final String BASE_TAG = "BiometricScheduler";
@@ -110,123 +111,6 @@
}
}
- /**
- * Contains all the necessary information for a HAL operation.
- */
- @VisibleForTesting
- static final class Operation {
-
- /**
- * The operation is added to the list of pending operations and waiting for its turn.
- */
- static final int STATE_WAITING_IN_QUEUE = 0;
-
- /**
- * The operation is added to the list of pending operations, but a subsequent operation
- * has been added. This state only applies to {@link Interruptable} operations. When this
- * operation reaches the head of the queue, it will send ERROR_CANCELED and finish.
- */
- static final int STATE_WAITING_IN_QUEUE_CANCELING = 1;
-
- /**
- * The operation has reached the front of the queue and has started.
- */
- static final int STATE_STARTED = 2;
-
- /**
- * The operation was started, but is now canceling. Operations should wait for the HAL to
- * acknowledge that the operation was canceled, at which point it finishes.
- */
- static final int STATE_STARTED_CANCELING = 3;
-
- /**
- * The operation has reached the head of the queue but is waiting for BiometricService
- * to acknowledge and start the operation.
- */
- static final int STATE_WAITING_FOR_COOKIE = 4;
-
- /**
- * The {@link BaseClientMonitor.Callback} has been invoked and the client is finished.
- */
- static final int STATE_FINISHED = 5;
-
- @IntDef({STATE_WAITING_IN_QUEUE,
- STATE_WAITING_IN_QUEUE_CANCELING,
- STATE_STARTED,
- STATE_STARTED_CANCELING,
- STATE_WAITING_FOR_COOKIE,
- STATE_FINISHED})
- @Retention(RetentionPolicy.SOURCE)
- @interface OperationState {}
-
- @NonNull final BaseClientMonitor mClientMonitor;
- @Nullable final BaseClientMonitor.Callback mClientCallback;
- @OperationState int mState;
-
- Operation(
- @NonNull BaseClientMonitor clientMonitor,
- @Nullable BaseClientMonitor.Callback callback
- ) {
- this(clientMonitor, callback, STATE_WAITING_IN_QUEUE);
- }
-
- protected Operation(
- @NonNull BaseClientMonitor clientMonitor,
- @Nullable BaseClientMonitor.Callback callback,
- @OperationState int state
- ) {
- mClientMonitor = clientMonitor;
- mClientCallback = callback;
- mState = state;
- }
-
- public boolean isHalOperation() {
- return mClientMonitor instanceof HalClientMonitor<?>;
- }
-
- /**
- * @return true if the operation requires the HAL, and the HAL is null.
- */
- public boolean isUnstartableHalOperation() {
- if (isHalOperation()) {
- final HalClientMonitor<?> client = (HalClientMonitor<?>) mClientMonitor;
- if (client.getFreshDaemon() == null) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public String toString() {
- return mClientMonitor + ", State: " + mState;
- }
- }
-
- /**
- * Monitors an operation's cancellation. If cancellation takes too long, the watchdog will
- * kill the current operation and forcibly start the next.
- */
- private static final class CancellationWatchdog implements Runnable {
- static final int DELAY_MS = 3000;
-
- final String tag;
- final Operation operation;
- CancellationWatchdog(String tag, Operation operation) {
- this.tag = tag;
- this.operation = operation;
- }
-
- @Override
- public void run() {
- if (operation.mState != Operation.STATE_FINISHED) {
- Slog.e(tag, "[Watchdog Triggered]: " + operation);
- operation.mClientMonitor.mCallback
- .onClientFinished(operation.mClientMonitor, false /* success */);
- }
- }
- }
-
private static final class CrashState {
static final int NUM_ENTRIES = 10;
final String timestamp;
@@ -263,10 +147,9 @@
private final @SensorType int mSensorType;
@Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
@NonNull private final IBiometricService mBiometricService;
- @NonNull protected final Handler mHandler = new Handler(Looper.getMainLooper());
- @NonNull private final InternalCallback mInternalCallback;
- @VisibleForTesting @NonNull final Deque<Operation> mPendingOperations;
- @VisibleForTesting @Nullable Operation mCurrentOperation;
+ @NonNull protected final Handler mHandler;
+ @VisibleForTesting @NonNull final Deque<BiometricSchedulerOperation> mPendingOperations;
+ @VisibleForTesting @Nullable BiometricSchedulerOperation mCurrentOperation;
@NonNull private final ArrayDeque<CrashState> mCrashStates;
private int mTotalOperationsHandled;
@@ -277,7 +160,7 @@
// Internal callback, notified when an operation is complete. Notifies the requester
// that the operation is complete, before performing internal scheduler work (such as
// starting the next client).
- public class InternalCallback implements BaseClientMonitor.Callback {
+ private final BaseClientMonitor.Callback mInternalCallback = new BaseClientMonitor.Callback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
Slog.d(getTag(), "[Started] " + clientMonitor);
@@ -286,16 +169,11 @@
mCoexCoordinator.addAuthenticationClient(mSensorType,
(AuthenticationClient<?>) clientMonitor);
}
-
- if (mCurrentOperation.mClientCallback != null) {
- mCurrentOperation.mClientCallback.onClientStarted(clientMonitor);
- }
}
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
mHandler.post(() -> {
- clientMonitor.destroy();
if (mCurrentOperation == null) {
Slog.e(getTag(), "[Finishing] " + clientMonitor
+ " but current operation is null, success: " + success
@@ -303,9 +181,9 @@
return;
}
- if (clientMonitor != mCurrentOperation.mClientMonitor) {
+ if (!mCurrentOperation.isFor(clientMonitor)) {
Slog.e(getTag(), "[Ignoring Finish] " + clientMonitor + " does not match"
- + " current: " + mCurrentOperation.mClientMonitor);
+ + " current: " + mCurrentOperation);
return;
}
@@ -315,36 +193,33 @@
(AuthenticationClient<?>) clientMonitor);
}
- mCurrentOperation.mState = Operation.STATE_FINISHED;
-
- if (mCurrentOperation.mClientCallback != null) {
- mCurrentOperation.mClientCallback.onClientFinished(clientMonitor, success);
- }
-
if (mGestureAvailabilityDispatcher != null) {
mGestureAvailabilityDispatcher.markSensorActive(
- mCurrentOperation.mClientMonitor.getSensorId(), false /* active */);
+ mCurrentOperation.getSensorId(), false /* active */);
}
if (mRecentOperations.size() >= mRecentOperationsLimit) {
mRecentOperations.remove(0);
}
- mRecentOperations.add(mCurrentOperation.mClientMonitor.getProtoEnum());
+ mRecentOperations.add(mCurrentOperation.getProtoEnum());
mCurrentOperation = null;
mTotalOperationsHandled++;
startNextOperationIfIdle();
});
}
- }
+ };
@VisibleForTesting
- BiometricScheduler(@NonNull String tag, @SensorType int sensorType,
+ BiometricScheduler(@NonNull String tag,
+ @NonNull Handler handler,
+ @SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
- @NonNull IBiometricService biometricService, int recentOperationsLimit,
+ @NonNull IBiometricService biometricService,
+ int recentOperationsLimit,
@NonNull CoexCoordinator coexCoordinator) {
mBiometricTag = tag;
+ mHandler = handler;
mSensorType = sensorType;
- mInternalCallback = new InternalCallback();
mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
mPendingOperations = new ArrayDeque<>();
mBiometricService = biometricService;
@@ -356,6 +231,7 @@
/**
* Creates a new scheduler.
+ *
* @param tag for the specific instance of the scheduler. Should be unique.
* @param sensorType the sensorType that this scheduler is handling.
* @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures
@@ -364,16 +240,14 @@
public BiometricScheduler(@NonNull String tag,
@SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
- this(tag, sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
- ServiceManager.getService(Context.BIOMETRIC_SERVICE)), LOG_NUM_RECENT_OPERATIONS,
- CoexCoordinator.getInstance());
+ this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher,
+ IBiometricService.Stub.asInterface(
+ ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
+ LOG_NUM_RECENT_OPERATIONS, CoexCoordinator.getInstance());
}
- /**
- * @return A reference to the internal callback that should be invoked whenever the scheduler
- * needs to (e.g. client started, client finished).
- */
- @NonNull protected InternalCallback getInternalCallback() {
+ @VisibleForTesting
+ public BaseClientMonitor.Callback getInternalCallback() {
return mInternalCallback;
}
@@ -392,72 +266,46 @@
}
mCurrentOperation = mPendingOperations.poll();
- final BaseClientMonitor currentClient = mCurrentOperation.mClientMonitor;
Slog.d(getTag(), "[Polled] " + mCurrentOperation);
// If the operation at the front of the queue has been marked for cancellation, send
// ERROR_CANCELED. No need to start this client.
- if (mCurrentOperation.mState == Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
+ if (mCurrentOperation.isMarkedCanceling()) {
Slog.d(getTag(), "[Now Cancelling] " + mCurrentOperation);
- if (!(currentClient instanceof Interruptable)) {
- throw new IllegalStateException("Mis-implemented client or scheduler, "
- + "trying to cancel non-interruptable operation: " + mCurrentOperation);
- }
-
- final Interruptable interruptable = (Interruptable) currentClient;
- interruptable.cancelWithoutStarting(getInternalCallback());
+ mCurrentOperation.cancel(mHandler, mInternalCallback);
// Now we wait for the client to send its FinishCallback, which kicks off the next
// operation.
return;
}
- if (mGestureAvailabilityDispatcher != null
- && mCurrentOperation.mClientMonitor instanceof AcquisitionClient) {
+ if (mGestureAvailabilityDispatcher != null && mCurrentOperation.isAcquisitionOperation()) {
mGestureAvailabilityDispatcher.markSensorActive(
- mCurrentOperation.mClientMonitor.getSensorId(),
- true /* active */);
+ mCurrentOperation.getSensorId(), true /* active */);
}
// Not all operations start immediately. BiometricPrompt waits for its operation
// to arrive at the head of the queue, before pinging it to start.
- final boolean shouldStartNow = currentClient.getCookie() == 0;
- if (shouldStartNow) {
- if (mCurrentOperation.isUnstartableHalOperation()) {
- final HalClientMonitor<?> halClientMonitor =
- (HalClientMonitor<?>) mCurrentOperation.mClientMonitor;
+ final int cookie = mCurrentOperation.isReadyToStart();
+ if (cookie == 0) {
+ if (!mCurrentOperation.start(mInternalCallback)) {
// Note down current length of queue
final int pendingOperationsLength = mPendingOperations.size();
- final Operation lastOperation = mPendingOperations.peekLast();
+ final BiometricSchedulerOperation lastOperation = mPendingOperations.peekLast();
Slog.e(getTag(), "[Unable To Start] " + mCurrentOperation
+ ". Last pending operation: " + lastOperation);
- // For current operations, 1) unableToStart, which notifies the caller-side, then
- // 2) notify operation's callback, to notify applicable system service that the
- // operation failed.
- halClientMonitor.unableToStart();
- if (mCurrentOperation.mClientCallback != null) {
- mCurrentOperation.mClientCallback.onClientFinished(
- mCurrentOperation.mClientMonitor, false /* success */);
- }
-
// Then for each operation currently in the pending queue at the time of this
// failure, do the same as above. Otherwise, it's possible that something like
// setActiveUser fails, but then authenticate (for the wrong user) is invoked.
for (int i = 0; i < pendingOperationsLength; i++) {
- final Operation operation = mPendingOperations.pollFirst();
- if (operation == null) {
+ final BiometricSchedulerOperation operation = mPendingOperations.pollFirst();
+ if (operation != null) {
+ Slog.w(getTag(), "[Aborting Operation] " + operation);
+ operation.abort();
+ } else {
Slog.e(getTag(), "Null operation, index: " + i
+ ", expected length: " + pendingOperationsLength);
- break;
}
- if (operation.isHalOperation()) {
- ((HalClientMonitor<?>) operation.mClientMonitor).unableToStart();
- }
- if (operation.mClientCallback != null) {
- operation.mClientCallback.onClientFinished(operation.mClientMonitor,
- false /* success */);
- }
- Slog.w(getTag(), "[Aborted Operation] " + operation);
}
// It's possible that during cleanup a new set of operations came in. We can try to
@@ -465,25 +313,20 @@
// actually be multiple operations (i.e. updateActiveUser + authenticate).
mCurrentOperation = null;
startNextOperationIfIdle();
- } else {
- Slog.d(getTag(), "[Starting] " + mCurrentOperation);
- currentClient.start(getInternalCallback());
- mCurrentOperation.mState = Operation.STATE_STARTED;
}
} else {
try {
- mBiometricService.onReadyForAuthentication(currentClient.getCookie());
+ mBiometricService.onReadyForAuthentication(cookie);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when contacting BiometricService", e);
}
Slog.d(getTag(), "Waiting for cookie before starting: " + mCurrentOperation);
- mCurrentOperation.mState = Operation.STATE_WAITING_FOR_COOKIE;
}
}
/**
* Starts the {@link #mCurrentOperation} if
- * 1) its state is {@link Operation#STATE_WAITING_FOR_COOKIE} and
+ * 1) its state is {@link BiometricSchedulerOperation#STATE_WAITING_FOR_COOKIE} and
* 2) its cookie matches this cookie
*
* This is currently only used by {@link com.android.server.biometrics.BiometricService}, which
@@ -499,45 +342,13 @@
Slog.e(getTag(), "Current operation is null");
return;
}
- if (mCurrentOperation.mState != Operation.STATE_WAITING_FOR_COOKIE) {
- if (mCurrentOperation.mState == Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
- Slog.d(getTag(), "Operation was marked for cancellation, cancelling now: "
- + mCurrentOperation);
- // This should trigger the internal onClientFinished callback, which clears the
- // operation and starts the next one.
- final ErrorConsumer errorConsumer =
- (ErrorConsumer) mCurrentOperation.mClientMonitor;
- errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED,
- 0 /* vendorCode */);
- return;
- } else {
- Slog.e(getTag(), "Operation is in the wrong state: " + mCurrentOperation
- + ", expected STATE_WAITING_FOR_COOKIE");
- return;
- }
- }
- if (mCurrentOperation.mClientMonitor.getCookie() != cookie) {
- Slog.e(getTag(), "Mismatched cookie for operation: " + mCurrentOperation
- + ", received: " + cookie);
- return;
- }
- if (mCurrentOperation.isUnstartableHalOperation()) {
+ if (mCurrentOperation.startWithCookie(mInternalCallback, cookie)) {
+ Slog.d(getTag(), "[Started] Prepared client: " + mCurrentOperation);
+ } else {
Slog.e(getTag(), "[Unable To Start] Prepared client: " + mCurrentOperation);
- // This is BiometricPrompt trying to auth but something's wrong with the HAL.
- final HalClientMonitor<?> halClientMonitor =
- (HalClientMonitor<?>) mCurrentOperation.mClientMonitor;
- halClientMonitor.unableToStart();
- if (mCurrentOperation.mClientCallback != null) {
- mCurrentOperation.mClientCallback.onClientFinished(mCurrentOperation.mClientMonitor,
- false /* success */);
- }
mCurrentOperation = null;
startNextOperationIfIdle();
- } else {
- Slog.d(getTag(), "[Starting] Prepared client: " + mCurrentOperation);
- mCurrentOperation.mState = Operation.STATE_STARTED;
- mCurrentOperation.mClientMonitor.start(getInternalCallback());
}
}
@@ -562,17 +373,14 @@
// pending clients as canceling. Once they reach the head of the queue, the scheduler will
// send ERROR_CANCELED and skip the operation.
if (clientMonitor.interruptsPrecedingClients()) {
- for (Operation operation : mPendingOperations) {
- if (operation.mClientMonitor instanceof Interruptable
- && operation.mState != Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
- Slog.d(getTag(), "New client incoming, marking pending client as canceling: "
- + operation.mClientMonitor);
- operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
+ for (BiometricSchedulerOperation operation : mPendingOperations) {
+ if (operation.markCanceling()) {
+ Slog.d(getTag(), "New client, marking pending op as canceling: " + operation);
}
}
}
- mPendingOperations.add(new Operation(clientMonitor, clientCallback));
+ mPendingOperations.add(new BiometricSchedulerOperation(clientMonitor, clientCallback));
Slog.d(getTag(), "[Added] " + clientMonitor
+ ", new queue size: " + mPendingOperations.size());
@@ -580,67 +388,34 @@
// cancellable, start the cancellation process.
if (clientMonitor.interruptsPrecedingClients()
&& mCurrentOperation != null
- && mCurrentOperation.mClientMonitor instanceof Interruptable
- && mCurrentOperation.mState == Operation.STATE_STARTED) {
+ && mCurrentOperation.isInterruptable()
+ && mCurrentOperation.isStarted()) {
Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation);
- cancelInternal(mCurrentOperation);
- }
-
- startNextOperationIfIdle();
- }
-
- private void cancelInternal(Operation operation) {
- if (operation != mCurrentOperation) {
- Slog.e(getTag(), "cancelInternal invoked on non-current operation: " + operation);
- return;
- }
- if (!(operation.mClientMonitor instanceof Interruptable)) {
- Slog.w(getTag(), "Operation not interruptable: " + operation);
- return;
- }
- if (operation.mState == Operation.STATE_STARTED_CANCELING) {
- Slog.w(getTag(), "Cancel already invoked for operation: " + operation);
- return;
- }
- if (operation.mState == Operation.STATE_WAITING_FOR_COOKIE) {
- Slog.w(getTag(), "Skipping cancellation for non-started operation: " + operation);
- // We can set it to null immediately, since the HAL was never notified to start.
- if (mCurrentOperation != null) {
- mCurrentOperation.mClientMonitor.destroy();
- }
- mCurrentOperation = null;
+ mCurrentOperation.cancel(mHandler, mInternalCallback);
+ } else {
startNextOperationIfIdle();
- return;
}
- Slog.d(getTag(), "[Cancelling] Current client: " + operation.mClientMonitor);
- final Interruptable interruptable = (Interruptable) operation.mClientMonitor;
- interruptable.cancel();
- operation.mState = Operation.STATE_STARTED_CANCELING;
-
- // Add a watchdog. If the HAL does not acknowledge within the timeout, we will
- // forcibly finish this client.
- mHandler.postDelayed(new CancellationWatchdog(getTag(), operation),
- CancellationWatchdog.DELAY_MS);
}
/**
* Requests to cancel enrollment.
* @param token from the caller, should match the token passed in when requesting enrollment
*/
- public void cancelEnrollment(IBinder token) {
- if (mCurrentOperation == null) {
- Slog.e(getTag(), "Unable to cancel enrollment, null operation");
- return;
- }
- final boolean isEnrolling = mCurrentOperation.mClientMonitor instanceof EnrollClient;
- final boolean tokenMatches = mCurrentOperation.mClientMonitor.getToken() == token;
- if (!isEnrolling || !tokenMatches) {
- Slog.w(getTag(), "Not cancelling enrollment, isEnrolling: " + isEnrolling
- + " tokenMatches: " + tokenMatches);
- return;
- }
+ public void cancelEnrollment(IBinder token, long requestId) {
+ Slog.d(getTag(), "cancelEnrollment, requestId: " + requestId);
- cancelInternal(mCurrentOperation);
+ if (mCurrentOperation != null
+ && canCancelEnrollOperation(mCurrentOperation, token, requestId)) {
+ Slog.d(getTag(), "Cancelling enrollment op: " + mCurrentOperation);
+ mCurrentOperation.cancel(mHandler, mInternalCallback);
+ } else {
+ for (BiometricSchedulerOperation operation : mPendingOperations) {
+ if (canCancelEnrollOperation(operation, token, requestId)) {
+ Slog.d(getTag(), "Cancelling pending enrollment op: " + operation);
+ operation.markCanceling();
+ }
+ }
+ }
}
/**
@@ -649,62 +424,42 @@
* @param requestId the id returned when requesting authentication
*/
public void cancelAuthenticationOrDetection(IBinder token, long requestId) {
- Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId
- + " current: " + mCurrentOperation
- + " stack size: " + mPendingOperations.size());
+ Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId);
if (mCurrentOperation != null
&& canCancelAuthOperation(mCurrentOperation, token, requestId)) {
- Slog.d(getTag(), "Cancelling: " + mCurrentOperation);
- cancelInternal(mCurrentOperation);
+ Slog.d(getTag(), "Cancelling auth/detect op: " + mCurrentOperation);
+ mCurrentOperation.cancel(mHandler, mInternalCallback);
} else {
- // Look through the current queue for all authentication clients for the specified
- // token, and mark them as STATE_WAITING_IN_QUEUE_CANCELING. Note that we're marking
- // all of them, instead of just the first one, since the API surface currently doesn't
- // allow us to distinguish between multiple authentication requests from the same
- // process. However, this generally does not happen anyway, and would be a class of
- // bugs on its own.
- for (Operation operation : mPendingOperations) {
+ for (BiometricSchedulerOperation operation : mPendingOperations) {
if (canCancelAuthOperation(operation, token, requestId)) {
- Slog.d(getTag(), "Marking " + operation
- + " as STATE_WAITING_IN_QUEUE_CANCELING");
- operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
+ Slog.d(getTag(), "Cancelling pending auth/detect op: " + operation);
+ operation.markCanceling();
}
}
}
}
- private static boolean canCancelAuthOperation(Operation operation, IBinder token,
- long requestId) {
+ private static boolean canCancelEnrollOperation(BiometricSchedulerOperation operation,
+ IBinder token, long requestId) {
+ return operation.isEnrollOperation()
+ && operation.isMatchingToken(token)
+ && operation.isMatchingRequestId(requestId);
+ }
+
+ private static boolean canCancelAuthOperation(BiometricSchedulerOperation operation,
+ IBinder token, long requestId) {
// TODO: restrict callers that can cancel without requestId (negative value)?
- return isAuthenticationOrDetectionOperation(operation)
- && operation.mClientMonitor.getToken() == token
- && isMatchingRequestId(operation, requestId);
- }
-
- // By default, monitors are not associated with a request id to retain the original
- // behavior (i.e. if no requestId is explicitly set then assume it matches)
- private static boolean isMatchingRequestId(Operation operation, long requestId) {
- return !operation.mClientMonitor.hasRequestId()
- || operation.mClientMonitor.getRequestId() == requestId;
- }
-
- private static boolean isAuthenticationOrDetectionOperation(@NonNull Operation operation) {
- final boolean isAuthentication =
- operation.mClientMonitor instanceof AuthenticationConsumer;
- final boolean isDetection =
- operation.mClientMonitor instanceof DetectionConsumer;
- return isAuthentication || isDetection;
+ return operation.isAuthenticationOrDetectionOperation()
+ && operation.isMatchingToken(token)
+ && operation.isMatchingRequestId(requestId);
}
/**
* @return the current operation
*/
public BaseClientMonitor getCurrentClient() {
- if (mCurrentOperation == null) {
- return null;
- }
- return mCurrentOperation.mClientMonitor;
+ return mCurrentOperation != null ? mCurrentOperation.getClientMonitor() : null;
}
public int getCurrentPendingCount() {
@@ -719,7 +474,7 @@
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
final String timestamp = dateFormat.format(new Date(System.currentTimeMillis()));
final List<String> pendingOperations = new ArrayList<>();
- for (Operation operation : mPendingOperations) {
+ for (BiometricSchedulerOperation operation : mPendingOperations) {
pendingOperations.add(operation.toString());
}
@@ -735,7 +490,7 @@
pw.println("Type: " + mSensorType);
pw.println("Current operation: " + mCurrentOperation);
pw.println("Pending operations: " + mPendingOperations.size());
- for (Operation operation : mPendingOperations) {
+ for (BiometricSchedulerOperation operation : mPendingOperations) {
pw.println("Pending operation: " + operation);
}
for (CrashState crashState : mCrashStates) {
@@ -746,7 +501,7 @@
public byte[] dumpProtoState(boolean clearSchedulerBuffer) {
final ProtoOutputStream proto = new ProtoOutputStream();
proto.write(BiometricSchedulerProto.CURRENT_OPERATION, mCurrentOperation != null
- ? mCurrentOperation.mClientMonitor.getProtoEnum() : BiometricsProto.CM_NONE);
+ ? mCurrentOperation.getProtoEnum() : BiometricsProto.CM_NONE);
proto.write(BiometricSchedulerProto.TOTAL_OPERATIONS, mTotalOperationsHandled);
if (!mRecentOperations.isEmpty()) {
@@ -771,6 +526,7 @@
* HAL dies.
*/
public void reset() {
+ Slog.d(getTag(), "Resetting scheduler");
mPendingOperations.clear();
mCurrentOperation = null;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
new file mode 100644
index 0000000..e8b50d9
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
@@ -0,0 +1,421 @@
+/*
+ * 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.biometrics.sensors;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricConstants;
+import android.os.Handler;
+import android.os.IBinder;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Contains all the necessary information for a HAL operation.
+ */
+public class BiometricSchedulerOperation {
+ protected static final String TAG = "BiometricSchedulerOperation";
+
+ /**
+ * The operation is added to the list of pending operations and waiting for its turn.
+ */
+ protected static final int STATE_WAITING_IN_QUEUE = 0;
+
+ /**
+ * The operation is added to the list of pending operations, but a subsequent operation
+ * has been added. This state only applies to {@link Interruptable} operations. When this
+ * operation reaches the head of the queue, it will send ERROR_CANCELED and finish.
+ */
+ protected static final int STATE_WAITING_IN_QUEUE_CANCELING = 1;
+
+ /**
+ * The operation has reached the front of the queue and has started.
+ */
+ protected static final int STATE_STARTED = 2;
+
+ /**
+ * The operation was started, but is now canceling. Operations should wait for the HAL to
+ * acknowledge that the operation was canceled, at which point it finishes.
+ */
+ protected static final int STATE_STARTED_CANCELING = 3;
+
+ /**
+ * The operation has reached the head of the queue but is waiting for BiometricService
+ * to acknowledge and start the operation.
+ */
+ protected static final int STATE_WAITING_FOR_COOKIE = 4;
+
+ /**
+ * The {@link BaseClientMonitor.Callback} has been invoked and the client is finished.
+ */
+ protected static final int STATE_FINISHED = 5;
+
+ @IntDef({STATE_WAITING_IN_QUEUE,
+ STATE_WAITING_IN_QUEUE_CANCELING,
+ STATE_STARTED,
+ STATE_STARTED_CANCELING,
+ STATE_WAITING_FOR_COOKIE,
+ STATE_FINISHED})
+ @Retention(RetentionPolicy.SOURCE)
+ protected @interface OperationState {}
+
+ private static final int CANCEL_WATCHDOG_DELAY_MS = 3000;
+
+ @NonNull
+ private final BaseClientMonitor mClientMonitor;
+ @Nullable
+ private final BaseClientMonitor.Callback mClientCallback;
+ @OperationState
+ private int mState;
+ @VisibleForTesting
+ @NonNull
+ final Runnable mCancelWatchdog;
+
+ BiometricSchedulerOperation(
+ @NonNull BaseClientMonitor clientMonitor,
+ @Nullable BaseClientMonitor.Callback callback
+ ) {
+ this(clientMonitor, callback, STATE_WAITING_IN_QUEUE);
+ }
+
+ protected BiometricSchedulerOperation(
+ @NonNull BaseClientMonitor clientMonitor,
+ @Nullable BaseClientMonitor.Callback callback,
+ @OperationState int state
+ ) {
+ mClientMonitor = clientMonitor;
+ mClientCallback = callback;
+ mState = state;
+ mCancelWatchdog = () -> {
+ if (!isFinished()) {
+ Slog.e(TAG, "[Watchdog Triggered]: " + this);
+ getWrappedCallback().onClientFinished(mClientMonitor, false /* success */);
+ }
+ };
+ }
+
+ /**
+ * Zero if this operation is ready to start or has already started. A non-zero cookie
+ * is returned if the operation has not started and is waiting on
+ * {@link android.hardware.biometrics.IBiometricService#onReadyForAuthentication(int)}.
+ *
+ * @return cookie or 0 if ready/started
+ */
+ public int isReadyToStart() {
+ if (mState == STATE_WAITING_FOR_COOKIE || mState == STATE_WAITING_IN_QUEUE) {
+ final int cookie = mClientMonitor.getCookie();
+ if (cookie != 0) {
+ mState = STATE_WAITING_FOR_COOKIE;
+ }
+ return cookie;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Start this operation without waiting for a cookie
+ * (i.e. {@link #isReadyToStart() returns zero}
+ *
+ * @param callback lifecycle callback
+ * @return if this operation started
+ */
+ public boolean start(@NonNull BaseClientMonitor.Callback callback) {
+ checkInState("start",
+ STATE_WAITING_IN_QUEUE,
+ STATE_WAITING_FOR_COOKIE,
+ STATE_WAITING_IN_QUEUE_CANCELING);
+
+ if (mClientMonitor.getCookie() != 0) {
+ throw new IllegalStateException("operation requires cookie");
+ }
+
+ return doStart(callback);
+ }
+
+ /**
+ * Start this operation after receiving the given cookie.
+ *
+ * @param callback lifecycle callback
+ * @param cookie cookie indicting the operation should begin
+ * @return if this operation started
+ */
+ public boolean startWithCookie(@NonNull BaseClientMonitor.Callback callback, int cookie) {
+ checkInState("start",
+ STATE_WAITING_IN_QUEUE,
+ STATE_WAITING_FOR_COOKIE,
+ STATE_WAITING_IN_QUEUE_CANCELING);
+
+ if (mClientMonitor.getCookie() != cookie) {
+ Slog.e(TAG, "Mismatched cookie for operation: " + this + ", received: " + cookie);
+ return false;
+ }
+
+ return doStart(callback);
+ }
+
+ private boolean doStart(@NonNull BaseClientMonitor.Callback callback) {
+ final BaseClientMonitor.Callback cb = getWrappedCallback(callback);
+
+ if (mState == STATE_WAITING_IN_QUEUE_CANCELING) {
+ Slog.d(TAG, "Operation marked for cancellation, cancelling now: " + this);
+
+ cb.onClientFinished(mClientMonitor, true /* success */);
+ if (mClientMonitor instanceof ErrorConsumer) {
+ final ErrorConsumer errorConsumer = (ErrorConsumer) mClientMonitor;
+ errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED,
+ 0 /* vendorCode */);
+ } else {
+ Slog.w(TAG, "monitor cancelled but does not implement ErrorConsumer");
+ }
+
+ return false;
+ }
+
+ if (isUnstartableHalOperation()) {
+ Slog.v(TAG, "unable to start: " + this);
+ ((HalClientMonitor<?>) mClientMonitor).unableToStart();
+ cb.onClientFinished(mClientMonitor, false /* success */);
+ return false;
+ }
+
+ mState = STATE_STARTED;
+ mClientMonitor.start(cb);
+
+ Slog.v(TAG, "started: " + this);
+ return true;
+ }
+
+ /**
+ * Abort a pending operation.
+ *
+ * This is similar to cancel but the operation must not have been started. It will
+ * immediately abort the operation and notify the client that it has finished unsuccessfully.
+ */
+ public void abort() {
+ checkInState("cannot abort a non-pending operation",
+ STATE_WAITING_IN_QUEUE,
+ STATE_WAITING_FOR_COOKIE,
+ STATE_WAITING_IN_QUEUE_CANCELING);
+
+ if (isHalOperation()) {
+ ((HalClientMonitor<?>) mClientMonitor).unableToStart();
+ }
+ getWrappedCallback().onClientFinished(mClientMonitor, false /* success */);
+
+ Slog.v(TAG, "Aborted: " + this);
+ }
+
+ /** Flags this operation as canceled, if possible, but does not cancel it until started. */
+ public boolean markCanceling() {
+ if (mState == STATE_WAITING_IN_QUEUE && isInterruptable()) {
+ mState = STATE_WAITING_IN_QUEUE_CANCELING;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Cancel the operation now.
+ *
+ * @param handler handler to use for the cancellation watchdog
+ * @param callback lifecycle callback (only used if this operation hasn't started, otherwise
+ * the callback used from {@link #start(BaseClientMonitor.Callback)} is used)
+ */
+ public void cancel(@NonNull Handler handler, @NonNull BaseClientMonitor.Callback callback) {
+ checkNotInState("cancel", STATE_FINISHED);
+
+ final int currentState = mState;
+ if (!isInterruptable()) {
+ Slog.w(TAG, "Cannot cancel - operation not interruptable: " + this);
+ return;
+ }
+ if (currentState == STATE_STARTED_CANCELING) {
+ Slog.w(TAG, "Cannot cancel - already invoked for operation: " + this);
+ return;
+ }
+
+ mState = STATE_STARTED_CANCELING;
+ if (currentState == STATE_WAITING_IN_QUEUE
+ || currentState == STATE_WAITING_IN_QUEUE_CANCELING
+ || currentState == STATE_WAITING_FOR_COOKIE) {
+ Slog.d(TAG, "[Cancelling] Current client (without start): " + mClientMonitor);
+ ((Interruptable) mClientMonitor).cancelWithoutStarting(getWrappedCallback(callback));
+ } else {
+ Slog.d(TAG, "[Cancelling] Current client: " + mClientMonitor);
+ ((Interruptable) mClientMonitor).cancel();
+ }
+
+ // forcibly finish this client if the HAL does not acknowledge within the timeout
+ handler.postDelayed(mCancelWatchdog, CANCEL_WATCHDOG_DELAY_MS);
+ }
+
+ @NonNull
+ private BaseClientMonitor.Callback getWrappedCallback() {
+ return getWrappedCallback(null);
+ }
+
+ @NonNull
+ private BaseClientMonitor.Callback getWrappedCallback(
+ @Nullable BaseClientMonitor.Callback callback) {
+ final BaseClientMonitor.Callback destroyCallback = new BaseClientMonitor.Callback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ Slog.d(TAG, "[Finished / destroy]: " + clientMonitor);
+ mClientMonitor.destroy();
+ mState = STATE_FINISHED;
+ }
+ };
+ return new BaseClientMonitor.CompositeCallback(destroyCallback, callback, mClientCallback);
+ }
+
+ /** {@link BaseClientMonitor#getSensorId()}. */
+ public int getSensorId() {
+ return mClientMonitor.getSensorId();
+ }
+
+ /** {@link BaseClientMonitor#getProtoEnum()}. */
+ public int getProtoEnum() {
+ return mClientMonitor.getProtoEnum();
+ }
+
+ /** {@link BaseClientMonitor#getTargetUserId()}. */
+ public int getTargetUserId() {
+ return mClientMonitor.getTargetUserId();
+ }
+
+ /** If the given clientMonitor is the same as the one in the constructor. */
+ public boolean isFor(@NonNull BaseClientMonitor clientMonitor) {
+ return mClientMonitor == clientMonitor;
+ }
+
+ /** If this operation is {@link Interruptable}. */
+ public boolean isInterruptable() {
+ return mClientMonitor instanceof Interruptable;
+ }
+
+ private boolean isHalOperation() {
+ return mClientMonitor instanceof HalClientMonitor<?>;
+ }
+
+ private boolean isUnstartableHalOperation() {
+ if (isHalOperation()) {
+ final HalClientMonitor<?> client = (HalClientMonitor<?>) mClientMonitor;
+ if (client.getFreshDaemon() == null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** If this operation is an enrollment. */
+ public boolean isEnrollOperation() {
+ return mClientMonitor instanceof EnrollClient;
+ }
+
+ /** If this operation is authentication. */
+ public boolean isAuthenticateOperation() {
+ return mClientMonitor instanceof AuthenticationClient;
+ }
+
+ /** If this operation is authentication or detection. */
+ public boolean isAuthenticationOrDetectionOperation() {
+ final boolean isAuthentication = mClientMonitor instanceof AuthenticationConsumer;
+ final boolean isDetection = mClientMonitor instanceof DetectionConsumer;
+ return isAuthentication || isDetection;
+ }
+
+ /** If this operation performs acquisition {@link AcquisitionClient}. */
+ public boolean isAcquisitionOperation() {
+ return mClientMonitor instanceof AcquisitionClient;
+ }
+
+ /**
+ * If this operation matches the original requestId.
+ *
+ * By default, monitors are not associated with a request id to retain the original
+ * behavior (i.e. if no requestId is explicitly set then assume it matches)
+ *
+ * @param requestId a unique id {@link BaseClientMonitor#setRequestId(long)}.
+ */
+ public boolean isMatchingRequestId(long requestId) {
+ return !mClientMonitor.hasRequestId()
+ || mClientMonitor.getRequestId() == requestId;
+ }
+
+ /** If the token matches */
+ public boolean isMatchingToken(@Nullable IBinder token) {
+ return mClientMonitor.getToken() == token;
+ }
+
+ /** If this operation has started. */
+ public boolean isStarted() {
+ return mState == STATE_STARTED;
+ }
+
+ /** If this operation is cancelling but has not yet completed. */
+ public boolean isCanceling() {
+ return mState == STATE_STARTED_CANCELING;
+ }
+
+ /** If this operation has finished and completed its lifecycle. */
+ public boolean isFinished() {
+ return mState == STATE_FINISHED;
+ }
+
+ /** If {@link #markCanceling()} was called but the operation hasn't been canceled. */
+ public boolean isMarkedCanceling() {
+ return mState == STATE_WAITING_IN_QUEUE_CANCELING;
+ }
+
+ /**
+ * The monitor passed to the constructor.
+ * @deprecated avoid using and move to encapsulate within the operation
+ */
+ @Deprecated
+ public BaseClientMonitor getClientMonitor() {
+ return mClientMonitor;
+ }
+
+ private void checkNotInState(String message, @OperationState int... states) {
+ for (int state : states) {
+ if (mState == state) {
+ throw new IllegalStateException(message + ": illegal state= " + state);
+ }
+ }
+ }
+
+ private void checkInState(String message, @OperationState int... states) {
+ for (int state : states) {
+ if (mState == state) {
+ return;
+ }
+ }
+ throw new IllegalStateException(message + ": illegal state= " + mState);
+ }
+
+ @Override
+ public String toString() {
+ return mClientMonitor + ", State: " + mState;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
index fab98b6..d5093c75 100644
--- a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
+++ b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
@@ -32,6 +32,11 @@
* {@link BaseClientMonitor#start(BaseClientMonitor.Callback)} was invoked. This usually happens
* if the client is still waiting in the pending queue and got notified that a subsequent
* operation is preempting it.
+ *
+ * This method must invoke
+ * {@link BaseClientMonitor.Callback#onClientFinished(BaseClientMonitor, boolean)} on the
+ * given callback (with success).
+ *
* @param callback invoked when the operation is completed.
*/
void cancelWithoutStarting(@NonNull BaseClientMonitor.Callback callback);
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
index b056bf8..603cc22 100644
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -16,10 +16,14 @@
package com.android.server.biometrics.sensors;
+import static com.android.server.biometrics.sensors.BiometricSchedulerOperation.STATE_STARTED;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.IBiometricService;
+import android.os.Handler;
+import android.os.Looper;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Slog;
@@ -68,9 +72,8 @@
return;
}
- Slog.d(getTag(), "[Client finished] "
- + clientMonitor + ", success: " + success);
- if (mCurrentOperation != null && mCurrentOperation.mClientMonitor == mOwner) {
+ Slog.d(getTag(), "[Client finished] " + clientMonitor + ", success: " + success);
+ if (mCurrentOperation != null && mCurrentOperation.isFor(mOwner)) {
mCurrentOperation = null;
startNextOperationIfIdle();
} else {
@@ -83,26 +86,30 @@
}
@VisibleForTesting
- UserAwareBiometricScheduler(@NonNull String tag, @SensorType int sensorType,
+ public UserAwareBiometricScheduler(@NonNull String tag,
+ @NonNull Handler handler,
+ @SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull IBiometricService biometricService,
@NonNull CurrentUserRetriever currentUserRetriever,
@NonNull UserSwitchCallback userSwitchCallback,
@NonNull CoexCoordinator coexCoordinator) {
- super(tag, sensorType, gestureAvailabilityDispatcher, biometricService,
+ super(tag, handler, sensorType, gestureAvailabilityDispatcher, biometricService,
LOG_NUM_RECENT_OPERATIONS, coexCoordinator);
mCurrentUserRetriever = currentUserRetriever;
mUserSwitchCallback = userSwitchCallback;
}
- public UserAwareBiometricScheduler(@NonNull String tag, @SensorType int sensorType,
+ public UserAwareBiometricScheduler(@NonNull String tag,
+ @SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull CurrentUserRetriever currentUserRetriever,
@NonNull UserSwitchCallback userSwitchCallback) {
- this(tag, sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
- ServiceManager.getService(Context.BIOMETRIC_SERVICE)), currentUserRetriever,
- userSwitchCallback, CoexCoordinator.getInstance());
+ this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher,
+ IBiometricService.Stub.asInterface(
+ ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
+ currentUserRetriever, userSwitchCallback, CoexCoordinator.getInstance());
}
@Override
@@ -122,7 +129,7 @@
}
final int currentUserId = mCurrentUserRetriever.getCurrentUserId();
- final int nextUserId = mPendingOperations.getFirst().mClientMonitor.getTargetUserId();
+ final int nextUserId = mPendingOperations.getFirst().getTargetUserId();
if (nextUserId == currentUserId) {
super.startNextOperationIfIdle();
@@ -133,8 +140,8 @@
new ClientFinishedCallback(startClient);
Slog.d(getTag(), "[Starting User] " + startClient);
- mCurrentOperation = new Operation(
- startClient, finishedCallback, Operation.STATE_STARTED);
+ mCurrentOperation = new BiometricSchedulerOperation(
+ startClient, finishedCallback, STATE_STARTED);
startClient.start(finishedCallback);
} else {
if (mStopUserClient != null) {
@@ -147,8 +154,8 @@
Slog.d(getTag(), "[Stopping User] current: " + currentUserId
+ ", next: " + nextUserId + ". " + mStopUserClient);
- mCurrentOperation = new Operation(
- mStopUserClient, finishedCallback, Operation.STATE_STARTED);
+ mCurrentOperation = new BiometricSchedulerOperation(
+ mStopUserClient, finishedCallback, STATE_STARTED);
mStopUserClient.start(finishedCallback);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 675ee545..039b08e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -213,7 +213,7 @@
}
@Override // Binder call
- public void enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
+ public long enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
final IFaceServiceReceiver receiver, final String opPackageName,
final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) {
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
@@ -221,23 +221,24 @@
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for enroll");
- return;
+ return -1;
}
- provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
+ return provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
receiver, opPackageName, disabledFeatures, previewSurface, debugConsent);
}
@Override // Binder call
- public void enrollRemotely(int userId, final IBinder token, final byte[] hardwareAuthToken,
+ public long enrollRemotely(int userId, final IBinder token, final byte[] hardwareAuthToken,
final IFaceServiceReceiver receiver, final String opPackageName,
final int[] disabledFeatures) {
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
// TODO(b/145027036): Implement this.
+ return -1;
}
@Override // Binder call
- public void cancelEnrollment(final IBinder token) {
+ public void cancelEnrollment(final IBinder token, long requestId) {
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
@@ -246,7 +247,7 @@
return;
}
- provider.second.cancelEnrollment(provider.first, token);
+ provider.second.cancelEnrollment(provider.first, token, requestId);
}
@Override // Binder call
@@ -624,7 +625,7 @@
private void addHidlProviders(@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
mServiceProviders.add(
- new Face10(getContext(), hidlSensor, mLockoutResetDispatcher));
+ Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher));
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index e099ba3..77e431c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -94,12 +94,12 @@
void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull String opPackageName, long challenge);
- void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
+ long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName,
@NonNull int[] disabledFeatures, @Nullable Surface previewSurface,
boolean debugConsent);
- void cancelEnrollment(int sensorId, @NonNull IBinder token);
+ void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId);
long scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId,
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index a806277..aae4fbe 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -82,13 +82,14 @@
FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
- @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName,
+ @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId,
@NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
@Nullable Surface previewSurface, int sensorId, int maxTemplatesPerUser,
boolean debugConsent) {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils,
timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
false /* shouldVibrate */);
+ setRequestId(requestId);
mEnrollIgnoreList = getContext().getResources()
.getIntArray(R.array.config_face_acquire_enroll_ignorelist);
mEnrollIgnoreListVendor = getContext().getResources()
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 4bae775..ae507ab 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -327,17 +327,18 @@
}
@Override
- public void scheduleEnroll(int sensorId, @NonNull IBinder token,
+ public long scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
@NonNull String opPackageName, @NonNull int[] disabledFeatures,
@Nullable Surface previewSurface, boolean debugConsent) {
+ final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
final int maxTemplatesPerUser = mSensors.get(
sensorId).getSensorProperties().maxEnrollmentsPerUser;
final FaceEnrollClient client = new FaceEnrollClient(mContext,
mSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
- opPackageName, FaceUtils.getInstance(sensorId), disabledFeatures,
+ opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser,
debugConsent);
scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
@@ -351,11 +352,13 @@
}
});
});
+ return id;
}
@Override
- public void cancelEnrollment(int sensorId, @NonNull IBinder token) {
- mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelEnrollment(token));
+ public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
+ mHandler.post(() ->
+ mSensors.get(sensorId).getScheduler().cancelEnrollment(token, requestId));
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
index 525e508..15d6a89 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
@@ -17,6 +17,7 @@
package com.android.server.biometrics.sensors.face.aidl;
import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.face.EnrollmentStageConfig;
import android.hardware.biometrics.face.Error;
import android.hardware.biometrics.face.IFace;
@@ -188,6 +189,24 @@
Slog.w(TAG, "close");
cb.onSessionClosed();
}
+
+ @Override
+ public ICancellationSignal authenticateWithContext(
+ long operationId, OperationContext context) {
+ return authenticate(operationId);
+ }
+
+ @Override
+ public ICancellationSignal enrollWithContext(
+ HardwareAuthToken hat, byte enrollmentType, byte[] features,
+ NativeHandle previewSurface, OperationContext context) {
+ return enroll(hat, enrollmentType, features, previewSurface);
+ }
+
+ @Override
+ public ICancellationSignal detectInteractionWithContext(OperationContext context) {
+ return detectInteraction();
+ }
};
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index f4dcbbb..e957794 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -333,12 +333,13 @@
Face10(@NonNull Context context,
@NonNull FaceSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull Handler handler,
@NonNull BiometricScheduler scheduler) {
mSensorProperties = sensorProps;
mContext = context;
mSensorId = sensorProps.sensorId;
mScheduler = scheduler;
- mHandler = new Handler(Looper.getMainLooper());
+ mHandler = handler;
mUsageStats = new UsageStats(context);
mAuthenticatorIds = new HashMap<>();
mLazyDaemon = Face10.this::getDaemon;
@@ -357,9 +358,11 @@
}
}
- public Face10(@NonNull Context context, @NonNull FaceSensorPropertiesInternal sensorProps,
+ public static Face10 newInstance(@NonNull Context context,
+ @NonNull FaceSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
- this(context, sensorProps, lockoutResetDispatcher,
+ final Handler handler = new Handler(Looper.getMainLooper());
+ return new Face10(context, sensorProps, lockoutResetDispatcher, handler,
new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
null /* gestureAvailabilityTracker */));
}
@@ -573,10 +576,11 @@
}
@Override
- public void scheduleEnroll(int sensorId, @NonNull IBinder token,
+ public long scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
@NonNull String opPackageName, @NonNull int[] disabledFeatures,
@Nullable Surface previewSurface, boolean debugConsent) {
+ final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
@@ -584,7 +588,7 @@
final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
- opPackageName, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
+ opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
ENROLL_TIMEOUT_SEC, previewSurface, mSensorId);
mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
@@ -598,13 +602,12 @@
}
});
});
+ return id;
}
@Override
- public void cancelEnrollment(int sensorId, @NonNull IBinder token) {
- mHandler.post(() -> {
- mScheduler.cancelEnrollment(token);
- });
+ public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
+ mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId));
}
@Override
@@ -893,6 +896,8 @@
boolean success) {
if (success) {
mCurrentUserId = targetUserId;
+ } else {
+ Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId);
}
}
});
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
index 80828cced..31e5c86 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
@@ -53,12 +53,13 @@
FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
- @NonNull byte[] hardwareAuthToken, @NonNull String owner,
+ @NonNull byte[] hardwareAuthToken, @NonNull String owner, long requestId,
@NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
@Nullable Surface previewSurface, int sensorId) {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
false /* shouldVibrate */);
+ setRequestId(requestId);
mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
mEnrollIgnoreList = getContext().getResources()
.getIntArray(R.array.config_face_acquire_enroll_ignorelist);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 3e70ee5..6366e19 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -249,7 +249,7 @@
}
@Override // Binder call
- public void enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken,
+ public long enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken,
final int userId, final IFingerprintServiceReceiver receiver,
final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
@@ -257,15 +257,15 @@
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for enroll");
- return;
+ return -1;
}
- provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
+ return provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
receiver, opPackageName, enrollReason);
}
@Override // Binder call
- public void cancelEnrollment(final IBinder token) {
+ public void cancelEnrollment(final IBinder token, long requestId) {
Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
@@ -274,7 +274,7 @@
return;
}
- provider.second.cancelEnrollment(provider.first, token);
+ provider.second.cancelEnrollment(provider.first, token, requestId);
}
@SuppressWarnings("deprecation")
@@ -818,7 +818,7 @@
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
} else {
fingerprint21 = Fingerprint21.newInstance(getContext(),
- mFingerprintStateCallback, hidlSensor,
+ mFingerprintStateCallback, hidlSensor, mHandler,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
}
mServiceProviders.add(fingerprint21);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 1772f81..535705c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -88,11 +88,11 @@
/**
* Schedules fingerprint enrollment.
*/
- void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
+ long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
int userId, @NonNull IFingerprintServiceReceiver receiver,
@NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason);
- void cancelEnrollment(int sensorId, @NonNull IBinder token);
+ void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId);
long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index ccb34aa..67507cc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -57,7 +57,7 @@
private boolean mIsPointerDown;
FingerprintEnrollClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
+ @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String owner,
@NonNull BiometricUtils<Fingerprint> utils, int sensorId,
@@ -69,6 +69,7 @@
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
!sensorProps.isAnyUdfpsType() /* shouldVibrate */);
+ setRequestId(requestId);
mSensorProps = sensorProps;
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mMaxTemplatesPerUser = maxTemplatesPerUser;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 734b173..eb16c76 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -347,15 +347,16 @@
}
@Override
- public void scheduleEnroll(int sensorId, @NonNull IBinder token,
+ public long scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId,
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
@FingerprintManager.EnrollReason int enrollReason) {
+ final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
.maxEnrollmentsPerUser;
final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
+ mSensors.get(sensorId).getLazySession(), token, id,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
mSensors.get(sensorId).getSensorProperties(),
@@ -378,11 +379,13 @@
}
});
});
+ return id;
}
@Override
- public void cancelEnrollment(int sensorId, @NonNull IBinder token) {
- mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelEnrollment(token));
+ public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
+ mHandler.post(() ->
+ mSensors.get(sensorId).getScheduler().cancelEnrollment(token, requestId));
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
index e771923..1eb153c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
@@ -17,10 +17,12 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.fingerprint.Error;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.ISessionCallback;
+import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.keymaster.HardwareAuthToken;
import android.os.RemoteException;
@@ -182,6 +184,34 @@
public void onUiReady() {
Slog.w(TAG, "onUiReady");
}
+
+ @Override
+ public ICancellationSignal authenticateWithContext(
+ long operationId, OperationContext context) {
+ return authenticate(operationId);
+ }
+
+ @Override
+ public ICancellationSignal enrollWithContext(
+ HardwareAuthToken hat, OperationContext context) {
+ return enroll(hat);
+ }
+
+ @Override
+ public ICancellationSignal detectInteractionWithContext(OperationContext context) {
+ return detectInteraction();
+ }
+
+ @Override
+ public void onPointerDownWithContext(PointerContext context) {
+ onPointerDown(
+ context.pointerId, context.x, context.y, context.minor, context.major);
+ }
+
+ @Override
+ public void onPointerUpWithContext(PointerContext context) {
+ onPointerUp(context.pointerId);
+ }
};
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 5f2f4cf..6feb5fa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -42,7 +42,6 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.IHwBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -320,7 +319,8 @@
Fingerprint21(@NonNull Context context,
@NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
- @NonNull BiometricScheduler scheduler, @NonNull Handler handler,
+ @NonNull BiometricScheduler scheduler,
+ @NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull HalResultController controller) {
mContext = context;
@@ -356,16 +356,15 @@
public static Fingerprint21 newInstance(@NonNull Context context,
@NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
+ @NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
- final Handler handler = new Handler(Looper.getMainLooper());
final BiometricScheduler scheduler =
new BiometricScheduler(TAG,
BiometricScheduler.sensorTypeFromFingerprintProperties(sensorProps),
gestureAvailabilityDispatcher);
final HalResultController controller = new HalResultController(sensorProps.sensorId,
- context, handler,
- scheduler);
+ context, handler, scheduler);
return new Fingerprint21(context, fingerprintStateCallback, sensorProps, scheduler, handler,
lockoutResetDispatcher, controller);
}
@@ -491,19 +490,25 @@
!getEnrolledFingerprints(mSensorProperties.sensorId, targetUserId).isEmpty();
final FingerprintUpdateActiveUserClient client =
new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
- mContext.getOpPackageName(), mSensorProperties.sensorId, mCurrentUserId,
- hasEnrolled, mAuthenticatorIds, force);
+ mContext.getOpPackageName(), mSensorProperties.sensorId,
+ this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force);
mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
if (success) {
mCurrentUserId = targetUserId;
+ } else {
+ Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId);
}
}
});
}
+ private int getCurrentUser() {
+ return mCurrentUserId;
+ }
+
@Override
public boolean containsSensor(int sensorId) {
return mSensorProperties.sensorId == sensorId;
@@ -558,18 +563,20 @@
}
@Override
- public void scheduleEnroll(int sensorId, @NonNull IBinder token,
+ public long scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId,
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
@FingerprintManager.EnrollReason int enrollReason) {
+ final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
- hardwareAuthToken, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
- ENROLL_TIMEOUT_SEC, mSensorProperties.sensorId, mUdfpsOverlayController,
- mSidefpsController, enrollReason);
+ mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver),
+ userId, hardwareAuthToken, opPackageName,
+ FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
+ mSensorProperties.sensorId, mUdfpsOverlayController, mSidefpsController,
+ enrollReason);
mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
@@ -588,13 +595,12 @@
}
});
});
+ return id;
}
@Override
- public void cancelEnrollment(int sensorId, @NonNull IBinder token) {
- mHandler.post(() -> {
- mScheduler.cancelEnrollment(token);
- });
+ public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
+ mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId));
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index dd68b4d..273f8a5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -26,7 +26,6 @@
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.hardware.fingerprint.FingerprintStateListener;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Handler;
import android.os.IBinder;
@@ -135,43 +134,16 @@
@NonNull private final RestartAuthRunnable mRestartAuthRunnable;
private static class TestableBiometricScheduler extends BiometricScheduler {
- @NonNull private final TestableInternalCallback mInternalCallback;
@NonNull private Fingerprint21UdfpsMock mFingerprint21;
- TestableBiometricScheduler(@NonNull String tag,
+ TestableBiometricScheduler(@NonNull String tag, @NonNull Handler handler,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
- super(tag, BiometricScheduler.SENSOR_TYPE_FP_OTHER,
- gestureAvailabilityDispatcher);
- mInternalCallback = new TestableInternalCallback();
- }
-
- class TestableInternalCallback extends InternalCallback {
- @Override
- public void onClientStarted(BaseClientMonitor clientMonitor) {
- super.onClientStarted(clientMonitor);
- Slog.d(TAG, "Client started: " + clientMonitor);
- mFingerprint21.setDebugMessage("Started: " + clientMonitor);
- }
-
- @Override
- public void onClientFinished(BaseClientMonitor clientMonitor, boolean success) {
- super.onClientFinished(clientMonitor, success);
- Slog.d(TAG, "Client finished: " + clientMonitor);
- mFingerprint21.setDebugMessage("Finished: " + clientMonitor);
- }
+ super(tag, BiometricScheduler.SENSOR_TYPE_FP_OTHER, gestureAvailabilityDispatcher);
}
void init(@NonNull Fingerprint21UdfpsMock fingerprint21) {
mFingerprint21 = fingerprint21;
}
-
- /**
- * Expose the internal finish callback so it can be used for testing
- */
- @Override
- @NonNull protected InternalCallback getInternalCallback() {
- return mInternalCallback;
- }
}
/**
@@ -280,7 +252,7 @@
final Handler handler = new Handler(Looper.getMainLooper());
final TestableBiometricScheduler scheduler =
- new TestableBiometricScheduler(TAG, gestureAvailabilityDispatcher);
+ new TestableBiometricScheduler(TAG, handler, gestureAvailabilityDispatcher);
final MockHalResultController controller =
new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index 1ebf44c..cc50bdf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -55,7 +55,7 @@
FingerprintEnrollClient(@NonNull Context context,
@NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
- @NonNull ClientMonitorCallbackConverter listener, int userId,
+ long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String owner,
@NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@@ -64,6 +64,7 @@
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
true /* shouldVibrate */);
+ setRequestId(requestId);
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mEnrollReason = enrollReason;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
index fd38bdd..a2c1892 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
@@ -31,6 +31,7 @@
import java.io.File;
import java.util.Map;
+import java.util.function.Supplier;
/**
* Sets the HAL's current active user, and updates the framework's authenticatorId cache.
@@ -40,7 +41,7 @@
private static final String TAG = "FingerprintUpdateActiveUserClient";
private static final String FP_DATA_DIR = "fpdata";
- private final int mCurrentUserId;
+ private final Supplier<Integer> mCurrentUserId;
private final boolean mForceUpdateAuthenticatorId;
private final boolean mHasEnrolledBiometrics;
private final Map<Integer, Long> mAuthenticatorIds;
@@ -48,8 +49,9 @@
FingerprintUpdateActiveUserClient(@NonNull Context context,
@NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, int userId,
- @NonNull String owner, int sensorId, int currentUserId, boolean hasEnrolledBiometrics,
- @NonNull Map<Integer, Long> authenticatorIds, boolean forceUpdateAuthenticatorId) {
+ @NonNull String owner, int sensorId, Supplier<Integer> currentUserId,
+ boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
+ boolean forceUpdateAuthenticatorId) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
@@ -63,7 +65,7 @@
public void start(@NonNull Callback callback) {
super.start(callback);
- if (mCurrentUserId == getTargetUserId() && !mForceUpdateAuthenticatorId) {
+ if (mCurrentUserId.get() == getTargetUserId() && !mForceUpdateAuthenticatorId) {
Slog.d(TAG, "Already user: " + mCurrentUserId + ", returning");
callback.onClientFinished(this, true /* success */);
return;
@@ -109,8 +111,10 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().setActiveGroup(getTargetUserId(), mDirectory.getAbsolutePath());
- mAuthenticatorIds.put(getTargetUserId(), mHasEnrolledBiometrics
+ final int targetId = getTargetUserId();
+ Slog.d(TAG, "Setting active user: " + targetId);
+ getFreshDaemon().setActiveGroup(targetId, mDirectory.getAbsolutePath());
+ mAuthenticatorIds.put(targetId, mHasEnrolledBiometrics
? getFreshDaemon().getAuthenticatorId() : 0L);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index e120343..582dd7c 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -614,9 +614,6 @@
mEmulatorClipboardMonitor.accept(clip);
final int userId = UserHandle.getUserId(uid);
- if (clip != null) {
- startClassificationLocked(clip, userId);
- }
// Update this user
setPrimaryClipInternalLocked(getClipboardLocked(userId), clip, uid, sourcePackage);
@@ -672,6 +669,17 @@
@GuardedBy("mLock")
private void setPrimaryClipInternalLocked(PerUserClipboard clipboard, @Nullable ClipData clip,
int uid, @Nullable String sourcePackage) {
+ final int userId = UserHandle.getUserId(uid);
+ if (clip != null) {
+ startClassificationLocked(clip, userId);
+ }
+
+ setPrimaryClipInternalNoClassifyLocked(clipboard, clip, uid, sourcePackage);
+ }
+
+ @GuardedBy("mLock")
+ private void setPrimaryClipInternalNoClassifyLocked(PerUserClipboard clipboard,
+ @Nullable ClipData clip, int uid, @Nullable String sourcePackage) {
revokeUris(clipboard);
clipboard.activePermissionOwners.clear();
if (clip == null && clipboard.primaryClip == null) {
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index 39fa3f2..135276e 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -50,6 +50,12 @@
public abstract void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId);
/**
+ * Returns the flags that should be added to any virtual displays created on this virtual
+ * device.
+ */
+ public abstract int getBaseVirtualDisplayFlags(IVirtualDevice virtualDevice);
+
+ /**
* Returns true if the given {@code uid} is the owner of any virtual devices that are
* currently active.
*/
diff --git a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
index 880dbf6..fe002ce 100644
--- a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
+++ b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
@@ -25,6 +25,7 @@
import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_OWNED_CHANGE_IDS;
import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_REMOVE_OVERRIDES;
+import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import android.annotation.NonNull;
@@ -157,17 +158,17 @@
Map<String, CompatibilityOverridesToRemoveConfig> packageNameToOverridesToRemove =
new ArrayMap<>();
for (String packageName : packageNames) {
- Long versionCode = getVersionCodeOrNull(packageName);
- if (versionCode == null) {
- // Package isn't installed yet.
- continue;
- }
-
Set<Long> changeIdsToSkip = packageToChangeIdsToSkip.getOrDefault(packageName,
emptySet());
- Map<Long, PackageOverride> overridesToAdd = mOverridesParser.parsePackageOverrides(
- properties.getString(packageName, /* defaultValue= */ ""), packageName,
- versionCode, changeIdsToSkip);
+
+ Map<Long, PackageOverride> overridesToAdd = emptyMap();
+ Long versionCode = getVersionCodeOrNull(packageName);
+ if (versionCode != null) {
+ // Only if package installed add overrides, otherwise just remove.
+ overridesToAdd = mOverridesParser.parsePackageOverrides(
+ properties.getString(packageName, /* defaultValue= */ ""), packageName,
+ versionCode, changeIdsToSkip);
+ }
if (!overridesToAdd.isEmpty()) {
packageNameToOverridesToAdd.put(packageName,
new CompatibilityOverrideConfig(overridesToAdd));
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java
index 7fe24ff..78d55b9 100644
--- a/services/core/java/com/android/server/devicestate/DeviceState.java
+++ b/services/core/java/com/android/server/devicestate/DeviceState.java
@@ -43,14 +43,14 @@
*/
public final class DeviceState {
/**
- * Flag that indicates sticky requests should be cancelled when this device state becomes the
+ * Flag that indicates override requests should be cancelled when this device state becomes the
* base device state.
*/
- public static final int FLAG_CANCEL_STICKY_REQUESTS = 1 << 0;
+ public static final int FLAG_CANCEL_OVERRIDE_REQUESTS = 1 << 0;
/** @hide */
@IntDef(prefix = {"FLAG_"}, flag = true, value = {
- FLAG_CANCEL_STICKY_REQUESTS,
+ FLAG_CANCEL_OVERRIDE_REQUESTS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeviceStateFlags {}
@@ -114,4 +114,10 @@
public int hashCode() {
return Objects.hash(mIdentifier, mName, mFlags);
}
+
+ /** Checks if a specific flag is set
+ */
+ public boolean hasFlag(int flagToCheckFor) {
+ return (mFlags & flagToCheckFor) == flagToCheckFor;
+ }
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 792feea..709af91 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -20,6 +20,7 @@
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
+import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
import static com.android.server.devicestate.OverrideRequestController.STATUS_SUSPENDED;
@@ -273,14 +274,14 @@
synchronized (mLock) {
final int[] oldStateIdentifiers = getSupportedStateIdentifiersLocked();
- // Whether or not at least one device state has the flag FLAG_CANCEL_STICKY_REQUESTS
+ // Whether or not at least one device state has the flag FLAG_CANCEL_OVERRIDE_REQUESTS
// set. If set to true, the OverrideRequestController will be configured to allow sticky
// requests.
boolean hasTerminalDeviceState = false;
mDeviceStates.clear();
for (int i = 0; i < supportedDeviceStates.length; i++) {
DeviceState state = supportedDeviceStates[i];
- if ((state.getFlags() & DeviceState.FLAG_CANCEL_STICKY_REQUESTS) != 0) {
+ if (state.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
hasTerminalDeviceState = true;
}
mDeviceStates.put(state.getIdentifier(), state);
@@ -345,8 +346,8 @@
}
mBaseState = Optional.of(baseState);
- if ((baseState.getFlags() & DeviceState.FLAG_CANCEL_STICKY_REQUESTS) != 0) {
- mOverrideRequestController.cancelStickyRequests();
+ if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
+ mOverrideRequestController.cancelOverrideRequests();
}
mOverrideRequestController.handleBaseStateChanged();
updatePendingStateLocked();
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index 05c9eb2..36cb416 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -153,6 +153,16 @@
}
/**
+ * Cancels all override requests, this could be due to the device being put
+ * into a hardware state that declares the flag "FLAG_CANCEL_OVERRIDE_REQUESTS"
+ */
+ void cancelOverrideRequests() {
+ mTmpRequestsToCancel.clear();
+ mTmpRequestsToCancel.addAll(mRequests);
+ cancelRequestsLocked(mTmpRequestsToCancel);
+ }
+
+ /**
* Returns {@code true} if this controller is current managing a request with the specified
* {@code token}, {@code false} otherwise.
*/
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index c04032f..ffed68e 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -54,6 +54,10 @@
private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false;
+ public static final int AUTO_BRIGHTNESS_ENABLED = 1;
+ public static final int AUTO_BRIGHTNESS_DISABLED = 2;
+ public static final int AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE = 3;
+
// How long the current sensor reading is assumed to be valid beyond the current time.
// This provides a bit of prediction, as well as ensures that the weight for the last sample is
// non-zero, which in turn ensures that the total weight is non-zero.
@@ -214,6 +218,7 @@
private IActivityTaskManager mActivityTaskManager;
private PackageManager mPackageManager;
private Context mContext;
+ private int mState = AUTO_BRIGHTNESS_DISABLED;
private final Injector mInjector;
@@ -331,10 +336,11 @@
return mCurrentBrightnessMapper.getAutoBrightnessAdjustment();
}
- public void configure(boolean enable, @Nullable BrightnessConfiguration configuration,
+ public void configure(int state, @Nullable BrightnessConfiguration configuration,
float brightness, boolean userChangedBrightness, float adjustment,
boolean userChangedAutoBrightnessAdjustment, int displayPolicy) {
- mHbmController.setAutoBrightnessEnabled(enable);
+ mState = state;
+ mHbmController.setAutoBrightnessEnabled(mState);
// While dozing, the application processor may be suspended which will prevent us from
// receiving new information from the light sensor. On some devices, we may be able to
// switch to a wake-up light sensor instead but for now we will simply disable the sensor
@@ -346,6 +352,7 @@
if (userChangedAutoBrightnessAdjustment) {
changed |= setAutoBrightnessAdjustment(adjustment);
}
+ final boolean enable = mState == AUTO_BRIGHTNESS_ENABLED;
if (userChangedBrightness && enable) {
// Update the brightness curve with the new user control point. It's critical this
// happens after we update the autobrightness adjustment since it may reset it.
@@ -459,6 +466,7 @@
public void dump(PrintWriter pw) {
pw.println();
pw.println("Automatic Brightness Controller Configuration:");
+ pw.println(" mState=" + configStateToString(mState));
pw.println(" mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum);
pw.println(" mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum);
pw.println(" mDozeScaleFactor=" + mDozeScaleFactor);
@@ -520,6 +528,19 @@
mScreenBrightnessThresholds.dump(pw);
}
+ private String configStateToString(int state) {
+ switch (state) {
+ case AUTO_BRIGHTNESS_ENABLED:
+ return "AUTO_BRIGHTNESS_ENABLED";
+ case AUTO_BRIGHTNESS_DISABLED:
+ return "AUTO_BRIGHTNESS_DISABLED";
+ case AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE:
+ return "AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE";
+ default:
+ return String.valueOf(state);
+ }
+ }
+
private boolean setLightSensorEnabled(boolean enable) {
if (enable) {
if (!mLightSensorEnabled) {
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index def9685..d0ce9ef 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -37,6 +37,8 @@
* </p>
*/
abstract class DisplayDevice {
+ private static final Display.Mode EMPTY_DISPLAY_MODE = new Display.Mode.Builder().build();
+
private final DisplayAdapter mDisplayAdapter;
private final IBinder mDisplayToken;
private final String mUniqueId;
@@ -213,6 +215,13 @@
public void setUserPreferredDisplayModeLocked(Display.Mode mode) { }
/**
+ * Returns the user preferred display mode.
+ */
+ public Display.Mode getUserPreferredDisplayModeLocked() {
+ return EMPTY_DISPLAY_MODE;
+ }
+
+ /**
* Sets the requested color mode.
*/
public void setRequestedColorModeLocked(int colorMode) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index be889e4..3feffc6 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -873,11 +873,11 @@
private void updateUserPreferredDisplayModeSettingsLocked() {
final float refreshRate = Settings.Global.getFloat(mContext.getContentResolver(),
- Settings.Global.USER_PREFERRED_REFRESH_RATE, 0.0f);
+ Settings.Global.USER_PREFERRED_REFRESH_RATE, Display.INVALID_DISPLAY_REFRESH_RATE);
final int height = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.USER_PREFERRED_RESOLUTION_HEIGHT, -1);
+ Settings.Global.USER_PREFERRED_RESOLUTION_HEIGHT, Display.INVALID_DISPLAY_HEIGHT);
final int width = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, -1);
+ Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, Display.INVALID_DISPLAY_WIDTH);
Display.Mode mode = new Display.Mode(width, height, refreshRate);
mUserPreferredMode = isResolutionAndRefreshRateValid(mode) ? mode : null;
}
@@ -1250,6 +1250,14 @@
}
final Surface surface = virtualDisplayConfig.getSurface();
int flags = virtualDisplayConfig.getFlags();
+ if (virtualDevice != null) {
+ final VirtualDeviceManagerInternal vdm =
+ getLocalService(VirtualDeviceManagerInternal.class);
+ if (!vdm.isValidVirtualDevice(virtualDevice)) {
+ throw new SecurityException("Invalid virtual device");
+ }
+ flags |= vdm.getBaseVirtualDisplayFlags(virtualDevice);
+ }
if (surface != null && surface.isSingleBuffered()) {
throw new IllegalArgumentException("Surface can't be single-buffered");
@@ -1282,14 +1290,6 @@
}
}
- if (virtualDevice != null) {
- final VirtualDeviceManagerInternal vdm =
- getLocalService(VirtualDeviceManagerInternal.class);
- if (!vdm.isValidVirtualDevice(virtualDevice)) {
- throw new SecurityException("Invalid virtual device");
- }
- }
-
if (callingUid != Process.SYSTEM_UID
&& (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
if (!canProjectVideo(projection)) {
@@ -1560,8 +1560,11 @@
}
if (mUserPreferredMode != null) {
device.setUserPreferredDisplayModeLocked(mUserPreferredMode);
+ } else {
+ configurePreferredDisplayModeLocked(display);
}
addDisplayPowerControllerLocked(display);
+
mDisplayStates.append(displayId, Display.STATE_UNKNOWN);
final float brightnessDefault = display.getDisplayInfoLocked().brightnessDefault;
@@ -1688,6 +1691,24 @@
}
}
+ private void configurePreferredDisplayModeLocked(LogicalDisplay display) {
+ final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
+ final Point userPreferredResolution =
+ mPersistentDataStore.getUserPreferredResolution(device);
+ final float refreshRate = mPersistentDataStore.getUserPreferredRefreshRate(device);
+ if (userPreferredResolution == null && Float.isNaN(refreshRate)) {
+ return;
+ }
+ Display.Mode.Builder modeBuilder = new Display.Mode.Builder();
+ if (userPreferredResolution != null) {
+ modeBuilder.setResolution(userPreferredResolution.x, userPreferredResolution.y);
+ }
+ if (!Float.isNaN(refreshRate)) {
+ modeBuilder.setRefreshRate(refreshRate);
+ }
+ device.setUserPreferredDisplayModeLocked(modeBuilder.build());
+ }
+
// If we've never recorded stable device stats for this device before and they aren't
// explicitly configured, go ahead and record the stable device stats now based on the status
// of the default display at first boot.
@@ -1731,36 +1752,79 @@
return mWideColorSpace.getId();
}
- void setUserPreferredDisplayModeInternal(Display.Mode mode) {
+ void setUserPreferredDisplayModeInternal(int displayId, Display.Mode mode) {
synchronized (mSyncRoot) {
- if (Objects.equals(mUserPreferredMode, mode)) {
+ if (Objects.equals(mUserPreferredMode, mode) && displayId == Display.INVALID_DISPLAY) {
return;
}
- if (mode != null && !isResolutionAndRefreshRateValid(mode)) {
+ if (mode != null && !isResolutionAndRefreshRateValid(mode)
+ && displayId == Display.INVALID_DISPLAY) {
throw new IllegalArgumentException("width, height and refresh rate of mode should "
- + "be greater than 0");
+ + "be greater than 0 when setting the global user preferred display mode.");
}
- mUserPreferredMode = mode;
- final int resolutionHeight = mode == null ? -1 : mode.getPhysicalHeight();
- final int resolutionWidth = mode == null ? -1 : mode.getPhysicalWidth();
- final float refreshRate = mode == null ? 0.0f : mode.getRefreshRate();
- Settings.Global.putFloat(mContext.getContentResolver(),
- Settings.Global.USER_PREFERRED_REFRESH_RATE, refreshRate);
- Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.USER_PREFERRED_RESOLUTION_HEIGHT, resolutionHeight);
- Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, resolutionWidth);
- mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> {
- device.setUserPreferredDisplayModeLocked(mode);
- });
+ final int resolutionHeight = mode == null ? Display.INVALID_DISPLAY_HEIGHT
+ : mode.getPhysicalHeight();
+ final int resolutionWidth = mode == null ? Display.INVALID_DISPLAY_WIDTH
+ : mode.getPhysicalWidth();
+ final float refreshRate = mode == null ? Display.INVALID_DISPLAY_REFRESH_RATE
+ : mode.getRefreshRate();
+
+ storeModeInPersistentDataStoreLocked(
+ displayId, resolutionWidth, resolutionHeight, refreshRate);
+ if (displayId != Display.INVALID_DISPLAY) {
+ setUserPreferredModeForDisplayLocked(displayId, mode);
+ } else {
+ mUserPreferredMode = mode;
+ storeModeInGlobalSettingsLocked(
+ resolutionWidth, resolutionHeight, refreshRate, mode);
+ }
}
}
- private Display.Mode getUserPreferredDisplayModeInternal() {
+ private void storeModeInPersistentDataStoreLocked(int displayId, int resolutionWidth,
+ int resolutionHeight, float refreshRate) {
+ DisplayDevice displayDevice = getDeviceForDisplayLocked(displayId);
+ if (displayDevice == null) {
+ return;
+ }
+ mPersistentDataStore.setUserPreferredResolution(
+ displayDevice, resolutionWidth, resolutionHeight);
+ mPersistentDataStore.setUserPreferredRefreshRate(displayDevice, refreshRate);
+ }
+
+ private void setUserPreferredModeForDisplayLocked(int displayId, Display.Mode mode) {
+ DisplayDevice displayDevice = getDeviceForDisplayLocked(displayId);
+ if (displayDevice == null) {
+ return;
+ }
+ displayDevice.setUserPreferredDisplayModeLocked(mode);
+ }
+
+ private void storeModeInGlobalSettingsLocked(
+ int resolutionWidth, int resolutionHeight, float refreshRate, Display.Mode mode) {
+ Settings.Global.putFloat(mContext.getContentResolver(),
+ Settings.Global.USER_PREFERRED_REFRESH_RATE, refreshRate);
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.USER_PREFERRED_RESOLUTION_HEIGHT, resolutionHeight);
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, resolutionWidth);
+ mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> {
+ device.setUserPreferredDisplayModeLocked(mode);
+ });
+ }
+
+ Display.Mode getUserPreferredDisplayModeInternal(int displayId) {
synchronized (mSyncRoot) {
- return mUserPreferredMode;
+ if (displayId == Display.INVALID_DISPLAY) {
+ return mUserPreferredMode;
+ }
+ DisplayDevice displayDevice = getDeviceForDisplayLocked(displayId);
+ if (displayDevice == null) {
+ return null;
+ }
+ return displayDevice.getUserPreferredDisplayModeLocked();
}
}
@@ -2373,7 +2437,7 @@
pw.println(" mMinimumBrightnessCurve=" + mMinimumBrightnessCurve);
if (mUserPreferredMode != null) {
- pw.println(mUserPreferredMode.toString());
+ pw.println(mUserPreferredMode);
}
pw.println();
@@ -3379,23 +3443,23 @@
}
@Override // Binder call
- public void setUserPreferredDisplayMode(Display.Mode mode) {
+ public void setUserPreferredDisplayMode(int displayId, Display.Mode mode) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE,
"Permission required to set the user preferred display mode.");
final long token = Binder.clearCallingIdentity();
try {
- setUserPreferredDisplayModeInternal(mode);
+ setUserPreferredDisplayModeInternal(displayId, mode);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override // Binder call
- public Display.Mode getUserPreferredDisplayMode() {
+ public Display.Mode getUserPreferredDisplayMode(int displayId) {
final long token = Binder.clearCallingIdentity();
try {
- return getUserPreferredDisplayModeInternal();
+ return getUserPreferredDisplayModeInternal(displayId);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index 9a7ddcb..a9a1f08 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -113,13 +113,18 @@
pw.println(" constrain-launcher-metrics [true|false]");
pw.println(" Sets if Display#getRealSize and getRealMetrics should be constrained for ");
pw.println(" Launcher.");
- pw.println(" set-user-preferred-display-mode WIDTH HEIGHT REFRESH-RATE");
+ pw.println(" set-user-preferred-display-mode WIDTH HEIGHT REFRESH-RATE "
+ + "DISPLAY_ID (optional)");
pw.println(" Sets the user preferred display mode which has fields WIDTH, HEIGHT and "
- + "REFRESH-RATE");
- pw.println(" clear-user-preferred-display-mode");
- pw.println(" Clears the user preferred display mode");
- pw.println(" get-user-preferred-display-mode");
- pw.println(" Returns the user preferred display mode or null id no mode is set by user");
+ + "REFRESH-RATE. If DISPLAY_ID is passed, the mode change is applied to display"
+ + "with id = DISPLAY_ID, else mode change is applied globally.");
+ pw.println(" clear-user-preferred-display-mode DISPLAY_ID (optional)");
+ pw.println(" Clears the user preferred display mode. If DISPLAY_ID is passed, the mode"
+ + " is cleared for display with id = DISPLAY_ID, else mode is cleared globally.");
+ pw.println(" get-user-preferred-display-mode DISPLAY_ID (optional)");
+ pw.println(" Returns the user preferred display mode or null if no mode is set by user."
+ + "If DISPLAY_ID is passed, the mode for display with id = DISPLAY_ID is "
+ + "returned, else global display mode is returned.");
pw.println(" set-match-content-frame-rate-pref PREFERENCE");
pw.println(" Sets the match content frame rate preference as PREFERENCE ");
pw.println(" get-match-content-frame-rate-pref");
@@ -235,28 +240,54 @@
getErrPrintWriter().println("Error: invalid format of width, height or refresh rate");
return 1;
}
- if (width < 0 || height < 0 || refreshRate <= 0.0f) {
- getErrPrintWriter().println("Error: invalid value of width, height or refresh rate");
+ if ((width < 0 || height < 0) && refreshRate <= 0.0f) {
+ getErrPrintWriter().println("Error: invalid value of resolution (width, height)"
+ + " and refresh rate");
return 1;
}
- final Context context = mService.getContext();
- final DisplayManager dm = context.getSystemService(DisplayManager.class);
- dm.setUserPreferredDisplayMode(new Display.Mode(width, height, refreshRate));
+ final String displayIdText = getNextArg();
+ int displayId = Display.INVALID_DISPLAY;
+ if (displayIdText != null) {
+ try {
+ displayId = Integer.parseInt(displayIdText);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: invalid format of display ID");
+ return 1;
+ }
+ }
+ mService.setUserPreferredDisplayModeInternal(
+ displayId, new Display.Mode(width, height, refreshRate));
return 0;
}
private int clearUserPreferredDisplayMode() {
- final Context context = mService.getContext();
- final DisplayManager dm = context.getSystemService(DisplayManager.class);
- dm.clearUserPreferredDisplayMode();
+ final String displayIdText = getNextArg();
+ int displayId = Display.INVALID_DISPLAY;
+ if (displayIdText != null) {
+ try {
+ displayId = Integer.parseInt(displayIdText);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: invalid format of display ID");
+ return 1;
+ }
+ }
+ mService.setUserPreferredDisplayModeInternal(displayId, null);
return 0;
}
private int getUserPreferredDisplayMode() {
- final Context context = mService.getContext();
- final DisplayManager dm = context.getSystemService(DisplayManager.class);
- final Display.Mode mode = dm.getUserPreferredDisplayMode();
+ final String displayIdText = getNextArg();
+ int displayId = Display.INVALID_DISPLAY;
+ if (displayIdText != null) {
+ try {
+ displayId = Integer.parseInt(displayIdText);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: invalid format of display ID");
+ return 1;
+ }
+ }
+ final Display.Mode mode = mService.getUserPreferredDisplayModeInternal(displayId);
if (mode == null) {
getOutPrintWriter().println("User preferred display mode: null");
return 0;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index c4d02c7..faf0038 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -62,6 +62,7 @@
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
@@ -127,6 +128,7 @@
private static final int MSG_STOP = 9;
private static final int MSG_UPDATE_BRIGHTNESS = 10;
private static final int MSG_UPDATE_RBC = 11;
+ private static final int MSG_STATSD_HBM_BRIGHTNESS = 12;
private static final int PROXIMITY_UNKNOWN = -1;
private static final int PROXIMITY_NEGATIVE = 0;
@@ -136,6 +138,8 @@
private static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0;
private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250;
+ private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
+
// Trigger proximity if distance is less than 5 cm.
private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f;
@@ -356,6 +360,9 @@
private float mBrightnessRampRateSlowDecrease;
private float mBrightnessRampRateSlowIncrease;
+ // Report HBM brightness change to StatsD
+ private int mDisplayStatsId;
+ private float mLastStatsBrightness = PowerManager.BRIGHTNESS_MIN;
// Whether or not to skip the initial brightness ramps into STATE_ON.
private final boolean mSkipScreenOnBrightnessRamp;
@@ -466,6 +473,7 @@
TAG = "DisplayPowerController[" + mDisplayId + "]";
mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
+ mDisplayStatsId = mUniqueDisplayId.hashCode();
mHandler = new DisplayControllerHandler(handler.getLooper());
if (mDisplayId == Display.DEFAULT_DISPLAY) {
@@ -763,6 +771,7 @@
if (mDisplayDevice != device) {
mDisplayDevice = device;
mUniqueDisplayId = uniqueId;
+ mDisplayStatsId = mUniqueDisplayId.hashCode();
mDisplayDeviceConfig = config;
loadFromDisplayDeviceConfig(token, info);
updatePowerState();
@@ -816,7 +825,7 @@
loadNitsRange(mContext.getResources());
setUpAutoBrightness(mContext.getResources(), mHandler);
reloadReduceBrightColours();
- mHbmController.resetHbmData(info.width, info.height, token,
+ mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
mDisplayDeviceConfig.getHighBrightnessModeData());
}
@@ -1004,7 +1013,15 @@
}
};
- private final RampAnimator.Listener mRampAnimatorListener = this::sendUpdatePowerState;
+ private final RampAnimator.Listener mRampAnimatorListener = new RampAnimator.Listener() {
+ @Override
+ public void onAnimationEnd() {
+ sendUpdatePowerState();
+
+ final float brightness = mPowerState.getScreenBrightness();
+ reportStats(brightness);
+ }
+ };
/** Clean up all resources that are accessed via the {@link #mHandler} thread. */
private void cleanupHandlerThreadAfterStop() {
@@ -1179,6 +1196,13 @@
&& (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
&& Float.isNaN(brightnessState)
&& mAutomaticBrightnessController != null;
+ final boolean autoBrightnessDisabledDueToDisplayOff = mPowerRequest.useAutoBrightness
+ && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
+ final int autoBrightnessState = autoBrightnessEnabled
+ ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
+ : autoBrightnessDisabledDueToDisplayOff
+ ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
+ : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
final boolean userSetBrightnessChanged = updateUserSetScreenBrightness();
@@ -1229,7 +1253,7 @@
// Configure auto-brightness.
if (mAutomaticBrightnessController != null) {
hadUserBrightnessPoint = mAutomaticBrightnessController.hasUserDataPoints();
- mAutomaticBrightnessController.configure(autoBrightnessEnabled,
+ mAutomaticBrightnessController.configure(autoBrightnessState,
mBrightnessConfiguration,
mLastUserSetScreenBrightness,
userSetBrightnessChanged, autoBrightnessAdjustment,
@@ -1626,11 +1650,13 @@
final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
final IBinder displayToken =
mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayTokenLocked();
+ final String displayUniqueId =
+ mLogicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
final DisplayDeviceConfig.HighBrightnessModeData hbmData =
ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
return new HighBrightnessModeController(mHandler, info.width, info.height, displayToken,
- PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData,
+ displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData,
() -> {
sendUpdatePowerStateLocked();
postBrightnessChangeRunnable();
@@ -2462,6 +2488,42 @@
}
}
+ private void reportStats(float brightness) {
+ float hbmTransitionPoint = PowerManager.BRIGHTNESS_MAX;
+ synchronized(mCachedBrightnessInfo) {
+ if (mCachedBrightnessInfo.hbmTransitionPoint == null) {
+ return;
+ }
+ hbmTransitionPoint = mCachedBrightnessInfo.hbmTransitionPoint.value;
+ }
+
+ final boolean aboveTransition = brightness > hbmTransitionPoint;
+ final boolean oldAboveTransition = mLastStatsBrightness > hbmTransitionPoint;
+
+ if (aboveTransition || oldAboveTransition) {
+ mLastStatsBrightness = brightness;
+ mHandler.removeMessages(MSG_STATSD_HBM_BRIGHTNESS);
+ if (aboveTransition != oldAboveTransition) {
+ // report immediately
+ logHbmBrightnessStats(brightness, mDisplayStatsId);
+ } else {
+ // delay for rate limiting
+ Message msg = mHandler.obtainMessage();
+ msg.what = MSG_STATSD_HBM_BRIGHTNESS;
+ msg.arg1 = Float.floatToIntBits(brightness);
+ msg.arg2 = mDisplayStatsId;
+ mHandler.sendMessageDelayed(msg, BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS);
+ }
+ }
+ }
+
+ private final void logHbmBrightnessStats(float brightness, int displayStatsId) {
+ synchronized (mHandler) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.DISPLAY_HBM_BRIGHTNESS_CHANGED, displayStatsId, brightness);
+ }
+ }
+
private final class DisplayControllerHandler extends Handler {
public DisplayControllerHandler(Looper looper) {
super(looper, null, true /*async*/);
@@ -2526,6 +2588,10 @@
final int justActivated = msg.arg2;
handleRbcChanged(strengthChanged == 1, justActivated == 1);
break;
+
+ case MSG_STATSD_HBM_BRIGHTNESS:
+ logHbmBrightnessStats(Float.intBitsToFloat(msg.arg1), msg.arg2);
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 1e1cfeb..16273ce 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -37,6 +37,7 @@
import android.view.SurfaceControlHdrLayerInfoListener;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
import com.android.server.display.DisplayManagerService.Clock;
@@ -80,6 +81,7 @@
private boolean mIsInAllowedAmbientRange = false;
private boolean mIsTimeAvailable = false;
private boolean mIsAutoBrightnessEnabled = false;
+ private boolean mIsAutoBrightnessOffByState = false;
private float mBrightness;
private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
private boolean mIsHdrLayerPresent = false;
@@ -88,6 +90,7 @@
private int mWidth;
private int mHeight;
private float mAmbientLux;
+ private int mDisplayStatsId;
/**
* If HBM is currently running, this is the start time for the current HBM session.
@@ -102,15 +105,15 @@
private LinkedList<HbmEvent> mEvents = new LinkedList<>();
HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken,
- float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData,
- Runnable hbmChangeCallback, Context context) {
- this(new Injector(), handler, width, height, displayToken, brightnessMin, brightnessMax,
- hbmData, hbmChangeCallback, context);
+ String displayUniqueId, float brightnessMin, float brightnessMax,
+ HighBrightnessModeData hbmData, Runnable hbmChangeCallback, Context context) {
+ this(new Injector(), handler, width, height, displayToken, displayUniqueId, brightnessMin,
+ brightnessMax, hbmData, hbmChangeCallback, context);
}
@VisibleForTesting
HighBrightnessModeController(Injector injector, Handler handler, int width, int height,
- IBinder displayToken, float brightnessMin, float brightnessMax,
+ IBinder displayToken, String displayUniqueId, float brightnessMin, float brightnessMax,
HighBrightnessModeData hbmData, Runnable hbmChangeCallback,
Context context) {
mInjector = injector;
@@ -126,10 +129,13 @@
mRecalcRunnable = this::recalculateTimeAllowance;
mHdrListener = new HdrListener();
- resetHbmData(width, height, displayToken, hbmData);
+ resetHbmData(width, height, displayToken, displayUniqueId, hbmData);
}
- void setAutoBrightnessEnabled(boolean isEnabled) {
+ void setAutoBrightnessEnabled(int state) {
+ final boolean isEnabled = state == AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+ mIsAutoBrightnessOffByState =
+ state == AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE;
if (!deviceSupportsHbm() || isEnabled == mIsAutoBrightnessEnabled) {
return;
}
@@ -231,10 +237,12 @@
mSettingsObserver.stopObserving();
}
- void resetHbmData(int width, int height, IBinder displayToken, HighBrightnessModeData hbmData) {
+ void resetHbmData(int width, int height, IBinder displayToken, String displayUniqueId,
+ HighBrightnessModeData hbmData) {
mWidth = width;
mHeight = height;
mHbmData = hbmData;
+ mDisplayStatsId = displayUniqueId.hashCode();
unregisterHdrListener();
mSkinThermalStatusObserver.stopObserving();
@@ -275,6 +283,7 @@
+ (mIsAutoBrightnessEnabled ? "" : " (old/invalid)"));
pw.println(" mIsInAllowedAmbientRange=" + mIsInAllowedAmbientRange);
pw.println(" mIsAutoBrightnessEnabled=" + mIsAutoBrightnessEnabled);
+ pw.println(" mIsAutoBrightnessOffByState=" + mIsAutoBrightnessOffByState);
pw.println(" mIsHdrLayerPresent=" + mIsHdrLayerPresent);
pw.println(" mBrightnessMin=" + mBrightnessMin);
pw.println(" mBrightnessMax=" + mBrightnessMax);
@@ -436,11 +445,52 @@
private void updateHbmMode() {
int newHbmMode = calculateHighBrightnessMode();
if (mHbmMode != newHbmMode) {
+ updateHbmStats(mHbmMode, newHbmMode);
mHbmMode = newHbmMode;
mHbmChangeCallback.run();
}
}
+ private void updateHbmStats(int mode, int newMode) {
+ int state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF;
+ if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR) {
+ state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR;
+ } else if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT) {
+ state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT;
+ }
+
+ int reason =
+ FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN;
+ boolean oldHbmSv = (mode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+ boolean newHbmSv = (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+ if (oldHbmSv && !newHbmSv) {
+ // If more than one conditions are flipped and turn off HBM sunlight
+ // visibility, only one condition will be reported to make it simple.
+ if (!mIsAutoBrightnessEnabled && mIsAutoBrightnessOffByState) {
+ reason = FrameworkStatsLog
+ .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF;
+ } else if (!mIsAutoBrightnessEnabled) {
+ reason = FrameworkStatsLog
+ .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_AUTOBRIGHTNESS_OFF;
+ } else if (!mIsInAllowedAmbientRange) {
+ reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP;
+ } else if (!mIsTimeAvailable) {
+ reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT;
+ } else if (!mIsThermalStatusWithinLimit) {
+ reason = FrameworkStatsLog
+ .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT;
+ } else if (mIsHdrLayerPresent) {
+ reason = FrameworkStatsLog
+ .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING;
+ } else if (mIsBlockedByLowPowerMode) {
+ reason = FrameworkStatsLog
+ .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_BATTERY_SAVE_ON;
+ }
+ }
+
+ mInjector.reportHbmStateChange(mDisplayStatsId, state, reason);
+ }
+
private int calculateHighBrightnessMode() {
if (!deviceSupportsHbm()) {
return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
@@ -642,5 +692,10 @@
return IThermalService.Stub.asInterface(
ServiceManager.getService(Context.THERMAL_SERVICE));
}
+
+ public void reportHbmStateChange(int display, int state, int reason) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED, display, state, reason);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 300f59e..84de822 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -847,6 +847,10 @@
public void setUserPreferredDisplayModeLocked(Display.Mode mode) {
final int oldModeId = getPreferredModeId();
mUserPreferredMode = mode;
+ if (mode != null && (mode.isRefreshRateSet() ^ mode.isResolutionSet())) {
+ mUserPreferredMode = findMode(mode.getPhysicalWidth(),
+ mode.getPhysicalHeight(), mode.getRefreshRate());
+ }
mUserPreferredModeId = findUserPreferredModeIdLocked(mode);
if (oldModeId != getPreferredModeId()) {
@@ -855,6 +859,11 @@
}
@Override
+ public Display.Mode getUserPreferredDisplayModeLocked() {
+ return mUserPreferredMode;
+ }
+
+ @Override
public void setRequestedColorModeLocked(int colorMode) {
requestColorModeLocked(colorMode);
}
@@ -1062,6 +1071,18 @@
return matchingModeId;
}
+ // Returns a mode with resolution (width, height) and/or refreshRate. If any one of the
+ // resolution or refresh-rate is valid, a mode having the valid parameters is returned.
+ private Display.Mode findMode(int width, int height, float refreshRate) {
+ for (int i = 0; i < mSupportedModes.size(); i++) {
+ Display.Mode supportedMode = mSupportedModes.valueAt(i).mMode;
+ if (supportedMode.matchesIfValid(width, height, refreshRate)) {
+ return supportedMode;
+ }
+ }
+ return null;
+ }
+
private int findUserPreferredModeIdLocked(Display.Mode userPreferredMode) {
if (userPreferredMode != null) {
for (int i = 0; i < mSupportedModes.size(); i++) {
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index 4b0d43b..2eba080 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -291,6 +291,54 @@
return false;
}
+ public boolean setUserPreferredRefreshRate(DisplayDevice displayDevice, float refreshRate) {
+ final String displayDeviceUniqueId = displayDevice.getUniqueId();
+ if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) {
+ return false;
+ }
+ DisplayState state = getDisplayState(displayDevice.getUniqueId(), true);
+ if (state.setRefreshRate(refreshRate)) {
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
+ public float getUserPreferredRefreshRate(DisplayDevice device) {
+ if (device == null || !device.hasStableUniqueId()) {
+ return Float.NaN;
+ }
+ final DisplayState state = getDisplayState(device.getUniqueId(), false);
+ if (state == null) {
+ return Float.NaN;
+ }
+ return state.getRefreshRate();
+ }
+
+ public boolean setUserPreferredResolution(DisplayDevice displayDevice, int width, int height) {
+ final String displayDeviceUniqueId = displayDevice.getUniqueId();
+ if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) {
+ return false;
+ }
+ DisplayState state = getDisplayState(displayDevice.getUniqueId(), true);
+ if (state.setResolution(width, height)) {
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
+ public Point getUserPreferredResolution(DisplayDevice displayDevice) {
+ if (displayDevice == null || !displayDevice.hasStableUniqueId()) {
+ return null;
+ }
+ final DisplayState state = getDisplayState(displayDevice.getUniqueId(), false);
+ if (state == null) {
+ return null;
+ }
+ return state.getResolution();
+ }
+
public Point getStableDisplaySize() {
loadIfNeeded();
return mStableDeviceValues.getDisplaySize();
@@ -536,6 +584,9 @@
private static final class DisplayState {
private int mColorMode;
private float mBrightness;
+ private int mWidth;
+ private int mHeight;
+ private float mRefreshRate;
// Brightness configuration by user
private BrightnessConfigurations mDisplayBrightnessConfigurations =
@@ -576,6 +627,31 @@
return mDisplayBrightnessConfigurations.mConfigurations.get(userSerial);
}
+ public boolean setResolution(int width, int height) {
+ if (width == mWidth && height == mHeight) {
+ return false;
+ }
+ mWidth = width;
+ mHeight = height;
+ return true;
+ }
+
+ public Point getResolution() {
+ return new Point(mWidth, mHeight);
+ }
+
+ public boolean setRefreshRate(float refreshRate) {
+ if (refreshRate == mRefreshRate) {
+ return false;
+ }
+ mRefreshRate = refreshRate;
+ return true;
+ }
+
+ public float getRefreshRate() {
+ return mRefreshRate;
+ }
+
public void loadFromXml(TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
final int outerDepth = parser.getDepth();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 220d790..3af51f4 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -264,6 +264,13 @@
}
/**
+ * Returns {@code true} if current IME supports Stylus Handwriting.
+ */
+ boolean supportsStylusHandwriting() {
+ return mSupportsStylusHw;
+ }
+
+ /**
* Used to bring IME service up to visible adjustment while it is being shown.
*/
@GuardedBy("ImfLock.class")
@@ -302,15 +309,17 @@
return;
}
if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
+ final InputMethodInfo info = mMethodMap.get(mSelectedMethodId);
+ mSupportsStylusHw = info.supportsStylusHandwriting();
// Dispatch display id for InputMethodService to update context display.
mService.executeOrSendMessage(mCurMethod,
- mService.mCaller.obtainMessageIOO(MSG_INITIALIZE_IME,
- mMethodMap.get(mSelectedMethodId).getConfigChanges(),
- mCurMethod, mCurToken));
+ mService.mCaller.obtainMessageIOOO(MSG_INITIALIZE_IME,
+ info.getConfigChanges(), mCurMethod, mCurToken,
+ mSupportsStylusHw));
mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
mService.reRequestCurrentClientSessionLocked();
}
- mSupportsStylusHw = mMethodMap.get(mSelectedMethodId).supportsStylusHandwriting();
+
if (mSupportsStylusHw) {
// TODO init Handwriting spy.
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index c87dc89..f1e8d0d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -233,6 +233,7 @@
static final int MSG_REMOVE_IME_SURFACE = 1060;
static final int MSG_REMOVE_IME_SURFACE_FROM_WINDOW = 1061;
static final int MSG_UPDATE_IME_WINDOW_STATUS = 1070;
+ static final int MSG_START_HANDWRITING = 1100;
static final int MSG_START_INPUT = 2000;
@@ -315,6 +316,12 @@
private int mMethodMapUpdateCount = 0;
/**
+ * Tracks requestIds for Stylus handwriting mode.
+ */
+ @GuardedBy("ImfLock.class")
+ private int mHwRequestId = 0;
+
+ /**
* The display id for which the latest startInput was called.
*/
@GuardedBy("ImfLock.class")
@@ -3019,21 +3026,8 @@
}
final long ident = Binder.clearCallingIdentity();
try {
- if (mCurClient == null || client == null
- || mCurClient.client.asBinder() != client.asBinder()) {
- // We need to check if this is the current client with
- // focus in the window manager, to allow this call to
- // be made before input is started in it.
- final ClientState cs = mClients.get(client.asBinder());
- if (cs == null) {
- throw new IllegalArgumentException(
- "unknown client " + client.asBinder());
- }
- if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid,
- cs.selfReportedDisplayId)) {
- Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
- return false;
- }
+ if (!canInteractWithImeLocked(uid, client, "showSoftInput")) {
+ return false;
}
if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
return showCurrentInputLocked(windowToken, flags, resultReceiver, reason);
@@ -3044,6 +3038,40 @@
}
}
+ @Override
+ public void startStylusHandwriting(IInputMethodClient client) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.startStylusHandwriting");
+ ImeTracing.getInstance().triggerManagerServiceDump(
+ "InputMethodManagerService#startStylusHandwriting");
+ int uid = Binder.getCallingUid();
+ synchronized (ImfLock.class) {
+ if (!calledFromValidUserLocked()) {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ return;
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting")) {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ return;
+ }
+ if (!mBindingController.supportsStylusHandwriting()) {
+ Slog.w(TAG, "Stylus HW unsupported by IME. Ignoring startStylusHandwriting()");
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ return;
+ }
+ if (DEBUG) Slog.v(TAG, "Client requesting Stylus Handwriting to be started");
+ if (getCurMethodLocked() != null) {
+ executeOrSendMessage(getCurMethodLocked(), mCaller.obtainMessageIO(
+ MSG_START_HANDWRITING, ++mHwRequestId, getCurMethodLocked()));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+ }
+
@BinderThread
@Override
public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
@@ -3523,6 +3551,27 @@
return res;
}
+ @GuardedBy("ImfLock.class")
+ private boolean canInteractWithImeLocked(
+ int uid, IInputMethodClient client, String methodName) {
+ if (mCurClient == null || client == null
+ || mCurClient.client.asBinder() != client.asBinder()) {
+ // We need to check if this is the current client with
+ // focus in the window manager, to allow this call to
+ // be made before input is started in it.
+ final ClientState cs = mClients.get(client.asBinder());
+ if (cs == null) {
+ throw new IllegalArgumentException("unknown client " + client.asBinder());
+ }
+ if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid,
+ cs.selfReportedDisplayId)) {
+ Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client));
+ return false;
+ }
+ }
+ return true;
+ }
+
private boolean shouldRestoreImeVisibility(IBinder windowToken,
@SoftInputModeFlags int softInputMode) {
switch (softInputMode & LayoutParams.SOFT_INPUT_MASK_STATE) {
@@ -4237,7 +4286,8 @@
}
final IBinder token = (IBinder) args.arg2;
((IInputMethod) args.arg1).initializeInternal(token,
- new InputMethodPrivilegedOperationsImpl(this, token), msg.arg1);
+ new InputMethodPrivilegedOperationsImpl(this, token),
+ msg.arg1, (boolean) args.arg3);
} catch (RemoteException e) {
}
args.recycle();
@@ -4410,10 +4460,34 @@
}
return true;
}
+ case MSG_START_HANDWRITING:
+ try {
+ (((IInputMethod) msg.obj)).canStartStylusHandwriting(msg.arg1);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException calling canStartStylusHandwriting(): ", e);
+ }
+ return true;
}
return false;
}
+ @BinderThread
+ private void onStylusHandwritingReady(int requestId) {
+ synchronized (ImfLock.class) {
+ if (mHwRequestId != requestId) {
+ // obsolete request
+ return;
+ }
+
+ try {
+ // TODO: replace null with actual Channel, MotionEvents
+ getCurMethodLocked().startStylusHandwriting(null, null);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException calling startStylusHandwriting(): ", e);
+ }
+ }
+ }
+
private void handleSetInteractive(final boolean interactive) {
synchronized (ImfLock.class) {
mIsInteractive = interactive;
@@ -5958,5 +6032,11 @@
public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible) {
mImms.applyImeVisibility(mToken, windowToken, setVisible);
}
+
+ @BinderThread
+ @Override
+ public void onStylusHandwritingReady(int requestId) {
+ mImms.onStylusHandwritingReady(requestId);
+ }
}
}
diff --git a/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java b/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java
new file mode 100644
index 0000000..282e3c1
--- /dev/null
+++ b/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java
@@ -0,0 +1,53 @@
+/*
+ * 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.locales;
+
+import static android.os.Process.INVALID_UID;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+/**
+ * Holds data used to report the ApplicationLocalesChanged atom.
+ */
+public final class AppLocaleChangedAtomRecord {
+ final int mCallingUid;
+ int mTargetUid = INVALID_UID;
+ String mNewLocales = "";
+ String mPrevLocales = "";
+ int mStatus = FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__STATUS__STATUS_UNSPECIFIED;
+
+ AppLocaleChangedAtomRecord(int callingUid) {
+ this.mCallingUid = callingUid;
+ }
+
+ void setNewLocales(String newLocales) {
+ this.mNewLocales = newLocales;
+ }
+
+ void setTargetUid(int targetUid) {
+ this.mTargetUid = targetUid;
+ }
+
+ void setPrevLocales(String prevLocales) {
+ this.mPrevLocales = prevLocales;
+ }
+
+ void setStatus(int status) {
+ this.mStatus = status;
+ }
+}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 6aabdb5..1657b22 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -39,6 +39,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -148,40 +149,52 @@
*/
public void setApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId,
@NonNull LocaleList locales) throws RemoteException, IllegalArgumentException {
- requireNonNull(appPackageName);
- requireNonNull(locales);
-
- //Allow apps with INTERACT_ACROSS_USERS permission to set locales for different user.
- userId = mActivityManagerInternal.handleIncomingUser(
- Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL,
- "setApplicationLocales", appPackageName);
-
- // This function handles two types of set operations:
- // 1.) A normal, non-privileged app setting its own locale.
- // 2.) A privileged system service setting locales of another package.
- // The least privileged case is a normal app performing a set, so check that first and
- // set locales if the package name is owned by the app. Next, check if the caller has the
- // necessary permission and set locales.
- boolean isCallerOwner = isPackageOwnedByCaller(appPackageName, userId);
- if (!isCallerOwner) {
- enforceChangeConfigurationPermission();
- }
-
- final long token = Binder.clearCallingIdentity();
+ AppLocaleChangedAtomRecord atomRecordForMetrics = new
+ AppLocaleChangedAtomRecord(Binder.getCallingUid());
try {
- setApplicationLocalesUnchecked(appPackageName, userId, locales);
+ requireNonNull(appPackageName);
+ requireNonNull(locales);
+ atomRecordForMetrics.setNewLocales(locales.toLanguageTags());
+ //Allow apps with INTERACT_ACROSS_USERS permission to set locales for different user.
+ userId = mActivityManagerInternal.handleIncomingUser(
+ Binder.getCallingPid(), Binder.getCallingUid(), userId,
+ false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL,
+ "setApplicationLocales", appPackageName);
+
+ // This function handles two types of set operations:
+ // 1.) A normal, non-privileged app setting its own locale.
+ // 2.) A privileged system service setting locales of another package.
+ // The least privileged case is a normal app performing a set, so check that first and
+ // set locales if the package name is owned by the app. Next, check if the caller has
+ // the necessary permission and set locales.
+ boolean isCallerOwner = isPackageOwnedByCaller(appPackageName, userId,
+ atomRecordForMetrics);
+ if (!isCallerOwner) {
+ enforceChangeConfigurationPermission(atomRecordForMetrics);
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ setApplicationLocalesUnchecked(appPackageName, userId, locales,
+ atomRecordForMetrics);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
} finally {
- Binder.restoreCallingIdentity(token);
+ logMetric(atomRecordForMetrics);
}
}
private void setApplicationLocalesUnchecked(@NonNull String appPackageName,
- @UserIdInt int userId, @NonNull LocaleList locales) {
+ @UserIdInt int userId, @NonNull LocaleList locales,
+ @NonNull AppLocaleChangedAtomRecord atomRecordForMetrics) {
if (DEBUG) {
Slog.d(TAG, "setApplicationLocales: setting locales for package " + appPackageName
+ " and user " + userId);
}
+
+ atomRecordForMetrics.setPrevLocales(getApplicationLocalesUnchecked(appPackageName, userId)
+ .toLanguageTags());
final ActivityTaskManagerInternal.PackageConfigurationUpdater updater =
mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName,
userId);
@@ -194,6 +207,11 @@
notifyRegisteredReceivers(appPackageName, userId, locales);
mBackupHelper.notifyBackupManager();
+ atomRecordForMetrics.setStatus(
+ FrameworkStatsLog.APPLICATION_LOCALES_CHANGED__STATUS__CONFIG_COMMITTED);
+ } else {
+ atomRecordForMetrics.setStatus(FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__STATUS__CONFIG_UNCOMMITTED);
}
}
@@ -259,26 +277,49 @@
}
/**
+ * Same as {@link LocaleManagerService#isPackageOwnedByCaller(String, int,
+ * AppLocaleChangedAtomRecord)}, but for methods that do not log locale atom.
+ */
+ private boolean isPackageOwnedByCaller(String appPackageName, int userId) {
+ return isPackageOwnedByCaller(appPackageName, userId, /* atomRecordForMetrics= */null);
+ }
+
+ /**
* Checks if the package is owned by the calling app or not for the given user id.
*
* @throws IllegalArgumentException if package not found for given userid
*/
- private boolean isPackageOwnedByCaller(String appPackageName, int userId) {
+ private boolean isPackageOwnedByCaller(String appPackageName, int userId,
+ @Nullable AppLocaleChangedAtomRecord atomRecordForMetrics) {
final int uid = mPackageManagerInternal
.getPackageUid(appPackageName, /* flags */ 0, userId);
if (uid < 0) {
Slog.w(TAG, "Unknown package " + appPackageName + " for user " + userId);
+ if (atomRecordForMetrics != null) {
+ atomRecordForMetrics.setStatus(FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__STATUS__FAILURE_INVALID_TARGET_PACKAGE);
+ }
throw new IllegalArgumentException("Unknown package: " + appPackageName
+ " for user " + userId);
}
+ if (atomRecordForMetrics != null) {
+ atomRecordForMetrics.setTargetUid(uid);
+ }
//Once valid package found, ignore the userId part for validating package ownership
//as apps with INTERACT_ACROSS_USERS permission could be changing locale for different user.
return UserHandle.isSameApp(Binder.getCallingUid(), uid);
}
- private void enforceChangeConfigurationPermission() {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.CHANGE_CONFIGURATION, "setApplicationLocales");
+ private void enforceChangeConfigurationPermission(@NonNull AppLocaleChangedAtomRecord
+ atomRecordForMetrics) {
+ try {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_CONFIGURATION, "setApplicationLocales");
+ } catch (SecurityException e) {
+ atomRecordForMetrics.setStatus(FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__STATUS__FAILURE_PERMISSION_ABSENT);
+ throw e;
+ }
}
/**
@@ -312,6 +353,7 @@
}
}
+ @NonNull
private LocaleList getApplicationLocalesUnchecked(@NonNull String appPackageName,
@UserIdInt int userId) {
if (DEBUG) {
@@ -345,4 +387,13 @@
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
// TODO(b/201766221): Implement when there is state.
}
+
+ private void logMetric(@NonNull AppLocaleChangedAtomRecord atomRecordForMetrics) {
+ FrameworkStatsLog.write(FrameworkStatsLog.APPLICATION_LOCALES_CHANGED,
+ atomRecordForMetrics.mCallingUid,
+ atomRecordForMetrics.mTargetUid,
+ atomRecordForMetrics.mNewLocales,
+ atomRecordForMetrics.mPrevLocales,
+ atomRecordForMetrics.mStatus);
+ }
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index fab11a1..682a27a 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -39,7 +39,10 @@
import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE;
import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
+import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_STRONG;
+import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_WEAK;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -123,6 +126,8 @@
import com.android.internal.util.Preconditions;
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
+import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
import com.android.internal.widget.LockscreenCredential;
@@ -135,6 +140,7 @@
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
+import com.android.server.locksettings.SyntheticPasswordManager.TokenType;
import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -1123,6 +1129,16 @@
return mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED;
}
+ private void checkManageWeakEscrowTokenMethodUsage() {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN,
+ "Requires MANAGE_WEAK_ESCROW_TOKEN permission.");
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ throw new IllegalArgumentException(
+ "Weak escrow token are only for automotive devices.");
+ }
+ }
+
@Override
public boolean hasSecureLockScreen() {
return mHasSecureLockScreen;
@@ -1911,6 +1927,97 @@
});
}
+ /** Register the given WeakEscrowTokenRemovedListener. */
+ @Override
+ public boolean registerWeakEscrowTokenRemovedListener(
+ @NonNull IWeakEscrowTokenRemovedListener listener) {
+ checkManageWeakEscrowTokenMethodUsage();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mSpManager.registerWeakEscrowTokenRemovedListener(listener);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /** Unregister the given WeakEscrowTokenRemovedListener. */
+ @Override
+ public boolean unregisterWeakEscrowTokenRemovedListener(
+ @NonNull IWeakEscrowTokenRemovedListener listener) {
+ checkManageWeakEscrowTokenMethodUsage();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mSpManager.unregisterWeakEscrowTokenRemovedListener(listener);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public long addWeakEscrowToken(byte[] token, int userId,
+ @NonNull IWeakEscrowTokenActivatedListener listener) {
+ checkManageWeakEscrowTokenMethodUsage();
+ Objects.requireNonNull(listener, "Listener can not be null.");
+ EscrowTokenStateChangeCallback internalListener = (handle, userId1) -> {
+ try {
+ listener.onWeakEscrowTokenActivated(handle, userId1);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception while notifying weak escrow token has been activated", e);
+ }
+ };
+ final long restoreToken = Binder.clearCallingIdentity();
+ try {
+ return addEscrowToken(token, TOKEN_TYPE_WEAK, userId, internalListener);
+ } finally {
+ Binder.restoreCallingIdentity(restoreToken);
+ }
+ }
+
+ @Override
+ public boolean removeWeakEscrowToken(long handle, int userId) {
+ checkManageWeakEscrowTokenMethodUsage();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return removeEscrowToken(handle, userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean isWeakEscrowTokenActive(long handle, int userId) {
+ checkManageWeakEscrowTokenMethodUsage();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return isEscrowTokenActive(handle, userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean isWeakEscrowTokenValid(long handle, byte[] token, int userId) {
+ checkManageWeakEscrowTokenMethodUsage();
+ final long restoreToken = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSpManager) {
+ if (!mSpManager.hasEscrowData(userId)) {
+ Slog.w(TAG, "Escrow token is disabled on the current user");
+ return false;
+ }
+ AuthenticationResult authResult = mSpManager.unwrapWeakTokenBasedSyntheticPassword(
+ getGateKeeperService(), handle, token, userId);
+ if (authResult.authToken == null) {
+ Slog.w(TAG, "Invalid escrow token supplied");
+ return false;
+ }
+ return true;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(restoreToken);
+ }
+ }
+
@VisibleForTesting /** Note: this method is overridden in unit tests */
protected void tieProfileLockToParent(int userId, LockscreenCredential password) {
if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
@@ -3029,6 +3136,7 @@
private long setLockCredentialWithAuthTokenLocked(LockscreenCredential credential,
AuthenticationToken auth, int userId) {
if (DEBUG) Slog.d(TAG, "setLockCredentialWithAuthTokenLocked: user=" + userId);
+ final int savedCredentialType = getCredentialTypeInternal(userId);
long newHandle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
credential, auth, userId);
final Map<Integer, LockscreenCredential> profilePasswords;
@@ -3075,6 +3183,9 @@
setUserPasswordMetrics(credential, userId);
mManagedProfilePasswordCache.removePassword(userId);
+ if (savedCredentialType != CREDENTIAL_TYPE_NONE) {
+ mSpManager.destroyAllWeakTokenBasedSyntheticPasswords(userId);
+ }
if (profilePasswords != null) {
for (Map.Entry<Integer, LockscreenCredential> entry : profilePasswords.entrySet()) {
@@ -3242,8 +3353,9 @@
}
}
- private long addEscrowToken(byte[] token, int userId, EscrowTokenStateChangeCallback callback) {
- if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId);
+ private long addEscrowToken(@NonNull byte[] token, @TokenType int type, int userId,
+ @NonNull EscrowTokenStateChangeCallback callback) {
+ if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId + ", type=" + type);
synchronized (mSpManager) {
// Migrate to synthetic password based credentials if the user has no password,
// the token can then be activated immediately.
@@ -3264,7 +3376,8 @@
throw new SecurityException("Escrow token is disabled on the current user");
}
}
- long handle = mSpManager.createTokenBasedSyntheticPassword(token, userId, callback);
+ long handle = mSpManager.createTokenBasedSyntheticPassword(token, type, userId,
+ callback);
if (auth != null) {
mSpManager.activateTokenBasedSyntheticPassword(handle, auth, userId);
}
@@ -3345,8 +3458,8 @@
private boolean setLockCredentialWithTokenInternalLocked(LockscreenCredential credential,
long tokenHandle, byte[] token, int userId) {
final AuthenticationResult result;
- result = mSpManager.unwrapTokenBasedSyntheticPassword(
- getGateKeeperService(), tokenHandle, token, userId);
+ result = mSpManager.unwrapTokenBasedSyntheticPassword(getGateKeeperService(), tokenHandle,
+ token, userId);
if (result.authToken == null) {
Slog.w(TAG, "Invalid escrow token supplied");
return false;
@@ -3369,7 +3482,8 @@
AuthenticationResult authResult;
synchronized (mSpManager) {
if (!mSpManager.hasEscrowData(userId)) {
- throw new SecurityException("Escrow token is disabled on the current user");
+ Slog.w(TAG, "Escrow token is disabled on the current user");
+ return false;
}
authResult = mSpManager.unwrapTokenBasedSyntheticPassword(getGateKeeperService(),
tokenHandle, token, userId);
@@ -3643,7 +3757,8 @@
@Override
public long addEscrowToken(byte[] token, int userId,
EscrowTokenStateChangeCallback callback) {
- return LockSettingsService.this.addEscrowToken(token, userId, callback);
+ return LockSettingsService.this.addEscrowToken(token, TOKEN_TYPE_STRONG, userId,
+ callback);
}
@Override
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 601a572..2da4431 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -18,6 +18,7 @@
import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.PasswordMetrics;
@@ -28,6 +29,7 @@
import android.hardware.weaver.V1_0.WeaverReadResponse;
import android.hardware.weaver.V1_0.WeaverReadStatus;
import android.hardware.weaver.V1_0.WeaverStatus;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserManager;
import android.security.GateKeeper;
@@ -41,6 +43,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.ICheckCredentialProgressCallback;
+import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
@@ -48,6 +51,8 @@
import libcore.util.HexEncoding;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
@@ -111,7 +116,8 @@
private static final byte SYNTHETIC_PASSWORD_VERSION_V2 = 2;
private static final byte SYNTHETIC_PASSWORD_VERSION_V3 = 3;
private static final byte SYNTHETIC_PASSWORD_PASSWORD_BASED = 0;
- private static final byte SYNTHETIC_PASSWORD_TOKEN_BASED = 1;
+ private static final byte SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED = 1;
+ private static final byte SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED = 2;
// 256-bit synthetic password
private static final byte SYNTHETIC_PASSWORD_LENGTH = 256 / 8;
@@ -366,10 +372,47 @@
}
}
+ static class SyntheticPasswordBlob {
+ byte mVersion;
+ byte mType;
+ byte[] mContent;
+
+ public static SyntheticPasswordBlob create(byte version, byte type, byte[] content) {
+ SyntheticPasswordBlob result = new SyntheticPasswordBlob();
+ result.mVersion = version;
+ result.mType = type;
+ result.mContent = content;
+ return result;
+ }
+
+ public static SyntheticPasswordBlob fromBytes(byte[] data) {
+ SyntheticPasswordBlob result = new SyntheticPasswordBlob();
+ result.mVersion = data[0];
+ result.mType = data[1];
+ result.mContent = Arrays.copyOfRange(data, 2, data.length);
+ return result;
+ }
+
+ public byte[] toByte() {
+ byte[] blob = new byte[mContent.length + 1 + 1];
+ blob[0] = mVersion;
+ blob[1] = mType;
+ System.arraycopy(mContent, 0, blob, 2, mContent.length);
+ return blob;
+ }
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({TOKEN_TYPE_STRONG, TOKEN_TYPE_WEAK})
+ @interface TokenType {}
+ static final int TOKEN_TYPE_STRONG = 0;
+ static final int TOKEN_TYPE_WEAK = 1;
+
static class TokenData {
byte[] secdiscardableOnDisk;
byte[] weaverSecret;
byte[] aggregatedSecret;
+ @TokenType int mType;
EscrowTokenStateChangeCallback mCallback;
}
@@ -381,6 +424,9 @@
private final UserManager mUserManager;
+ private final RemoteCallbackList<IWeakEscrowTokenRemovedListener> mListeners =
+ new RemoteCallbackList<>();
+
public SyntheticPasswordManager(Context context, LockSettingsStorage storage,
UserManager userManager, PasswordSlotManager passwordSlotManager) {
mContext = context;
@@ -880,13 +926,34 @@
* Create a token based Synthetic password for the given user.
* @return the handle of the token
*/
- public long createTokenBasedSyntheticPassword(byte[] token, int userId,
+ public long createStrongTokenBasedSyntheticPassword(byte[] token, int userId,
+ @Nullable EscrowTokenStateChangeCallback changeCallback) {
+ return createTokenBasedSyntheticPassword(token, TOKEN_TYPE_STRONG, userId,
+ changeCallback);
+ }
+
+ /**
+ * Create a weak token based Synthetic password for the given user.
+ * @return the handle of the weak token
+ */
+ public long createWeakTokenBasedSyntheticPassword(byte[] token, int userId,
+ @Nullable EscrowTokenStateChangeCallback changeCallback) {
+ return createTokenBasedSyntheticPassword(token, TOKEN_TYPE_WEAK, userId,
+ changeCallback);
+ }
+
+ /**
+ * Create a token based Synthetic password of the given type for the given user.
+ * @return the handle of the token
+ */
+ public long createTokenBasedSyntheticPassword(byte[] token, @TokenType int type, int userId,
@Nullable EscrowTokenStateChangeCallback changeCallback) {
long handle = generateHandle();
if (!tokenMap.containsKey(userId)) {
tokenMap.put(userId, new ArrayMap<>());
}
TokenData tokenData = new TokenData();
+ tokenData.mType = type;
final byte[] secdiscardable = secureRandom(SECDISCARDABLE_LENGTH);
if (isWeaverAvailable()) {
tokenData.weaverSecret = secureRandom(mWeaverConfig.valueSize);
@@ -910,6 +977,7 @@
return new ArraySet<>(tokenMap.get(userId).keySet());
}
+ /** Remove the given pending token. */
public boolean removePendingToken(long handle, int userId) {
if (!tokenMap.containsKey(userId)) {
return false;
@@ -941,7 +1009,7 @@
mPasswordSlotManager.markSlotInUse(slot);
}
saveSecdiscardable(handle, tokenData.secdiscardableOnDisk, userId);
- createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, authToken,
+ createSyntheticPasswordBlob(handle, getTokenBasedBlobType(tokenData.mType), authToken,
tokenData.aggregatedSecret, 0L, userId);
tokenMap.get(userId).remove(handle);
if (tokenData.mCallback != null) {
@@ -953,26 +1021,23 @@
private void createSyntheticPasswordBlob(long handle, byte type, AuthenticationToken authToken,
byte[] applicationId, long sid, int userId) {
final byte[] secret;
- if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
+ if (type == SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED
+ || type == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) {
secret = authToken.getEscrowSecret();
} else {
secret = authToken.getSyntheticPassword();
}
byte[] content = createSPBlob(getKeyName(handle), secret, applicationId, sid);
- byte[] blob = new byte[content.length + 1 + 1];
/*
* We can upgrade from v1 to v2 because that's just a change in the way that
* the SP is stored. However, we can't upgrade to v3 because that is a change
* in the way that passwords are derived from the SP.
*/
- if (authToken.mVersion == SYNTHETIC_PASSWORD_VERSION_V3) {
- blob[0] = SYNTHETIC_PASSWORD_VERSION_V3;
- } else {
- blob[0] = SYNTHETIC_PASSWORD_VERSION_V2;
- }
- blob[1] = type;
- System.arraycopy(content, 0, blob, 2, content.length);
- saveState(SP_BLOB_NAME, blob, handle, userId);
+ byte version = authToken.mVersion == SYNTHETIC_PASSWORD_VERSION_V3
+ ? SYNTHETIC_PASSWORD_VERSION_V3 : SYNTHETIC_PASSWORD_VERSION_V2;
+
+ SyntheticPasswordBlob blob = SyntheticPasswordBlob.create(version, type, content);
+ saveState(SP_BLOB_NAME, blob.toByte(), handle, userId);
}
/**
@@ -1089,6 +1154,36 @@
*/
public @NonNull AuthenticationResult unwrapTokenBasedSyntheticPassword(
IGateKeeperService gatekeeper, long handle, byte[] token, int userId) {
+ SyntheticPasswordBlob blob = SyntheticPasswordBlob
+ .fromBytes(loadState(SP_BLOB_NAME, handle, userId));
+ return unwrapTokenBasedSyntheticPasswordInternal(gatekeeper, handle,
+ blob.mType, token, userId);
+ }
+
+ /**
+ * Decrypt a synthetic password by supplying an strong escrow token and corresponding token
+ * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
+ * verification to referesh the SID & Auth token maintained by the system.
+ */
+ public @NonNull AuthenticationResult unwrapStrongTokenBasedSyntheticPassword(
+ IGateKeeperService gatekeeper, long handle, byte[] token, int userId) {
+ return unwrapTokenBasedSyntheticPasswordInternal(gatekeeper, handle,
+ SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED, token, userId);
+ }
+
+ /**
+ * Decrypt a synthetic password by supplying a weak escrow token and corresponding token
+ * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
+ * verification to referesh the SID & Auth token maintained by the system.
+ */
+ public @NonNull AuthenticationResult unwrapWeakTokenBasedSyntheticPassword(
+ IGateKeeperService gatekeeper, long handle, byte[] token, int userId) {
+ return unwrapTokenBasedSyntheticPasswordInternal(gatekeeper, handle,
+ SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED, token, userId);
+ }
+
+ private @NonNull AuthenticationResult unwrapTokenBasedSyntheticPasswordInternal(
+ IGateKeeperService gatekeeper, long handle, byte type, byte[] token, int userId) {
AuthenticationResult result = new AuthenticationResult();
byte[] secdiscardable = loadSecdiscardable(handle, userId);
int slotId = loadWeaverSlot(handle, userId);
@@ -1109,8 +1204,7 @@
PERSONALISATION_WEAVER_TOKEN, secdiscardable);
}
byte[] applicationId = transformUnderSecdiscardable(token, secdiscardable);
- result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED,
- applicationId, 0L, userId);
+ result.authToken = unwrapSyntheticPasswordBlob(handle, type, applicationId, 0L, userId);
if (result.authToken != null) {
result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
if (result.gkResponse == null) {
@@ -1126,33 +1220,33 @@
private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type,
byte[] applicationId, long sid, int userId) {
- byte[] blob = loadState(SP_BLOB_NAME, handle, userId);
- if (blob == null) {
+ byte[] data = loadState(SP_BLOB_NAME, handle, userId);
+ if (data == null) {
return null;
}
- final byte version = blob[0];
- if (version != SYNTHETIC_PASSWORD_VERSION_V3
- && version != SYNTHETIC_PASSWORD_VERSION_V2
- && version != SYNTHETIC_PASSWORD_VERSION_V1) {
+ SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(data);
+ if (blob.mVersion != SYNTHETIC_PASSWORD_VERSION_V3
+ && blob.mVersion != SYNTHETIC_PASSWORD_VERSION_V2
+ && blob.mVersion != SYNTHETIC_PASSWORD_VERSION_V1) {
throw new IllegalArgumentException("Unknown blob version");
}
- if (blob[1] != type) {
+ if (blob.mType != type) {
throw new IllegalArgumentException("Invalid blob type");
}
final byte[] secret;
- if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
- secret = SyntheticPasswordCrypto.decryptBlobV1(getKeyName(handle),
- Arrays.copyOfRange(blob, 2, blob.length), applicationId);
+ if (blob.mVersion == SYNTHETIC_PASSWORD_VERSION_V1) {
+ secret = SyntheticPasswordCrypto.decryptBlobV1(getKeyName(handle), blob.mContent,
+ applicationId);
} else {
- secret = decryptSPBlob(getKeyName(handle),
- Arrays.copyOfRange(blob, 2, blob.length), applicationId);
+ secret = decryptSPBlob(getKeyName(handle), blob.mContent, applicationId);
}
if (secret == null) {
Slog.e(TAG, "Fail to decrypt SP for user " + userId);
return null;
}
- AuthenticationToken result = new AuthenticationToken(version);
- if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
+ AuthenticationToken result = new AuthenticationToken(blob.mVersion);
+ if (type == SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED
+ || type == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) {
if (!loadEscrowData(result, userId)) {
Slog.e(TAG, "User is not escrowable: " + userId);
return null;
@@ -1161,7 +1255,7 @@
} else {
result.recreateDirectly(secret);
}
- if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
+ if (blob.mVersion == SYNTHETIC_PASSWORD_VERSION_V1) {
Slog.i(TAG, "Upgrade v1 SP blob for user " + userId + ", type = " + type);
createSyntheticPasswordBlob(handle, type, result, applicationId, sid, userId);
}
@@ -1233,9 +1327,28 @@
return hasState(SP_BLOB_NAME, handle, userId);
}
+ /** Destroy the escrow token with the given handle for the given user. */
public void destroyTokenBasedSyntheticPassword(long handle, int userId) {
+ SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(loadState(SP_BLOB_NAME, handle,
+ userId));
destroySyntheticPassword(handle, userId);
destroyState(SECDISCARDABLE_NAME, handle, userId);
+ if (blob.mType == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) {
+ notifyWeakEscrowTokenRemovedListeners(handle, userId);
+ }
+ }
+
+ /** Destroy all weak escrow tokens for the given user. */
+ public void destroyAllWeakTokenBasedSyntheticPasswords(int userId) {
+ List<Long> handles = mStorage.listSyntheticPasswordHandlesForUser(SECDISCARDABLE_NAME,
+ userId);
+ for (long handle: handles) {
+ SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(loadState(SP_BLOB_NAME,
+ handle, userId));
+ if (blob.mType == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) {
+ destroyTokenBasedSyntheticPassword(handle, userId);
+ }
+ }
}
public void destroyPasswordBasedSyntheticPassword(long handle, int userId) {
@@ -1285,6 +1398,16 @@
return loadState(SECDISCARDABLE_NAME, handle, userId);
}
+ private byte getTokenBasedBlobType(@TokenType int type) {
+ switch (type) {
+ case TOKEN_TYPE_WEAK:
+ return SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED;
+ case TOKEN_TYPE_STRONG:
+ default:
+ return SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED;
+ }
+ }
+
/**
* Retrieves the saved password metrics associated with a SP handle. Only meaningful to be
* called on the handle of a password-based synthetic password. A valid AuthenticationToken for
@@ -1439,4 +1562,33 @@
}
return success;
}
+
+ /** Register the given IWeakEscrowTokenRemovedListener. */
+ public boolean registerWeakEscrowTokenRemovedListener(
+ IWeakEscrowTokenRemovedListener listener) {
+ return mListeners.register(listener);
+ }
+
+ /** Unregister the given IWeakEscrowTokenRemovedListener. */
+ public boolean unregisterWeakEscrowTokenRemovedListener(
+ IWeakEscrowTokenRemovedListener listener) {
+ return mListeners.unregister(listener);
+ }
+
+ private void notifyWeakEscrowTokenRemovedListeners(long handle, int userId) {
+ int i = mListeners.beginBroadcast();
+ try {
+ while (i > 0) {
+ i--;
+ try {
+ mListeners.getBroadcastItem(i).onWeakEscrowTokenRemoved(handle, userId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception while notifying WeakEscrowTokenRemovedListener.",
+ e);
+ }
+ }
+ } finally {
+ mListeners.finishBroadcast();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
index 03a63b9..8ef42ff 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
@@ -16,11 +16,8 @@
package com.android.server.net;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.Network;
-import android.net.NetworkTemplate;
-import android.net.netstats.provider.NetworkStatsProvider;
import android.os.PowerExemptionManager.ReasonCode;
import android.telephony.SubscriptionPlan;
@@ -56,11 +53,6 @@
*/
public abstract SubscriptionPlan getSubscriptionPlan(Network network);
- /**
- * Return the active {@link SubscriptionPlan} for the given template.
- */
- public abstract SubscriptionPlan getSubscriptionPlan(NetworkTemplate template);
-
public static final int QUOTA_TYPE_JOBS = 1;
public static final int QUOTA_TYPE_MULTIPATH = 2;
@@ -99,13 +91,4 @@
*/
public abstract void setMeteredRestrictedPackagesAsync(
Set<String> packageNames, int userId);
-
- /**
- * Notifies that the specified {@link NetworkStatsProvider} has reached its quota
- * which was set through {@link NetworkStatsProvider#onSetLimit(String, long)} or
- * {@link NetworkStatsProvider#onSetWarningAndLimit(String, long, long)}.
- *
- * @param tag the human readable identifier of the custom network stats provider.
- */
- public abstract void onStatsProviderWarningOrLimitReached(@NonNull String tag);
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 5660951..8b1416a 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -3363,6 +3363,35 @@
return result;
}
+ /**
+ * Get subscription plan for the given networkTemplate.
+ *
+ * @param template the networkTemplate to get the subscription plan for.
+ */
+ @Override
+ public SubscriptionPlan getSubscriptionPlan(@NonNull NetworkTemplate template) {
+ enforceAnyPermissionOf(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+ synchronized (mNetworkPoliciesSecondLock) {
+ final int subId = findRelevantSubIdNL(template);
+ return getPrimarySubscriptionPlanLocked(subId);
+ }
+ }
+
+ /**
+ * Notifies that the specified {@link NetworkStatsProvider} has reached its quota
+ * which was set through {@link NetworkStatsProvider#onSetLimit(String, long)} or
+ * {@link NetworkStatsProvider#onSetWarningAndLimit(String, long, long)}.
+ */
+ @Override
+ public void onStatsProviderWarningOrLimitReached() {
+ enforceAnyPermissionOf(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+ // This API may be called before the system is ready.
+ synchronized (mNetworkPoliciesSecondLock) {
+ if (!mSystemReady) return;
+ }
+ mHandler.obtainMessage(MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED).sendToTarget();
+ }
+
@Override
public SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage) {
enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage);
@@ -5583,14 +5612,6 @@
}
@Override
- public SubscriptionPlan getSubscriptionPlan(NetworkTemplate template) {
- synchronized (mNetworkPoliciesSecondLock) {
- final int subId = findRelevantSubIdNL(template);
- return getPrimarySubscriptionPlanLocked(subId);
- }
- }
-
- @Override
public long getSubscriptionOpportunisticQuota(Network network, int quotaType) {
final long quotaBytes;
synchronized (mNetworkPoliciesSecondLock) {
@@ -5632,12 +5653,6 @@
mHandler.obtainMessage(MSG_METERED_RESTRICTED_PACKAGES_CHANGED,
userId, 0, packageNames).sendToTarget();
}
-
- @Override
- public void onStatsProviderWarningOrLimitReached(@NonNull String tag) {
- Log.v(TAG, "onStatsProviderWarningOrLimitReached: " + tag);
- mHandler.obtainMessage(MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED).sendToTarget();
- }
}
private void setMeteredRestrictedPackagesInternal(Set<String> packageNames, int userId) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 399ae53..86b385b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2657,16 +2657,20 @@
}
private void sendAppBlockStateChangedBroadcast(String pkg, int uid, boolean blocked) {
- try {
- getContext().sendBroadcastAsUser(
- new Intent(ACTION_APP_BLOCK_STATE_CHANGED)
- .putExtra(NotificationManager.EXTRA_BLOCKED_STATE, blocked)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- .setPackage(pkg),
- UserHandle.of(UserHandle.getUserId(uid)), null);
- } catch (SecurityException e) {
- Slog.w(TAG, "Can't notify app about app block change", e);
- }
+ // From Android T, revoking the notification permission will cause the app to be killed.
+ // delay this broadcast so it doesn't race with that process death
+ mHandler.postDelayed(() -> {
+ try {
+ getContext().sendBroadcastAsUser(
+ new Intent(ACTION_APP_BLOCK_STATE_CHANGED)
+ .putExtra(NotificationManager.EXTRA_BLOCKED_STATE, blocked)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .setPackage(pkg),
+ UserHandle.of(UserHandle.getUserId(uid)), null);
+ } catch (SecurityException e) {
+ Slog.w(TAG, "Can't notify app about app block change", e);
+ }
+ }, 500);
}
@Override
@@ -3094,6 +3098,13 @@
if (mPreferencesHelper.setValidMessageSent(
r.getSbn().getPackageName(), r.getUid())) {
handleSavePolicyFile();
+ } else if (r.getNotification().getBubbleMetadata() != null) {
+ // If bubble metadata is present it is valid (if invalid it's removed
+ // via BubbleExtractor).
+ if (mPreferencesHelper.setValidBubbleSent(
+ r.getSbn().getPackageName(), r.getUid())) {
+ handleSavePolicyFile();
+ }
}
} else {
if (mPreferencesHelper.setInvalidMessageSent(
@@ -3597,6 +3608,12 @@
}
@Override
+ public boolean hasSentValidBubble(String pkg, int uid) {
+ checkCallerIsSystem();
+ return mPreferencesHelper.hasSentValidBubble(pkg, uid);
+ }
+
+ @Override
public void setNotificationDelegate(String callingPkg, String delegate) {
checkCallerIsSameApp(callingPkg);
final int callingUid = Binder.getCallingUid();
@@ -6674,31 +6691,33 @@
// package or a registered listener can enqueue. Prevents DOS attacks and deals with leaks.
if (!isSystemNotification && !isNotificationFromListener) {
final int callingUid = Binder.getCallingUid();
- if (mNotificationsByKey.get(r.getSbn().getKey()) == null
- && isCallerInstantApp(callingUid, userId)) {
- // Ephemeral apps have some special constraints for notifications.
- // They are not allowed to create new notifications however they are allowed to
- // update notifications created by the system (e.g. a foreground service
- // notification).
- throw new SecurityException("Instant app " + pkg
- + " cannot create notifications");
- }
+ synchronized (mNotificationLock) {
+ if (mNotificationsByKey.get(r.getSbn().getKey()) == null
+ && isCallerInstantApp(callingUid, userId)) {
+ // Ephemeral apps have some special constraints for notifications.
+ // They are not allowed to create new notifications however they are allowed to
+ // update notifications created by the system (e.g. a foreground service
+ // notification).
+ throw new SecurityException("Instant app " + pkg
+ + " cannot create notifications");
+ }
- // rate limit updates that aren't completed progress notifications
- if (mNotificationsByKey.get(r.getSbn().getKey()) != null
- && !r.getNotification().hasCompletedProgress()
- && !isAutogroup) {
+ // rate limit updates that aren't completed progress notifications
+ if (mNotificationsByKey.get(r.getSbn().getKey()) != null
+ && !r.getNotification().hasCompletedProgress()
+ && !isAutogroup) {
- final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
- if (appEnqueueRate > mMaxPackageEnqueueRate) {
- mUsageStats.registerOverRateQuota(pkg);
- final long now = SystemClock.elapsedRealtime();
- if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
- Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate
- + ". Shedding " + r.getSbn().getKey() + ". package=" + pkg);
- mLastOverRateLogTime = now;
+ final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
+ if (appEnqueueRate > mMaxPackageEnqueueRate) {
+ mUsageStats.registerOverRateQuota(pkg);
+ final long now = SystemClock.elapsedRealtime();
+ if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
+ Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate
+ + ". Shedding " + r.getSbn().getKey() + ". package=" + pkg);
+ mLastOverRateLogTime = now;
+ }
+ return false;
}
- return false;
}
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 0246c0c..5e333da 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -808,6 +808,23 @@
}
}
+ /** Sets whether this package has sent a notification with valid bubble metadata. */
+ public boolean setValidBubbleSent(String packageName, int uid) {
+ synchronized (mPackagePreferences) {
+ PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
+ boolean valueChanged = !r.hasSentValidBubble;
+ r.hasSentValidBubble = true;
+ return valueChanged;
+ }
+ }
+
+ boolean hasSentValidBubble(String packageName, int uid) {
+ synchronized (mPackagePreferences) {
+ PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
+ return r.hasSentValidBubble;
+ }
+ }
+
@Override
public boolean isGroupBlocked(String packageName, int uid, String groupId) {
if (groupId == null) {
@@ -2816,8 +2833,9 @@
boolean hasSentInvalidMessage = false;
boolean hasSentValidMessage = false;
- // notE: only valid while hasSentMessage is false and hasSentInvalidMessage is true
+ // note: only valid while hasSentMessage is false and hasSentInvalidMessage is true
boolean userDemotedMsgApp = false;
+ boolean hasSentValidBubble = false;
Delegate delegate = null;
ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java
index 8acc857..54dd113 100644
--- a/services/core/java/com/android/server/notification/VibratorHelper.java
+++ b/services/core/java/com/android/server/notification/VibratorHelper.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.media.AudioAttributes;
import android.os.Process;
import android.os.VibrationAttributes;
@@ -39,18 +40,16 @@
private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
private static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps
- private static final int CHIRP_LEVEL_DURATION_MILLIS = 100;
- private static final int DEFAULT_CHIRP_RAMP_DURATION_MILLIS = 100;
- private static final int FALLBACK_CHIRP_RAMP_DURATION_MILLIS = 50;
private final Vibrator mVibrator;
private final long[] mDefaultPattern;
private final long[] mFallbackPattern;
+ @Nullable private final float[] mDefaultPwlePattern;
+ @Nullable private final float[] mFallbackPwlePattern;
public VibratorHelper(Context context) {
mVibrator = context.getSystemService(Vibrator.class);
- mDefaultPattern = getLongArray(
- context.getResources(),
+ mDefaultPattern = getLongArray(context.getResources(),
com.android.internal.R.array.config_defaultNotificationVibePattern,
VIBRATE_PATTERN_MAXLEN,
DEFAULT_VIBRATE_PATTERN);
@@ -58,6 +57,10 @@
R.array.config_notificationFallbackVibePattern,
VIBRATE_PATTERN_MAXLEN,
DEFAULT_VIBRATE_PATTERN);
+ mDefaultPwlePattern = getFloatArray(context.getResources(),
+ com.android.internal.R.array.config_defaultNotificationVibeWaveform);
+ mFallbackPwlePattern = getFloatArray(context.getResources(),
+ com.android.internal.R.array.config_notificationFallbackVibeWaveform);
}
/**
@@ -83,6 +86,52 @@
}
/**
+ * Safely create a {@link VibrationEffect} from given waveform description.
+ *
+ * <p>The waveform is described by a sequence of values for target amplitude, frequency and
+ * duration, that are forwarded to
+ * {@link VibrationEffect.WaveformBuilder#addRamp(float, float, int)}.
+ *
+ * <p>This method returns {@code null} if the pattern is also {@code null} or invalid.
+ *
+ * @param values The list of values describing the waveform as a sequence of target amplitude,
+ * frequency and duration.
+ * @param insistent {@code true} if the vibration should loop until it is cancelled.
+ */
+ @Nullable
+ public static VibrationEffect createPwleWaveformVibration(@Nullable float[] values,
+ boolean insistent) {
+ try {
+ if (values == null) {
+ return null;
+ }
+
+ int length = values.length;
+ // The waveform is described by triples (amplitude, frequency, duration)
+ if ((length == 0) || (length % 3 != 0)) {
+ return null;
+ }
+
+ VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform();
+ for (int i = 0; i < length; i += 3) {
+ waveformBuilder.addRamp(
+ /* amplitude= */ values[i],
+ /* frequencyHz= */ values[i + 1],
+ /* duration= */ (int) values[i + 2]);
+ }
+
+ if (insistent) {
+ return waveformBuilder.build(/* repeat= */ 0);
+ }
+ return waveformBuilder.build();
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Error creating vibration PWLE waveform with pattern: "
+ + Arrays.toString(values));
+ }
+ return null;
+ }
+
+ /**
* Vibrate the device with given {@code effect}.
*
* <p>We need to vibrate as "android" so we can breakthrough DND.
@@ -106,7 +155,10 @@
*/
public VibrationEffect createFallbackVibration(boolean insistent) {
if (mVibrator.hasFrequencyControl()) {
- return createChirpVibration(FALLBACK_CHIRP_RAMP_DURATION_MILLIS, insistent);
+ VibrationEffect effect = createPwleWaveformVibration(mFallbackPwlePattern, insistent);
+ if (effect != null) {
+ return effect;
+ }
}
return createWaveformVibration(mFallbackPattern, insistent);
}
@@ -118,29 +170,29 @@
*/
public VibrationEffect createDefaultVibration(boolean insistent) {
if (mVibrator.hasFrequencyControl()) {
- return createChirpVibration(DEFAULT_CHIRP_RAMP_DURATION_MILLIS, insistent);
+ VibrationEffect effect = createPwleWaveformVibration(mDefaultPwlePattern, insistent);
+ if (effect != null) {
+ return effect;
+ }
}
return createWaveformVibration(mDefaultPattern, insistent);
}
- private static VibrationEffect createChirpVibration(int rampDuration, boolean insistent) {
- VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform()
- .addStep(/* amplitude= */ 0, /* frequencyHz= */ 60f, /* duration= */ 0)
- .addRamp(/* amplitude= */ 1, /* frequencyHz= */ 120f, rampDuration)
- .addStep(/* amplitude= */ 1, /* frequencyHz= */ 120f, CHIRP_LEVEL_DURATION_MILLIS)
- .addRamp(/* amplitude= */ 0, /* frequencyHz= */ 60f, rampDuration);
-
- if (insistent) {
- return waveformBuilder
- .addStep(/* amplitude= */ 0, CHIRP_LEVEL_DURATION_MILLIS)
- .build(/* repeat= */ 0);
+ @Nullable
+ private static float[] getFloatArray(Resources resources, int resId) {
+ TypedArray array = resources.obtainTypedArray(resId);
+ try {
+ float[] values = new float[array.length()];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = array.getFloat(i, Float.NaN);
+ if (Float.isNaN(values[i])) {
+ return null;
+ }
+ }
+ return values;
+ } finally {
+ array.recycle();
}
-
- VibrationEffect singleBeat = waveformBuilder.build();
- return VibrationEffect.startComposition()
- .addEffect(singleBeat)
- .addEffect(singleBeat, /* delay= */ CHIRP_LEVEL_DURATION_MILLIS)
- .compose();
}
private static long[] getLongArray(Resources resources, int resId, int maxLength, long[] def) {
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index c285e27..a1c97a8 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -413,9 +413,11 @@
throws PackageManagerException;
/**
- * Get a map of system services defined in an apex mapped to the jar files they reside in.
+ * Get a list of apex system services implemented in an apex.
+ *
+ * <p>The list is sorted by initOrder for consistency.
*/
- public abstract Map<String, String> getApexSystemServices();
+ public abstract List<ApexSystemServiceInfo> getApexSystemServices();
/**
* Dumps various state information to the provided {@link PrintWriter} object.
@@ -448,7 +450,7 @@
* Map of all apex system services to the jar files they are contained in.
*/
@GuardedBy("mLock")
- private Map<String, String> mApexSystemServices = new ArrayMap<>();
+ private List<ApexSystemServiceInfo> mApexSystemServices = new ArrayList<>();
/**
* Contains the list of {@code packageName}s of apks-in-apex for given
@@ -604,14 +606,19 @@
}
String name = service.getName();
- if (mApexSystemServices.containsKey(name)) {
- throw new IllegalStateException(String.format(
- "Duplicate apex-system-service %s from %s, %s",
- name, mApexSystemServices.get(name), service.getJarPath()));
+ for (ApexSystemServiceInfo info : mApexSystemServices) {
+ if (info.getName().equals(name)) {
+ throw new IllegalStateException(String.format(
+ "Duplicate apex-system-service %s from %s, %s",
+ name, info.mJarPath, service.getJarPath()));
+ }
}
- mApexSystemServices.put(name, service.getJarPath());
+ ApexSystemServiceInfo info = new ApexSystemServiceInfo(
+ service.getName(), service.getJarPath(), service.getInitOrder());
+ mApexSystemServices.add(info);
}
+ Collections.sort(mApexSystemServices);
mPackageNameToApexModuleName.put(packageInfo.packageName, ai.moduleName);
if (ai.isActive) {
if (activePackagesSet.contains(packageInfo.packageName)) {
@@ -1132,7 +1139,7 @@
}
@Override
- public Map<String, String> getApexSystemServices() {
+ public List<ApexSystemServiceInfo> getApexSystemServices() {
synchronized (mLock) {
Preconditions.checkState(mApexSystemServices != null,
"APEX packages have not been scanned");
@@ -1418,10 +1425,10 @@
}
@Override
- public Map<String, String> getApexSystemServices() {
+ public List<ApexSystemServiceInfo> getApexSystemServices() {
// TODO(satayev): we can't really support flattened apex use case, and need to migrate
// the manifest entries into system's manifest asap.
- return Collections.emptyMap();
+ return Collections.emptyList();
}
@Override
diff --git a/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java b/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java
new file mode 100644
index 0000000..f75ba6d
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java
@@ -0,0 +1,58 @@
+/*
+ * 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.pm;
+
+import android.annotation.Nullable;
+
+/**
+ * A helper class that contains information about apex-system-service to be used within system
+ * server process.
+ */
+public final class ApexSystemServiceInfo implements Comparable<ApexSystemServiceInfo> {
+
+ final String mName;
+ @Nullable
+ final String mJarPath;
+ final int mInitOrder;
+
+ public ApexSystemServiceInfo(String name, String jarPath, int initOrder) {
+ this.mName = name;
+ this.mJarPath = jarPath;
+ this.mInitOrder = initOrder;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getJarPath() {
+ return mJarPath;
+ }
+
+ public int getInitOrder() {
+ return mInitOrder;
+ }
+
+ @Override
+ public int compareTo(ApexSystemServiceInfo other) {
+ if (mInitOrder == other.mInitOrder) {
+ return mName.compareTo(other.mName);
+ }
+ // higher initOrder values take precedence
+ return -Integer.compare(mInitOrder, other.mInitOrder);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index 62db886..b307984 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.MANAGE_APP_OPS_MODES;
+import static android.Manifest.permission.START_CROSS_PROFILE_ACTIVITIES;
import static android.app.AppOpsManager.OP_INTERACT_ACROSS_PROFILES;
import static android.content.Intent.FLAG_RECEIVER_REGISTERED_ONLY;
import static android.content.pm.CrossProfileApps.ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED;
@@ -154,17 +155,15 @@
// must have the required permission and the users must be in the same profile group
// in order to launch any of its own activities.
if (callerUserId != userId) {
- final int permissionFlag = PermissionChecker.checkPermissionForPreflight(
- mContext,
- INTERACT_ACROSS_PROFILES,
- callingPid,
- callingUid,
- callingPackage);
- if (permissionFlag != PermissionChecker.PERMISSION_GRANTED
- || !isSameProfileGroup(callerUserId, userId)) {
- throw new SecurityException("Attempt to launch activity without required "
- + INTERACT_ACROSS_PROFILES
- + " permission or target user is not in the same profile group.");
+ if (!hasInteractAcrossProfilesPermission(callingPackage, callingUid, callingPid)
+ && !isPermissionGranted(START_CROSS_PROFILE_ACTIVITIES, callingUid)) {
+ throw new SecurityException("Attempt to launch activity without one of the"
+ + " required " + INTERACT_ACROSS_PROFILES + " or "
+ + START_CROSS_PROFILE_ACTIVITIES + " permissions.");
+ }
+ if (!isSameProfileGroup(callerUserId, userId)) {
+ throw new SecurityException("Attempt to launch activity when target user is"
+ + " not in the same profile group.");
}
}
launchIntent.setComponent(component);
diff --git a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
similarity index 61%
rename from services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
rename to services/core/java/com/android/server/pm/InitAppsHelper.java
index dfa6c66..a5e6d6f 100644
--- a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -31,6 +31,7 @@
import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS;
import static com.android.server.pm.PackageManagerService.TAG;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.parsing.ParsingPackageUtils;
import android.os.Environment;
@@ -59,14 +60,25 @@
* further cleanup and eventually all the installation/scanning related logic will go to another
* class.
*/
-final class InitAndSystemPackageHelper {
+final class InitAppsHelper {
private final PackageManagerService mPm;
-
private final List<ScanPartition> mDirsToScanAsSystem;
private final int mScanFlags;
private final int mSystemParseFlags;
private final int mSystemScanFlags;
private final InstallPackageHelper mInstallPackageHelper;
+ private final ApexManager mApexManager;
+ private final PackageParser2 mPackageParser;
+ private final ExecutorService mExecutorService;
+ /* Tracks how long system scan took */
+ private long mSystemScanTime;
+ /* Track of the number of cached system apps */
+ private int mCachedSystemApps;
+ /* Track of the number of system apps */
+ private int mSystemPackagesCount;
+ private final boolean mIsDeviceUpgrading;
+ private final boolean mIsOnlyCoreApps;
+ private final List<ScanPartition> mSystemPartitions;
/**
* Tracks new system packages [received in an OTA] that we expect to
@@ -74,26 +86,39 @@
* are package location.
*/
private final ArrayMap<String, File> mExpectingBetter = new ArrayMap<>();
+ /* Tracks of any system packages that no longer exist that needs to be pruned. */
+ private final List<String> mPossiblyDeletedUpdatedSystemApps = new ArrayList<>();
+ // Tracks of stub packages that must either be replaced with full versions in the /data
+ // partition or be disabled.
+ private final List<String> mStubSystemApps = new ArrayList<>();
// TODO(b/198166813): remove PMS dependency
- InitAndSystemPackageHelper(PackageManagerService pm) {
+ InitAppsHelper(PackageManagerService pm, ApexManager apexManager,
+ InstallPackageHelper installPackageHelper, PackageParser2 packageParser,
+ List<ScanPartition> systemPartitions) {
mPm = pm;
- mInstallPackageHelper = new InstallPackageHelper(pm);
+ mApexManager = apexManager;
+ mInstallPackageHelper = installPackageHelper;
+ mPackageParser = packageParser;
+ mSystemPartitions = systemPartitions;
mDirsToScanAsSystem = getSystemScanPartitions();
+ mIsDeviceUpgrading = mPm.isDeviceUpgrading();
+ mIsOnlyCoreApps = mPm.isOnlyCoreApps();
// Set flag to monitor and not change apk file paths when scanning install directories.
int scanFlags = SCAN_BOOTING | SCAN_INITIAL;
- if (mPm.isDeviceUpgrading() || mPm.isFirstBoot()) {
+ if (mIsDeviceUpgrading || mPm.isFirstBoot()) {
mScanFlags = scanFlags | SCAN_FIRST_BOOT_OR_UPGRADE;
} else {
mScanFlags = scanFlags;
}
mSystemParseFlags = mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
mSystemScanFlags = scanFlags | SCAN_AS_SYSTEM;
+ mExecutorService = ParallelPackageParser.makeExecutorService();
}
private List<ScanPartition> getSystemScanPartitions() {
final List<ScanPartition> scanPartitions = new ArrayList<>();
- scanPartitions.addAll(mPm.mInjector.getSystemPartitions());
+ scanPartitions.addAll(mSystemPartitions);
scanPartitions.addAll(getApexScanPartitions());
Slog.d(TAG, "Directories scanned as system partitions: " + scanPartitions);
return scanPartitions;
@@ -101,8 +126,7 @@
private List<ScanPartition> getApexScanPartitions() {
final List<ScanPartition> scanPartitions = new ArrayList<>();
- final List<ApexManager.ActiveApexInfo> activeApexInfos =
- mPm.mApexManager.getActiveApexInfos();
+ final List<ApexManager.ActiveApexInfo> activeApexInfos = mApexManager.getActiveApexInfos();
for (int i = 0; i < activeApexInfos.size(); i++) {
final ScanPartition scanPartition = resolveApexToScanPartition(activeApexInfos.get(i));
if (scanPartition != null) {
@@ -119,116 +143,133 @@
if (apexInfo.preInstalledApexPath.getAbsolutePath().equals(
sp.getFolder().getAbsolutePath())
|| apexInfo.preInstalledApexPath.getAbsolutePath().startsWith(
- sp.getFolder().getAbsolutePath() + File.separator)) {
+ sp.getFolder().getAbsolutePath() + File.separator)) {
return new ScanPartition(apexInfo.apexDirectory, sp, SCAN_AS_APK_IN_APEX);
}
}
return null;
}
- public OverlayConfig initPackages(
- WatchedArrayMap<String, PackageSetting> packageSettings, int[] userIds,
- long startTime) {
- PackageParser2 packageParser = mPm.mInjector.getScanningCachingPackageParser();
-
- ExecutorService executorService = ParallelPackageParser.makeExecutorService();
+ /**
+ * Install apps from system dirs.
+ */
+ @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
+ public OverlayConfig initSystemApps(WatchedArrayMap<String, PackageSetting> packageSettings,
+ int[] userIds, long startTime) {
// Prepare apex package info before scanning APKs, this information is needed when
// scanning apk in apex.
- mPm.mApexManager.scanApexPackagesTraced(packageParser, executorService);
+ mApexManager.scanApexPackagesTraced(mPackageParser, mExecutorService);
- scanSystemDirs(packageParser, executorService);
+ scanSystemDirs(mPackageParser, mExecutorService);
// Parse overlay configuration files to set default enable state, mutability, and
// priority of system overlays.
final ArrayMap<String, File> apkInApexPreInstalledPaths = new ArrayMap<>();
- for (ApexManager.ActiveApexInfo apexInfo : mPm.mApexManager.getActiveApexInfos()) {
- for (String packageName : mPm.mApexManager.getApksInApex(apexInfo.apexModuleName)) {
+ for (ApexManager.ActiveApexInfo apexInfo : mApexManager.getActiveApexInfos()) {
+ for (String packageName : mApexManager.getApksInApex(apexInfo.apexModuleName)) {
apkInApexPreInstalledPaths.put(packageName, apexInfo.preInstalledApexPath);
}
}
- OverlayConfig overlayConfig = OverlayConfig.initializeSystemInstance(
+ final OverlayConfig overlayConfig = OverlayConfig.initializeSystemInstance(
consumer -> mPm.forEachPackage(
pkg -> consumer.accept(pkg, pkg.isSystem(),
- apkInApexPreInstalledPaths.get(pkg.getPackageName()))));
- // Prune any system packages that no longer exist.
- final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<>();
- // Stub packages must either be replaced with full versions in the /data
- // partition or be disabled.
- final List<String> stubSystemApps = new ArrayList<>();
+ apkInApexPreInstalledPaths.get(pkg.getPackageName()))));
- if (!mPm.isOnlyCoreApps()) {
+ if (!mIsOnlyCoreApps) {
// do this first before mucking with mPackages for the "expecting better" case
- updateStubSystemAppsList(stubSystemApps);
+ updateStubSystemAppsList(mStubSystemApps);
mInstallPackageHelper.prepareSystemPackageCleanUp(packageSettings,
- possiblyDeletedUpdatedSystemApps, mExpectingBetter, userIds);
+ mPossiblyDeletedUpdatedSystemApps, mExpectingBetter, userIds);
}
- final int cachedSystemApps = PackageCacher.sCachedPackageReadCount.get();
+ logSystemAppsScanningTime(startTime);
+ return overlayConfig;
+ }
+
+ @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
+ private void logSystemAppsScanningTime(long startTime) {
+ mCachedSystemApps = PackageCacher.sCachedPackageReadCount.get();
// Remove any shared userIDs that have no associated packages
mPm.mSettings.pruneSharedUsersLPw();
- final long systemScanTime = SystemClock.uptimeMillis() - startTime;
- final int systemPackagesCount = mPm.mPackages.size();
- Slog.i(TAG, "Finished scanning system apps. Time: " + systemScanTime
- + " ms, packageCount: " + systemPackagesCount
+ mSystemScanTime = SystemClock.uptimeMillis() - startTime;
+ mSystemPackagesCount = mPm.mPackages.size();
+ Slog.i(TAG, "Finished scanning system apps. Time: " + mSystemScanTime
+ + " ms, packageCount: " + mSystemPackagesCount
+ " , timePerPackage: "
- + (systemPackagesCount == 0 ? 0 : systemScanTime / systemPackagesCount)
- + " , cached: " + cachedSystemApps);
- if (mPm.isDeviceUpgrading() && systemPackagesCount > 0) {
+ + (mSystemPackagesCount == 0 ? 0 : mSystemScanTime / mSystemPackagesCount)
+ + " , cached: " + mCachedSystemApps);
+ if (mIsDeviceUpgrading && mSystemPackagesCount > 0) {
//CHECKSTYLE:OFF IndentationCheck
FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_DURATION_REPORTED,
BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_SYSTEM_APP_AVG_SCAN_TIME,
- systemScanTime / systemPackagesCount);
+ mSystemScanTime / mSystemPackagesCount);
//CHECKSTYLE:ON IndentationCheck
}
+ }
- if (!mPm.isOnlyCoreApps()) {
+ /**
+ * Install apps/updates from data dir and fix system apps that are affected.
+ */
+ @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
+ public void initNonSystemApps(@NonNull int[] userIds, long startTime) {
+ if (!mIsOnlyCoreApps) {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
SystemClock.uptimeMillis());
- scanDirTracedLI(mPm.getAppInstallDir(), 0, mScanFlags | SCAN_REQUIRE_KNOWN, 0,
- packageParser, executorService);
+ scanDirTracedLI(mPm.getAppInstallDir(), 0, mScanFlags | SCAN_REQUIRE_KNOWN,
+ mPackageParser, mExecutorService);
}
- List<Runnable> unfinishedTasks = executorService.shutdownNow();
+ List<Runnable> unfinishedTasks = mExecutorService.shutdownNow();
if (!unfinishedTasks.isEmpty()) {
throw new IllegalStateException("Not all tasks finished before calling close: "
+ unfinishedTasks);
}
-
- if (!mPm.isOnlyCoreApps()) {
- mInstallPackageHelper.cleanupDisabledPackageSettings(possiblyDeletedUpdatedSystemApps,
- userIds, mScanFlags);
- mInstallPackageHelper.checkExistingBetterPackages(mExpectingBetter,
- stubSystemApps, mSystemScanFlags, mSystemParseFlags);
-
- // Uncompress and install any stubbed system applications.
- // This must be done last to ensure all stubs are replaced or disabled.
- mInstallPackageHelper.installSystemStubPackages(stubSystemApps, mScanFlags);
-
- final int cachedNonSystemApps = PackageCacher.sCachedPackageReadCount.get()
- - cachedSystemApps;
-
- final long dataScanTime = SystemClock.uptimeMillis() - systemScanTime - startTime;
- final int dataPackagesCount = mPm.mPackages.size() - systemPackagesCount;
- Slog.i(TAG, "Finished scanning non-system apps. Time: " + dataScanTime
- + " ms, packageCount: " + dataPackagesCount
- + " , timePerPackage: "
- + (dataPackagesCount == 0 ? 0 : dataScanTime / dataPackagesCount)
- + " , cached: " + cachedNonSystemApps);
- if (mPm.isDeviceUpgrading() && dataPackagesCount > 0) {
- //CHECKSTYLE:OFF IndentationCheck
- FrameworkStatsLog.write(
- FrameworkStatsLog.BOOT_TIME_EVENT_DURATION_REPORTED,
- BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_DATA_APP_AVG_SCAN_TIME,
- dataScanTime / dataPackagesCount);
- //CHECKSTYLE:OFF IndentationCheck
- }
+ if (!mIsOnlyCoreApps) {
+ fixSystemPackages(userIds);
+ logNonSystemAppScanningTime(startTime);
}
mExpectingBetter.clear();
-
mPm.mSettings.pruneRenamedPackagesLPw();
- packageParser.close();
- return overlayConfig;
+ mPackageParser.close();
+ }
+
+ /**
+ * Clean up system packages now that some system package updates have been installed from
+ * the data dir. Also install system stub packages as the last step.
+ */
+ @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
+ private void fixSystemPackages(@NonNull int[] userIds) {
+ mInstallPackageHelper.cleanupDisabledPackageSettings(mPossiblyDeletedUpdatedSystemApps,
+ userIds, mScanFlags);
+ mInstallPackageHelper.checkExistingBetterPackages(mExpectingBetter,
+ mStubSystemApps, mSystemScanFlags, mSystemParseFlags);
+
+ // Uncompress and install any stubbed system applications.
+ // This must be done last to ensure all stubs are replaced or disabled.
+ mInstallPackageHelper.installSystemStubPackages(mStubSystemApps, mScanFlags);
+ }
+
+ @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
+ private void logNonSystemAppScanningTime(long startTime) {
+ final int cachedNonSystemApps = PackageCacher.sCachedPackageReadCount.get()
+ - mCachedSystemApps;
+
+ final long dataScanTime = SystemClock.uptimeMillis() - mSystemScanTime - startTime;
+ final int dataPackagesCount = mPm.mPackages.size() - mSystemPackagesCount;
+ Slog.i(TAG, "Finished scanning non-system apps. Time: " + dataScanTime
+ + " ms, packageCount: " + dataPackagesCount
+ + " , timePerPackage: "
+ + (dataPackagesCount == 0 ? 0 : dataScanTime / dataPackagesCount)
+ + " , cached: " + cachedNonSystemApps);
+ if (mIsDeviceUpgrading && dataPackagesCount > 0) {
+ //CHECKSTYLE:OFF IndentationCheck
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.BOOT_TIME_EVENT_DURATION_REPORTED,
+ BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_DATA_APP_AVG_SCAN_TIME,
+ dataScanTime / dataPackagesCount);
+ //CHECKSTYLE:OFF IndentationCheck
+ }
}
/**
@@ -248,12 +289,12 @@
continue;
}
scanDirTracedLI(partition.getOverlayFolder(), mSystemParseFlags,
- mSystemScanFlags | partition.scanFlag, 0,
+ mSystemScanFlags | partition.scanFlag,
packageParser, executorService);
}
scanDirTracedLI(frameworkDir, mSystemParseFlags,
- mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0,
+ mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED,
packageParser, executorService);
if (!mPm.mPackages.containsKey("android")) {
throw new IllegalStateException(
@@ -264,11 +305,11 @@
final ScanPartition partition = mDirsToScanAsSystem.get(i);
if (partition.getPrivAppFolder() != null) {
scanDirTracedLI(partition.getPrivAppFolder(), mSystemParseFlags,
- mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0,
+ mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag,
packageParser, executorService);
}
scanDirTracedLI(partition.getAppFolder(), mSystemParseFlags,
- mSystemScanFlags | partition.scanFlag, 0,
+ mSystemScanFlags | partition.scanFlag,
packageParser, executorService);
}
}
@@ -286,11 +327,11 @@
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags,
- long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
+ PackageParser2 packageParser, ExecutorService executorService) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
try {
mInstallPackageHelper.installPackagesFromDir(scanDir, parseFlags, scanFlags,
- currentTime, packageParser, executorService);
+ packageParser, executorService);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 9302aad..d002c26 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -91,7 +91,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.app.AppOpsManager;
import android.app.ApplicationPackageManager;
import android.app.backup.IBackupManager;
import android.content.ContentResolver;
@@ -195,38 +194,32 @@
private final AppDataHelper mAppDataHelper;
private final BroadcastHelper mBroadcastHelper;
private final RemovePackageHelper mRemovePackageHelper;
- private final StorageManager mStorageManager;
- private final RollbackManagerInternal mRollbackManager;
private final IncrementalManager mIncrementalManager;
private final ApexManager mApexManager;
private final DexManager mDexManager;
private final ArtManagerService mArtManagerService;
- private final AppOpsManager mAppOpsManager;
private final Context mContext;
private final PackageDexOptimizer mPackageDexOptimizer;
private final PackageAbiHelper mPackageAbiHelper;
private final ViewCompiler mViewCompiler;
- private final IBackupManager mIBackupManager;
private final SharedLibrariesImpl mSharedLibraries;
+ private final PackageManagerServiceInjector mInjector;
// TODO(b/198166813): remove PMS dependency
InstallPackageHelper(PackageManagerService pm, AppDataHelper appDataHelper) {
mPm = pm;
+ mInjector = pm.mInjector;
mAppDataHelper = appDataHelper;
mBroadcastHelper = new BroadcastHelper(pm.mInjector);
mRemovePackageHelper = new RemovePackageHelper(pm);
- mStorageManager = pm.mInjector.getSystemService(StorageManager.class);
- mRollbackManager = pm.mInjector.getLocalService(RollbackManagerInternal.class);
mIncrementalManager = pm.mInjector.getIncrementalManager();
mApexManager = pm.mInjector.getApexManager();
mDexManager = pm.mInjector.getDexManager();
mArtManagerService = pm.mInjector.getArtManagerService();
- mAppOpsManager = pm.mInjector.getSystemService(AppOpsManager.class);
mContext = pm.mInjector.getContext();
mPackageDexOptimizer = pm.mInjector.getPackageDexOptimizer();
mPackageAbiHelper = pm.mInjector.getAbiHelper();
mViewCompiler = pm.mInjector.getViewCompiler();
- mIBackupManager = pm.mInjector.getIBackupManager();
mSharedLibraries = pm.mInjector.getSharedLibrariesImpl();
}
@@ -693,7 +686,8 @@
* Returns whether the restore successfully completed.
*/
private boolean performBackupManagerRestore(int userId, int token, PackageInstalledInfo res) {
- if (mIBackupManager != null) {
+ IBackupManager iBackupManager = mInjector.getIBackupManager();
+ if (iBackupManager != null) {
// For backwards compatibility as USER_ALL previously routed directly to USER_SYSTEM
// in the BackupManager. USER_ALL is used in compatibility tests.
if (userId == UserHandle.USER_ALL) {
@@ -704,8 +698,8 @@
}
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "restore", token);
try {
- if (mIBackupManager.isUserReadyForBackup(userId)) {
- mIBackupManager.restoreAtInstallForUser(
+ if (iBackupManager.isUserReadyForBackup(userId)) {
+ iBackupManager.restoreAtInstallForUser(
userId, res.mPkg.getPackageName(), token);
} else {
Slog.w(TAG, "User " + userId + " is not ready. Restore at install "
@@ -756,7 +750,9 @@
if (ps != null && doSnapshotOrRestore) {
final String seInfo = AndroidPackageUtils.getSeInfo(res.mPkg, ps);
- mRollbackManager.snapshotAndRestoreUserData(packageName,
+ final RollbackManagerInternal rollbackManager =
+ mInjector.getLocalService(RollbackManagerInternal.class);
+ rollbackManager.snapshotAndRestoreUserData(packageName,
UserHandle.toUserHandles(installedUsers), appId, ceDataInode, seInfo, token);
return true;
}
@@ -1969,14 +1965,15 @@
reconciledPkg.mPrepareResult.mExistingPackage.getPackageName());
if ((reconciledPkg.mInstallArgs.mInstallFlags & PackageManager.DONT_KILL_APP)
== 0) {
- if (ps1.getOldCodePaths() == null) {
- ps1.setOldCodePaths(new ArraySet<>());
+ Set<String> oldCodePaths = ps1.getOldCodePaths();
+ if (oldCodePaths == null) {
+ oldCodePaths = new ArraySet<>();
}
- Collections.addAll(ps1.getOldCodePaths(), oldPackage.getBaseApkPath());
+ Collections.addAll(oldCodePaths, oldPackage.getBaseApkPath());
if (oldPackage.getSplitCodePaths() != null) {
- Collections.addAll(ps1.getOldCodePaths(),
- oldPackage.getSplitCodePaths());
+ Collections.addAll(oldCodePaths, oldPackage.getSplitCodePaths());
}
+ ps1.setOldCodePaths(oldCodePaths);
} else {
ps1.setOldCodePaths(null);
}
@@ -2787,8 +2784,10 @@
// Send broadcast package appeared if external for all users
if (res.mPkg.isExternalStorage()) {
if (!update) {
+ final StorageManager storageManager =
+ mInjector.getSystemService(StorageManager.class);
VolumeInfo volume =
- mStorageManager.findVolumeByUuid(
+ storageManager.findVolumeByUuid(
StorageManager.convert(
res.mPkg.getVolumeUuid()).toString());
int packageExternalStorageType =
@@ -3055,7 +3054,7 @@
final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
removePackageHelper.removePackageLI(stubPkg, true /*chatty*/);
try {
- return scanSystemPackageTracedLI(scanFile, parseFlags, scanFlags, 0, null);
+ return scanSystemPackageTracedLI(scanFile, parseFlags, scanFlags, null);
} catch (PackageManagerException e) {
Slog.w(TAG, "Failed to install compressed system package:" + stubPkg.getPackageName(),
e);
@@ -3188,7 +3187,7 @@
| ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
@PackageManagerService.ScanFlags int scanFlags = mPm.getSystemPackageScanFlags(codePath);
final AndroidPackage pkg = scanSystemPackageTracedLI(
- codePath, parseFlags, scanFlags, 0 /*currentTime*/, null);
+ codePath, parseFlags, scanFlags, null);
PackageSetting pkgSetting = mPm.mSettings.getPackageLPr(pkg.getPackageName());
@@ -3363,7 +3362,7 @@
mRemovePackageHelper.removePackageLI(pkg, true);
try {
final File codePath = new File(pkg.getPath());
- scanSystemPackageTracedLI(codePath, 0, scanFlags, 0, null);
+ scanSystemPackageTracedLI(codePath, 0, scanFlags, null);
} catch (PackageManagerException e) {
Slog.e(TAG, "Failed to parse updated, ex-system package: "
+ e.getMessage());
@@ -3384,7 +3383,7 @@
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
public void installPackagesFromDir(File scanDir, int parseFlags, int scanFlags,
- long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
+ PackageParser2 packageParser, ExecutorService executorService) {
final File[] files = scanDir.listFiles();
if (ArrayUtils.isEmpty(files)) {
Log.d(TAG, "No files in app dir " + scanDir);
@@ -3426,7 +3425,7 @@
parseResult.parsedPackage);
}
try {
- addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags, currentTime,
+ addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags,
null);
} catch (PackageManagerException e) {
errorCode = e.error;
@@ -3489,7 +3488,7 @@
try {
final AndroidPackage newPkg = scanSystemPackageTracedLI(
- scanFile, reparseFlags, rescanFlags, 0, null);
+ scanFile, reparseFlags, rescanFlags, null);
// We rescanned a stub, add it to the list of stubbed system packages
if (newPkg.isStub()) {
stubSystemApps.add(packageName);
@@ -3503,14 +3502,14 @@
/**
* Traces a package scan.
- * @see #scanSystemPackageLI(File, int, int, long, UserHandle)
+ * @see #scanSystemPackageLI(File, int, int, UserHandle)
*/
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
public AndroidPackage scanSystemPackageTracedLI(File scanFile, final int parseFlags,
- int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
+ int scanFlags, UserHandle user) throws PackageManagerException {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage [" + scanFile.toString() + "]");
try {
- return scanSystemPackageLI(scanFile, parseFlags, scanFlags, currentTime, user);
+ return scanSystemPackageLI(scanFile, parseFlags, scanFlags, user);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -3522,7 +3521,7 @@
*/
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
private AndroidPackage scanSystemPackageLI(File scanFile, int parseFlags, int scanFlags,
- long currentTime, UserHandle user) throws PackageManagerException {
+ UserHandle user) throws PackageManagerException {
if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
@@ -3538,7 +3537,7 @@
PackageManagerService.renameStaticSharedLibraryPackage(parsedPackage);
}
- return addForInitLI(parsedPackage, parseFlags, scanFlags, currentTime, user);
+ return addForInitLI(parsedPackage, parseFlags, scanFlags, user);
}
/**
@@ -3557,11 +3556,11 @@
@GuardedBy({"mPm.mLock", "mPm.mInstallLock"})
private AndroidPackage addForInitLI(ParsedPackage parsedPackage,
@ParsingPackageUtils.ParseFlags int parseFlags,
- @PackageManagerService.ScanFlags int scanFlags, long currentTime,
+ @PackageManagerService.ScanFlags int scanFlags,
@Nullable UserHandle user) throws PackageManagerException {
final Pair<ScanResult, Boolean> scanResultPair = scanSystemPackageLI(
- parsedPackage, parseFlags, scanFlags, currentTime, user);
+ parsedPackage, parseFlags, scanFlags, user);
final ScanResult scanResult = scanResultPair.first;
boolean shouldHideSystemApp = scanResultPair.second;
if (scanResult.mSuccess) {
@@ -3739,7 +3738,7 @@
private Pair<ScanResult, Boolean> scanSystemPackageLI(ParsedPackage parsedPackage,
@ParsingPackageUtils.ParseFlags int parseFlags,
- @PackageManagerService.ScanFlags int scanFlags, long currentTime,
+ @PackageManagerService.ScanFlags int scanFlags,
@Nullable UserHandle user) throws PackageManagerException {
final boolean scanSystemPartition =
(parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0;
@@ -3926,7 +3925,7 @@
}
final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags,
- scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user, null);
+ scanFlags | SCAN_UPDATE_SIGNATURE, 0 /* currentTime */, user, null);
return new Pair<>(scanResult, shouldHideSystemApp);
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 8ebd254..b3bb26c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -513,19 +513,25 @@
if (!valid) {
Slog.w(TAG, "Remove old session: " + session.sessionId);
// Remove expired sessions as well as child sessions if any
- mSessions.remove(session.sessionId);
- // Since this is early during boot we don't send
- // any observer events about the session, but we
- // keep details around for dumpsys.
- addHistoricalSessionLocked(session);
- for (PackageInstallerSession child : session.getChildSessions()) {
- mSessions.remove(child.sessionId);
- addHistoricalSessionLocked(child);
- }
+ removeActiveSession(session);
}
}
}
+ /**
+ * Moves a session (including the child sessions) from mSessions to mHistoricalSessions.
+ * This should only be called on a root session.
+ */
+ @GuardedBy("mSessions")
+ private void removeActiveSession(PackageInstallerSession session) {
+ mSessions.remove(session.sessionId);
+ addHistoricalSessionLocked(session);
+ for (PackageInstallerSession child : session.getChildSessions()) {
+ mSessions.remove(child.sessionId);
+ addHistoricalSessionLocked(child);
+ }
+ }
+
@GuardedBy("mSessions")
private void addHistoricalSessionLocked(PackageInstallerSession session) {
CharArrayWriter writer = new CharArrayWriter();
@@ -1654,10 +1660,11 @@
mStagingManager.abortSession(session.mStagedSession);
}
synchronized (mSessions) {
- if (!session.isStaged() || !success) {
- mSessions.remove(session.sessionId);
+ // Child sessions will be removed along with its parent as a whole
+ if (!session.hasParentSessionId()
+ && (!session.isStaged() || session.isDestroyed())) {
+ removeActiveSession(session);
}
- addHistoricalSessionLocked(session);
final File appIconFile = buildAppIconFile(session.sessionId);
if (appIconFile.exists()) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 304ad72..c813317 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -27,6 +27,7 @@
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
import static android.content.pm.PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT;
+import static android.content.pm.PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
@@ -2097,10 +2098,10 @@
} else {
// Session is sealed and committed but could not be verified, we need to destroy it.
destroy();
- // Dispatch message to remove session from PackageInstallerService.
- dispatchSessionFinished(error, msg, null);
- maybeFinishChildSessions(error, msg);
}
+ // Dispatch message to remove session from PackageInstallerService.
+ dispatchSessionFinished(error, msg, null);
+ maybeFinishChildSessions(error, msg);
}
private void onSessionInstallationFailure(int error, String detailedMessage) {
@@ -2301,7 +2302,7 @@
if (params.isStaged) {
// TODO(b/136257624): CTS test fails if we don't send session finished broadcast, even
// though ideally, we just need to send session committed broadcast.
- dispatchSessionFinished(INSTALL_SUCCEEDED, "Session staged", null);
+ sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED, "Session staged", null);
mStagedSession.verifySession();
} else {
@@ -2549,6 +2550,9 @@
mStagedSession.notifyEndPreRebootVerification();
if (error == SessionInfo.SESSION_NO_ERROR) {
mStagingManager.commitSession(mStagedSession);
+ } else {
+ dispatchSessionFinished(INSTALL_FAILED_VERIFICATION_FAILURE, msg, null);
+ maybeFinishChildSessions(INSTALL_FAILED_VERIFICATION_FAILURE, msg);
}
});
return;
@@ -2578,7 +2582,7 @@
// Do not try to install staged apex session. Parent session will have at least one apk
// session.
if (!isMultiPackage() && isApexSession() && params.isStaged) {
- sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED,
+ dispatchSessionFinished(INSTALL_SUCCEEDED,
"Apex package should have been installed by apexd", null);
return null;
}
@@ -2592,14 +2596,12 @@
@Override
public void onPackageInstalled(String basePackageName, int returnCode, String msg,
Bundle extras) {
- if (isStaged()) {
- sendUpdateToRemoteStatusReceiver(returnCode, msg, extras);
- } else {
+ if (!isStaged()) {
// We've reached point of no return; call into PMS to install the stage.
// Regardless of success or failure we always destroy session.
destroyInternal();
- dispatchSessionFinished(returnCode, msg, extras);
}
+ dispatchSessionFinished(returnCode, msg, extras);
}
};
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 248944e..548eb58 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -241,7 +241,6 @@
import com.android.server.pm.pkg.SuspendParams;
import com.android.server.pm.pkg.mutate.PackageStateMutator;
import com.android.server.pm.pkg.mutate.PackageStateWrite;
-import com.android.server.pm.pkg.mutate.PackageUserStateWrite;
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.verify.domain.DomainVerificationService;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
@@ -965,7 +964,7 @@
private final BroadcastHelper mBroadcastHelper;
private final RemovePackageHelper mRemovePackageHelper;
private final DeletePackageHelper mDeletePackageHelper;
- private final InitAndSystemPackageHelper mInitAndSystemPackageHelper;
+ private final InitAppsHelper mInitAppsHelper;
private final AppDataHelper mAppDataHelper;
private final InstallPackageHelper mInstallPackageHelper;
private final PreferredActivityHelper mPreferredActivityHelper;
@@ -1701,7 +1700,7 @@
mAppDataHelper = testParams.appDataHelper;
mInstallPackageHelper = testParams.installPackageHelper;
mRemovePackageHelper = testParams.removePackageHelper;
- mInitAndSystemPackageHelper = testParams.initAndSystemPackageHelper;
+ mInitAppsHelper = testParams.initAndSystemPackageHelper;
mDeletePackageHelper = testParams.deletePackageHelper;
mPreferredActivityHelper = testParams.preferredActivityHelper;
mResolveIntentHelper = testParams.resolveIntentHelper;
@@ -1845,7 +1844,8 @@
mAppDataHelper = new AppDataHelper(this);
mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper);
mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper);
- mInitAndSystemPackageHelper = new InitAndSystemPackageHelper(this);
+ mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper,
+ mInjector.getScanningPackageParser(), mInjector.getSystemPartitions());
mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper,
mAppDataHelper);
mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);
@@ -1977,8 +1977,8 @@
mIsEngBuild, mIsUserDebugBuild, mIncrementalVersion);
final int[] userIds = mUserManager.getUserIds();
- mOverlayConfig = mInitAndSystemPackageHelper.initPackages(packageSettings,
- userIds, startTime);
+ mOverlayConfig = mInitAppsHelper.initSystemApps(packageSettings, userIds, startTime);
+ mInitAppsHelper.initNonSystemApps(userIds, startTime);
// Resolve the storage manager.
mStorageManagerPackage = getStorageManagerPackageName();
@@ -9147,7 +9147,7 @@
}
boolean isExpectingBetter(String packageName) {
- return mInitAndSystemPackageHelper.isExpectingBetter(packageName);
+ return mInitAppsHelper.isExpectingBetter(packageName);
}
int getDefParseFlags() {
@@ -9256,7 +9256,7 @@
@ScanFlags int getSystemPackageScanFlags(File codePath) {
List<ScanPartition> dirsToScanAsSystem =
- mInitAndSystemPackageHelper.getDirsToScanAsSystem();
+ mInitAppsHelper.getDirsToScanAsSystem();
@PackageManagerService.ScanFlags int scanFlags = SCAN_AS_SYSTEM;
for (int i = dirsToScanAsSystem.size() - 1; i >= 0; i--) {
ScanPartition partition = dirsToScanAsSystem.get(i);
@@ -9274,7 +9274,7 @@
Pair<Integer, Integer> getSystemPackageRescanFlagsAndReparseFlags(File scanFile,
int systemScanFlags, int systemParseFlags) {
List<ScanPartition> dirsToScanAsSystem =
- mInitAndSystemPackageHelper.getDirsToScanAsSystem();
+ mInitAppsHelper.getDirsToScanAsSystem();
@ParsingPackageUtils.ParseFlags int reparseFlags = 0;
@PackageManagerService.ScanFlags int rescanFlags = 0;
for (int i1 = dirsToScanAsSystem.size() - 1; i1 >= 0; i1--) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index a1acc38..168401a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -106,7 +106,7 @@
public AppDataHelper appDataHelper;
public InstallPackageHelper installPackageHelper;
public RemovePackageHelper removePackageHelper;
- public InitAndSystemPackageHelper initAndSystemPackageHelper;
+ public InitAppsHelper initAndSystemPackageHelper;
public DeletePackageHelper deletePackageHelper;
public PreferredActivityHelper preferredActivityHelper;
public ResolveIntentHelper resolveIntentHelper;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index be2bdaa..e27ad17 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2745,7 +2745,7 @@
IUserManager um = IUserManager.Stub.asInterface(
ServiceManager.getService(Context.USER_SERVICE));
if (setEphemeralIfInUse) {
- return removeUserOrSetEphemeral(um, userId);
+ return removeUserWhenPossible(um, userId);
} else {
final boolean success = wait ? removeUserAndWait(um, userId) : removeUser(um, userId);
if (success) {
@@ -2808,10 +2808,10 @@
}
}
- private int removeUserOrSetEphemeral(IUserManager um, @UserIdInt int userId)
+ private int removeUserWhenPossible(IUserManager um, @UserIdInt int userId)
throws RemoteException {
Slog.i(TAG, "Removing " + userId + " or set as ephemeral if in use.");
- int result = um.removeUserOrSetEphemeral(userId, /* evenWhenDisallowed= */ false);
+ int result = um.removeUserWhenPossible(userId, /* overrideDevicePolicy= */ false);
switch (result) {
case UserManager.REMOVE_RESULT_REMOVED:
getOutPrintWriter().printf("Success: user %d removed\n", userId);
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index 1433abd..de64405 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -150,7 +150,7 @@
final AndroidPackage pkg;
try {
pkg = installPackageHelper.scanSystemPackageTracedLI(
- ps.getPath(), parseFlags, SCAN_INITIAL, 0, null);
+ ps.getPath(), parseFlags, SCAN_INITIAL, null);
loaded.add(pkg);
} catch (PackageManagerException e) {
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 4bcc2a3..f87063a 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -56,27 +56,6 @@
]
},
{
- "name": "CtsAppSecurityHostTestCases",
- "options": [
- {
- "include-filter": "android.appsecurity.cts.PrivilegedUpdateTests"
- }
- ]
- },
- {
- "name": "CtsAppSecurityHostTestCases",
- "file_patterns": [
- "core/java/.*Install.*",
- "services/core/.*Install.*",
- "services/core/java/com/android/server/pm/.*"
- ],
- "options": [
- {
- "include-filter": "android.appsecurity.cts.SplitTests"
- }
- ]
- },
- {
"name": "PackageManagerServiceHostTests",
"options": [
{
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index fb3ca91..d29dbbc 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2487,20 +2487,25 @@
return false;
}
- // Limit the number of profiles that can be created
- final int maxUsersOfType = getMaxUsersOfTypePerParent(type);
- if (maxUsersOfType != UserTypeDetails.UNLIMITED_NUMBER_OF_USERS) {
- final int userTypeCount = getProfileIds(userId, userType, false).length;
- final int profilesRemovedCount = userTypeCount > 0 && allowedToRemoveOne ? 1 : 0;
- if (userTypeCount - profilesRemovedCount >= maxUsersOfType) {
+ final int userTypeCount = getProfileIds(userId, userType, false).length;
+ final int profilesRemovedCount = userTypeCount > 0 && allowedToRemoveOne ? 1 : 0;
+ final int usersCountAfterRemoving = getAliveUsersExcludingGuestsCountLU()
+ - profilesRemovedCount;
+
+ // Limit total number of users that can be created
+ if (usersCountAfterRemoving >= UserManager.getMaxSupportedUsers()) {
+ // Special case: Allow creating a managed profile anyway if there's only 1 user
+ // Otherwise, disallow.
+ if (!(isManagedProfile && usersCountAfterRemoving == 1)) {
return false;
}
- // Allow creating a managed profile in the special case where there is only one user
- if (isManagedProfile) {
- int usersCountAfterRemoving = getAliveUsersExcludingGuestsCountLU()
- - profilesRemovedCount;
- return usersCountAfterRemoving == 1
- || usersCountAfterRemoving < UserManager.getMaxSupportedUsers();
+ }
+
+ // Limit the number of profiles of this type that can be created.
+ final int maxUsersOfType = getMaxUsersOfTypePerParent(type);
+ if (maxUsersOfType != UserTypeDetails.UNLIMITED_NUMBER_OF_USERS) {
+ if (userTypeCount - profilesRemovedCount >= maxUsersOfType) {
+ return false;
}
}
}
@@ -3753,6 +3758,7 @@
final boolean isGuest = UserManager.isUserTypeGuest(userType);
final boolean isRestricted = UserManager.isUserTypeRestricted(userType);
final boolean isDemo = UserManager.isUserTypeDemo(userType);
+ final boolean isManagedProfile = UserManager.isUserTypeManagedProfile(userType);
final long ident = Binder.clearCallingIdentity();
UserInfo userInfo;
@@ -3776,6 +3782,14 @@
+ ". Maximum number of that type already exists.",
UserManager.USER_OPERATION_ERROR_MAX_USERS);
}
+ if (!isGuest && !isManagedProfile && !isDemo && isUserLimitReached()) {
+ // If the user limit has been reached, we cannot add a user (except guest/demo).
+ // Note that managed profiles can bypass it in certain circumstances (taken
+ // into account in the profile check below).
+ throwCheckedUserOperationException(
+ "Cannot add user. Maximum user limit is reached.",
+ UserManager.USER_OPERATION_ERROR_MAX_USERS);
+ }
// TODO(b/142482943): Perhaps let the following code apply to restricted users too.
if (isProfile && !canAddMoreProfilesToUser(userType, parentId, false)) {
throwCheckedUserOperationException(
@@ -3783,13 +3797,6 @@
+ " for user " + parentId,
UserManager.USER_OPERATION_ERROR_MAX_USERS);
}
- if (!isGuest && !isProfile && !isDemo && isUserLimitReached()) {
- // If we're not adding a guest/demo user or a profile and the 'user limit' has
- // been reached, cannot add a user.
- throwCheckedUserOperationException(
- "Cannot add user. Maximum user limit is reached.",
- UserManager.USER_OPERATION_ERROR_MAX_USERS);
- }
// In legacy mode, restricted profile's parent can only be the owner user
if (isRestricted && !UserManager.isSplitSystemUser()
&& (parentId != UserHandle.USER_SYSTEM)) {
@@ -4430,11 +4437,11 @@
}
@Override
- public @UserManager.RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId,
- boolean evenWhenDisallowed) {
+ public @UserManager.RemoveResult int removeUserWhenPossible(@UserIdInt int userId,
+ boolean overrideDevicePolicy) {
checkCreateUsersPermission("Only the system can remove users");
- if (!evenWhenDisallowed) {
+ if (!overrideDevicePolicy) {
final String restriction = getUserRemovalRestriction(userId);
if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) {
Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 27a16e9..17a5fd0 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -94,6 +94,7 @@
private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/";
private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/";
private static final String CONFIG_FILE_NAME = "device_state_configuration.xml";
+ private static final String FLAG_CANCEL_OVERRIDE_REQUESTS = "FLAG_CANCEL_OVERRIDE_REQUESTS";
/** Interface that allows reading the device state configuration. */
interface ReadableConfig {
@@ -141,8 +142,8 @@
for (int i = 0; i < configFlagStrings.size(); i++) {
final String configFlagString = configFlagStrings.get(i);
switch (configFlagString) {
- case "FLAG_CANCEL_STICKY_REQUESTS":
- flags |= DeviceState.FLAG_CANCEL_STICKY_REQUESTS;
+ case FLAG_CANCEL_OVERRIDE_REQUESTS:
+ flags |= DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
break;
default:
Slog.w(TAG, "Parsed unknown flag with name: "
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 4b2770c..abfa016 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -102,7 +102,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.os.BackgroundThread;
@@ -290,7 +289,6 @@
private BatteryManagerInternal mBatteryManagerInternal;
private DisplayManagerInternal mDisplayManagerInternal;
private IBatteryStats mBatteryStats;
- private IAppOpsService mAppOps;
private WindowManagerPolicy mPolicy;
private Notifier mNotifier;
private WirelessChargerDetector mWirelessChargerDetector;
@@ -298,7 +296,7 @@
private DreamManagerInternal mDreamManager;
private LogicalLight mAttentionLight;
- private InattentiveSleepWarningController mInattentiveSleepWarningOverlayController;
+ private final InattentiveSleepWarningController mInattentiveSleepWarningOverlayController;
private final AmbientDisplaySuppressionController mAmbientDisplaySuppressionController;
private final Object mLock = LockGuard.installNewLock(LockGuard.INDEX_POWER);
@@ -318,10 +316,10 @@
// Table of all suspend blockers.
// There should only be a few of these.
- private final ArrayList<SuspendBlocker> mSuspendBlockers = new ArrayList<SuspendBlocker>();
+ private final ArrayList<SuspendBlocker> mSuspendBlockers = new ArrayList<>();
// Table of all wake locks acquired by applications.
- private final ArrayList<WakeLock> mWakeLocks = new ArrayList<WakeLock>();
+ private final ArrayList<WakeLock> mWakeLocks = new ArrayList<>();
// A bitfield that summarizes the state of all active wakelocks.
private int mWakeLockSummary;
@@ -354,8 +352,6 @@
private long mLastScreenBrightnessBoostTime;
private boolean mScreenBrightnessBoostInProgress;
- private DisplayGroupPowerChangeListener mDisplayGroupPowerChangeListener;
-
// The suspend blocker used to keep the CPU alive while the device is booting.
private final SuspendBlocker mBootingSuspendBlocker;
@@ -938,7 +934,7 @@
* Handler for asynchronous operations performed by the power manager.
*/
Handler createHandler(Looper looper, Handler.Callback callback) {
- return new Handler(looper, callback, true /*async*/);
+ return new Handler(looper, callback, /* async= */ true);
}
void invalidateIsInteractiveCaches() {
@@ -973,7 +969,7 @@
mInjector = injector;
mHandlerThread = new ServiceThread(TAG,
- Process.THREAD_PRIORITY_DISPLAY, false /*allowIo*/);
+ Process.THREAD_PRIORITY_DISPLAY, /* allowIo= */ false);
mHandlerThread.start();
mHandler = injector.createHandler(mHandlerThread.getLooper(),
new PowerManagerHandlerCallback());
@@ -1160,18 +1156,18 @@
}
}
- public void systemReady(IAppOpsService appOps) {
+ public void systemReady() {
synchronized (mLock) {
mSystemReady = true;
- mAppOps = appOps;
mDreamManager = getLocalService(DreamManagerInternal.class);
mDisplayManagerInternal = getLocalService(DisplayManagerInternal.class);
mPolicy = getLocalService(WindowManagerPolicy.class);
mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class);
mAttentionDetector.systemReady(mContext);
mPowerGroups.append(Display.DEFAULT_DISPLAY_GROUP, new PowerGroup());
- mDisplayGroupPowerChangeListener = new DisplayGroupPowerChangeListener();
- mDisplayManagerInternal.registerDisplayGroupListener(mDisplayGroupPowerChangeListener);
+ DisplayGroupPowerChangeListener displayGroupPowerChangeListener =
+ new DisplayGroupPowerChangeListener();
+ mDisplayManagerInternal.registerDisplayGroupListener(displayGroupPowerChangeListener);
SensorManager sensorManager = new SystemSensorManager(mContext, mHandler.getLooper());
@@ -1723,6 +1719,7 @@
}
// Called from native code.
+ @SuppressWarnings("unused")
private void userActivityFromNative(long eventTime, int event, int displayId, int flags) {
userActivityInternal(displayId, eventTime, event, flags, Process.SYSTEM_UID);
}
@@ -1757,7 +1754,7 @@
if (userActivityNoUpdateLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP),
mClock.uptimeMillis(),
PowerManager.USER_ACTIVITY_EVENT_ATTENTION,
- 0 /* flags */,
+ /* flags= */ 0,
Process.SYSTEM_UID)) {
updatePowerStateLocked();
}
@@ -2046,6 +2043,7 @@
}
}
+ @SuppressWarnings("deprecation")
@GuardedBy("mLock")
private void setGlobalWakefulnessLocked(int wakefulness, long eventTime, int reason, int uid,
int opUid, String opPackageName, String details) {
@@ -2491,9 +2489,8 @@
* Updates the value of mWakeLockSummary to summarize the state of all active wake locks.
* Note that most wake-locks are ignored when the system is asleep.
*
- * This function must have no other side-effects.
+ * This function must have no other side effects.
*/
- @SuppressWarnings("deprecation")
@GuardedBy("mLock")
private void updateWakeLockSummaryLocked(int dirty) {
if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_WAKEFULNESS | DIRTY_DISPLAY_GROUP_WAKEFULNESS))
@@ -2596,6 +2593,7 @@
}
/** Get wake lock summary flags that correspond to the given wake lock. */
+ @SuppressWarnings("deprecation")
private int getWakeLockSummaryFlags(WakeLock wakeLock) {
switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
case PowerManager.PARTIAL_WAKE_LOCK:
@@ -3170,7 +3168,7 @@
if (mDreamManager != null) {
// Restart the dream whenever the sandman is summoned.
if (startDreaming) {
- mDreamManager.stopDream(false /*immediate*/);
+ mDreamManager.stopDream(/* immediate= */ false);
mDreamManager.startDream(wakefulness == WAKEFULNESS_DOZING);
}
isDreaming = mDreamManager.isDreaming();
@@ -3254,7 +3252,7 @@
// Stop dream.
if (isDreaming) {
- mDreamManager.stopDream(false /*immediate*/);
+ mDreamManager.stopDream(/* immediate= */ false);
}
}
@@ -3518,7 +3516,7 @@
mDirty |= DIRTY_PROXIMITY_POSITIVE;
userActivityNoUpdateLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP),
mClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER,
- 0 /* flags */, Process.SYSTEM_UID);
+ /* flags= */ 0, Process.SYSTEM_UID);
updatePowerStateLocked();
}
}
@@ -4302,7 +4300,7 @@
if (sQuiescent) {
// Pass the optional "quiescent" argument to the bootloader to let it know
// that it should not turn the screen/lights on.
- if (reason != ""){
+ if (!"".equals(reason)) {
reason += ",";
}
reason = reason + "quiescent";
@@ -5412,8 +5410,8 @@
ws = new WorkSource();
// XXX should WorkSource have a way to set uids as an int[] instead of adding them
// one at a time?
- for (int i = 0; i < uids.length; i++) {
- ws.add(uids[i]);
+ for (int uid : uids) {
+ ws.add(uid);
}
}
updateWakeLockWorkSource(lock, ws, null);
@@ -5954,7 +5952,8 @@
// if uid is of root's, we permit this operation straight away
if (uid != Process.ROOT_UID) {
if (!Settings.checkAndNoteWriteSettingsOperation(mContext, uid,
- Settings.getPackageNameForUid(mContext, uid), true)) {
+ Settings.getPackageNameForUid(mContext, uid), /* attributionTag= */ null,
+ /* throwException= */ true)) {
return;
}
}
@@ -6118,6 +6117,7 @@
for (String arg : args) {
if (arg.equals("--proto")) {
isDumpProto = true;
+ break;
}
}
try {
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 7f50cd6..16176f0 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -3435,7 +3435,11 @@
convertTimeZoneSuggestionToProtoBytes(
metricsState.getLatestTelephonySuggestion()),
convertTimeZoneSuggestionToProtoBytes(
- metricsState.getLatestGeolocationSuggestion())
+ metricsState.getLatestGeolocationSuggestion()),
+ metricsState.isTelephonyTimeZoneFallbackSupported(),
+ metricsState.getDeviceTimeZoneId(),
+ metricsState.isEnhancedMetricsCollectionEnabled(),
+ metricsState.getGeoDetectionRunInBackgroundEnabled()
));
} catch (RuntimeException e) {
Slog.e(TAG, "Getting time zone detection state failed: ", e);
@@ -3482,6 +3486,14 @@
android.app.time.MetricsTimeZoneSuggestion.TIME_ZONE_ORDINALS,
zoneIdOrdinal);
}
+ String[] zoneIds = suggestion.getZoneIds();
+ if (zoneIds != null) {
+ for (String zoneId : zoneIds) {
+ protoOutputStream.write(
+ android.app.time.MetricsTimeZoneSuggestion.TIME_ZONE_IDS,
+ zoneId);
+ }
+ }
}
protoOutputStream.flush();
closeQuietly(byteArrayOutputStream);
@@ -4587,7 +4599,8 @@
int matchContentFrameRateUserPreference =
displayManager.getMatchContentFrameRateUserPreference();
byte[] userDisabledHdrTypes = toBytes(displayManager.getUserDisabledHdrTypes());
- Display.Mode userPreferredDisplayMode = displayManager.getUserPreferredDisplayMode();
+ Display.Mode userPreferredDisplayMode =
+ displayManager.getGlobalUserPreferredDisplayMode();
int userPreferredWidth = userPreferredDisplayMode != null
? userPreferredDisplayMode.getPhysicalWidth() : -1;
int userPreferredHeight = userPreferredDisplayMode != null
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
index b23f11a..36ab111d 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
@@ -281,15 +281,7 @@
LocationTimeZoneProvider primary = mPrimaryProviderConfig.createProvider();
LocationTimeZoneProvider secondary = mSecondaryProviderConfig.createProvider();
LocationTimeZoneProviderController.MetricsLogger metricsLogger =
- new LocationTimeZoneProviderController.MetricsLogger() {
- @Override
- public void onStateChange(
- @LocationTimeZoneProviderController.State String state) {
- // TODO b/200279201 - wire this up to metrics code
- // No-op.
- }
- };
-
+ new RealControllerMetricsLogger();
boolean recordStateChanges = mServiceConfigAccessor.getRecordStateChangesForTests();
LocationTimeZoneProviderController controller =
new LocationTimeZoneProviderController(mThreadingDomain, metricsLogger,
diff --git a/services/core/java/com/android/server/timezonedetector/location/RealControllerMetricsLogger.java b/services/core/java/com/android/server/timezonedetector/location/RealControllerMetricsLogger.java
new file mode 100644
index 0000000..9cb36ef
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/location/RealControllerMetricsLogger.java
@@ -0,0 +1,80 @@
+/*
+ * 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.timezonedetector.location;
+
+import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__CERTAIN;
+import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__DESTROYED;
+import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__FAILED;
+import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__INITIALIZING;
+import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__PROVIDERS_INITIALIZING;
+import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__STOPPED;
+import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__UNCERTAIN;
+import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__UNKNOWN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_CERTAIN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_DESTROYED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_FAILED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_INITIALIZING;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_PROVIDERS_INITIALIZING;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_STOPPED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_UNCERTAIN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_UNKNOWN;
+
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.timezonedetector.location.LocationTimeZoneProviderController.State;
+
+/**
+ * The real implementation of {@link LocationTimeZoneProviderController.MetricsLogger} which logs
+ * using {@link FrameworkStatsLog}.
+ */
+final class RealControllerMetricsLogger
+ implements LocationTimeZoneProviderController.MetricsLogger {
+
+ RealControllerMetricsLogger() {
+ }
+
+ @Override
+ public void onStateChange(@State String state) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED,
+ metricsState(state));
+ }
+
+ private static int metricsState(@State String state) {
+ switch (state) {
+ case STATE_PROVIDERS_INITIALIZING:
+ // Disable lint check (line length) for generated long constant name.
+ // CHECKSTYLE:OFF Generated code
+ return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__PROVIDERS_INITIALIZING;
+ // CHECKSTYLE:ON Generated code
+ case STATE_STOPPED:
+ return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__STOPPED;
+ case STATE_INITIALIZING:
+ return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__INITIALIZING;
+ case STATE_CERTAIN:
+ return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__CERTAIN;
+ case STATE_UNCERTAIN:
+ return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__UNCERTAIN;
+ case STATE_DESTROYED:
+ return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__DESTROYED;
+ case STATE_FAILED:
+ return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__FAILED;
+ case STATE_UNKNOWN:
+ default:
+ return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__UNKNOWN;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/location/RealProviderMetricsLogger.java b/services/core/java/com/android/server/timezonedetector/location/RealProviderMetricsLogger.java
index e19ec84..fe543ad 100644
--- a/services/core/java/com/android/server/timezonedetector/location/RealProviderMetricsLogger.java
+++ b/services/core/java/com/android/server/timezonedetector/location/RealProviderMetricsLogger.java
@@ -41,12 +41,12 @@
* The real implementation of {@link ProviderMetricsLogger} which logs using
* {@link FrameworkStatsLog}.
*/
-public class RealProviderMetricsLogger implements ProviderMetricsLogger {
+final class RealProviderMetricsLogger implements ProviderMetricsLogger {
@IntRange(from = 0, to = 1)
private final int mProviderIndex;
- public RealProviderMetricsLogger(@IntRange(from = 0, to = 1) int providerIndex) {
+ RealProviderMetricsLogger(@IntRange(from = 0, to = 1) int providerIndex) {
mProviderIndex = providerIndex;
}
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 59f8e54..79231f7 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -123,6 +123,7 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_GRANT_TRUST:
+ // TODO(b/213631675): Respect FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE
if (!isConnected()) {
Log.w(TAG, "Agent is not connected, cannot grant trust: "
+ mName.flattenToShortString());
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index e066ca3..e786fa2 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -1851,18 +1851,19 @@
}
@Override
- public void setIAppNotificationEnabled(IBinder sessionToken, boolean enabled, int userId) {
+ public void setInteractiveAppNotificationEnabled(
+ IBinder sessionToken, boolean enabled, int userId) {
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
- userId, "setIAppNotificationEnabled");
+ userId, "setInteractiveAppNotificationEnabled");
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
getSessionLocked(sessionToken, callingUid, resolvedUserId)
- .setIAppNotificationEnabled(enabled);
+ .setInteractiveAppNotificationEnabled(enabled);
} catch (RemoteException | SessionNotFoundException e) {
- Slog.e(TAG, "error in setIAppNotificationEnabled", e);
+ Slog.e(TAG, "error in setInteractiveAppNotificationEnabled", e);
}
}
} finally {
@@ -3538,6 +3539,23 @@
}
@Override
+ public void onSignalStrength(int strength) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "onSignalStrength(" + strength + ")");
+ }
+ if (mSessionState.session == null || mSessionState.client == null) {
+ return;
+ }
+ try {
+ mSessionState.client.onSignalStrength(strength, mSessionState.seq);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in onSignalStrength", e);
+ }
+ }
+ }
+
+ @Override
public void onTuned(Uri channelUri) {
synchronized (mLock) {
if (DEBUG) {
diff --git a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
index a4732c1..6058d88 100644
--- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
@@ -34,15 +34,15 @@
import android.media.tv.BroadcastInfoRequest;
import android.media.tv.BroadcastInfoResponse;
import android.media.tv.TvTrackInfo;
-import android.media.tv.interactive.ITvIAppClient;
import android.media.tv.interactive.ITvIAppManager;
-import android.media.tv.interactive.ITvIAppManagerCallback;
-import android.media.tv.interactive.ITvIAppService;
-import android.media.tv.interactive.ITvIAppServiceCallback;
-import android.media.tv.interactive.ITvIAppSession;
-import android.media.tv.interactive.ITvIAppSessionCallback;
-import android.media.tv.interactive.TvIAppInfo;
+import android.media.tv.interactive.ITvInteractiveAppClient;
+import android.media.tv.interactive.ITvInteractiveAppManagerCallback;
+import android.media.tv.interactive.ITvInteractiveAppService;
+import android.media.tv.interactive.ITvInteractiveAppServiceCallback;
+import android.media.tv.interactive.ITvInteractiveAppSession;
+import android.media.tv.interactive.ITvInteractiveAppSessionCallback;
import android.media.tv.interactive.TvIAppService;
+import android.media.tv.interactive.TvInteractiveAppInfo;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -53,6 +53,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.view.InputChannel;
@@ -112,54 +113,55 @@
}
@GuardedBy("mLock")
- private void buildTvIAppServiceListLocked(int userId, String[] updatedPackages) {
+ private void buildTvInteractiveAppServiceListLocked(int userId, String[] updatedPackages) {
UserState userState = getOrCreateUserStateLocked(userId);
userState.mPackageSet.clear();
if (DEBUG) {
- Slogf.d(TAG, "buildTvIAppServiceListLocked");
+ Slogf.d(TAG, "buildTvInteractiveAppServiceListLocked");
}
PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> services = pm.queryIntentServicesAsUser(
new Intent(TvIAppService.SERVICE_INTERFACE),
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
userId);
- List<TvIAppInfo> iAppList = new ArrayList<>();
+ List<TvInteractiveAppInfo> iAppList = new ArrayList<>();
for (ResolveInfo ri : services) {
ServiceInfo si = ri.serviceInfo;
- // TODO: add BIND_TV_IAPP permission and check it here
+ // TODO: add BIND_TV_INTERACTIVE_APP permission and check it here
ComponentName component = new ComponentName(si.packageName, si.name);
try {
- TvIAppInfo info = new TvIAppInfo.Builder(mContext, component).build();
+ TvInteractiveAppInfo info =
+ new TvInteractiveAppInfo(mContext, component);
iAppList.add(info);
} catch (Exception e) {
- Slogf.e(TAG, "failed to load TV IApp service " + si.name, e);
+ Slogf.e(TAG, "failed to load TV Interactive App service " + si.name, e);
continue;
}
userState.mPackageSet.add(si.packageName);
}
// sort the iApp list by iApp service id
- Collections.sort(iAppList, Comparator.comparing(TvIAppInfo::getId));
- Map<String, TvIAppState> iAppMap = new HashMap<>();
+ Collections.sort(iAppList, Comparator.comparing(TvInteractiveAppInfo::getId));
+ Map<String, TvInteractiveAppState> iAppMap = new HashMap<>();
ArrayMap<String, Integer> tiasAppCount = new ArrayMap<>(iAppMap.size());
- for (TvIAppInfo info : iAppList) {
+ for (TvInteractiveAppInfo info : iAppList) {
String iAppServiceId = info.getId();
if (DEBUG) {
Slogf.d(TAG, "add " + iAppServiceId);
}
- // Running count of IApp for each IApp service
+ // Running count of Interactive App for each Interactive App service
Integer count = tiasAppCount.get(iAppServiceId);
count = count == null ? 1 : count + 1;
tiasAppCount.put(iAppServiceId, count);
- TvIAppState iAppState = userState.mIAppMap.get(iAppServiceId);
+ TvInteractiveAppState iAppState = userState.mIAppMap.get(iAppServiceId);
if (iAppState == null) {
- iAppState = new TvIAppState();
+ iAppState = new TvInteractiveAppState();
}
iAppState.mInfo = info;
- iAppState.mUid = getIAppUid(info);
+ iAppState.mUid = getInteractiveAppUid(info);
iAppState.mComponentName = info.getComponent();
iAppMap.put(iAppServiceId, iAppState);
iAppState.mIAppNumber = count;
@@ -167,14 +169,14 @@
for (String iAppServiceId : iAppMap.keySet()) {
if (!userState.mIAppMap.containsKey(iAppServiceId)) {
- notifyIAppServiceAddedLocked(userState, iAppServiceId);
+ notifyInteractiveAppServiceAddedLocked(userState, iAppServiceId);
} else if (updatedPackages != null) {
// Notify the package updates
ComponentName component = iAppMap.get(iAppServiceId).mInfo.getComponent();
for (String updatedPackage : updatedPackages) {
if (component.getPackageName().equals(updatedPackage)) {
updateServiceConnectionLocked(component, userId);
- notifyIAppServiceUpdatedLocked(userState, iAppServiceId);
+ notifyInteractiveAppServiceUpdatedLocked(userState, iAppServiceId);
break;
}
}
@@ -183,12 +185,12 @@
for (String iAppServiceId : userState.mIAppMap.keySet()) {
if (!iAppMap.containsKey(iAppServiceId)) {
- TvIAppInfo info = userState.mIAppMap.get(iAppServiceId).mInfo;
+ TvInteractiveAppInfo info = userState.mIAppMap.get(iAppServiceId).mInfo;
ServiceState serviceState = userState.mServiceStateMap.get(info.getComponent());
if (serviceState != null) {
abortPendingCreateSessionRequestsLocked(serviceState, iAppServiceId, userId);
}
- notifyIAppServiceRemovedLocked(userState, iAppServiceId);
+ notifyInteractiveAppServiceRemovedLocked(userState, iAppServiceId);
}
}
@@ -197,48 +199,56 @@
}
@GuardedBy("mLock")
- private void notifyIAppServiceAddedLocked(UserState userState, String iAppServiceId) {
+ private void notifyInteractiveAppServiceAddedLocked(UserState userState, String iAppServiceId) {
if (DEBUG) {
- Slog.d(TAG, "notifyIAppServiceAddedLocked(iAppServiceId=" + iAppServiceId + ")");
+ Slog.d(TAG, "notifyInteractiveAppServiceAddedLocked(iAppServiceId="
+ + iAppServiceId + ")");
}
int n = userState.mCallbacks.beginBroadcast();
for (int i = 0; i < n; ++i) {
try {
- userState.mCallbacks.getBroadcastItem(i).onIAppServiceAdded(iAppServiceId);
+ userState.mCallbacks.getBroadcastItem(i)
+ .onInteractiveAppServiceAdded(iAppServiceId);
} catch (RemoteException e) {
- Slog.e(TAG, "failed to report added IApp service to callback", e);
+ Slog.e(TAG, "failed to report added Interactive App service to callback", e);
}
}
userState.mCallbacks.finishBroadcast();
}
@GuardedBy("mLock")
- private void notifyIAppServiceRemovedLocked(UserState userState, String iAppServiceId) {
+ private void notifyInteractiveAppServiceRemovedLocked(
+ UserState userState, String iAppServiceId) {
if (DEBUG) {
- Slog.d(TAG, "notifyIAppServiceRemovedLocked(iAppServiceId=" + iAppServiceId + ")");
+ Slog.d(TAG, "notifyInteractiveAppServiceRemovedLocked(iAppServiceId="
+ + iAppServiceId + ")");
}
int n = userState.mCallbacks.beginBroadcast();
for (int i = 0; i < n; ++i) {
try {
- userState.mCallbacks.getBroadcastItem(i).onIAppServiceRemoved(iAppServiceId);
+ userState.mCallbacks.getBroadcastItem(i)
+ .onInteractiveAppServiceRemoved(iAppServiceId);
} catch (RemoteException e) {
- Slog.e(TAG, "failed to report removed IApp service to callback", e);
+ Slog.e(TAG, "failed to report removed Interactive App service to callback", e);
}
}
userState.mCallbacks.finishBroadcast();
}
@GuardedBy("mLock")
- private void notifyIAppServiceUpdatedLocked(UserState userState, String iAppServiceId) {
+ private void notifyInteractiveAppServiceUpdatedLocked(
+ UserState userState, String iAppServiceId) {
if (DEBUG) {
- Slog.d(TAG, "notifyIAppServiceUpdatedLocked(iAppServiceId=" + iAppServiceId + ")");
+ Slog.d(TAG, "notifyInteractiveAppServiceUpdatedLocked(iAppServiceId="
+ + iAppServiceId + ")");
}
int n = userState.mCallbacks.beginBroadcast();
for (int i = 0; i < n; ++i) {
try {
- userState.mCallbacks.getBroadcastItem(i).onIAppServiceUpdated(iAppServiceId);
+ userState.mCallbacks.getBroadcastItem(i)
+ .onInteractiveAppServiceUpdated(iAppServiceId);
} catch (RemoteException e) {
- Slog.e(TAG, "failed to report updated IApp service to callback", e);
+ Slog.e(TAG, "failed to report updated Interactive App service to callback", e);
}
}
userState.mCallbacks.finishBroadcast();
@@ -262,7 +272,7 @@
userState.mCallbacks.finishBroadcast();
}
- private int getIAppUid(TvIAppInfo info) {
+ private int getInteractiveAppUid(TvInteractiveAppInfo info) {
try {
return getContext().getPackageManager().getApplicationInfo(
info.getServiceInfo().packageName, 0).uid;
@@ -286,18 +296,18 @@
registerBroadcastReceivers();
} else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
synchronized (mLock) {
- buildTvIAppServiceListLocked(mCurrentUserId, null);
+ buildTvInteractiveAppServiceListLocked(mCurrentUserId, null);
}
}
}
private void registerBroadcastReceivers() {
PackageMonitor monitor = new PackageMonitor() {
- private void buildTvIAppServiceList(String[] packages) {
+ private void buildTvInteractiveAppServiceList(String[] packages) {
int userId = getChangingUserId();
synchronized (mLock) {
if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) {
- buildTvIAppServiceListLocked(userId, packages);
+ buildTvInteractiveAppServiceListLocked(userId, packages);
}
}
}
@@ -305,9 +315,9 @@
@Override
public void onPackageUpdateFinished(String packageName, int uid) {
if (DEBUG) Slogf.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")");
- // This callback is invoked when the TV iApp service is reinstalled.
+ // This callback is invoked when the TV interactive App service is reinstalled.
// In this case, isReplacing() always returns true.
- buildTvIAppServiceList(new String[] { packageName });
+ buildTvInteractiveAppServiceList(new String[] { packageName });
}
@Override
@@ -318,7 +328,7 @@
// This callback is invoked when the media on which some packages exist become
// available.
if (isReplacing()) {
- buildTvIAppServiceList(packages);
+ buildTvInteractiveAppServiceList(packages);
}
}
@@ -331,7 +341,7 @@
+ ")");
}
if (isReplacing()) {
- buildTvIAppServiceList(packages);
+ buildTvInteractiveAppServiceList(packages);
}
}
@@ -339,17 +349,19 @@
public void onSomePackagesChanged() {
if (DEBUG) Slogf.d(TAG, "onSomePackagesChanged()");
if (isReplacing()) {
- if (DEBUG) Slogf.d(TAG, "Skipped building TV iApp list due to replacing");
- // When the package is updated, buildTvIAppServiceListLocked is called in other
- // methods instead.
+ if (DEBUG) {
+ Slogf.d(TAG, "Skipped building TV interactive App list due to replacing");
+ }
+ // When the package is updated, buildTvInteractiveAppServiceListLocked is called
+ // in other methods instead.
return;
}
- buildTvIAppServiceList(null);
+ buildTvInteractiveAppServiceList(null);
}
@Override
public boolean onPackageChanged(String packageName, int uid, String[] components) {
- // The iApp list needs to be updated in any cases, regardless of whether
+ // The interactive App list needs to be updated in any cases, regardless of whether
// it happened to the whole package or a specific component. Returning true so that
// the update can be handled in {@link #onSomePackagesChanged}.
return true;
@@ -401,7 +413,7 @@
unbindServiceOfUserLocked(mCurrentUserId);
mCurrentUserId = userId;
- buildTvIAppServiceListLocked(userId, null);
+ buildTvInteractiveAppServiceListLocked(userId, null);
}
}
@@ -486,7 +498,7 @@
@GuardedBy("mLock")
private void startProfileLocked(int userId) {
mRunningProfiles.add(userId);
- buildTvIAppServiceListLocked(userId, null);
+ buildTvInteractiveAppServiceListLocked(userId, null);
}
@GuardedBy("mLock")
@@ -601,13 +613,14 @@
}
@GuardedBy("mLock")
- private ITvIAppSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
+ private ITvInteractiveAppSession getSessionLocked(
+ IBinder sessionToken, int callingUid, int userId) {
return getSessionLocked(getSessionStateLocked(sessionToken, callingUid, userId));
}
@GuardedBy("mLock")
- private ITvIAppSession getSessionLocked(SessionState sessionState) {
- ITvIAppSession session = sessionState.mSession;
+ private ITvInteractiveAppSession getSessionLocked(SessionState sessionState) {
+ ITvInteractiveAppSession session = sessionState.mSession;
if (session == null) {
throw new IllegalStateException("Session not yet created for token "
+ sessionState.mSessionToken);
@@ -618,15 +631,15 @@
private final class BinderService extends ITvIAppManager.Stub {
@Override
- public List<TvIAppInfo> getTvIAppServiceList(int userId) {
+ public List<TvInteractiveAppInfo> getTvInteractiveAppServiceList(int userId) {
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
- Binder.getCallingUid(), userId, "getTvIAppServiceList");
+ Binder.getCallingUid(), userId, "getTvInteractiveAppServiceList");
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
UserState userState = getOrCreateUserStateLocked(resolvedUserId);
- List<TvIAppInfo> iAppList = new ArrayList<>();
- for (TvIAppState state : userState.mIAppMap.values()) {
+ List<TvInteractiveAppInfo> iAppList = new ArrayList<>();
+ for (TvInteractiveAppState state : userState.mIAppMap.values()) {
iAppList.add(state.mInfo);
}
return iAppList;
@@ -645,7 +658,7 @@
try {
synchronized (mLock) {
UserState userState = getOrCreateUserStateLocked(resolvedUserId);
- TvIAppState iAppState = userState.mIAppMap.get(tiasId);
+ TvInteractiveAppState iAppState = userState.mIAppMap.get(tiasId);
if (iAppState == null) {
Slogf.e(TAG, "failed to prepare TIAS - unknown TIAS id " + tiasId);
return;
@@ -673,16 +686,16 @@
}
@Override
- public void notifyAppLinkInfo(String tiasId, Bundle appLinkInfo, int userId) {
+ public void registerAppLinkInfo(String tiasId, Bundle appLinkInfo, int userId) {
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
- Binder.getCallingUid(), userId, "notifyAppLinkInfo");
+ Binder.getCallingUid(), userId, "registerAppLinkInfo");
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
UserState userState = getOrCreateUserStateLocked(resolvedUserId);
- TvIAppState iAppState = userState.mIAppMap.get(tiasId);
+ TvInteractiveAppState iAppState = userState.mIAppMap.get(tiasId);
if (iAppState == null) {
- Slogf.e(TAG, "failed to notifyAppLinkInfo - unknown TIAS id "
+ Slogf.e(TAG, "failed to registerAppLinkInfo - unknown TIAS id "
+ tiasId);
return;
}
@@ -691,18 +704,54 @@
if (serviceState == null) {
serviceState = new ServiceState(
componentName, tiasId, resolvedUserId);
- serviceState.addPendingAppLink(appLinkInfo);
+ serviceState.addPendingAppLink(appLinkInfo, true);
userState.mServiceStateMap.put(componentName, serviceState);
updateServiceConnectionLocked(componentName, resolvedUserId);
} else if (serviceState.mService != null) {
- serviceState.mService.notifyAppLinkInfo(appLinkInfo);
+ serviceState.mService.registerAppLinkInfo(appLinkInfo);
} else {
- serviceState.addPendingAppLink(appLinkInfo);
+ serviceState.addPendingAppLink(appLinkInfo, true);
updateServiceConnectionLocked(componentName, resolvedUserId);
}
}
} catch (RemoteException e) {
- Slogf.e(TAG, "error in notifyAppLinkInfo", e);
+ Slogf.e(TAG, "error in registerAppLinkInfo", e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void unregisterAppLinkInfo(String tiasId, Bundle appLinkInfo, int userId) {
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, "unregisterAppLinkInfo");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+ TvInteractiveAppState iAppState = userState.mIAppMap.get(tiasId);
+ if (iAppState == null) {
+ Slogf.e(TAG, "failed to unregisterAppLinkInfo - unknown TIAS id "
+ + tiasId);
+ return;
+ }
+ ComponentName componentName = iAppState.mInfo.getComponent();
+ ServiceState serviceState = userState.mServiceStateMap.get(componentName);
+ if (serviceState == null) {
+ serviceState = new ServiceState(
+ componentName, tiasId, resolvedUserId);
+ serviceState.addPendingAppLink(appLinkInfo, false);
+ userState.mServiceStateMap.put(componentName, serviceState);
+ updateServiceConnectionLocked(componentName, resolvedUserId);
+ } else if (serviceState.mService != null) {
+ serviceState.mService.unregisterAppLinkInfo(appLinkInfo);
+ } else {
+ serviceState.addPendingAppLink(appLinkInfo, false);
+ updateServiceConnectionLocked(componentName, resolvedUserId);
+ }
+ }
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in unregisterAppLinkInfo", e);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -716,7 +765,7 @@
try {
synchronized (mLock) {
UserState userState = getOrCreateUserStateLocked(resolvedUserId);
- TvIAppState iAppState = userState.mIAppMap.get(tiasId);
+ TvInteractiveAppState iAppState = userState.mIAppMap.get(tiasId);
if (iAppState == null) {
Slogf.e(TAG, "failed to sendAppLinkCommand - unknown TIAS id "
+ tiasId);
@@ -743,7 +792,8 @@
}
@Override
- public void createSession(final ITvIAppClient client, final String iAppServiceId, int type,
+ public void createSession(
+ final ITvInteractiveAppClient client, final String iAppServiceId, int type,
int seq, int userId) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
@@ -760,7 +810,7 @@
return;
}
UserState userState = getOrCreateUserStateLocked(resolvedUserId);
- TvIAppState iAppState = userState.mIAppMap.get(iAppServiceId);
+ TvInteractiveAppState iAppState = userState.mIAppMap.get(iAppServiceId);
if (iAppState == null) {
Slogf.w(TAG, "Failed to find state for iAppServiceId=" + iAppServiceId);
sendSessionTokenToClientLocked(client, iAppServiceId, null, null, seq);
@@ -994,13 +1044,35 @@
}
@Override
- public void startIApp(IBinder sessionToken, int userId) {
+ public void notifySignalStrength(IBinder sessionToken, int strength, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+ "notifySignalStrength");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).notifySignalStrength(strength);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in notifySignalStrength", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void startInteractiveApp(IBinder sessionToken, int userId) {
if (DEBUG) {
Slogf.d(TAG, "BinderService#start(userId=%d)", userId);
}
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
- userId, "startIApp");
+ userId, "startInteractiveApp");
SessionState sessionState = null;
final long identity = Binder.clearCallingIdentity();
try {
@@ -1008,7 +1080,7 @@
try {
sessionState = getSessionStateLocked(sessionToken, callingUid,
resolvedUserId);
- getSessionLocked(sessionState).startIApp();
+ getSessionLocked(sessionState).startInteractiveApp();
} catch (RemoteException | SessionNotFoundException e) {
Slogf.e(TAG, "error in start", e);
}
@@ -1019,13 +1091,13 @@
}
@Override
- public void stopIApp(IBinder sessionToken, int userId) {
+ public void stopInteractiveApp(IBinder sessionToken, int userId) {
if (DEBUG) {
Slogf.d(TAG, "BinderService#stop(userId=%d)", userId);
}
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
- userId, "stopIApp");
+ userId, "stopInteractiveApp");
SessionState sessionState = null;
final long identity = Binder.clearCallingIdentity();
try {
@@ -1033,7 +1105,7 @@
try {
sessionState = getSessionStateLocked(sessionToken, callingUid,
resolvedUserId);
- getSessionLocked(sessionState).stopIApp();
+ getSessionLocked(sessionState).stopInteractiveApp();
} catch (RemoteException | SessionNotFoundException e) {
Slogf.e(TAG, "error in stop", e);
}
@@ -1044,6 +1116,31 @@
}
@Override
+ public void resetInteractiveApp(IBinder sessionToken, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "BinderService#reset(userId=%d)", userId);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "resetInteractiveApp");
+ SessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).resetInteractiveApp();
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in reset", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void createBiInteractiveApp(
IBinder sessionToken, Uri biIAppUri, Bundle params, int userId) {
if (DEBUG) {
@@ -1096,6 +1193,31 @@
}
@Override
+ public void setTeletextAppEnabled(IBinder sessionToken, boolean enable, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "setTeletextAppEnabled(enable=%d)", enable);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "setTeletextAppEnabled");
+ SessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).setTeletextAppEnabled(enable);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in setTeletextAppEnabled", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void sendCurrentChannelUri(IBinder sessionToken, Uri channelUri, int userId) {
if (DEBUG) {
Slogf.d(TAG, "sendCurrentChannelUri(channelUri=%s)", channelUri.toString());
@@ -1196,6 +1318,31 @@
}
@Override
+ public void sendCurrentTvInputId(IBinder sessionToken, String inputId, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "sendCurrentTvInputId(inputId=%s)", inputId);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "sendCurrentTvInputId");
+ SessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).sendCurrentTvInputId(inputId);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in sendCurrentTvInputId", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void setSurface(IBinder sessionToken, Surface surface, int userId) {
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
@@ -1290,7 +1437,7 @@
}
@Override
- public void registerCallback(final ITvIAppManagerCallback callback, int userId) {
+ public void registerCallback(final ITvInteractiveAppManagerCallback callback, int userId) {
int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
@@ -1309,7 +1456,7 @@
}
@Override
- public void unregisterCallback(ITvIAppManagerCallback callback, int userId) {
+ public void unregisterCallback(ITvInteractiveAppManagerCallback callback, int userId) {
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Binder.getCallingUid(), userId, "unregisterCallback");
final long identity = Binder.clearCallingIdentity();
@@ -1335,7 +1482,7 @@
try {
getSessionLocked(sessionToken, callingUid, resolvedUserId)
.createMediaView(windowToken, frame);
- } catch (RemoteException | TvIAppManagerService.SessionNotFoundException e) {
+ } catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in createMediaView", e);
}
}
@@ -1355,7 +1502,7 @@
try {
getSessionLocked(sessionToken, callingUid, resolvedUserId)
.relayoutMediaView(frame);
- } catch (RemoteException | TvIAppManagerService.SessionNotFoundException e) {
+ } catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in relayoutMediaView", e);
}
}
@@ -1375,7 +1522,7 @@
try {
getSessionLocked(sessionToken, callingUid, resolvedUserId)
.removeMediaView();
- } catch (RemoteException | TvIAppManagerService.SessionNotFoundException e) {
+ } catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in removeMediaView", e);
}
}
@@ -1386,8 +1533,9 @@
}
@GuardedBy("mLock")
- private void sendSessionTokenToClientLocked(ITvIAppClient client, String iAppServiceId,
- IBinder sessionToken, InputChannel channel, int seq) {
+ private void sendSessionTokenToClientLocked(
+ ITvInteractiveAppClient client, String iAppServiceId, IBinder sessionToken,
+ InputChannel channel, int seq) {
try {
client.onSessionCreated(iAppServiceId, sessionToken, channel, seq);
} catch (RemoteException e) {
@@ -1396,8 +1544,8 @@
}
@GuardedBy("mLock")
- private boolean createSessionInternalLocked(ITvIAppService service, IBinder sessionToken,
- int userId) {
+ private boolean createSessionInternalLocked(
+ ITvInteractiveAppService service, IBinder sessionToken, int userId) {
UserState userState = getOrCreateUserStateLocked(userId);
SessionState sessionState = userState.mSessionStateMap.get(sessionToken);
if (DEBUG) {
@@ -1407,7 +1555,7 @@
InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
// Set up a callback to send the session token.
- ITvIAppSessionCallback callback = new SessionCallback(sessionState, channels);
+ ITvInteractiveAppSessionCallback callback = new SessionCallback(sessionState, channels);
boolean created = true;
// Create a session. When failed, send a null token immediately.
@@ -1514,7 +1662,8 @@
}
boolean shouldBind = (!serviceState.mSessionTokens.isEmpty())
- || (serviceState.mPendingPrepare) || (!serviceState.mPendingAppLinkInfo.isEmpty());
+ || (serviceState.mPendingPrepare)
+ || (!serviceState.mPendingAppLinkInfo.isEmpty());
if (serviceState.mService == null && shouldBind) {
// This means that the service is not yet connected but its state indicates that we
@@ -1528,7 +1677,8 @@
Slogf.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
}
- Intent i = new Intent(TvIAppService.SERVICE_INTERFACE).setComponent(component);
+ Intent i =
+ new Intent(TvIAppService.SERVICE_INTERFACE).setComponent(component);
serviceState.mBound = mContext.bindServiceAsUser(
i, serviceState.mConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
@@ -1546,20 +1696,20 @@
private static final class UserState {
private final int mUserId;
- // A mapping from the TV IApp ID to its TvIAppState.
- private Map<String, TvIAppState> mIAppMap = new HashMap<>();
+ // A mapping from the TV Interactive App ID to its TvInteractiveAppState.
+ private Map<String, TvInteractiveAppState> mIAppMap = new HashMap<>();
// A mapping from the token of a client to its state.
private final Map<IBinder, ClientState> mClientStateMap = new HashMap<>();
- // A mapping from the name of a TV IApp service to its state.
+ // A mapping from the name of a TV Interactive App service to its state.
private final Map<ComponentName, ServiceState> mServiceStateMap = new HashMap<>();
- // A mapping from the token of a TV IApp session to its state.
+ // A mapping from the token of a TV Interactive App session to its state.
private final Map<IBinder, SessionState> mSessionStateMap = new HashMap<>();
- // A set of all TV IApp service packages.
+ // A set of all TV Interactive App service packages.
private final Set<String> mPackageSet = new HashSet<>();
// A list of callbacks.
- private final RemoteCallbackList<ITvIAppManagerCallback> mCallbacks =
+ private final RemoteCallbackList<ITvInteractiveAppManagerCallback> mCallbacks =
new RemoteCallbackList<>();
private UserState(int userId) {
@@ -1567,20 +1717,20 @@
}
}
- private static final class TvIAppState {
+ private static final class TvInteractiveAppState {
private String mIAppServiceId;
private ComponentName mComponentName;
- private TvIAppInfo mInfo;
+ private TvInteractiveAppInfo mInfo;
private int mUid;
private int mIAppNumber;
}
private final class SessionState implements IBinder.DeathRecipient {
private final IBinder mSessionToken;
- private ITvIAppSession mSession;
+ private ITvInteractiveAppSession mSession;
private final String mIAppServiceId;
private final int mType;
- private final ITvIAppClient mClient;
+ private final ITvInteractiveAppClient mClient;
private final int mSeq;
private final ComponentName mComponent;
@@ -1595,8 +1745,8 @@
private final int mUserId;
private SessionState(IBinder sessionToken, String iAppServiceId, int type,
- ComponentName componentName, ITvIAppClient client, int seq, int callingUid,
- int callingPid, int userId) {
+ ComponentName componentName, ITvInteractiveAppClient client, int seq,
+ int callingUid, int callingPid, int userId) {
mSessionToken = sessionToken;
mIAppServiceId = iAppServiceId;
mComponent = componentName;
@@ -1660,12 +1810,12 @@
private final ServiceConnection mConnection;
private final ComponentName mComponent;
private final String mIAppServiceId;
- private final List<Bundle> mPendingAppLinkInfo = new ArrayList<>();
+ private final List<Pair<Bundle, Boolean>> mPendingAppLinkInfo = new ArrayList<>();
private final List<Bundle> mPendingAppLinkCommand = new ArrayList<>();
private boolean mPendingPrepare = false;
private Integer mPendingPrepareType = null;
- private ITvIAppService mService;
+ private ITvInteractiveAppService mService;
private ServiceCallback mCallback;
private boolean mBound;
private boolean mReconnecting;
@@ -1679,12 +1829,12 @@
mComponent = component;
mPendingPrepare = pendingPrepare;
mPendingPrepareType = prepareType;
- mConnection = new IAppServiceConnection(component, userId);
+ mConnection = new InteractiveAppServiceConnection(component, userId);
mIAppServiceId = tias;
}
- private void addPendingAppLink(Bundle info) {
- mPendingAppLinkInfo.add(info);
+ private void addPendingAppLink(Bundle info, boolean register) {
+ mPendingAppLinkInfo.add(Pair.create(info, register));
}
private void addPendingAppLinkCommand(Bundle command) {
@@ -1692,11 +1842,11 @@
}
}
- private final class IAppServiceConnection implements ServiceConnection {
+ private final class InteractiveAppServiceConnection implements ServiceConnection {
private final ComponentName mComponent;
private final int mUserId;
- private IAppServiceConnection(ComponentName component, int userId) {
+ private InteractiveAppServiceConnection(ComponentName component, int userId) {
mComponent = component;
mUserId = userId;
}
@@ -1714,7 +1864,7 @@
return;
}
ServiceState serviceState = userState.mServiceStateMap.get(mComponent);
- serviceState.mService = ITvIAppService.Stub.asInterface(service);
+ serviceState.mService = ITvInteractiveAppService.Stub.asInterface(service);
if (serviceState.mPendingPrepare) {
final long identity = Binder.clearCallingIdentity();
@@ -1730,15 +1880,20 @@
}
if (!serviceState.mPendingAppLinkInfo.isEmpty()) {
- for (Iterator<Bundle> it = serviceState.mPendingAppLinkInfo.iterator();
+ for (Iterator<Pair<Bundle, Boolean>> it =
+ serviceState.mPendingAppLinkInfo.iterator();
it.hasNext(); ) {
- Bundle appLinkInfo = it.next();
+ Pair<Bundle, Boolean> appLinkInfoPair = it.next();
final long identity = Binder.clearCallingIdentity();
try {
- serviceState.mService.notifyAppLinkInfo(appLinkInfo);
+ if (appLinkInfoPair.second) {
+ serviceState.mService.registerAppLinkInfo(appLinkInfoPair.first);
+ } else {
+ serviceState.mService.unregisterAppLinkInfo(appLinkInfoPair.first);
+ }
it.remove();
} catch (RemoteException e) {
- Slogf.e(TAG, "error in notifyAppLinkInfo(" + appLinkInfo
+ Slogf.e(TAG, "error in notifyAppLinkInfo(" + appLinkInfoPair
+ ") when onServiceConnected", e);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -1803,7 +1958,7 @@
}
}
- private final class ServiceCallback extends ITvIAppServiceCallback.Stub {
+ private final class ServiceCallback extends ITvInteractiveAppServiceCallback.Stub {
private final ComponentName mComponent;
private final int mUserId;
@@ -1828,7 +1983,7 @@
}
}
- private final class SessionCallback extends ITvIAppSessionCallback.Stub {
+ private final class SessionCallback extends ITvInteractiveAppSessionCallback.Stub {
private final SessionState mSessionState;
private final InputChannel[] mInputChannels;
@@ -1838,7 +1993,7 @@
}
@Override
- public void onSessionCreated(ITvIAppSession session) {
+ public void onSessionCreated(ITvInteractiveAppSession session) {
if (DEBUG) {
Slogf.d(TAG, "onSessionCreated(iAppServiceId="
+ mSessionState.mIAppServiceId + ")");
@@ -1916,7 +2071,8 @@
}
@Override
- public void onCommandRequest(@TvIAppService.IAppServiceCommandType String cmdType,
+ public void onCommandRequest(
+ @TvIAppService.InteractiveAppServiceCommandType String cmdType,
Bundle parameters) {
synchronized (mLock) {
if (DEBUG) {
@@ -2020,6 +2176,23 @@
}
@Override
+ public void onRequestCurrentTvInputId() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestCurrentTvInputId");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestCurrentTvInputId(mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestCurrentTvInputId", e);
+ }
+ }
+ }
+
+ @Override
public void onAdRequest(AdRequest request) {
synchronized (mLock) {
if (DEBUG) {
@@ -2072,8 +2245,25 @@
}
}
+ @Override
+ public void onTeletextAppStateChanged(int state) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onTeletextAppStateChanged (state=" + state + ")");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onTeletextAppStateChanged(state, mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onTeletextAppStateChanged", e);
+ }
+ }
+ }
+
@GuardedBy("mLock")
- private boolean addSessionTokenToClientStateLocked(ITvIAppSession session) {
+ private boolean addSessionTokenToClientStateLocked(ITvInteractiveAppSession session) {
try {
session.asBinder().linkToDeath(mSessionState, 0);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index f29c40f..e0cc8e1 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -39,13 +39,10 @@
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnManager.VcnErrorCode;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.Message;
import android.os.ParcelUuid;
import android.provider.Settings;
-import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
-import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -60,7 +57,6 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -152,10 +148,6 @@
@NonNull private final VcnContentResolver mContentResolver;
@NonNull private final ContentObserver mMobileDataSettingsObserver;
- @NonNull
- private final Map<Integer, VcnUserMobileDataStateListener> mMobileDataStateListeners =
- new ArrayMap<>();
-
/**
* Map containing all VcnGatewayConnections and their VcnGatewayConnectionConfigs.
*
@@ -229,9 +221,6 @@
// Update mIsMobileDataEnabled before starting handling of NetworkRequests.
mIsMobileDataEnabled = getMobileDataStatus();
- // Register mobile data state listeners.
- updateMobileDataStateListeners();
-
// Register to receive cached and future NetworkRequests
mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener);
}
@@ -359,12 +348,6 @@
gatewayConnection.teardownAsynchronously();
}
- // Unregister MobileDataStateListeners
- for (VcnUserMobileDataStateListener listener : mMobileDataStateListeners.values()) {
- getTelephonyManager().unregisterTelephonyCallback(listener);
- }
- mMobileDataStateListeners.clear();
-
mCurrentStatus = VCN_STATUS_CODE_INACTIVE;
}
@@ -471,40 +454,11 @@
gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot);
}
- updateMobileDataStateListeners();
-
// Update the mobile data state after updating the subscription snapshot as a change in
// subIds for a subGroup may affect the mobile data state.
handleMobileDataToggled();
}
- private void updateMobileDataStateListeners() {
- final Set<Integer> subIdsInGroup = mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup);
- final HandlerExecutor executor = new HandlerExecutor(this);
-
- // Register new callbacks
- for (int subId : subIdsInGroup) {
- if (!mMobileDataStateListeners.containsKey(subId)) {
- final VcnUserMobileDataStateListener listener =
- new VcnUserMobileDataStateListener();
-
- getTelephonyManagerForSubid(subId).registerTelephonyCallback(executor, listener);
- mMobileDataStateListeners.put(subId, listener);
- }
- }
-
- // Unregister old callbacks
- Iterator<Entry<Integer, VcnUserMobileDataStateListener>> iterator =
- mMobileDataStateListeners.entrySet().iterator();
- while (iterator.hasNext()) {
- final Entry<Integer, VcnUserMobileDataStateListener> entry = iterator.next();
- if (!subIdsInGroup.contains(entry.getKey())) {
- getTelephonyManager().unregisterTelephonyCallback(entry.getValue());
- iterator.remove();
- }
- }
- }
-
private void handleMobileDataToggled() {
final boolean oldMobileDataEnabledStatus = mIsMobileDataEnabled;
mIsMobileDataEnabled = getMobileDataStatus();
@@ -539,8 +493,11 @@
}
private boolean getMobileDataStatus() {
+ final TelephonyManager genericTelMan =
+ mVcnContext.getContext().getSystemService(TelephonyManager.class);
+
for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) {
- if (getTelephonyManagerForSubid(subId).isDataEnabled()) {
+ if (genericTelMan.createForSubscriptionId(subId).isDataEnabled()) {
return true;
}
}
@@ -560,14 +517,6 @@
return request.canBeSatisfiedBy(builder.build());
}
- private TelephonyManager getTelephonyManager() {
- return mVcnContext.getContext().getSystemService(TelephonyManager.class);
- }
-
- private TelephonyManager getTelephonyManagerForSubid(int subid) {
- return getTelephonyManager().createForSubscriptionId(subid);
- }
-
private String getLogPrefix() {
return "["
+ LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
@@ -721,16 +670,6 @@
}
}
- @VisibleForTesting(visibility = Visibility.PRIVATE)
- class VcnUserMobileDataStateListener extends TelephonyCallback
- implements TelephonyCallback.UserMobileDataStateListener {
-
- @Override
- public void onUserMobileDataStateChanged(boolean enabled) {
- sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED));
- }
- }
-
/** External dependencies used by Vcn, for injection in tests */
@VisibleForTesting(visibility = Visibility.PRIVATE)
public static class Dependencies {
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
index 6db25b7..c96c1ee 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
@@ -22,8 +22,6 @@
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
import static com.android.server.VcnManagementService.LOCAL_LOG;
@@ -47,6 +45,7 @@
import com.android.server.vcn.VcnContext;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/** @hide */
@@ -121,9 +120,10 @@
TelephonySubscriptionSnapshot snapshot,
UnderlyingNetworkRecord currentlySelected,
PersistableBundle carrierConfig) {
- // TODO: Check Network Quality reported by metric monitors/probers.
-
final NetworkCapabilities caps = networkRecord.networkCapabilities;
+ final boolean isSelectedUnderlyingNetwork =
+ currentlySelected != null
+ && Objects.equals(currentlySelected.network, networkRecord.network);
final int meteredMatch = networkPriority.getMetered();
final boolean isMetered = !caps.hasCapability(NET_CAPABILITY_NOT_METERED);
@@ -132,6 +132,23 @@
return false;
}
+ // Fails bandwidth requirements if either (a) less than exit threshold, or (b), not
+ // selected, but less than entry threshold
+ if (caps.getLinkUpstreamBandwidthKbps() < networkPriority.getMinExitUpstreamBandwidthKbps()
+ || (caps.getLinkUpstreamBandwidthKbps()
+ < networkPriority.getMinEntryUpstreamBandwidthKbps()
+ && !isSelectedUnderlyingNetwork)) {
+ return false;
+ }
+
+ if (caps.getLinkDownstreamBandwidthKbps()
+ < networkPriority.getMinExitDownstreamBandwidthKbps()
+ || (caps.getLinkDownstreamBandwidthKbps()
+ < networkPriority.getMinEntryDownstreamBandwidthKbps()
+ && !isSelectedUnderlyingNetwork)) {
+ return false;
+ }
+
if (vcnContext.isInTestMode() && caps.hasTransport(TRANSPORT_TEST)) {
return true;
}
@@ -172,8 +189,7 @@
}
// TODO: Move the Network Quality check to the network metric monitor framework.
- if (networkPriority.getNetworkQuality()
- > getWifiQuality(networkRecord, currentlySelected, carrierConfig)) {
+ if (!isWifiRssiAcceptable(networkRecord, currentlySelected, carrierConfig)) {
return false;
}
@@ -185,7 +201,7 @@
return true;
}
- private static int getWifiQuality(
+ private static boolean isWifiRssiAcceptable(
UnderlyingNetworkRecord networkRecord,
UnderlyingNetworkRecord currentlySelected,
PersistableBundle carrierConfig) {
@@ -196,14 +212,14 @@
if (isSelectedNetwork
&& caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)) {
- return NETWORK_QUALITY_OK;
+ return true;
}
if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) {
- return NETWORK_QUALITY_OK;
+ return true;
}
- return NETWORK_QUALITY_ANY;
+ return false;
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 8f703c5..0396a11 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -45,6 +45,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowTracing.WINSCOPE_EXT;
+import static com.android.server.wm.utils.RegionUtils.forEachRect;
import android.accessibilityservice.AccessibilityTrace;
import android.animation.ObjectAnimator;
@@ -100,7 +101,6 @@
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.wm.AccessibilityWindowsPopulator.AccessibilityWindow;
import com.android.server.wm.WindowManagerInternal.AccessibilityControllerInternal;
import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
@@ -133,22 +133,19 @@
private static final Rect EMPTY_RECT = new Rect();
private static final float[] sTempFloats = new float[9];
- private final SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>();
- private final SparseArray<WindowsForAccessibilityObserver> mWindowsForAccessibilityObserver =
+ private SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>();
+ private SparseArray<WindowsForAccessibilityObserver> mWindowsForAccessibilityObserver =
new SparseArray<>();
private SparseArray<IBinder> mFocusedWindow = new SparseArray<>();
private int mFocusedDisplay = -1;
private boolean mIsImeVisible = false;
// Set to true if initializing window population complete.
private boolean mAllObserversInitialized = true;
- private final AccessibilityWindowsPopulator mAccessibilityWindowsPopulator;
AccessibilityController(WindowManagerService service) {
mService = service;
mAccessibilityTracing =
AccessibilityController.getAccessibilityControllerInternal(service);
-
- mAccessibilityWindowsPopulator = new AccessibilityWindowsPopulator(mService, this);
}
boolean setMagnificationCallbacks(int displayId, MagnificationCallbacks callbacks) {
@@ -212,9 +209,7 @@
}
mWindowsForAccessibilityObserver.remove(displayId);
}
- mAccessibilityWindowsPopulator.setWindowsNotification(true);
- observer = new WindowsForAccessibilityObserver(mService, displayId, callback,
- mAccessibilityWindowsPopulator);
+ observer = new WindowsForAccessibilityObserver(mService, displayId, callback);
mWindowsForAccessibilityObserver.put(displayId, observer);
mAllObserversInitialized &= observer.mInitialized;
} else {
@@ -229,10 +224,6 @@
}
}
mWindowsForAccessibilityObserver.remove(displayId);
-
- if (mWindowsForAccessibilityObserver.size() <= 0) {
- mAccessibilityWindowsPopulator.setWindowsNotification(false);
- }
}
}
@@ -318,6 +309,11 @@
if (displayMagnifier != null) {
displayMagnifier.onDisplaySizeChanged(displayContent);
}
+ final WindowsForAccessibilityObserver windowsForA11yObserver =
+ mWindowsForAccessibilityObserver.get(displayId);
+ if (windowsForA11yObserver != null) {
+ windowsForA11yObserver.scheduleComputeChangedWindows();
+ }
}
void onAppWindowTransition(int displayId, int transition) {
@@ -345,6 +341,11 @@
if (displayMagnifier != null) {
displayMagnifier.onWindowTransition(windowState, transition);
}
+ final WindowsForAccessibilityObserver windowsForA11yObserver =
+ mWindowsForAccessibilityObserver.get(displayId);
+ if (windowsForA11yObserver != null) {
+ windowsForA11yObserver.scheduleComputeChangedWindows();
+ }
}
void onWindowFocusChangedNot(int displayId) {
@@ -454,19 +455,6 @@
return null;
}
- boolean getMagnificationSpecForDisplay(int displayId, MagnificationSpec outSpec) {
- if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(TAG + ".getMagnificationSpecForDisplay",
- FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId);
- }
- final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
- if (displayMagnifier == null) {
- return false;
- }
-
- return displayMagnifier.getMagnificationSpec(outSpec);
- }
-
boolean hasCallbacks() {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
| FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
@@ -768,25 +756,6 @@
return spec;
}
- boolean getMagnificationSpec(MagnificationSpec outSpec) {
- if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationSpec",
- FLAGS_MAGNIFICATION_CALLBACK);
- }
- MagnificationSpec spec = mMagnifedViewport.getMagnificationSpec();
- if (spec == null) {
- return false;
- }
-
- outSpec.setTo(spec);
- if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationSpec",
- FLAGS_MAGNIFICATION_CALLBACK, "outSpec={" + outSpec + "}");
- }
-
- return true;
- }
-
void getMagnificationRegion(Region outMagnificationRegion) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationRegion",
@@ -1434,18 +1403,20 @@
private static final boolean DEBUG = false;
- private final List<AccessibilityWindow> mTempA11yWindows = new ArrayList<>();
+ private final SparseArray<WindowState> mTempWindowStates = new SparseArray<>();
private final Set<IBinder> mTempBinderSet = new ArraySet<>();
+ private final RectF mTempRectF = new RectF();
+
+ private final Matrix mTempMatrix = new Matrix();
+
private final Point mTempPoint = new Point();
private final Region mTempRegion = new Region();
private final Region mTempRegion1 = new Region();
- private final Region mTempRegion2 = new Region();
-
private final WindowManagerService mService;
private final Handler mHandler;
@@ -1460,11 +1431,10 @@
// Set to true if initializing window population complete.
private boolean mInitialized;
- private final AccessibilityWindowsPopulator mA11yWindowsPopulator;
WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
- int displayId, WindowsForAccessibilityCallback callback,
- AccessibilityWindowsPopulator accessibilityWindowsPopulator) {
+ int displayId,
+ WindowsForAccessibilityCallback callback) {
mService = windowManagerService;
mCallback = callback;
mDisplayId = displayId;
@@ -1473,7 +1443,6 @@
AccessibilityController.getAccessibilityControllerInternal(mService);
mRecurringAccessibilityEventsIntervalMillis = ViewConfiguration
.getSendRecurringAccessibilityEventsInterval();
- mA11yWindowsPopulator = accessibilityWindowsPopulator;
computeChangedWindows(true);
}
@@ -1497,6 +1466,52 @@
}
}
+ boolean shellRootIsAbove(WindowState windowState, ShellRoot shellRoot) {
+ int wsLayer = mService.mPolicy.getWindowLayerLw(windowState);
+ int shellLayer = mService.mPolicy.getWindowLayerFromTypeLw(shellRoot.getWindowType(),
+ true);
+ return shellLayer >= wsLayer;
+ }
+
+ int addShellRootsIfAbove(WindowState windowState, ArrayList<ShellRoot> shellRoots,
+ int shellRootIndex, List<WindowInfo> windows, Set<IBinder> addedWindows,
+ Region unaccountedSpace, boolean focusedWindowAdded) {
+ while (shellRootIndex < shellRoots.size()
+ && shellRootIsAbove(windowState, shellRoots.get(shellRootIndex))) {
+ ShellRoot shellRoot = shellRoots.get(shellRootIndex);
+ shellRootIndex++;
+ final WindowInfo info = shellRoot.getWindowInfo();
+ if (info == null) {
+ continue;
+ }
+
+ info.layer = addedWindows.size();
+ windows.add(info);
+ addedWindows.add(info.token);
+ unaccountedSpace.op(info.regionInScreen, unaccountedSpace,
+ Region.Op.REVERSE_DIFFERENCE);
+ if (unaccountedSpace.isEmpty() && focusedWindowAdded) {
+ break;
+ }
+ }
+ return shellRootIndex;
+ }
+
+ private ArrayList<ShellRoot> getSortedShellRoots(
+ SparseArray<ShellRoot> originalShellRoots) {
+ ArrayList<ShellRoot> sortedShellRoots = new ArrayList<>(originalShellRoots.size());
+ for (int i = originalShellRoots.size() - 1; i >= 0; --i) {
+ sortedShellRoots.add(originalShellRoots.valueAt(i));
+ }
+
+ sortedShellRoots.sort((left, right) ->
+ mService.mPolicy.getWindowLayerFromTypeLw(right.getWindowType(), true)
+ - mService.mPolicy.getWindowLayerFromTypeLw(left.getWindowType(),
+ true));
+
+ return sortedShellRoots;
+ }
+
/**
* Check if windows have changed, and send them to the accessibility subsystem if they have.
*
@@ -1546,29 +1561,44 @@
Region unaccountedSpace = mTempRegion;
unaccountedSpace.set(0, 0, screenWidth, screenHeight);
- final List<AccessibilityWindow> visibleWindows = mTempA11yWindows;
- mA11yWindowsPopulator.populateVisibleWindowsOnScreenLocked(
- mDisplayId, visibleWindows);
+ final SparseArray<WindowState> visibleWindows = mTempWindowStates;
+ populateVisibleWindowsOnScreen(visibleWindows);
Set<IBinder> addedWindows = mTempBinderSet;
addedWindows.clear();
boolean focusedWindowAdded = false;
final int visibleWindowCount = visibleWindows.size();
+ ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments = new ArrayList<>();
+
+ ArrayList<ShellRoot> shellRoots = getSortedShellRoots(dc.mShellRoots);
// Iterate until we figure out what is touchable for the entire screen.
- for (int i = 0; i < visibleWindowCount; i++) {
- final AccessibilityWindow a11yWindow = visibleWindows.get(i);
- final Region regionInWindow = new Region();
- a11yWindow.getTouchableRegionInWindow(regionInWindow);
- if (windowMattersToAccessibility(a11yWindow, regionInWindow,
- unaccountedSpace)) {
- addPopulatedWindowInfo(a11yWindow, regionInWindow, windows, addedWindows);
- if (windowMattersToUnaccountedSpaceComputation(a11yWindow)) {
- updateUnaccountedSpace(a11yWindow, unaccountedSpace);
+ int shellRootIndex = 0;
+ for (int i = visibleWindowCount - 1; i >= 0; i--) {
+ final WindowState windowState = visibleWindows.valueAt(i);
+ int prevShellRootIndex = shellRootIndex;
+ shellRootIndex = addShellRootsIfAbove(windowState, shellRoots, shellRootIndex,
+ windows, addedWindows, unaccountedSpace, focusedWindowAdded);
+
+ // If a Shell Root was added, it could have accounted for all the space already.
+ if (shellRootIndex > prevShellRootIndex && unaccountedSpace.isEmpty()
+ && focusedWindowAdded) {
+ break;
+ }
+
+ final Region regionInScreen = new Region();
+ computeWindowRegionInScreen(windowState, regionInScreen);
+ if (windowMattersToAccessibility(windowState,
+ regionInScreen, unaccountedSpace,
+ skipRemainingWindowsForTaskFragments)) {
+ addPopulatedWindowInfo(windowState, regionInScreen, windows, addedWindows);
+ if (windowMattersToUnaccountedSpaceComputation(windowState)) {
+ updateUnaccountedSpace(windowState, regionInScreen, unaccountedSpace,
+ skipRemainingWindowsForTaskFragments);
}
- focusedWindowAdded |= a11yWindow.isFocused();
- } else if (a11yWindow.isUntouchableNavigationBar()) {
+ focusedWindowAdded |= windowState.isFocused();
+ } else if (isUntouchableNavigationBar(windowState, mTempRegion1)) {
// If this widow is navigation bar without touchable region, accounting the
// region of navigation bar inset because all touch events from this region
// would be received by launcher, i.e. this region is a un-touchable one
@@ -1617,39 +1647,47 @@
// Some windows should be excluded from unaccounted space computation, though they still
// should be reported
- private boolean windowMattersToUnaccountedSpaceComputation(AccessibilityWindow a11yWindow) {
+ private boolean windowMattersToUnaccountedSpaceComputation(WindowState windowState) {
// Do not account space of trusted non-touchable windows, except the split-screen
// divider.
// If it's not trusted, touch events are not sent to the windows behind it.
- if (((a11yWindow.getFlags() & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0)
- && (a11yWindow.getType() != TYPE_DOCK_DIVIDER)
- && a11yWindow.isTrustedOverlay()) {
+ if (((windowState.mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0)
+ && (windowState.mAttrs.type != TYPE_DOCK_DIVIDER)
+ && windowState.isTrustedOverlay()) {
return false;
}
- if (a11yWindow.getType() == WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) {
+ if (windowState.mAttrs.type
+ == WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) {
return false;
}
return true;
}
- private boolean windowMattersToAccessibility(AccessibilityWindow a11yWindow,
- Region regionInScreen, Region unaccountedSpace) {
- if (a11yWindow.ignoreRecentsAnimationForAccessibility()) {
+ private boolean windowMattersToAccessibility(WindowState windowState,
+ Region regionInScreen, Region unaccountedSpace,
+ ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments) {
+ final RecentsAnimationController controller = mService.getRecentsAnimationController();
+ if (controller != null && controller.shouldIgnoreForAccessibility(windowState)) {
return false;
}
- if (a11yWindow.isFocused()) {
+ if (windowState.isFocused()) {
return true;
}
+ // If the window is part of a task that we're finished with - ignore.
+ final TaskFragment taskFragment = windowState.getTaskFragment();
+ if (taskFragment != null
+ && skipRemainingWindowsForTaskFragments.contains(taskFragment)) {
+ return false;
+ }
+
// Ignore non-touchable windows, except the split-screen divider, which is
// occasionally non-touchable but still useful for identifying split-screen
- // mode and the PIP menu.
- if (((a11yWindow.getFlags()
- & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0)
- && (a11yWindow.getType() != TYPE_DOCK_DIVIDER
- && !a11yWindow.isPIPMenu())) {
+ // mode.
+ if (((windowState.mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0)
+ && (windowState.mAttrs.type != TYPE_DOCK_DIVIDER)) {
return false;
}
@@ -1659,36 +1697,88 @@
}
// Add windows of certain types not covered by modal windows.
- if (isReportedWindowType(a11yWindow.getType())) {
+ if (isReportedWindowType(windowState.mAttrs.type)) {
return true;
}
return false;
}
- private void updateUnaccountedSpace(AccessibilityWindow a11yWindow,
- Region unaccountedSpace) {
- if (a11yWindow.getType()
- != WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) {
- // Account for the space this window takes if the window
- // is not an accessibility overlay which does not change
- // the reported windows.
- final Region touchableRegion = mTempRegion2;
- a11yWindow.getTouchableRegionInScreen(touchableRegion);
- unaccountedSpace.op(touchableRegion, unaccountedSpace,
- Region.Op.REVERSE_DIFFERENCE);
- // Account for the space of letterbox.
- final Region letterboxBounds = mTempRegion1;
- if (a11yWindow.setLetterBoxBoundsIfNeeded(letterboxBounds)) {
- unaccountedSpace.op(letterboxBounds,
- unaccountedSpace, Region.Op.REVERSE_DIFFERENCE);
+ private void updateUnaccountedSpace(WindowState windowState, Region regionInScreen,
+ Region unaccountedSpace,
+ ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments) {
+ // Account for the space this window takes if the window
+ // is not an accessibility overlay which does not change
+ // the reported windows.
+ unaccountedSpace.op(regionInScreen, unaccountedSpace,
+ Region.Op.REVERSE_DIFFERENCE);
+
+ // If a window is modal it prevents other windows from being touched
+ if ((windowState.mAttrs.flags & (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)) == 0) {
+ if (!windowState.hasTapExcludeRegion()) {
+ // Account for all space in the task, whether the windows in it are
+ // touchable or not. The modal window blocks all touches from the task's
+ // area.
+ unaccountedSpace.op(windowState.getDisplayFrame(), unaccountedSpace,
+ Region.Op.REVERSE_DIFFERENCE);
+ } else {
+ // If a window has tap exclude region, we need to account it.
+ final Region displayRegion = new Region(windowState.getDisplayFrame());
+ final Region tapExcludeRegion = new Region();
+ windowState.getTapExcludeRegion(tapExcludeRegion);
+ displayRegion.op(tapExcludeRegion, displayRegion,
+ Region.Op.REVERSE_DIFFERENCE);
+ unaccountedSpace.op(displayRegion, unaccountedSpace,
+ Region.Op.REVERSE_DIFFERENCE);
}
+
+ final TaskFragment taskFragment = windowState.getTaskFragment();
+ if (taskFragment != null) {
+ // If the window is associated with a particular task, we can skip the
+ // rest of the windows for that task.
+ skipRemainingWindowsForTaskFragments.add(taskFragment);
+ } else if (!windowState.hasTapExcludeRegion()) {
+ // If the window is not associated with a particular task, then it is
+ // globally modal. In this case we can skip all remaining windows when
+ // it doesn't has tap exclude region.
+ unaccountedSpace.setEmpty();
+ }
+ }
+
+ // Account for the space of letterbox.
+ if (windowState.areAppWindowBoundsLetterboxed()) {
+ unaccountedSpace.op(getLetterboxBounds(windowState), unaccountedSpace,
+ Region.Op.REVERSE_DIFFERENCE);
}
}
- private static void addPopulatedWindowInfo(AccessibilityWindow a11yWindow,
- Region regionInScreen, List<WindowInfo> out, Set<IBinder> tokenOut) {
- final WindowInfo window = a11yWindow.getWindowInfo();
+ private void computeWindowRegionInScreen(WindowState windowState, Region outRegion) {
+ // Get the touchable frame.
+ Region touchableRegion = mTempRegion1;
+ windowState.getTouchableRegion(touchableRegion);
+
+ // Map the frame to get what appears on the screen.
+ Matrix matrix = mTempMatrix;
+ populateTransformationMatrix(windowState, matrix);
+
+ forEachRect(touchableRegion, rect -> {
+ // Move to origin as all transforms are captured by the matrix.
+ RectF windowFrame = mTempRectF;
+ windowFrame.set(rect);
+ windowFrame.offset(-windowState.getFrame().left, -windowState.getFrame().top);
+
+ matrix.mapRect(windowFrame);
+
+ // Union all rects.
+ outRegion.union(new Rect((int) windowFrame.left, (int) windowFrame.top,
+ (int) windowFrame.right, (int) windowFrame.bottom));
+ });
+ }
+
+ private static void addPopulatedWindowInfo(WindowState windowState, Region regionInScreen,
+ List<WindowInfo> out, Set<IBinder> tokenOut) {
+ final WindowInfo window = windowState.getWindowInfo();
window.regionInScreen.set(regionInScreen);
window.layer = tokenOut.size();
out.add(window);
@@ -1715,6 +1805,23 @@
&& windowType != WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
}
+ private void populateVisibleWindowsOnScreen(SparseArray<WindowState> outWindows) {
+ final List<WindowState> tempWindowStatesList = new ArrayList<>();
+ final DisplayContent dc = mService.mRoot.getDisplayContent(mDisplayId);
+ if (dc == null) {
+ return;
+ }
+
+ dc.forAllWindows(w -> {
+ if (w.isVisible()) {
+ tempWindowStatesList.add(w);
+ }
+ }, false /* traverseTopToBottom */);
+ for (int i = 0; i < tempWindowStatesList.size(); i++) {
+ outWindows.put(i, tempWindowStatesList.get(i));
+ }
+ }
+
private WindowState getTopFocusWindow() {
return mService.mRoot.getTopFocusedDisplayContent().mCurrentFocus;
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
deleted file mode 100644
index f31ae06..0000000
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ /dev/null
@@ -1,625 +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.wm;
-
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-
-import static com.android.server.wm.utils.RegionUtils.forEachRect;
-
-import android.annotation.NonNull;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.view.IWindow;
-import android.view.InputWindowHandle;
-import android.view.MagnificationSpec;
-import android.view.WindowInfo;
-import android.view.WindowManager;
-import android.window.WindowInfosListener;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * This class is the accessibility windows population adapter.
- */
-public final class AccessibilityWindowsPopulator extends WindowInfosListener {
-
- private static final String TAG = AccessibilityWindowsPopulator.class.getSimpleName();
- // If the surface flinger callback is not coming within in 2 frames time, i.e. about
- // 35ms, then assuming the windows become stable.
- private static final int SURFACE_FLINGER_CALLBACK_WINDOWS_STABLE_TIMES_MS = 35;
- // To avoid the surface flinger callbacks always comes within in 2 frames, then no windows
- // are reported to the A11y framework, and the animation duration time is 500ms, so setting
- // this value as the max timeout value to force computing changed windows.
- private static final int WINDOWS_CHANGED_NOTIFICATION_MAX_DURATION_TIMES_MS = 500;
-
- private static final float[] sTempFloats = new float[9];
-
- private final WindowManagerService mService;
- private final AccessibilityController mAccessibilityController;
- @GuardedBy("mLock")
- private final SparseArray<List<InputWindowHandle>> mInputWindowHandlesOnDisplays =
- new SparseArray<>();
- @GuardedBy("mLock")
- private final SparseArray<Matrix> mMagnificationSpecInverseMatrix = new SparseArray<>();
- @GuardedBy("mLock")
- private final SparseArray<DisplayInfo> mDisplayInfos = new SparseArray<>();
- @GuardedBy("mLock")
- private final List<InputWindowHandle> mVisibleWindows = new ArrayList<>();
- @GuardedBy("mLock")
- private boolean mWindowsNotificationEnabled = false;
- private final Object mLock = new Object();
- private final Handler mHandler;
-
- AccessibilityWindowsPopulator(WindowManagerService service,
- AccessibilityController accessibilityController) {
- mService = service;
- mAccessibilityController = accessibilityController;
- mHandler = new MyHandler(mService.mH.getLooper());
-
- register();
- }
-
- /**
- * Gets the visible windows list with the window layer on the specified display.
- *
- * @param displayId The display.
- * @param outWindows The visible windows list. The z-order of each window in the list
- * is from the top to bottom.
- */
- public void populateVisibleWindowsOnScreenLocked(int displayId,
- List<AccessibilityWindow> outWindows) {
- List<InputWindowHandle> inputWindowHandles;
- final Matrix inverseMatrix = new Matrix();
- final Matrix displayMatrix = new Matrix();
-
- synchronized (mLock) {
- inputWindowHandles = mInputWindowHandlesOnDisplays.get(displayId);
- if (inputWindowHandles == null) {
- outWindows.clear();
-
- return;
- }
- inverseMatrix.set(mMagnificationSpecInverseMatrix.get(displayId));
-
- final DisplayInfo displayInfo = mDisplayInfos.get(displayId);
- if (displayInfo != null) {
- displayMatrix.set(displayInfo.mTransform);
- } else {
- Slog.w(TAG, "The displayInfo of this displayId (" + displayId + ") called "
- + "back from the surface fligner is null");
- }
- }
-
- final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
- final ShellRoot shellroot = dc.mShellRoots.get(WindowManager.SHELL_ROOT_LAYER_PIP);
- final IBinder pipMenuIBinder =
- shellroot != null ? shellroot.getAccessibilityWindowToken() : null;
-
- for (final InputWindowHandle windowHandle : inputWindowHandles) {
- final AccessibilityWindow accessibilityWindow =
- AccessibilityWindow.initializeData(mService, windowHandle, inverseMatrix,
- pipMenuIBinder, displayMatrix);
-
- outWindows.add(accessibilityWindow);
- }
- }
-
- @Override
- public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
- DisplayInfo[] displayInfos) {
- synchronized (mLock) {
- mVisibleWindows.clear();
- for (InputWindowHandle window : windowHandles) {
- if (window.visible && window.getWindow() != null) {
- mVisibleWindows.add(window);
- }
- }
-
- mDisplayInfos.clear();
- for (final DisplayInfo displayInfo : displayInfos) {
- mDisplayInfos.put(displayInfo.mDisplayId, displayInfo);
- }
-
- if (mWindowsNotificationEnabled) {
- if (!mHandler.hasMessages(
- MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_TIMEOUT)) {
- mHandler.sendEmptyMessageDelayed(
- MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_TIMEOUT,
- WINDOWS_CHANGED_NOTIFICATION_MAX_DURATION_TIMES_MS);
- }
- populateVisibleWindowHandlesAndNotifyWindowsChangeIfNeededLocked();
- }
- }
- }
-
- /**
- * Sets to notify the accessibilityController to compute changed windows on
- * the display after populating the visible windows if the windows reported
- * from the surface flinger changes.
- *
- * @param register {@code true} means starting windows population.
- */
- public void setWindowsNotification(boolean register) {
- synchronized (mLock) {
- if (mWindowsNotificationEnabled == register) {
- return;
- }
- mWindowsNotificationEnabled = register;
- if (mWindowsNotificationEnabled) {
- populateVisibleWindowHandlesAndNotifyWindowsChangeIfNeededLocked();
- } else {
- releaseResources();
- }
- }
- }
-
- private void populateVisibleWindowHandlesAndNotifyWindowsChangeIfNeededLocked() {
- final SparseArray<List<InputWindowHandle>> tempWindowHandleList = new SparseArray<>();
-
- for (final InputWindowHandle windowHandle : mVisibleWindows) {
- List<InputWindowHandle> inputWindowHandles = tempWindowHandleList.get(
- windowHandle.displayId);
-
- if (inputWindowHandles == null) {
- inputWindowHandles = new ArrayList<>();
- tempWindowHandleList.put(windowHandle.displayId, inputWindowHandles);
- generateMagnificationSpecInverseMatrixLocked(windowHandle.displayId);
- }
- inputWindowHandles.add(windowHandle);
- }
-
- final List<Integer> displayIdsForWindowsChanged = new ArrayList<>();
-
- getDisplaysForWindowsChangedLocked(displayIdsForWindowsChanged, tempWindowHandleList,
- mInputWindowHandlesOnDisplays);
- // Clones all windows from the callback of the surface flinger.
- mInputWindowHandlesOnDisplays.clear();
- for (int i = 0; i < tempWindowHandleList.size(); i++) {
- final int displayId = tempWindowHandleList.keyAt(i);
- mInputWindowHandlesOnDisplays.put(displayId, tempWindowHandleList.get(displayId));
- }
-
- if (displayIdsForWindowsChanged.size() > 0) {
- if (!mHandler.hasMessages(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED)) {
- mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED,
- displayIdsForWindowsChanged).sendToTarget();
- }
-
- return;
- }
- mHandler.removeMessages(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_UI_STABLE);
- mHandler.sendEmptyMessageDelayed(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_UI_STABLE,
- SURFACE_FLINGER_CALLBACK_WINDOWS_STABLE_TIMES_MS);
- }
-
- private void getDisplaysForWindowsChangedLocked(List<Integer> outDisplayIdsForWindowsChanged,
- SparseArray<List<InputWindowHandle>> newWindowsList,
- SparseArray<List<InputWindowHandle>> oldWindowsList) {
- for (int i = 0; i < newWindowsList.size(); i++) {
- final int displayId = newWindowsList.keyAt(i);
- final List<InputWindowHandle> newWindows = newWindowsList.get(displayId);
- final List<InputWindowHandle> oldWindows = oldWindowsList.get(displayId);
-
- if (hasWindowsChangedLocked(newWindows, oldWindows)) {
- outDisplayIdsForWindowsChanged.add(displayId);
- }
- }
- }
-
- private boolean hasWindowsChangedLocked(List<InputWindowHandle> newWindows,
- List<InputWindowHandle> oldWindows) {
- if (oldWindows == null || oldWindows.size() != newWindows.size()) {
- return true;
- }
-
- final int windowsCount = newWindows.size();
- // Since we always traverse windows from high to low layer,
- // the old and new windows at the same index should be the
- // same, otherwise something changed.
- for (int i = 0; i < windowsCount; i++) {
- final InputWindowHandle newWindow = newWindows.get(i);
- final InputWindowHandle oldWindow = oldWindows.get(i);
-
- if (!newWindow.getWindow().asBinder().equals(oldWindow.getWindow().asBinder())) {
- return true;
- }
- }
-
- return false;
- }
-
- private void generateMagnificationSpecInverseMatrixLocked(int displayId) {
- MagnificationSpec spec = new MagnificationSpec();
- if (!mAccessibilityController.getMagnificationSpecForDisplay(displayId, spec)) {
- return;
- }
- sTempFloats[Matrix.MSCALE_X] = spec.scale;
- sTempFloats[Matrix.MSKEW_Y] = 0;
- sTempFloats[Matrix.MSKEW_X] = 0;
- sTempFloats[Matrix.MSCALE_Y] = spec.scale;
- sTempFloats[Matrix.MTRANS_X] = spec.offsetX;
- sTempFloats[Matrix.MTRANS_Y] = spec.offsetY;
- sTempFloats[Matrix.MPERSP_0] = 0;
- sTempFloats[Matrix.MPERSP_1] = 0;
- sTempFloats[Matrix.MPERSP_2] = 1;
-
- final Matrix tempMatrix = new Matrix();
- tempMatrix.setValues(sTempFloats);
-
- final Matrix inverseMatrix = new Matrix();
- final boolean result = tempMatrix.invert(inverseMatrix);
-
- if (!result) {
- Slog.e(TAG, "Can't inverse the magnification spec matrix with the "
- + "magnification spec = " + spec + " on the displayId = " + displayId);
- return;
- }
- mMagnificationSpecInverseMatrix.set(displayId, inverseMatrix);
- }
-
- private void notifyWindowsChanged(@NonNull List<Integer> displayIdsForWindowsChanged) {
- mHandler.removeMessages(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_TIMEOUT);
-
- for (int i = 0; i < displayIdsForWindowsChanged.size(); i++) {
- mAccessibilityController.performComputeChangedWindowsNot(
- displayIdsForWindowsChanged.get(i), false);
- }
- }
-
- private void forceUpdateWindows() {
- final List<Integer> displayIdsForWindowsChanged = new ArrayList<>();
-
- synchronized (mLock) {
- for (int i = 0; i < mInputWindowHandlesOnDisplays.size(); i++) {
- final int displayId = mInputWindowHandlesOnDisplays.keyAt(i);
- displayIdsForWindowsChanged.add(displayId);
- }
- }
- notifyWindowsChanged(displayIdsForWindowsChanged);
- }
-
- @GuardedBy("mLock")
- private void releaseResources() {
- mInputWindowHandlesOnDisplays.clear();
- mMagnificationSpecInverseMatrix.clear();
- mVisibleWindows.clear();
- mDisplayInfos.clear();
- mWindowsNotificationEnabled = false;
- mHandler.removeCallbacksAndMessages(null);
- }
-
- private class MyHandler extends Handler {
- public static final int MESSAGE_NOTIFY_WINDOWS_CHANGED = 1;
- public static final int MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_UI_STABLE = 2;
- public static final int MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_TIMEOUT = 3;
-
- MyHandler(Looper looper) {
- super(looper, null, false);
- }
-
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MESSAGE_NOTIFY_WINDOWS_CHANGED: {
- final List<Integer> displayIdsForWindowsChanged = (List<Integer>) message.obj;
- notifyWindowsChanged(displayIdsForWindowsChanged);
- } break;
-
- case MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_UI_STABLE: {
- forceUpdateWindows();
- } break;
-
- case MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_TIMEOUT: {
- Slog.w(TAG, "Windows change within in 2 frames continuously over 500 ms "
- + "and notify windows changed immediately");
- mHandler.removeMessages(
- MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_UI_STABLE);
-
- forceUpdateWindows();
- } break;
- }
- }
- }
-
- /**
- * This class represents information about a window from the
- * surface flinger to the accessibility framework.
- */
- public static class AccessibilityWindow {
- private static final Region TEMP_REGION = new Region();
- private static final RectF TEMP_RECTF = new RectF();
- // Data
- private IWindow mWindow;
- private int mDisplayId;
- private int mFlags;
- private int mType;
- private int mPrivateFlags;
- private boolean mIsPIPMenu;
- private boolean mIsFocused;
- private boolean mShouldMagnify;
- private boolean mIgnoreDuetoRecentsAnimation;
- private boolean mIsTrustedOverlay;
- private final Region mTouchableRegionInScreen = new Region();
- private final Region mTouchableRegionInWindow = new Region();
- private final Region mLetterBoxBounds = new Region();
- private WindowInfo mWindowInfo;
-
- /**
- * Returns the instance after initializing the internal data.
- * @param service The window manager service.
- * @param inputWindowHandle The window from the surface flinger.
- * @param inverseMatrix The magnification spec inverse matrix.
- */
- public static AccessibilityWindow initializeData(WindowManagerService service,
- InputWindowHandle inputWindowHandle, Matrix inverseMatrix, IBinder pipIBinder,
- Matrix displayMatrix) {
- final IWindow window = inputWindowHandle.getWindow();
- final WindowState windowState = window != null ? service.mWindowMap.get(
- window.asBinder()) : null;
-
- final AccessibilityWindow instance = new AccessibilityWindow();
-
- instance.mWindow = inputWindowHandle.getWindow();
- instance.mDisplayId = inputWindowHandle.displayId;
- instance.mFlags = inputWindowHandle.layoutParamsFlags;
- instance.mType = inputWindowHandle.layoutParamsType;
- instance.mIsPIPMenu = inputWindowHandle.getWindow().asBinder().equals(pipIBinder);
-
- // TODO (b/199357848): gets the private flag of the window from other way.
- instance.mPrivateFlags = windowState != null ? windowState.mAttrs.privateFlags : 0;
- // TODO (b/199358208) : using new way to implement the focused window.
- instance.mIsFocused = windowState != null && windowState.isFocused();
- instance.mShouldMagnify = windowState == null || windowState.shouldMagnify();
-
- final RecentsAnimationController controller = service.getRecentsAnimationController();
- instance.mIgnoreDuetoRecentsAnimation = windowState != null && controller != null
- && controller.shouldIgnoreForAccessibility(windowState);
- instance.mIsTrustedOverlay = inputWindowHandle.trustedOverlay;
-
- // TODO (b/199358388) : gets the letterbox bounds of the window from other way.
- if (windowState != null && windowState.areAppWindowBoundsLetterboxed()) {
- getLetterBoxBounds(windowState, instance.mLetterBoxBounds);
- }
-
- final Rect windowFrame = new Rect(inputWindowHandle.frameLeft,
- inputWindowHandle.frameTop, inputWindowHandle.frameRight,
- inputWindowHandle.frameBottom);
- getTouchableRegionInWindow(instance.mShouldMagnify, inputWindowHandle.touchableRegion,
- instance.mTouchableRegionInWindow, windowFrame, inverseMatrix, displayMatrix);
- getUnMagnifiedTouchableRegion(instance.mShouldMagnify,
- inputWindowHandle.touchableRegion, instance.mTouchableRegionInScreen,
- inverseMatrix, displayMatrix);
- instance.mWindowInfo = windowState != null
- ? windowState.getWindowInfo() : getWindowInfoForWindowlessWindows(instance);
-
- return instance;
- }
-
- /**
- * Returns the touchable region in the screen.
- * @param outRegion The touchable region.
- */
- public void getTouchableRegionInScreen(Region outRegion) {
- outRegion.set(mTouchableRegionInScreen);
- }
-
- /**
- * Returns the touchable region in the window.
- * @param outRegion The touchable region.
- */
- public void getTouchableRegionInWindow(Region outRegion) {
- outRegion.set(mTouchableRegionInWindow);
- }
-
- /**
- * @return the layout parameter flag {@link android.view.WindowManager.LayoutParams#flags}.
- */
- public int getFlags() {
- return mFlags;
- }
-
- /**
- * @return the layout parameter type {@link android.view.WindowManager.LayoutParams#type}.
- */
- public int getType() {
- return mType;
- }
-
- /**
- * @return the layout parameter private flag
- * {@link android.view.WindowManager.LayoutParams#privateFlags}.
- */
- public int getPrivateFlag() {
- return mPrivateFlags;
- }
-
- /**
- * @return the windowInfo {@link WindowInfo}.
- */
- public WindowInfo getWindowInfo() {
- return mWindowInfo;
- }
-
- /**
- * Gets the letter box bounds if activity bounds are letterboxed
- * or letterboxed for display cutout.
- *
- * @return {@code true} there's a letter box bounds.
- */
- public Boolean setLetterBoxBoundsIfNeeded(Region outBounds) {
- if (mLetterBoxBounds.isEmpty()) {
- return false;
- }
-
- outBounds.set(mLetterBoxBounds);
- return true;
- }
-
- /**
- * @return true if this window should be magnified.
- */
- public boolean shouldMagnify() {
- return mShouldMagnify;
- }
-
- /**
- * @return true if this window is focused.
- */
- public boolean isFocused() {
- return mIsFocused;
- }
-
- /**
- * @return true if it's running the recent animation but not the target app.
- */
- public boolean ignoreRecentsAnimationForAccessibility() {
- return mIgnoreDuetoRecentsAnimation;
- }
-
- /**
- * @return true if this window is the trusted overlay.
- */
- public boolean isTrustedOverlay() {
- return mIsTrustedOverlay;
- }
-
- /**
- * @return true if this window is the navigation bar with the gesture mode.
- */
- public boolean isUntouchableNavigationBar() {
- if (mType != WindowManager.LayoutParams.TYPE_NAVIGATION_BAR) {
- return false;
- }
-
- return mTouchableRegionInScreen.isEmpty();
- }
-
- /**
- * @return true if this window is PIP menu.
- */
- public boolean isPIPMenu() {
- return mIsPIPMenu;
- }
-
- private static void getTouchableRegionInWindow(boolean shouldMagnify, Region inRegion,
- Region outRegion, Rect frame, Matrix inverseMatrix, Matrix displayMatrix) {
- // Some modal windows, like the activity with Theme.dialog, has the full screen
- // as its touchable region, but its window frame is smaller than the touchable
- // region. The region we report should be the touchable area in the window frame
- // for the consistency and match developers expectation.
- // So we need to make the intersection between the frame and touchable region to
- // obtain the real touch region in the screen.
- Region touchRegion = TEMP_REGION;
- touchRegion.set(inRegion);
- touchRegion.op(frame, Region.Op.INTERSECT);
-
- getUnMagnifiedTouchableRegion(shouldMagnify, touchRegion, outRegion, inverseMatrix,
- displayMatrix);
- }
-
- /**
- * Gets the un-magnified touchable region. If this window can be magnified and magnifying,
- * we will transform the input touchable region by applying the inverse matrix of the
- * magnification spec to get the un-magnified touchable region.
- * @param shouldMagnify The window can be magnified.
- * @param inRegion The touchable region of this window.
- * @param outRegion The un-magnified touchable region of this window.
- * @param inverseMatrix The inverse matrix of the magnification spec.
- * @param displayMatrix The display transform matrix which takes display coordinates to
- * logical display coordinates.
- */
- private static void getUnMagnifiedTouchableRegion(boolean shouldMagnify, Region inRegion,
- Region outRegion, Matrix inverseMatrix, Matrix displayMatrix) {
- if ((!shouldMagnify || inverseMatrix.isIdentity()) && displayMatrix.isIdentity()) {
- outRegion.set(inRegion);
- return;
- }
-
- forEachRect(inRegion, rect -> {
- // Move to origin as all transforms are captured by the matrix.
- RectF windowFrame = TEMP_RECTF;
- windowFrame.set(rect);
-
- inverseMatrix.mapRect(windowFrame);
- displayMatrix.mapRect(windowFrame);
- // Union all rects.
- outRegion.union(new Rect((int) windowFrame.left, (int) windowFrame.top,
- (int) windowFrame.right, (int) windowFrame.bottom));
- });
- }
-
- private static WindowInfo getWindowInfoForWindowlessWindows(AccessibilityWindow window) {
- WindowInfo windowInfo = WindowInfo.obtain();
- windowInfo.displayId = window.mDisplayId;
- windowInfo.type = window.mType;
- windowInfo.token = window.mWindow.asBinder();
- windowInfo.hasFlagWatchOutsideTouch = (window.mFlags
- & WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH) != 0;
- windowInfo.inPictureInPicture = false;
-
- // There only are two windowless windows now, one is split window, and the other
- // one is PIP.
- if (windowInfo.type == TYPE_DOCK_DIVIDER) {
- windowInfo.title = "Splitscreen Divider";
- } else if (window.mIsPIPMenu) {
- windowInfo.title = "Picture-in-Picture menu";
- }
- return windowInfo;
- }
-
- private static void getLetterBoxBounds(WindowState windowState, Region outRegion) {
- final Rect letterboxInsets = windowState.mActivityRecord.getLetterboxInsets();
- final Rect nonLetterboxRect = windowState.getBounds();
-
- nonLetterboxRect.inset(letterboxInsets);
- outRegion.set(windowState.getBounds());
- outRegion.op(nonLetterboxRect, Region.Op.DIFFERENCE);
- }
-
- @Override
- public String toString() {
- String builder = "A11yWindow=[" + mWindow.asBinder()
- + ", displayId=" + mDisplayId
- + ", flag=0x" + Integer.toHexString(mFlags)
- + ", type=" + mType
- + ", privateFlag=0x" + Integer.toHexString(mPrivateFlags)
- + ", focused=" + mIsFocused
- + ", shouldMagnify=" + mShouldMagnify
- + ", ignoreDuetoRecentsAnimation=" + mIgnoreDuetoRecentsAnimation
- + ", isTrustedOverlay=" + mIsTrustedOverlay
- + ", regionInScreen=" + mTouchableRegionInScreen
- + ", touchableRegion=" + mTouchableRegionInWindow
- + ", letterBoxBounds=" + mLetterBoxBounds
- + ", isPIPMenu=" + mIsPIPMenu
- + ", windowInfo=" + mWindowInfo
- + "]";
-
- return builder;
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
index 1bb9ca7..48dd2f4 100644
--- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
+++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
@@ -55,6 +55,7 @@
FIRST_ORDERED_ID,
COMMUNAL_MODE_ORDERED_ID,
PERMISSION_POLICY_ORDERED_ID,
+ VIRTUAL_DEVICE_SERVICE_ORDERED_ID,
LAST_ORDERED_ID // Update this when adding new ids
})
@Retention(RetentionPolicy.SOURCE)
@@ -76,10 +77,16 @@
public static final int PERMISSION_POLICY_ORDERED_ID = 2;
/**
+ * The identifier for {@link com.android.server.companion.virtual.VirtualDeviceManagerService}
+ * interceptor.
+ */
+ public static final int VIRTUAL_DEVICE_SERVICE_ORDERED_ID = 3;
+
+ /**
* The final id, used by the framework to determine the valid range of ids. Update this when
* adding new ids.
*/
- static final int LAST_ORDERED_ID = PERMISSION_POLICY_ORDERED_ID;
+ static final int LAST_ORDERED_ID = VIRTUAL_DEVICE_SERVICE_ORDERED_ID;
/**
* Data class for storing the various arguments needed for activity interception.
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c929cbb..c2765db 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1546,10 +1546,17 @@
void onDisplayChanged(DisplayContent dc) {
DisplayContent prevDc = mDisplayContent;
super.onDisplayChanged(dc);
- if (prevDc == null || prevDc == mDisplayContent) {
+ if (prevDc == mDisplayContent) {
return;
}
+ mDisplayContent.onRunningActivityChanged();
+
+ if (prevDc == null) {
+ return;
+ }
+ prevDc.onRunningActivityChanged();
+
// TODO(b/169035022): move to a more-appropriate place.
mTransitionController.collect(this);
if (prevDc.mOpeningApps.remove(this)) {
@@ -1623,6 +1630,11 @@
}
// Trigger TaskInfoChanged to update the camera compat UI.
getTask().dispatchTaskInfoChangedIfNeeded(true /* force */);
+ // TaskOrganizerController#onTaskInfoChanged adds pending task events to the queue waiting
+ // for the surface placement to be ready. So need to trigger surface placement to dispatch
+ // events to avoid stale state for the camera compat control.
+ getDisplayContent().setLayoutNeeded();
+ mWmService.mWindowPlacerLocked.performSurfacePlacement();
}
void updateCameraCompatStateFromUser(@CameraCompatControlState int state) {
@@ -3900,6 +3912,7 @@
// Reset the last saved PiP snap fraction on removal.
mDisplayContent.mPinnedTaskController.onActivityHidden(mActivityComponent);
+ mDisplayContent.onRunningActivityChanged();
mWmService.mEmbeddedWindowController.onActivityRemoved(this);
mRemovingFromDisplay = false;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 3cecce2..9d53e5a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -364,6 +364,17 @@
/** Check if placing task or activity on specified display is allowed. */
boolean canPlaceEntityOnDisplay(int displayId, int callingPid, int callingUid,
ActivityInfo activityInfo) {
+ return canPlaceEntityOnDisplay(displayId, callingPid, callingUid, null /* task */,
+ activityInfo);
+ }
+
+ boolean canPlaceEntityOnDisplay(int displayId, int callingPid, int callingUid, Task task) {
+ return canPlaceEntityOnDisplay(displayId, callingPid, callingUid, task,
+ null /* activityInfo */);
+ }
+
+ private boolean canPlaceEntityOnDisplay(int displayId, int callingPid, int callingUid,
+ Task task, ActivityInfo activityInfo) {
if (displayId == DEFAULT_DISPLAY) {
// No restrictions for the default display.
return true;
@@ -372,12 +383,31 @@
// Can't launch on secondary displays if feature is not supported.
return false;
}
+
if (!isCallerAllowedToLaunchOnDisplay(callingPid, callingUid, displayId, activityInfo)) {
// Can't place activities to a display that has restricted launch rules.
// In this case the request should be made by explicitly adding target display id and
// by caller with corresponding permissions. See #isCallerAllowedToLaunchOnDisplay().
return false;
}
+
+ final DisplayContent displayContent =
+ mRootWindowContainer.getDisplayContentOrCreate(displayId);
+ if (displayContent != null && displayContent.mDwpcHelper.hasController()) {
+ final ArrayList<ActivityInfo> activities = new ArrayList<>();
+ if (activityInfo != null) {
+ activities.add(activityInfo);
+ }
+ if (task != null) {
+ task.forAllActivities((r) -> {
+ activities.add(r.info);
+ });
+ }
+ if (!displayContent.mDwpcHelper.canContainActivities(activities)) {
+ return false;
+ }
+ }
+
return true;
}
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index 9661e8d..5919806 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -34,6 +34,7 @@
import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature;
import static com.android.server.wm.DisplayAreaPolicyBuilder.HierarchyBuilder;
+import android.annotation.Nullable;
import android.content.res.Resources;
import android.os.Bundle;
import android.text.TextUtils;
@@ -86,6 +87,9 @@
*/
public abstract TaskDisplayArea getDefaultTaskDisplayArea();
+ /** Returns the {@link TaskDisplayArea} specified by launch options. */
+ public abstract TaskDisplayArea getTaskDisplayArea(@Nullable Bundle options);
+
/** Provider for platform-default display area policy. */
static final class DefaultProvider implements DisplayAreaPolicy.Provider {
@Override
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
index 3d7ac6c..8e21d96 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
@@ -27,12 +27,19 @@
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_LAST;
import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityOptions;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.window.DisplayAreaOrganizer;
+import android.window.WindowContainerToken;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.server.policy.WindowManagerPolicy;
import java.util.ArrayList;
@@ -43,6 +50,7 @@
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
+import java.util.function.Function;
/**
* A builder for instantiating a complex {@link DisplayAreaPolicy}
@@ -149,6 +157,8 @@
**/
@Nullable private BiFunction<Integer, Bundle, RootDisplayArea> mSelectRootForWindowFunc;
+ @Nullable private Function<Bundle, TaskDisplayArea> mSelectTaskDisplayAreaFunc;
+
/** Defines the root hierarchy for the whole logical display. */
DisplayAreaPolicyBuilder setRootHierarchy(HierarchyBuilder rootHierarchyBuilder) {
mRootHierarchyBuilder = rootHierarchyBuilder;
@@ -176,14 +186,25 @@
}
/**
+ * The policy will use this function to find the {@link TaskDisplayArea}.
+ * @see DefaultSelectTaskDisplayAreaFunction as an example.
+ */
+ DisplayAreaPolicyBuilder setSelectTaskDisplayAreaFunc(
+ Function<Bundle, TaskDisplayArea> selectTaskDisplayAreaFunc) {
+ mSelectTaskDisplayAreaFunc = selectTaskDisplayAreaFunc;
+ return this;
+ }
+
+ /**
* Makes sure the setting meets the requirement:
* 1. {@link #mRootHierarchyBuilder} must be set.
* 2. {@link RootDisplayArea} and {@link TaskDisplayArea} must have unique ids.
* 3. {@link Feature} below the same {@link RootDisplayArea} must have unique ids.
* 4. There must be exactly one {@link HierarchyBuilder} that contains the IME container.
* 5. There must be exactly one {@link HierarchyBuilder} that contains the default
- * {@link TaskDisplayArea} with id {@link FEATURE_DEFAULT_TASK_CONTAINER}.
- * 6. None of the ids is greater than {@link FEATURE_VENDOR_LAST}.
+ * {@link TaskDisplayArea} with id
+ * {@link DisplayAreaOrganizer#FEATURE_DEFAULT_TASK_CONTAINER}.
+ * 6. None of the ids is greater than {@link DisplayAreaOrganizer#FEATURE_VENDOR_LAST}.
*/
private void validate() {
if (mRootHierarchyBuilder == null) {
@@ -250,7 +271,7 @@
* {@link Feature} below the same {@link RootDisplayArea} must have unique ids, but
* {@link Feature} below different {@link RootDisplayArea} can have the same id so that we can
* organize them together.
- * None of the ids is greater than {@link FEATURE_VENDOR_LAST}
+ * None of the ids is greater than {@link DisplayAreaOrganizer#FEATURE_VENDOR_LAST}
*
* @param uniqueIdSet ids of {@link RootDisplayArea} and {@link TaskDisplayArea} that must be
* unique,
@@ -323,7 +344,7 @@
mRootHierarchyBuilder.mRoot, displayAreaGroupRoots);
}
return new Result(wmService, mRootHierarchyBuilder.mRoot, displayAreaGroupRoots,
- mSelectRootForWindowFunc);
+ mSelectRootForWindowFunc, mSelectTaskDisplayAreaFunc);
}
/**
@@ -368,6 +389,51 @@
}
/**
+ * The default function to find {@link TaskDisplayArea} if there's no other function set
+ * through {@link #setSelectTaskDisplayAreaFunc(Function)}.
+ * <p>
+ * This function returns {@link TaskDisplayArea} specified by
+ * {@link ActivityOptions#getLaunchTaskDisplayArea()} if it is not {@code null}. Otherwise,
+ * returns {@link DisplayContent#getDefaultTaskDisplayArea()}.
+ * </p>
+ */
+ private static class DefaultSelectTaskDisplayAreaFunction implements
+ Function<Bundle, TaskDisplayArea> {
+ private final TaskDisplayArea mDefaultTaskDisplayArea;
+ private final int mDisplayId;
+
+ DefaultSelectTaskDisplayAreaFunction(TaskDisplayArea defaultTaskDisplayArea) {
+ mDefaultTaskDisplayArea = defaultTaskDisplayArea;
+ mDisplayId = defaultTaskDisplayArea.getDisplayId();
+ }
+
+ @Override
+ public TaskDisplayArea apply(@Nullable Bundle options) {
+ if (options == null) {
+ return mDefaultTaskDisplayArea;
+ }
+ final ActivityOptions activityOptions = new ActivityOptions(options);
+ final WindowContainerToken tdaToken = activityOptions.getLaunchTaskDisplayArea();
+ if (tdaToken == null) {
+ return mDefaultTaskDisplayArea;
+ }
+ final TaskDisplayArea tda = WindowContainer.fromBinder(tdaToken.asBinder())
+ .asTaskDisplayArea();
+ if (tda == null) {
+ ProtoLog.w(WM_DEBUG_WINDOW_ORGANIZER, "The TaskDisplayArea with %s does not "
+ + "exist.", tdaToken);
+ return mDefaultTaskDisplayArea;
+ }
+ if (tda.getDisplayId() != mDisplayId) {
+ throw new IllegalArgumentException("The specified TaskDisplayArea must attach "
+ + "to Display#" + mDisplayId + ", but it is in Display#"
+ + tda.getDisplayId());
+ }
+ return tda;
+ }
+ }
+
+ /**
* Builder to define {@link Feature} and {@link DisplayArea} hierarchy under a
* {@link RootDisplayArea}
*/
@@ -722,11 +788,13 @@
static class Result extends DisplayAreaPolicy {
final List<RootDisplayArea> mDisplayAreaGroupRoots;
final BiFunction<Integer, Bundle, RootDisplayArea> mSelectRootForWindowFunc;
+ private final Function<Bundle, TaskDisplayArea> mSelectTaskDisplayAreaFunc;
private final TaskDisplayArea mDefaultTaskDisplayArea;
Result(WindowManagerService wmService, RootDisplayArea root,
List<RootDisplayArea> displayAreaGroupRoots,
- BiFunction<Integer, Bundle, RootDisplayArea> selectRootForWindowFunc) {
+ BiFunction<Integer, Bundle, RootDisplayArea> selectRootForWindowFunc,
+ Function<Bundle, TaskDisplayArea> selectTaskDisplayAreaFunc) {
super(wmService, root);
mDisplayAreaGroupRoots = Collections.unmodifiableList(displayAreaGroupRoots);
mSelectRootForWindowFunc = selectRootForWindowFunc;
@@ -740,6 +808,9 @@
throw new IllegalStateException(
"No display area with FEATURE_DEFAULT_TASK_CONTAINER");
}
+ mSelectTaskDisplayAreaFunc = selectTaskDisplayAreaFunc != null
+ ? selectTaskDisplayAreaFunc
+ : new DefaultSelectTaskDisplayAreaFunction(mDefaultTaskDisplayArea);
}
@Override
@@ -796,6 +867,12 @@
public TaskDisplayArea getDefaultTaskDisplayArea() {
return mDefaultTaskDisplayArea;
}
+
+ @NonNull
+ @Override
+ public TaskDisplayArea getTaskDisplayArea(@Nullable Bundle options) {
+ return mSelectTaskDisplayAreaFunc.apply(options);
+ }
}
static class PendingArea {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8e0435f..d91f48e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -222,7 +222,6 @@
import android.view.WindowManager;
import android.view.WindowManager.DisplayImePolicy;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
-import android.window.DisplayWindowPolicyController;
import android.window.IDisplayAreaOrganizer;
import android.window.TransitionRequestInfo;
@@ -698,12 +697,11 @@
boolean mDontMoveToTop;
/**
- * The policy controller of the windows that can be displayed on the virtual display.
+ * The helper of policy controller.
*
- * @see DisplayWindowPolicyController
+ * @see DisplayWindowPolicyControllerHelper
*/
- @Nullable
- DisplayWindowPolicyController mDisplayWindowPolicyController;
+ DisplayWindowPolicyControllerHelper mDwpcHelper;
private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
WindowStateAnimator winAnimator = w.mWinAnimator;
@@ -2739,8 +2737,7 @@
mDisplayInfo.copyFrom(newDisplayInfo);
}
- mDisplayWindowPolicyController =
- displayManagerInternal.getDisplayWindowPolicyController(mDisplayId);
+ mDwpcHelper = new DisplayWindowPolicyControllerHelper(this);
}
updateBaseDisplayMetrics(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight,
@@ -2781,7 +2778,10 @@
if (displayMetricsChanged || physicalDisplayChanged) {
if (physicalDisplayChanged) {
// Reapply the window settings as the underlying physical display has changed.
- mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
+ // Do not include rotation settings here, postpone them until the display
+ // metrics are updated as rotation settings might depend on them
+ mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this,
+ /* includeRotationSettings */ false);
}
// If there is an override set for base values - use it, otherwise use new values.
@@ -3450,10 +3450,7 @@
mInputMonitor.dump(pw, " ");
pw.println();
mInsetsStateController.dump(prefix, pw);
- if (mDisplayWindowPolicyController != null) {
- pw.println();
- mDisplayWindowPolicyController.dump(prefix, pw);
- }
+ mDwpcHelper.dump(prefix, pw);
}
@Override
@@ -3681,6 +3678,11 @@
return true;
}
+ /** Update the top activity and the uids of non-finishing activity */
+ void onRunningActivityChanged() {
+ mDwpcHelper.onRunningActivityChanged();
+ }
+
/** Called when the focused {@link TaskDisplayArea} on this display may have changed. */
void onLastFocusedTaskDisplayAreaChanged(@Nullable TaskDisplayArea taskDisplayArea) {
// Only record the TaskDisplayArea that handles orientation request.
@@ -6464,9 +6466,8 @@
DisplayArea findAreaForWindowType(int windowType, Bundle options,
boolean ownerCanManageAppToken, boolean roundedCornerOverlay) {
- // TODO(b/159767464): figure out how to find an appropriate TDA.
if (windowType >= FIRST_APPLICATION_WINDOW && windowType <= LAST_APPLICATION_WINDOW) {
- return getDefaultTaskDisplayArea();
+ return mDisplayAreaPolicy.getTaskDisplayArea(options);
}
// Return IME container here because it could be in one of sub RootDisplayAreas depending on
// the focused edit text. Also, the RootDisplayArea choosing strategy is implemented by
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index f0e8b8f..6e94dfed 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1122,6 +1122,7 @@
if (!mNavButtonForcedVisible) {
inOutFrame.inset(windowState.getLayoutingAttrs(
displayFrames.mRotation).providedInternalInsets);
+ inOutFrame.inset(win.mGivenContentInsets);
}
},
@@ -1190,9 +1191,12 @@
break;
}
mDisplayContent.setInsetProvider(insetsType, win, (displayFrames,
- windowState, inOutFrame) -> inOutFrame.inset(
- windowState.getLayoutingAttrs(displayFrames.mRotation)
- .providedInternalInsets), imeFrameProvider);
+ windowState, inOutFrame) -> {
+ inOutFrame.inset(
+ windowState.getLayoutingAttrs(displayFrames.mRotation)
+ .providedInternalInsets);
+ inOutFrame.inset(win.mGivenContentInsets);
+ }, imeFrameProvider);
mInsetsSourceWindowsExceptIme.add(win);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
new file mode 100644
index 0000000..60d2a5d
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ActivityInfo;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.window.DisplayWindowPolicyController;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+class DisplayWindowPolicyControllerHelper {
+
+ private final DisplayContent mDisplayContent;
+
+ /**
+ * The policy controller of the windows that can be displayed on the virtual display.
+ *
+ * @see DisplayWindowPolicyController
+ */
+ @Nullable
+ private DisplayWindowPolicyController mDisplayWindowPolicyController;
+
+ /**
+ * The top non-finishing activity of this display.
+ */
+ private ActivityRecord mTopRunningActivity = null;
+
+ /**
+ * All the uids of non-finishing activity on this display.
+ * @see DisplayWindowPolicyController#onRunningAppsChanged(ArraySet)
+ */
+ private ArraySet<Integer> mRunningUid = new ArraySet<>();
+
+ DisplayWindowPolicyControllerHelper(DisplayContent displayContent) {
+ mDisplayContent = displayContent;
+ mDisplayWindowPolicyController = mDisplayContent.mWmService.mDisplayManagerInternal
+ .getDisplayWindowPolicyController(mDisplayContent.mDisplayId);
+ }
+
+ /**
+ * Return {@code true} if there is DisplayWindowPolicyController.
+ */
+ public boolean hasController() {
+ return mDisplayWindowPolicyController != null;
+ }
+
+ /**
+ * @see DisplayWindowPolicyController#canContainActivities(List)
+ */
+ public boolean canContainActivities(@NonNull List<ActivityInfo> activities) {
+ if (mDisplayWindowPolicyController == null) {
+ return true;
+ }
+ return mDisplayWindowPolicyController.canContainActivities(activities);
+ }
+
+ /**
+ * @see DisplayWindowPolicyController#keepActivityOnWindowFlagsChanged(ActivityInfo, int, int)
+ */
+ boolean keepActivityOnWindowFlagsChanged(ActivityInfo aInfo, int flagChanges,
+ int privateFlagChanges) {
+ if (mDisplayWindowPolicyController == null) {
+ return true;
+ }
+
+ if (!mDisplayWindowPolicyController.isInterestedWindowFlags(
+ flagChanges, privateFlagChanges)) {
+ return true;
+ }
+
+ return mDisplayWindowPolicyController.keepActivityOnWindowFlagsChanged(
+ aInfo, flagChanges, privateFlagChanges);
+ }
+
+ /** Update the top activity and the uids of non-finishing activity */
+ void onRunningActivityChanged() {
+ if (mDisplayWindowPolicyController == null) {
+ return;
+ }
+
+ // Update top activity.
+ ActivityRecord topActivity = mDisplayContent.getTopActivity(false /* includeFinishing */,
+ true /* includeOverlays */);
+ if (topActivity != mTopRunningActivity) {
+ mTopRunningActivity = topActivity;
+ mDisplayWindowPolicyController.onTopActivityChanged(
+ topActivity == null ? null : topActivity.info.getComponentName(),
+ topActivity == null
+ ? UserHandle.USER_NULL : topActivity.info.applicationInfo.uid);
+ }
+
+ // Update running uid.
+ final boolean[] notifyChanged = {false};
+ ArraySet<Integer> runningUids = new ArraySet<>();
+ mDisplayContent.forAllActivities((r) -> {
+ if (!r.finishing) {
+ notifyChanged[0] |= runningUids.add(r.getUid());
+ }
+ });
+
+ // We need to compare the size because if it is the following case, we can't know the
+ // existence of 3 in the forAllActivities() loop.
+ // Old set: 1,2,3
+ // New set: 1,2
+ if (notifyChanged[0] || (mRunningUid.size() != runningUids.size())) {
+ mRunningUid = runningUids;
+ mDisplayWindowPolicyController.onRunningAppsChanged(runningUids);
+ }
+ }
+
+ void dump(String prefix, PrintWriter pw) {
+ if (mDisplayWindowPolicyController != null) {
+ pw.println();
+ mDisplayWindowPolicyController.dump(prefix, pw);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 8260fd6..483c799 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -244,6 +244,10 @@
}
void applySettingsToDisplayLocked(DisplayContent dc) {
+ applySettingsToDisplayLocked(dc, /* includeRotationSettings */ true);
+ }
+
+ void applySettingsToDisplayLocked(DisplayContent dc, boolean includeRotationSettings) {
final DisplayInfo displayInfo = dc.getDisplayInfo();
final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
@@ -282,6 +286,8 @@
boolean dontMoveToTop = settings.mDontMoveToTop != null
? settings.mDontMoveToTop : false;
dc.mDontMoveToTop = dontMoveToTop;
+
+ if (includeRotationSettings) applyRotationSettingsToDisplayLocked(dc);
}
void applyRotationSettingsToDisplayLocked(DisplayContent dc) {
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index cbb473c..1955e30 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -175,7 +175,7 @@
* Overrides corners raidus for activities presented in the letterbox mode. If given value < 0,
* both it and a value of {@link
* com.android.internal.R.integer.config_letterboxActivityCornersRadius} will be ignored and
- * and corners of the activity won't be rounded.
+ * corners of the activity won't be rounded.
*/
void setLetterboxActivityCornersRadius(int cornersRadius) {
mLetterboxActivityCornersRadius = cornersRadius;
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 7d07357..8866343 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -150,7 +150,7 @@
if (mLetterbox == null) {
mLetterbox = new Letterbox(() -> mActivityRecord.makeChildSurface(null),
mActivityRecord.mWmService.mTransactionFactory,
- mLetterboxConfiguration::isLetterboxActivityCornersRounded,
+ this::shouldLetterboxHaveRoundedCorners,
this::getLetterboxBackgroundColor,
this::hasWallpaperBackgroudForLetterbox,
this::getLetterboxWallpaperBlurRadius,
@@ -175,6 +175,13 @@
}
}
+ private boolean shouldLetterboxHaveRoundedCorners() {
+ // TODO(b/214030873): remove once background is drawn for transparent activities
+ // Letterbox shouldn't have rounded corners if the activity is transparent
+ return mLetterboxConfiguration.isLetterboxActivityCornersRounded()
+ && mActivityRecord.fillsParent();
+ }
+
float getHorizontalPositionMultiplier(Configuration parentConfiguration) {
// Don't check resolved configuration because it may not be updated yet during
// configuration change.
@@ -257,8 +264,6 @@
@VisibleForTesting
boolean shouldShowLetterboxUi(WindowState mainWindow) {
return isSurfaceReadyAndVisible(mainWindow) && mainWindow.areAppWindowBoundsLetterboxed()
- // Check that an activity isn't transparent.
- && mActivityRecord.fillsParent()
// Check for FLAG_SHOW_WALLPAPER explicitly instead of using
// WindowContainer#showWallpaper because the later will return true when this
// activity is using blurred wallpaper for letterbox backgroud.
diff --git a/services/core/java/com/android/server/wm/OverlayHost.java b/services/core/java/com/android/server/wm/OverlayHost.java
new file mode 100644
index 0000000..14f8983
--- /dev/null
+++ b/services/core/java/com/android/server/wm/OverlayHost.java
@@ -0,0 +1,129 @@
+/*
+ * 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.wm;
+
+import android.content.res.Configuration;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to assist WindowContainer in the hosting of
+ * SurfacePackage based overlays. Manages overlays inside
+ * one parent control, and manages the lifetime of that parent control
+ * in order to obscure details from WindowContainer.
+ *
+ * Also handles multiplexing of event dispatch and tracking of overlays
+ * to make things easier for WindowContainer.
+ */
+class OverlayHost {
+ // Lazily initialized when required
+ SurfaceControl mSurfaceControl;
+ final ArrayList<SurfaceControlViewHost.SurfacePackage> mOverlays = new ArrayList<>();
+ final WindowManagerService mWmService;
+
+ OverlayHost(WindowManagerService wms) {
+ mWmService = wms;
+ }
+
+ void requireOverlaySurfaceControl() {
+ if (mSurfaceControl == null) {
+ final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(null)
+ .setContainerLayer()
+ .setHidden(true)
+ .setName("Overlay Host Leash");
+
+ mSurfaceControl = b.build();
+ }
+ }
+
+ void setParent(SurfaceControl.Transaction t, SurfaceControl newParent) {
+ if (mSurfaceControl == null) {
+ return;
+ }
+ t.reparent(mSurfaceControl, newParent);
+ if (newParent != null) {
+ t.show(mSurfaceControl);
+ } else {
+ t.hide(mSurfaceControl);
+ }
+ }
+
+ void setLayer(SurfaceControl.Transaction t, int layer) {
+ if (mSurfaceControl != null) {
+ t.setLayer(mSurfaceControl, layer);
+ }
+ }
+
+ void addOverlay(SurfaceControlViewHost.SurfacePackage p, SurfaceControl currentParent) {
+ requireOverlaySurfaceControl();
+ mOverlays.add(p);
+
+ SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
+ t.reparent(p.getSurfaceControl(), mSurfaceControl)
+ .show(p.getSurfaceControl());
+ setParent(t,currentParent);
+ t.apply();
+ }
+
+ boolean removeOverlay(SurfaceControlViewHost.SurfacePackage p) {
+ final SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
+
+ for (int i = mOverlays.size() - 1; i >= 0; i--) {
+ SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i);
+ if (l.getSurfaceControl().isSameSurface(p.getSurfaceControl())) {
+ mOverlays.remove(i);
+ t.reparent(l.getSurfaceControl(), null);
+ l.release();
+ }
+ }
+ t.apply();
+ return mOverlays.size() > 0;
+ }
+
+ void dispatchConfigurationChanged(Configuration c) {
+ for (int i = mOverlays.size() - 1; i >= 0; i--) {
+ SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i);
+ try {
+ l.getRemoteInterface().onConfigurationChanged(c);
+ } catch (Exception e) {
+ removeOverlay(l);
+ }
+ }
+ }
+
+ private void dispatchDetachedFromWindow() {
+ for (int i = mOverlays.size() - 1; i >= 0; i--) {
+ SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i);
+ try {
+ l.getRemoteInterface().onDispatchDetachedFromWindow();
+ } catch (Exception e) {
+ // Oh well we are tearing down anyway.
+ }
+ l.release();
+ }
+ }
+
+ void release() {
+ dispatchDetachedFromWindow();
+ mOverlays.clear();
+ final SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
+ t.remove(mSurfaceControl).apply();
+ mSurfaceControl = null;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java
index f9d7b53..6ed59e9 100644
--- a/services/core/java/com/android/server/wm/ShellRoot.java
+++ b/services/core/java/com/android/server/wm/ShellRoot.java
@@ -25,14 +25,15 @@
import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.graphics.Point;
+import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import android.view.DisplayInfo;
import android.view.IWindow;
import android.view.SurfaceControl;
+import android.view.WindowInfo;
import android.view.WindowManager;
import android.view.animation.Animation;
@@ -135,12 +136,47 @@
ANIMATION_TYPE_WINDOW_ANIMATION);
}
- @Nullable
- IBinder getAccessibilityWindowToken() {
- if (mAccessibilityWindow != null) {
- return mAccessibilityWindow.asBinder();
+ WindowInfo getWindowInfo() {
+ if (mShellRootLayer != SHELL_ROOT_LAYER_DIVIDER
+ && mShellRootLayer != SHELL_ROOT_LAYER_PIP) {
+ return null;
}
- return null;
+ if (mShellRootLayer == SHELL_ROOT_LAYER_DIVIDER
+ && !mDisplayContent.getDefaultTaskDisplayArea().isSplitScreenModeActivated()) {
+ return null;
+ }
+ if (mShellRootLayer == SHELL_ROOT_LAYER_PIP
+ && mDisplayContent.getDefaultTaskDisplayArea().getRootPinnedTask() == null) {
+ return null;
+ }
+ if (mAccessibilityWindow == null) {
+ return null;
+ }
+ WindowInfo windowInfo = WindowInfo.obtain();
+ windowInfo.displayId = mToken.getDisplayArea().getDisplayContent().mDisplayId;
+ windowInfo.type = mToken.windowType;
+ windowInfo.layer = mToken.getWindowLayerFromType();
+ windowInfo.token = mAccessibilityWindow.asBinder();
+ windowInfo.focused = false;
+ windowInfo.hasFlagWatchOutsideTouch = false;
+ final Rect regionRect = new Rect();
+
+
+ // DividerView
+ if (mShellRootLayer == SHELL_ROOT_LAYER_DIVIDER) {
+ windowInfo.inPictureInPicture = false;
+ mDisplayContent.getDockedDividerController().getTouchRegion(regionRect);
+ windowInfo.regionInScreen.set(regionRect);
+ windowInfo.title = "Splitscreen Divider";
+ }
+ // PipMenuView
+ if (mShellRootLayer == SHELL_ROOT_LAYER_PIP) {
+ windowInfo.inPictureInPicture = true;
+ mDisplayContent.getDefaultTaskDisplayArea().getRootPinnedTask().getBounds(regionRect);
+ windowInfo.regionInScreen.set(regionRect);
+ windowInfo.title = "Picture-in-Picture menu";
+ }
+ return windowInfo;
}
void setAccessibilityWindow(IWindow window) {
@@ -161,5 +197,9 @@
mAccessibilityWindow = null;
}
}
+ if (mDisplayContent.mWmService.mAccessibilityController.hasCallbacks()) {
+ mDisplayContent.mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(
+ mDisplayContent.getDisplayId());
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 43038ce..cfd1f6e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -125,6 +125,8 @@
import static com.android.server.wm.TaskProto.SURFACE_WIDTH;
import static com.android.server.wm.TaskProto.TASK_FRAGMENT;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
+import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
+import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainerChildProto.TASK;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
@@ -609,6 +611,8 @@
*/
ActivityRecord mChildPipActivity;
+ boolean mLastSurfaceShowing = true;
+
private Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent,
Intent _affinityIntent, String _affinity, String _rootAffinity,
ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
@@ -1749,7 +1753,7 @@
*/
boolean canBeLaunchedOnDisplay(int displayId) {
return mTaskSupervisor.canPlaceEntityOnDisplay(displayId,
- -1 /* don't check PID */, -1 /* don't check UID */, null /* activityInfo */);
+ -1 /* don't check PID */, -1 /* don't check UID */, this);
}
/**
@@ -3308,6 +3312,17 @@
if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
scheduleAnimation();
}
+
+ // We intend to let organizer manage task visibility but it doesn't
+ // have enough information until we finish shell transitions.
+ // In the mean time we do an easy fix here.
+ final boolean show = isVisible() || isAnimating(TRANSITION | PARENTS);
+ if (mSurfaceControl != null) {
+ if (show != mLastSurfaceShowing) {
+ getSyncTransaction().setVisibility(mSurfaceControl, show);
+ }
+ }
+ mLastSurfaceShowing = show;
}
@Override
@@ -5971,7 +5986,19 @@
}
void reparent(TaskDisplayArea newParent, boolean onTop) {
- reparent(newParent, onTop ? POSITION_TOP : POSITION_BOTTOM);
+ if (newParent == null) {
+ throw new IllegalArgumentException("Task can't reparent to null " + this);
+ }
+
+ if (getParent() == newParent) {
+ throw new IllegalArgumentException("Task=" + this + " already child of " + newParent);
+ }
+
+ if (canBeLaunchedOnDisplay(newParent.getDisplayId())) {
+ reparent(newParent, onTop ? POSITION_TOP : POSITION_BOTTOM);
+ } else {
+ Slog.w(TAG, "Task=" + this + " can't reparent to " + newParent);
+ }
}
void setLastRecentsAnimationTransaction(@NonNull PictureInPictureSurfaceTransaction transaction,
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index d133ca9..b681a96 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -398,8 +398,16 @@
Slog.d(TAG, "setResumedActivity taskFrag:" + this + " + from: "
+ mResumedActivity + " to:" + r + " reason:" + reason);
}
+ final ActivityRecord prevR = mResumedActivity;
mResumedActivity = r;
mTaskSupervisor.updateTopResumedActivityIfNeeded();
+ if (r == null && prevR.mDisplayContent != null
+ && prevR.mDisplayContent.getFocusedRootTask() == null) {
+ // Only need to notify DWPC when no activity will resume.
+ prevR.mDisplayContent.onRunningActivityChanged();
+ } else if (r != null) {
+ r.mDisplayContent.onRunningActivityChanged();
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index c7fdefc..123ca88 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -22,6 +22,7 @@
import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -497,6 +498,23 @@
return null;
}
+ private boolean shouldSendEventWhenTaskInvisible(@NonNull Task task,
+ @NonNull PendingTaskFragmentEvent event) {
+ final TaskFragmentOrganizerState state =
+ mTaskFragmentOrganizerState.get(event.mTaskFragmentOrg.asBinder());
+ final TaskFragmentInfo lastInfo = state.mLastSentTaskFragmentInfos.get(event.mTaskFragment);
+ final TaskFragmentInfo info = event.mTaskFragment.getTaskFragmentInfo();
+ // Send an info changed callback if this event is for the last activities to finish in a
+ // Task so that the {@link TaskFragmentOrganizer} can delete this TaskFragment. Otherwise,
+ // the Task may be removed before it becomes visible again to send this event because it no
+ // longer has activities. As a result, the organizer will never get this info changed event
+ // and will not delete the TaskFragment because the organizer thinks the TaskFragment still
+ // has running activities.
+ return event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED
+ && task.topRunningActivity() == null && lastInfo != null
+ && lastInfo.getRunningActivityCount() > 0 && info.getRunningActivityCount() == 0;
+ }
+
void dispatchPendingEvents() {
if (mAtmService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()
|| mPendingTaskFragmentEvents.isEmpty()) {
@@ -510,7 +528,8 @@
final PendingTaskFragmentEvent event = mPendingTaskFragmentEvents.get(i);
final Task task = event.mTaskFragment != null ? event.mTaskFragment.getTask() : null;
if (task != null && (task.lastActiveTime <= event.mDeferTime
- || !isTaskVisible(task, visibleTasks, invisibleTasks))) {
+ || !(isTaskVisible(task, visibleTasks, invisibleTasks)
+ || shouldSendEventWhenTaskInvisible(task, event)))) {
// Defer sending events to the TaskFragment until the host task is active again.
event.mDeferTime = task.lastActiveTime;
continue;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 61acb97..137d7f8 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -85,6 +85,7 @@
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
import android.view.SurfaceControl.Builder;
import android.view.SurfaceSession;
import android.view.TaskTransitionSpec;
@@ -312,6 +313,8 @@
private final List<WindowContainerListener> mListeners = new ArrayList<>();
+ private OverlayHost mOverlayHost;
+
WindowContainer(WindowManagerService wms) {
mWmService = wms;
mTransitionController = mWmService.mAtmService.getTransitionController();
@@ -341,6 +344,9 @@
super.onConfigurationChanged(newParentConfig);
updateSurfacePositionNonOrganized();
scheduleAnimation();
+ if (mOverlayHost != null) {
+ mOverlayHost.dispatchConfigurationChanged(getConfiguration());
+ }
}
void reparent(WindowContainer newParent, int position) {
@@ -487,6 +493,11 @@
t.reparent(sc, mSurfaceControl);
}
}
+
+ if (mOverlayHost != null) {
+ mOverlayHost.setParent(t, mSurfaceControl);
+ }
+
scheduleAnimation();
}
@@ -632,6 +643,10 @@
mLastSurfacePosition.set(0, 0);
scheduleAnimation();
}
+ if (mOverlayHost != null) {
+ mOverlayHost.release();
+ mOverlayHost = null;
+ }
// This must happen after updating the surface so that sync transactions can be handled
// properly.
@@ -2308,6 +2323,9 @@
wc.assignLayer(t, layer++);
}
}
+ if (mOverlayHost != null) {
+ mOverlayHost.setLayer(t, layer++);
+ }
}
void assignChildLayers() {
@@ -3570,4 +3588,18 @@
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type, @Nullable AnimationAdapter snapshotAnim);
}
+
+ void addOverlay(SurfaceControlViewHost.SurfacePackage overlay) {
+ if (mOverlayHost == null) {
+ mOverlayHost = new OverlayHost(mWmService);
+ }
+ mOverlayHost.addOverlay(overlay, mSurfaceControl);
+ }
+
+ void removeOverlay(SurfaceControlViewHost.SurfacePackage overlay) {
+ if (mOverlayHost != null && !mOverlayHost.removeOverlay(overlay)) {
+ mOverlayHost.release();
+ mOverlayHost = null;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 62c674b..1ab191b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -32,6 +32,7 @@
import android.view.InputChannel;
import android.view.MagnificationSpec;
import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControlViewHost;
import android.view.WindowInfo;
import android.view.WindowManager.DisplayImePolicy;
@@ -767,4 +768,16 @@
* {@code false} otherwise.
*/
public abstract boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken);
+
+ /**
+ * Internal methods for other parts of SystemServer to manage
+ * SurfacePackage based overlays on tasks.
+ *
+ * Callers prepare a view hierarchy with SurfaceControlViewHost
+ * and send the package to WM here. The remote view hierarchy will receive
+ * configuration change, lifecycle events, etc, forwarded over the
+ * ISurfaceControlViewHost interface inside the SurfacePackage.
+ */
+ public abstract void addTaskOverlay(int taskId, SurfaceControlViewHost.SurfacePackage overlay);
+ public abstract void removeTaskOverlay(int taskId, SurfaceControlViewHost.SurfacePackage overlay);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b5e6f49..e4216bf 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -55,6 +55,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
@@ -266,6 +267,7 @@
import android.view.ScrollCaptureResponse;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
import android.view.SurfaceSession;
import android.view.TaskTransitionSpec;
import android.view.View;
@@ -1746,7 +1748,10 @@
activity.attachStartingWindow(win);
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "addWindow: %s startingWindow=%s",
activity, win);
- } else if (type == TYPE_INPUT_METHOD) {
+ } else if (type == TYPE_INPUT_METHOD
+ // IME window is always touchable.
+ // Ignore non-touchable windows e.g. Stylus InkWindow.java.
+ && (win.getAttrs().flags & FLAG_NOT_TOUCHABLE) == 0) {
displayContent.setInputMethodWindowLocked(win);
imMayMove = false;
} else if (type == TYPE_INPUT_METHOD_DIALOG) {
@@ -2241,6 +2246,15 @@
winAnimator.setColorSpaceAgnosticLocked((win.mAttrs.privateFlags
& WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0);
}
+ if (win.mActivityRecord != null
+ && !displayContent.mDwpcHelper.keepActivityOnWindowFlagsChanged(
+ win.mActivityRecord.info, flagChanges, privateFlagChanges)) {
+ mH.sendMessage(mH.obtainMessage(H.REPARENT_TASK_TO_DEFAULT_DISPLAY,
+ win.mActivityRecord.getTask()));
+ Slog.w(TAG_WM, "Activity " + win.mActivityRecord + " window flag changed,"
+ + " can't remain on display " + displayContent.getDisplayId());
+ return 0;
+ }
}
if (DEBUG_LAYOUT) Slog.v(TAG_WM, "Relayout " + win + ": viewVisibility=" + viewVisibility
@@ -5060,6 +5074,7 @@
public static final int ON_POINTER_DOWN_OUTSIDE_FOCUS = 62;
public static final int LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED = 63;
public static final int WINDOW_STATE_BLAST_SYNC_TIMEOUT = 64;
+ public static final int REPARENT_TASK_TO_DEFAULT_DISPLAY = 65;
/**
* Used to denote that an integer field in a message will not be used.
@@ -5377,6 +5392,15 @@
}
break;
}
+ case REPARENT_TASK_TO_DEFAULT_DISPLAY: {
+ synchronized (mGlobalLock) {
+ Task task = (Task) msg.obj;
+ task.reparent(mRoot.getDefaultTaskDisplayArea(), true /* onTop */);
+ // Resume focusable root task after reparenting to another display area.
+ task.resumeNextFocusAfterReparent();
+ }
+ break;
+ }
}
if (DEBUG_WINDOW_TRACE) {
Slog.v(TAG_WM, "handleMessage: exit");
@@ -7887,6 +7911,28 @@
public boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) {
return WindowManagerService.this.shouldRestoreImeVisibility(imeTargetWindowToken);
}
+
+ @Override
+ public void addTaskOverlay(int taskId, SurfaceControlViewHost.SurfacePackage overlay) {
+ synchronized (mGlobalLock) {
+ final Task task = mRoot.getRootTask(taskId);
+ if (task == null) {
+ throw new IllegalArgumentException("no task with taskId" + taskId);
+ }
+ task.addOverlay(overlay);
+ }
+ }
+
+ @Override
+ public void removeTaskOverlay(int taskId, SurfaceControlViewHost.SurfacePackage overlay) {
+ synchronized (mGlobalLock) {
+ final Task task = mRoot.getRootTask(taskId);
+ if (task == null) {
+ throw new IllegalArgumentException("no task with taskId" + taskId);
+ }
+ task.removeOverlay(overlay);
+ }
+ }
}
void registerAppFreezeListener(AppFreezeListener listener) {
@@ -8255,7 +8301,7 @@
flags = sanitizeFlagSlippery(flags, name, callingUid, callingPid);
- final int sanitizedFlags = flags & (LayoutParams.FLAG_NOT_TOUCHABLE
+ final int sanitizedFlags = flags & (FLAG_NOT_TOUCHABLE
| FLAG_SLIPPERY | LayoutParams.FLAG_NOT_FOCUSABLE);
h.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | sanitizedFlags;
h.layoutParamsType = type;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5bbe2cd..94d4a77 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4809,6 +4809,9 @@
if (isAnimating()) {
return;
}
+ if (mWmService.mAccessibilityController.hasCallbacks()) {
+ mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(getDisplayId());
+ }
if (!isSelfOrAncestorWindowAnimatingExit()) {
return;
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.cpp b/services/core/jni/gnss/GnssMeasurementCallback.cpp
index 34ae469..5a7cee9 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.cpp
+++ b/services/core/jni/gnss/GnssMeasurementCallback.cpp
@@ -27,12 +27,16 @@
using hardware::gnss::GnssData;
using hardware::gnss::GnssMeasurement;
using hardware::gnss::SatellitePvt;
+using GnssAgc = hardware::gnss::GnssData::GnssAgc;
namespace {
jclass class_arrayList;
jclass class_clockInfo;
jclass class_correlationVectorBuilder;
+jclass class_gnssAgc;
+jclass class_gnssAgcBuilder;
jclass class_gnssMeasurementsEvent;
+jclass class_gnssMeasurementsEventBuilder;
jclass class_gnssMeasurement;
jclass class_gnssClock;
jclass class_positionEcef;
@@ -47,7 +51,16 @@
jmethodID method_correlationVectorBuilderSetMagnitude;
jmethodID method_correlationVectorBuilderSetSamplingStartMeters;
jmethodID method_correlationVectorBuilderSetSamplingWidthMeters;
-jmethodID method_gnssMeasurementsEventCtor;
+jmethodID method_gnssAgcBuilderCtor;
+jmethodID method_gnssAgcBuilderSetLevelDb;
+jmethodID method_gnssAgcBuilderSetConstellationType;
+jmethodID method_gnssAgcBuilderSetCarrierFrequencyHz;
+jmethodID method_gnssAgcBuilderBuild;
+jmethodID method_gnssMeasurementsEventBuilderCtor;
+jmethodID method_gnssMeasurementsEventBuilderSetClock;
+jmethodID method_gnssMeasurementsEventBuilderSetMeasurements;
+jmethodID method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls;
+jmethodID method_gnssMeasurementsEventBuilderBuild;
jmethodID method_gnssMeasurementsSetCorrelationVectors;
jmethodID method_gnssMeasurementsSetSatellitePvt;
jmethodID method_gnssClockCtor;
@@ -69,12 +82,55 @@
void GnssMeasurement_class_init_once(JNIEnv* env, jclass& clazz) {
method_reportMeasurementData = env->GetMethodID(clazz, "reportMeasurementData",
"(Landroid/location/GnssMeasurementsEvent;)V");
+
+ // Initialize GnssMeasurement related classes and methods
jclass gnssMeasurementsEventClass = env->FindClass("android/location/GnssMeasurementsEvent");
class_gnssMeasurementsEvent = (jclass)env->NewGlobalRef(gnssMeasurementsEventClass);
- method_gnssMeasurementsEventCtor =
- env->GetMethodID(class_gnssMeasurementsEvent, "<init>",
- "(Landroid/location/GnssClock;[Landroid/location/GnssMeasurement;)V");
+ jclass gnssMeasurementsEventBuilderClass =
+ env->FindClass("android/location/GnssMeasurementsEvent$Builder");
+ class_gnssMeasurementsEventBuilder =
+ (jclass)env->NewGlobalRef(gnssMeasurementsEventBuilderClass);
+ method_gnssMeasurementsEventBuilderCtor =
+ env->GetMethodID(class_gnssMeasurementsEventBuilder, "<init>", "()V");
+ method_gnssMeasurementsEventBuilderSetClock =
+ env->GetMethodID(class_gnssMeasurementsEventBuilder, "setClock",
+ "(Landroid/location/GnssClock;)"
+ "Landroid/location/GnssMeasurementsEvent$Builder;");
+ method_gnssMeasurementsEventBuilderSetMeasurements =
+ env->GetMethodID(class_gnssMeasurementsEventBuilder, "setMeasurements",
+ "([Landroid/location/GnssMeasurement;)"
+ "Landroid/location/GnssMeasurementsEvent$Builder;");
+ method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls =
+ env->GetMethodID(class_gnssMeasurementsEventBuilder, "setGnssAutomaticGainControls",
+ "([Landroid/location/GnssAutomaticGainControl;)"
+ "Landroid/location/GnssMeasurementsEvent$Builder;");
+ method_gnssMeasurementsEventBuilderBuild =
+ env->GetMethodID(class_gnssMeasurementsEventBuilder, "build",
+ "()Landroid/location/GnssMeasurementsEvent;");
+ // Initialize GnssAgc related classes and methods
+ jclass gnssAgcClass = env->FindClass("android/location/GnssAutomaticGainControl");
+ class_gnssAgc = (jclass)env->NewGlobalRef(gnssAgcClass);
+ jclass gnssAgcBuilderClass =
+ env->FindClass("android/location/GnssAutomaticGainControl$Builder");
+ class_gnssAgcBuilder = (jclass)env->NewGlobalRef(gnssAgcBuilderClass);
+ method_gnssAgcBuilderCtor = env->GetMethodID(class_gnssAgcBuilder, "<init>", "()V");
+ method_gnssAgcBuilderSetLevelDb =
+ env->GetMethodID(class_gnssAgcBuilder, "setLevelDb",
+ "(D)"
+ "Landroid/location/GnssAutomaticGainControl$Builder;");
+ method_gnssAgcBuilderSetConstellationType =
+ env->GetMethodID(class_gnssAgcBuilder, "setConstellationType",
+ "(I)"
+ "Landroid/location/GnssAutomaticGainControl$Builder;");
+ method_gnssAgcBuilderSetCarrierFrequencyHz =
+ env->GetMethodID(class_gnssAgcBuilder, "setCarrierFrequencyHz",
+ "(J)"
+ "Landroid/location/GnssAutomaticGainControl$Builder;");
+ method_gnssAgcBuilderBuild = env->GetMethodID(class_gnssAgcBuilder, "build",
+ "()Landroid/location/GnssAutomaticGainControl;");
+
+ // Initialize GnssMeasurement related classes and methods
jclass gnssMeasurementClass = env->FindClass("android/location/GnssMeasurement");
class_gnssMeasurement = (jclass)env->NewGlobalRef(gnssMeasurementClass);
method_gnssMeasurementCtor = env->GetMethodID(class_gnssMeasurement, "<init>", "()V");
@@ -152,14 +208,25 @@
}
void setMeasurementData(JNIEnv* env, jobject& callbacksObj, jobject clock,
- jobjectArray measurementArray) {
- jobject gnssMeasurementsEvent =
- env->NewObject(class_gnssMeasurementsEvent, method_gnssMeasurementsEventCtor, clock,
- measurementArray);
+ jobjectArray measurementArray, jobjectArray gnssAgcArray) {
+ jobject gnssMeasurementsEventBuilderObject =
+ env->NewObject(class_gnssMeasurementsEventBuilder,
+ method_gnssMeasurementsEventBuilderCtor);
+ env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
+ method_gnssMeasurementsEventBuilderSetClock, clock);
+ env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
+ method_gnssMeasurementsEventBuilderSetMeasurements, measurementArray);
+ env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
+ method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls,
+ gnssAgcArray);
+ jobject gnssMeasurementsEventObject =
+ env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
+ method_gnssMeasurementsEventBuilderBuild);
- env->CallVoidMethod(callbacksObj, method_reportMeasurementData, gnssMeasurementsEvent);
+ env->CallVoidMethod(callbacksObj, method_reportMeasurementData, gnssMeasurementsEventObject);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
- env->DeleteLocalRef(gnssMeasurementsEvent);
+ env->DeleteLocalRef(gnssMeasurementsEventBuilderObject);
+ env->DeleteLocalRef(gnssMeasurementsEventObject);
}
template <class T_Measurement, class T_Flags>
@@ -289,9 +356,13 @@
JavaObject gnssClockJavaObject(env, class_gnssClock, method_gnssClockCtor);
translateGnssClock(env, data, gnssClockJavaObject);
jobject clock = gnssClockJavaObject.get();
-
jobjectArray measurementArray = translateAllGnssMeasurements(env, data.measurements);
- setMeasurementData(env, mCallbacksObj, clock, measurementArray);
+
+ jobjectArray gnssAgcArray = nullptr;
+ if (data.gnssAgcs.has_value()) {
+ gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs.value());
+ }
+ setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray);
env->DeleteLocalRef(clock);
env->DeleteLocalRef(measurementArray);
@@ -436,6 +507,38 @@
return gnssMeasurementArray;
}
+jobjectArray GnssMeasurementCallbackAidl::translateAllGnssAgcs(
+ JNIEnv* env, const std::vector<std::optional<GnssAgc>>& agcs) {
+ if (agcs.size() == 0) {
+ return nullptr;
+ }
+
+ jobjectArray gnssAgcArray =
+ env->NewObjectArray(agcs.size(), class_gnssAgc, nullptr /* initialElement */);
+
+ for (uint16_t i = 0; i < agcs.size(); ++i) {
+ if (!agcs[i].has_value()) {
+ continue;
+ }
+ const GnssAgc& gnssAgc = agcs[i].value();
+
+ jobject agcBuilderObject = env->NewObject(class_gnssAgcBuilder, method_gnssAgcBuilderCtor);
+ env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetLevelDb,
+ gnssAgc.agcLevelDb);
+ env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetConstellationType,
+ (int)gnssAgc.constellation);
+ env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetCarrierFrequencyHz,
+ gnssAgc.carrierFrequencyHz);
+ jobject agcObject = env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderBuild);
+
+ env->SetObjectArrayElement(gnssAgcArray, i, agcObject);
+ env->DeleteLocalRef(agcBuilderObject);
+ env->DeleteLocalRef(agcObject);
+ }
+
+ return gnssAgcArray;
+}
+
void GnssMeasurementCallbackAidl::translateGnssClock(JNIEnv* env, const GnssData& data,
JavaObject& object) {
setElapsedRealtimeFields<ElapsedRealtime, ElapsedRealtime>(data.elapsedRealtime, object);
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.h b/services/core/jni/gnss/GnssMeasurementCallback.h
index 32200fd..9b34631 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.h
+++ b/services/core/jni/gnss/GnssMeasurementCallback.h
@@ -48,7 +48,7 @@
void GnssMeasurement_class_init_once(JNIEnv* env, jclass& clazz);
void setMeasurementData(JNIEnv* env, jobject& callbacksObj, jobject clock,
- jobjectArray measurementArray);
+ jobjectArray measurementArray, jobjectArray gnssAgcArray);
class GnssMeasurementCallbackAidl : public hardware::gnss::BnGnssMeasurementCallback {
public:
@@ -62,6 +62,8 @@
jobjectArray translateAllGnssMeasurements(
JNIEnv* env, const std::vector<hardware::gnss::GnssMeasurement>& measurements);
+ jobjectArray translateAllGnssAgcs(
+ JNIEnv* env, const std::vector<std::optional<hardware::gnss::GnssData::GnssAgc>>& agcs);
void translateAndSetGnssData(const hardware::gnss::GnssData& data);
@@ -139,7 +141,7 @@
size_t count = getMeasurementCount(data);
jobjectArray measurementArray =
translateAllGnssMeasurements(env, data.measurements.data(), count);
- setMeasurementData(env, mCallbacksObj, clock, measurementArray);
+ setMeasurementData(env, mCallbacksObj, clock, measurementArray, nullptr);
env->DeleteLocalRef(clock);
env->DeleteLocalRef(measurementArray);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index db8da11..6caf731 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -231,6 +231,7 @@
import android.net.ConnectivityManager;
import android.net.ConnectivitySettingsManager;
import android.net.IIpConnectivityMetrics;
+import android.net.ProfileNetworkPreference;
import android.net.ProxyInfo;
import android.net.Uri;
import android.net.VpnManager;
@@ -17827,10 +17828,14 @@
}
int networkPreference = preferentialNetworkServiceEnabled
? PROFILE_NETWORK_PREFERENCE_ENTERPRISE : PROFILE_NETWORK_PREFERENCE_DEFAULT;
+ ProfileNetworkPreference.Builder preferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ preferenceBuilder.setPreference(networkPreference);
+ List<ProfileNetworkPreference> preferences = new ArrayList<>();
+ preferences.add(preferenceBuilder.build());
mInjector.binderWithCleanCallingIdentity(() ->
- mInjector.getConnectivityManager().setProfileNetworkPreference(
- UserHandle.of(userId),
- networkPreference,
+ mInjector.getConnectivityManager().setProfileNetworkPreferences(
+ UserHandle.of(userId), preferences,
null /* executor */, null /* listener */));
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 5fcee9b..ee8288e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -152,6 +152,7 @@
import com.android.server.os.SchedulingPolicyService;
import com.android.server.people.PeopleService;
import com.android.server.pm.ApexManager;
+import com.android.server.pm.ApexSystemServiceInfo;
import com.android.server.pm.CrossProfileAppsService;
import com.android.server.pm.DataLoaderManagerService;
import com.android.server.pm.DynamicCodeLoggingService;
@@ -220,8 +221,8 @@
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
+import java.util.List;
import java.util.Locale;
-import java.util.Map;
import java.util.Timer;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
@@ -1459,7 +1460,7 @@
// TelecomLoader hooks into classes with defined HFP logic,
// so check for either telephony or microphone.
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE) ||
- mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
t.traceBegin("StartTelecomLoaderService");
mSystemServiceManager.startService(TelecomLoaderService.class);
t.traceEnd();
@@ -1467,7 +1468,7 @@
t.traceBegin("StartTelephonyRegistry");
telephonyRegistry = new TelephonyRegistry(
- context, new TelephonyRegistry.ConfigurationProvider());
+ context, new TelephonyRegistry.ConfigurationProvider());
ServiceManager.addService("telephony.registry", telephonyRegistry);
t.traceEnd();
@@ -2676,7 +2677,7 @@
t.traceBegin("MakePowerManagerServiceReady");
try {
// TODO: use boot phase
- mPowerManagerService.systemReady(mActivityManagerService.getAppOpsService());
+ mPowerManagerService.systemReady();
} catch (Throwable e) {
reportWtf("making Power Manager Service ready", e);
}
@@ -2998,7 +2999,9 @@
t.traceEnd();
t.traceBegin("MakeTelephonyRegistryReady");
try {
- if (telephonyRegistryF != null) telephonyRegistryF.systemRunning();
+ if (telephonyRegistryF != null) {
+ telephonyRegistryF.systemRunning();
+ }
} catch (Throwable e) {
reportWtf("Notifying TelephonyRegistry running", e);
}
@@ -3063,10 +3066,12 @@
*/
private void startApexServices(@NonNull TimingsTraceAndSlog t) {
t.traceBegin("startApexServices");
- Map<String, String> services = ApexManager.getInstance().getApexSystemServices();
- // TODO(satayev): introduce android:order for services coming the same apexes
- for (String name : new TreeSet<>(services.keySet())) {
- String jarPath = services.get(name);
+ // TODO(b/192880996): get the list from "android" package, once the manifest entries
+ // are migrated to system manifest.
+ List<ApexSystemServiceInfo> services = ApexManager.getInstance().getApexSystemServices();
+ for (ApexSystemServiceInfo info : services) {
+ String name = info.getName();
+ String jarPath = info.getJarPath();
t.traceBegin("starting " + name);
if (TextUtils.isEmpty(jarPath)) {
mSystemServiceManager.startService(name);
diff --git a/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp b/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp
index 16d6241..0a9b7b1 100644
--- a/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp
+++ b/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp
@@ -32,7 +32,7 @@
name: "test_com.android.server",
manifest: "manifest.json",
androidManifest: "AndroidManifest.xml",
- java_libs: ["FakeApexSystemService"],
+ java_libs: ["FakeApexSystemServices"],
file_contexts: ":apex.test-file_contexts",
key: "test_com.android.server.key",
updatable: false,
diff --git a/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml b/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml
index eb741ca..6bec284 100644
--- a/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml
+++ b/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml
@@ -21,21 +21,29 @@
<application android:hasCode="false" android:testOnly="true">
<apex-system-service
android:name="com.android.server.testing.FakeApexSystemService"
- android:path="/apex/test_com.android.server/javalib/FakeApexSystemService.jar"
- android:minSdkVersion="30"/>
+ android:path="/apex/test_com.android.server/javalib/FakeApexSystemServices.jar"
+ android:minSdkVersion="30"
+ />
+
+ <apex-system-service
+ android:name="com.android.server.testing.FakeApexSystemService2"
+ android:path="/apex/test_com.android.server/javalib/FakeApexSystemServices.jar"
+ android:minSdkVersion="30"
+ android:initOrder="1"
+ />
<!-- Always inactive system service, since maxSdkVersion is low -->
<apex-system-service
- android:name="com.android.apex.test.OldApexSystemService"
- android:path="/apex/com.android.apex.test/javalib/fake.jar"
+ android:name="com.android.server.testing.OldApexSystemService"
+ android:path="/apex/test_com.android.server/javalib/fake.jar"
android:minSdkVersion="1"
android:maxSdkVersion="1"
/>
<!-- Always inactive system service, since minSdkVersion is high -->
<apex-system-service
- android:name="com.android.apex.test.NewApexSystemService"
- android:path="/apex/com.android.apex.test/javalib/fake.jar"
+ android:name="com.android.server.testing.NewApexSystemService"
+ android:path="/apex/test_com.android.server/javalib/fake.jar"
android:minSdkVersion="999999"
/>
</application>
diff --git a/services/tests/apexsystemservices/service/Android.bp b/services/tests/apexsystemservices/services/Android.bp
similarity index 94%
rename from services/tests/apexsystemservices/service/Android.bp
rename to services/tests/apexsystemservices/services/Android.bp
index 9d04f39..477ea4c 100644
--- a/services/tests/apexsystemservices/service/Android.bp
+++ b/services/tests/apexsystemservices/services/Android.bp
@@ -8,7 +8,7 @@
}
java_library {
- name: "FakeApexSystemService",
+ name: "FakeApexSystemServices",
srcs: ["**/*.java"],
sdk_version: "system_server_current",
libs: [
diff --git a/services/tests/apexsystemservices/service/src/com/android/server/testing/FakeApexSystemService.java b/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService.java
similarity index 100%
rename from services/tests/apexsystemservices/service/src/com/android/server/testing/FakeApexSystemService.java
rename to services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService.java
diff --git a/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService2.java b/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService2.java
new file mode 100644
index 0000000..e83343b
--- /dev/null
+++ b/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService2.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.testing;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.SystemService;
+
+/**
+ * A fake system service that just logs when it is started.
+ */
+public class FakeApexSystemService2 extends SystemService {
+
+ private static final String TAG = "FakeApexSystemService";
+
+ public FakeApexSystemService2(@NonNull Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ Log.d(TAG, "FakeApexSystemService2 onStart");
+ }
+}
diff --git a/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java b/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java
index 2b453a9..7ab7b6ed 100644
--- a/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java
+++ b/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java
@@ -37,6 +37,10 @@
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
+import java.util.Objects;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
@RunWith(DeviceJUnit4ClassRunner.class)
public class ApexSystemServicesTestCases extends BaseHostJUnit4Test {
@@ -67,7 +71,7 @@
}
@Test
- public void noApexSystemServerStartsWithoutApex() throws Exception {
+ public void testNoApexSystemServiceStartsWithoutApex() throws Exception {
mPreparer.reboot();
assertThat(getFakeApexSystemServiceLogcat())
@@ -75,7 +79,7 @@
}
@Test
- public void apexSystemServerStarts() throws Exception {
+ public void testApexSystemServiceStarts() throws Exception {
// Pre-install the apex
String apex = "test_com.android.server.apex";
mPreparer.pushResourceFile(apex, "/system/apex/" + apex);
@@ -86,9 +90,40 @@
.contains("FakeApexSystemService onStart");
}
+ @Test
+ public void testInitOrder() throws Exception {
+ // Pre-install the apex
+ String apex = "test_com.android.server.apex";
+ mPreparer.pushResourceFile(apex, "/system/apex/" + apex);
+ // Reboot activates the apex
+ mPreparer.reboot();
+
+ assertThat(getFakeApexSystemServiceLogcat().lines()
+ .map(ApexSystemServicesTestCases::getDebugMessage)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()))
+ .containsExactly(
+ // Second service has a higher initOrder and must be started first
+ "FakeApexSystemService2 onStart",
+ "FakeApexSystemService onStart"
+ )
+ .inOrder();
+ }
+
private String getFakeApexSystemServiceLogcat() throws DeviceNotAvailableException {
return mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", "FakeApexSystemService:D",
"*:S");
}
+ private static final Pattern DEBUG_MESSAGE =
+ Pattern.compile("(FakeApexSystemService[0-9]* onStart)");
+
+ private static String getDebugMessage(String logcatLine) {
+ return DEBUG_MESSAGE.matcher(logcatLine)
+ .results()
+ .map(m -> m.group(1))
+ .findFirst()
+ .orElse(null);
+ }
+
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java b/services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java
index 5a6275d..cc97b8f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java
@@ -24,7 +24,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
@@ -226,7 +225,7 @@
}
private void expectWipeNonSystemUser() {
- when(mUserManager.removeUserOrSetEphemeral(anyInt(), anyBoolean()))
+ when(mUserManager.removeUserWhenPossible(any(), anyBoolean()))
.thenReturn(UserManager.REMOVE_RESULT_REMOVED);
}
@@ -266,7 +265,7 @@
}
private void verifyWipeNonSystemUser() {
- verify(mUserManager).removeUserOrSetEphemeral(anyInt(), anyBoolean());
+ verify(mUserManager).removeUserWhenPossible(any(), anyBoolean());
}
private void setPendingResultForUser(int userId) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java
index edf6816..1a5888e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java
@@ -183,7 +183,8 @@
mOverridesToRemoveByPackageConfigCaptor.getValue().packageNameToOverridesToRemove;
Map<Long, PackageOverride> addedOverrides;
assertThat(packageNameToAddedOverrides.keySet()).containsExactly(PACKAGE_1, PACKAGE_3);
- assertThat(packageNameToRemovedOverrides.keySet()).containsExactly(PACKAGE_3, PACKAGE_4);
+ assertThat(packageNameToRemovedOverrides.keySet()).containsExactly(PACKAGE_2, PACKAGE_3,
+ PACKAGE_4);
// Package 1
addedOverrides = packageNameToAddedOverrides.get(PACKAGE_1).overrides;
assertThat(addedOverrides).hasSize(3);
@@ -193,6 +194,9 @@
new PackageOverride.Builder().setMinVersionCode(2).setEnabled(true).build());
assertThat(addedOverrides.get(789L)).isEqualTo(
new PackageOverride.Builder().setEnabled(false).build());
+ // Package 2
+ assertThat(packageNameToRemovedOverrides.get(PACKAGE_2).changeIds).containsExactly(123L,
+ 456L, 789L);
// Package 3
addedOverrides = packageNameToAddedOverrides.get(PACKAGE_3).overrides;
assertThat(addedOverrides).hasSize(1);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
new file mode 100644
index 0000000..52d0494
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2018 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.job.controllers;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.verify;
+
+import android.app.AppGlobals;
+import android.app.job.JobInfo;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ServiceInfo;
+import android.os.BatteryManagerInternal;
+import android.os.RemoteException;
+import android.util.ArraySet;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@RunWith(AndroidJUnit4.class)
+public class BatteryControllerTest {
+ private static final int CALLING_UID = 1000;
+ private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
+ private static final int SOURCE_USER_ID = 0;
+
+ private BatteryController mBatteryController;
+ private BroadcastReceiver mPowerReceiver;
+ private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants();
+ private int mSourceUid;
+
+ private MockitoSession mMockingSession;
+ @Mock
+ private Context mContext;
+ @Mock
+ private BatteryManagerInternal mBatteryManagerInternal;
+ @Mock
+ private JobSchedulerService mJobSchedulerService;
+ @Mock
+ private PackageManagerInternal mPackageManagerInternal;
+
+ @Before
+ public void setUp() {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .mockStatic(LocalServices.class)
+ .startMocking();
+
+ // Called in StateController constructor.
+ when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
+ when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
+ when(mJobSchedulerService.getConstants()).thenReturn(mConstants);
+ // Called in BatteryController constructor.
+ doReturn(mBatteryManagerInternal)
+ .when(() -> LocalServices.getService(BatteryManagerInternal.class));
+ // Used in JobStatus.
+ doReturn(mPackageManagerInternal)
+ .when(() -> LocalServices.getService(PackageManagerInternal.class));
+
+ // Initialize real objects.
+ // Capture the listeners.
+ ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ mBatteryController = new BatteryController(mJobSchedulerService);
+
+ verify(mContext).registerReceiver(receiverCaptor.capture(),
+ ArgumentMatchers.argThat(filter ->
+ filter.hasAction(Intent.ACTION_POWER_CONNECTED)
+ && filter.hasAction(Intent.ACTION_POWER_DISCONNECTED)));
+ mPowerReceiver = receiverCaptor.getValue();
+ try {
+ mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
+ // Need to do this since we're using a mock JS and not a real object.
+ doReturn(new ArraySet<>(new String[]{SOURCE_PACKAGE}))
+ .when(mJobSchedulerService).getPackagesForUidLocked(mSourceUid);
+ } catch (RemoteException e) {
+ fail(e.getMessage());
+ }
+ setPowerConnected(false);
+ }
+
+ @After
+ public void tearDown() {
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ private void setBatteryNotLow(boolean notLow) {
+ doReturn(notLow).when(mJobSchedulerService).isBatteryNotLow();
+ synchronized (mBatteryController.mLock) {
+ mBatteryController.onBatteryStateChangedLocked();
+ }
+ waitForNonDelayedMessagesProcessed();
+ }
+
+ private void setCharging() {
+ doReturn(true).when(mJobSchedulerService).isBatteryCharging();
+ synchronized (mBatteryController.mLock) {
+ mBatteryController.onBatteryStateChangedLocked();
+ }
+ waitForNonDelayedMessagesProcessed();
+ }
+
+ private void setDischarging() {
+ doReturn(false).when(mJobSchedulerService).isBatteryCharging();
+ synchronized (mBatteryController.mLock) {
+ mBatteryController.onBatteryStateChangedLocked();
+ }
+ waitForNonDelayedMessagesProcessed();
+ }
+
+ private void setPowerConnected(boolean connected) {
+ Intent intent = new Intent(
+ connected ? Intent.ACTION_POWER_CONNECTED : Intent.ACTION_POWER_DISCONNECTED);
+ mPowerReceiver.onReceive(mContext, intent);
+ }
+
+ private void setUidBias(int uid, int bias) {
+ int prevBias = mJobSchedulerService.getUidBias(uid);
+ doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
+ synchronized (mBatteryController.mLock) {
+ mBatteryController.onUidBiasChangedLocked(uid, prevBias, bias);
+ }
+ }
+
+ private void trackJobs(JobStatus... jobs) {
+ for (JobStatus job : jobs) {
+ synchronized (mBatteryController.mLock) {
+ mBatteryController.maybeStartTrackingJobLocked(job, null);
+ }
+ }
+ }
+
+ private void waitForNonDelayedMessagesProcessed() {
+ JobSchedulerBackgroundThread.getHandler().runWithScissors(() -> {}, 15_000);
+ }
+
+ private JobInfo.Builder createBaseJobInfoBuilder(int jobId) {
+ return new JobInfo.Builder(jobId, new ComponentName(mContext, "TestBatteryJobService"));
+ }
+
+ private JobInfo.Builder createBaseJobInfoBuilder(int jobId, String pkgName) {
+ return new JobInfo.Builder(jobId, new ComponentName(pkgName, "TestBatteryJobService"));
+ }
+
+ private JobStatus createJobStatus(String testTag, String packageName, int callingUid,
+ JobInfo jobInfo) {
+ JobStatus js = JobStatus.createFromJobInfo(
+ jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
+ js.serviceInfo = mock(ServiceInfo.class);
+ // Make sure tests aren't passing just because the default bucket is likely ACTIVE.
+ js.setStandbyBucket(FREQUENT_INDEX);
+ return js;
+ }
+
+ @Test
+ public void testBatteryNotLow() {
+ JobStatus job1 = createJobStatus("testBatteryNotLow", SOURCE_PACKAGE, CALLING_UID,
+ createBaseJobInfoBuilder(1).setRequiresBatteryNotLow(true).build());
+ JobStatus job2 = createJobStatus("testBatteryNotLow", SOURCE_PACKAGE, CALLING_UID,
+ createBaseJobInfoBuilder(2).setRequiresBatteryNotLow(true).build());
+
+ setBatteryNotLow(false);
+ trackJobs(job1);
+ assertFalse(job1.isConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW));
+
+ setBatteryNotLow(true);
+ assertTrue(job1.isConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW));
+
+ trackJobs(job2);
+ assertTrue(job2.isConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW));
+ }
+
+ @Test
+ public void testCharging_BatteryNotLow() {
+ JobStatus job1 = createJobStatus("testCharging_BatteryNotLow", SOURCE_PACKAGE, CALLING_UID,
+ createBaseJobInfoBuilder(1)
+ .setRequiresCharging(true)
+ .setRequiresBatteryNotLow(true).build());
+ JobStatus job2 = createJobStatus("testCharging_BatteryNotLow", SOURCE_PACKAGE, CALLING_UID,
+ createBaseJobInfoBuilder(2)
+ .setRequiresCharging(true)
+ .setRequiresBatteryNotLow(false).build());
+
+ setBatteryNotLow(true);
+ setDischarging();
+ trackJobs(job1, job2);
+ assertFalse(job1.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ assertFalse(job2.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+
+ setCharging();
+ assertTrue(job1.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ assertTrue(job2.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ }
+
+ @Test
+ public void testTopPowerConnectedExemption() {
+ final int uid1 = mSourceUid;
+ final int uid2 = mSourceUid + 1;
+ final int uid3 = mSourceUid + 2;
+ JobStatus jobFg = createJobStatus("testTopPowerConnectedExemption", SOURCE_PACKAGE, uid1,
+ createBaseJobInfoBuilder(1).setRequiresCharging(true).build());
+ JobStatus jobFgRunner = createJobStatus("testTopPowerConnectedExemption",
+ SOURCE_PACKAGE, uid1,
+ createBaseJobInfoBuilder(2).setRequiresCharging(true).build());
+ JobStatus jobFgLow = createJobStatus("testTopPowerConnectedExemption", SOURCE_PACKAGE, uid1,
+ createBaseJobInfoBuilder(3)
+ .setRequiresCharging(true)
+ .setPriority(JobInfo.PRIORITY_LOW)
+ .build());
+ JobStatus jobBg = createJobStatus("testTopPowerConnectedExemption",
+ "some.background.app", uid2,
+ createBaseJobInfoBuilder(4, "some.background.app")
+ .setRequiresCharging(true)
+ .build());
+ JobStatus jobLateFg = createJobStatus("testTopPowerConnectedExemption",
+ "switch.to.fg", uid3,
+ createBaseJobInfoBuilder(5, "switch.to.fg").setRequiresCharging(true).build());
+ JobStatus jobLateFgLow = createJobStatus("testTopPowerConnectedExemption",
+ "switch.to.fg", uid3,
+ createBaseJobInfoBuilder(6, "switch.to.fg")
+ .setRequiresCharging(true)
+ .setPriority(JobInfo.PRIORITY_MIN)
+ .build());
+
+ setBatteryNotLow(false);
+ setDischarging();
+ setUidBias(uid1, JobInfo.BIAS_TOP_APP);
+ setUidBias(uid2, JobInfo.BIAS_DEFAULT);
+ setUidBias(uid3, JobInfo.BIAS_DEFAULT);
+
+ // Jobs are scheduled when power isn't connected.
+ setPowerConnected(false);
+ trackJobs(jobFg, jobFgLow, jobBg, jobLateFg, jobLateFgLow);
+ assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ assertFalse(jobFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ assertFalse(jobLateFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ assertFalse(jobLateFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+
+ // Power is connected. TOP app should be allowed to start job DEFAULT+ jobs.
+ setPowerConnected(true);
+ assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ assertFalse(jobFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ assertFalse(jobLateFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ assertFalse(jobLateFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+
+ // Test that newly scheduled job of TOP app is correctly allowed to run.
+ trackJobs(jobFgRunner);
+ assertTrue(jobFgRunner.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+
+ // Switch top app. New TOP app should be allowed to run job and the running job of
+ // previously TOP app should be allowed to continue to run.
+ synchronized (mBatteryController.mLock) {
+ mBatteryController.prepareForExecutionLocked(jobFgRunner);
+ }
+ setUidBias(uid1, JobInfo.BIAS_DEFAULT);
+ setUidBias(uid2, JobInfo.BIAS_DEFAULT);
+ setUidBias(uid3, JobInfo.BIAS_TOP_APP);
+ assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ assertTrue(jobFgRunner.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ assertFalse(jobFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ assertTrue(jobLateFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ assertFalse(jobLateFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+
+ setPowerConnected(false);
+ assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ assertFalse(jobFgRunner.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ assertFalse(jobFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ assertFalse(jobLateFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ assertFalse(jobLateFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
index 95912b2..d741459 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
@@ -200,8 +200,10 @@
}
private void setUidBias(int uid, int bias) {
+ int prevBias = mJobSchedulerService.getUidBias(uid);
+ doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
synchronized (mPrefetchController.mLock) {
- mPrefetchController.onUidBiasChangedLocked(uid, bias);
+ mPrefetchController.onUidBiasChangedLocked(uid, prevBias, bias);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java
index b65c3e9..0411b94 100644
--- a/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java
@@ -263,7 +263,7 @@
@Test
public void testUserActivityOnDeviceStateChange() {
createService();
- mService.systemReady(null);
+ mService.systemReady();
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
final DisplayInfo info = new DisplayInfo();
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 202a54d..587447a 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -95,6 +95,7 @@
<uses-permission android:name="android.permission.CONTROL_DEVICE_STATE"/>
<uses-permission android:name="android.permission.READ_PROJECTION_STATE"/>
<uses-permission android:name="android.permission.KILL_UID"/>
+ <uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK"/>
<uses-permission
android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
new file mode 100644
index 0000000..d4bac2c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
@@ -0,0 +1,326 @@
+/*
+ * 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.biometrics.sensors;
+
+import static android.testing.TestableLooper.RunWithLooper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.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 static org.testng.Assert.assertThrows;
+
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+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;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class BiometricSchedulerOperationTest {
+
+ public interface FakeHal {}
+ public abstract static class InterruptableMonitor<T>
+ extends HalClientMonitor<T> implements Interruptable {
+ public InterruptableMonitor() {
+ super(null, null, null, null, 0, null, 0, 0, 0, 0, 0);
+ }
+ }
+
+ @Mock
+ private InterruptableMonitor<FakeHal> mClientMonitor;
+ @Mock
+ private BaseClientMonitor.Callback mClientCallback;
+ @Mock
+ private FakeHal mHal;
+ @Captor
+ ArgumentCaptor<BaseClientMonitor.Callback> mStartCallback;
+
+ private Handler mHandler;
+ private BiometricSchedulerOperation mOperation;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mHandler = new Handler(TestableLooper.get(this).getLooper());
+ mOperation = new BiometricSchedulerOperation(mClientMonitor, mClientCallback);
+ }
+
+ @Test
+ public void testStartWithCookie() {
+ final int cookie = 200;
+ when(mClientMonitor.getCookie()).thenReturn(cookie);
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ assertThat(mOperation.isReadyToStart()).isEqualTo(cookie);
+ assertThat(mOperation.isStarted()).isFalse();
+ assertThat(mOperation.isCanceling()).isFalse();
+ assertThat(mOperation.isFinished()).isFalse();
+
+ final boolean started = mOperation.startWithCookie(
+ mock(BaseClientMonitor.Callback.class), cookie);
+
+ assertThat(started).isTrue();
+ verify(mClientMonitor).start(mStartCallback.capture());
+ mStartCallback.getValue().onClientStarted(mClientMonitor);
+ assertThat(mOperation.isStarted()).isTrue();
+ }
+
+ @Test
+ public void testNoStartWithoutCookie() {
+ final int goodCookie = 20;
+ final int badCookie = 22;
+ when(mClientMonitor.getCookie()).thenReturn(goodCookie);
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ assertThat(mOperation.isReadyToStart()).isEqualTo(goodCookie);
+ final boolean started = mOperation.startWithCookie(
+ mock(BaseClientMonitor.Callback.class), badCookie);
+
+ assertThat(started).isFalse();
+ assertThat(mOperation.isStarted()).isFalse();
+ assertThat(mOperation.isCanceling()).isFalse();
+ assertThat(mOperation.isFinished()).isFalse();
+ }
+
+ @Test
+ public void startsWhenReadyAndHalAvailable() {
+ when(mClientMonitor.getCookie()).thenReturn(0);
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class);
+ mOperation.start(cb);
+ verify(mClientMonitor).start(mStartCallback.capture());
+ mStartCallback.getValue().onClientStarted(mClientMonitor);
+
+ assertThat(mOperation.isStarted()).isTrue();
+ assertThat(mOperation.isCanceling()).isFalse();
+ assertThat(mOperation.isFinished()).isFalse();
+
+ verify(mClientCallback).onClientStarted(eq(mClientMonitor));
+ verify(cb).onClientStarted(eq(mClientMonitor));
+ verify(mClientCallback, never()).onClientFinished(any(), anyBoolean());
+ verify(cb, never()).onClientFinished(any(), anyBoolean());
+
+ mStartCallback.getValue().onClientFinished(mClientMonitor, true);
+
+ assertThat(mOperation.isFinished()).isTrue();
+ assertThat(mOperation.isCanceling()).isFalse();
+ verify(mClientMonitor).destroy();
+ verify(cb).onClientFinished(eq(mClientMonitor), eq(true));
+ }
+
+ @Test
+ public void startFailsWhenReadyButHalNotAvailable() {
+ when(mClientMonitor.getCookie()).thenReturn(0);
+ when(mClientMonitor.getFreshDaemon()).thenReturn(null);
+
+ final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class);
+ mOperation.start(cb);
+ verify(mClientMonitor, never()).start(any());
+
+ assertThat(mOperation.isStarted()).isFalse();
+ assertThat(mOperation.isCanceling()).isFalse();
+ assertThat(mOperation.isFinished()).isTrue();
+
+ verify(mClientCallback, never()).onClientStarted(eq(mClientMonitor));
+ verify(cb, never()).onClientStarted(eq(mClientMonitor));
+ verify(mClientCallback).onClientFinished(eq(mClientMonitor), eq(false));
+ verify(cb).onClientFinished(eq(mClientMonitor), eq(false));
+ }
+
+ @Test
+ public void doesNotStartWithCookie() {
+ when(mClientMonitor.getCookie()).thenReturn(9);
+ assertThrows(IllegalStateException.class,
+ () -> mOperation.start(mock(BaseClientMonitor.Callback.class)));
+ }
+
+ @Test
+ public void cannotRestart() {
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ mOperation.start(mock(BaseClientMonitor.Callback.class));
+
+ assertThrows(IllegalStateException.class,
+ () -> mOperation.start(mock(BaseClientMonitor.Callback.class)));
+ }
+
+ @Test
+ public void abortsNotRunning() {
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ mOperation.abort();
+
+ assertThat(mOperation.isFinished()).isTrue();
+ verify(mClientMonitor).unableToStart();
+ verify(mClientMonitor).destroy();
+ assertThrows(IllegalStateException.class,
+ () -> mOperation.start(mock(BaseClientMonitor.Callback.class)));
+ }
+
+ @Test
+ public void cannotAbortRunning() {
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ mOperation.start(mock(BaseClientMonitor.Callback.class));
+
+ assertThrows(IllegalStateException.class, () -> mOperation.abort());
+ }
+
+ @Test
+ public void cancel() {
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ final BaseClientMonitor.Callback startCb = mock(BaseClientMonitor.Callback.class);
+ final BaseClientMonitor.Callback cancelCb = mock(BaseClientMonitor.Callback.class);
+ mOperation.start(startCb);
+ verify(mClientMonitor).start(mStartCallback.capture());
+ mStartCallback.getValue().onClientStarted(mClientMonitor);
+ mOperation.cancel(mHandler, cancelCb);
+
+ assertThat(mOperation.isCanceling()).isTrue();
+ verify(mClientMonitor).cancel();
+ verify(mClientMonitor, never()).cancelWithoutStarting(any());
+ verify(mClientMonitor, never()).destroy();
+
+ mStartCallback.getValue().onClientFinished(mClientMonitor, true);
+
+ assertThat(mOperation.isFinished()).isTrue();
+ assertThat(mOperation.isCanceling()).isFalse();
+ verify(mClientMonitor).destroy();
+
+ // should be unused since the operation was started
+ verify(cancelCb, never()).onClientStarted(any());
+ verify(cancelCb, never()).onClientFinished(any(), anyBoolean());
+ }
+
+ @Test
+ public void cancelWithoutStarting() {
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ final BaseClientMonitor.Callback cancelCb = mock(BaseClientMonitor.Callback.class);
+ mOperation.cancel(mHandler, cancelCb);
+
+ assertThat(mOperation.isCanceling()).isTrue();
+ ArgumentCaptor<BaseClientMonitor.Callback> cbCaptor =
+ ArgumentCaptor.forClass(BaseClientMonitor.Callback.class);
+ verify(mClientMonitor).cancelWithoutStarting(cbCaptor.capture());
+
+ cbCaptor.getValue().onClientFinished(mClientMonitor, true);
+ verify(cancelCb).onClientFinished(eq(mClientMonitor), eq(true));
+ verify(mClientMonitor, never()).start(any());
+ verify(mClientMonitor, never()).cancel();
+ verify(mClientMonitor).destroy();
+ }
+
+ @Test
+ public void markCanceling() {
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ mOperation.markCanceling();
+
+ assertThat(mOperation.isMarkedCanceling()).isTrue();
+ assertThat(mOperation.isCanceling()).isFalse();
+ assertThat(mOperation.isFinished()).isFalse();
+ verify(mClientMonitor, never()).start(any());
+ verify(mClientMonitor, never()).cancel();
+ verify(mClientMonitor, never()).cancelWithoutStarting(any());
+ verify(mClientMonitor, never()).unableToStart();
+ verify(mClientMonitor, never()).destroy();
+ }
+
+ @Test
+ public void cancelPendingWithCookie() {
+ markCancellingAndStart(2);
+ }
+
+ @Test
+ public void cancelPendingWithoutCookie() {
+ markCancellingAndStart(null);
+ }
+
+ private void markCancellingAndStart(Integer withCookie) {
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ if (withCookie != null) {
+ when(mClientMonitor.getCookie()).thenReturn(withCookie);
+ }
+
+ mOperation.markCanceling();
+ final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class);
+ if (withCookie != null) {
+ mOperation.startWithCookie(cb, withCookie);
+ } else {
+ mOperation.start(cb);
+ }
+
+ assertThat(mOperation.isFinished()).isTrue();
+ verify(cb).onClientFinished(eq(mClientMonitor), eq(true));
+ verify(mClientMonitor, never()).start(any());
+ verify(mClientMonitor, never()).cancel();
+ verify(mClientMonitor, never()).cancelWithoutStarting(any());
+ verify(mClientMonitor, never()).unableToStart();
+ verify(mClientMonitor).destroy();
+ }
+
+ @Test
+ public void cancelWatchdogWhenStarted() {
+ cancelWatchdog(true);
+ }
+
+ @Test
+ public void cancelWatchdogWithoutStarting() {
+ cancelWatchdog(false);
+ }
+
+ private void cancelWatchdog(boolean start) {
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ mOperation.start(mock(BaseClientMonitor.Callback.class));
+ if (start) {
+ verify(mClientMonitor).start(mStartCallback.capture());
+ mStartCallback.getValue().onClientStarted(mClientMonitor);
+ }
+ mOperation.cancel(mHandler, mock(BaseClientMonitor.Callback.class));
+
+ assertThat(mOperation.isCanceling()).isTrue();
+
+ // omit call to onClientFinished and trigger watchdog
+ mOperation.mCancelWatchdog.run();
+
+ assertThat(mOperation.isFinished()).isTrue();
+ assertThat(mOperation.isCanceling()).isFalse();
+ verify(mClientMonitor).destroy();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index d192697..ac08319 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -16,10 +16,14 @@
package com.android.server.biometrics.sensors;
+import static android.testing.TestableLooper.RunWithLooper;
+
import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -34,10 +38,13 @@
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.IBiometricService;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
+import android.testing.TestableLooper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -46,16 +53,18 @@
import com.android.server.biometrics.nano.BiometricSchedulerProto;
import com.android.server.biometrics.nano.BiometricsProto;
-import com.android.server.biometrics.sensors.BiometricScheduler.Operation;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@Presubmit
@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper(setAsMainLooper = true)
public class BiometricSchedulerTest {
private static final String TAG = "BiometricSchedulerTest";
@@ -76,8 +85,9 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mToken = new Binder();
- mScheduler = new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_UNKNOWN,
- null /* gestureAvailabilityTracker */, mBiometricService, LOG_NUM_RECENT_OPERATIONS,
+ mScheduler = new BiometricScheduler(TAG, new Handler(TestableLooper.get(this).getLooper()),
+ BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */,
+ mBiometricService, LOG_NUM_RECENT_OPERATIONS,
CoexCoordinator.getInstance());
}
@@ -86,9 +96,9 @@
final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
final HalClientMonitor<Object> client1 =
- new TestClientMonitor(mContext, mToken, nonNullDaemon);
+ new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
final HalClientMonitor<Object> client2 =
- new TestClientMonitor(mContext, mToken, nonNullDaemon);
+ new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
mScheduler.scheduleClientMonitor(client1);
mScheduler.scheduleClientMonitor(client2);
@@ -99,20 +109,17 @@
@Test
public void testRemovesPendingOperations_whenNullHal_andNotBiometricPrompt() {
// Even if second client has a non-null daemon, it needs to be canceled.
- Object daemon2 = mock(Object.class);
-
- final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> null;
- final HalClientMonitor.LazyDaemon<Object> lazyDaemon2 = () -> daemon2;
-
- final TestClientMonitor client1 = new TestClientMonitor(mContext, mToken, lazyDaemon1);
- final TestClientMonitor client2 = new TestClientMonitor(mContext, mToken, lazyDaemon2);
+ final TestHalClientMonitor client1 = new TestHalClientMonitor(
+ mContext, mToken, () -> null);
+ final TestHalClientMonitor client2 = new TestHalClientMonitor(
+ mContext, mToken, () -> mock(Object.class));
final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
// Pretend the scheduler is busy so the first operation doesn't start right away. We want
// to pretend like there are two operations in the queue before kicking things off
- mScheduler.mCurrentOperation = new BiometricScheduler.Operation(
+ mScheduler.mCurrentOperation = new BiometricSchedulerOperation(
mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class));
mScheduler.scheduleClientMonitor(client1, callback1);
@@ -122,11 +129,11 @@
mScheduler.scheduleClientMonitor(client2, callback2);
waitForIdle();
- assertTrue(client1.wasUnableToStart());
+ assertTrue(client1.mUnableToStart);
verify(callback1).onClientFinished(eq(client1), eq(false) /* success */);
verify(callback1, never()).onClientStarted(any());
- assertTrue(client2.wasUnableToStart());
+ assertTrue(client2.mUnableToStart);
verify(callback2).onClientFinished(eq(client2), eq(false) /* success */);
verify(callback2, never()).onClientStarted(any());
@@ -138,21 +145,19 @@
// Second non-BiometricPrompt client has a valid daemon
final Object daemon2 = mock(Object.class);
- final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> null;
- final HalClientMonitor.LazyDaemon<Object> lazyDaemon2 = () -> daemon2;
-
final ClientMonitorCallbackConverter listener1 = mock(ClientMonitorCallbackConverter.class);
final TestAuthenticationClient client1 =
- new TestAuthenticationClient(mContext, lazyDaemon1, mToken, listener1);
- final TestClientMonitor client2 = new TestClientMonitor(mContext, mToken, lazyDaemon2);
+ new TestAuthenticationClient(mContext, () -> null, mToken, listener1);
+ final TestHalClientMonitor client2 =
+ new TestHalClientMonitor(mContext, mToken, () -> daemon2);
final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
// Pretend the scheduler is busy so the first operation doesn't start right away. We want
// to pretend like there are two operations in the queue before kicking things off
- mScheduler.mCurrentOperation = new BiometricScheduler.Operation(
+ mScheduler.mCurrentOperation = new BiometricSchedulerOperation(
mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class));
mScheduler.scheduleClientMonitor(client1, callback1);
@@ -172,8 +177,8 @@
verify(callback1, never()).onClientStarted(any());
// Client 2 was able to start
- assertFalse(client2.wasUnableToStart());
- assertTrue(client2.hasStarted());
+ assertFalse(client2.mUnableToStart);
+ assertTrue(client2.mStarted);
verify(callback2).onClientStarted(eq(client2));
}
@@ -187,16 +192,18 @@
// Schedule a BiometricPrompt authentication request
mScheduler.scheduleClientMonitor(client1, callback1);
- assertEquals(Operation.STATE_WAITING_FOR_COOKIE, mScheduler.mCurrentOperation.mState);
- assertEquals(client1, mScheduler.mCurrentOperation.mClientMonitor);
+ assertNotEquals(0, mScheduler.mCurrentOperation.isReadyToStart());
+ assertEquals(client1, mScheduler.mCurrentOperation.getClientMonitor());
assertEquals(0, mScheduler.mPendingOperations.size());
// Request it to be canceled. The operation can be canceled immediately, and the scheduler
// should go back to idle, since in this case the framework has not even requested the HAL
// to authenticate yet.
mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
+ waitForIdle();
assertTrue(client1.isAlreadyDone());
assertTrue(client1.mDestroyed);
+ assertFalse(client1.mStartedHal);
assertNull(mScheduler.mCurrentOperation);
}
@@ -210,8 +217,8 @@
// assertEquals(0, bsp.recentOperations.length);
// Pretend the scheduler is busy enrolling, and check the proto dump again.
- final TestClientMonitor2 client = new TestClientMonitor2(mContext, mToken,
- () -> mock(Object.class), BiometricsProto.CM_ENROLL);
+ final TestHalClientMonitor client = new TestHalClientMonitor(mContext, mToken,
+ () -> mock(Object.class), 0, BiometricsProto.CM_ENROLL);
mScheduler.scheduleClientMonitor(client);
waitForIdle();
bsp = getDump(true /* clearSchedulerBuffer */);
@@ -230,8 +237,8 @@
@Test
public void testProtoDump_fifo() throws Exception {
// Add the first operation
- final TestClientMonitor2 client = new TestClientMonitor2(mContext, mToken,
- () -> mock(Object.class), BiometricsProto.CM_ENROLL);
+ final TestHalClientMonitor client = new TestHalClientMonitor(mContext, mToken,
+ () -> mock(Object.class), 0, BiometricsProto.CM_ENROLL);
mScheduler.scheduleClientMonitor(client);
waitForIdle();
BiometricSchedulerProto bsp = getDump(false /* clearSchedulerBuffer */);
@@ -244,8 +251,8 @@
client.getCallback().onClientFinished(client, true);
// Add another operation
- final TestClientMonitor2 client2 = new TestClientMonitor2(mContext, mToken,
- () -> mock(Object.class), BiometricsProto.CM_REMOVE);
+ final TestHalClientMonitor client2 = new TestHalClientMonitor(mContext, mToken,
+ () -> mock(Object.class), 0, BiometricsProto.CM_REMOVE);
mScheduler.scheduleClientMonitor(client2);
waitForIdle();
bsp = getDump(false /* clearSchedulerBuffer */);
@@ -256,8 +263,8 @@
client2.getCallback().onClientFinished(client2, true);
// And another operation
- final TestClientMonitor2 client3 = new TestClientMonitor2(mContext, mToken,
- () -> mock(Object.class), BiometricsProto.CM_AUTHENTICATE);
+ final TestHalClientMonitor client3 = new TestHalClientMonitor(mContext, mToken,
+ () -> mock(Object.class), 0, BiometricsProto.CM_AUTHENTICATE);
mScheduler.scheduleClientMonitor(client3);
waitForIdle();
bsp = getDump(false /* clearSchedulerBuffer */);
@@ -290,8 +297,7 @@
@Test
public void testCancelPendingAuth() throws RemoteException {
final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
-
- final TestClientMonitor client1 = new TestClientMonitor(mContext, mToken, lazyDaemon);
+ final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon);
final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
mToken, callback);
@@ -302,14 +308,12 @@
waitForIdle();
assertEquals(mScheduler.getCurrentClient(), client1);
- assertEquals(Operation.STATE_WAITING_IN_QUEUE,
- mScheduler.mPendingOperations.getFirst().mState);
+ assertFalse(mScheduler.mPendingOperations.getFirst().isStarted());
// Request cancel before the authentication client has started
mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
waitForIdle();
- assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING,
- mScheduler.mPendingOperations.getFirst().mState);
+ assertTrue(mScheduler.mPendingOperations.getFirst().isMarkedCanceling());
// Finish the blocking client. The authentication client should send ERROR_CANCELED
client1.getCallback().onClientFinished(client1, true /* success */);
@@ -326,67 +330,109 @@
@Test
public void testCancels_whenAuthRequestIdNotSet() {
- testCancelsWhenRequestId(null /* requestId */, 2, true /* started */);
+ testCancelsAuthDetectWhenRequestId(null /* requestId */, 2, true /* started */);
}
@Test
public void testCancels_whenAuthRequestIdNotSet_notStarted() {
- testCancelsWhenRequestId(null /* requestId */, 2, false /* started */);
+ testCancelsAuthDetectWhenRequestId(null /* requestId */, 2, false /* started */);
}
@Test
public void testCancels_whenAuthRequestIdMatches() {
- testCancelsWhenRequestId(200L, 200, true /* started */);
+ testCancelsAuthDetectWhenRequestId(200L, 200, true /* started */);
}
@Test
public void testCancels_whenAuthRequestIdMatches_noStarted() {
- testCancelsWhenRequestId(200L, 200, false /* started */);
+ testCancelsAuthDetectWhenRequestId(200L, 200, false /* started */);
}
@Test
public void testDoesNotCancel_whenAuthRequestIdMismatched() {
- testCancelsWhenRequestId(10L, 20, true /* started */);
+ testCancelsAuthDetectWhenRequestId(10L, 20, true /* started */);
}
@Test
public void testDoesNotCancel_whenAuthRequestIdMismatched_notStarted() {
- testCancelsWhenRequestId(10L, 20, false /* started */);
+ testCancelsAuthDetectWhenRequestId(10L, 20, false /* started */);
+ }
+
+ private void testCancelsAuthDetectWhenRequestId(@Nullable Long requestId, long cancelRequestId,
+ boolean started) {
+ final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+ final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+ testCancelsWhenRequestId(requestId, cancelRequestId, started,
+ new TestAuthenticationClient(mContext, lazyDaemon, mToken, callback));
+ }
+
+ @Test
+ public void testCancels_whenEnrollRequestIdNotSet() {
+ testCancelsEnrollWhenRequestId(null /* requestId */, 2, false /* started */);
+ }
+
+ @Test
+ public void testCancels_whenEnrollRequestIdMatches() {
+ testCancelsEnrollWhenRequestId(200L, 200, false /* started */);
+ }
+
+ @Test
+ public void testDoesNotCancel_whenEnrollRequestIdMismatched() {
+ testCancelsEnrollWhenRequestId(10L, 20, false /* started */);
+ }
+
+ private void testCancelsEnrollWhenRequestId(@Nullable Long requestId, long cancelRequestId,
+ boolean started) {
+ final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+ final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+ testCancelsWhenRequestId(requestId, cancelRequestId, started,
+ new TestEnrollClient(mContext, lazyDaemon, mToken, callback));
}
private void testCancelsWhenRequestId(@Nullable Long requestId, long cancelRequestId,
- boolean started) {
+ boolean started, HalClientMonitor<?> client) {
final boolean matches = requestId == null || requestId == cancelRequestId;
- final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
- final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
- final TestAuthenticationClient client = new TestAuthenticationClient(
- mContext, lazyDaemon, mToken, callback);
if (requestId != null) {
client.setRequestId(requestId);
}
+ final boolean isAuth = client instanceof TestAuthenticationClient;
+ final boolean isEnroll = client instanceof TestEnrollClient;
+
mScheduler.scheduleClientMonitor(client);
if (started) {
mScheduler.startPreparedClient(client.getCookie());
}
waitForIdle();
- mScheduler.cancelAuthenticationOrDetection(mToken, cancelRequestId);
+ if (isAuth) {
+ mScheduler.cancelAuthenticationOrDetection(mToken, cancelRequestId);
+ } else if (isEnroll) {
+ mScheduler.cancelEnrollment(mToken, cancelRequestId);
+ } else {
+ fail("unexpected operation type");
+ }
waitForIdle();
- assertEquals(matches && started ? 1 : 0, client.mNumCancels);
+ if (isAuth) {
+ // auth clients that were waiting for cookie when canceled should never invoke the hal
+ final TestAuthenticationClient authClient = (TestAuthenticationClient) client;
+ assertEquals(matches && started ? 1 : 0, authClient.mNumCancels);
+ assertEquals(started, authClient.mStartedHal);
+ } else if (isEnroll) {
+ final TestEnrollClient enrollClient = (TestEnrollClient) client;
+ assertEquals(matches ? 1 : 0, enrollClient.mNumCancels);
+ assertTrue(enrollClient.mStartedHal);
+ }
if (matches) {
- if (started) {
- assertEquals(Operation.STATE_STARTED_CANCELING,
- mScheduler.mCurrentOperation.mState);
+ if (started || isEnroll) { // prep'd auth clients and enroll clients
+ assertTrue(mScheduler.mCurrentOperation.isCanceling());
}
} else {
- if (started) {
- assertEquals(Operation.STATE_STARTED,
- mScheduler.mCurrentOperation.mState);
+ if (started || isEnroll) { // prep'd auth clients and enroll clients
+ assertTrue(mScheduler.mCurrentOperation.isStarted());
} else {
- assertEquals(Operation.STATE_WAITING_FOR_COOKIE,
- mScheduler.mCurrentOperation.mState);
+ assertNotEquals(0, mScheduler.mCurrentOperation.isReadyToStart());
}
}
}
@@ -411,18 +457,14 @@
mScheduler.cancelAuthenticationOrDetection(mToken, 9999);
waitForIdle();
- assertEquals(Operation.STATE_STARTED,
- mScheduler.mCurrentOperation.mState);
- assertEquals(Operation.STATE_WAITING_IN_QUEUE,
- mScheduler.mPendingOperations.getFirst().mState);
+ assertTrue(mScheduler.mCurrentOperation.isStarted());
+ assertFalse(mScheduler.mPendingOperations.getFirst().isStarted());
mScheduler.cancelAuthenticationOrDetection(mToken, requestId2);
waitForIdle();
- assertEquals(Operation.STATE_STARTED,
- mScheduler.mCurrentOperation.mState);
- assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING,
- mScheduler.mPendingOperations.getFirst().mState);
+ assertTrue(mScheduler.mCurrentOperation.isStarted());
+ assertTrue(mScheduler.mPendingOperations.getFirst().isMarkedCanceling());
}
@Test
@@ -459,12 +501,12 @@
@Test
public void testClientDestroyed_afterFinish() {
final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
- final TestClientMonitor client =
- new TestClientMonitor(mContext, mToken, nonNullDaemon);
+ final TestHalClientMonitor client =
+ new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
mScheduler.scheduleClientMonitor(client);
client.mCallback.onClientFinished(client, true /* success */);
waitForIdle();
- assertTrue(client.wasDestroyed());
+ assertTrue(client.mDestroyed);
}
private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
@@ -472,8 +514,10 @@
}
private static class TestAuthenticationClient extends AuthenticationClient<Object> {
- int mNumCancels = 0;
+ boolean mStartedHal = false;
+ boolean mStoppedHal = false;
boolean mDestroyed = false;
+ int mNumCancels = 0;
public TestAuthenticationClient(@NonNull Context context,
@NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
@@ -488,18 +532,16 @@
@Override
protected void stopHalOperation() {
-
+ mStoppedHal = true;
}
@Override
protected void startHalOperation() {
-
+ mStartedHal = true;
}
@Override
- protected void handleLifecycleAfterAuth(boolean authenticated) {
-
- }
+ protected void handleLifecycleAfterAuth(boolean authenticated) {}
@Override
public boolean wasUserDetected() {
@@ -519,36 +561,59 @@
}
}
- private static class TestClientMonitor2 extends TestClientMonitor {
- private final int mProtoEnum;
+ private static class TestEnrollClient extends EnrollClient<Object> {
+ boolean mStartedHal = false;
+ boolean mStoppedHal = false;
+ int mNumCancels = 0;
- public TestClientMonitor2(@NonNull Context context, @NonNull IBinder token,
- @NonNull LazyDaemon<Object> lazyDaemon, int protoEnum) {
- super(context, token, lazyDaemon);
- mProtoEnum = protoEnum;
+ TestEnrollClient(@NonNull Context context,
+ @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
+ @NonNull ClientMonitorCallbackConverter listener) {
+ super(context, lazyDaemon, token, listener, 0 /* userId */, new byte[69],
+ "test" /* owner */, mock(BiometricUtils.class),
+ 5 /* timeoutSec */, 0 /* statsModality */, TEST_SENSOR_ID,
+ true /* shouldVibrate */);
}
@Override
- public int getProtoEnum() {
- return mProtoEnum;
+ protected void stopHalOperation() {
+ mStoppedHal = true;
+ }
+
+ @Override
+ protected void startHalOperation() {
+ mStartedHal = true;
+ }
+
+ @Override
+ protected boolean hasReachedEnrollmentLimit() {
+ return false;
+ }
+
+ @Override
+ public void cancel() {
+ mNumCancels++;
+ super.cancel();
}
}
- private static class TestClientMonitor extends HalClientMonitor<Object> {
+ private static class TestHalClientMonitor extends HalClientMonitor<Object> {
+ private final int mProtoEnum;
private boolean mUnableToStart;
private boolean mStarted;
private boolean mDestroyed;
- public TestClientMonitor(@NonNull Context context, @NonNull IBinder token,
+ TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token,
@NonNull LazyDaemon<Object> lazyDaemon) {
- this(context, token, lazyDaemon, 0 /* cookie */);
+ this(context, token, lazyDaemon, 0 /* cookie */, BiometricsProto.CM_UPDATE_ACTIVE_USER);
}
- public TestClientMonitor(@NonNull Context context, @NonNull IBinder token,
- @NonNull LazyDaemon<Object> lazyDaemon, int cookie) {
+ TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token,
+ @NonNull LazyDaemon<Object> lazyDaemon, int cookie, int protoEnum) {
super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */,
TAG, cookie, TEST_SENSOR_ID, 0 /* statsModality */,
0 /* statsAction */, 0 /* statsClient */);
+ mProtoEnum = protoEnum;
}
@Override
@@ -559,9 +624,7 @@
@Override
public int getProtoEnum() {
- // Anything other than CM_NONE, which is used to represent "idle". Tests that need
- // real proto enums should use TestClientMonitor2
- return BiometricsProto.CM_UPDATE_ACTIVE_USER;
+ return mProtoEnum;
}
@Override
@@ -573,7 +636,7 @@
@Override
protected void startHalOperation() {
-
+ mStarted = true;
}
@Override
@@ -581,22 +644,9 @@
super.destroy();
mDestroyed = true;
}
-
- public boolean wasUnableToStart() {
- return mUnableToStart;
- }
-
- public boolean hasStarted() {
- return mStarted;
- }
-
- public boolean wasDestroyed() {
- return mDestroyed;
- }
-
}
- private static void waitForIdle() {
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ private void waitForIdle() {
+ TestableLooper.get(this).processAllMessages();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
index 7fccd49..407f5fb 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors;
+import static android.testing.TestableLooper.RunWithLooper;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
@@ -28,52 +30,53 @@
import android.content.Context;
import android.hardware.biometrics.IBiometricService;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@Presubmit
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
@SmallTest
public class UserAwareBiometricSchedulerTest {
- private static final String TAG = "BiometricSchedulerTest";
+ private static final String TAG = "UserAwareBiometricSchedulerTest";
private static final int TEST_SENSOR_ID = 0;
+ private Handler mHandler;
private UserAwareBiometricScheduler mScheduler;
- private IBinder mToken;
+ private IBinder mToken = new Binder();
@Mock
private Context mContext;
@Mock
private IBiometricService mBiometricService;
- private TestUserStartedCallback mUserStartedCallback;
- private TestUserStoppedCallback mUserStoppedCallback;
+ private TestUserStartedCallback mUserStartedCallback = new TestUserStartedCallback();
+ private TestUserStoppedCallback mUserStoppedCallback = new TestUserStoppedCallback();
private int mCurrentUserId = UserHandle.USER_NULL;
- private boolean mStartOperationsFinish;
- private int mStartUserClientCount;
+ private boolean mStartOperationsFinish = true;
+ private int mStartUserClientCount = 0;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
-
- mToken = new Binder();
- mStartOperationsFinish = true;
- mStartUserClientCount = 0;
- mUserStartedCallback = new TestUserStartedCallback();
- mUserStoppedCallback = new TestUserStoppedCallback();
-
+ mHandler = new Handler(TestableLooper.get(this).getLooper());
mScheduler = new UserAwareBiometricScheduler(TAG,
+ mHandler,
BiometricScheduler.SENSOR_TYPE_UNKNOWN,
null /* gestureAvailabilityDispatcher */,
mBiometricService,
@@ -117,7 +120,7 @@
mCurrentUserId = UserHandle.USER_NULL;
mStartOperationsFinish = false;
- final BaseClientMonitor[] nextClients = new BaseClientMonitor[] {
+ final BaseClientMonitor[] nextClients = new BaseClientMonitor[]{
mock(BaseClientMonitor.class),
mock(BaseClientMonitor.class),
mock(BaseClientMonitor.class)
@@ -147,11 +150,11 @@
waitForIdle();
final TestStartUserClient startUserClient =
- (TestStartUserClient) mScheduler.mCurrentOperation.mClientMonitor;
+ (TestStartUserClient) mScheduler.mCurrentOperation.getClientMonitor();
mScheduler.reset();
assertNull(mScheduler.mCurrentOperation);
- final BiometricScheduler.Operation fakeOperation = new BiometricScheduler.Operation(
+ final BiometricSchedulerOperation fakeOperation = new BiometricSchedulerOperation(
mock(BaseClientMonitor.class), new BaseClientMonitor.Callback() {});
mScheduler.mCurrentOperation = fakeOperation;
startUserClient.mCallback.onClientFinished(startUserClient, true);
@@ -194,8 +197,8 @@
verify(nextClient).start(any());
}
- private static void waitForIdle() {
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ private void waitForIdle() {
+ TestableLooper.get(this).processAllMessages();
}
private class TestUserStoppedCallback implements StopUserClient.UserStoppedCallback {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index a13dff2..2718bf9 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -33,6 +33,7 @@
import androidx.test.filters.SmallTest;
import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.CoexCoordinator;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -79,10 +80,13 @@
when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
mScheduler = new UserAwareBiometricScheduler(TAG,
+ new Handler(mLooper.getLooper()),
BiometricScheduler.SENSOR_TYPE_FACE,
null /* gestureAvailabilityDispatcher */,
+ mBiometricService,
() -> USER_ID,
- mUserSwitchCallback);
+ mUserSwitchCallback,
+ CoexCoordinator.getInstance());
mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
TAG, mScheduler, SENSOR_ID,
USER_ID, mLockoutCache, mLockoutResetDispatcher, mHalSessionCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index 39c51d5..21a7a8a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -32,7 +32,9 @@
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
@@ -69,6 +71,7 @@
@Mock
private BiometricScheduler mScheduler;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
private LockoutResetDispatcher mLockoutResetDispatcher;
private com.android.server.biometrics.sensors.face.hidl.Face10 mFace10;
private IBinder mBinder;
@@ -97,7 +100,7 @@
resetLockoutRequiresChallenge);
Face10.sSystemClock = Clock.fixed(Instant.ofEpochMilli(100), ZoneId.of("PST"));
- mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mScheduler);
+ mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mHandler, mScheduler);
mBinder = new Binder();
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index 0d520ca..d4609b5 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -33,6 +33,7 @@
import androidx.test.filters.SmallTest;
import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.CoexCoordinator;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -79,10 +80,13 @@
when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
mScheduler = new UserAwareBiometricScheduler(TAG,
+ new Handler(mLooper.getLooper()),
BiometricScheduler.SENSOR_TYPE_FP_OTHER,
null /* gestureAvailabilityDispatcher */,
+ mBiometricService,
() -> USER_ID,
- mUserSwitchCallback);
+ mUserSwitchCallback,
+ CoexCoordinator.getInstance());
mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
TAG, mScheduler, SENSOR_ID,
USER_ID, mLockoutCache, mLockoutResetDispatcher, mHalSessionCallback);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index c7c0756..0a0f7d7 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -26,6 +26,7 @@
import static org.testng.Assert.assertThrows;
import android.Manifest;
+import android.companion.virtual.VirtualDeviceParams;
import android.content.Context;
import android.content.ContextWrapper;
import android.graphics.Point;
@@ -70,6 +71,8 @@
private InputController.NativeWrapper mNativeWrapperMock;
@Mock
private DisplayManagerInternal mDisplayManagerInternalMock;
+ @Mock
+ private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback;
@Before
public void setUp() {
@@ -84,7 +87,8 @@
mInputController = new InputController(new Object(), mNativeWrapperMock);
mDeviceImpl = new VirtualDeviceImpl(mContext,
/* association info */ null, new Binder(), /* uid */ 0, mInputController,
- (int associationId) -> {});
+ (int associationId) -> {}, mPendingTrampolineCallback,
+ new VirtualDeviceParams.Builder().build());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
new file mode 100644
index 0000000..77f1e24
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.companion.virtual;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.companion.virtual.VirtualDeviceParams;
+import android.os.Parcel;
+import android.os.UserHandle;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualDeviceParamsTest {
+
+ @Test
+ public void parcelable_shouldRecreateSuccessfully() {
+ VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder()
+ .setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED)
+ .setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
+ .build();
+ Parcel parcel = Parcel.obtain();
+ originalParams.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ VirtualDeviceParams params = VirtualDeviceParams.CREATOR.createFromParcel(parcel);
+ assertThat(params).isEqualTo(originalParams);
+ assertThat(params.getLockState()).isEqualTo(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED);
+ assertThat(params.getUsersWithMatchingAccounts())
+ .containsExactly(UserHandle.of(123), UserHandle.of(456));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 3c809f9..1228d62 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -37,6 +37,8 @@
import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
import static android.app.admin.PasswordMetrics.computeForPasswordOrPin;
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
import static android.net.InetAddresses.parseNumericAddress;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
@@ -100,7 +102,7 @@
import android.content.pm.UserInfo;
import android.graphics.Color;
import android.hardware.usb.UsbManager;
-import android.net.ConnectivityManager;
+import android.net.ProfileNetworkPreference;
import android.net.Uri;
import android.os.Build;
import android.os.Build.VERSION_CODES;
@@ -4058,12 +4060,15 @@
mServiceContext.permissions.add(permission.INTERACT_ACROSS_USERS_FULL);
dpms.handleStartUser(managedProfileUserId);
- verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference(
- eq(UserHandle.of(managedProfileUserId)),
- anyInt(),
- any(),
- any()
- );
+ ProfileNetworkPreference preferenceDetails =
+ new ProfileNetworkPreference.Builder()
+ .setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT)
+ .build();
+ List<ProfileNetworkPreference> preferences = new ArrayList<>();
+ preferences.add(preferenceDetails);
+ verify(getServices().connectivityManager, times(1))
+ .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences,
+ null, null);
}
@Test
@@ -4075,12 +4080,15 @@
mServiceContext.permissions.add(permission.INTERACT_ACROSS_USERS_FULL);
dpms.handleStopUser(managedProfileUserId);
- verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference(
- eq(UserHandle.of(managedProfileUserId)),
- eq(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT),
- any(),
- any()
- );
+ ProfileNetworkPreference preferenceDetails =
+ new ProfileNetworkPreference.Builder()
+ .setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT)
+ .build();
+ List<ProfileNetworkPreference> preferences = new ArrayList<>();
+ preferences.add(preferenceDetails);
+ verify(getServices().connectivityManager, times(1))
+ .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences,
+ null, null);
}
@Test
@@ -4098,21 +4106,29 @@
dpm.setPreferentialNetworkServiceEnabled(false);
assertThat(dpm.isPreferentialNetworkServiceEnabled()).isFalse();
- verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference(
- eq(UserHandle.of(managedProfileUserId)),
- eq(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT),
- any(),
- any()
- );
+
+ ProfileNetworkPreference preferenceDetails =
+ new ProfileNetworkPreference.Builder()
+ .setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT)
+ .build();
+ List<ProfileNetworkPreference> preferences = new ArrayList<>();
+ preferences.add(preferenceDetails);
+ verify(getServices().connectivityManager, times(1))
+ .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences,
+ null, null);
dpm.setPreferentialNetworkServiceEnabled(true);
assertThat(dpm.isPreferentialNetworkServiceEnabled()).isTrue();
- verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference(
- eq(UserHandle.of(managedProfileUserId)),
- eq(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE),
- any(),
- any()
- );
+
+ ProfileNetworkPreference preferenceDetails2 =
+ new ProfileNetworkPreference.Builder()
+ .setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE)
+ .build();
+ List<ProfileNetworkPreference> preferences2 = new ArrayList<>();
+ preferences2.add(preferenceDetails);
+ verify(getServices().connectivityManager, times(1))
+ .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences2,
+ null, null);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
index e286cb2..d54524e 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
@@ -41,10 +41,10 @@
@Test
public void testConstruct() {
final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE /* identifier */,
- "CLOSED" /* name */, DeviceState.FLAG_CANCEL_STICKY_REQUESTS /* flags */);
+ "TEST_CLOSED" /* name */, DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS /* flags */);
assertEquals(state.getIdentifier(), MINIMUM_DEVICE_STATE);
- assertEquals(state.getName(), "CLOSED");
- assertEquals(state.getFlags(), DeviceState.FLAG_CANCEL_STICKY_REQUESTS);
+ assertEquals(state.getName(), "TEST_CLOSED");
+ assertEquals(state.getFlags(), DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
index c9cf2f0..b94fc43 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
@@ -213,6 +213,25 @@
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
}
+ @Test
+ public void cancelOverrideRequestsTest() {
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 1 /* requestedState */, 0 /* flags */);
+ OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 2 /* requestedState */, 0 /* flags */);
+
+ mController.addRequest(firstRequest);
+ mController.addRequest(secondRequest);
+
+ assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
+
+ mController.cancelOverrideRequests();
+
+ assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
+ }
+
private static final class TestStatusChangeListener implements
OverrideRequestController.StatusChangeListener {
private Map<OverrideRequest, Integer> mLastStatusMap = new HashMap<>();
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index abe7d89..176e5a9 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -16,6 +16,8 @@
package com.android.server.display;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
@@ -111,7 +113,7 @@
// Configure the brightness controller and grab an instance of the sensor listener,
// through which we can deliver fake (for test) sensor values.
- controller.configure(true /* enable */, null /* configuration */,
+ controller.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
0 /* brightness */, false /* userChangedBrightness */, 0 /* adjustment */,
false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
@@ -227,7 +229,7 @@
listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000));
// User sets brightness to 100
- mController.configure(true /* enable */, null /* configuration */,
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
@@ -250,7 +252,7 @@
listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000));
// User sets brightness to 100
- mController.configure(true /* enable */, null /* configuration */,
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
@@ -267,7 +269,7 @@
verifyNoMoreInteractions(mBrightnessMappingStrategy);
// User sets idle brightness to 0.5
- mController.configure(true /* enable */, null /* configuration */,
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index aca8632..beecdad 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -20,6 +20,11 @@
import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+import static com.android.server.display.AutomaticBrightnessController
+ .AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE;
+
import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
import static org.junit.Assert.assertEquals;
@@ -47,6 +52,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
@@ -87,6 +93,7 @@
private TestLooper mTestLooper;
private Handler mHandler;
private Binder mDisplayToken;
+ private String mDisplayUniqueId;
private Context mContextSpy;
@Rule
@@ -108,6 +115,7 @@
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
mDisplayToken = null;
+ mDisplayUniqueId = "unique_id";
mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
when(mContextSpy.getContentResolver()).thenReturn(resolver);
@@ -123,8 +131,8 @@
public void testNoHbmData() {
initHandler(null);
final HighBrightnessModeController hbmc = new HighBrightnessModeController(
- mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN,
- DEFAULT_MAX, null, () -> {}, mContextSpy);
+ mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken,
+ mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, () -> {}, mContextSpy);
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
}
@@ -133,9 +141,9 @@
public void testNoHbmData_Enabled() {
initHandler(null);
final HighBrightnessModeController hbmc = new HighBrightnessModeController(
- mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN,
- DEFAULT_MAX, null, () -> {}, mContextSpy);
- hbmc.setAutoBrightnessEnabled(true);
+ mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken,
+ mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, () -> {}, mContextSpy);
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
@@ -152,7 +160,7 @@
public void testAutoBrightnessEnabled_NoLux() {
final HighBrightnessModeController hbmc = createDefaultHbm();
- hbmc.setAutoBrightnessEnabled(true);
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
}
@@ -160,7 +168,7 @@
public void testAutoBrightnessEnabled_LowLux() {
final HighBrightnessModeController hbmc = createDefaultHbm();
- hbmc.setAutoBrightnessEnabled(true);
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
}
@@ -169,7 +177,7 @@
public void testAutoBrightnessEnabled_HighLux() {
final HighBrightnessModeController hbmc = createDefaultHbm();
- hbmc.setAutoBrightnessEnabled(true);
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
}
@@ -178,9 +186,9 @@
public void testAutoBrightnessEnabled_HighLux_ThenDisable() {
final HighBrightnessModeController hbmc = createDefaultHbm();
- hbmc.setAutoBrightnessEnabled(true);
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
- hbmc.setAutoBrightnessEnabled(false);
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_DISABLED);
assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
}
@@ -189,7 +197,7 @@
public void testWithinHighRange_thenOverTime_thenEarnBackTime() {
final HighBrightnessModeController hbmc = createDefaultHbm();
- hbmc.setAutoBrightnessEnabled(true);
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
@@ -221,7 +229,7 @@
public void testInHBM_ThenLowLux() {
final HighBrightnessModeController hbmc = createDefaultHbm();
- hbmc.setAutoBrightnessEnabled(true);
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
@@ -245,7 +253,7 @@
public void testInHBM_TestMultipleEvents_DueToAutoBrightness() {
final HighBrightnessModeController hbmc = createDefaultHbm();
- hbmc.setAutoBrightnessEnabled(true);
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
@@ -274,7 +282,7 @@
public void testInHBM_TestMultipleEvents_DueToLux() {
final HighBrightnessModeController hbmc = createDefaultHbm();
- hbmc.setAutoBrightnessEnabled(true);
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
// Go into HBM for half the allowed window
@@ -316,7 +324,7 @@
listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL));
// Try to go into HBM mode but fail
- hbmc.setAutoBrightnessEnabled(true);
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
advanceTime(10);
@@ -335,7 +343,7 @@
listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_LIGHT));
// Try to go into HBM mode
- hbmc.setAutoBrightnessEnabled(true);
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
advanceTime(1);
@@ -378,7 +386,7 @@
final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
// Turn on sunlight
- hbmc.setAutoBrightnessEnabled(true);
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
advanceTime(0);
assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
@@ -451,6 +459,137 @@
assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
}
+ @Test
+ public void testHbmStats_StateChange() {
+ final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+ final int displayStatsId = mDisplayUniqueId.hashCode();
+
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+ hbmc.onBrightnessChanged(TRANSITION_POINT);
+ hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/);
+ advanceTime(0);
+ assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
+
+ // Verify Stats HBM_ON_HDR
+ verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+ hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 0 /*numberOfHdrLayers*/,
+ 0, 0, 0 /*flags*/);
+ advanceTime(0);
+
+ // Verify Stats HBM_OFF
+ verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+ hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+ hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+
+ // Verify Stats HBM_ON_SUNLIGHT
+ verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+ hbmc.onAmbientLuxChange(1);
+ advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2 + 1);
+
+ // Verify Stats HBM_OFF
+ verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP));
+ }
+
+ @Test
+ public void testHbmStats_ThermalOff() throws Exception {
+ final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+ final int displayStatsId = mDisplayUniqueId.hashCode();
+
+ verify(mThermalServiceMock).registerThermalEventListenerWithType(
+ mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+ final IThermalEventListener thermListener = mThermalEventListenerCaptor.getValue();
+
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+ hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+ hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ advanceTime(1);
+ verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+ thermListener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL));
+ advanceTime(10);
+ assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode());
+ verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT));
+ }
+
+ @Test
+ public void testHbmStats_TimeOut() {
+ final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+ final int displayStatsId = mDisplayUniqueId.hashCode();
+
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+ hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+ hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ advanceTime(0);
+ verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+ // Use up all the time in the window.
+ advanceTime(TIME_WINDOW_MILLIS + 1);
+
+ verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT));
+ }
+
+ @Test
+ public void testHbmStats_DisplayOff() {
+ final HighBrightnessModeController hbmc = createDefaultHbm();
+ final int displayStatsId = mDisplayUniqueId.hashCode();
+
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+ hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+ hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ advanceTime(0);
+ verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
+ verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF));
+ }
+
+ @Test
+ public void testHbmStats_HdrPlaying() {
+ final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+ final int displayStatsId = mDisplayUniqueId.hashCode();
+
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+ hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+ hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ advanceTime(0);
+ verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+ hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/);
+ advanceTime(0);
+
+ verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING));
+ }
+
private void assertState(HighBrightnessModeController hbmc,
float brightnessMin, float brightnessMax, int hbmMode) {
assertEquals(brightnessMin, hbmc.getCurrentBrightnessMin(), EPSILON);
@@ -466,8 +605,8 @@
private HighBrightnessModeController createDefaultHbm(OffsettableClock clock) {
initHandler(clock);
return new HighBrightnessModeController(mInjectorMock, mHandler, DISPLAY_WIDTH,
- DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN, DEFAULT_MAX, DEFAULT_HBM_DATA, () -> {},
- mContextSpy);
+ DISPLAY_HEIGHT, mDisplayToken, mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX,
+ DEFAULT_HBM_DATA, () -> {}, mContextSpy);
}
private void initHandler(OffsettableClock clock) {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java
new file mode 100644
index 0000000..51ddcef
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java
@@ -0,0 +1,186 @@
+/*
+ * 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.locksettings;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.PasswordMetrics;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
+import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
+import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.VerifyCredentialResponse;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** atest FrameworksServicesTests:WeakEscrowTokenTests */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WeakEscrowTokenTests extends BaseLockSettingsServiceTests{
+
+ @Test
+ public void testWeakTokenActivatedImmediatelyIfNoUserPassword()
+ throws RemoteException {
+ mockAutoHardware();
+ final byte[] token = "some-high-entropy-secure-token".getBytes();
+ IWeakEscrowTokenActivatedListener mockListener =
+ mock(IWeakEscrowTokenActivatedListener.Stub.class);
+ long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockListener);
+ assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
+ assertTrue(mService.isWeakEscrowTokenValid(handle, token, PRIMARY_USER_ID));
+ verify(mockListener).onWeakEscrowTokenActivated(handle, PRIMARY_USER_ID);
+ }
+
+ @Test
+ public void testWeakTokenActivatedLaterWithUserPassword()
+ throws RemoteException {
+ mockAutoHardware();
+ byte[] token = "some-high-entropy-secure-token".getBytes();
+ IWeakEscrowTokenActivatedListener mockListener =
+ mock(IWeakEscrowTokenActivatedListener.Stub.class);
+ LockscreenCredential password = newPassword("password");
+ mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID);
+
+ long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockListener);
+ // Token not activated immediately since user password exists
+ assertFalse(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
+ // Activate token (password gets migrated to SP at the same time)
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
+ // Verify token is activated and valid
+ assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
+ assertTrue(mService.isWeakEscrowTokenValid(handle, token, PRIMARY_USER_ID));
+ verify(mockListener).onWeakEscrowTokenActivated(handle, PRIMARY_USER_ID);
+ }
+
+ @Test
+ public void testWeakTokensRemovedIfCredentialChanged() throws Exception {
+ mockAutoHardware();
+ byte[] token = "some-high-entropy-secure-token".getBytes();
+ IWeakEscrowTokenRemovedListener mockRemoveListener = mockAliveRemoveListener();
+ IWeakEscrowTokenActivatedListener mockActivateListener =
+ mock(IWeakEscrowTokenActivatedListener.Stub.class);
+ LockscreenCredential password = newPassword("password");
+ LockscreenCredential pattern = newPattern("123654");
+ mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID);
+ mService.registerWeakEscrowTokenRemovedListener(mockRemoveListener);
+
+ long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockActivateListener);
+
+ // Activate token
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
+
+ // Verify token removed
+ assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
+ assertTrue(mLocalService.setLockCredentialWithToken(
+ pattern, handle, token, PRIMARY_USER_ID));
+ assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+ verify(mockRemoveListener).onWeakEscrowTokenRemoved(handle, PRIMARY_USER_ID);
+ }
+
+ @Test
+ public void testWeakTokenRemovedListenerRegistered() throws Exception {
+ mockAutoHardware();
+ IWeakEscrowTokenRemovedListener mockRemoveListener = mockAliveRemoveListener();
+ IWeakEscrowTokenActivatedListener mockActivateListener =
+ mock(IWeakEscrowTokenActivatedListener.Stub.class);
+ byte[] token = "some-high-entropy-secure-token".getBytes();
+ long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockActivateListener);
+
+ mService.registerWeakEscrowTokenRemovedListener(mockRemoveListener);
+ mService.removeWeakEscrowToken(handle, PRIMARY_USER_ID);
+
+ verify(mockRemoveListener).onWeakEscrowTokenRemoved(handle, PRIMARY_USER_ID);
+ }
+
+ @Test
+ public void testWeakTokenRemovedListenerUnregistered() throws Exception {
+ mockAutoHardware();
+ IWeakEscrowTokenRemovedListener mockRemoveListener = mockAliveRemoveListener();
+ IWeakEscrowTokenActivatedListener mockActivateListener =
+ mock(IWeakEscrowTokenActivatedListener.Stub.class);
+ byte[] token0 = "some-high-entropy-secure-token-0".getBytes();
+ byte[] token1 = "some-high-entropy-secure-token-1".getBytes();
+ long handle0 = mService.addWeakEscrowToken(token0, PRIMARY_USER_ID, mockActivateListener);
+ long handle1 = mService.addWeakEscrowToken(token1, PRIMARY_USER_ID, mockActivateListener);
+
+ mService.registerWeakEscrowTokenRemovedListener(mockRemoveListener);
+ mService.removeWeakEscrowToken(handle0, PRIMARY_USER_ID);
+ verify(mockRemoveListener).onWeakEscrowTokenRemoved(handle0, PRIMARY_USER_ID);
+
+ mService.unregisterWeakEscrowTokenRemovedListener(mockRemoveListener);
+ mService.removeWeakEscrowToken(handle1, PRIMARY_USER_ID);
+ verify(mockRemoveListener, never()).onWeakEscrowTokenRemoved(handle1, PRIMARY_USER_ID);
+ }
+
+ @Test
+ public void testUnlockUserWithToken_weakEscrowToken() throws Exception {
+ mockAutoHardware();
+ IWeakEscrowTokenActivatedListener mockActivateListener =
+ mock(IWeakEscrowTokenActivatedListener.Stub.class);
+ LockscreenCredential password = newPassword("password");
+ byte[] token = "some-high-entropy-secure-token".getBytes();
+ mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID);
+ // Disregard any reportPasswordChanged() invocations as part of credential setup.
+ flushHandlerTasks();
+ reset(mDevicePolicyManager);
+
+ long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockActivateListener);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
+ assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
+ assertTrue(mService.isWeakEscrowTokenValid(handle, token, PRIMARY_USER_ID));
+
+ mService.onCleanupUser(PRIMARY_USER_ID);
+ assertNull(mLocalService.getUserPasswordMetrics(PRIMARY_USER_ID));
+
+ assertTrue(mLocalService.unlockUserWithToken(handle, token, PRIMARY_USER_ID));
+ assertEquals(PasswordMetrics.computeForCredential(password),
+ mLocalService.getUserPasswordMetrics(PRIMARY_USER_ID));
+ }
+
+ private void mockAutoHardware() {
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(true);
+ }
+
+ private IWeakEscrowTokenRemovedListener mockAliveRemoveListener() {
+ IWeakEscrowTokenRemovedListener mockListener =
+ mock(IWeakEscrowTokenRemovedListener.Stub.class);
+ IBinder mockIBinder = mock(IBinder.class);
+ when(mockIBinder.isBinderAlive()).thenReturn(true);
+ when(mockListener.asBinder()).thenReturn(mockIBinder);
+ return mockListener;
+ }
+}
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 b811e28..66df0fe 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -1796,9 +1796,7 @@
}
private void triggerOnStatsProviderWarningOrLimitReached() throws InterruptedException {
- final NetworkPolicyManagerInternal npmi = LocalServices
- .getService(NetworkPolicyManagerInternal.class);
- npmi.onStatsProviderWarningOrLimitReached("TEST");
+ mService.onStatsProviderWarningOrLimitReached();
// Wait for processing of MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED.
postMsgAndWaitForCompletion();
verify(mStatsService).forceUpdate();
diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
index 7f7c716..2f5993d1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -61,7 +61,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.Map;
+import java.util.List;
@SmallTest
@Presubmit
@@ -136,9 +136,10 @@
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
- Map<String, String> services = mApexManager.getApexSystemServices();
+ List<ApexSystemServiceInfo> services = mApexManager.getApexSystemServices();
assertThat(services).hasSize(1);
- assertThat(services).containsKey("com.android.apex.test.ApexSystemService");
+ assertThat(services.stream().map(ApexSystemServiceInfo::getName).findFirst().orElse(null))
+ .matches("com.android.apex.test.ApexSystemService");
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index e4273dc..429445f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -315,8 +315,8 @@
mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ true,
asHandle(currentUser));
try {
- assertThat(mUserManager.removeUserOrSetEphemeral(user1.id,
- /* evenWhenDisallowed= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR);
+ assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
+ /* overrideDevicePolicy= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR);
} finally {
mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ false,
asHandle(currentUser));
@@ -335,8 +335,8 @@
asHandle(currentUser));
try {
synchronized (mUserRemoveLock) {
- assertThat(mUserManager.removeUserOrSetEphemeral(user1.id,
- /* evenWhenDisallowed= */ true))
+ assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
+ /* overrideDevicePolicy= */ true))
.isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
waitForUserRemovalLocked(user1.id);
}
@@ -352,8 +352,8 @@
@MediumTest
@Test
public void testRemoveUserOrSetEphemeral_systemUserReturnsError() throws Exception {
- assertThat(mUserManager.removeUserOrSetEphemeral(UserHandle.USER_SYSTEM,
- /* evenWhenDisallowed= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR);
+ assertThat(mUserManager.removeUserWhenPossible(UserHandle.SYSTEM,
+ /* overrideDevicePolicy= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR);
assertThat(hasUser(UserHandle.USER_SYSTEM)).isTrue();
}
@@ -362,8 +362,8 @@
@Test
public void testRemoveUserOrSetEphemeral_invalidUserReturnsError() throws Exception {
assertThat(hasUser(Integer.MAX_VALUE)).isFalse();
- assertThat(mUserManager.removeUserOrSetEphemeral(Integer.MAX_VALUE,
- /* evenWhenDisallowed= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR);
+ assertThat(mUserManager.removeUserWhenPossible(UserHandle.of(Integer.MAX_VALUE),
+ /* overrideDevicePolicy= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR);
}
@MediumTest
@@ -374,8 +374,8 @@
// Switch to the user just created.
switchUser(user1.id, null, /* ignoreHandle= */ true);
- assertThat(mUserManager.removeUserOrSetEphemeral(user1.id, /* evenWhenDisallowed= */ false))
- .isEqualTo(UserManager.REMOVE_RESULT_DEFERRED);
+ assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
+ /* overrideDevicePolicy= */ false)).isEqualTo(UserManager.REMOVE_RESULT_DEFERRED);
assertThat(hasUser(user1.id)).isTrue();
assertThat(getUser(user1.id).isEphemeral()).isTrue();
@@ -395,8 +395,9 @@
public void testRemoveUserOrSetEphemeral_nonCurrentUserRemoved() throws Exception {
final UserInfo user1 = createUser("User 1", /* flags= */ 0);
synchronized (mUserRemoveLock) {
- assertThat(mUserManager.removeUserOrSetEphemeral(user1.id,
- /* evenWhenDisallowed= */ false)).isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
+ assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
+ /* overrideDevicePolicy= */ false))
+ .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
waitForUserRemovalLocked(user1.id);
}
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 761cea7..90b19a4 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -160,12 +160,12 @@
}
@Test
- public void create_stateWithCancelStickyRequestFlag() {
+ public void create_stateWithCancelOverrideRequestFlag() {
String configString = "<device-state-config>\n"
+ " <device-state>\n"
+ " <identifier>1</identifier>\n"
+ " <flags>\n"
- + " <flag>FLAG_CANCEL_STICKY_REQUESTS</flag>\n"
+ + " <flag>FLAG_CANCEL_OVERRIDE_REQUESTS</flag>\n"
+ " </flags>\n"
+ " <conditions/>\n"
+ " </device-state>\n"
@@ -183,7 +183,7 @@
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
final DeviceState[] expectedStates = new DeviceState[]{
- new DeviceState(1, "", DeviceState.FLAG_CANCEL_STICKY_REQUESTS),
+ new DeviceState(1, "", DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS),
new DeviceState(2, "", 0 /* flags */) };
assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index e47a07c..c832a3e 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -73,7 +73,7 @@
import android.view.Display;
import android.view.DisplayInfo;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.core.app.ApplicationProvider;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.test.FakeSettingsProvider;
@@ -138,7 +138,6 @@
private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
private PowerManagerService mService;
- private PowerSaveState mPowerSaveState;
private DisplayPowerRequest mDisplayPowerRequest;
private ContextWrapper mContextSpy;
private BatteryReceiver mBatteryReceiver;
@@ -147,7 +146,7 @@
private OffsettableClock mClock;
private TestLooper mTestLooper;
- private class IntentFilterMatcher implements ArgumentMatcher<IntentFilter> {
+ private static class IntentFilterMatcher implements ArgumentMatcher<IntentFilter> {
private final IntentFilter mFilter;
IntentFilterMatcher(IntentFilter filter) {
@@ -173,13 +172,13 @@
MockitoAnnotations.initMocks(this);
FakeSettingsProvider.clearSettingsProvider();
- mPowerSaveState = new PowerSaveState.Builder()
+ PowerSaveState powerSaveState = new PowerSaveState.Builder()
.setBatterySaverEnabled(BATTERY_SAVER_ENABLED)
.setBrightnessFactor(BRIGHTNESS_FACTOR)
.build();
when(mBatterySaverPolicyMock.getBatterySaverPolicy(
eq(PowerManager.ServiceType.SCREEN_BRIGHTNESS)))
- .thenReturn(mPowerSaveState);
+ .thenReturn(powerSaveState);
when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(false);
when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(false);
when(mDisplayManagerInternalMock.requestPowerState(anyInt(), any(), anyBoolean()))
@@ -195,7 +194,7 @@
addLocalServiceMock(AttentionManagerInternal.class, mAttentionManagerInternalMock);
addLocalServiceMock(DreamManagerInternal.class, mDreamManagerInternalMock);
- mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+ mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
mResourcesSpy = spy(mContextSpy.getResources());
when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
@@ -304,8 +303,8 @@
LocalServices.addService(clazz, mock);
}
- private void startSystem() throws Exception {
- mService.systemReady(null);
+ private void startSystem() {
+ mService.systemReady();
// Grab the BatteryReceiver
ArgumentCaptor<BatteryReceiver> batCaptor = ArgumentCaptor.forClass(BatteryReceiver.class);
@@ -403,9 +402,9 @@
}
@Test
- public void testGetDesiredScreenPolicy_WithVR() throws Exception {
+ public void testGetDesiredScreenPolicy_WithVR() {
createService();
- mService.systemReady(null);
+ mService.systemReady();
// Brighten up the screen
mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_AWAKE, 0, 0, 0, 0,
null, null);
@@ -436,13 +435,13 @@
}
@Test
- public void testWakefulnessAwake_InitialValue() throws Exception {
+ public void testWakefulnessAwake_InitialValue() {
createService();
assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
}
@Test
- public void testWakefulnessSleep_NoDozeSleepFlag() throws Exception {
+ public void testWakefulnessSleep_NoDozeSleepFlag() {
createService();
// Start with AWAKE state
startSystem();
@@ -455,7 +454,7 @@
}
@Test
- public void testWakefulnessAwake_AcquireCausesWakeup() throws Exception {
+ public void testWakefulnessAwake_AcquireCausesWakeup() {
createService();
startSystem();
forceSleep();
@@ -487,7 +486,7 @@
}
@Test
- public void testWakefulnessAwake_IPowerManagerWakeUp() throws Exception {
+ public void testWakefulnessAwake_IPowerManagerWakeUp() {
createService();
startSystem();
forceSleep();
@@ -501,9 +500,7 @@
* or docked.
*/
@Test
- public void testWakefulnessAwake_ShouldWakeUpWhenPluggedIn() throws Exception {
- boolean powerState;
-
+ public void testWakefulnessAwake_ShouldWakeUpWhenPluggedIn() {
createService();
startSystem();
forceSleep();
@@ -579,7 +576,7 @@
}
@Test
- public void testWakefulnessDoze_goToSleep() throws Exception {
+ public void testWakefulnessDoze_goToSleep() {
createService();
// Start with AWAKE state
startSystem();
@@ -595,7 +592,7 @@
public void testWasDeviceIdleFor_true() {
int interval = 1000;
createService();
- mService.systemReady(null);
+ mService.systemReady();
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
mService.onUserActivity();
advanceTime(interval + 1 /* just a little more */);
@@ -606,7 +603,7 @@
public void testWasDeviceIdleFor_false() {
int interval = 1000;
createService();
- mService.systemReady(null);
+ mService.systemReady();
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
mService.onUserActivity();
assertThat(mService.wasDeviceIdleForInternal(interval)).isFalse();
@@ -615,7 +612,7 @@
@Test
public void testForceSuspend_putsDeviceToSleep() {
createService();
- mService.systemReady(null);
+ mService.systemReady();
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
// Verify that we start awake
@@ -636,7 +633,7 @@
}
@Test
- public void testForceSuspend_pakeLocksDisabled() {
+ public void testForceSuspend_wakeLocksDisabled() {
final String tag = "TestWakelockTag_098213";
final int flags = PowerManager.PARTIAL_WAKE_LOCK;
final String pkg = mContextSpy.getOpPackageName();
@@ -661,7 +658,7 @@
//
// TEST STARTS HERE
//
- mService.systemReady(null);
+ mService.systemReady();
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
// Verify that we start awake
@@ -686,7 +683,7 @@
}
@Test
- public void testForceSuspend_forceSuspendFailurePropagated() throws Exception {
+ public void testForceSuspend_forceSuspendFailurePropagated() {
createService();
startSystem();
when(mNativeWrapperMock.nativeForceSuspend()).thenReturn(false);
@@ -694,7 +691,7 @@
}
@Test
- public void testSetDozeOverrideFromDreamManager_triggersSuspendBlocker() throws Exception {
+ public void testSetDozeOverrideFromDreamManager_triggersSuspendBlocker() {
final String suspendBlockerName = "PowerManagerService.Display";
final String tag = "acq_causes_wakeup";
final String packageName = "pkg.name";
@@ -741,7 +738,7 @@
}
@Test
- public void testSuspendBlockerHeldDuringBoot() throws Exception {
+ public void testSuspendBlockerHeldDuringBoot() {
final String suspendBlockerName = "PowerManagerService.Booting";
final boolean[] isAcquired = new boolean[1];
@@ -760,7 +757,7 @@
createService();
assertTrue(isAcquired[0]);
- mService.systemReady(null);
+ mService.systemReady();
assertTrue(isAcquired[0]);
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@@ -768,7 +765,7 @@
}
@Test
- public void testInattentiveSleep_hideWarningIfStayOnIsEnabledAndPluggedIn() throws Exception {
+ public void testInattentiveSleep_hideWarningIfStayOnIsEnabledAndPluggedIn() {
setMinimumScreenOffTimeoutConfig(5);
setAttentiveWarningDuration(120);
setAttentiveTimeout(100);
@@ -788,7 +785,7 @@
}
@Test
- public void testInattentiveSleep_hideWarningIfInattentiveSleepIsDisabled() throws Exception {
+ public void testInattentiveSleep_hideWarningIfInattentiveSleepIsDisabled() {
setMinimumScreenOffTimeoutConfig(5);
setAttentiveWarningDuration(120);
setAttentiveTimeout(100);
@@ -807,7 +804,7 @@
}
@Test
- public void testInattentiveSleep_userActivityDismissesWarning() throws Exception {
+ public void testInattentiveSleep_userActivityDismissesWarning() {
final DisplayInfo info = new DisplayInfo();
info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
@@ -833,7 +830,7 @@
}
@Test
- public void testInattentiveSleep_warningHiddenAfterWakingUp() throws Exception {
+ public void testInattentiveSleep_warningHiddenAfterWakingUp() {
setMinimumScreenOffTimeoutConfig(5);
setAttentiveWarningDuration(70);
setAttentiveTimeout(100);
@@ -851,7 +848,7 @@
}
@Test
- public void testInattentiveSleep_noWarningShownIfInattentiveSleepDisabled() throws Exception {
+ public void testInattentiveSleep_noWarningShownIfInattentiveSleepDisabled() {
setAttentiveTimeout(-1);
createService();
startSystem();
@@ -859,7 +856,7 @@
}
@Test
- public void testInattentiveSleep_goesToSleepAfterTimeout() throws Exception {
+ public void testInattentiveSleep_goesToSleepAfterTimeout() {
setMinimumScreenOffTimeoutConfig(5);
setAttentiveTimeout(5);
createService();
@@ -871,7 +868,7 @@
}
@Test
- public void testInattentiveSleep_goesToSleepWithWakeLock() throws Exception {
+ public void testInattentiveSleep_goesToSleepWithWakeLock() {
final String pkg = mContextSpy.getOpPackageName();
final Binder token = new Binder();
final String tag = "testInattentiveSleep_goesToSleepWithWakeLock";
@@ -893,8 +890,7 @@
}
@Test
- public void testInattentiveSleep_wakeLockOnAfterRelease_inattentiveSleepTimeoutNotAffected()
- throws Exception {
+ public void testInattentiveSleep_wakeLockOnAfterRelease_inattentiveSleepTimeoutNotAffected() {
final DisplayInfo info = new DisplayInfo();
info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
@@ -922,8 +918,7 @@
}
@Test
- public void testInattentiveSleep_userActivityNoChangeLights_inattentiveSleepTimeoutNotAffected()
- throws Exception {
+ public void testInattentiveSleep_userActivityNoChangeLights_inattentiveSleepTimeoutNotAffected() {
final DisplayInfo info = new DisplayInfo();
info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
@@ -945,8 +940,7 @@
}
@Test
- public void testInattentiveSleep_userActivity_inattentiveSleepTimeoutExtended()
- throws Exception {
+ public void testInattentiveSleep_userActivity_inattentiveSleepTimeoutExtended() {
final DisplayInfo info = new DisplayInfo();
info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
@@ -965,7 +959,7 @@
}
@Test
- public void testWakeLock_affectsProperDisplayGroup() throws Exception {
+ public void testWakeLock_affectsProperDisplayGroup() {
final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
new AtomicReference<>();
@@ -1005,7 +999,7 @@
}
@Test
- public void testInvalidDisplayGroupWakeLock_affectsAllDisplayGroups() throws Exception {
+ public void testInvalidDisplayGroupWakeLock_affectsAllDisplayGroups() {
final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
new AtomicReference<>();
@@ -1045,7 +1039,7 @@
}
@Test
- public void testRemovedDisplayGroupWakeLock_affectsNoDisplayGroups() throws Exception {
+ public void testRemovedDisplayGroupWakeLock_affectsNoDisplayGroups() {
final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
@@ -1086,7 +1080,7 @@
}
@Test
- public void testBoot_ShouldBeAwake() throws Exception {
+ public void testBoot_ShouldBeAwake() {
createService();
startSystem();
@@ -1095,7 +1089,7 @@
}
@Test
- public void testBoot_DesiredScreenPolicyShouldBeBright() throws Exception {
+ public void testBoot_DesiredScreenPolicyShouldBeBright() {
createService();
startSystem();
@@ -1104,7 +1098,7 @@
}
@Test
- public void testQuiescentBoot_ShouldBeAsleep() throws Exception {
+ public void testQuiescentBoot_ShouldBeAsleep() {
when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
createService();
startSystem();
@@ -1115,7 +1109,7 @@
}
@Test
- public void testQuiescentBoot_DesiredScreenPolicyShouldBeOff() throws Exception {
+ public void testQuiescentBoot_DesiredScreenPolicyShouldBeOff() {
when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
createService();
startSystem();
@@ -1124,7 +1118,7 @@
}
@Test
- public void testQuiescentBoot_WakeUp_DesiredScreenPolicyShouldBeBright() throws Exception {
+ public void testQuiescentBoot_WakeUp_DesiredScreenPolicyShouldBeBright() {
when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
createService();
startSystem();
@@ -1134,11 +1128,10 @@
}
@Test
- public void testQuiescentBoot_WakeKeyBeforeBootCompleted_AwakeAfterBootCompleted()
- throws Exception {
+ public void testQuiescentBoot_WakeKeyBeforeBootCompleted_AwakeAfterBootCompleted() {
when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
createService();
- mService.systemReady(null);
+ mService.systemReady();
mService.getBinderServiceInstance().wakeUp(mClock.now(),
PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
@@ -1150,7 +1143,7 @@
}
@Test
- public void testIsAmbientDisplayAvailable_available() throws Exception {
+ public void testIsAmbientDisplayAvailable_available() {
createService();
when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
@@ -1158,7 +1151,7 @@
}
@Test
- public void testIsAmbientDisplayAvailable_unavailable() throws Exception {
+ public void testIsAmbientDisplayAvailable_unavailable() {
createService();
when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(false);
@@ -1166,14 +1159,14 @@
}
@Test
- public void testIsAmbientDisplaySuppressed_default_notSuppressed() throws Exception {
+ public void testIsAmbientDisplaySuppressed_default_notSuppressed() {
createService();
assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isFalse();
}
@Test
- public void testIsAmbientDisplaySuppressed_suppressed() throws Exception {
+ public void testIsAmbientDisplaySuppressed_suppressed() {
createService();
mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
@@ -1181,7 +1174,7 @@
}
@Test
- public void testIsAmbientDisplaySuppressed_notSuppressed() throws Exception {
+ public void testIsAmbientDisplaySuppressed_notSuppressed() {
createService();
mService.getBinderServiceInstance().suppressAmbientDisplay("test", false);
@@ -1189,7 +1182,7 @@
}
@Test
- public void testIsAmbientDisplaySuppressed_multipleTokens_suppressed() throws Exception {
+ public void testIsAmbientDisplaySuppressed_multipleTokens_suppressed() {
createService();
mService.getBinderServiceInstance().suppressAmbientDisplay("test1", false);
mService.getBinderServiceInstance().suppressAmbientDisplay("test2", true);
@@ -1198,7 +1191,7 @@
}
@Test
- public void testIsAmbientDisplaySuppressed_multipleTokens_notSuppressed() throws Exception {
+ public void testIsAmbientDisplaySuppressed_multipleTokens_notSuppressed() {
createService();
mService.getBinderServiceInstance().suppressAmbientDisplay("test1", false);
mService.getBinderServiceInstance().suppressAmbientDisplay("test2", false);
@@ -1207,7 +1200,7 @@
}
@Test
- public void testIsAmbientDisplaySuppressedForToken_default_notSuppressed() throws Exception {
+ public void testIsAmbientDisplaySuppressedForToken_default_notSuppressed() {
createService();
assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test"))
@@ -1215,7 +1208,7 @@
}
@Test
- public void testIsAmbientDisplaySuppressedForToken_suppressed() throws Exception {
+ public void testIsAmbientDisplaySuppressedForToken_suppressed() {
createService();
mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
@@ -1224,7 +1217,7 @@
}
@Test
- public void testIsAmbientDisplaySuppressedForToken_notSuppressed() throws Exception {
+ public void testIsAmbientDisplaySuppressedForToken_notSuppressed() {
createService();
mService.getBinderServiceInstance().suppressAmbientDisplay("test", false);
@@ -1233,8 +1226,7 @@
}
@Test
- public void testIsAmbientDisplaySuppressedForToken_multipleTokens_suppressed()
- throws Exception {
+ public void testIsAmbientDisplaySuppressedForToken_multipleTokens_suppressed() {
createService();
mService.getBinderServiceInstance().suppressAmbientDisplay("test1", true);
mService.getBinderServiceInstance().suppressAmbientDisplay("test2", true);
@@ -1246,8 +1238,7 @@
}
@Test
- public void testIsAmbientDisplaySuppressedForToken_multipleTokens_notSuppressed()
- throws Exception {
+ public void testIsAmbientDisplaySuppressedForToken_multipleTokens_notSuppressed() {
createService();
mService.getBinderServiceInstance().suppressAmbientDisplay("test1", true);
mService.getBinderServiceInstance().suppressAmbientDisplay("test2", false);
@@ -1259,8 +1250,7 @@
}
@Test
- public void testIsAmbientDisplaySuppressedForTokenByApp_ambientDisplayUnavailable()
- throws Exception {
+ public void testIsAmbientDisplaySuppressedForTokenByApp_ambientDisplayUnavailable() {
createService();
when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(false);
@@ -1270,8 +1260,7 @@
}
@Test
- public void testIsAmbientDisplaySuppressedForTokenByApp_default()
- throws Exception {
+ public void testIsAmbientDisplaySuppressedForTokenByApp_default() {
createService();
BinderService service = mService.getBinderServiceInstance();
@@ -1280,8 +1269,7 @@
}
@Test
- public void testIsAmbientDisplaySuppressedForTokenByApp_suppressedByCallingApp()
- throws Exception {
+ public void testIsAmbientDisplaySuppressedForTokenByApp_suppressedByCallingApp() {
createService();
BinderService service = mService.getBinderServiceInstance();
service.suppressAmbientDisplay("test", true);
@@ -1294,8 +1282,7 @@
}
@Test
- public void testIsAmbientDisplaySuppressedForTokenByApp_notSuppressedByCallingApp()
- throws Exception {
+ public void testIsAmbientDisplaySuppressedForTokenByApp_notSuppressedByCallingApp() {
createService();
BinderService service = mService.getBinderServiceInstance();
service.suppressAmbientDisplay("test", false);
@@ -1308,8 +1295,7 @@
}
@Test
- public void testIsAmbientDisplaySuppressedForTokenByApp_multipleTokensSuppressedByCallingApp()
- throws Exception {
+ public void testIsAmbientDisplaySuppressedForTokenByApp_multipleTokensSuppressedByCallingApp() {
createService();
BinderService service = mService.getBinderServiceInstance();
service.suppressAmbientDisplay("test1", true);
@@ -1358,7 +1344,7 @@
@Test
public void testSetPowerBoost_redirectsCallToNativeWrapper() {
createService();
- mService.systemReady(null);
+ mService.systemReady();
mService.getBinderServiceInstance().setPowerBoost(Boost.INTERACTION, 1234);
@@ -1368,7 +1354,7 @@
@Test
public void testSetPowerMode_redirectsCallToNativeWrapper() {
createService();
- mService.systemReady(null);
+ mService.systemReady();
// Enabled launch boost in BatterySaverController to allow setting launch mode.
when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(false);
@@ -1384,7 +1370,7 @@
@Test
public void testSetPowerMode_withLaunchBoostDisabledAndModeLaunch_ignoresCallToEnable() {
createService();
- mService.systemReady(null);
+ mService.systemReady();
// Disables launch boost in BatterySaverController.
when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(true);
@@ -1400,7 +1386,7 @@
@Test
public void testSetPowerModeChecked_returnsNativeCallResult() {
createService();
- mService.systemReady(null);
+ mService.systemReady();
// Disables launch boost in BatterySaverController.
when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(true);
@@ -1419,7 +1405,7 @@
}
@Test
- public void testMultiDisplay_wakefulnessUpdates() throws Exception {
+ public void testMultiDisplay_wakefulnessUpdates() {
final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
new AtomicReference<>();
@@ -1448,7 +1434,7 @@
}
@Test
- public void testMultiDisplay_addDisplayGroup_preservesWakefulness() throws Exception {
+ public void testMultiDisplay_addDisplayGroup_preservesWakefulness() {
final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
new AtomicReference<>();
@@ -1472,7 +1458,7 @@
}
@Test
- public void testMultiDisplay_removeDisplayGroup_updatesWakefulness() throws Exception {
+ public void testMultiDisplay_removeDisplayGroup_updatesWakefulness() {
final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
new AtomicReference<>();
@@ -1502,7 +1488,7 @@
@Test
public void testGetFullPowerSavePolicy_returnsStateMachineResult() {
createService();
- mService.systemReady(null);
+ mService.systemReady();
BatterySaverPolicyConfig mockReturnConfig = new BatterySaverPolicyConfig.Builder().build();
when(mBatterySaverStateMachineMock.getFullBatterySaverPolicy())
.thenReturn(mockReturnConfig);
@@ -1517,7 +1503,7 @@
@Test
public void testSetFullPowerSavePolicy_callsStateMachine() {
createService();
- mService.systemReady(null);
+ mService.systemReady();
BatterySaverPolicyConfig mockSetPolicyConfig =
new BatterySaverPolicyConfig.Builder().build();
when(mBatterySaverStateMachineMock.setFullBatterySaverPolicy(any())).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/wm/OWNERS b/services/tests/servicestests/src/com/android/server/wm/OWNERS
new file mode 100644
index 0000000..361760d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index d831903..a192bf8 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -2701,6 +2701,8 @@
// should trigger a broadcast
mBinderService.setNotificationsEnabledForPackage(PKG, 0, true);
+ Thread.sleep(500);
+ waitForIdle();
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
@@ -2728,6 +2730,8 @@
// should trigger a broadcast
mBinderService.setNotificationsEnabledForPackage(PKG, 0, true);
+ Thread.sleep(500);
+ waitForIdle();
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
index 1362628..f6400b6 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
@@ -601,6 +601,8 @@
when(mAppOpsManager.checkOpNoThrow(anyInt(), eq(mUid), eq(PKG))).thenReturn(MODE_IGNORED);
mService.mAppOpsCallback.opChanged(0, mUid, PKG);
+ Thread.sleep(500);
+ waitForIdle();
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
@@ -616,6 +618,8 @@
when(mAppOpsManager.checkOpNoThrow(anyInt(), eq(mUid), eq(PKG))).thenReturn(MODE_ALLOWED);
mService.mAppOpsCallback.opChanged(0, mUid, PKG);
+ Thread.sleep(500);
+ waitForIdle();
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 76bd4eb..b48c9c3 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -542,6 +542,7 @@
mHelper.setInvalidMessageSent(PKG_P, UID_P);
mHelper.setValidMessageSent(PKG_P, UID_P);
mHelper.setInvalidMsgAppDemoted(PKG_P, UID_P, true);
+ mHelper.setValidBubbleSent(PKG_P, UID_P);
mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_NONE);
@@ -561,6 +562,7 @@
assertFalse(mHelper.hasSentInvalidMsg(PKG_N_MR1, UID_N_MR1));
assertTrue(mHelper.hasSentValidMsg(PKG_P, UID_P));
assertTrue(mHelper.didUserEverDemoteInvalidMsgApp(PKG_P, UID_P));
+ assertTrue(mHelper.hasSentValidBubble(PKG_P, UID_P));
assertEquals(channel1,
mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false));
compareChannels(channel2,
@@ -4940,6 +4942,18 @@
}
@Test
+ public void testValidBubbleSent() {
+ // create package preferences
+ mHelper.canShowBadge(PKG_P, UID_P);
+ // false by default
+ assertFalse(mHelper.hasSentValidBubble(PKG_P, UID_P));
+
+ // set something valid was sent
+ mHelper.setValidBubbleSent(PKG_P, UID_P);
+ assertTrue(mHelper.hasSentValidBubble(PKG_P, UID_P));
+ }
+
+ @Test
public void testPullPackageChannelPreferencesStats() {
String channelId = "parent";
String name = "messages";
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
index c77a474..f135d16 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
@@ -40,7 +40,10 @@
@RunWith(AndroidJUnit4.class)
public class VibratorHelperTest extends UiServiceTestCase {
+ // OFF/ON vibration pattern
private static final long[] CUSTOM_PATTERN = new long[] { 100, 200, 300, 400 };
+ // (amplitude, frequency, duration) triples list
+ private static final float[] PWLE_PATTERN = new float[] { 1, 120, 100 };
@Mock private Vibrator mVibrator;
@@ -58,12 +61,16 @@
public void createWaveformVibration_insistent_createsRepeatingVibration() {
assertRepeatingVibration(
VibratorHelper.createWaveformVibration(CUSTOM_PATTERN, /* insistent= */ true));
+ assertRepeatingVibration(
+ VibratorHelper.createPwleWaveformVibration(PWLE_PATTERN, /* insistent= */ true));
}
@Test
public void createWaveformVibration_nonInsistent_createsSingleShotVibration() {
assertSingleVibration(
VibratorHelper.createWaveformVibration(CUSTOM_PATTERN, /* insistent= */ false));
+ assertSingleVibration(
+ VibratorHelper.createPwleWaveformVibration(PWLE_PATTERN, /* insistent= */ false));
}
@Test
@@ -71,6 +78,11 @@
assertNull(VibratorHelper.createWaveformVibration(null, false));
assertNull(VibratorHelper.createWaveformVibration(new long[0], false));
assertNull(VibratorHelper.createWaveformVibration(new long[] { 0, 0 }, false));
+
+ assertNull(VibratorHelper.createPwleWaveformVibration(null, false));
+ assertNull(VibratorHelper.createPwleWaveformVibration(new float[0], false));
+ assertNull(VibratorHelper.createPwleWaveformVibration(new float[] { 0 }, false));
+ assertNull(VibratorHelper.createPwleWaveformVibration(new float[] { 0, 0, 0 }, false));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
index cb9eb52..22e411e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
@@ -48,12 +49,14 @@
import static java.util.stream.Collectors.toList;
+import android.app.ActivityOptions;
import android.content.res.Resources;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
import com.google.android.collect.Lists;
@@ -80,6 +83,8 @@
*/
@Presubmit
public class DisplayAreaPolicyBuilderTest {
+ private static final String KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID =
+ "android.test.launchTaskDisplayAreaFeatureId";
@Rule
public final SystemServicesTestRule mSystemServices = new SystemServicesTestRule();
@@ -103,6 +108,7 @@
mImeContainer = new DisplayArea.Tokens(mWms, ABOVE_TASKS, "ImeContainer");
mDisplayContent = mock(DisplayContent.class);
doReturn(true).when(mDisplayContent).isTrusted();
+ doReturn(DEFAULT_DISPLAY).when(mDisplayContent).getDisplayId();
mDisplayContent.isDefaultDisplay = true;
mDefaultTaskDisplayArea = new TaskDisplayArea(mDisplayContent, mWms, "Tasks",
FEATURE_DEFAULT_TASK_CONTAINER);
@@ -711,6 +717,208 @@
.build();
}
+ @Test
+ public void testGetTaskDisplayArea_DefaultFunction_NullOptions_ReturnsDefaultTda() {
+ final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy0 =
+ new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
+ .setTaskDisplayAreas(mTaskDisplayAreaList);
+ final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy1 =
+ new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot1)
+ .setImeContainer(mImeContainer)
+ .setTaskDisplayAreas(Lists.newArrayList(mTda1));
+ final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy2 =
+ new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot2)
+ .setTaskDisplayAreas(Lists.newArrayList(mTda2));
+ final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
+ .setRootHierarchy(hierarchy0)
+ .addDisplayAreaGroupHierarchy(hierarchy1)
+ .addDisplayAreaGroupHierarchy(hierarchy2)
+ .build(mWms);
+
+ final TaskDisplayArea tda = policy.getTaskDisplayArea(null /* options */);
+
+ assertThat(tda).isEqualTo(mDefaultTaskDisplayArea);
+ assertThat(tda).isEqualTo(policy.getDefaultTaskDisplayArea());
+ }
+
+ @Test
+ public void testGetTaskDisplayArea_DefaultFunction_NotContainsLunchedTda_ReturnsDefaultTda() {
+ final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy0 =
+ new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
+ .setTaskDisplayAreas(mTaskDisplayAreaList);
+ final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy1 =
+ new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot1)
+ .setImeContainer(mImeContainer)
+ .setTaskDisplayAreas(Lists.newArrayList(mTda1));
+ final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy2 =
+ new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot2)
+ .setTaskDisplayAreas(Lists.newArrayList(mTda2));
+ final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
+ .setRootHierarchy(hierarchy0)
+ .addDisplayAreaGroupHierarchy(hierarchy1)
+ .addDisplayAreaGroupHierarchy(hierarchy2)
+ .build(mWms);
+
+ final TaskDisplayArea tda = policy.getTaskDisplayArea(new Bundle());
+
+ assertThat(tda).isEqualTo(mDefaultTaskDisplayArea);
+ assertThat(tda).isEqualTo(policy.getDefaultTaskDisplayArea());
+ }
+
+ @Test
+ public void testGetTaskDisplayArea_DefaultFunction_InvalidTdaToken_ReturnsDefaultTda() {
+ final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy0 =
+ new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
+ .setTaskDisplayAreas(mTaskDisplayAreaList);
+ final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy1 =
+ new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot1)
+ .setImeContainer(mImeContainer)
+ .setTaskDisplayAreas(Lists.newArrayList(mTda1));
+ final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy2 =
+ new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot2)
+ .setTaskDisplayAreas(Lists.newArrayList(mTda2));
+ final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
+ .setRootHierarchy(hierarchy0)
+ .addDisplayAreaGroupHierarchy(hierarchy1)
+ .addDisplayAreaGroupHierarchy(hierarchy2)
+ .build(mWms);
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ final WindowContainerToken fakeToken = mRoot.mRemoteToken.toWindowContainerToken();
+ options.setLaunchTaskDisplayArea(fakeToken);
+
+ final TaskDisplayArea tda = policy.getTaskDisplayArea(options.toBundle());
+
+ assertThat(tda).isEqualTo(mDefaultTaskDisplayArea);
+ assertThat(tda).isEqualTo(policy.getDefaultTaskDisplayArea());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetTaskDisplayArea_DefaultFunction_TdaOnDifferentDisplay_ThrowException() {
+ final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy0 =
+ new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
+ .setTaskDisplayAreas(mTaskDisplayAreaList);
+ final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy1 =
+ new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot1)
+ .setImeContainer(mImeContainer)
+ .setTaskDisplayAreas(Lists.newArrayList(mTda1));
+ final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy2 =
+ new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot2)
+ .setTaskDisplayAreas(Lists.newArrayList(mTda2));
+ final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
+ .setRootHierarchy(hierarchy0)
+ .addDisplayAreaGroupHierarchy(hierarchy1)
+ .addDisplayAreaGroupHierarchy(hierarchy2)
+ .build(mWms);
+ final TaskDisplayArea tdaOnSecondaryDisplay = mock(TaskDisplayArea.class);
+ doReturn(DEFAULT_DISPLAY + 1).when(tdaOnSecondaryDisplay).getDisplayId();
+ doReturn(tdaOnSecondaryDisplay).when(tdaOnSecondaryDisplay).asTaskDisplayArea();
+ tdaOnSecondaryDisplay.mRemoteToken = new WindowContainer.RemoteToken(tdaOnSecondaryDisplay);
+
+ final WindowContainerToken tdaToken = tdaOnSecondaryDisplay.mRemoteToken
+ .toWindowContainerToken();
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchTaskDisplayArea(tdaToken);
+ final TaskDisplayArea tda = policy.getTaskDisplayArea(options.toBundle());
+
+ assertThat(tda).isEqualTo(mDefaultTaskDisplayArea);
+ assertThat(tda).isEqualTo(policy.getDefaultTaskDisplayArea());
+ }
+
+ @Test
+ public void testGetTaskDisplayArea_DefaultFunction_ContainsTdaToken_ReturnsTda() {
+ final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy0 =
+ new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
+ .setTaskDisplayAreas(mTaskDisplayAreaList);
+ final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy1 =
+ new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot1)
+ .setImeContainer(mImeContainer)
+ .setTaskDisplayAreas(Lists.newArrayList(mTda1));
+ final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy2 =
+ new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot2)
+ .setTaskDisplayAreas(Lists.newArrayList(mTda2));
+ final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
+ .setRootHierarchy(hierarchy0)
+ .addDisplayAreaGroupHierarchy(hierarchy1)
+ .addDisplayAreaGroupHierarchy(hierarchy2)
+ .build(mWms);
+ final ActivityOptions options = ActivityOptions.makeBasic();
+
+ final WindowContainerToken defaultTdaToken = mDefaultTaskDisplayArea.mRemoteToken
+ .toWindowContainerToken();
+ options.setLaunchTaskDisplayArea(defaultTdaToken);
+ TaskDisplayArea tda = policy.getTaskDisplayArea(options.toBundle());
+
+ assertThat(tda).isEqualTo(mDefaultTaskDisplayArea);
+ assertThat(tda).isEqualTo(policy.getDefaultTaskDisplayArea());
+
+ final WindowContainerToken tda1Token = mTda1.mRemoteToken.toWindowContainerToken();
+ options.setLaunchTaskDisplayArea(tda1Token);
+ tda = policy.getTaskDisplayArea(options.toBundle());
+
+ assertThat(tda).isEqualTo(mTda1);
+
+ final WindowContainerToken tda2Token = mTda2.mRemoteToken.toWindowContainerToken();
+ options.setLaunchTaskDisplayArea(tda2Token);
+ tda = policy.getTaskDisplayArea(options.toBundle());
+
+ assertThat(tda).isEqualTo(mTda2);
+ }
+
+ @Test
+ public void testBuilder_getTaskDisplayArea_setSelectTaskDisplayAreaFunc() {
+ final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy0 =
+ new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
+ .setTaskDisplayAreas(mTaskDisplayAreaList);
+ final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy1 =
+ new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot1)
+ .setImeContainer(mImeContainer)
+ .setTaskDisplayAreas(Lists.newArrayList(mTda1));
+ final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy2 =
+ new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot2)
+ .setTaskDisplayAreas(Lists.newArrayList(mTda2));
+ final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
+ .setRootHierarchy(hierarchy0)
+ .setSelectTaskDisplayAreaFunc((options) -> {
+ if (options == null) {
+ return mDefaultTaskDisplayArea;
+ }
+ final int tdaFeatureId =
+ options.getInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID);
+ if (tdaFeatureId == mTda1.mFeatureId) {
+ return mTda1;
+ }
+ if (tdaFeatureId == mTda2.mFeatureId) {
+ return mTda2;
+ }
+ return mDefaultTaskDisplayArea;
+ })
+ .addDisplayAreaGroupHierarchy(hierarchy1)
+ .addDisplayAreaGroupHierarchy(hierarchy2)
+ .build(mWms);
+
+ TaskDisplayArea tda = policy.getTaskDisplayArea(null /* options */);
+
+ assertThat(tda).isEqualTo(mDefaultTaskDisplayArea);
+ assertThat(tda).isEqualTo(policy.getDefaultTaskDisplayArea());
+
+ final Bundle options = new Bundle();
+ options.putInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID, -1);
+ tda = policy.getTaskDisplayArea(options);
+ assertThat(tda).isEqualTo(mDefaultTaskDisplayArea);
+
+ options.putInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID, mDefaultTaskDisplayArea.mFeatureId);
+ tda = policy.getTaskDisplayArea(options);
+ assertThat(tda).isEqualTo(mDefaultTaskDisplayArea);
+
+ options.putInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID, mTda1.mFeatureId);
+ tda = policy.getTaskDisplayArea(options);
+ assertThat(tda).isEqualTo(mTda1);
+
+ options.putInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID, mTda2.mFeatureId);
+ tda = policy.getTaskDisplayArea(options);
+ assertThat(tda).isEqualTo(mTda2);
+ }
+
private static Resources resourcesWithProvider(String provider) {
Resources mock = mock(Resources.class);
when(mock.getString(
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 6970005..2b131e1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -322,4 +322,26 @@
assertFalse(navBarSource.getFrame().isEmpty());
assertTrue(imeSource.getFrame().contains(navBarSource.getFrame()));
}
+
+ @UseTestDisplay(addWindows = { W_NAVIGATION_BAR })
+ @Test
+ public void testInsetsGivenContentFrame() {
+ final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = 1000;
+ displayInfo.logicalHeight = 2000;
+ displayInfo.rotation = ROTATION_0;
+
+ WindowManager.LayoutParams attrs = mNavBarWindow.mAttrs;
+ displayPolicy.addWindowLw(mNavBarWindow, attrs);
+ mNavBarWindow.setRequestedSize(attrs.width, attrs.height);
+ mNavBarWindow.getControllableInsetProvider().setServerVisible(true);
+
+ mNavBarWindow.mGivenContentInsets.set(0, 10, 0, 0);
+
+ displayPolicy.layoutWindowLw(mNavBarWindow, null, mDisplayContent.mDisplayFrames);
+ final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
+ final InsetsSource navBarSource = state.peekSource(ITYPE_NAVIGATION_BAR);
+ assertEquals(attrs.height - 10, navBarSource.getFrame().height());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerHelperTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerHelperTests.java
new file mode 100644
index 0000000..6e11d8c
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerHelperTests.java
@@ -0,0 +1,147 @@
+/*
+ * 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.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.view.Display;
+import android.window.DisplayWindowPolicyController;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * Tests for the {@link DisplayWindowPolicyControllerHelper} class.
+ *
+ * Build/Install/Run:
+ * atest WmTests:DisplayWindowPolicyControllerHelperTests
+ */
+@RunWith(WindowTestRunner.class)
+public class DisplayWindowPolicyControllerHelperTests extends WindowTestsBase {
+ private static final int TEST_USER_0_ID = 0;
+ private static final int TEST_USER_1_ID = 10;
+
+ private TestDisplayWindowPolicyController mDwpc = new TestDisplayWindowPolicyController();
+ private DisplayContent mSecondaryDisplay;
+
+ @Before
+ public void setUp() {
+ doReturn(mDwpc).when(mWm.mDisplayManagerInternal)
+ .getDisplayWindowPolicyController(anyInt());
+ mSecondaryDisplay = createNewDisplay();
+ assertNotEquals(Display.DEFAULT_DISPLAY, mSecondaryDisplay.getDisplayId());
+ assertTrue(mSecondaryDisplay.mDwpcHelper.hasController());
+ }
+
+ @Test
+ public void testOnRunningActivityChanged() {
+ final ActivityRecord activity1 = launchActivityOnDisplay(mSecondaryDisplay, TEST_USER_0_ID);
+ verifyTopActivityAndRunningUid(activity1,
+ true /* expectedUid0 */, false /* expectedUid1 */);
+ final ActivityRecord activity2 = launchActivityOnDisplay(mSecondaryDisplay, TEST_USER_1_ID);
+ verifyTopActivityAndRunningUid(activity2,
+ true /* expectedUid0 */, true /* expectedUid1 */);
+ final ActivityRecord activity3 = launchActivityOnDisplay(mSecondaryDisplay, TEST_USER_0_ID);
+ verifyTopActivityAndRunningUid(activity3,
+ true /* expectedUid0 */, true /* expectedUid1 */);
+
+ activity3.finishing = true;
+ verifyTopActivityAndRunningUid(activity2,
+ true /* expectedUid0 */, true /* expectedUid1 */);
+
+ activity2.finishing = true;
+ verifyTopActivityAndRunningUid(activity1,
+ true /* expectedUid0 */, false /* expectedUid1 */);
+
+ activity1.finishing = true;
+ verifyTopActivityAndRunningUid(null /* expectedTopActivity */,
+ false /* expectedUid0 */, false /* expectedUid1 */);
+ }
+
+ private void verifyTopActivityAndRunningUid(ActivityRecord expectedTopActivity,
+ boolean expectedUid0, boolean expectedUid1) {
+ mSecondaryDisplay.onRunningActivityChanged();
+ int uidAmount = (expectedUid0 && expectedUid1) ? 2 : (expectedUid0 || expectedUid1) ? 1 : 0;
+ assertEquals(expectedTopActivity == null ? null :
+ expectedTopActivity.info.getComponentName(), mDwpc.mTopActivity);
+ assertEquals(expectedTopActivity == null ? UserHandle.USER_NULL :
+ expectedTopActivity.info.applicationInfo.uid, mDwpc.mTopActivityUid);
+ assertEquals(uidAmount, mDwpc.mRunningUids.size());
+ assertTrue(mDwpc.mRunningUids.contains(TEST_USER_0_ID) == expectedUid0);
+ assertTrue(mDwpc.mRunningUids.contains(TEST_USER_1_ID) == expectedUid1);
+
+ }
+
+ private ActivityRecord launchActivityOnDisplay(DisplayContent display, int uid) {
+ final Task task = new TaskBuilder(mSupervisor)
+ .setDisplay(display)
+ .setUserId(uid)
+ .build();
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(task)
+ .setUid(uid)
+ .setOnTop(true)
+ .build();
+ return activity;
+ }
+
+ private class TestDisplayWindowPolicyController extends DisplayWindowPolicyController {
+
+ ComponentName mTopActivity = null;
+ int mTopActivityUid = UserHandle.USER_NULL;
+ ArraySet<Integer> mRunningUids = new ArraySet<>();
+
+ @Override
+ public boolean canContainActivities(@NonNull List<ActivityInfo> activities) {
+ return false;
+ }
+
+ @Override
+ public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
+ int systemWindowFlags) {
+ return false;
+ }
+
+ @Override
+ public void onTopActivityChanged(ComponentName topActivity, int uid) {
+ super.onTopActivityChanged(topActivity, uid);
+ mTopActivity = topActivity;
+ mTopActivityUid = uid;
+ }
+
+ @Override
+ public void onRunningAppsChanged(ArraySet<Integer> runningUids) {
+ super.onRunningAppsChanged(runningUids);
+ mRunningUids.clear();
+ mRunningUids.addAll(runningUids);
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index 9e4cd16..365e749 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -51,6 +51,7 @@
import com.android.server.LocalServices;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
import org.junit.Before;
import org.junit.Test;
@@ -447,6 +448,21 @@
assertEquals(456, config.densityDpi);
}
+ @Test
+ public void testDisplayRotationSettingsAppliedOnCreation() {
+ // Create new displays with different rotation settings
+ final SettingsEntry settingsEntry1 = new SettingsEntry();
+ settingsEntry1.mIgnoreOrientationRequest = false;
+ final DisplayContent dcDontIgnoreOrientation = createMockSimulatedDisplay(settingsEntry1);
+ final SettingsEntry settingsEntry2 = new SettingsEntry();
+ settingsEntry2.mIgnoreOrientationRequest = true;
+ final DisplayContent dcIgnoreOrientation = createMockSimulatedDisplay(settingsEntry2);
+
+ // Verify that newly created displays are created with correct rotation settings
+ assertFalse(dcDontIgnoreOrientation.getIgnoreOrientationRequest());
+ assertTrue(dcIgnoreOrientation.getIgnoreOrientationRequest());
+ }
+
public final class TestSettingsProvider implements DisplayWindowSettings.SettingsProvider {
Map<DisplayInfo, SettingsEntry> mOverrideSettingsCache = new HashMap<>();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 9ad8c5b..0debdfa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -21,14 +21,17 @@
import static com.android.server.wm.testing.Assert.assertThrows;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import android.content.Intent;
@@ -471,6 +474,7 @@
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setOrganizer(mOrganizer)
+ .setFragmentToken(mFragmentToken)
.build();
// Mock the task to invisible
@@ -485,4 +489,38 @@
// Verifies that event was not sent
verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
}
+
+ /**
+ * Tests that a task fragment info changed event is still sent if the task is invisible only
+ * when the info changed event is because of the last activity in a task finishing.
+ */
+ @Test
+ public void testLastPendingTaskFragmentInfoChangedEventOfInvisibleTaskSent() {
+ // Create a TaskFragment with an activity, all within a parent task
+ final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setOrganizer(mOrganizer)
+ .setFragmentToken(mFragmentToken)
+ .setCreateParentTask()
+ .createActivityCount(1)
+ .build();
+ final Task parentTask = taskFragment.getTask();
+ final ActivityRecord activity = taskFragment.getTopNonFinishingActivity();
+ assertTrue(parentTask.shouldBeVisible(null));
+
+ // Dispatch pending info changed event from creating the activity
+ mController.registerOrganizer(mIOrganizer);
+ taskFragment.mTaskFragmentAppearedSent = true;
+ mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
+ mController.dispatchPendingEvents();
+
+ // Finish the activity and verify that the task is invisible
+ activity.finishing = true;
+ assertFalse(parentTask.shouldBeVisible(null));
+
+ // Verify the info changed callback still occurred despite the task being invisible
+ reset(mOrganizer);
+ mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
+ mController.dispatchPendingEvents();
+ verify(mOrganizer).onTaskFragmentInfoChanged(any());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 3065e7d..8b0716c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -30,6 +30,7 @@
import static org.mockito.ArgumentMatchers.any;
+import android.annotation.Nullable;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -38,6 +39,8 @@
import android.view.DisplayCutout;
import android.view.DisplayInfo;
+import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
+
class TestDisplayContent extends DisplayContent {
public static final int DEFAULT_LOGICAL_DISPLAY_DENSITY = 300;
@@ -81,6 +84,7 @@
protected final ActivityTaskManagerService mService;
private boolean mSystemDecorations = false;
private int mStatusBarHeight = 0;
+ private SettingsEntry mOverrideSettings;
Builder(ActivityTaskManagerService service, int width, int height) {
mService = service;
@@ -104,6 +108,10 @@
private String generateUniqueId() {
return "TEST_DISPLAY_CONTENT_" + System.currentTimeMillis();
}
+ Builder setOverrideSettings(@Nullable SettingsEntry overrideSettings) {
+ mOverrideSettings = overrideSettings;
+ return this;
+ }
Builder setSystemDecorations(boolean yes) {
mSystemDecorations = yes;
return this;
@@ -151,6 +159,11 @@
TestDisplayContent build() {
SystemServicesTestRule.checkHoldsLock(mService.mGlobalLock);
+ if (mOverrideSettings != null) {
+ mService.mWindowManager.mDisplayWindowSettingsProvider
+ .updateOverrideSettings(mInfo, mOverrideSettings);
+ }
+
final int displayId = SystemServicesTestRule.sNextDisplayId++;
final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
mInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 34038c5..59a2068 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -106,6 +106,7 @@
import com.android.internal.policy.AttributeCache;
import com.android.internal.util.ArrayUtils;
+import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
import org.junit.After;
import org.junit.Before;
@@ -720,18 +721,21 @@
/** Creates a {@link DisplayContent} and adds it to the system. */
private DisplayContent createNewDisplayWithImeSupport(@DisplayImePolicy int imePolicy) {
- return createNewDisplay(mDisplayInfo, imePolicy);
+ return createNewDisplay(mDisplayInfo, imePolicy, /* overrideSettings */ null);
}
/** Creates a {@link DisplayContent} that supports IME and adds it to the system. */
DisplayContent createNewDisplay(DisplayInfo info) {
- return createNewDisplay(info, DISPLAY_IME_POLICY_LOCAL);
+ return createNewDisplay(info, DISPLAY_IME_POLICY_LOCAL, /* overrideSettings */ null);
}
/** Creates a {@link DisplayContent} and adds it to the system. */
- private DisplayContent createNewDisplay(DisplayInfo info, @DisplayImePolicy int imePolicy) {
+ private DisplayContent createNewDisplay(DisplayInfo info, @DisplayImePolicy int imePolicy,
+ @Nullable SettingsEntry overrideSettings) {
final DisplayContent display =
- new TestDisplayContent.Builder(mAtm, info).build();
+ new TestDisplayContent.Builder(mAtm, info)
+ .setOverrideSettings(overrideSettings)
+ .build();
final DisplayContent dc = display.mDisplayContent;
// this display can show IME.
dc.mWmService.mDisplayWindowSettings.setDisplayImePolicy(dc, imePolicy);
@@ -749,7 +753,7 @@
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.copyFrom(mDisplayInfo);
displayInfo.state = displayState;
- return createNewDisplay(displayInfo, DISPLAY_IME_POLICY_LOCAL);
+ return createNewDisplay(displayInfo, DISPLAY_IME_POLICY_LOCAL, /* overrideSettings */ null);
}
/** Creates a {@link TestWindowState} */
@@ -761,11 +765,15 @@
/** Creates a {@link DisplayContent} as parts of simulate display info for test. */
DisplayContent createMockSimulatedDisplay() {
+ return createMockSimulatedDisplay(/* overrideSettings */ null);
+ }
+
+ DisplayContent createMockSimulatedDisplay(@Nullable SettingsEntry overrideSettings) {
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.copyFrom(mDisplayInfo);
displayInfo.type = Display.TYPE_VIRTUAL;
displayInfo.ownerUid = SYSTEM_UID;
- return createNewDisplay(displayInfo, DISPLAY_IME_POLICY_FALLBACK_DISPLAY);
+ return createNewDisplay(displayInfo, DISPLAY_IME_POLICY_FALLBACK_DISPLAY, overrideSettings);
}
IDisplayWindowInsetsController createDisplayWindowInsetsController() {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 808df50..6d8edc5 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2935,19 +2935,6 @@
"signal_strength_nr_nsa_use_lte_as_primary_bool";
/**
- * String array of TCP buffer sizes per network type.
- * The entries should be of the following form, with values in bytes:
- * "network_name:read_min,read_default,read_max,write_min,write_default,write_max".
- * For NR (5G), the following network names should be used:
- * - NR_NSA: NR NSA, sub-6 frequencies
- * - NR_NSA_MMWAVE: NR NSA, mmwave frequencies
- * - NR_SA: NR SA, sub-6 frequencies
- * - NR_SA_MMWAVE: NR SA, mmwave frequencies
- * @hide
- */
- public static final String KEY_TCP_BUFFERS_STRING_ARRAY = "tcp_buffers_string_array";
-
- /**
* String array of default bandwidth values per network type.
* The entries should be of form: "network_name:downlink,uplink", with values in Kbps.
* For NR (5G), the following network names should be used:
@@ -7426,7 +7413,7 @@
/**
* A priority list of ePDG addresses to be used. Possible values are {@link
* #EPDG_ADDRESS_STATIC}, {@link #EPDG_ADDRESS_PLMN}, {@link #EPDG_ADDRESS_PCO}, {@link
- * #EPDG_ADDRESS_CELLULAR_LOC}
+ * #EPDG_ADDRESS_CELLULAR_LOC}, {@link #EPDG_ADDRESS_VISITED_COUNTRY}
*/
public static final String KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY =
KEY_PREFIX + "epdg_address_priority_int_array";
@@ -7605,7 +7592,8 @@
EPDG_ADDRESS_STATIC,
EPDG_ADDRESS_PLMN,
EPDG_ADDRESS_PCO,
- EPDG_ADDRESS_CELLULAR_LOC
+ EPDG_ADDRESS_CELLULAR_LOC,
+ EPDG_ADDRESS_VISITED_COUNTRY
})
public @interface EpdgAddressType {}
@@ -7619,6 +7607,8 @@
public static final int EPDG_ADDRESS_PCO = 2;
/** Use cellular location to chose epdg server */
public static final int EPDG_ADDRESS_CELLULAR_LOC = 3;
+ /* Use Visited Country FQDN rule*/
+ public static final int EPDG_ADDRESS_VISITED_COUNTRY = 4;
/** @hide */
@IntDef({ID_TYPE_FQDN, ID_TYPE_RFC822_ADDR, ID_TYPE_KEY_ID})
@@ -8570,28 +8560,6 @@
"iDEN:14,14", "LTE:30000,15000", "HSPA+:13000,3400", "GSM:24,24",
"TD_SCDMA:115,115", "LTE_CA:30000,15000", "NR_NSA:47000,18000",
"NR_NSA_MMWAVE:145000,60000", "NR_SA:145000,60000", "NR_SA_MMWAVE:145000,60000"});
- sDefaults.putStringArray(KEY_TCP_BUFFERS_STRING_ARRAY, new String[]{
- "GPRS:4092,8760,48000,4096,8760,48000", "EDGE:4093,26280,70800,4096,16384,70800",
- "UMTS:58254,349525,1048576,58254,349525,1048576",
- "CDMA:4094,87380,262144,4096,16384,262144",
- "1xRTT:16384,32768,131072,4096,16384,102400",
- "EvDo_0:4094,87380,262144,4096,16384,262144",
- "EvDo_A:4094,87380,262144,4096,16384,262144",
- "HSDPA:61167,367002,1101005,8738,52429,262114",
- "HSUPA:40778,244668,734003,16777,100663,301990",
- "HSPA:40778,244668,734003,16777,100663,301990",
- "EvDo_B:4094,87380,262144,4096,16384,262144",
- "eHRPD:131072,262144,1048576,4096,16384,524288",
- "iDEN:4094,87380,262144,4096,16384,262144",
- "LTE:524288,1048576,2097152,262144,524288,1048576",
- "HSPA+:122334,734003,2202010,32040,192239,576717",
- "GSM:4092,8760,48000,4096,8760,48000",
- "TD_SCDMA:58254,349525,1048576,58254,349525,1048576",
- "LTE_CA:4096,6291456,12582912,4096,1048576,2097152",
- "NR_NSA:2097152,6291456,16777216,512000,2097152,8388608",
- "NR_NSA_MMWAVE:2097152,6291456,16777216,512000,2097152,8388608",
- "NR_SA:2097152,6291456,16777216,512000,2097152,8388608",
- "NR_SA_MMWAVE:2097152,6291456,16777216,512000,2097152,8388608"});
sDefaults.putBoolean(KEY_BANDWIDTH_NR_NSA_USE_LTE_VALUE_FOR_UPLINK_BOOL, false);
sDefaults.putString(KEY_WCDMA_DEFAULT_SIGNAL_STRENGTH_MEASUREMENT_STRING, "rssi");
sDefaults.putBoolean(KEY_CONFIG_SHOW_ORIG_DIAL_STRING_FOR_CDMA_BOOL, false);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f5505e6..0c56de8 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -142,6 +142,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
@@ -6807,6 +6808,24 @@
}
/**
+ * Get the first active portIndex from the corresponding physical slot index.
+ * @param physicalSlotIndex physical slot index
+ * @return first active port index or INVALID_PORT_INDEX if no port is active
+ */
+ private int getFirstActivePortIndex(int physicalSlotIndex) {
+ UiccSlotInfo[] slotInfos = getUiccSlotsInfo();
+ if (slotInfos != null && physicalSlotIndex >= 0 && physicalSlotIndex < slotInfos.length
+ && slotInfos[physicalSlotIndex] != null) {
+ Optional<UiccPortInfo> result = slotInfos[physicalSlotIndex].getPorts().stream()
+ .filter(portInfo -> portInfo.isActive()).findFirst();
+ if (result.isPresent()) {
+ return result.get().getPortIndex();
+ }
+ }
+ return INVALID_PORT_INDEX;
+ }
+
+ /**
* Opens a logical channel to the ICC card.
*
* Input parameters equivalent to TS 27.007 AT+CCHO command.
@@ -6852,7 +6871,8 @@
* @param p2 P2 parameter (described in ISO 7816-4).
* @return an IccOpenLogicalChannelResponse object.
* @hide
- * @deprecated instead use {@link #iccOpenLogicalChannelByPort(int, int, String, int)}
+ * @deprecated This API is not compatible on eUICC supporting Multiple Enabled Profile(MEP),
+ * instead use {@link #iccOpenLogicalChannelByPort(int, int, String, int)}
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -6866,6 +6886,7 @@
if (telephony != null) {
IccLogicalChannelRequest request = new IccLogicalChannelRequest();
request.slotIndex = slotIndex;
+ request.portIndex = getFirstActivePortIndex(slotIndex);
request.aid = aid;
request.p2 = p2;
request.callingPackage = getOpPackageName();
@@ -7021,7 +7042,8 @@
* iccOpenLogicalChannel.
* @return true if the channel was closed successfully.
* @hide
- * @deprecated instead use {@link #iccCloseLogicalChannelByPort(int, int, int)}
+ * @deprecated This API is not compatible on eUICC supporting Multiple Enabled Profile(MEP),
+ * instead use {@link #iccCloseLogicalChannelByPort(int, int, int)}
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -7033,6 +7055,7 @@
if (telephony != null) {
IccLogicalChannelRequest request = new IccLogicalChannelRequest();
request.slotIndex = slotIndex;
+ request.portIndex = getFirstActivePortIndex(slotIndex);
request.channel = channel;
return telephony.iccCloseLogicalChannel(request);
}
@@ -7153,7 +7176,8 @@
* @return The APDU response from the ICC card with the status appended at the end, or null if
* there is an issue connecting to the Telephony service.
* @hide
- * @deprecated instead use
+ * @deprecated This API is not compatible on eUICC supporting Multiple Enabled Profile(MEP),
+ * instead use
* {@link #iccTransmitApduLogicalChannelByPort(int, int, int, int, int, int, int, int, String)}
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -7166,8 +7190,9 @@
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.iccTransmitApduLogicalChannelByPort(slotIndex, DEFAULT_PORT_INDEX,
- channel, cla, instruction, p1, p2, p3, data);
+ return telephony.iccTransmitApduLogicalChannelByPort(slotIndex,
+ getFirstActivePortIndex(slotIndex), channel, cla, instruction,
+ p1, p2, p3, data);
}
} catch (RemoteException ex) {
} catch (NullPointerException ex) {
@@ -7305,7 +7330,8 @@
* @return The APDU response from the ICC card with the status appended at
* the end.
* @hide
- * @deprecated instead use
+ * @deprecated This API is not compatible on eUICC supporting Multiple Enabled Profile(MEP),
+ * instead use
* {@link #iccTransmitApduBasicChannelByPort(int, int, int, int, int, int, int, String)}
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -7318,8 +7344,9 @@
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.iccTransmitApduBasicChannelByPort(slotIndex, DEFAULT_PORT_INDEX,
- getOpPackageName(), cla, instruction, p1, p2, p3, data);
+ return telephony.iccTransmitApduBasicChannelByPort(slotIndex,
+ getFirstActivePortIndex(slotIndex), getOpPackageName(),
+ cla, instruction, p1, p2, p3, data);
}
} catch (RemoteException ex) {
} catch (NullPointerException ex) {
@@ -12588,12 +12615,15 @@
if (carriers == null || !SubscriptionManager.isValidPhoneId(slotIndex)) {
return -1;
}
- // Execute the method setCarrierRestrictionRules with an empty excluded list and
- // indicating priority for the allowed list.
+ // Execute the method setCarrierRestrictionRules with an empty excluded list.
+ // If the allowed list is empty, it means that all carriers are allowed (default allowed),
+ // otherwise it means that only specified carriers are allowed (default not allowed).
CarrierRestrictionRules carrierRestrictionRules = CarrierRestrictionRules.newBuilder()
.setAllowedCarriers(carriers)
.setDefaultCarrierRestriction(
- CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED)
+ carriers.isEmpty()
+ ? CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_ALLOWED
+ : CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED)
.build();
int result = setCarrierRestrictionRules(carrierRestrictionRules);
diff --git a/telephony/java/android/telephony/UiccCardInfo.java b/telephony/java/android/telephony/UiccCardInfo.java
index 464389b..30ca162 100644
--- a/telephony/java/android/telephony/UiccCardInfo.java
+++ b/telephony/java/android/telephony/UiccCardInfo.java
@@ -257,8 +257,6 @@
+ mCardId
+ ", mEid="
+ mEid
- + ", mIccId="
- + SubscriptionInfo.givePrintableIccid(getIccId())
+ ", mPhysicalSlotIndex="
+ mPhysicalSlotIndex
+ ", mIsRemovable="
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
index 2b1c8c8..17f34db 100644
--- a/telephony/java/android/telephony/UiccSlotInfo.java
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -271,16 +271,13 @@
@NonNull
@Override
public String toString() {
- return "UiccSlotInfo (mIsActive="
- + mIsActive
+ return "UiccSlotInfo ("
+ ", mIsEuicc="
+ mIsEuicc
+ ", mCardId="
+ mCardId
+ ", cardState="
+ mCardStateInfo
- + ", phoneId="
- + mLogicalSlotIdx
+ ", mIsExtendedApduSupported="
+ mIsExtendedApduSupported
+ ", mIsRemovable="
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index ba95841..39ab7eb 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -592,6 +592,7 @@
int RIL_UNSOL_UNTHROTTLE_APN = 1052;
int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED = 1053;
int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED = 1054;
+ int RIL_UNSOL_SLICING_CONFIG_CHANGED = 1055;
/* The following unsols are not defined in RIL.h */
int RIL_UNSOL_HAL_NON_RIL_BASE = 1100;
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index 56879c9..c87d8e1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -31,7 +31,6 @@
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
@@ -39,7 +38,6 @@
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -157,11 +155,7 @@
@FlakyTest(bugId = 206753786)
@Test
- fun statusBarLayerRotatesScales() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
- testSpec.statusBarLayerRotatesScales()
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index c28466c..f2696d8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -34,12 +34,10 @@
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -117,11 +115,7 @@
@Presubmit
@Test
- fun entireScreenCovered() {
- // This test doesn't work in shell transitions because of b/206086894
- assumeFalse(isShellTransitionsEnabled)
- testSpec.entireScreenCovered()
- }
+ fun entireScreenCovered() = testSpec.entireScreenCovered()
@Presubmit
@Test
@@ -159,11 +153,7 @@
@FlakyTest(bugId = 206753786)
@Test
- fun statusBarLayerRotatesScales() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
- testSpec.statusBarLayerRotatesScales()
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index c7f1b99..24b1598 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -32,7 +32,6 @@
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
@@ -134,11 +133,7 @@
@FlakyTest(bugId = 206753786)
@Test
- fun statusBarLayerRotatesScales() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
- testSpec.statusBarLayerRotatesScales()
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index 46ed0ad..e5d82a1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -34,11 +34,9 @@
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -126,11 +124,7 @@
@Presubmit
@Test
- fun entireScreenCovered() {
- // This test doesn't work in shell transitions because of b/206086894
- assumeFalse(isShellTransitionsEnabled)
- testSpec.entireScreenCovered()
- }
+ fun entireScreenCovered() = testSpec.entireScreenCovered()
@Presubmit
@Test
@@ -152,11 +146,7 @@
@FlakyTest(bugId = 206753786)
@Test
- fun statusBarLayerRotatesScales() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
- testSpec.statusBarLayerRotatesScales()
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index ebe4be2..87f8ef2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -34,11 +34,9 @@
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -130,11 +128,7 @@
@FlakyTest(bugId = 206753786)
@Test
- fun statusBarLayerRotatesScales() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
- testSpec.statusBarLayerRotatesScales()
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index f6e5adc..0ad0a03 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -101,8 +101,6 @@
@Presubmit
@Test
fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
- // This test doesn't work in shell transitions because of b/204570898
- assumeFalse(isShellTransitionsEnabled)
val component = FlickerComponentName("", "RecentTaskScreenshotSurface")
testSpec.assertWm {
this.visibleWindowsShownMoreThanOneConsecutiveEntry(
@@ -116,8 +114,6 @@
@Presubmit
@Test
fun launcherWindowBecomesInvisible() {
- // This test doesn't work in shell transitions because of b/204574221
- assumeFalse(isShellTransitionsEnabled)
testSpec.assertWm {
this.isAppWindowVisible(LAUNCHER_COMPONENT)
.then()
@@ -127,11 +123,7 @@
@Presubmit
@Test
- fun imeWindowIsAlwaysVisible() {
- // This test doesn't work in shell transitions because of b/204570898
- assumeFalse(isShellTransitionsEnabled)
- testSpec.imeWindowIsAlwaysVisible(!isShellTransitionsEnabled)
- }
+ fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible(!isShellTransitionsEnabled)
@Presubmit
@Test
@@ -202,8 +194,6 @@
@Presubmit
@Test
fun appLayerReplacesLauncher() {
- // This test doesn't work in shell transitions because of b/204574221
- assumeFalse(isShellTransitionsEnabled)
testSpec.assertLayers {
this.isVisible(LAUNCHER_COMPONENT)
.then()
@@ -219,11 +209,7 @@
@FlakyTest(bugId = 206753786)
@Test
- fun statusBarLayerRotatesScales() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
- testSpec.statusBarLayerRotatesScales()
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index dcb5c86..8f2803e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -261,7 +261,7 @@
testSpec.assertWm {
this.isAppWindowOnTop(LAUNCHER_COMPONENT)
.then()
- .isAppWindowVisible(FlickerComponentName.SNAPSHOT)
+ .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
.then()
.isAppWindowVisible(testApp.component)
}
@@ -342,4 +342,4 @@
)
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
index 4a724b7..2fbcf9d 100644
--- a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
@@ -18,25 +18,29 @@
import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
-public class VcnCellUnderlyingNetworkTemplateTest {
+public class VcnCellUnderlyingNetworkTemplateTest extends VcnUnderlyingNetworkTemplateTestBase {
private static final Set<String> ALLOWED_PLMN_IDS = new HashSet<>();
private static final Set<Integer> ALLOWED_CARRIER_IDS = new HashSet<>();
// Package private for use in VcnGatewayConnectionConfigTest
static VcnCellUnderlyingNetworkTemplate getTestNetworkTemplate() {
return new VcnCellUnderlyingNetworkTemplate.Builder()
- .setNetworkQuality(NETWORK_QUALITY_OK)
.setMetered(MATCH_FORBIDDEN)
+ .setMinUpstreamBandwidthKbps(
+ TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS,
+ TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS)
+ .setMinDownstreamBandwidthKbps(
+ TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
+ TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS)
.setOperatorPlmnIds(ALLOWED_PLMN_IDS)
.setSimSpecificCarrierIds(ALLOWED_CARRIER_IDS)
.setRoaming(MATCH_FORBIDDEN)
@@ -47,8 +51,19 @@
@Test
public void testBuilderAndGetters() {
final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate();
- assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality());
assertEquals(MATCH_FORBIDDEN, networkPriority.getMetered());
+ assertEquals(
+ TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS,
+ networkPriority.getMinEntryUpstreamBandwidthKbps());
+ assertEquals(
+ TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
+ networkPriority.getMinExitUpstreamBandwidthKbps());
+ assertEquals(
+ TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
+ networkPriority.getMinEntryDownstreamBandwidthKbps());
+ assertEquals(
+ TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
+ networkPriority.getMinExitDownstreamBandwidthKbps());
assertEquals(ALLOWED_PLMN_IDS, networkPriority.getOperatorPlmnIds());
assertEquals(ALLOWED_CARRIER_IDS, networkPriority.getSimSpecificCarrierIds());
assertEquals(MATCH_FORBIDDEN, networkPriority.getRoaming());
@@ -59,8 +74,14 @@
public void testBuilderAndGettersForDefaultValues() {
final VcnCellUnderlyingNetworkTemplate networkPriority =
new VcnCellUnderlyingNetworkTemplate.Builder().build();
- assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality());
assertEquals(MATCH_ANY, networkPriority.getMetered());
+
+ // Explicitly expect 0, as documented in Javadoc on setter methods.
+ assertEquals(0, networkPriority.getMinEntryUpstreamBandwidthKbps());
+ assertEquals(0, networkPriority.getMinExitUpstreamBandwidthKbps());
+ assertEquals(0, networkPriority.getMinEntryDownstreamBandwidthKbps());
+ assertEquals(0, networkPriority.getMinExitDownstreamBandwidthKbps());
+
assertEquals(new HashSet<String>(), networkPriority.getOperatorPlmnIds());
assertEquals(new HashSet<Integer>(), networkPriority.getSimSpecificCarrierIds());
assertEquals(MATCH_ANY, networkPriority.getRoaming());
@@ -68,6 +89,29 @@
}
@Test
+ public void testBuilderRequiresStricterEntryCriteria() {
+ try {
+ new VcnCellUnderlyingNetworkTemplate.Builder()
+ .setMinUpstreamBandwidthKbps(
+ TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
+ TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS);
+
+ fail("Expected IAE for exit threshold > entry threshold");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ new VcnCellUnderlyingNetworkTemplate.Builder()
+ .setMinDownstreamBandwidthKbps(
+ TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
+ TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS);
+
+ fail("Expected IAE for exit threshold > entry threshold");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
public void testPersistableBundle() {
final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate();
assertEquals(
diff --git a/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkTemplateTestBase.java b/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkTemplateTestBase.java
new file mode 100644
index 0000000..399e136
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkTemplateTestBase.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.vcn;
+
+public class VcnUnderlyingNetworkTemplateTestBase {
+ // Public for use in NetworkPriorityClassifierTest
+ public static final int TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS = 200;
+ public static final int TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS = 100;
+ public static final int TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS = 400;
+ public static final int TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS = 300;
+}
diff --git a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java
index cb5b47b..4063178 100644
--- a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java
@@ -17,8 +17,6 @@
import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -28,15 +26,19 @@
import java.util.Set;
-public class VcnWifiUnderlyingNetworkTemplateTest {
+public class VcnWifiUnderlyingNetworkTemplateTest extends VcnUnderlyingNetworkTemplateTestBase {
private static final String SSID = "TestWifi";
- private static final int INVALID_NETWORK_QUALITY = -1;
// Package private for use in VcnGatewayConnectionConfigTest
static VcnWifiUnderlyingNetworkTemplate getTestNetworkTemplate() {
return new VcnWifiUnderlyingNetworkTemplate.Builder()
- .setNetworkQuality(NETWORK_QUALITY_OK)
.setMetered(MATCH_FORBIDDEN)
+ .setMinUpstreamBandwidthKbps(
+ TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS,
+ TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS)
+ .setMinDownstreamBandwidthKbps(
+ TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
+ TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS)
.setSsids(Set.of(SSID))
.build();
}
@@ -44,8 +46,19 @@
@Test
public void testBuilderAndGetters() {
final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate();
- assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality());
assertEquals(MATCH_FORBIDDEN, networkPriority.getMetered());
+ assertEquals(
+ TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS,
+ networkPriority.getMinEntryUpstreamBandwidthKbps());
+ assertEquals(
+ TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
+ networkPriority.getMinExitUpstreamBandwidthKbps());
+ assertEquals(
+ TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
+ networkPriority.getMinEntryDownstreamBandwidthKbps());
+ assertEquals(
+ TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
+ networkPriority.getMinExitDownstreamBandwidthKbps());
assertEquals(Set.of(SSID), networkPriority.getSsids());
}
@@ -53,18 +66,37 @@
public void testBuilderAndGettersForDefaultValues() {
final VcnWifiUnderlyingNetworkTemplate networkPriority =
new VcnWifiUnderlyingNetworkTemplate.Builder().build();
- assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality());
assertEquals(MATCH_ANY, networkPriority.getMetered());
+
+ // Explicitly expect 0, as documented in Javadoc on setter methods..
+ assertEquals(0, networkPriority.getMinEntryUpstreamBandwidthKbps());
+ assertEquals(0, networkPriority.getMinExitUpstreamBandwidthKbps());
+ assertEquals(0, networkPriority.getMinEntryDownstreamBandwidthKbps());
+ assertEquals(0, networkPriority.getMinExitDownstreamBandwidthKbps());
+
assertTrue(networkPriority.getSsids().isEmpty());
}
@Test
- public void testBuildWithInvalidNetworkQuality() {
+ public void testBuilderRequiresStricterEntryCriteria() {
try {
new VcnWifiUnderlyingNetworkTemplate.Builder()
- .setNetworkQuality(INVALID_NETWORK_QUALITY);
- fail("Expected to fail due to the invalid network quality");
- } catch (Exception expected) {
+ .setMinUpstreamBandwidthKbps(
+ TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
+ TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS);
+
+ fail("Expected IAE for exit threshold > entry threshold");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ new VcnWifiUnderlyingNetworkTemplate.Builder()
+ .setMinDownstreamBandwidthKbps(
+ TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
+ TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS);
+
+ fail("Expected IAE for exit threshold > entry threshold");
+ } catch (IllegalArgumentException expected) {
}
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java
index 6d26968..5d2f9d7 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java
@@ -58,7 +58,6 @@
import com.android.server.VcnManagementService.VcnCallback;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
-import com.android.server.vcn.Vcn.VcnUserMobileDataStateListener;
import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener;
import org.junit.Before;
@@ -209,13 +208,6 @@
}
@Test
- public void testMobileDataStateListenersRegistered() {
- // Validate state from setUp()
- verify(mTelephonyManager, times(3))
- .registerTelephonyCallback(any(), any(VcnUserMobileDataStateListener.class));
- }
-
- @Test
public void testMobileDataStateCheckedOnInitialization_enabled() {
// Validate state from setUp()
assertTrue(mVcn.isMobileDataEnabled());
@@ -271,24 +263,6 @@
assertFalse(mVcn.isMobileDataEnabled());
}
- @Test
- public void testSubscriptionSnapshotUpdatesMobileDataStateListeners() {
- final TelephonySubscriptionSnapshot updatedSnapshot =
- mock(TelephonySubscriptionSnapshot.class);
-
- doReturn(new ArraySet<>(Arrays.asList(2, 4)))
- .when(updatedSnapshot)
- .getAllSubIdsInGroup(any());
-
- mVcn.updateSubscriptionSnapshot(updatedSnapshot);
- mTestLooper.dispatchAll();
-
- verify(mTelephonyManager, times(4))
- .registerTelephonyCallback(any(), any(VcnUserMobileDataStateListener.class));
- verify(mTelephonyManager, times(2))
- .unregisterTelephonyCallback(any(VcnUserMobileDataStateListener.class));
- }
-
private void triggerVcnRequestListeners(NetworkRequestListener requestListener) {
for (final int[] caps : TEST_CAPS) {
startVcnGatewayWithCapabilities(requestListener, caps);
@@ -428,17 +402,24 @@
verify(mVcnNetworkProvider).resendAllRequests(requestListener);
}
- private void setupForMobileDataTest(boolean startingToggleState) {
+ private void verifyMobileDataToggled(boolean startingToggleState, boolean endingToggleState) {
+ final ArgumentCaptor<ContentObserver> captor =
+ ArgumentCaptor.forClass(ContentObserver.class);
+ verify(mContentResolver).registerContentObserver(any(), anyBoolean(), captor.capture());
+ final ContentObserver contentObserver = captor.getValue();
+
// Start VcnGatewayConnections
final NetworkRequestListener requestListener = verifyAndGetRequestListener();
mVcn.setMobileDataEnabled(startingToggleState);
triggerVcnRequestListeners(requestListener);
- }
+ final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways =
+ mVcn.getVcnGatewayConnectionConfigMap();
- private void verifyMobileDataToggledUpdatesGatewayConnections(
- boolean startingToggleState,
- boolean endingToggleState,
- Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways) {
+ // Trigger data toggle change.
+ doReturn(endingToggleState).when(mTelephonyManager).isDataEnabled();
+ contentObserver.onChange(false /* selfChange, ignored */);
+ mTestLooper.dispatchAll();
+
// Verify that data toggle changes restart ONLY INTERNET or DUN networks, and only if the
// toggle state changed.
for (Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry : gateways.entrySet()) {
@@ -452,98 +433,29 @@
}
}
- final NetworkRequestListener requestListener = verifyAndGetRequestListener();
if (startingToggleState != endingToggleState) {
verify(mVcnNetworkProvider).resendAllRequests(requestListener);
}
assertEquals(endingToggleState, mVcn.isMobileDataEnabled());
}
- private void verifyGlobalMobileDataToggled(
- boolean startingToggleState, boolean endingToggleState) {
- setupForMobileDataTest(startingToggleState);
- final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways =
- mVcn.getVcnGatewayConnectionConfigMap();
-
- // Trigger data toggle change
- final ArgumentCaptor<ContentObserver> captor =
- ArgumentCaptor.forClass(ContentObserver.class);
- verify(mContentResolver).registerContentObserver(any(), anyBoolean(), captor.capture());
- final ContentObserver contentObserver = captor.getValue();
-
- doReturn(endingToggleState).when(mTelephonyManager).isDataEnabled();
- contentObserver.onChange(false /* selfChange, ignored */);
- mTestLooper.dispatchAll();
-
- // Verify resultant behavior
- verifyMobileDataToggledUpdatesGatewayConnections(
- startingToggleState, endingToggleState, gateways);
+ @Test
+ public void testMobileDataEnabled() {
+ verifyMobileDataToggled(false /* startingToggleState */, true /* endingToggleState */);
}
@Test
- public void testGlobalMobileDataEnabled() {
- verifyGlobalMobileDataToggled(
- false /* startingToggleState */, true /* endingToggleState */);
+ public void testMobileDataDisabled() {
+ verifyMobileDataToggled(true /* startingToggleState */, false /* endingToggleState */);
}
@Test
- public void testGlobalMobileDataDisabled() {
- verifyGlobalMobileDataToggled(
- true /* startingToggleState */, false /* endingToggleState */);
+ public void testMobileDataObserverFiredWithoutChanges_dataEnabled() {
+ verifyMobileDataToggled(false /* startingToggleState */, false /* endingToggleState */);
}
@Test
- public void testGlobalMobileDataObserverFiredWithoutChanges_dataEnabled() {
- verifyGlobalMobileDataToggled(
- false /* startingToggleState */, false /* endingToggleState */);
- }
-
- @Test
- public void testGlobalMobileDataObserverFiredWithoutChanges_dataDisabled() {
- verifyGlobalMobileDataToggled(true /* startingToggleState */, true /* endingToggleState */);
- }
-
- private void verifySubscriptionMobileDataToggled(
- boolean startingToggleState, boolean endingToggleState) {
- setupForMobileDataTest(startingToggleState);
- final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways =
- mVcn.getVcnGatewayConnectionConfigMap();
-
- // Trigger data toggle change.
- final ArgumentCaptor<VcnUserMobileDataStateListener> captor =
- ArgumentCaptor.forClass(VcnUserMobileDataStateListener.class);
- verify(mTelephonyManager, times(3)).registerTelephonyCallback(any(), captor.capture());
- final VcnUserMobileDataStateListener listener = captor.getValue();
-
- doReturn(endingToggleState).when(mTelephonyManager).isDataEnabled();
- listener.onUserMobileDataStateChanged(false /* enabled, ignored */);
- mTestLooper.dispatchAll();
-
- // Verify resultant behavior
- verifyMobileDataToggledUpdatesGatewayConnections(
- startingToggleState, endingToggleState, gateways);
- }
-
- @Test
- public void testSubscriptionMobileDataEnabled() {
- verifyGlobalMobileDataToggled(
- false /* startingToggleState */, true /* endingToggleState */);
- }
-
- @Test
- public void testSubscriptionMobileDataDisabled() {
- verifyGlobalMobileDataToggled(
- true /* startingToggleState */, false /* endingToggleState */);
- }
-
- @Test
- public void testSubscriptionMobileDataListenerFiredWithoutChanges_dataEnabled() {
- verifyGlobalMobileDataToggled(
- false /* startingToggleState */, false /* endingToggleState */);
- }
-
- @Test
- public void testSubscriptionMobileDataListenerFiredWithoutChanges_dataDisabled() {
- verifyGlobalMobileDataToggled(true /* startingToggleState */, true /* endingToggleState */);
+ public void testMobileDataObserverFiredWithoutChanges_dataDisabled() {
+ verifyMobileDataToggled(true /* startingToggleState */, true /* endingToggleState */);
}
}
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
index 4bb7de8..6c849b5 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
@@ -18,7 +18,10 @@
import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
+import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS;
+import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS;
+import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS;
+import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS;
import static com.android.server.vcn.VcnTestUtils.setupSystemService;
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_ANY;
@@ -76,6 +79,12 @@
private static final int CARRIER_ID = 1;
private static final int CARRIER_ID_OTHER = 2;
+ private static final int LINK_UPSTREAM_BANDWIDTH_KBPS = 1024;
+ private static final int LINK_DOWNSTREAM_BANDWIDTH_KBPS = 2048;
+
+ private static final int TEST_MIN_UPSTREAM_BANDWIDTH_KBPS = 100;
+ private static final int TEST_MIN_DOWNSTREAM_BANDWIDTH_KBPS = 200;
+
private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
private static final NetworkCapabilities WIFI_NETWORK_CAPABILITIES =
@@ -83,6 +92,8 @@
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.setSignalStrength(WIFI_RSSI)
.setSsid(SSID)
+ .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS)
+ .setLinkDownstreamBandwidthKbps(LINK_DOWNSTREAM_BANDWIDTH_KBPS)
.build();
private static final TelephonyNetworkSpecifier TEL_NETWORK_SPECIFIER =
@@ -93,6 +104,8 @@
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.setSubscriptionIds(Set.of(SUB_ID))
.setNetworkSpecifier(TEL_NETWORK_SPECIFIER)
+ .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS)
+ .setLinkDownstreamBandwidthKbps(LINK_DOWNSTREAM_BANDWIDTH_KBPS)
.build();
private static final LinkProperties LINK_PROPERTIES = getLinkPropertiesWithName("test_iface");
@@ -146,7 +159,6 @@
public void testMatchWithoutNotMeteredBit() {
final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
new VcnWifiUnderlyingNetworkTemplate.Builder()
- .setNetworkQuality(NETWORK_QUALITY_OK)
.setMetered(MATCH_FORBIDDEN)
.build();
@@ -161,11 +173,133 @@
null /* carrierConfig */));
}
+ private void verifyMatchesPriorityRuleForUpstreamBandwidth(
+ int entryUpstreamBandwidth,
+ int exitUpstreamBandwidth,
+ UnderlyingNetworkRecord currentlySelected,
+ boolean expectMatch) {
+ final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
+ new VcnWifiUnderlyingNetworkTemplate.Builder()
+ .setMinUpstreamBandwidthKbps(entryUpstreamBandwidth, exitUpstreamBandwidth)
+ .build();
+
+ assertEquals(
+ expectMatch,
+ checkMatchesPriorityRule(
+ mVcnContext,
+ wifiNetworkPriority,
+ mWifiNetworkRecord,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ currentlySelected,
+ null /* carrierConfig */));
+ }
+
+ private void verifyMatchesPriorityRuleForDownstreamBandwidth(
+ int entryDownstreamBandwidth,
+ int exitDownstreamBandwidth,
+ UnderlyingNetworkRecord currentlySelected,
+ boolean expectMatch) {
+ final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
+ new VcnWifiUnderlyingNetworkTemplate.Builder()
+ .setMinDownstreamBandwidthKbps(
+ entryDownstreamBandwidth, exitDownstreamBandwidth)
+ .build();
+
+ assertEquals(
+ expectMatch,
+ checkMatchesPriorityRule(
+ mVcnContext,
+ wifiNetworkPriority,
+ mWifiNetworkRecord,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ currentlySelected,
+ null /* carrierConfig */));
+ }
+
+ @Test
+ public void testMatchWithEntryUpstreamBandwidthEquals() {
+ verifyMatchesPriorityRuleForUpstreamBandwidth(
+ TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS,
+ TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
+ null /* currentlySelected */,
+ true);
+ }
+
+ @Test
+ public void testMatchWithEntryUpstreamBandwidthTooLow() {
+ verifyMatchesPriorityRuleForUpstreamBandwidth(
+ LINK_UPSTREAM_BANDWIDTH_KBPS + 1,
+ LINK_UPSTREAM_BANDWIDTH_KBPS + 1,
+ null /* currentlySelected */,
+ false);
+ }
+
+ @Test
+ public void testMatchWithEntryDownstreamBandwidthEquals() {
+ verifyMatchesPriorityRuleForDownstreamBandwidth(
+ TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
+ TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
+ null /* currentlySelected */,
+ true);
+ }
+
+ @Test
+ public void testMatchWithEntryDownstreamBandwidthTooLow() {
+ verifyMatchesPriorityRuleForDownstreamBandwidth(
+ LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1,
+ LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1,
+ null /* currentlySelected */,
+ false);
+ }
+
+ @Test
+ public void testMatchWithExitUpstreamBandwidthEquals() {
+ verifyMatchesPriorityRuleForUpstreamBandwidth(
+ TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
+ TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
+ mWifiNetworkRecord,
+ true);
+ }
+
+ @Test
+ public void testMatchWithExitUpstreamBandwidthTooLow() {
+ verifyMatchesPriorityRuleForUpstreamBandwidth(
+ LINK_UPSTREAM_BANDWIDTH_KBPS + 1,
+ LINK_UPSTREAM_BANDWIDTH_KBPS + 1,
+ mWifiNetworkRecord,
+ false);
+ }
+
+ @Test
+ public void testMatchWithExitDownstreamBandwidthEquals() {
+ verifyMatchesPriorityRuleForDownstreamBandwidth(
+ TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
+ TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
+ mWifiNetworkRecord,
+ true);
+ }
+
+ @Test
+ public void testMatchWithExitDownstreamBandwidthTooLow() {
+ verifyMatchesPriorityRuleForDownstreamBandwidth(
+ LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1,
+ LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1,
+ mWifiNetworkRecord,
+ false);
+ }
+
private void verifyMatchWifi(
boolean isSelectedNetwork, PersistableBundle carrierConfig, boolean expectMatch) {
final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
new VcnWifiUnderlyingNetworkTemplate.Builder()
- .setNetworkQuality(NETWORK_QUALITY_OK)
+ .setMinUpstreamBandwidthKbps(
+ TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS,
+ TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS)
+ .setMinDownstreamBandwidthKbps(
+ TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
+ TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS)
.build();
final UnderlyingNetworkRecord selectedNetworkRecord =
isSelectedNetwork ? mWifiNetworkRecord : null;
@@ -214,7 +348,12 @@
final String nwPrioritySsid = useMatchedSsid ? SSID : SSID_OTHER;
final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
new VcnWifiUnderlyingNetworkTemplate.Builder()
- .setNetworkQuality(NETWORK_QUALITY_OK)
+ .setMinUpstreamBandwidthKbps(
+ TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS,
+ TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS)
+ .setMinDownstreamBandwidthKbps(
+ TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
+ TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS)
.setSsids(Set.of(nwPrioritySsid))
.build();
@@ -238,7 +377,13 @@
}
private static VcnCellUnderlyingNetworkTemplate.Builder getCellNetworkPriorityBuilder() {
- return new VcnCellUnderlyingNetworkTemplate.Builder().setNetworkQuality(NETWORK_QUALITY_OK);
+ return new VcnCellUnderlyingNetworkTemplate.Builder()
+ .setMinUpstreamBandwidthKbps(
+ TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS,
+ TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS)
+ .setMinDownstreamBandwidthKbps(
+ TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
+ TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS);
}
@Test
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index b939f35..4a2d0ae 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -311,7 +311,7 @@
component_process ? component_process->value : default_process_;
get_name = !process.empty() && process[0] != ':';
}
- } else if (node->name == "instrumentation") {
+ } else if (node->name == "instrumentation" || node->name == "process") {
get_name = true;
}
diff --git a/tools/aapt2/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp
index e104066..466b7d9 100644
--- a/tools/aapt2/java/ProguardRules_test.cpp
+++ b/tools/aapt2/java/ProguardRules_test.cpp
@@ -44,6 +44,9 @@
android:name="com.foo.BarApplication"
android:zygotePreloadName="com.foo.BarZygotePreload"
>
+ <processes>
+ <process android:process=":sub" android:name="com.foo.BazApplication" />
+ </processes>
<activity android:name="com.foo.BarActivity"/>
<service android:name="com.foo.BarService"/>
<receiver android:name="com.foo.BarReceiver"/>
@@ -59,6 +62,7 @@
EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarAppComponentFactory { <init>(); }"));
EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarBackupAgent { <init>(); }"));
EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarApplication { <init>(); }"));
+ EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BazApplication { <init>(); }"));
EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarActivity { <init>(); }"));
EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarService { <init>(); }"));
EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarReceiver { <init>(); }"));
diff --git a/tools/aosp/aosp_sha.sh b/tools/aosp/aosp_sha.sh
index 36bea57..95b43cd 100755
--- a/tools/aosp/aosp_sha.sh
+++ b/tools/aosp/aosp_sha.sh
@@ -1,7 +1,7 @@
#!/bin/bash
LOCAL_DIR="$( dirname "${BASH_SOURCE}" )"
-if git branch -vv | grep -q -E "^\*[^\[]+\[aosp/"; then
+if git log -n 1 --format='%D' HEAD@{upstream} | grep -q aosp/; then
# Change appears to be in AOSP
exit 0
elif git log -n 1 --format='%B' $1 | grep -q -E "^Ignore-AOSP-First: .+" ; then