Merge "ShellTranstion: Apply recents input consumer when the window in transion"
diff --git a/Android.bp b/Android.bp
index d64951c..537f558 100644
--- a/Android.bp
+++ b/Android.bp
@@ -93,7 +93,7 @@
":framework-mca-effect-sources",
":framework-mca-filterfw-sources",
":framework-mca-filterpacks-sources",
- ":framework-media-sources",
+ ":framework-media-non-updatable-sources",
":framework-mms-sources",
":framework-omapi-sources",
":framework-opengl-sources",
@@ -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 7d4a5e5..321952d 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -74,11 +74,6 @@
srcs: [
":android-non-updatable-stub-sources",
- // Module sources
- ":art.module.public.api{.public.stubs.source}",
- ":conscrypt.module.public.api{.public.stubs.source}",
- ":i18n.module.public.api{.public.stubs.source}",
-
// No longer part of the stubs, but are included in the docs.
":android-test-base-sources",
":android-test-mock-sources",
@@ -116,6 +111,10 @@
name: "framework-doc-stubs-sources-default",
defaults: ["framework-doc-stubs-default"],
srcs: [
+ ":art.module.public.api{.public.stubs.source}",
+ ":conscrypt.module.public.api{.public.stubs.source}",
+ ":i18n.module.public.api{.public.stubs.source}",
+
":framework-appsearch-sources",
":framework-connectivity-sources",
":framework-connectivity-tiramisu-updatable-sources",
@@ -160,26 +159,8 @@
droidstubs {
name: "framework-doc-stubs",
defaults: ["framework-doc-stubs-default"],
+ srcs: [":all-modules-public-stubs-source"],
args: metalava_framework_docs_args,
- srcs: [
- ":android.net.ipsec.ike{.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}",
- ],
aidl: {
local_include_dirs: [
"apex/media/aidl/stable",
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 7aa68f8..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"],
diff --git a/api/api.go b/api/api.go
index 6cc129a..4b6ebc1 100644
--- a/api/api.go
+++ b/api/api.go
@@ -21,8 +21,13 @@
"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
@@ -69,6 +74,19 @@
Visibility []string
}
+type libraryProps struct {
+ Name *string
+ Sdk_version *string
+ Static_libs []string
+ Visibility []string
+}
+
+type fgProps struct {
+ Name *string
+ Srcs []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"
@@ -99,7 +117,7 @@
props.Tools = []string{"metalava"}
props.Out = []string{filename}
props.Cmd = proptools.StringPtr(metalavaCmd + "$(in) --api $(out)")
- props.Srcs = createSrcs(txt.BaseTxt, txt.Modules, txt.ModuleTag)
+ props.Srcs = append([]string{txt.BaseTxt}, createSrcs(txt.Modules, txt.ModuleTag)...)
props.Dists = []android.Dist{
{
Targets: []string{"droidcore"},
@@ -122,7 +140,7 @@
props.Tools = []string{"merge_zips"}
props.Out = []string{"current.srcjar"}
props.Cmd = proptools.StringPtr("$(location merge_zips) $(out) $(in)")
- props.Srcs = createSrcs(":api-stubs-docs-non-updatable", modules, "{.public.stubs.source}")
+ props.Srcs = append([]string{":api-stubs-docs-non-updatable"}, createSrcs(modules, "{.public.stubs.source}")...)
props.Visibility = []string{"//visibility:private"} // Used by make module in //development, mind
ctx.CreateModule(genrule.GenRuleFactory, &props)
}
@@ -130,17 +148,28 @@
// 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}")
+ props.Srcs = append([]string{":android-non-updatable-doc-stubs{.annotations.zip}"}, createSrcs(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,36 +178,34 @@
// 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
- props.Srcs = createSrcs(":framework-doc-stubs{.api_versions.xml}", modules, ".stubs{.jar}")
+ props.Srcs = append([]string{":framework-doc-stubs{.api_versions.xml}"}, createSrcs(modules, ".stubs{.jar}")...)
props.Dists = []android.Dist{{Targets: []string{"sdk"}}}
ctx.CreateModule(genrule.GenRuleFactory, &props)
}
-func createSrcs(base string, modules []string, tag string) []string {
- a := make([]string, 0, len(modules)+1)
- a = append(a, base)
- for _, module := range modules {
- a = append(a, ":"+module+tag)
- }
- return a
+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 = transformArray(modules, "", ".stubs.module_lib")
+ props.Sdk_version = proptools.StringPtr("module_current")
+ props.Visibility = []string{"//frameworks/base"}
+ ctx.CreateModule(java.LibraryFactory, &props)
}
-func remove(s []string, v string) []string {
- s2 := make([]string, 0, len(s))
- for _, sv := range s {
- if sv != v {
- s2 = append(s2, sv)
- }
- }
- return s2
+func createPublicStubsSourceFilegroup(ctx android.LoadHookContext, modules []string) {
+ props := fgProps{}
+ props.Name = proptools.StringPtr("all-modules-public-stubs-source")
+ props.Srcs = createSrcs(modules, "{.public.stubs.source}")
+ props.Visibility = []string{"//frameworks/base"}
+ ctx.CreateModule(android.FileGroupFactory, &props)
}
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 := bootclasspath
- bcpWithSystemApi = remove(bcpWithSystemApi, "conscrypt.module.public.api")
- bcpWithSystemApi = remove(bcpWithSystemApi, "i18n.module.public.api")
+ bcpWithSystemApi := removeAll(bootclasspath, []string{conscrypt, i18n})
tagSuffix := []string{".api.txt}", ".removed-api.txt}"}
for i, f := range []string{"current.txt", "removed.txt"} {
@@ -226,22 +253,13 @@
createMergedStubsSrcjar(ctx, bootclasspath)
- // Conscrypt and i18n currently do not enable annotations
- annotationModules := bootclasspath
- annotationModules = remove(annotationModules, "conscrypt.module.public.api")
- annotationModules = remove(annotationModules, "i18n.module.public.api")
- createMergedAnnotations(ctx, annotationModules)
+ createMergedModuleLibStubs(ctx, bootclasspath)
- // 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.
- filteredModules := bootclasspath
- filteredModules = remove(filteredModules, "art.module.public.api")
- createFilteredApiVersions(ctx, filteredModules)
+ createMergedAnnotations(ctx, bootclasspath)
+
+ createFilteredApiVersions(ctx, bootclasspath)
+
+ createPublicStubsSourceFilegroup(ctx, bootclasspath)
}
func combinedApisModuleFactory() android.Module {
@@ -251,3 +269,36 @@
android.AddLoadHook(module, func(ctx android.LoadHookContext) { module.createInternalModules(ctx) })
return module
}
+
+// Various utility methods below.
+
+// Creates an array of ":<m><tag>" for each m in <modules>.
+func createSrcs(modules []string, tag string) []string {
+ return transformArray(modules, ":", tag)
+}
+
+// Creates an array of "<prefix><m><suffix>", for each m in <modules>.
+func transformArray(modules []string, prefix, suffix string) []string {
+ a := make([]string, 0, len(modules))
+ for _, module := range modules {
+ a = append(a, prefix+module+suffix)
+ }
+ 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 {
+ if sv != v {
+ s2 = append(s2, sv)
+ }
+ }
+ return s2
+}
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index 260c8a4..b384e70 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -259,7 +259,8 @@
public void runDisableAppDataIsolation() throws RemoteException {
if (!SystemProperties.getBoolean(
ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false)) {
- throw new IllegalStateException("Storage app data isolation is not enabled.");
+ System.err.println("Storage app data isolation is not enabled.");
+ return;
}
final String pkgName = nextArg();
final int pid = Integer.parseInt(nextArg());
diff --git a/core/api/current.txt b/core/api/current.txt
index 4f15fa9..9e7bc691 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";
@@ -971,6 +972,7 @@
field public static final int listSeparatorTextViewStyle = 16843272; // 0x1010208
field public static final int listViewStyle = 16842868; // 0x1010074
field public static final int listViewWhiteStyle = 16842869; // 0x1010075
+ field public static final int localeConfig;
field public static final int lockTaskMode = 16844013; // 0x10104ed
field public static final int logo = 16843454; // 0x10102be
field public static final int logoDescription = 16844009; // 0x10104e9
@@ -5696,6 +5698,17 @@
method @Deprecated public android.view.Window startActivity(String, android.content.Intent);
}
+ public class LocaleConfig {
+ ctor public LocaleConfig(@NonNull android.content.Context);
+ method public int getStatus();
+ method @Nullable public android.os.LocaleList getSupportedLocales();
+ field public static final int STATUS_NOT_SPECIFIED = 1; // 0x1
+ field public static final int STATUS_PARSING_FAILED = 2; // 0x2
+ field public static final int STATUS_SUCCESS = 0; // 0x0
+ field public static final String TAG_LOCALE = "locale";
+ field public static final String TAG_LOCALE_CONFIG = "locale-config";
+ }
+
public class LocaleManager {
method @NonNull public android.os.LocaleList getApplicationLocales();
method public void setApplicationLocales(@NonNull android.os.LocaleList);
@@ -18330,10 +18343,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();
}
@@ -20149,6 +20164,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();
@@ -20205,7 +20238,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();
@@ -20227,7 +20260,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();
@@ -20289,11 +20322,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);
@@ -20965,6 +21008,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();
@@ -27724,6 +27768,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);
@@ -27784,6 +27830,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
@@ -27797,6 +27847,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>);
}
@@ -38029,6 +38081,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);
@@ -38040,19 +38137,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);
@@ -38065,6 +38162,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);
@@ -38103,22 +38201,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 {
@@ -47166,6 +47271,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);
@@ -49165,6 +49279,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
@@ -52267,6 +52382,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);
}
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 26f5c01..d674507 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -367,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 {
@@ -3835,7 +3834,7 @@
}
public class HdmiDeviceInfo implements android.os.Parcelable {
- ctor public HdmiDeviceInfo();
+ ctor @Deprecated public HdmiDeviceInfo();
method public int describeContents();
method public int getAdopterId();
method public int getDeviceId();
@@ -3856,6 +3855,7 @@
method public boolean isSourceType();
method public void writeToParcel(android.os.Parcel, int);
field public static final int ADDR_INTERNAL = 0; // 0x0
+ field public static final int ADDR_INVALID = -1; // 0xffffffff
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.hdmi.HdmiDeviceInfo> CREATOR;
field public static final int DEVICE_AUDIO_SYSTEM = 5; // 0x5
field public static final int DEVICE_INACTIVE = -1; // 0xffffffff
@@ -3869,6 +3869,7 @@
field public static final int PATH_INTERNAL = 0; // 0x0
field public static final int PATH_INVALID = 65535; // 0xffff
field public static final int PORT_INVALID = -1; // 0xffffffff
+ field public static final int VENDOR_ID_UNKNOWN = 16777215; // 0xffffff
}
public final class HdmiHotplugEvent implements android.os.Parcelable {
@@ -6384,6 +6385,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);
}
@@ -8166,6 +8168,17 @@
public static class NetworkStats.Entry {
ctor public NetworkStats.Entry(@Nullable String, int, int, int, int, int, int, long, long, long, long, long);
+ method public int getDefaultNetwork();
+ method public int getMetered();
+ method public long getOperations();
+ method public int getRoaming();
+ method public long getRxBytes();
+ method public long getRxPackets();
+ method public int getSet();
+ method public int getTag();
+ method public long getTxBytes();
+ method public long getTxPackets();
+ method public int getUid();
}
@Deprecated public class RssiCurve implements android.os.Parcelable {
@@ -9154,6 +9167,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/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
new file mode 100644
index 0000000..436eac3
--- /dev/null
+++ b/core/java/android/app/LocaleConfig.java
@@ -0,0 +1,166 @@
+/*
+ * 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.app;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.LocaleList;
+import android.util.AttributeSet;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * The LocaleConfig of an application.
+ * Defined in an XML resource file with an {@code <locale-config>} element and
+ * referenced in the manifest via {@code android:localeConfig} on
+ * {@code <application>}.
+ *
+ * For more information, see TODO(b/214154050): add link to guide
+ *
+ * @attr ref android.R.styleable#LocaleConfig_Locale_name
+ * @attr ref android.R.styleable#AndroidManifestApplication_localeConfig
+ */
+public class LocaleConfig {
+
+ private static final String TAG = "LocaleConfig";
+ public static final String TAG_LOCALE_CONFIG = "locale-config";
+ public static final String TAG_LOCALE = "locale";
+ private LocaleList mLocales;
+ private int mStatus;
+
+ /**
+ * succeeded reading the LocaleConfig structure stored in an XML file.
+ */
+ public static final int STATUS_SUCCESS = 0;
+ /**
+ * No android:localeConfig tag on <application>.
+ */
+ public static final int STATUS_NOT_SPECIFIED = 1;
+ /**
+ * Malformed input in the XML file where the LocaleConfig was stored.
+ */
+ public static final int STATUS_PARSING_FAILED = 2;
+
+ /** @hide */
+ @IntDef(prefix = { "STATUS_" }, value = {
+ STATUS_SUCCESS,
+ STATUS_NOT_SPECIFIED,
+ STATUS_PARSING_FAILED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Status{}
+
+ /**
+ * Returns the LocaleConfig for the provided application context
+ *
+ * @param context the context of the application
+ *
+ * @see Context#createPackageContext(String, int).
+ */
+ public LocaleConfig(@NonNull Context context) {
+ int resId = 0;
+ Resources res = context.getResources();
+ try {
+ //Get the resource id
+ resId = new ApplicationInfo(context.getApplicationInfo()).getLocaleConfigRes();
+ //Get the parser to read XML data
+ XmlResourceParser parser = res.getXml(resId);
+ parseLocaleConfig(parser, res);
+ } catch (Resources.NotFoundException e) {
+ Slog.w(TAG, "The resource file pointed to by the given resource ID isn't found.", e);
+ mStatus = STATUS_NOT_SPECIFIED;
+ } catch (XmlPullParserException | IOException e) {
+ Slog.w(TAG, "Failed to parse XML configuration from "
+ + res.getResourceEntryName(resId), e);
+ mStatus = STATUS_PARSING_FAILED;
+ }
+ }
+
+ /**
+ * Parse the XML content and get the locales supported by the application
+ */
+ private void parseLocaleConfig(XmlResourceParser parser, Resources res)
+ throws IOException, XmlPullParserException {
+ XmlUtils.beginDocument(parser, TAG_LOCALE_CONFIG);
+ int outerDepth = parser.getDepth();
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+ Set<String> localeNames = new HashSet<String>();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ if (TAG_LOCALE.equals(parser.getName())) {
+ final TypedArray attributes = res.obtainAttributes(
+ attrs, com.android.internal.R.styleable.LocaleConfig_Locale);
+ String nameAttr = attributes.getString(
+ com.android.internal.R.styleable.LocaleConfig_Locale_name);
+ localeNames.add(nameAttr);
+ attributes.recycle();
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ mStatus = STATUS_SUCCESS;
+ mLocales = LocaleList.forLanguageTags(String.join(",", localeNames));
+ }
+
+ /**
+ * Returns the locales supported by the specified application.
+ *
+ * <p><b>Note:</b> The locale format should follow the
+ * <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 regular expression</a>
+ *
+ * @return the {@link LocaleList}
+ */
+ public @Nullable LocaleList getSupportedLocales() {
+ return mLocales;
+ }
+
+ /**
+ * Get the status of reading the resource file where the LocaleConfig was stored.
+ *
+ * <p>Distinguish "the application didn't provide the resource file" from "the application
+ * provided malformed input" if {@link #getSupportedLocales()} returns {@code null}.
+ *
+ * @return {@code STATUS_SUCCESS} if the LocaleConfig structure existed in an XML file was
+ * successfully read, or {@code STATUS_NOT_SPECIFIED} if no android:localeConfig tag on
+ * <application> pointing to an XML file that stores the LocaleConfig, or
+ * {@code STATUS_PARSING_FAILED} if the application provided malformed input for the
+ * LocaleConfig structure.
+ *
+ * @see #STATUS_SUCCESS
+ * @see #STATUS_NOT_SPECIFIED
+ * @see #STATUS_PARSING_FAILED
+ *
+ */
+ public @Status int getStatus() {
+ return mStatus;
+ }
+}
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/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 858e4daa1..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.
@@ -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.
*
@@ -299,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/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/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 76b4e5c..9e9dd1e 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1543,6 +1543,11 @@
@Nullable
private ArrayMap<String, String> mAppClassNamesByProcess;
+ /**
+ * Resource file providing the application's locales configuration.
+ */
+ private int localeConfigRes;
+
public void dump(Printer pw, String prefix) {
dump(pw, prefix, DUMP_FLAG_ALL);
}
@@ -1660,6 +1665,10 @@
pw.println(prefix + "requestRawExternalStorageAccess="
+ requestRawExternalStorageAccess);
}
+ if (localeConfigRes != 0) {
+ pw.println(prefix + "localeConfigRes=0x"
+ + Integer.toHexString(localeConfigRes));
+ }
}
pw.println(prefix + "createTimestamp=" + createTimestamp);
super.dumpBack(pw, prefix);
@@ -1891,6 +1900,7 @@
memtagMode = orig.memtagMode;
nativeHeapZeroInitialized = orig.nativeHeapZeroInitialized;
requestRawExternalStorageAccess = orig.requestRawExternalStorageAccess;
+ localeConfigRes = orig.localeConfigRes;
createTimestamp = System.currentTimeMillis();
}
@@ -1993,6 +2003,7 @@
dest.writeString(mAppClassNamesByProcess.valueAt(i));
}
}
+ dest.writeInt(localeConfigRes);
}
public static final @android.annotation.NonNull Parcelable.Creator<ApplicationInfo> CREATOR
@@ -2088,6 +2099,7 @@
mAppClassNamesByProcess.put(source.readString(), source.readString());
}
}
+ localeConfigRes = source.readInt();
}
/**
@@ -2631,4 +2643,16 @@
}
return className;
}
+
+ /** @hide */ public void setLocaleConfigRes(int value) { localeConfigRes = value; }
+
+ /**
+ * Return the resource id pointing to the resource file that provides the application's locales
+ * configuration.
+ *
+ * @hide
+ */
+ public int getLocaleConfigRes() {
+ return localeConfigRes;
+ }
}
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/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index fc9f1a5..e635e91 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -375,6 +375,8 @@
ParsingPackage setResetEnabledSettingsOnAppDataCleared(
boolean resetEnabledSettingsOnAppDataCleared);
+ ParsingPackage setLocaleConfigRes(int localeConfigRes);
+
// TODO(b/135203078): This class no longer has access to ParsedPackage, find a replacement
// for moving to the next step
@CallSuper
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index ddab207..fb42804 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -559,6 +559,8 @@
private UUID mStorageUuid;
private long mLongVersionCode;
+ private int mLocaleConfigRes;
+
@VisibleForTesting
public ParsingPackageImpl(@NonNull String packageName, @NonNull String baseApkPath,
@NonNull String path, @Nullable TypedArray manifestArray) {
@@ -1136,6 +1138,7 @@
appInfo.setSplitResourcePaths(splitCodePaths);
appInfo.setVersionCode(mLongVersionCode);
appInfo.setAppClassNamesByProcess(buildAppClassNamesByProcess());
+ appInfo.setLocaleConfigRes(mLocaleConfigRes);
return appInfo;
}
@@ -1314,6 +1317,7 @@
dest.writeInt(this.memtagMode);
dest.writeInt(this.nativeHeapZeroInitialized);
sForBoolean.parcel(this.requestRawExternalStorageAccess, dest, flags);
+ dest.writeInt(this.mLocaleConfigRes);
}
public ParsingPackageImpl(Parcel in) {
@@ -1461,6 +1465,7 @@
this.memtagMode = in.readInt();
this.nativeHeapZeroInitialized = in.readInt();
this.requestRawExternalStorageAccess = sForBoolean.unparcel(in);
+ this.mLocaleConfigRes = in.readInt();
assignDerivedFields();
}
@@ -2279,6 +2284,11 @@
return nativeHeapZeroInitialized;
}
+ @Override
+ public int getLocaleConfigRes() {
+ return mLocaleConfigRes;
+ }
+
@Nullable
@Override
public Boolean hasRequestRawExternalStorageAccess() {
@@ -2936,4 +2946,10 @@
resetEnabledSettingsOnAppDataCleared);
return this;
}
+
+ @Override
+ public ParsingPackageImpl setLocaleConfigRes(int value) {
+ mLocaleConfigRes = value;
+ return this;
+ }
}
diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java
index c8113ef..a5e98d6 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageRead.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java
@@ -343,4 +343,11 @@
* @see R.styleable#AndroidManifestApplication_resetEnabledSettingsOnAppDataCleared
*/
boolean isResetEnabledSettingsOnAppDataCleared();
+
+ /**
+ * The resource ID used to provide the application's locales configuration.
+ *
+ * @see R.styleable#AndroidManifestApplication_localeConfig
+ */
+ int getLocaleConfigRes();
}
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index e02eb7c..795e7ef 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -2339,6 +2339,7 @@
.setTheme(resId(R.styleable.AndroidManifestApplication_theme, sa))
.setDataExtractionRules(
resId(R.styleable.AndroidManifestApplication_dataExtractionRules, sa))
+ .setLocaleConfigRes(resId(R.styleable.AndroidManifestApplication_localeConfig, sa))
// Strings
.setClassLoaderName(string(R.styleable.AndroidManifestApplication_classLoader, sa))
.setRequiredAccountType(string(R.styleable.AndroidManifestApplication_requiredAccountType, sa))
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/hdmi/DeviceFeatures.java b/core/java/android/hardware/hdmi/DeviceFeatures.java
new file mode 100644
index 0000000..dc530ff
--- /dev/null
+++ b/core/java/android/hardware/hdmi/DeviceFeatures.java
@@ -0,0 +1,395 @@
+/*
+ * 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.hardware.hdmi;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * Immutable class that stores support status for features in the [Device Features] operand.
+ * Each feature may be supported, be not supported, or have an unknown support status.
+ *
+ * @hide
+ */
+public class DeviceFeatures {
+
+ @IntDef({
+ FEATURE_NOT_SUPPORTED,
+ FEATURE_SUPPORTED,
+ FEATURE_SUPPORT_UNKNOWN
+ })
+ public @interface FeatureSupportStatus {};
+
+ public static final int FEATURE_NOT_SUPPORTED = 0;
+ public static final int FEATURE_SUPPORTED = 1;
+ public static final int FEATURE_SUPPORT_UNKNOWN = 2;
+
+ /**
+ * Instance representing no knowledge of any feature's support.
+ */
+ @NonNull
+ public static final DeviceFeatures ALL_FEATURES_SUPPORT_UNKNOWN =
+ new Builder(FEATURE_SUPPORT_UNKNOWN).build();
+
+ /**
+ * Instance representing no support for any feature.
+ */
+ @NonNull
+ public static final DeviceFeatures NO_FEATURES_SUPPORTED =
+ new Builder(FEATURE_NOT_SUPPORTED).build();
+
+ @FeatureSupportStatus private final int mRecordTvScreenSupport;
+ @FeatureSupportStatus private final int mSetOsdStringSupport;
+ @FeatureSupportStatus private final int mDeckControlSupport;
+ @FeatureSupportStatus private final int mSetAudioRateSupport;
+ @FeatureSupportStatus private final int mArcTxSupport;
+ @FeatureSupportStatus private final int mArcRxSupport;
+ @FeatureSupportStatus private final int mSetAudioVolumeLevelSupport;
+
+ private DeviceFeatures(@NonNull Builder builder) {
+ this.mRecordTvScreenSupport = builder.mRecordTvScreenSupport;
+ this.mSetOsdStringSupport = builder.mOsdStringSupport;
+ this.mDeckControlSupport = builder.mDeckControlSupport;
+ this.mSetAudioRateSupport = builder.mSetAudioRateSupport;
+ this.mArcTxSupport = builder.mArcTxSupport;
+ this.mArcRxSupport = builder.mArcRxSupport;
+ this.mSetAudioVolumeLevelSupport = builder.mSetAudioVolumeLevelSupport;
+ }
+
+ /**
+ * Converts an instance to a builder.
+ */
+ public Builder toBuilder() {
+ return new Builder(this);
+ }
+
+ /**
+ * Constructs an instance from a [Device Features] operand.
+ *
+ * Bit 7 of [Device Features] is currently ignored. It indicates whether the operand spans more
+ * than one byte, but only the first byte contains information as of CEC 2.0.
+ *
+ * @param deviceFeaturesOperand The [Device Features] operand to parse.
+ * @return Instance representing the [Device Features] operand.
+ */
+ @NonNull
+ public static DeviceFeatures fromOperand(@NonNull byte[] deviceFeaturesOperand) {
+ Builder builder = new Builder(FEATURE_SUPPORT_UNKNOWN);
+
+ // Read feature support status from the bits of [Device Features]
+ if (deviceFeaturesOperand.length >= 1) {
+ byte b = deviceFeaturesOperand[0];
+ builder
+ .setRecordTvScreenSupport(bitToFeatureSupportStatus(((b >> 6) & 1) == 1))
+ .setSetOsdStringSupport(bitToFeatureSupportStatus(((b >> 5) & 1) == 1))
+ .setDeckControlSupport(bitToFeatureSupportStatus(((b >> 4) & 1) == 1))
+ .setSetAudioRateSupport(bitToFeatureSupportStatus(((b >> 3) & 1) == 1))
+ .setArcTxSupport(bitToFeatureSupportStatus(((b >> 2) & 1) == 1))
+ .setArcRxSupport(bitToFeatureSupportStatus(((b >> 1) & 1) == 1))
+ .setSetAudioVolumeLevelSupport(bitToFeatureSupportStatus((b & 1) == 1));
+ }
+ return builder.build();
+ }
+
+ /**
+ * Returns the input that is not {@link #FEATURE_SUPPORT_UNKNOWN}. If neither is equal to
+ * {@link #FEATURE_SUPPORT_UNKNOWN}, returns the second input.
+ */
+ private static @FeatureSupportStatus int updateFeatureSupportStatus(
+ @FeatureSupportStatus int oldStatus, @FeatureSupportStatus int newStatus) {
+ if (newStatus == FEATURE_SUPPORT_UNKNOWN) {
+ return oldStatus;
+ } else {
+ return newStatus;
+ }
+ }
+
+ /**
+ * Returns the [Device Features] operand corresponding to this instance.
+ * {@link #FEATURE_SUPPORT_UNKNOWN} maps to 0, indicating no support.
+ *
+ * As of CEC 2.0, the returned byte array will always be of length 1.
+ */
+ @NonNull
+ public byte[] toOperand() {
+ byte result = 0;
+
+ if (mRecordTvScreenSupport == FEATURE_SUPPORTED) result |= (byte) (1 << 6);
+ if (mSetOsdStringSupport == FEATURE_SUPPORTED) result = (byte) (1 << 5);
+ if (mDeckControlSupport == FEATURE_SUPPORTED) result = (byte) (1 << 4);
+ if (mSetAudioRateSupport == FEATURE_SUPPORTED) result = (byte) (1 << 3);
+ if (mArcTxSupport == FEATURE_SUPPORTED) result = (byte) (1 << 2);
+ if (mArcRxSupport == FEATURE_SUPPORTED) result = (byte) (1 << 1);
+ if (mSetAudioVolumeLevelSupport == FEATURE_SUPPORTED) result = (byte) 1;
+
+ return new byte[]{ result };
+ }
+
+ @FeatureSupportStatus
+ private static int bitToFeatureSupportStatus(boolean bit) {
+ return bit ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED;
+ }
+
+ /**
+ * Returns whether the device is a TV that supports <Record TV Screen>.
+ */
+ @FeatureSupportStatus
+ public int getRecordTvScreenSupport() {
+ return mRecordTvScreenSupport;
+ }
+
+ /**
+ * Returns whether the device is a TV that supports <Set OSD String>.
+ */
+ @FeatureSupportStatus
+ public int getSetOsdStringSupport() {
+ return mSetOsdStringSupport;
+ }
+
+ /**
+ * Returns whether the device supports being controlled by Deck Control.
+ */
+ @FeatureSupportStatus
+ public int getDeckControlSupport() {
+ return mDeckControlSupport;
+ }
+
+ /**
+ * Returns whether the device is a Source that supports <Set Audio Rate>.
+ */
+ @FeatureSupportStatus
+ public int getSetAudioRateSupport() {
+ return mSetAudioRateSupport;
+ }
+
+ /**
+ * Returns whether the device is a Sink that supports ARC Tx.
+ */
+ @FeatureSupportStatus
+ public int getArcTxSupport() {
+ return mArcTxSupport;
+ }
+
+ /**
+ * Returns whether the device is a Source that supports ARC Rx.
+ */
+ @FeatureSupportStatus
+ public int getArcRxSupport() {
+ return mArcRxSupport;
+ }
+
+ /**
+ * Returns whether the device supports <Set Audio Volume Level>.
+ */
+ @FeatureSupportStatus
+ public int getSetAudioVolumeLevelSupport() {
+ return mSetAudioVolumeLevelSupport;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof DeviceFeatures)) {
+ return false;
+ }
+
+ DeviceFeatures other = (DeviceFeatures) obj;
+ return mRecordTvScreenSupport == other.mRecordTvScreenSupport
+ && mSetOsdStringSupport == other.mSetOsdStringSupport
+ && mDeckControlSupport == other.mDeckControlSupport
+ && mSetAudioRateSupport == other.mSetAudioRateSupport
+ && mArcTxSupport == other.mArcTxSupport
+ && mArcRxSupport == other.mArcRxSupport
+ && mSetAudioVolumeLevelSupport == other.mSetAudioVolumeLevelSupport;
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(
+ mRecordTvScreenSupport,
+ mSetOsdStringSupport,
+ mDeckControlSupport,
+ mSetAudioRateSupport,
+ mArcTxSupport,
+ mArcRxSupport,
+ mSetAudioVolumeLevelSupport
+ );
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder s = new StringBuilder();
+ s.append("Device features: ");
+ s.append("record_tv_screen: ")
+ .append(featureSupportStatusToString(mRecordTvScreenSupport)).append(" ");
+ s.append("set_osd_string: ")
+ .append(featureSupportStatusToString(mSetOsdStringSupport)).append(" ");
+ s.append("deck_control: ")
+ .append(featureSupportStatusToString(mDeckControlSupport)).append(" ");
+ s.append("set_audio_rate: ")
+ .append(featureSupportStatusToString(mSetAudioRateSupport)).append(" ");
+ s.append("arc_tx: ")
+ .append(featureSupportStatusToString(mArcTxSupport)).append(" ");
+ s.append("arc_rx: ")
+ .append(featureSupportStatusToString(mArcRxSupport)).append(" ");
+ s.append("set_audio_volume_level: ")
+ .append(featureSupportStatusToString(mSetAudioVolumeLevelSupport)).append(" ");
+ return s.toString();
+ }
+
+ @NonNull
+ private static String featureSupportStatusToString(@FeatureSupportStatus int status) {
+ switch (status) {
+ case FEATURE_SUPPORTED:
+ return "Y";
+ case FEATURE_NOT_SUPPORTED:
+ return "N";
+ case FEATURE_SUPPORT_UNKNOWN:
+ default:
+ return "?";
+ }
+ }
+
+ /**
+ * Builder for {@link DeviceFeatures} instances.
+ */
+ public static final class Builder {
+ @FeatureSupportStatus private int mRecordTvScreenSupport;
+ @FeatureSupportStatus private int mOsdStringSupport;
+ @FeatureSupportStatus private int mDeckControlSupport;
+ @FeatureSupportStatus private int mSetAudioRateSupport;
+ @FeatureSupportStatus private int mArcTxSupport;
+ @FeatureSupportStatus private int mArcRxSupport;
+ @FeatureSupportStatus private int mSetAudioVolumeLevelSupport;
+
+ private Builder(@FeatureSupportStatus int defaultFeatureSupportStatus) {
+ mRecordTvScreenSupport = defaultFeatureSupportStatus;
+ mOsdStringSupport = defaultFeatureSupportStatus;
+ mDeckControlSupport = defaultFeatureSupportStatus;
+ mSetAudioRateSupport = defaultFeatureSupportStatus;
+ mArcTxSupport = defaultFeatureSupportStatus;
+ mArcRxSupport = defaultFeatureSupportStatus;
+ mSetAudioVolumeLevelSupport = defaultFeatureSupportStatus;
+ }
+
+ private Builder(DeviceFeatures info) {
+ mRecordTvScreenSupport = info.getRecordTvScreenSupport();
+ mOsdStringSupport = info.getSetOsdStringSupport();
+ mDeckControlSupport = info.getDeckControlSupport();
+ mSetAudioRateSupport = info.getSetAudioRateSupport();
+ mArcTxSupport = info.getArcTxSupport();
+ mArcRxSupport = info.getArcRxSupport();
+ mSetAudioVolumeLevelSupport = info.getSetAudioVolumeLevelSupport();
+ }
+
+ /**
+ * Creates a new {@link DeviceFeatures} object.
+ */
+ public DeviceFeatures build() {
+ return new DeviceFeatures(this);
+ }
+
+ /**
+ * Sets the value for {@link #getRecordTvScreenSupport()}.
+ */
+ @NonNull
+ public Builder setRecordTvScreenSupport(@FeatureSupportStatus int recordTvScreenSupport) {
+ mRecordTvScreenSupport = recordTvScreenSupport;
+ return this;
+ }
+
+ /**
+ * Sets the value for {@link #getSetOsdStringSupport()}.
+ */
+ @NonNull
+ public Builder setSetOsdStringSupport(@FeatureSupportStatus int setOsdStringSupport) {
+ mOsdStringSupport = setOsdStringSupport;
+ return this;
+ }
+
+ /**
+ * Sets the value for {@link #getDeckControlSupport()}.
+ */
+ @NonNull
+ public Builder setDeckControlSupport(@FeatureSupportStatus int deckControlSupport) {
+ mDeckControlSupport = deckControlSupport;
+ return this;
+ }
+
+ /**
+ * Sets the value for {@link #getSetAudioRateSupport()}.
+ */
+ @NonNull
+ public Builder setSetAudioRateSupport(@FeatureSupportStatus int setAudioRateSupport) {
+ mSetAudioRateSupport = setAudioRateSupport;
+ return this;
+ }
+
+ /**
+ * Sets the value for {@link #getArcTxSupport()}.
+ */
+ @NonNull
+ public Builder setArcTxSupport(@FeatureSupportStatus int arcTxSupport) {
+ mArcTxSupport = arcTxSupport;
+ return this;
+ }
+
+ /**
+ * Sets the value for {@link #getArcRxSupport()}.
+ */
+ @NonNull
+ public Builder setArcRxSupport(@FeatureSupportStatus int arcRxSupport) {
+ mArcRxSupport = arcRxSupport;
+ return this;
+ }
+
+ /**
+ * Sets the value for {@link #getSetAudioRateSupport()}.
+ */
+ @NonNull
+ public Builder setSetAudioVolumeLevelSupport(
+ @FeatureSupportStatus int setAudioVolumeLevelSupport) {
+ mSetAudioVolumeLevelSupport = setAudioVolumeLevelSupport;
+ return this;
+ }
+
+ /**
+ * Updates all fields with those in a 'new' instance of {@link DeviceFeatures}.
+ * All fields are replaced with those in the new instance, except when the field is
+ * {@link #FEATURE_SUPPORT_UNKNOWN} in the new instance.
+ */
+ @NonNull
+ public Builder update(DeviceFeatures newDeviceFeatures) {
+ mRecordTvScreenSupport = updateFeatureSupportStatus(mRecordTvScreenSupport,
+ newDeviceFeatures.getRecordTvScreenSupport());
+ mOsdStringSupport = updateFeatureSupportStatus(mOsdStringSupport,
+ newDeviceFeatures.getSetOsdStringSupport());
+ mDeckControlSupport = updateFeatureSupportStatus(mDeckControlSupport,
+ newDeviceFeatures.getDeckControlSupport());
+ mSetAudioRateSupport = updateFeatureSupportStatus(mSetAudioRateSupport,
+ newDeviceFeatures.getSetAudioRateSupport());
+ mArcTxSupport = updateFeatureSupportStatus(mArcTxSupport,
+ newDeviceFeatures.getArcTxSupport());
+ mArcRxSupport = updateFeatureSupportStatus(mArcRxSupport,
+ newDeviceFeatures.getArcRxSupport());
+ mSetAudioVolumeLevelSupport = updateFeatureSupportStatus(mSetAudioVolumeLevelSupport,
+ newDeviceFeatures.getSetAudioVolumeLevelSupport());
+ return this;
+ }
+ }
+}
diff --git a/core/java/android/hardware/hdmi/HdmiDeviceInfo.java b/core/java/android/hardware/hdmi/HdmiDeviceInfo.java
index c0b177d..e3ea4e2 100644
--- a/core/java/android/hardware/hdmi/HdmiDeviceInfo.java
+++ b/core/java/android/hardware/hdmi/HdmiDeviceInfo.java
@@ -68,6 +68,9 @@
*/
public static final int ADDR_INTERNAL = 0;
+ /** Invalid or uninitialized logical address */
+ public static final int ADDR_INVALID = -1;
+
/**
* Physical address used to indicate the source comes from internal device. The physical address
* of TV(0) is used.
@@ -83,7 +86,15 @@
/** Invalid device ID */
public static final int ID_INVALID = 0xFFFF;
- /** Device info used to indicate an inactivated device. */
+ /** Unknown vendor ID */
+ public static final int VENDOR_ID_UNKNOWN = 0xFFFFFF;
+
+ /**
+ * Instance that represents an inactive device.
+ * Can be passed to an input change listener to indicate that the active source
+ * yielded its status, allowing the listener to take an appropriate action such as
+ * switching to another input.
+ */
public static final HdmiDeviceInfo INACTIVE_DEVICE = new HdmiDeviceInfo();
private static final int HDMI_DEVICE_TYPE_CEC = 0;
@@ -109,10 +120,11 @@
private final int mLogicalAddress;
private final int mDeviceType;
@HdmiCecVersion
- private final int mHdmiCecVersion;
+ private final int mCecVersion;
private final int mVendorId;
private final String mDisplayName;
private final int mDevicePowerStatus;
+ private final DeviceFeatures mDeviceFeatures;
// MHL only parameters.
private final int mDeviceId;
@@ -137,14 +149,22 @@
int powerStatus = source.readInt();
String displayName = source.readString();
int cecVersion = source.readInt();
- return new HdmiDeviceInfo(logicalAddress, physicalAddress, portId,
- deviceType, vendorId, displayName, powerStatus, cecVersion);
+ return cecDeviceBuilder()
+ .setLogicalAddress(logicalAddress)
+ .setPhysicalAddress(physicalAddress)
+ .setPortId(portId)
+ .setDeviceType(deviceType)
+ .setVendorId(vendorId)
+ .setDisplayName(displayName)
+ .setDevicePowerStatus(powerStatus)
+ .setCecVersion(cecVersion)
+ .build();
case HDMI_DEVICE_TYPE_MHL:
int deviceId = source.readInt();
int adopterId = source.readInt();
- return new HdmiDeviceInfo(physicalAddress, portId, adopterId, deviceId);
+ return mhlDevice(physicalAddress, portId, adopterId, deviceId);
case HDMI_DEVICE_TYPE_HARDWARE:
- return new HdmiDeviceInfo(physicalAddress, portId);
+ return hardwarePort(physicalAddress, portId);
case HDMI_DEVICE_TYPE_INACTIVE:
return HdmiDeviceInfo.INACTIVE_DEVICE;
default:
@@ -159,97 +179,82 @@
};
/**
- * Constructor. Used to initialize the instance for CEC device.
+ * Constructor. Initializes the instance representing an inactive device.
+ * Can be passed to an input change listener to indicate that the active source
+ * yielded its status, allowing the listener to take an appropriate action such as
+ * switching to another input.
*
- * @param logicalAddress logical address of HDMI-CEC device
- * @param physicalAddress physical address of HDMI-CEC device
- * @param portId HDMI port ID (1 for HDMI1)
- * @param deviceType type of device
- * @param vendorId vendor id of device. Used for vendor specific command.
- * @param displayName name of device
- * @param powerStatus device power status
- * @hide
+ * @deprecated Use {@link #INACTIVE_DEVICE} instead.
*/
- public HdmiDeviceInfo(int logicalAddress, int physicalAddress, int portId, int deviceType,
- int vendorId, String displayName, int powerStatus, int hdmiCecVersion) {
- mHdmiDeviceType = HDMI_DEVICE_TYPE_CEC;
- mPhysicalAddress = physicalAddress;
- mPortId = portId;
+ @Deprecated
+ public HdmiDeviceInfo() {
+ mHdmiDeviceType = HDMI_DEVICE_TYPE_INACTIVE;
+ mPhysicalAddress = PATH_INVALID;
+ mId = ID_INVALID;
- mId = idForCecDevice(logicalAddress);
- mLogicalAddress = logicalAddress;
- mDeviceType = deviceType;
- mHdmiCecVersion = hdmiCecVersion;
- mVendorId = vendorId;
- mDevicePowerStatus = powerStatus;
- mDisplayName = displayName;
-
- mDeviceId = -1;
- mAdopterId = -1;
- }
-
- /**
- * Constructor. Used to initialize the instance for CEC device.
- *
- * @param logicalAddress logical address of HDMI-CEC device
- * @param physicalAddress physical address of HDMI-CEC device
- * @param portId HDMI port ID (1 for HDMI1)
- * @param deviceType type of device
- * @param vendorId vendor id of device. Used for vendor specific command.
- * @param displayName name of device
- * @param powerStatus device power status
- * @hide
- */
- public HdmiDeviceInfo(int logicalAddress, int physicalAddress, int portId, int deviceType,
- int vendorId, String displayName, int powerStatus) {
- this(logicalAddress, physicalAddress, portId, deviceType,
- vendorId, displayName, powerStatus, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- }
-
- /**
- * Constructor. Used to initialize the instance for CEC device.
- *
- * @param logicalAddress logical address of HDMI-CEC device
- * @param physicalAddress physical address of HDMI-CEC device
- * @param portId HDMI port ID (1 for HDMI1)
- * @param deviceType type of device
- * @param vendorId vendor id of device. Used for vendor specific command.
- * @param displayName name of device
- * @hide
- */
- public HdmiDeviceInfo(int logicalAddress, int physicalAddress, int portId, int deviceType,
- int vendorId, String displayName) {
- this(logicalAddress, physicalAddress, portId, deviceType,
- vendorId, displayName, HdmiControlManager.POWER_STATUS_UNKNOWN,
- HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- }
-
- /**
- * Constructor. Used to initialize the instance for device representing hardware port.
- *
- * @param physicalAddress physical address of the port
- * @param portId HDMI port ID (1 for HDMI1)
- * @hide
- */
- public HdmiDeviceInfo(int physicalAddress, int portId) {
- mHdmiDeviceType = HDMI_DEVICE_TYPE_HARDWARE;
- mPhysicalAddress = physicalAddress;
- mPortId = portId;
-
- mId = idForHardware(portId);
- mLogicalAddress = -1;
- mDeviceType = DEVICE_RESERVED;
- mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B;
- mVendorId = 0;
+ mLogicalAddress = ADDR_INVALID;
+ mDeviceType = DEVICE_INACTIVE;
+ mCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B;
+ mPortId = PORT_INVALID;
mDevicePowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN;
- mDisplayName = "HDMI" + portId;
+ mDisplayName = "Inactive";
+ mVendorId = 0;
+ mDeviceFeatures = DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN;
mDeviceId = -1;
mAdopterId = -1;
}
/**
- * Constructor. Used to initialize the instance for MHL device.
+ * Converts an instance to a builder.
+ *
+ * @hide
+ */
+ public Builder toBuilder() {
+ return new Builder(this);
+ }
+
+ private HdmiDeviceInfo(Builder builder) {
+ this.mHdmiDeviceType = builder.mHdmiDeviceType;
+ this.mPhysicalAddress = builder.mPhysicalAddress;
+ this.mPortId = builder.mPortId;
+ this.mLogicalAddress = builder.mLogicalAddress;
+ this.mDeviceType = builder.mDeviceType;
+ this.mCecVersion = builder.mCecVersion;
+ this.mVendorId = builder.mVendorId;
+ this.mDisplayName = builder.mDisplayName;
+ this.mDevicePowerStatus = builder.mDevicePowerStatus;
+ this.mDeviceFeatures = builder.mDeviceFeatures;
+ this.mDeviceId = builder.mDeviceId;
+ this.mAdopterId = builder.mAdopterId;
+
+ switch (mHdmiDeviceType) {
+ case HDMI_DEVICE_TYPE_MHL:
+ this.mId = idForMhlDevice(mPortId);
+ break;
+ case HDMI_DEVICE_TYPE_HARDWARE:
+ this.mId = idForHardware(mPortId);
+ break;
+ case HDMI_DEVICE_TYPE_CEC:
+ this.mId = idForCecDevice(mLogicalAddress);
+ break;
+ case HDMI_DEVICE_TYPE_INACTIVE:
+ default:
+ this.mId = ID_INVALID;
+ }
+ }
+
+ /**
+ * Creates a Builder for an {@link HdmiDeviceInfo} representing a CEC device.
+ *
+ * @hide
+ */
+ public static Builder cecDeviceBuilder() {
+ return new Builder(HDMI_DEVICE_TYPE_CEC);
+ }
+
+ /**
+ * Creates an {@link HdmiDeviceInfo} representing an MHL device.
*
* @param physicalAddress physical address of HDMI device
* @param portId portId HDMI port ID (1 for HDMI1)
@@ -257,44 +262,32 @@
* @param deviceId device id of MHL
* @hide
*/
- public HdmiDeviceInfo(int physicalAddress, int portId, int adopterId, int deviceId) {
- mHdmiDeviceType = HDMI_DEVICE_TYPE_MHL;
- mPhysicalAddress = physicalAddress;
- mPortId = portId;
-
- mId = idForMhlDevice(portId);
- mLogicalAddress = -1;
- mDeviceType = DEVICE_RESERVED;
- mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B;
- mVendorId = 0;
- mDevicePowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN;
- mDisplayName = "Mobile";
-
- mDeviceId = adopterId;
- mAdopterId = deviceId;
+ public static HdmiDeviceInfo mhlDevice(
+ int physicalAddress, int portId, int adopterId, int deviceId) {
+ return new Builder(HDMI_DEVICE_TYPE_MHL)
+ .setPhysicalAddress(physicalAddress)
+ .setPortId(portId)
+ .setVendorId(0)
+ .setDisplayName("Mobile")
+ .setDeviceId(adopterId)
+ .setAdopterId(deviceId)
+ .build();
}
/**
- * Constructor. Used to initialize the instance representing an inactivated device.
- * Can be passed input change listener to indicate the active source yielded
- * its status, hence the listener should take an appropriate action such as
- * switching to other input.
+ * Creates an {@link HdmiDeviceInfo} representing a hardware port.
+ *
+ * @param physicalAddress physical address of the port
+ * @param portId HDMI port ID (1 for HDMI1)
+ * @hide
*/
- public HdmiDeviceInfo() {
- mHdmiDeviceType = HDMI_DEVICE_TYPE_INACTIVE;
- mPhysicalAddress = PATH_INVALID;
- mId = ID_INVALID;
-
- mLogicalAddress = -1;
- mDeviceType = DEVICE_INACTIVE;
- mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B;
- mPortId = PORT_INVALID;
- mDevicePowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN;
- mDisplayName = "Inactive";
- mVendorId = 0;
-
- mDeviceId = -1;
- mAdopterId = -1;
+ public static HdmiDeviceInfo hardwarePort(int physicalAddress, int portId) {
+ return new Builder(HDMI_DEVICE_TYPE_HARDWARE)
+ .setPhysicalAddress(physicalAddress)
+ .setPortId(portId)
+ .setVendorId(0)
+ .setDisplayName("HDMI" + portId)
+ .build();
}
/**
@@ -305,6 +298,15 @@
}
/**
+ * Returns the CEC features that this device supports.
+ *
+ * @hide
+ */
+ public DeviceFeatures getDeviceFeatures() {
+ return mDeviceFeatures;
+ }
+
+ /**
* Returns the id to be used for CEC device.
*
* @param address logical address of CEC device
@@ -372,7 +374,7 @@
*/
@HdmiCecVersion
public int getCecVersion() {
- return mHdmiCecVersion;
+ return mCecVersion;
}
/**
@@ -485,7 +487,7 @@
dest.writeInt(mVendorId);
dest.writeInt(mDevicePowerStatus);
dest.writeString(mDisplayName);
- dest.writeInt(mHdmiCecVersion);
+ dest.writeInt(mCecVersion);
break;
case HDMI_DEVICE_TYPE_MHL:
dest.writeInt(mDeviceId);
@@ -508,7 +510,7 @@
s.append("logical_address: ").append(String.format("0x%02X", mLogicalAddress));
s.append(" ");
s.append("device_type: ").append(mDeviceType).append(" ");
- s.append("cec_version: ").append(mHdmiCecVersion).append(" ");
+ s.append("cec_version: ").append(mCecVersion).append(" ");
s.append("vendor_id: ").append(mVendorId).append(" ");
s.append("display_name: ").append(mDisplayName).append(" ");
s.append("power_status: ").append(mDevicePowerStatus).append(" ");
@@ -531,6 +533,11 @@
s.append("physical_address: ").append(String.format("0x%04X", mPhysicalAddress));
s.append(" ");
s.append("port_id: ").append(mPortId);
+
+ if (mHdmiDeviceType == HDMI_DEVICE_TYPE_CEC) {
+ s.append("\n ").append(mDeviceFeatures.toString());
+ }
+
return s.toString();
}
@@ -546,7 +553,7 @@
&& mPortId == other.mPortId
&& mLogicalAddress == other.mLogicalAddress
&& mDeviceType == other.mDeviceType
- && mHdmiCecVersion == other.mHdmiCecVersion
+ && mCecVersion == other.mCecVersion
&& mVendorId == other.mVendorId
&& mDevicePowerStatus == other.mDevicePowerStatus
&& mDisplayName.equals(other.mDisplayName)
@@ -562,11 +569,180 @@
mPortId,
mLogicalAddress,
mDeviceType,
- mHdmiCecVersion,
+ mCecVersion,
mVendorId,
mDevicePowerStatus,
mDisplayName,
mDeviceId,
mAdopterId);
}
+
+ /**
+ * Builder for {@link HdmiDeviceInfo} instances.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ // Required parameters
+ private final int mHdmiDeviceType;
+
+ // Common parameters
+ private int mPhysicalAddress = PATH_INVALID;
+ private int mPortId = PORT_INVALID;
+
+ // CEC parameters
+ private int mLogicalAddress = ADDR_INVALID;
+ private int mDeviceType = DEVICE_RESERVED;
+ @HdmiCecVersion
+ private int mCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B;
+ private int mVendorId = VENDOR_ID_UNKNOWN;
+ private String mDisplayName = "";
+ private int mDevicePowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN;
+ private DeviceFeatures mDeviceFeatures;
+
+ // MHL parameters
+ private int mDeviceId = -1;
+ private int mAdopterId = -1;
+
+ private Builder(int hdmiDeviceType) {
+ mHdmiDeviceType = hdmiDeviceType;
+ if (hdmiDeviceType == HDMI_DEVICE_TYPE_CEC) {
+ mDeviceFeatures = DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN;
+ } else {
+ mDeviceFeatures = DeviceFeatures.NO_FEATURES_SUPPORTED;
+ }
+ }
+
+ private Builder(@NonNull HdmiDeviceInfo hdmiDeviceInfo) {
+ mHdmiDeviceType = hdmiDeviceInfo.mHdmiDeviceType;
+ mPhysicalAddress = hdmiDeviceInfo.mPhysicalAddress;
+ mPortId = hdmiDeviceInfo.mPortId;
+ mLogicalAddress = hdmiDeviceInfo.mLogicalAddress;
+ mDeviceType = hdmiDeviceInfo.mDeviceType;
+ mCecVersion = hdmiDeviceInfo.mCecVersion;
+ mVendorId = hdmiDeviceInfo.mVendorId;
+ mDisplayName = hdmiDeviceInfo.mDisplayName;
+ mDevicePowerStatus = hdmiDeviceInfo.mDevicePowerStatus;
+ mDeviceId = hdmiDeviceInfo.mDeviceId;
+ mAdopterId = hdmiDeviceInfo.mAdopterId;
+ mDeviceFeatures = hdmiDeviceInfo.mDeviceFeatures;
+ }
+
+ /**
+ * Create a new {@link HdmiDeviceInfo} object.
+ */
+ @NonNull
+ public HdmiDeviceInfo build() {
+ return new HdmiDeviceInfo(this);
+ }
+
+ /**
+ * Sets the value for {@link #getPhysicalAddress()}.
+ */
+ @NonNull
+ public Builder setPhysicalAddress(int physicalAddress) {
+ mPhysicalAddress = physicalAddress;
+ return this;
+ }
+
+ /**
+ * Sets the value for {@link #getPortId()}.
+ */
+ @NonNull
+ public Builder setPortId(int portId) {
+ mPortId = portId;
+ return this;
+ }
+
+ /**
+ * Sets the value for {@link #getLogicalAddress()}.
+ */
+ @NonNull
+ public Builder setLogicalAddress(int logicalAddress) {
+ mLogicalAddress = logicalAddress;
+ return this;
+ }
+
+ /**
+ * Sets the value for {@link #getDeviceType()}.
+ */
+ @NonNull
+ public Builder setDeviceType(int deviceType) {
+ mDeviceType = deviceType;
+ return this;
+ }
+
+ /**
+ * Sets the value for {@link #getCecVersion()}.
+ */
+ @NonNull
+ public Builder setCecVersion(int hdmiCecVersion) {
+ mCecVersion = hdmiCecVersion;
+ return this;
+ }
+
+ /**
+ * Sets the value for {@link #getVendorId()}.
+ */
+ @NonNull
+ public Builder setVendorId(int vendorId) {
+ mVendorId = vendorId;
+ return this;
+ }
+
+ /**
+ * Sets the value for {@link #getDisplayName()}.
+ */
+ @NonNull
+ public Builder setDisplayName(@NonNull String displayName) {
+ mDisplayName = displayName;
+ return this;
+ }
+
+ /**
+ * Sets the value for {@link #getDevicePowerStatus()}.
+ */
+ @NonNull
+ public Builder setDevicePowerStatus(int devicePowerStatus) {
+ mDevicePowerStatus = devicePowerStatus;
+ return this;
+ }
+
+ /**
+ * Sets the value for {@link #getDeviceFeatures()}.
+ */
+ @NonNull
+ public Builder setDeviceFeatures(DeviceFeatures deviceFeatures) {
+ this.mDeviceFeatures = deviceFeatures;
+ return this;
+ }
+
+ /**
+ * Sets the value for {@link #getDeviceId()}.
+ */
+ @NonNull
+ public Builder setDeviceId(int deviceId) {
+ mDeviceId = deviceId;
+ return this;
+ }
+
+ /**
+ * Sets the value for {@link #getAdopterId()}.
+ */
+ @NonNull
+ public Builder setAdopterId(int adopterId) {
+ mAdopterId = adopterId;
+ return this;
+ }
+
+ /**
+ * Updates the value for {@link #getDeviceFeatures()} with a new set of device features.
+ * New information overrides the old, except when feature support was unknown.
+ */
+ @NonNull
+ public Builder updateDeviceFeatures(DeviceFeatures deviceFeatures) {
+ mDeviceFeatures = mDeviceFeatures.toBuilder().update(deviceFeatures).build();
+ return this;
+ }
+ }
}
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/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/provider/Settings.java b/core/java/android/provider/Settings.java
index 72e2863..5cdb1fd 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10525,7 +10525,7 @@
DEVICE_STATE_ROTATION_LOCK_UNLOCKED,
})
@Retention(RetentionPolicy.SOURCE)
- @interface DeviceStateRotationLockSetting {
+ public @interface DeviceStateRotationLockSetting {
}
/**
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/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/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index cd5d0a1..5a66e9a 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -62,6 +62,7 @@
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;
@@ -174,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;
@@ -226,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;
@@ -290,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 {
@@ -695,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";
}
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/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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 777ddaa..d6bbe71 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3595,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 -->
<!-- ==================================== -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 2267781..d2dc600 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -9665,4 +9665,11 @@
<attr name="iconfactoryBadgeSize" format="dimension"/>
<!-- Perceptual luminance of a color, in accessibility friendly color space. From 0 to 100. -->
<attr name="lStar" format="float"/>
-</resources>
+
+ <!-- The attributes of the {@code <locale>} tag within {@code <locale-config>}. -->
+ <declare-styleable name="LocaleConfig_Locale">
+ <!-- The <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 language tag</a>
+ of the supported locale. {@link android.app.LocaleConfig} -->
+ <attr name="name" />
+ </declare-styleable>
+ </resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index db24475..e055749 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1519,6 +1519,9 @@
<!-- An XML resource with the application's Network Security Config. -->
<attr name="networkSecurityConfig" format="reference" />
+ <!-- An XML resource with the application's {@link android.app.LocaleConfig} -->
+ <attr name="localeConfig" format="reference" />
+
<!-- When an application is partitioned into splits, this is the name of the
split that contains the defined component. -->
<attr name="splitName" format="string" />
@@ -1801,6 +1804,7 @@
<attr name="maxAspectRatio" />
<attr name="minAspectRatio" />
<attr name="networkSecurityConfig" />
+ <attr name="localeConfig" />
<!-- Declare the category of this app. Categories are used to cluster multiple apps
together into meaningful groups, such as when summarizing battery, network, or
disk usage. Apps should only define this value when they fit well into one of
@@ -2825,6 +2829,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/public.xml b/core/res/res/values/public.xml
index 5fa7409..dfeeccf 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3253,6 +3253,7 @@
<public name="showClockAndComplications" />
<!-- @hide @SystemApi -->
<public name="gameSessionService" />
+ <public name="localeConfig" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01de0000">
@@ -3317,8 +3318,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 0acd4bf..4e77563 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3654,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/hdmitests/src/android/hardware/hdmi/DeviceFeaturesTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/DeviceFeaturesTest.java
new file mode 100644
index 0000000..875ab38
--- /dev/null
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/DeviceFeaturesTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.hdmi;
+
+import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED;
+import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED;
+import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORT_UNKNOWN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import com.google.common.testing.EqualsTester;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link DeviceFeatures} */
+@RunWith(JUnit4.class)
+@SmallTest
+public class DeviceFeaturesTest {
+
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN)
+ .addEqualityGroup(DeviceFeatures.NO_FEATURES_SUPPORTED)
+ .addEqualityGroup(
+ DeviceFeatures.fromOperand(
+ new byte[]{(byte) 0b0111_0000}),
+ DeviceFeatures.fromOperand(
+ new byte[]{(byte) 0b1111_0000}),
+ DeviceFeatures.fromOperand(
+ new byte[]{(byte) 0b1111_0000, (byte) 0b0101_0101}),
+ DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN.toBuilder()
+ .setRecordTvScreenSupport(FEATURE_SUPPORTED)
+ .setSetOsdStringSupport(FEATURE_SUPPORTED)
+ .setDeckControlSupport(FEATURE_SUPPORTED)
+ .setSetAudioRateSupport(FEATURE_NOT_SUPPORTED)
+ .setArcTxSupport(FEATURE_NOT_SUPPORTED)
+ .setArcRxSupport(FEATURE_NOT_SUPPORTED)
+ .setSetAudioVolumeLevelSupport(FEATURE_NOT_SUPPORTED)
+ .build()
+ )
+ .testEquals();
+ }
+
+ @Test
+ public void testDeviceFeaturesOperandConversion() {
+ DeviceFeatures info = DeviceFeatures.fromOperand(
+ new byte[]{(byte) 0b0111_0000});
+
+ assertThat(info.getRecordTvScreenSupport()).isEqualTo(FEATURE_SUPPORTED);
+ assertThat(info.getSetOsdStringSupport()).isEqualTo(FEATURE_SUPPORTED);
+ assertThat(info.getDeckControlSupport()).isEqualTo(FEATURE_SUPPORTED);
+ assertThat(info.getSetAudioRateSupport()).isEqualTo(FEATURE_NOT_SUPPORTED);
+ assertThat(info.getArcTxSupport()).isEqualTo(FEATURE_NOT_SUPPORTED);
+ assertThat(info.getArcRxSupport()).isEqualTo(FEATURE_NOT_SUPPORTED);
+ assertThat(info.getSetAudioVolumeLevelSupport()).isEqualTo(FEATURE_NOT_SUPPORTED);
+
+ assertThat(info.toOperand()).isEqualTo(new byte[]{(byte) 0b0111_0000});
+ }
+
+ @Test
+ public void testUpdate() {
+ DeviceFeatures oldFeatures = DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN.toBuilder()
+ .setRecordTvScreenSupport(FEATURE_SUPPORTED)
+ .setSetOsdStringSupport(FEATURE_SUPPORTED)
+ .setDeckControlSupport(FEATURE_NOT_SUPPORTED)
+ .setSetAudioRateSupport(FEATURE_NOT_SUPPORTED)
+ .setArcTxSupport(FEATURE_SUPPORT_UNKNOWN)
+ .setArcRxSupport(FEATURE_SUPPORT_UNKNOWN)
+ .setSetAudioVolumeLevelSupport(FEATURE_SUPPORT_UNKNOWN)
+ .build();
+
+ DeviceFeatures newFeatures = DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN.toBuilder()
+ .setRecordTvScreenSupport(FEATURE_NOT_SUPPORTED)
+ .setSetOsdStringSupport(FEATURE_SUPPORT_UNKNOWN)
+ .setDeckControlSupport(FEATURE_SUPPORTED)
+ .setSetAudioRateSupport(FEATURE_SUPPORT_UNKNOWN)
+ .setArcTxSupport(FEATURE_SUPPORTED)
+ .setArcRxSupport(FEATURE_NOT_SUPPORTED)
+ .setSetAudioVolumeLevelSupport(FEATURE_SUPPORT_UNKNOWN)
+ .build();
+
+ // Always take the field from newFeatures, unless it's FEATURE_SUPPORT_UNKNOWN
+ DeviceFeatures updatedFeatures = DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN.toBuilder()
+ .setRecordTvScreenSupport(FEATURE_NOT_SUPPORTED)
+ .setSetOsdStringSupport(FEATURE_SUPPORTED)
+ .setDeckControlSupport(FEATURE_SUPPORTED)
+ .setSetAudioRateSupport(FEATURE_NOT_SUPPORTED)
+ .setArcTxSupport(FEATURE_SUPPORTED)
+ .setArcRxSupport(FEATURE_NOT_SUPPORTED)
+ .setSetAudioVolumeLevelSupport(FEATURE_SUPPORT_UNKNOWN)
+ .build();
+
+ assertThat(oldFeatures.toBuilder().update(newFeatures).build()).isEqualTo(updatedFeatures);
+ }
+}
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiDeviceInfoTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiDeviceInfoTest.java
index 4ce072c..5f7468e 100755
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiDeviceInfoTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiDeviceInfoTest.java
@@ -43,44 +43,32 @@
int adopterId = 2;
new EqualsTester()
- .addEqualityGroup(new HdmiDeviceInfo())
+ .addEqualityGroup(HdmiDeviceInfo.INACTIVE_DEVICE)
.addEqualityGroup(
- new HdmiDeviceInfo(phyAddr, portId), new HdmiDeviceInfo(phyAddr, portId))
+ HdmiDeviceInfo.hardwarePort(phyAddr, portId),
+ HdmiDeviceInfo.hardwarePort(phyAddr, portId))
.addEqualityGroup(
- new HdmiDeviceInfo(phyAddr, portId, adopterId, deviceId),
- new HdmiDeviceInfo(phyAddr, portId, adopterId, deviceId))
+ HdmiDeviceInfo.mhlDevice(phyAddr, portId, adopterId, deviceId),
+ HdmiDeviceInfo.mhlDevice(phyAddr, portId, adopterId, deviceId))
.addEqualityGroup(
- new HdmiDeviceInfo(
- logicalAddr, phyAddr, portId, deviceType, vendorId, displayName),
- new HdmiDeviceInfo(
- logicalAddr, phyAddr, portId, deviceType, vendorId, displayName))
- .addEqualityGroup(
- new HdmiDeviceInfo(
- logicalAddr,
- phyAddr,
- portId,
- deviceType,
- vendorId,
- displayName,
- powerStatus),
- new HdmiDeviceInfo(
- logicalAddr,
- phyAddr,
- portId,
- deviceType,
- vendorId,
- displayName,
- powerStatus))
- .addEqualityGroup(
- new HdmiDeviceInfo(
- logicalAddr,
- phyAddr,
- portId,
- deviceType,
- vendorId,
- displayName,
- powerStatus,
- cecVersion))
+ HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(logicalAddr)
+ .setPhysicalAddress(phyAddr)
+ .setPortId(portId)
+ .setDeviceType(deviceType)
+ .setVendorId(vendorId)
+ .setDisplayName(displayName)
+ .setDevicePowerStatus(powerStatus)
+ .setCecVersion(cecVersion).build(),
+ HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(logicalAddr)
+ .setPhysicalAddress(phyAddr)
+ .setPortId(portId)
+ .setDeviceType(deviceType)
+ .setVendorId(vendorId)
+ .setDisplayName(displayName)
+ .setDevicePowerStatus(powerStatus)
+ .setCecVersion(cecVersion).build())
.testEquals();
}
}
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/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/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/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 73f65b0..f8902c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -269,6 +269,8 @@
// touchable or focusable by the user. We also add in the ALT_FOCUSABLE_IM
// flag because we do know that the next window will take input
// focus, so we want to get the IME window up on top of us right away.
+ // Touches will only pass through to the host activity window and will be blocked from
+ // passing to any other windows.
windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -276,9 +278,6 @@
params.token = appToken;
params.packageName = activityInfo.packageName;
params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
- // Setting as trusted overlay to let touches pass through. This is safe because this
- // window is controlled by the system.
- params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
if (!context.getResources().getCompatibilityInfo().supportsScreen()) {
params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
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/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index d3bb008..2deff7b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -26,9 +26,7 @@
import com.android.server.wm.flicker.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Rule
import org.junit.Test
@@ -93,11 +91,7 @@
/** {@inheritDoc} */
@FlakyTest(bugId = 206753786)
@Test
- override fun statusBarLayerRotatesScales() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
- super.statusBarLayerRotatesScales()
- }
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
/**
* Checks [pipApp] window remains visible throughout the animation
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index fa9fbcd..0e73463 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.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.WindowUtils
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.traces.common.FlickerComponentName
@@ -36,7 +35,6 @@
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP
import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -121,11 +119,7 @@
*/
@FlakyTest(bugId = 206753786)
@Test
- override fun statusBarLayerRotatesScales() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
- testSpec.statusBarLayerRotatesScales()
- }
+ override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
/**
* Checks that all parts of the screen are covered at the start and end of the transition
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
index f8a3aff..990872f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
@@ -18,9 +18,7 @@
import android.platform.test.annotations.Presubmit
import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.wm.shell.flicker.helpers.FixedAppHelper
-import org.junit.Assume.assumeFalse
import org.junit.Test
/**
@@ -29,15 +27,6 @@
abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
protected val testApp = FixedAppHelper(instrumentation)
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun statusBarLayerRotatesScales() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
- super.statusBarLayerRotatesScales()
- }
-
/**
* Checks that the pip app window remains inside the display bounds throughout the whole
* animation
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
index 2231d88..2c08b7f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
@@ -24,8 +24,6 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -82,11 +80,7 @@
/** {@inheritDoc} */
@FlakyTest(bugId = 206753786)
@Test
- override fun statusBarLayerRotatesScales() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
- super.statusBarLayerRotatesScales()
- }
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
companion object {
/**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index fcac2c7..e340f4c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -24,9 +24,7 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Rule
import org.junit.Test
@@ -101,11 +99,7 @@
/** {@inheritDoc} */
@FlakyTest(bugId = 206753786)
@Test
- override fun statusBarLayerRotatesScales() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
- super.statusBarLayerRotatesScales()
- }
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
companion object {
/**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index 8e6603b..c036515 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -24,9 +24,7 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -81,11 +79,7 @@
@FlakyTest(bugId = 206753786)
@Test
- override fun statusBarLayerRotatesScales() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
- testSpec.statusBarLayerRotatesScales()
- }
+ override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
companion object {
/**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
index f9e180e..e415024 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
@@ -24,8 +24,6 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import org.junit.Assume.assumeFalse
import com.android.server.wm.flicker.traces.region.RegionSubject
import org.junit.FixMethodOrder
import org.junit.Test
@@ -85,11 +83,7 @@
/** {@inheritDoc} */
@FlakyTest(bugId = 206753786)
@Test
- override fun statusBarLayerRotatesScales() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
- super.statusBarLayerRotatesScales()
- }
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
companion object {
/**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
index b7bfa1b..4a4c46c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
@@ -25,8 +25,6 @@
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.traces.region.RegionSubject
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -85,11 +83,7 @@
/** {@inheritDoc} */
@FlakyTest(bugId = 206753786)
@Test
- override fun statusBarLayerRotatesScales() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
- super.statusBarLayerRotatesScales()
- }
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
companion object {
/**
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.bp b/media/java/Android.bp
index eeaf6e9..c7c1d54 100644
--- a/media/java/Android.bp
+++ b/media/java/Android.bp
@@ -8,7 +8,7 @@
}
filegroup {
- name: "framework-media-sources",
+ name: "framework-media-non-updatable-sources",
srcs: [
"**/*.java",
"**/*.aidl",
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/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
index cc7b2a5..f74edb1 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -142,7 +142,15 @@
setAugmentWithSubscriptionPlan(true);
}
- /** @hide */
+ /**
+ * Set poll on open flag to indicate the poll is needed before service gets statistics
+ * result. This is default enabled. However, for any non-privileged caller, the poll might
+ * be omitted in case of rate limiting.
+ *
+ * @param pollOnOpen true if poll is needed.
+ * @hide
+ */
+ // @SystemApi(client = MODULE_LIBRARIES)
public void setPollOnOpen(boolean pollOnOpen) {
if (pollOnOpen) {
mFlags |= FLAG_POLL_ON_OPEN;
@@ -863,4 +871,74 @@
return msg.getData().getParcelable(key);
}
}
+
+ /**
+ * Mark given UID as being in foreground for stats purposes.
+ *
+ * @hide
+ */
+ // @SystemApi
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK})
+ public void setUidForeground(int uid, boolean uidForeground) {
+ try {
+ mService.setUidForeground(uid, uidForeground);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Advise persistence threshold; may be overridden internally.
+ *
+ * @hide
+ */
+ // @SystemApi
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK})
+ public void advisePersistThreshold(long thresholdBytes) {
+ try {
+ mService.advisePersistThreshold(thresholdBytes);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Force update of statistics.
+ *
+ * @hide
+ */
+ // @SystemApi
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK})
+ public void forceUpdate() {
+ try {
+ mService.forceUpdate();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set the warning and limit to all registered custom network stats providers.
+ * Note that invocation of any interface will be sent to all providers.
+ *
+ * @hide
+ */
+ // @SystemApi
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK})
+ public void setStatsProviderWarningAndLimitAsync(@NonNull String iface, long warning,
+ long limit) {
+ try {
+ mService.setStatsProviderWarningAndLimitAsync(iface, warning, limit);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl
index 12937b5..a4babb5 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl
@@ -94,4 +94,16 @@
/** Registers a network stats provider */
INetworkStatsProviderCallback registerNetworkStatsProvider(String tag,
in INetworkStatsProvider provider);
+
+ /** Mark given UID as being in foreground for stats purposes. */
+ void setUidForeground(int uid, boolean uidForeground);
+
+ /** Advise persistence threshold; may be overridden internally. */
+ void advisePersistThreshold(long thresholdBytes);
+
+ /**
+ * Set the warning and limit to all registered custom network stats providers.
+ * Note that invocation of any interface will be sent to all providers.
+ */
+ void setStatsProviderWarningAndLimitAsync(String iface, long warning, long limit);
}
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/NetworkStats.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
index b00fea4..9d532e7 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
@@ -383,6 +383,95 @@
this.operations += another.operations;
}
+ /**
+ * @return interface name of this entry.
+ * @hide
+ */
+ @Nullable public String getIface() {
+ return iface;
+ }
+
+ /**
+ * @return the uid of this entry.
+ */
+ public int getUid() {
+ return uid;
+ }
+
+ /**
+ * @return the set state of this entry. Should be one of the following
+ * values: {@link #SET_DEFAULT}, {@link #SET_FOREGROUND}.
+ */
+ @State public int getSet() {
+ return set;
+ }
+
+ /**
+ * @return the tag value of this entry.
+ */
+ public int getTag() {
+ return tag;
+ }
+
+ /**
+ * @return the metered state. Should be one of the following
+ * values: {link #METERED_YES}, {link #METERED_NO}.
+ */
+ @Meteredness public int getMetered() {
+ return metered;
+ }
+
+ /**
+ * @return the roaming state. Should be one of the following
+ * values: {link #ROAMING_YES}, {link #ROAMING_NO}.
+ */
+ @Roaming public int getRoaming() {
+ return roaming;
+ }
+
+ /**
+ * @return the default network state. Should be one of the following
+ * values: {link #DEFAULT_NETWORK_YES}, {link #DEFAULT_NETWORK_NO}.
+ */
+ @DefaultNetwork public int getDefaultNetwork() {
+ return defaultNetwork;
+ }
+
+ /**
+ * @return the number of received bytes.
+ */
+ public long getRxBytes() {
+ return rxBytes;
+ }
+
+ /**
+ * @return the number of received packets.
+ */
+ public long getRxPackets() {
+ return rxPackets;
+ }
+
+ /**
+ * @return the number of transmitted bytes.
+ */
+ public long getTxBytes() {
+ return txBytes;
+ }
+
+ /**
+ * @return the number of transmitted packets.
+ */
+ public long getTxPackets() {
+ return txPackets;
+ }
+
+ /**
+ * @return the count of network operations performed for this entry.
+ */
+ public long getOperations() {
+ return operations;
+ }
+
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
@@ -593,7 +682,7 @@
* @hide
*/
@UnsupportedAppUsage
- public Entry getValues(int i, Entry recycle) {
+ public Entry getValues(int i, @Nullable Entry recycle) {
final Entry entry = recycle != null ? recycle : new Entry();
entry.iface = iface[i];
entry.uid = uid[i];
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/NetworkStatsManagerInternal.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsManagerInternal.java
deleted file mode 100644
index 0e9a9da..0000000
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsManagerInternal.java
+++ /dev/null
@@ -1,45 +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.server.net;
-
-import android.annotation.NonNull;
-import android.net.NetworkStats;
-import android.net.NetworkTemplate;
-
-public abstract class NetworkStatsManagerInternal {
- /** Return network layer usage total for traffic that matches template. */
- public abstract long getNetworkTotalBytes(NetworkTemplate template, long start, long end);
-
- /** Return network layer usage per-UID for traffic that matches template. */
- public abstract NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end);
-
- /** Mark given UID as being in foreground for stats purposes. */
- public abstract void setUidForeground(int uid, boolean uidForeground);
-
- /** Advise persistance threshold; may be overridden internally. */
- public abstract void advisePersistThreshold(long thresholdBytes);
-
- /** Force update of statistics. */
- public abstract void forceUpdate();
-
- /**
- * Set the warning and limit to all registered custom network stats providers.
- * Note that invocation of any interface will be sent to all providers.
- */
- public abstract void setStatsProviderWarningAndLimitAsync(@NonNull String iface, long warning,
- long limit);
-}
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 3273e88..e15acf3 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;
@@ -423,7 +431,6 @@
new DefaultNetworkStatsSettings(context), new NetworkStatsFactory(netd),
new NetworkStatsObservers(), getDefaultSystemDir(), getDefaultBaseDir(),
new Dependencies());
- service.registerLocalService();
return service;
}
@@ -504,11 +511,6 @@
}
}
- private void registerLocalService() {
- LocalServices.addService(NetworkStatsManagerInternal.class,
- new NetworkStatsManagerInternalImpl());
- }
-
/**
* Observer that watches for {@link INetdUnsolicitedEventListener} alerts.
*/
@@ -860,7 +862,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);
@@ -999,7 +1001,8 @@
}
@VisibleForTesting
- void setUidForeground(int uid, boolean uidForeground) {
+ public void setUidForeground(int uid, boolean uidForeground) {
+ PermissionUtils.enforceNetworkStackPermission(mContext);
synchronized (mStatsLock) {
final int set = uidForeground ? SET_FOREGROUND : SET_DEFAULT;
final int oldSet = mActiveUidCounterSet.get(uid, SET_DEFAULT);
@@ -1035,7 +1038,7 @@
@Override
public void forceUpdate() {
- mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
final long token = Binder.clearCallingIdentity();
try {
@@ -1045,7 +1048,9 @@
}
}
- private void advisePersistThreshold(long thresholdBytes) {
+ /** Advise persistence threshold; may be overridden internally. */
+ public void advisePersistThreshold(long thresholdBytes) {
+ PermissionUtils.enforceNetworkStackPermission(mContext);
// clamp threshold into safe range
mPersistThreshold = NetworkStatsUtils.constrain(thresholdBytes,
128 * KB_IN_BYTES, 2 * MB_IN_BYTES);
@@ -1624,7 +1629,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,
@@ -1636,7 +1641,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,
@@ -1682,52 +1687,19 @@
removeUidsLocked(CollectionUtils.toIntArray(uids));
}
- private class NetworkStatsManagerInternalImpl extends NetworkStatsManagerInternal {
- @Override
- public long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
- Trace.traceBegin(TRACE_TAG_NETWORK, "getNetworkTotalBytes");
- try {
- return NetworkStatsService.this.getNetworkTotalBytes(template, start, end);
- } finally {
- Trace.traceEnd(TRACE_TAG_NETWORK);
- }
+ /**
+ * Set the warning and limit to all registered custom network stats providers.
+ * Note that invocation of any interface will be sent to all providers.
+ */
+ public void setStatsProviderWarningAndLimitAsync(
+ @NonNull String iface, long warning, long limit) {
+ PermissionUtils.enforceNetworkStackPermission(mContext);
+ if (LOGV) {
+ Log.v(TAG, "setStatsProviderWarningAndLimitAsync("
+ + iface + "," + warning + "," + limit + ")");
}
-
- @Override
- public NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end) {
- Trace.traceBegin(TRACE_TAG_NETWORK, "getNetworkUidBytes");
- try {
- return NetworkStatsService.this.getNetworkUidBytes(template, start, end);
- } finally {
- Trace.traceEnd(TRACE_TAG_NETWORK);
- }
- }
-
- @Override
- public void setUidForeground(int uid, boolean uidForeground) {
- NetworkStatsService.this.setUidForeground(uid, uidForeground);
- }
-
- @Override
- public void advisePersistThreshold(long thresholdBytes) {
- NetworkStatsService.this.advisePersistThreshold(thresholdBytes);
- }
-
- @Override
- public void forceUpdate() {
- NetworkStatsService.this.forceUpdate();
- }
-
- @Override
- public void setStatsProviderWarningAndLimitAsync(
- @NonNull String iface, long warning, long limit) {
- if (LOGV) {
- Log.v(TAG, "setStatsProviderWarningAndLimitAsync("
- + iface + "," + warning + "," + limit + ")");
- }
- invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetWarningAndLimit(iface,
- warning, limit));
- }
+ invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetWarningAndLimit(iface,
+ warning, limit));
}
@Override
@@ -2031,10 +2003,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());
@@ -2076,6 +2050,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();
@@ -2089,7 +2064,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;
@@ -2097,6 +2073,7 @@
mSemaphore = semaphore;
mAlertObserver = alertObserver;
mStatsProviderCbList = cbList;
+ mNetworkPolicyManager = networkPolicyManager;
}
@NonNull
@@ -2143,8 +2120,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..2d35c331
--- /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/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_dark.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_dark.xml
new file mode 100644
index 0000000..1e930ea
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_dark.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<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/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_light.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_light.xml
new file mode 100644
index 0000000..378fc16
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_light.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<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/packages/SettingsLib/ButtonPreference/res/values-night/colors.xml b/packages/SettingsLib/ButtonPreference/res/values-night/colors.xml
new file mode 100644
index 0000000..6be7b93
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values-night/colors.xml
@@ -0,0 +1,21 @@
+<?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>
+ <!-- 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..d8c6ac3f
--- /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/packages/SettingsLib/ButtonPreference/res/values/attrs.xml b/packages/SettingsLib/ButtonPreference/res/values/attrs.xml
new file mode 100644
index 0000000..9a4312a
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values/attrs.xml
@@ -0,0 +1,22 @@
+<?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>
+ <declare-styleable name="ButtonPreference">
+ <attr name="android:gravity" />
+ </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/values/colors.xml b/packages/SettingsLib/ButtonPreference/res/values/colors.xml
new file mode 100644
index 0000000..45baeeb
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values/colors.xml
@@ -0,0 +1,21 @@
+<?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>
+ <!-- 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/packages/SettingsLib/ButtonPreference/res/values/dimens.xml b/packages/SettingsLib/ButtonPreference/res/values/dimens.xml
new file mode 100644
index 0000000..3d7831e
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values/dimens.xml
@@ -0,0 +1,20 @@
+<?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>
+ <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 1303a62..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" />
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/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/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/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
index c46ffa0..b24d08d 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
@@ -28,6 +28,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Parcelable;
+import android.os.Trace;
import android.util.ArrayMap;
import android.view.LayoutInflater;
import android.view.View;
@@ -224,10 +225,12 @@
}
public void reloadFragments() {
+ Trace.beginSection("FrargmentHostManager#reloadFragments");
// Save the old state.
Parcelable p = destroyFragmentHost();
// Generate a new fragment host and restore its state.
createFragmentHost(p);
+ Trace.endSection();
}
class HostCallbacks extends FragmentHostCallback<FragmentHostManager> {
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/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/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 41dced6..259b786 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -25,6 +25,7 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Trace;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -172,9 +173,14 @@
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
- inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(),
- R.style.Theme_SystemUI_QuickSettings));
- return inflater.inflate(R.layout.qs_panel, container, false);
+ try {
+ Trace.beginSection("QSFragment#onCreateView");
+ inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(),
+ R.style.Theme_SystemUI_QuickSettings));
+ return inflater.inflate(R.layout.qs_panel, container, false);
+ } finally {
+ Trace.endSection();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 866b1b8..d3f8db38 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/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/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/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 4d625cf..23ea45b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -69,6 +69,7 @@
import android.os.Handler;
import android.os.PowerManager;
import android.os.SystemClock;
+import android.os.Trace;
import android.os.UserManager;
import android.os.VibrationEffect;
import android.provider.Settings;
@@ -4590,11 +4591,13 @@
@Override
public void onSmallestScreenWidthChanged() {
+ Trace.beginSection("onSmallestScreenWidthChanged");
if (DEBUG) Log.d(TAG, "onSmallestScreenWidthChanged");
// Can affect multi-user switcher visibility as it depends on screen size by default:
// it is enabled only for devices with large screens (see config_keyguardUserSwitcher)
reInflateViews();
+ Trace.endSection();
}
@Override
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 981c843..137c519 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -2963,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();
@@ -3569,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/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
index 41cacf5..1030bfd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -16,110 +16,53 @@
package com.android.systemui.statusbar.policy;
-
import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED;
import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED;
-import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED;
-
-import static com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule.DEVICE_STATE_ROTATION_LOCK_DEFAULTS;
import android.annotation.Nullable;
import android.hardware.devicestate.DeviceStateManager;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.text.TextUtils;
import android.util.Log;
-import android.util.SparseIntArray;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.wrapper.RotationPolicyWrapper;
import java.util.concurrent.Executor;
import javax.inject.Inject;
-import javax.inject.Named;
/**
- * Handles reading and writing of rotation lock settings per device state, as well as setting
- * the rotation lock when device state changes.
- **/
+ * Handles reading and writing of rotation lock settings per device state, as well as setting the
+ * rotation lock when device state changes.
+ */
@SysUISingleton
-public final class DeviceStateRotationLockSettingController implements Listenable,
- RotationLockController.RotationLockControllerCallback {
+public final class DeviceStateRotationLockSettingController
+ implements Listenable, RotationLockController.RotationLockControllerCallback {
private static final String TAG = "DSRotateLockSettingCon";
- private static final String SEPARATOR_REGEX = ":";
-
- private final SecureSettings mSecureSettings;
private final RotationPolicyWrapper mRotationPolicyWrapper;
private final DeviceStateManager mDeviceStateManager;
private final Executor mMainExecutor;
- private final String[] mDeviceStateRotationLockDefaults;
+ private final DeviceStateRotationLockSettingsManager mDeviceStateRotationLockSettingsManager;
- private SparseIntArray mDeviceStateRotationLockSettings;
- // TODO(b/183001527): Add API to query current device state and initialize this.
+ // On registration for DeviceStateCallback, we will receive a callback with the current state
+ // and this will be initialized.
private int mDeviceState = -1;
- @Nullable
- private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
-
+ @Nullable private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
+ private DeviceStateRotationLockSettingsManager.DeviceStateRotationLockSettingsListener
+ mDeviceStateRotationLockSettingsListener;
@Inject
public DeviceStateRotationLockSettingController(
- SecureSettings secureSettings,
RotationPolicyWrapper rotationPolicyWrapper,
DeviceStateManager deviceStateManager,
@Main Executor executor,
- @Named(DEVICE_STATE_ROTATION_LOCK_DEFAULTS) String[] deviceStateRotationLockDefaults
- ) {
- mSecureSettings = secureSettings;
+ DeviceStateRotationLockSettingsManager deviceStateRotationLockSettingsManager) {
mRotationPolicyWrapper = rotationPolicyWrapper;
mDeviceStateManager = deviceStateManager;
mMainExecutor = executor;
- mDeviceStateRotationLockDefaults = deviceStateRotationLockDefaults;
- }
-
- /**
- * Loads the settings from storage.
- */
- public void initialize() {
- String serializedSetting =
- mSecureSettings.getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
- UserHandle.USER_CURRENT);
- if (TextUtils.isEmpty(serializedSetting)) {
- // No settings saved, we should load the defaults and persist them.
- fallbackOnDefaults();
- return;
- }
- String[] values = serializedSetting.split(SEPARATOR_REGEX);
- if (values.length % 2 != 0) {
- // Each entry should be a key/value pair, so this is corrupt.
- Log.wtf(TAG, "Can't deserialize saved settings, falling back on defaults");
- fallbackOnDefaults();
- return;
- }
- mDeviceStateRotationLockSettings = new SparseIntArray(values.length / 2);
- int key;
- int value;
-
- for (int i = 0; i < values.length - 1; ) {
- try {
- key = Integer.parseInt(values[i++]);
- value = Integer.parseInt(values[i++]);
- mDeviceStateRotationLockSettings.put(key, value);
- } catch (NumberFormatException e) {
- Log.wtf(TAG, "Error deserializing one of the saved settings", e);
- fallbackOnDefaults();
- return;
- }
- }
- }
-
- private void fallbackOnDefaults() {
- loadDefaults();
- persistSettings();
+ mDeviceStateRotationLockSettingsManager = deviceStateRotationLockSettingsManager;
}
@Override
@@ -129,10 +72,17 @@
// is no user action.
mDeviceStateCallback = this::updateDeviceState;
mDeviceStateManager.registerCallback(mMainExecutor, mDeviceStateCallback);
+ mDeviceStateRotationLockSettingsListener = () -> readPersistedSetting(mDeviceState);
+ mDeviceStateRotationLockSettingsManager.registerListener(
+ mDeviceStateRotationLockSettingsListener);
} else {
if (mDeviceStateCallback != null) {
mDeviceStateManager.unregisterCallback(mDeviceStateCallback);
}
+ if (mDeviceStateRotationLockSettingsListener != null) {
+ mDeviceStateRotationLockSettingsManager.unregisterListener(
+ mDeviceStateRotationLockSettingsListener);
+ }
}
}
@@ -143,7 +93,8 @@
return;
}
- if (rotationLocked == isRotationLockedForCurrentState()) {
+ if (rotationLocked
+ == mDeviceStateRotationLockSettingsManager.isRotationLocked(mDeviceState)) {
Log.v(TAG, "Rotation lock same as the current setting, no need to update.");
return;
}
@@ -152,19 +103,15 @@
}
private void saveNewRotationLockSetting(boolean isRotationLocked) {
- Log.v(TAG, "saveNewRotationLockSetting [state=" + mDeviceState + "] [isRotationLocked="
- + isRotationLocked + "]");
+ Log.v(
+ TAG,
+ "saveNewRotationLockSetting [state="
+ + mDeviceState
+ + "] [isRotationLocked="
+ + isRotationLocked
+ + "]");
- mDeviceStateRotationLockSettings.put(mDeviceState,
- isRotationLocked
- ? DEVICE_STATE_ROTATION_LOCK_LOCKED
- : DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
- persistSettings();
- }
-
- private boolean isRotationLockedForCurrentState() {
- return mDeviceStateRotationLockSettings.get(mDeviceState,
- DEVICE_STATE_ROTATION_LOCK_IGNORED) == DEVICE_STATE_ROTATION_LOCK_LOCKED;
+ mDeviceStateRotationLockSettingsManager.updateSetting(mDeviceState, isRotationLocked);
}
private void updateDeviceState(int state) {
@@ -173,8 +120,12 @@
return;
}
+ readPersistedSetting(state);
+ }
+
+ private void readPersistedSetting(int state) {
int rotationLockSetting =
- mDeviceStateRotationLockSettings.get(state, DEVICE_STATE_ROTATION_LOCK_IGNORED);
+ mDeviceStateRotationLockSettingsManager.getRotationLockSetting(state);
if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) {
// We won't handle this device state. The same rotation lock setting as before should
// apply and any changes to the rotation lock setting will be written for the previous
@@ -186,54 +137,10 @@
// Accept the new state
mDeviceState = state;
- // Update the rotation lock setting if needed for this new device state
+ // Update the rotation policy, if needed, for this new device state
boolean newRotationLockSetting = rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_LOCKED;
if (newRotationLockSetting != mRotationPolicyWrapper.isRotationLocked()) {
mRotationPolicyWrapper.setRotationLock(newRotationLockSetting);
}
}
-
- private void persistSettings() {
- if (mDeviceStateRotationLockSettings.size() == 0) {
- mSecureSettings.putStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
- /* value= */"", UserHandle.USER_CURRENT);
- return;
- }
-
- StringBuilder stringBuilder = new StringBuilder();
- stringBuilder.append(mDeviceStateRotationLockSettings.keyAt(0))
- .append(SEPARATOR_REGEX)
- .append(mDeviceStateRotationLockSettings.valueAt(0));
-
- for (int i = 1; i < mDeviceStateRotationLockSettings.size(); i++) {
- stringBuilder
- .append(SEPARATOR_REGEX)
- .append(mDeviceStateRotationLockSettings.keyAt(i))
- .append(SEPARATOR_REGEX)
- .append(mDeviceStateRotationLockSettings.valueAt(i));
- }
- mSecureSettings.putStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
- stringBuilder.toString(), UserHandle.USER_CURRENT);
- }
-
- private void loadDefaults() {
- if (mDeviceStateRotationLockDefaults.length == 0) {
- Log.w(TAG, "Empty default settings");
- mDeviceStateRotationLockSettings = new SparseIntArray(/* initialCapacity= */0);
- return;
- }
- mDeviceStateRotationLockSettings =
- new SparseIntArray(mDeviceStateRotationLockDefaults.length);
- for (String serializedDefault : mDeviceStateRotationLockDefaults) {
- String[] entry = serializedDefault.split(SEPARATOR_REGEX);
- try {
- int key = Integer.parseInt(entry[0]);
- int value = Integer.parseInt(entry[1]);
- mDeviceStateRotationLockSettings.put(key, value);
- } catch (NumberFormatException e) {
- Log.wtf(TAG, "Error deserializing default settings", e);
- }
- }
- }
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java
new file mode 100644
index 0000000..a418c74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED;
+import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED;
+import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Manages device-state based rotation lock settings. Handles reading, writing, and listening for
+ * changes.
+ */
+public final class DeviceStateRotationLockSettingsManager {
+
+ private static final String TAG = "DSRotLockSettingsMngr";
+ private static final String SEPARATOR_REGEX = ":";
+
+ private static DeviceStateRotationLockSettingsManager sSingleton;
+
+ private final ContentResolver mContentResolver;
+ private final String[] mDeviceStateRotationLockDefaults;
+ private final Handler mMainHandler = Handler.getMain();
+ private final Set<DeviceStateRotationLockSettingsListener> mListeners = new HashSet<>();
+ private SparseIntArray mDeviceStateRotationLockSettings;
+
+ private DeviceStateRotationLockSettingsManager(Context context) {
+ mContentResolver = context.getContentResolver();
+ mDeviceStateRotationLockDefaults =
+ context.getResources()
+ .getStringArray(R.array.config_perDeviceStateRotationLockDefaults);
+ initializeInMemoryMap();
+ listenForSettingsChange(context);
+ }
+
+ /** Returns a singleton instance of this class */
+ public static synchronized DeviceStateRotationLockSettingsManager getInstance(Context context) {
+ if (sSingleton == null) {
+ sSingleton =
+ new DeviceStateRotationLockSettingsManager(context.getApplicationContext());
+ }
+ return sSingleton;
+ }
+
+ /** Returns true if device-state based rotation lock settings are enabled. */
+ public static boolean isDeviceStateRotationLockEnabled(Context context) {
+ return context.getResources()
+ .getStringArray(R.array.config_perDeviceStateRotationLockDefaults)
+ .length
+ > 0;
+ }
+
+ private void listenForSettingsChange(Context context) {
+ context.getContentResolver()
+ .registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.DEVICE_STATE_ROTATION_LOCK),
+ /* notifyForDescendents= */ false, //NOTYPO
+ new ContentObserver(mMainHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ onPersistedSettingsChanged();
+ }
+ },
+ UserHandle.USER_CURRENT);
+ }
+
+ /**
+ * Registers a {@link DeviceStateRotationLockSettingsListener} to be notified when the settings
+ * change. Can be called multiple times with different listeners.
+ */
+ public void registerListener(DeviceStateRotationLockSettingsListener runnable) {
+ mListeners.add(runnable);
+ }
+
+ /**
+ * Unregisters a {@link DeviceStateRotationLockSettingsListener}. No-op if the given instance
+ * was never registered.
+ */
+ public void unregisterListener(
+ DeviceStateRotationLockSettingsListener deviceStateRotationLockSettingsListener) {
+ if (!mListeners.remove(deviceStateRotationLockSettingsListener)) {
+ Log.w(TAG, "Attempting to unregister a listener hadn't been registered");
+ }
+ }
+
+ /** Updates the rotation lock setting for a specified device state. */
+ public void updateSetting(int deviceState, boolean rotationLocked) {
+ mDeviceStateRotationLockSettings.put(
+ deviceState,
+ rotationLocked
+ ? DEVICE_STATE_ROTATION_LOCK_LOCKED
+ : DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
+ persistSettings();
+ }
+
+ /**
+ * Returns the {@link DeviceStateRotationLockSetting} for the given device state. If no setting
+ * is specified for this device state, it will return {@link
+ * DEVICE_STATE_ROTATION_LOCK_IGNORED}.
+ */
+ @Settings.Secure.DeviceStateRotationLockSetting
+ public int getRotationLockSetting(int deviceState) {
+ return mDeviceStateRotationLockSettings.get(
+ deviceState, DEVICE_STATE_ROTATION_LOCK_IGNORED);
+ }
+
+ /** Returns true if the rotation is locked for the current device state */
+ public boolean isRotationLocked(int deviceState) {
+ return getRotationLockSetting(deviceState) == DEVICE_STATE_ROTATION_LOCK_LOCKED;
+ }
+
+ /**
+ * Returns true if there is no device state for which the current setting is {@link
+ * DEVICE_STATE_ROTATION_LOCK_UNLOCKED}.
+ */
+ public boolean isRotationLockedForAllStates() {
+ for (int i = 0; i < mDeviceStateRotationLockSettings.size(); i++) {
+ if (mDeviceStateRotationLockSettings.valueAt(i)
+ == DEVICE_STATE_ROTATION_LOCK_UNLOCKED) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void initializeInMemoryMap() {
+ String serializedSetting =
+ Settings.Secure.getStringForUser(
+ mContentResolver,
+ Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ UserHandle.USER_CURRENT);
+ if (TextUtils.isEmpty(serializedSetting)) {
+ // No settings saved, we should load the defaults and persist them.
+ fallbackOnDefaults();
+ return;
+ }
+ String[] values = serializedSetting.split(SEPARATOR_REGEX);
+ if (values.length % 2 != 0) {
+ // Each entry should be a key/value pair, so this is corrupt.
+ Log.wtf(TAG, "Can't deserialize saved settings, falling back on defaults");
+ fallbackOnDefaults();
+ return;
+ }
+ mDeviceStateRotationLockSettings = new SparseIntArray(values.length / 2);
+ int key;
+ int value;
+
+ for (int i = 0; i < values.length - 1; ) {
+ try {
+ key = Integer.parseInt(values[i++]);
+ value = Integer.parseInt(values[i++]);
+ mDeviceStateRotationLockSettings.put(key, value);
+ } catch (NumberFormatException e) {
+ Log.wtf(TAG, "Error deserializing one of the saved settings", e);
+ fallbackOnDefaults();
+ return;
+ }
+ }
+ }
+
+ private void fallbackOnDefaults() {
+ loadDefaults();
+ persistSettings();
+ }
+
+ private void persistSettings() {
+ if (mDeviceStateRotationLockSettings.size() == 0) {
+ Settings.Secure.putStringForUser(
+ mContentResolver,
+ Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ /* value= */ "",
+ UserHandle.USER_CURRENT);
+ return;
+ }
+
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder
+ .append(mDeviceStateRotationLockSettings.keyAt(0))
+ .append(SEPARATOR_REGEX)
+ .append(mDeviceStateRotationLockSettings.valueAt(0));
+
+ for (int i = 1; i < mDeviceStateRotationLockSettings.size(); i++) {
+ stringBuilder
+ .append(SEPARATOR_REGEX)
+ .append(mDeviceStateRotationLockSettings.keyAt(i))
+ .append(SEPARATOR_REGEX)
+ .append(mDeviceStateRotationLockSettings.valueAt(i));
+ }
+ Settings.Secure.putStringForUser(
+ mContentResolver,
+ Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ stringBuilder.toString(),
+ UserHandle.USER_CURRENT);
+ }
+
+ private void loadDefaults() {
+ if (mDeviceStateRotationLockDefaults.length == 0) {
+ Log.w(TAG, "Empty default settings");
+ mDeviceStateRotationLockSettings = new SparseIntArray(/* initialCapacity= */ 0);
+ return;
+ }
+ mDeviceStateRotationLockSettings =
+ new SparseIntArray(mDeviceStateRotationLockDefaults.length);
+ for (String serializedDefault : mDeviceStateRotationLockDefaults) {
+ String[] entry = serializedDefault.split(SEPARATOR_REGEX);
+ try {
+ int key = Integer.parseInt(entry[0]);
+ int value = Integer.parseInt(entry[1]);
+ mDeviceStateRotationLockSettings.put(key, value);
+ } catch (NumberFormatException e) {
+ Log.wtf(TAG, "Error deserializing default settings", e);
+ }
+ }
+ }
+
+ /**
+ * Called when the persisted settings have changed, requiring a reinitialization of the
+ * in-memory map.
+ */
+ @VisibleForTesting
+ public void onPersistedSettingsChanged() {
+ initializeInMemoryMap();
+ notifyListeners();
+ }
+
+ private void notifyListeners() {
+ for (DeviceStateRotationLockSettingsListener r : mListeners) {
+ r.onSettingsChanged();
+ }
+ }
+
+ /** Listener for changes in device-state based rotation lock settings */
+ public interface DeviceStateRotationLockSettingsListener {
+ /** Called whenever the settings have changed. */
+ void onSettingsChanged();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
index 3143a47..1eeb0ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
@@ -64,7 +64,6 @@
mDeviceStateRotationLockSettingController = deviceStateRotationLockSettingController;
mIsPerDeviceStateRotationLockEnabled = deviceStateRotationLockDefaults.length > 0;
if (mIsPerDeviceStateRotationLockEnabled) {
- deviceStateRotationLockSettingController.initialize();
mCallbacks.add(mDeviceStateRotationLockSettingController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index b6a96a7..60938fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy.dagger;
+import android.content.Context;
import android.content.res.Resources;
import android.os.UserManager;
@@ -35,6 +36,7 @@
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.DevicePostureControllerImpl;
+import com.android.systemui.statusbar.policy.DeviceStateRotationLockSettingsManager;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.ExtensionControllerImpl;
import com.android.systemui.statusbar.policy.FlashlightController;
@@ -163,6 +165,14 @@
return controller;
}
+ /** Returns a singleton instance of DeviceStateRotationLockSettingsManager */
+ @SysUISingleton
+ @Provides
+ static DeviceStateRotationLockSettingsManager provideAutoRotateSettingsManager(
+ Context context) {
+ return DeviceStateRotationLockSettingsManager.getInstance(context);
+ }
+
/**
* Default values for per-device state rotation lock settings.
*/
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/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
index e453ff2d..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/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/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/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
index db7b2f2..a8522c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
@@ -16,6 +16,10 @@
package com.android.systemui.statusbar.policy;
+import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED;
+import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED;
+import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -25,14 +29,15 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableContentResolver;
import android.testing.TestableResources;
import androidx.test.filters.SmallTest;
+import com.android.internal.R;
import com.android.internal.view.RotationPolicy;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.wrapper.RotationPolicyWrapper;
@@ -47,63 +52,55 @@
@SmallTest
public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase {
- private static final String[] DEFAULT_SETTINGS = new String[]{
- "0:0",
- "1:2"
- };
+ private static final String[] DEFAULT_SETTINGS = new String[] {"0:0", "1:2"};
- private final FakeSettings mFakeSettings = new FakeSettings();
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
@Mock DeviceStateManager mDeviceStateManager;
RotationPolicyWrapper mFakeRotationPolicy = new FakeRotationPolicy();
DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
+ private DeviceStateRotationLockSettingsManager mSettingsManager;
+ private TestableContentResolver mContentResolver;
@Before
public void setUp() {
MockitoAnnotations.initMocks(/* testClass= */ this);
TestableResources resources = mContext.getOrCreateTestableResources();
+ resources.addOverride(R.array.config_perDeviceStateRotationLockDefaults, DEFAULT_SETTINGS);
ArgumentCaptor<DeviceStateManager.DeviceStateCallback> deviceStateCallbackArgumentCaptor =
- ArgumentCaptor.forClass(
- DeviceStateManager.DeviceStateCallback.class);
+ ArgumentCaptor.forClass(DeviceStateManager.DeviceStateCallback.class);
- mDeviceStateRotationLockSettingController = new DeviceStateRotationLockSettingController(
- mFakeSettings,
- mFakeRotationPolicy,
- mDeviceStateManager,
- mFakeExecutor,
- DEFAULT_SETTINGS
- );
+ mContentResolver = mContext.getContentResolver();
+ mSettingsManager = DeviceStateRotationLockSettingsManager.getInstance(mContext);
+ mDeviceStateRotationLockSettingController =
+ new DeviceStateRotationLockSettingController(
+ mFakeRotationPolicy, mDeviceStateManager, mFakeExecutor, mSettingsManager);
mDeviceStateRotationLockSettingController.setListening(true);
- verify(mDeviceStateManager).registerCallback(any(),
- deviceStateCallbackArgumentCaptor.capture());
+ verify(mDeviceStateManager)
+ .registerCallback(any(), deviceStateCallbackArgumentCaptor.capture());
mDeviceStateCallback = deviceStateCallbackArgumentCaptor.getValue();
}
@Test
public void whenSavedSettingsEmpty_defaultsLoadedAndSaved() {
- mFakeSettings.putStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK, "",
- UserHandle.USER_CURRENT);
+ initializeSettingsWith();
- mDeviceStateRotationLockSettingController.initialize();
-
- assertThat(mFakeSettings
- .getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
- UserHandle.USER_CURRENT))
+ assertThat(
+ Settings.Secure.getStringForUser(
+ mContentResolver,
+ Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ UserHandle.USER_CURRENT))
.isEqualTo("0:0:1:2");
}
@Test
public void whenNoSavedValueForDeviceState_assumeIgnored() {
- mFakeSettings.putStringForUser(
- Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
- /* value= */"0:2:1:2",
- UserHandle.USER_CURRENT);
+ initializeSettingsWith(
+ 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
mFakeRotationPolicy.setRotationLock(true);
- mDeviceStateRotationLockSettingController.initialize();
mDeviceStateCallback.onStateChanged(1);
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
@@ -116,52 +113,43 @@
@Test
public void whenDeviceStateSwitched_loadCorrectSetting() {
- mFakeSettings.putStringForUser(
- Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
- /* value= */"0:2:1:1",
- UserHandle.USER_CURRENT);
+ initializeSettingsWith(
+ 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 1, DEVICE_STATE_ROTATION_LOCK_LOCKED);
mFakeRotationPolicy.setRotationLock(true);
- mDeviceStateRotationLockSettingController.initialize();
mDeviceStateCallback.onStateChanged(0);
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
mDeviceStateCallback.onStateChanged(1);
assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
-
}
@Test
public void whenUserChangesSetting_saveSettingForCurrentState() {
- mFakeSettings.putStringForUser(
- Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
- /* value= */"0:1:1:2",
- UserHandle.USER_CURRENT);
+ initializeSettingsWith(
+ 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
+ mSettingsManager.onPersistedSettingsChanged();
mFakeRotationPolicy.setRotationLock(true);
- mDeviceStateRotationLockSettingController.initialize();
mDeviceStateCallback.onStateChanged(0);
assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
- mDeviceStateRotationLockSettingController
- .onRotationLockStateChanged(/* rotationLocked= */false,
- /* affordanceVisible= */ true);
+ mDeviceStateRotationLockSettingController.onRotationLockStateChanged(
+ /* rotationLocked= */ false, /* affordanceVisible= */ true);
- assertThat(mFakeSettings
- .getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
- UserHandle.USER_CURRENT))
+ assertThat(
+ Settings.Secure.getStringForUser(
+ mContentResolver,
+ Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ UserHandle.USER_CURRENT))
.isEqualTo("0:2:1:2");
}
-
@Test
public void whenDeviceStateSwitchedToIgnoredState_usePreviousSetting() {
- mFakeSettings.putStringForUser(
- Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
- /* value= */"0:0:1:2",
- UserHandle.USER_CURRENT);
+ initializeSettingsWith(
+ 0, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
mFakeRotationPolicy.setRotationLock(true);
- mDeviceStateRotationLockSettingController.initialize();
mDeviceStateCallback.onStateChanged(1);
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
@@ -172,12 +160,9 @@
@Test
public void whenDeviceStateSwitchedToIgnoredState_newSettingsSaveForPreviousState() {
- mFakeSettings.putStringForUser(
- Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
- /* value= */"0:0:1:2",
- UserHandle.USER_CURRENT);
+ initializeSettingsWith(
+ 0, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
mFakeRotationPolicy.setRotationLock(true);
- mDeviceStateRotationLockSettingController.initialize();
mDeviceStateCallback.onStateChanged(1);
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
@@ -185,16 +170,52 @@
mDeviceStateCallback.onStateChanged(0);
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
- mDeviceStateRotationLockSettingController
- .onRotationLockStateChanged(/* rotationLocked= */true,
- /* affordanceVisible= */ true);
+ mDeviceStateRotationLockSettingController.onRotationLockStateChanged(
+ /* rotationLocked= */ true, /* affordanceVisible= */ true);
- assertThat(mFakeSettings
- .getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
- UserHandle.USER_CURRENT))
+ assertThat(
+ Settings.Secure.getStringForUser(
+ mContentResolver,
+ Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ UserHandle.USER_CURRENT))
.isEqualTo("0:0:1:1");
}
+ @Test
+ public void whenSettingsChangedExternally_updateRotationPolicy() throws InterruptedException {
+ initializeSettingsWith(
+ 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED,
+ 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
+ mFakeRotationPolicy.setRotationLock(false);
+ mDeviceStateCallback.onStateChanged(0);
+
+ assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
+
+ // Changing device state 0 to LOCKED
+ initializeSettingsWith(
+ 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
+
+ assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
+ }
+
+ private void initializeSettingsWith(int... values) {
+ if (values.length % 2 != 0) {
+ throw new IllegalArgumentException("Expecting key-value pairs");
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < values.length; sb.append(":")) {
+ sb.append(values[i++]).append(":").append(values[i++]);
+ }
+
+ Settings.Secure.putStringForUser(
+ mContentResolver,
+ Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ sb.toString(),
+ UserHandle.USER_CURRENT);
+
+ mSettingsManager.onPersistedSettingsChanged();
+ }
+
private static class FakeRotationPolicy implements RotationPolicyWrapper {
private boolean mRotationLock;
@@ -230,8 +251,8 @@
}
@Override
- public void registerRotationPolicyListener(RotationPolicy.RotationPolicyListener listener,
- int userHandle) {
+ public void registerRotationPolicyListener(
+ RotationPolicy.RotationPolicyListener listener, int userHandle) {
throw new AssertionError("Not implemented");
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
index 0581264..ea620a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
@@ -23,7 +23,6 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.testing.TestableResources;
import androidx.test.filters.SmallTest;
@@ -43,25 +42,19 @@
@SmallTest
public class RotationLockControllerImplTest extends SysuiTestCase {
- private static final String[] DEFAULT_SETTINGS = new String[]{
- "0:0",
- "1:2"
- };
+ private static final String[] DEFAULT_SETTINGS = new String[] {"0:0", "1:2"};
@Mock RotationPolicyWrapper mRotationPolicyWrapper;
@Mock DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
- private TestableResources mResources;
- private ArgumentCaptor<RotationPolicy.RotationPolicyListener>
- mRotationPolicyListenerCaptor;
+ private ArgumentCaptor<RotationPolicy.RotationPolicyListener> mRotationPolicyListenerCaptor;
@Before
public void setUp() {
MockitoAnnotations.initMocks(/* testClass= */ this);
- mResources = mContext.getOrCreateTestableResources();
- mRotationPolicyListenerCaptor = ArgumentCaptor.forClass(
- RotationPolicy.RotationPolicyListener.class);
+ mRotationPolicyListenerCaptor =
+ ArgumentCaptor.forClass(RotationPolicy.RotationPolicyListener.class);
}
@Test
@@ -79,14 +72,7 @@
}
@Test
- public void whenFlagOn_initializesDeviceStateRotationController() {
- createRotationLockController();
-
- verify(mDeviceStateRotationLockSettingController).initialize();
- }
-
- @Test
- public void whenFlagOn_dviceStateRotationControllerAddedToCallbacks() {
+ public void whenFlagOn_deviceStateRotationControllerAddedToCallbacks() {
createRotationLockController();
captureRotationPolicyListener().onChange();
@@ -103,11 +89,11 @@
private void createRotationLockController() {
createRotationLockController(DEFAULT_SETTINGS);
}
+
private void createRotationLockController(String[] deviceStateRotationLockDefaults) {
new RotationLockControllerImpl(
mRotationPolicyWrapper,
mDeviceStateRotationLockSettingController,
- deviceStateRotationLockDefaults
- );
+ deviceStateRotationLockDefaults);
}
}
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 6d0eadb..196c6aa 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -279,6 +279,9 @@
// 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 8e71dd3..734e5c3 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -84,11 +84,9 @@
}
@Override
- public void onRunningAppsChanged(int[] runningUids) {
+ public void onRunningAppsChanged(ArraySet<Integer> runningUids) {
mRunningUids.clear();
- for (int i = 0; i < runningUids.length; i++) {
- mRunningUids.add(runningUids[i]);
- }
+ mRunningUids.addAll(runningUids);
}
/**
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 1bb95f8..0c0ee52 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -23,6 +23,9 @@
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;
@@ -38,9 +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;
@@ -55,10 +60,12 @@
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
@@ -76,17 +83,18 @@
VirtualDeviceImpl(Context context, AssociationInfo associationInfo,
IBinder token, int ownerUid, OnDeviceCloseListener listener,
- VirtualDeviceParams params) {
+ PendingTrampolineCallback pendingTrampolineCallback, VirtualDeviceParams params) {
this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener,
- params);
+ pendingTrampolineCallback, params);
}
@VisibleForTesting
VirtualDeviceImpl(Context context, AssociationInfo associationInfo, IBinder token,
int ownerUid, InputController inputController, OnDeviceCloseListener listener,
- VirtualDeviceParams params) {
+ PendingTrampolineCallback pendingTrampolineCallback, VirtualDeviceParams params) {
mContext = context;
mAssociationInfo = associationInfo;
+ mPendingTrampolineCallback = pendingTrampolineCallback;
mOwnerUid = ownerUid;
mAppToken = token;
mParams = params;
@@ -121,6 +129,49 @@
}
@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);
@@ -276,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) {
@@ -346,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 12df79d..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,9 +16,13 @@
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;
@@ -26,7 +30,9 @@
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;
@@ -37,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;
@@ -48,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
@@ -80,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")
@@ -128,7 +168,8 @@
}
}
- class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub {
+ class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements
+ VirtualDeviceImpl.PendingTrampolineCallback {
@Override // Binder call
public IVirtualDevice createVirtualDevice(
@@ -164,7 +205,8 @@
mVirtualDevices.remove(associationId);
}
}
- }, params);
+ },
+ this, params);
mVirtualDevices.put(associationInfo.getId(), virtualDevice);
return virtualDevice;
}
@@ -185,7 +227,7 @@
}
}
} else {
- Slog.w(LOG_TAG, "No associations for user " + callingUserId);
+ Slog.w(TAG, "No associations for user " + callingUserId);
}
return null;
}
@@ -196,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);
}
}
@@ -205,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: ");
@@ -215,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) {
@@ -272,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/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 37ff879..262933d 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -20,6 +20,7 @@
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+import static android.os.UserHandle.USER_SYSTEM;
import static android.permission.PermissionCheckerManager.PERMISSION_HARD_DENIED;
import android.Manifest;
@@ -64,7 +65,6 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
-import android.os.IUserRestrictionsListener;
import android.os.Looper;
import android.os.Message;
import android.os.PowerExemptionManager;
@@ -88,6 +88,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
+import com.android.server.pm.UserRestrictionsUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -262,34 +265,30 @@
}
};
- private final IUserRestrictionsListener mUserRestrictionsListener =
- new IUserRestrictionsListener.Stub() {
+ private final UserRestrictionsListener mUserRestrictionsListener =
+ new UserRestrictionsListener() {
@Override
public void onUserRestrictionsChanged(int userId, Bundle newRestrictions,
Bundle prevRestrictions) {
- final boolean newDisallowBluetoothSharing = newRestrictions
- .getBoolean(UserManager.DISALLOW_BLUETOOTH_SHARING, false);
- final boolean prevDisallowBluetoothSharing = prevRestrictions
- .getBoolean(UserManager.DISALLOW_BLUETOOTH_SHARING, false);
- if (newDisallowBluetoothSharing != prevDisallowBluetoothSharing) {
+ if (UserRestrictionsUtils.restrictionsChanged(prevRestrictions, newRestrictions,
+ UserManager.DISALLOW_BLUETOOTH_SHARING)) {
updateOppLauncherComponentState(userId,
newRestrictions.getBoolean(UserManager.DISALLOW_BLUETOOTH_SHARING));
}
- final boolean newDisallowBluetooth = newRestrictions
- .getBoolean(UserManager.DISALLOW_BLUETOOTH, false);
- final boolean prevDisallowBluetooth = prevRestrictions
- .getBoolean(UserManager.DISALLOW_BLUETOOTH, false);
// DISALLOW_BLUETOOTH can only be set by DO or PO on the system user.
- final boolean isUserSystem = userId == UserHandle.SYSTEM.getIdentifier();
- if (isUserSystem && newDisallowBluetooth != prevDisallowBluetooth) {
- if (isUserSystem && newDisallowBluetooth) {
+ if (userId == USER_SYSTEM
+ && UserRestrictionsUtils.restrictionsChanged(prevRestrictions,
+ newRestrictions, UserManager.DISALLOW_BLUETOOTH)) {
+ if (userId == USER_SYSTEM && newRestrictions.getBoolean(
+ UserManager.DISALLOW_BLUETOOTH)) {
updateOppLauncherComponentState(userId, true); // Sharing disallowed
sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_DISALLOWED,
mContext.getPackageName());
} else {
- updateOppLauncherComponentState(userId, newDisallowBluetoothSharing);
+ updateOppLauncherComponentState(userId, newRestrictions.getBoolean(
+ UserManager.DISALLOW_BLUETOOTH_SHARING));
}
}
}
@@ -542,7 +541,7 @@
if (!noHome) {
PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
systemUiUid = pm.getPackageUid(pm.getSystemUiServiceComponent().getPackageName(),
- MATCH_SYSTEM_ONLY, UserHandle.SYSTEM.getIdentifier());
+ MATCH_SYSTEM_ONLY, USER_SYSTEM);
}
if (systemUiUid >= 0) {
Slog.d(TAG, "Detected SystemUiUid: " + Integer.toString(systemUiUid));
@@ -1397,8 +1396,9 @@
Slog.d(TAG, "Bluetooth boot completed");
}
mAppOps = mContext.getSystemService(AppOpsManager.class);
- UserManager userManager = mContext.getSystemService(UserManager.class);
- userManager.addUserRestrictionsListener(mUserRestrictionsListener);
+ UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ userManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener);
final boolean isBluetoothDisallowed = isBluetoothDisallowed();
if (isBluetoothDisallowed) {
return;
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 513d86e7..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/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/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 c558f91..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;
}
@@ -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/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 58308d8..751f2db 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -81,7 +81,7 @@
public static final int ADDR_BROADCAST = 15;
/** Logical address used to indicate it is not initialized or invalid. */
- public static final int ADDR_INVALID = -1;
+ public static final int ADDR_INVALID = HdmiDeviceInfo.ADDR_INVALID;
/** Logical address used to indicate the source comes from internal device. */
public static final int ADDR_INTERNAL = HdmiDeviceInfo.ADDR_INTERNAL;
@@ -199,6 +199,7 @@
static final int MESSAGE_SET_SYSTEM_AUDIO_MODE = 0x72;
static final int MESSAGE_REPORT_AUDIO_STATUS = 0x7A;
static final int MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS = 0x7D;
+ static final int MESSAGE_SET_AUDIO_VOLUME_LEVEL = 0x73;
static final int MESSAGE_SYSTEM_AUDIO_MODE_STATUS = 0x7E;
static final int MESSAGE_ROUTING_CHANGE = 0x80;
static final int MESSAGE_ROUTING_INFORMATION = 0x81;
@@ -243,7 +244,7 @@
static final int MESSAGE_CDC_MESSAGE = 0xF8;
static final int MESSAGE_ABORT = 0xFF;
- static final int UNKNOWN_VENDOR_ID = 0xFFFFFF;
+ static final int VENDOR_ID_UNKNOWN = HdmiDeviceInfo.VENDOR_ID_UNKNOWN;
static final int TRUE = 1;
static final int FALSE = 0;
@@ -389,6 +390,17 @@
static final int UNKNOWN_VOLUME = -1;
+ // This constant is used in two operands in the CEC spec.
+ //
+ // CEC 1.4: [Audio Volume Status] (part of [Audio Status]) - operand for <Report Audio Status>
+ // Indicates that the current audio volume status is unknown.
+ //
+ // CEC 2.1a: [Audio Volume Level] - operand for <Set Audio Volume Level>
+ // Part of the Absolute Volume Control feature. Indicates that no change shall be made to the
+ // volume level of the recipient. This allows <Set Audio Volume Level> to be sent to determine
+ // whether the recipient supports Absolute Volume Control.
+ static final int AUDIO_VOLUME_STATUS_UNKNOWN = 0x7F;
+
// States of property PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON
// to decide if turn on the system audio control when power on the device
@IntDef({
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index 8980de1..e827866 100755
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -77,7 +77,7 @@
private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
private int mPortId = Constants.INVALID_PORT_ID;
- private int mVendorId = Constants.UNKNOWN_VENDOR_ID;
+ private int mVendorId = Constants.VENDOR_ID_UNKNOWN;
private int mPowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN;
private String mDisplayName = "";
private int mDeviceType = HdmiDeviceInfo.DEVICE_INACTIVE;
@@ -87,8 +87,15 @@
}
private HdmiDeviceInfo toHdmiDeviceInfo() {
- return new HdmiDeviceInfo(mLogicalAddress, mPhysicalAddress, mPortId, mDeviceType,
- mVendorId, mDisplayName, mPowerStatus);
+ return HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(mLogicalAddress)
+ .setPhysicalAddress(mPhysicalAddress)
+ .setPortId(mPortId)
+ .setVendorId(mVendorId)
+ .setDeviceType(mDeviceType)
+ .setDisplayName(mDisplayName)
+ .setDevicePowerStatus(mPowerStatus)
+ .build();
}
}
diff --git a/services/core/java/com/android/server/hdmi/GiveFeaturesAction.java b/services/core/java/com/android/server/hdmi/GiveFeaturesAction.java
new file mode 100644
index 0000000..4542565
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/GiveFeaturesAction.java
@@ -0,0 +1,79 @@
+/*
+ * 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.hdmi;
+
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.hardware.tv.cec.V1_0.SendMessageResult;
+
+/**
+ * Sends <Give Features> to a target device. This action succeeds if the device responds with
+ * <Report Features> within {@link HdmiConfig.TIMEOUT_MS}.
+ *
+ * This action does not update the CEC network directly; an incoming <Report Features> message
+ * should be handled separately by {@link HdmiCecNetwork}.
+ */
+public class GiveFeaturesAction extends HdmiCecFeatureAction {
+ private static final String TAG = "GiveFeaturesAction";
+
+ private static final int STATE_WAITING_FOR_REPORT_FEATURES = 1;
+
+ private final int mTargetAddress;
+
+ public GiveFeaturesAction(HdmiCecLocalDevice source, int targetAddress,
+ IHdmiControlCallback callback) {
+ super(source, callback);
+
+ mTargetAddress = targetAddress;
+ }
+
+ boolean start() {
+ sendCommand(HdmiCecMessageBuilder.buildGiveFeatures(getSourceAddress(), mTargetAddress),
+ result -> {
+ if (result == SendMessageResult.SUCCESS) {
+ mState = STATE_WAITING_FOR_REPORT_FEATURES;
+ addTimer(STATE_WAITING_FOR_REPORT_FEATURES, HdmiConfig.TIMEOUT_MS);
+ } else {
+ finishWithCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED);
+ }
+ });
+ return true;
+ }
+
+ boolean processCommand(HdmiCecMessage cmd) {
+ if (mState != STATE_WAITING_FOR_REPORT_FEATURES) {
+ return false;
+ }
+ if (cmd instanceof ReportFeaturesMessage) {
+ return handleReportFeatures((ReportFeaturesMessage) cmd);
+ }
+ return false;
+ }
+
+ private boolean handleReportFeatures(ReportFeaturesMessage cmd) {
+ if (cmd.getSource() == mTargetAddress) {
+ // No need to update the network, since it should already have processed this message.
+ finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
+ return true;
+ }
+ return false;
+ }
+
+ void handleTimerEvent(int state) {
+ finishWithCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED);
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index cc86430..1a568c3 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -47,6 +47,7 @@
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
@@ -694,7 +695,19 @@
@ServiceThreadOnly
private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
assertRunOnServiceThread();
- HdmiCecMessage command = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body);
+
+ if (body.length == 0) {
+ Slog.e(TAG, "Message with empty body received.");
+ return;
+ }
+
+ HdmiCecMessage command = HdmiCecMessage.build(srcAddress, dstAddress, body[0],
+ Arrays.copyOfRange(body, 1, body.length));
+
+ if (command.getValidationResult() != HdmiCecMessageValidator.OK) {
+ Slog.e(TAG, "Invalid message received: " + command);
+ }
+
HdmiLogger.debug("[R]:" + command);
addCecMessageToHistory(true /* isReceived */, command);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 3aa2f42..c674ffe 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -17,6 +17,7 @@
package com.android.server.hdmi;
import android.annotation.CallSuper;
+import android.hardware.hdmi.DeviceFeatures;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
@@ -634,7 +635,34 @@
protected abstract List<Integer> getRcFeatures();
- protected abstract List<Integer> getDeviceFeatures();
+ /**
+ * Computes the set of supported device features. To update local state with changes in
+ * the set of supported device features, use {@link #getDeviceFeatures} instead.
+ */
+ protected DeviceFeatures computeDeviceFeatures() {
+ return DeviceFeatures.NO_FEATURES_SUPPORTED;
+ }
+
+ /**
+ * Computes the set of supported device features, and updates local state to match.
+ */
+ private void updateDeviceFeatures() {
+ synchronized (mLock) {
+ setDeviceInfo(getDeviceInfo().toBuilder()
+ .setDeviceFeatures(computeDeviceFeatures())
+ .build());
+ }
+ }
+
+ /**
+ * Computes and returns the set of supported device features. Updates local state to match.
+ */
+ protected final DeviceFeatures getDeviceFeatures() {
+ updateDeviceFeatures();
+ synchronized (mLock) {
+ return getDeviceInfo().getDeviceFeatures();
+ }
+ }
@Constants.HandleMessageResult
protected int handleGiveFeatures(HdmiCecMessage message) {
@@ -655,11 +683,17 @@
int rcProfile = getRcProfile();
List<Integer> rcFeatures = getRcFeatures();
- List<Integer> deviceFeatures = getDeviceFeatures();
+ DeviceFeatures deviceFeatures = getDeviceFeatures();
+
+
+ int logicalAddress;
+ synchronized (mLock) {
+ logicalAddress = mDeviceInfo.getLogicalAddress();
+ }
mService.sendCecCommand(
- HdmiCecMessageBuilder.buildReportFeatures(
- mDeviceInfo.getLogicalAddress(),
+ ReportFeaturesMessage.build(
+ logicalAddress,
mService.getCecVersion(),
localDeviceTypes,
rcProfile,
@@ -922,6 +956,7 @@
final void handleAddressAllocated(int logicalAddress, int reason) {
assertRunOnServiceThread();
mPreferredAddress = logicalAddress;
+ updateDeviceFeatures();
if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
reportFeatures();
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 7e71589..2ef3ebf 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -15,6 +15,9 @@
*/
package com.android.server.hdmi;
+import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED;
+import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED;
+
import static com.android.server.hdmi.Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
import static com.android.server.hdmi.Constants.PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
import static com.android.server.hdmi.Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
@@ -22,6 +25,7 @@
import android.annotation.Nullable;
import android.content.ActivityNotFoundException;
import android.content.Intent;
+import android.hardware.hdmi.DeviceFeatures;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
@@ -177,14 +181,12 @@
}
@Override
- protected List<Integer> getDeviceFeatures() {
- List<Integer> deviceFeatures = new ArrayList<>();
+ protected DeviceFeatures computeDeviceFeatures() {
+ boolean arcSupport = SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true);
- if (SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) {
- deviceFeatures.add(Constants.DEVICE_FEATURE_SOURCE_SUPPORTS_ARC_RX);
- }
-
- return deviceFeatures;
+ return DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
+ .setArcRxSupport(arcSupport ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED)
+ .build();
}
@Override
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 4f55249..90b4f76 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -28,8 +28,6 @@
import com.android.server.hdmi.Constants.LocalActivePort;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
-import com.google.android.collect.Lists;
-
import java.util.ArrayList;
import java.util.List;
@@ -358,11 +356,6 @@
return features;
}
- @Override
- protected List<Integer> getDeviceFeatures() {
- return Lists.newArrayList();
- }
-
// Active source claiming needs to be handled in Service
// since service can decide who will be the active source when the device supports
// multiple device types in this method.
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index c2ed24a..afcd3dd 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -16,6 +16,8 @@
package com.android.server.hdmi;
+import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED;
+import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED;
import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CEC_DISABLE;
import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION;
import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE;
@@ -31,6 +33,7 @@
import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL;
import android.annotation.Nullable;
+import android.hardware.hdmi.DeviceFeatures;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
@@ -346,7 +349,7 @@
if (info == null) {
// No CEC/MHL device is present at the port. Attempt to switch to
// the hardware port itself for non-CEC devices that may be connected.
- info = new HdmiDeviceInfo(path, getActivePortId());
+ info = HdmiDeviceInfo.hardwarePort(path, getActivePortId());
}
}
mService.invokeInputChangeListener(info);
@@ -1548,9 +1551,7 @@
}
@Override
- protected List<Integer> getDeviceFeatures() {
- List<Integer> deviceFeatures = new ArrayList<>();
-
+ protected DeviceFeatures computeDeviceFeatures() {
boolean hasArcPort = false;
List<HdmiPortInfo> ports = mService.getPortInfo();
for (HdmiPortInfo port : ports) {
@@ -1559,11 +1560,11 @@
break;
}
}
- if (hasArcPort) {
- deviceFeatures.add(Constants.DEVICE_FEATURE_SINK_SUPPORTS_ARC_TX);
- }
- deviceFeatures.add(Constants.DEVICE_FEATURE_TV_SUPPORTS_RECORD_TV_SCREEN);
- return deviceFeatures;
+
+ return DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
+ .setRecordTvScreenSupport(FEATURE_SUPPORTED)
+ .setArcTxSupport(hasArcPort ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED)
+ .build();
}
@Override
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
index e3292a3..290cae5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
@@ -16,6 +16,8 @@
package com.android.server.hdmi;
+import static com.android.server.hdmi.HdmiCecMessageValidator.ValidationResult;
+
import android.annotation.Nullable;
import com.android.server.hdmi.Constants.FeatureOpcode;
@@ -26,11 +28,13 @@
import java.util.Objects;
/**
- * A class to encapsulate HDMI-CEC message used for the devices connected via
- * HDMI cable to communicate with one another. A message is defined by its
- * source and destination address, command (or opcode), and optional parameters.
+ * Encapsulates the data that defines an HDMI-CEC message: source and destination address,
+ * command (or opcode), and optional parameters. Also stores the result of validating the message.
+ *
+ * Subclasses of this class represent specific messages that have been validated, and expose their
+ * parsed parameters.
*/
-public final class HdmiCecMessage {
+public class HdmiCecMessage {
public static final byte[] EMPTY_PARAM = EmptyArray.BYTE;
private final int mSource;
@@ -39,24 +43,60 @@
private final int mOpcode;
private final byte[] mParams;
+ private final int mValidationResult;
+
/**
- * Constructor.
+ * Constructor that allows the caller to provide the validation result.
+ * Must only be called by subclasses; other callers should use {@link #build}.
*/
- public HdmiCecMessage(int source, int destination, int opcode, byte[] params) {
+ protected HdmiCecMessage(int source, int destination, int opcode, byte[] params,
+ @ValidationResult int validationResult) {
mSource = source;
mDestination = destination;
mOpcode = opcode & 0xFF;
mParams = Arrays.copyOf(params, params.length);
+ mValidationResult = validationResult;
+ }
+
+ private HdmiCecMessage(int source, int destination, int opcode, byte[] params) {
+ this(source, destination, opcode, params,
+ HdmiCecMessageValidator.validate(source, destination, opcode & 0xFF, params));
+ }
+
+ /**
+ * Constructs and validates a message. The result of validation will be accessible via
+ * {@link #getValidationResult}.
+ *
+ * Intended for parsing incoming messages, as it takes raw bytes as message parameters.
+ *
+ * If the opcode has its own subclass of this one, this method will instead validate and build
+ * the message using the logic in that class. If successful, it will return a validated
+ * instance of that class that exposes parsed parameters.
+ */
+ static HdmiCecMessage build(int source, int destination, int opcode, byte[] params) {
+ switch (opcode & 0xFF) {
+ case Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL:
+ return SetAudioVolumeLevelMessage.build(source, destination, params);
+ case Constants.MESSAGE_REPORT_FEATURES:
+ return ReportFeaturesMessage.build(source, destination, params);
+ default:
+ return new HdmiCecMessage(source, destination, opcode & 0xFF, params);
+ }
+ }
+
+ static HdmiCecMessage build(int source, int destination, int opcode) {
+ return new HdmiCecMessage(source, destination, opcode, EMPTY_PARAM);
}
@Override
public boolean equals(@Nullable Object message) {
if (message instanceof HdmiCecMessage) {
HdmiCecMessage that = (HdmiCecMessage) message;
- return this.mSource == that.getSource() &&
- this.mDestination == that.getDestination() &&
- this.mOpcode == that.getOpcode() &&
- Arrays.equals(this.mParams, that.getParams());
+ return this.mSource == that.getSource()
+ && this.mDestination == that.getDestination()
+ && this.mOpcode == that.getOpcode()
+ && Arrays.equals(this.mParams, that.getParams())
+ && this.mValidationResult == that.getValidationResult();
}
return false;
}
@@ -111,6 +151,13 @@
return mParams;
}
+ /**
+ * Returns the validation result of the message.
+ */
+ public int getValidationResult() {
+ return mValidationResult;
+ }
+
@Override
public String toString() {
StringBuilder s = new StringBuilder();
@@ -129,9 +176,30 @@
}
}
}
+ if (mValidationResult != HdmiCecMessageValidator.OK) {
+ s.append(String.format(" <Validation error: %s>",
+ validationResultToString(mValidationResult)));
+ }
return s.toString();
}
+ private static String validationResultToString(@ValidationResult int validationResult) {
+ switch (validationResult) {
+ case HdmiCecMessageValidator.OK:
+ return "ok";
+ case HdmiCecMessageValidator.ERROR_SOURCE:
+ return "invalid source";
+ case HdmiCecMessageValidator.ERROR_DESTINATION:
+ return "invalid destination";
+ case HdmiCecMessageValidator.ERROR_PARAMETER:
+ return "invalid parameters";
+ case HdmiCecMessageValidator.ERROR_PARAMETER_SHORT:
+ return "short parameters";
+ default:
+ return "unknown error";
+ }
+ }
+
private static String opcodeToString(@FeatureOpcode int opcode) {
switch (opcode) {
case Constants.MESSAGE_FEATURE_ABORT:
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 1c8f21f..adcff4c 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -16,17 +16,15 @@
package com.android.server.hdmi;
-import android.hardware.hdmi.HdmiControlManager;
-import android.hardware.hdmi.HdmiDeviceInfo;
-
import com.android.server.hdmi.Constants.AudioCodec;
import java.io.UnsupportedEncodingException;
-import java.util.Arrays;
-import java.util.List;
/**
* A helper class to build {@link HdmiCecMessage} from various cec commands.
+ *
+ * If a message type has its own specific subclass of {@link HdmiCecMessage},
+ * its static factory method is instead declared in that subclass.
*/
public class HdmiCecMessageBuilder {
private static final int OSD_NAME_MAX_LENGTH = 14;
@@ -34,20 +32,6 @@
private HdmiCecMessageBuilder() {}
/**
- * Build {@link HdmiCecMessage} from raw data.
- *
- * @param src source address of command
- * @param dest destination address of command
- * @param body body of message. It includes opcode.
- * @return newly created {@link HdmiCecMessage}
- */
- static HdmiCecMessage of(int src, int dest, byte[] body) {
- byte opcode = body[0];
- byte params[] = Arrays.copyOfRange(body, 1, body.length);
- return new HdmiCecMessage(src, dest, opcode, params);
- }
-
- /**
* Build <Feature Abort> command. <Feature Abort> consists of
* 1 byte original opcode and 1 byte reason fields with basic fields.
*
@@ -63,7 +47,7 @@
(byte) (originalOpcode & 0xFF),
(byte) (reason & 0xFF),
};
- return buildCommand(src, dest, Constants.MESSAGE_FEATURE_ABORT, params);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_FEATURE_ABORT, params);
}
/**
@@ -74,7 +58,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildGivePhysicalAddress(int src, int dest) {
- return buildCommand(src, dest, Constants.MESSAGE_GIVE_PHYSICAL_ADDRESS);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_PHYSICAL_ADDRESS);
}
/**
@@ -85,7 +69,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildGiveOsdNameCommand(int src, int dest) {
- return buildCommand(src, dest, Constants.MESSAGE_GIVE_OSD_NAME);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_OSD_NAME);
}
/**
@@ -96,7 +80,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildGiveDeviceVendorIdCommand(int src, int dest) {
- return buildCommand(src, dest, Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID);
}
/**
@@ -121,7 +105,7 @@
(byte) (normalized.charAt(2) & 0xFF),
};
// <Set Menu Language> is broadcast message.
- return buildCommand(src, Constants.ADDR_BROADCAST,
+ return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST,
Constants.MESSAGE_SET_MENU_LANGUAGE, params);
}
@@ -141,7 +125,7 @@
} catch (UnsupportedEncodingException e) {
return null;
}
- return buildCommand(src, dest, Constants.MESSAGE_SET_OSD_NAME, params);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_SET_OSD_NAME, params);
}
/**
@@ -164,7 +148,7 @@
(byte) (deviceType & 0xFF)
};
// <Report Physical Address> is broadcast message.
- return buildCommand(src, Constants.ADDR_BROADCAST,
+ return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST,
Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS, params);
}
@@ -185,7 +169,7 @@
(byte) (vendorId & 0xFF)
};
// <Device Vendor Id> is broadcast message.
- return buildCommand(src, Constants.ADDR_BROADCAST,
+ return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST,
Constants.MESSAGE_DEVICE_VENDOR_ID, params);
}
@@ -202,7 +186,7 @@
byte[] params = new byte[] {
(byte) (version & 0xFF)
};
- return buildCommand(src, dest, Constants.MESSAGE_CEC_VERSION, params);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_CEC_VERSION, params);
}
/**
@@ -213,7 +197,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildRequestArcInitiation(int src, int dest) {
- return buildCommand(src, dest, Constants.MESSAGE_REQUEST_ARC_INITIATION);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_REQUEST_ARC_INITIATION);
}
/**
@@ -224,7 +208,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildInitiateArc(int src, int dest) {
- return buildCommand(src, dest, Constants.MESSAGE_INITIATE_ARC);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_INITIATE_ARC);
}
/**
@@ -235,7 +219,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildTerminateArc(int src, int dest) {
- return buildCommand(src, dest, Constants.MESSAGE_TERMINATE_ARC);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_TERMINATE_ARC);
}
/**
@@ -246,7 +230,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildRequestArcTermination(int src, int dest) {
- return buildCommand(src, dest, Constants.MESSAGE_REQUEST_ARC_TERMINATION);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_REQUEST_ARC_TERMINATION);
}
/**
@@ -257,7 +241,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildReportArcInitiated(int src, int dest) {
- return buildCommand(src, dest, Constants.MESSAGE_REPORT_ARC_INITIATED);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_REPORT_ARC_INITIATED);
}
/**
@@ -268,7 +252,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildReportArcTerminated(int src, int dest) {
- return buildCommand(src, dest, Constants.MESSAGE_REPORT_ARC_TERMINATED);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_REPORT_ARC_TERMINATED);
}
@@ -286,7 +270,8 @@
for (int i = 0; i < params.length ; i++){
params[i] = (byte) (audioFormats[i] & 0xff);
}
- return buildCommand(src, dest, Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR, params);
+ return HdmiCecMessage.build(
+ src, dest, Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR, params);
}
@@ -298,7 +283,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildTextViewOn(int src, int dest) {
- return buildCommand(src, dest, Constants.MESSAGE_TEXT_VIEW_ON);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_TEXT_VIEW_ON);
}
/**
@@ -308,7 +293,8 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildRequestActiveSource(int src) {
- return buildCommand(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_REQUEST_ACTIVE_SOURCE);
+ return HdmiCecMessage.build(
+ src, Constants.ADDR_BROADCAST, Constants.MESSAGE_REQUEST_ACTIVE_SOURCE);
}
/**
@@ -319,7 +305,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildActiveSource(int src, int physicalAddress) {
- return buildCommand(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_ACTIVE_SOURCE,
+ return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_ACTIVE_SOURCE,
physicalAddressToParam(physicalAddress));
}
@@ -331,7 +317,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildInactiveSource(int src, int physicalAddress) {
- return buildCommand(src, Constants.ADDR_TV,
+ return HdmiCecMessage.build(src, Constants.ADDR_TV,
Constants.MESSAGE_INACTIVE_SOURCE, physicalAddressToParam(physicalAddress));
}
@@ -345,7 +331,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildSetStreamPath(int src, int streamPath) {
- return buildCommand(src, Constants.ADDR_BROADCAST,
+ return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST,
Constants.MESSAGE_SET_STREAM_PATH, physicalAddressToParam(streamPath));
}
@@ -364,7 +350,7 @@
(byte) ((oldPath >> 8) & 0xFF), (byte) (oldPath & 0xFF),
(byte) ((newPath >> 8) & 0xFF), (byte) (newPath & 0xFF)
};
- return buildCommand(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_ROUTING_CHANGE,
+ return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_ROUTING_CHANGE,
param);
}
@@ -378,7 +364,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildRoutingInformation(int src, int physicalAddress) {
- return buildCommand(src, Constants.ADDR_BROADCAST,
+ return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST,
Constants.MESSAGE_ROUTING_INFORMATION, physicalAddressToParam(physicalAddress));
}
@@ -390,7 +376,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildGiveDevicePowerStatus(int src, int dest) {
- return buildCommand(src, dest, Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS);
}
/**
@@ -405,7 +391,7 @@
byte[] param = new byte[] {
(byte) (powerStatus & 0xFF)
};
- return buildCommand(src, dest, Constants.MESSAGE_REPORT_POWER_STATUS, param);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_REPORT_POWER_STATUS, param);
}
/**
@@ -420,7 +406,7 @@
byte[] param = new byte[] {
(byte) (menuStatus & 0xFF)
};
- return buildCommand(src, dest, Constants.MESSAGE_MENU_STATUS, param);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_MENU_STATUS, param);
}
/**
@@ -435,10 +421,10 @@
static HdmiCecMessage buildSystemAudioModeRequest(int src, int avr, int avrPhysicalAddress,
boolean enableSystemAudio) {
if (enableSystemAudio) {
- return buildCommand(src, avr, Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST,
+ return HdmiCecMessage.build(src, avr, Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST,
physicalAddressToParam(avrPhysicalAddress));
} else {
- return buildCommand(src, avr, Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST);
+ return HdmiCecMessage.build(src, avr, Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST);
}
}
@@ -479,7 +465,8 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildReportShortAudioDescriptor(int src, int des, byte[] sadBytes) {
- return buildCommand(src, des, Constants.MESSAGE_REPORT_SHORT_AUDIO_DESCRIPTOR, sadBytes);
+ return HdmiCecMessage.build(
+ src, des, Constants.MESSAGE_REPORT_SHORT_AUDIO_DESCRIPTOR, sadBytes);
}
/**
@@ -490,7 +477,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildGiveAudioStatus(int src, int dest) {
- return buildCommand(src, dest, Constants.MESSAGE_GIVE_AUDIO_STATUS);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_AUDIO_STATUS);
}
/**
@@ -505,7 +492,7 @@
static HdmiCecMessage buildReportAudioStatus(int src, int dest, int volume, boolean mute) {
byte status = (byte) ((byte) (mute ? 1 << 7 : 0) | ((byte) volume & 0x7F));
byte[] params = new byte[] { status };
- return buildCommand(src, dest, Constants.MESSAGE_REPORT_AUDIO_STATUS, params);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_REPORT_AUDIO_STATUS, params);
}
/**
@@ -529,7 +516,8 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildUserControlPressed(int src, int dest, byte[] commandParam) {
- return buildCommand(src, dest, Constants.MESSAGE_USER_CONTROL_PRESSED, commandParam);
+ return HdmiCecMessage.build(
+ src, dest, Constants.MESSAGE_USER_CONTROL_PRESSED, commandParam);
}
/**
@@ -540,7 +528,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildUserControlReleased(int src, int dest) {
- return buildCommand(src, dest, Constants.MESSAGE_USER_CONTROL_RELEASED);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_USER_CONTROL_RELEASED);
}
/**
@@ -551,7 +539,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildGiveSystemAudioModeStatus(int src, int dest) {
- return buildCommand(src, dest, Constants.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS);
}
/**
@@ -562,7 +550,7 @@
* @return newly created {@link HdmiCecMessage}
*/
public static HdmiCecMessage buildStandby(int src, int dest) {
- return buildCommand(src, dest, Constants.MESSAGE_STANDBY);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_STANDBY);
}
/**
@@ -574,7 +562,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildVendorCommand(int src, int dest, byte[] params) {
- return buildCommand(src, dest, Constants.MESSAGE_VENDOR_COMMAND, params);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_VENDOR_COMMAND, params);
}
/**
@@ -593,7 +581,7 @@
params[1] = (byte) ((vendorId >> 8) & 0xFF);
params[2] = (byte) (vendorId & 0xFF);
System.arraycopy(operands, 0, params, 3, operands.length);
- return buildCommand(src, dest, Constants.MESSAGE_VENDOR_COMMAND_WITH_ID, params);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_VENDOR_COMMAND_WITH_ID, params);
}
/**
@@ -605,7 +593,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildRecordOn(int src, int dest, byte[] params) {
- return buildCommand(src, dest, Constants.MESSAGE_RECORD_ON, params);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_RECORD_ON, params);
}
/**
@@ -616,7 +604,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildRecordOff(int src, int dest) {
- return buildCommand(src, dest, Constants.MESSAGE_RECORD_OFF);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_RECORD_OFF);
}
/**
@@ -628,7 +616,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildSetDigitalTimer(int src, int dest, byte[] params) {
- return buildCommand(src, dest, Constants.MESSAGE_SET_DIGITAL_TIMER, params);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_SET_DIGITAL_TIMER, params);
}
/**
@@ -640,7 +628,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildSetAnalogueTimer(int src, int dest, byte[] params) {
- return buildCommand(src, dest, Constants.MESSAGE_SET_ANALOG_TIMER, params);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_SET_ANALOG_TIMER, params);
}
/**
@@ -652,7 +640,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildSetExternalTimer(int src, int dest, byte[] params) {
- return buildCommand(src, dest, Constants.MESSAGE_SET_EXTERNAL_TIMER, params);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_SET_EXTERNAL_TIMER, params);
}
/**
@@ -664,7 +652,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildClearDigitalTimer(int src, int dest, byte[] params) {
- return buildCommand(src, dest, Constants.MESSAGE_CLEAR_DIGITAL_TIMER, params);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_CLEAR_DIGITAL_TIMER, params);
}
/**
@@ -676,7 +664,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildClearAnalogueTimer(int src, int dest, byte[] params) {
- return buildCommand(src, dest, Constants.MESSAGE_CLEAR_ANALOG_TIMER, params);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_CLEAR_ANALOG_TIMER, params);
}
/**
@@ -688,73 +676,16 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildClearExternalTimer(int src, int dest, byte[] params) {
- return buildCommand(src, dest, Constants.MESSAGE_CLEAR_EXTERNAL_TIMER, params);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_CLEAR_EXTERNAL_TIMER, params);
}
static HdmiCecMessage buildGiveFeatures(int src, int dest) {
- return buildCommand(src, dest, Constants.MESSAGE_GIVE_FEATURES);
- }
-
- static HdmiCecMessage buildReportFeatures(int src,
- @HdmiControlManager.HdmiCecVersion int cecVersion,
- List<Integer> allDeviceTypes, @Constants.RcProfile int rcProfile,
- List<Integer> rcFeatures,
- List<Integer> deviceFeatures) {
- byte cecVersionByte = (byte) (cecVersion & 0xFF);
- byte deviceTypes = 0;
- for (Integer deviceType : allDeviceTypes) {
- deviceTypes |= 1 << hdmiDeviceInfoDeviceTypeToShiftValue(deviceType);
- }
-
- byte rcProfileByte = 0;
- rcProfileByte |= rcProfile << 6;
- if (rcProfile == Constants.RC_PROFILE_SOURCE) {
- for (@Constants.RcProfileSource Integer rcFeature : rcFeatures) {
- rcProfileByte |= 1 << rcFeature;
- }
- } else {
- @Constants.RcProfileTv byte rcProfileTv = (byte) (rcFeatures.get(0) & 0xFFFF);
- rcProfileByte |= rcProfileTv;
- }
-
- byte deviceFeaturesByte = 0;
- for (@Constants.DeviceFeature Integer deviceFeature : deviceFeatures) {
- deviceFeaturesByte |= 1 << deviceFeature;
- }
-
- byte[] params = {cecVersionByte, deviceTypes, rcProfileByte, deviceFeaturesByte};
- return buildCommand(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_REPORT_FEATURES,
- params);
+ return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_FEATURES);
}
/***** Please ADD new buildXXX() methods above. ******/
/**
- * Build a {@link HdmiCecMessage} without extra parameter.
- *
- * @param src source address of command
- * @param dest destination address of command
- * @param opcode opcode for a message
- * @return newly created {@link HdmiCecMessage}
- */
- private static HdmiCecMessage buildCommand(int src, int dest, int opcode) {
- return new HdmiCecMessage(src, dest, opcode, HdmiCecMessage.EMPTY_PARAM);
- }
-
- /**
- * Build a {@link HdmiCecMessage} with given values.
- *
- * @param src source address of command
- * @param dest destination address of command
- * @param opcode opcode for a message
- * @param params extra parameters for command
- * @return newly created {@link HdmiCecMessage}
- */
- private static HdmiCecMessage buildCommand(int src, int dest, int opcode, byte[] params) {
- return new HdmiCecMessage(src, dest, opcode, params);
- }
-
- /**
* Build a {@link HdmiCecMessage} with a boolean param and other given values.
*
* @param src source address of command
@@ -768,7 +699,7 @@
byte[] params = new byte[]{
param ? (byte) 0b1 : 0b0
};
- return buildCommand(src, des, opcode, params);
+ return HdmiCecMessage.build(src, des, opcode, params);
}
private static byte[] physicalAddressToParam(int physicalAddress) {
@@ -777,24 +708,4 @@
(byte) (physicalAddress & 0xFF)
};
}
-
- @Constants.DeviceType
- private static int hdmiDeviceInfoDeviceTypeToShiftValue(int deviceType) {
- switch (deviceType) {
- case HdmiDeviceInfo.DEVICE_TV:
- return Constants.ALL_DEVICE_TYPES_TV;
- case HdmiDeviceInfo.DEVICE_RECORDER:
- return Constants.ALL_DEVICE_TYPES_RECORDER;
- case HdmiDeviceInfo.DEVICE_TUNER:
- return Constants.ALL_DEVICE_TYPES_TUNER;
- case HdmiDeviceInfo.DEVICE_PLAYBACK:
- return Constants.ALL_DEVICE_TYPES_PLAYBACK;
- case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM:
- return Constants.ALL_DEVICE_TYPES_AUDIO_SYSTEM;
- case HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH:
- return Constants.ALL_DEVICE_TYPES_SWITCH;
- default:
- throw new IllegalArgumentException("Unhandled device type: " + deviceType);
- }
- }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index 8a727c6..220a438 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -16,23 +16,34 @@
package com.android.server.hdmi;
+import android.annotation.IntDef;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.util.SparseArray;
/**
- * A helper class to validates {@link HdmiCecMessage}.
+ * A helper class to validate {@link HdmiCecMessage}.
+ *
+ * If a message type has its own specific subclass of {@link HdmiCecMessage},
+ * validation is performed in that subclass instead.
*/
public class HdmiCecMessageValidator {
private static final String TAG = "HdmiCecMessageValidator";
+ @IntDef({
+ OK,
+ ERROR_SOURCE,
+ ERROR_DESTINATION,
+ ERROR_PARAMETER,
+ ERROR_PARAMETER_SHORT,
+ })
+ public @interface ValidationResult {};
+
static final int OK = 0;
static final int ERROR_SOURCE = 1;
static final int ERROR_DESTINATION = 2;
static final int ERROR_PARAMETER = 3;
static final int ERROR_PARAMETER_SHORT = 4;
- private final HdmiControlService mService;
-
interface ParameterValidator {
/**
* @return errorCode errorCode can be {@link #OK}, {@link #ERROR_PARAMETER} or
@@ -42,13 +53,13 @@
}
// Only the direct addressing is allowed.
- private static final int DEST_DIRECT = 1 << 0;
+ public static final int DEST_DIRECT = 1 << 0;
// Only the broadcast addressing is allowed.
- private static final int DEST_BROADCAST = 1 << 1;
+ public static final int DEST_BROADCAST = 1 << 1;
// Both the direct and the broadcast addressing are allowed.
- private static final int DEST_ALL = DEST_DIRECT | DEST_BROADCAST;
+ public static final int DEST_ALL = DEST_DIRECT | DEST_BROADCAST;
// True if the messages from address 15 (unregistered) are allowed.
- private static final int SRC_UNREGISTERED = 1 << 2;
+ public static final int SRC_UNREGISTERED = 1 << 2;
private static class ValidationInfo {
public final ParameterValidator parameterValidator;
@@ -60,11 +71,11 @@
}
}
- final SparseArray<ValidationInfo> mValidationInfo = new SparseArray<>();
+ private HdmiCecMessageValidator() {}
- public HdmiCecMessageValidator(HdmiControlService service) {
- mService = service;
+ private static final SparseArray<ValidationInfo> sValidationInfo = new SparseArray<>();
+ static {
// Messages related to the physical address.
PhysicalAddressValidator physicalAddressValidator = new PhysicalAddressValidator();
addValidationInfo(Constants.MESSAGE_ACTIVE_SOURCE,
@@ -234,8 +245,6 @@
// Messages for Feature Discovery.
addValidationInfo(Constants.MESSAGE_GIVE_FEATURES, noneValidator,
DEST_DIRECT | SRC_UNREGISTERED);
- addValidationInfo(Constants.MESSAGE_REPORT_FEATURES, new VariableLengthValidator(4, 14),
- DEST_BROADCAST);
// Messages for Dynamic Auto Lipsync
addValidationInfo(Constants.MESSAGE_REQUEST_CURRENT_LATENCY, physicalAddressValidator,
@@ -250,59 +259,62 @@
DEST_BROADCAST | SRC_UNREGISTERED);
}
- private void addValidationInfo(int opcode, ParameterValidator validator, int addrType) {
- mValidationInfo.append(opcode, new ValidationInfo(validator, addrType));
+ private static void addValidationInfo(int opcode, ParameterValidator validator, int addrType) {
+ sValidationInfo.append(opcode, new ValidationInfo(validator, addrType));
}
- int isValid(HdmiCecMessage message, boolean isMessageReceived) {
- int opcode = message.getOpcode();
- ValidationInfo info = mValidationInfo.get(opcode);
+ /**
+ * Validates all parameters of a HDMI-CEC message using static information stored in this class.
+ */
+ @ValidationResult
+ static int validate(int source, int destination, int opcode, byte[] params) {
+ ValidationInfo info = sValidationInfo.get(opcode);
+
if (info == null) {
- HdmiLogger.warning("No validation information for the message: " + message);
+ HdmiLogger.warning("No validation information for the opcode: " + opcode);
return OK;
}
+ int addressValidationResult = validateAddress(source, destination, info.addressType);
+ if (addressValidationResult != OK) {
+ return addressValidationResult;
+ }
+
+ // Validate parameters
+ int errorCode = info.parameterValidator.isValid(params);
+ if (errorCode != OK) {
+ return errorCode;
+ }
+
+ return OK;
+ }
+
+ /**
+ * Validates the source and destination addresses of a HDMI-CEC message according to input
+ * address type. Allows address validation logic to be expressed concisely without depending
+ * on static information in this class.
+ * @param source Source address to validate
+ * @param destination Destination address to validate
+ * @param addressType Rules for validating the addresses - e.g. {@link #DEST_BROADCAST}
+ */
+ @ValidationResult
+ static int validateAddress(int source, int destination, int addressType) {
// Check the source field.
- if (message.getSource() == Constants.ADDR_UNREGISTERED &&
- (info.addressType & SRC_UNREGISTERED) == 0) {
- HdmiLogger.warning("Unexpected source: " + message);
+ if (source == Constants.ADDR_UNREGISTERED
+ && (addressType & SRC_UNREGISTERED) == 0) {
return ERROR_SOURCE;
}
- if (isMessageReceived) {
- // Check if the source's logical address and local device's logical
- // address are the same.
- for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
- synchronized (device.mLock) {
- if (message.getSource() == device.getDeviceInfo().getLogicalAddress()
- && message.getSource() != Constants.ADDR_UNREGISTERED) {
- HdmiLogger.warning(
- "Unexpected source: message sent from device itself, " + message);
- return ERROR_SOURCE;
- }
- }
- }
- }
-
// Check the destination field.
- if (message.getDestination() == Constants.ADDR_BROADCAST) {
- if ((info.addressType & DEST_BROADCAST) == 0) {
- HdmiLogger.warning("Unexpected broadcast message: " + message);
+ if (destination == Constants.ADDR_BROADCAST) {
+ if ((addressType & DEST_BROADCAST) == 0) {
return ERROR_DESTINATION;
}
} else { // Direct addressing.
- if ((info.addressType & DEST_DIRECT) == 0) {
- HdmiLogger.warning("Unexpected direct message: " + message);
+ if ((addressType & DEST_DIRECT) == 0) {
return ERROR_DESTINATION;
}
}
-
- // Check the parameter type.
- int errorCode = info.parameterValidator.isValid(message.getParams());
- if (errorCode != OK) {
- HdmiLogger.warning("Unexpected parameters: " + message);
- return errorCode;
- }
return OK;
}
@@ -336,7 +348,7 @@
}
}
- private boolean isValidPhysicalAddress(byte[] params, int offset) {
+ private static boolean isValidPhysicalAddress(byte[] params, int offset) {
int physicalAddress = HdmiUtils.twoBytesToInt(params, offset);
while (physicalAddress != 0) {
int maskedAddress = physicalAddress & 0xF000;
@@ -345,19 +357,6 @@
return false;
}
}
-
- if (!mService.isTvDevice()) {
- // If the device is not TV, we can't convert path to port-id, so stop here.
- return true;
- }
- int path = HdmiUtils.twoBytesToInt(params, offset);
- if (path != Constants.INVALID_PHYSICAL_ADDRESS && path == mService.getPhysicalAddress()) {
- return true;
- }
- int portId = mService.pathToPortId(path);
- if (portId == Constants.INVALID_PORT_ID) {
- return false;
- }
return true;
}
@@ -380,7 +379,7 @@
return success ? OK : ERROR_PARAMETER;
}
- private boolean isWithinRange(int value, int min, int max) {
+ private static boolean isWithinRange(int value, int min, int max) {
value = value & 0xFF;
return (value >= min && value <= max);
}
@@ -392,7 +391,7 @@
* @param value Display Control
* @return true if the Display Control is valid
*/
- private boolean isValidDisplayControl(int value) {
+ private static boolean isValidDisplayControl(int value) {
value = value & 0xFF;
return (value == 0x00 || value == 0x40 || value == 0x80 || value == 0xC0);
}
@@ -407,7 +406,7 @@
* @param maxLength Maximum length of string to be evaluated
* @return true if the given type is valid
*/
- private boolean isValidAsciiString(byte[] params, int offset, int maxLength) {
+ private static boolean isValidAsciiString(byte[] params, int offset, int maxLength) {
for (int i = offset; i < params.length && i < maxLength; i++) {
if (!isWithinRange(params[i], 0x20, 0x7E)) {
return false;
@@ -423,7 +422,7 @@
* @param value day of month
* @return true if the day of month is valid
*/
- private boolean isValidDayOfMonth(int value) {
+ private static boolean isValidDayOfMonth(int value) {
return isWithinRange(value, 1, 31);
}
@@ -434,7 +433,7 @@
* @param value month of year
* @return true if the month of year is valid
*/
- private boolean isValidMonthOfYear(int value) {
+ private static boolean isValidMonthOfYear(int value) {
return isWithinRange(value, 1, 12);
}
@@ -445,7 +444,7 @@
* @param value hour
* @return true if the hour is valid
*/
- private boolean isValidHour(int value) {
+ private static boolean isValidHour(int value) {
return isWithinRange(value, 0, 23);
}
@@ -456,7 +455,7 @@
* @param value minute
* @return true if the minute is valid
*/
- private boolean isValidMinute(int value) {
+ private static boolean isValidMinute(int value) {
return isWithinRange(value, 0, 59);
}
@@ -467,7 +466,7 @@
* @param value duration hours
* @return true if the duration hours is valid
*/
- private boolean isValidDurationHours(int value) {
+ private static boolean isValidDurationHours(int value) {
return isWithinRange(value, 0, 99);
}
@@ -478,7 +477,7 @@
* @param value recording sequence
* @return true if the given recording sequence is valid
*/
- private boolean isValidRecordingSequence(int value) {
+ private static boolean isValidRecordingSequence(int value) {
value = value & 0xFF;
// Validate bit 7 is set to zero
if ((value & 0x80) != 0x00) {
@@ -496,7 +495,7 @@
* @param value analogue broadcast type
* @return true if the analogue broadcast type is valid
*/
- private boolean isValidAnalogueBroadcastType(int value) {
+ private static boolean isValidAnalogueBroadcastType(int value) {
return isWithinRange(value, 0x00, 0x02);
}
@@ -508,7 +507,7 @@
* @param value analogue frequency
* @return true if the analogue frequency is valid
*/
- private boolean isValidAnalogueFrequency(int value) {
+ private static boolean isValidAnalogueFrequency(int value) {
value = value & 0xFFFF;
return (value != 0x000 && value != 0xFFFF);
}
@@ -520,7 +519,7 @@
* @param value broadcast system
* @return true if the broadcast system is valid
*/
- private boolean isValidBroadcastSystem(int value) {
+ private static boolean isValidBroadcastSystem(int value) {
return isWithinRange(value, 0, 31);
}
@@ -531,7 +530,7 @@
* @param value Digital Broadcast System
* @return true if the Digital Broadcast System is ARIB type
*/
- private boolean isAribDbs(int value) {
+ private static boolean isAribDbs(int value) {
return (value == 0x00 || isWithinRange(value, 0x08, 0x0A));
}
@@ -542,7 +541,7 @@
* @param value Digital Broadcast System
* @return true if the Digital Broadcast System is ATSC type
*/
- private boolean isAtscDbs(int value) {
+ private static boolean isAtscDbs(int value) {
return (value == 0x01 || isWithinRange(value, 0x10, 0x12));
}
@@ -553,7 +552,7 @@
* @param value Digital Broadcast System
* @return true if the Digital Broadcast System is DVB type
*/
- private boolean isDvbDbs(int value) {
+ private static boolean isDvbDbs(int value) {
return (value == 0x02 || isWithinRange(value, 0x18, 0x1B));
}
@@ -565,7 +564,7 @@
* @param value Digital Broadcast System
* @return true if the Digital Broadcast System is valid
*/
- private boolean isValidDigitalBroadcastSystem(int value) {
+ private static boolean isValidDigitalBroadcastSystem(int value) {
return (isAribDbs(value) || isAtscDbs(value) || isDvbDbs(value));
}
@@ -578,7 +577,7 @@
* @param offset start offset of Channel Identifier
* @return true if the Channel Identifier is valid
*/
- private boolean isValidChannelIdentifier(byte[] params, int offset) {
+ private static boolean isValidChannelIdentifier(byte[] params, int offset) {
// First 6 bits contain Channel Number Format
int channelNumberFormat = params[offset] & 0xFC;
if (channelNumberFormat == 0x04) {
@@ -600,7 +599,7 @@
* @param offset start offset of Digital Service Identification
* @return true if the Digital Service Identification is valid
*/
- private boolean isValidDigitalServiceIdentification(byte[] params, int offset) {
+ private static boolean isValidDigitalServiceIdentification(byte[] params, int offset) {
// MSB contains Service Identification Method
int serviceIdentificationMethod = params[offset] & 0x80;
// Last 7 bits contains Digital Broadcast System
@@ -634,7 +633,7 @@
* @param value External Plug
* @return true if the External Plug is valid
*/
- private boolean isValidExternalPlug(int value) {
+ private static boolean isValidExternalPlug(int value) {
return isWithinRange(value, 1, 255);
}
@@ -645,7 +644,7 @@
* @param value External Source Specifier
* @return true if the External Source is valid
*/
- private boolean isValidExternalSource(byte[] params, int offset) {
+ private static boolean isValidExternalSource(byte[] params, int offset) {
int externalSourceSpecifier = params[offset];
offset = offset + 1;
if (externalSourceSpecifier == 0x04) {
@@ -661,15 +660,15 @@
return false;
}
- private boolean isValidProgrammedInfo(int programedInfo) {
+ private static boolean isValidProgrammedInfo(int programedInfo) {
return (isWithinRange(programedInfo, 0x00, 0x0B));
}
- private boolean isValidNotProgrammedErrorInfo(int nonProgramedErrorInfo) {
+ private static boolean isValidNotProgrammedErrorInfo(int nonProgramedErrorInfo) {
return (isWithinRange(nonProgramedErrorInfo, 0x00, 0x0E));
}
- private boolean isValidTimerStatusData(byte[] params, int offset) {
+ private static boolean isValidTimerStatusData(byte[] params, int offset) {
int programedIndicator = params[offset] & 0x10;
boolean durationAvailable = false;
if (programedIndicator == 0x10) {
@@ -708,7 +707,7 @@
* @param value Play mode
* @return true if the Play mode is valid
*/
- private boolean isValidPlayMode(int value) {
+ private static boolean isValidPlayMode(int value) {
return (isWithinRange(value, 0x05, 0x07)
|| isWithinRange(value, 0x09, 0x0B)
|| isWithinRange(value, 0x15, 0x17)
@@ -725,7 +724,7 @@
* @param value UI Broadcast type
* @return true if the UI Broadcast type is valid
*/
- private boolean isValidUiBroadcastType(int value) {
+ private static boolean isValidUiBroadcastType(int value) {
return ((value == 0x00)
|| (value == 0x01)
|| (value == 0x10)
@@ -749,7 +748,7 @@
* @param value UI Sound Presenation Control
* @return true if the UI Sound Presenation Control is valid
*/
- private boolean isValidUiSoundPresenationControl(int value) {
+ private static boolean isValidUiSoundPresenationControl(int value) {
value = value & 0xFF;
return ((value == 0x20)
|| (value == 0x30)
@@ -768,7 +767,7 @@
* @param params Tuner device info
* @return true if the Tuner device info is valid
*/
- private boolean isValidTunerDeviceInfo(byte[] params) {
+ private static boolean isValidTunerDeviceInfo(byte[] params) {
int tunerDisplayInfo = params[0] & 0x7F;
if (tunerDisplayInfo == 0x00) {
// Displaying digital tuner
@@ -789,7 +788,7 @@
return false;
}
- private class PhysicalAddressValidator implements ParameterValidator {
+ private static class PhysicalAddressValidator implements ParameterValidator {
@Override
public int isValid(byte[] params) {
if (params.length < 2) {
@@ -799,7 +798,7 @@
}
}
- private class SystemAudioModeRequestValidator extends PhysicalAddressValidator {
+ private static class SystemAudioModeRequestValidator extends PhysicalAddressValidator {
@Override
public int isValid(byte[] params) {
// TV can send <System Audio Mode Request> with no parameters to terminate system audio.
@@ -810,7 +809,7 @@
}
}
- private class ReportPhysicalAddressValidator implements ParameterValidator {
+ private static class ReportPhysicalAddressValidator implements ParameterValidator {
@Override
public int isValid(byte[] params) {
if (params.length < 3) {
@@ -820,7 +819,7 @@
}
}
- private class RoutingChangeValidator implements ParameterValidator {
+ private static class RoutingChangeValidator implements ParameterValidator {
@Override
public int isValid(byte[] params) {
if (params.length < 4) {
@@ -836,7 +835,7 @@
* A valid parameter should lie within the range description of Record Status Info defined in
* CEC 1.4 Specification : Operand Descriptions (Section 17)
*/
- private class RecordStatusInfoValidator implements ParameterValidator {
+ private static class RecordStatusInfoValidator implements ParameterValidator {
@Override
public int isValid(byte[] params) {
if (params.length < 1) {
@@ -855,7 +854,7 @@
* A valid parameter should lie within the range description of ASCII defined in CEC 1.4
* Specification : Operand Descriptions (Section 17)
*/
- private class AsciiValidator implements ParameterValidator {
+ private static class AsciiValidator implements ParameterValidator {
private final int mMinLength;
private final int mMaxLength;
@@ -885,7 +884,7 @@
* A valid parameter should lie within the range description of ASCII defined in CEC 1.4
* Specification : Operand Descriptions (Section 17)
*/
- private class OsdStringValidator implements ParameterValidator {
+ private static class OsdStringValidator implements ParameterValidator {
@Override
public int isValid(byte[] params) {
// If the length is longer than expected, we assume it's OK since the parameter can be
@@ -902,7 +901,7 @@
}
/** Check if the given parameters are one byte parameters and within range. */
- private class OneByteRangeValidator implements ParameterValidator {
+ private static class OneByteRangeValidator implements ParameterValidator {
private final int mMinValue, mMaxValue;
OneByteRangeValidator(int minValue, int maxValue) {
@@ -924,7 +923,7 @@
* adhere to message description of Analogue Timer defined in CEC 1.4 Specification : Message
* Descriptions for Timer Programming Feature (CEC Table 12)
*/
- private class AnalogueTimerValidator implements ParameterValidator {
+ private static class AnalogueTimerValidator implements ParameterValidator {
@Override
public int isValid(byte[] params) {
if (params.length < 11) {
@@ -950,7 +949,7 @@
* to message description of Digital Timer defined in CEC 1.4 Specification : Message
* Descriptions for Timer Programming Feature (CEC Table 12)
*/
- private class DigitalTimerValidator implements ParameterValidator {
+ private static class DigitalTimerValidator implements ParameterValidator {
@Override
public int isValid(byte[] params) {
if (params.length < 11) {
@@ -974,7 +973,7 @@
* adhere to message description of External Timer defined in CEC 1.4 Specification : Message
* Descriptions for Timer Programming Feature (CEC Table 12)
*/
- private class ExternalTimerValidator implements ParameterValidator {
+ private static class ExternalTimerValidator implements ParameterValidator {
@Override
public int isValid(byte[] params) {
if (params.length < 9) {
@@ -997,7 +996,7 @@
* within the range description defined in CEC 1.4 Specification : Operand Descriptions
* (Section 17)
*/
- private class TimerClearedStatusValidator implements ParameterValidator {
+ private static class TimerClearedStatusValidator implements ParameterValidator {
@Override
public int isValid(byte[] params) {
if (params.length < 1) {
@@ -1011,7 +1010,7 @@
* Check if the given timer status data parameter is valid. A valid parameter should lie within
* the range description defined in CEC 1.4 Specification : Operand Descriptions (Section 17)
*/
- private class TimerStatusValidator implements ParameterValidator {
+ private static class TimerStatusValidator implements ParameterValidator {
@Override
public int isValid(byte[] params) {
if (params.length < 1) {
@@ -1025,7 +1024,7 @@
* Check if the given play mode parameter is valid. A valid parameter should lie within the
* range description defined in CEC 1.4 Specification : Operand Descriptions (Section 17)
*/
- private class PlayModeValidator implements ParameterValidator {
+ private static class PlayModeValidator implements ParameterValidator {
@Override
public int isValid(byte[] params) {
if (params.length < 1) {
@@ -1040,7 +1039,7 @@
* within the range description defined in CEC 1.4 Specification : Operand Descriptions
* (Section 17)
*/
- private class SelectAnalogueServiceValidator implements ParameterValidator {
+ private static class SelectAnalogueServiceValidator implements ParameterValidator {
@Override
public int isValid(byte[] params) {
if (params.length < 4) {
@@ -1057,7 +1056,7 @@
* within the range description defined in CEC 1.4 Specification : Operand Descriptions
* (Section 17)
*/
- private class SelectDigitalServiceValidator implements ParameterValidator {
+ private static class SelectDigitalServiceValidator implements ParameterValidator {
@Override
public int isValid(byte[] params) {
if (params.length < 4) {
@@ -1072,7 +1071,7 @@
* within the range description defined in CEC 1.4 Specification : Operand Descriptions (Section
* 17)
*/
- private class TunerDeviceStatusValidator implements ParameterValidator {
+ private static class TunerDeviceStatusValidator implements ParameterValidator {
@Override
public int isValid(byte[] params) {
if (params.length < 1) {
@@ -1083,7 +1082,7 @@
}
/** Check if the given user control press parameter is valid. */
- private class UserControlPressedValidator implements ParameterValidator {
+ private static class UserControlPressedValidator implements ParameterValidator {
@Override
public int isValid(byte[] params) {
if (params.length < 1) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
index 225785a..6497174 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
@@ -57,7 +57,8 @@
* This class should not take any active action in sending CEC messages.
*
* Note that the information cached in this class is not guaranteed to be up-to-date, especially OSD
- * names, power states can be outdated.
+ * names, power states can be outdated. For local devices, more up-to-date information can be
+ * accessed through {@link HdmiCecLocalDevice#getDeviceInfo()}.
*/
@VisibleForTesting
public class HdmiCecNetwork {
@@ -390,7 +391,7 @@
return;
}
- updateCecDevice(HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus));
+ updateCecDevice(info.toBuilder().setDevicePowerStatus(newPowerStatus).build());
}
/**
@@ -427,7 +428,8 @@
for (HdmiPortInfo info : cecPortInfo) {
portIdMap.put(info.getAddress(), info.getId());
portInfoMap.put(info.getId(), info);
- portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
+ portDeviceMap.put(info.getId(),
+ HdmiDeviceInfo.hardwarePort(info.getAddress(), info.getId()));
}
mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
@@ -496,13 +498,19 @@
// Add device by logical address if it's not already known
int sourceAddress = message.getSource();
if (getCecDeviceInfo(sourceAddress) == null) {
- HdmiDeviceInfo newDevice = new HdmiDeviceInfo(sourceAddress,
- HdmiDeviceInfo.PATH_INVALID, HdmiDeviceInfo.PORT_INVALID,
- HdmiDeviceInfo.DEVICE_RESERVED, Constants.UNKNOWN_VENDOR_ID,
- HdmiUtils.getDefaultDeviceName(sourceAddress));
+ HdmiDeviceInfo newDevice = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(sourceAddress)
+ .setDisplayName(HdmiUtils.getDefaultDeviceName(sourceAddress))
+ .build();
addCecDevice(newDevice);
}
+ // If a message type has its own class, all valid messages of that type
+ // will be represented by an instance of that class.
+ if (message instanceof ReportFeaturesMessage) {
+ handleReportFeatures((ReportFeaturesMessage) message);
+ }
+
switch (message.getOpcode()) {
case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS:
handleReportPhysicalAddress(message);
@@ -519,18 +527,20 @@
case Constants.MESSAGE_CEC_VERSION:
handleCecVersion(message);
break;
- case Constants.MESSAGE_REPORT_FEATURES:
- handleReportFeatures(message);
- break;
}
}
@ServiceThreadOnly
- private void handleReportFeatures(HdmiCecMessage message) {
+ private void handleReportFeatures(ReportFeaturesMessage message) {
assertRunOnServiceThread();
- int version = Byte.toUnsignedInt(message.getParams()[0]);
- updateDeviceCecVersion(message.getSource(), version);
+ HdmiDeviceInfo currentDeviceInfo = getCecDeviceInfo(message.getSource());
+ HdmiDeviceInfo newDeviceInfo = currentDeviceInfo.toBuilder()
+ .setCecVersion(message.getCecVersion())
+ .updateDeviceFeatures(message.getDeviceFeatures())
+ .build();
+
+ updateCecDevice(newDeviceInfo);
}
@ServiceThreadOnly
@@ -554,11 +564,11 @@
if (deviceInfo == null) {
Slog.i(TAG, "Unknown source device info for <Report Physical Address> " + message);
} else {
- HdmiDeviceInfo updatedDeviceInfo = new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
- physicalAddress,
- physicalAddressToPortId(physicalAddress), type, deviceInfo.getVendorId(),
- deviceInfo.getDisplayName(), deviceInfo.getDevicePowerStatus(),
- deviceInfo.getCecVersion());
+ HdmiDeviceInfo updatedDeviceInfo = deviceInfo.toBuilder()
+ .setPhysicalAddress(physicalAddress)
+ .setPortId(physicalAddressToPortId(physicalAddress))
+ .setDeviceType(type)
+ .build();
updateCecDevice(updatedDeviceInfo);
}
}
@@ -588,11 +598,9 @@
return;
}
- HdmiDeviceInfo updatedDeviceInfo = new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
- deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(), deviceInfo.getDeviceType(),
- deviceInfo.getVendorId(),
- deviceInfo.getDisplayName(), deviceInfo.getDevicePowerStatus(),
- hdmiCecVersion);
+ HdmiDeviceInfo updatedDeviceInfo = deviceInfo.toBuilder()
+ .setCecVersion(hdmiCecVersion)
+ .build();
updateCecDevice(updatedDeviceInfo);
}
@@ -623,10 +631,11 @@
Slog.d(TAG, "Updating device OSD name from "
+ deviceInfo.getDisplayName()
+ " to " + osdName);
- updateCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
- deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
- deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName,
- deviceInfo.getDevicePowerStatus(), deviceInfo.getCecVersion()));
+
+ HdmiDeviceInfo updatedDeviceInfo = deviceInfo.toBuilder()
+ .setDisplayName(osdName)
+ .build();
+ updateCecDevice(updatedDeviceInfo);
}
@ServiceThreadOnly
@@ -639,11 +648,9 @@
if (deviceInfo == null) {
Slog.i(TAG, "Unknown source device info for <Device Vendor ID> " + message);
} else {
- HdmiDeviceInfo updatedDeviceInfo = new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
- deviceInfo.getPhysicalAddress(),
- deviceInfo.getPortId(), deviceInfo.getDeviceType(), vendorId,
- deviceInfo.getDisplayName(), deviceInfo.getDevicePowerStatus(),
- deviceInfo.getCecVersion());
+ HdmiDeviceInfo updatedDeviceInfo = deviceInfo.toBuilder()
+ .setVendorId(vendorId)
+ .build();
updateCecDevice(updatedDeviceInfo);
}
}
@@ -723,10 +730,7 @@
/**
* Returns the {@link HdmiDeviceInfo} instance whose physical address matches
- *
- *
- *
- * qq * the given routing path. CEC devices use routing path for its physical address to
+ * the given routing path. CEC devices use routing path for its physical address to
* describe the hierarchy of the devices in the network.
*
* @param path routing path or physical address
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 6dd9aa0..8391e0b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -372,8 +372,6 @@
@Nullable
private HdmiCecController mCecController;
- private HdmiCecMessageValidator mMessageValidator;
-
private HdmiCecPowerStatusController mPowerStatusController;
@ServiceThreadOnly
@@ -606,9 +604,6 @@
mMhlDevices = Collections.emptyList();
mHdmiCecNetwork.initPortInfo();
- if (mMessageValidator == null) {
- mMessageValidator = new HdmiCecMessageValidator(this);
- }
mHdmiCecConfig.registerChangeListener(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
new HdmiCecConfig.SettingChangeListener() {
@Override
@@ -1086,11 +1081,6 @@
}
@VisibleForTesting
- void setMessageValidator(HdmiCecMessageValidator messageValidator) {
- mMessageValidator = messageValidator;
- }
-
- @VisibleForTesting
void setCecMessageBuffer(CecMessageBuffer cecMessageBuffer) {
this.mCecMessageBuffer = cecMessageBuffer;
}
@@ -1182,7 +1172,8 @@
@ServiceThreadOnly
void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
assertRunOnServiceThread();
- if (mMessageValidator.isValid(command, false) == HdmiCecMessageValidator.OK) {
+ if (command.getValidationResult() == HdmiCecMessageValidator.OK
+ && verifyPhysicalAddresses(command)) {
mCecController.sendCommand(command, callback);
} else {
HdmiLogger.error("Invalid message type:" + command);
@@ -1210,20 +1201,99 @@
mCecController.maySendFeatureAbortCommand(command, reason);
}
+ /**
+ * Returns whether all of the physical addresses in a message could exist in this CEC network.
+ */
+ boolean verifyPhysicalAddresses(HdmiCecMessage message) {
+ byte[] params = message.getParams();
+ switch (message.getOpcode()) {
+ case Constants.MESSAGE_ROUTING_CHANGE:
+ return verifyPhysicalAddress(params, 0)
+ && verifyPhysicalAddress(params, 2);
+ case Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST:
+ return params.length == 0 || verifyPhysicalAddress(params, 0);
+ case Constants.MESSAGE_ACTIVE_SOURCE:
+ case Constants.MESSAGE_INACTIVE_SOURCE:
+ case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS:
+ case Constants.MESSAGE_ROUTING_INFORMATION:
+ case Constants.MESSAGE_SET_STREAM_PATH:
+ return verifyPhysicalAddress(params, 0);
+ case Constants.MESSAGE_CLEAR_EXTERNAL_TIMER:
+ case Constants.MESSAGE_SET_EXTERNAL_TIMER:
+ return verifyExternalSourcePhysicalAddress(params, 7);
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Returns whether a given physical address could exist in this CEC network.
+ * For a TV, the physical address must either be the address of the TV itself,
+ * or the address of a device connected to one of its ports (possibly indirectly).
+ */
+ private boolean verifyPhysicalAddress(byte[] params, int offset) {
+ if (!isTvDevice()) {
+ // If the device is not TV, we can't convert path to port-id, so stop here.
+ return true;
+ }
+ int path = HdmiUtils.twoBytesToInt(params, offset);
+ if (path != Constants.INVALID_PHYSICAL_ADDRESS && path == getPhysicalAddress()) {
+ return true;
+ }
+ int portId = pathToPortId(path);
+ if (portId == Constants.INVALID_PORT_ID) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns whether the physical address of an external source could exist in this network.
+ */
+ private boolean verifyExternalSourcePhysicalAddress(byte[] params, int offset) {
+ int externalSourceSpecifier = params[offset];
+ offset = offset + 1;
+ if (externalSourceSpecifier == 0x05) {
+ if (params.length - offset >= 2) {
+ return verifyPhysicalAddress(params, offset);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns whether the source address of a message is a local logical address.
+ */
+ private boolean sourceAddressIsLocal(HdmiCecMessage message) {
+ for (HdmiCecLocalDevice device : getAllLocalDevices()) {
+ synchronized (device.mLock) {
+ if (message.getSource() == device.getDeviceInfo().getLogicalAddress()
+ && message.getSource() != Constants.ADDR_UNREGISTERED) {
+ HdmiLogger.warning(
+ "Unexpected source: message sent from device itself, " + message);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
@ServiceThreadOnly
@VisibleForTesting
@Constants.HandleMessageResult
protected int handleCecCommand(HdmiCecMessage message) {
assertRunOnServiceThread();
- int errorCode = mMessageValidator.isValid(message, true);
- if (errorCode != HdmiCecMessageValidator.OK) {
- // We'll not response on the messages with the invalid source or destination
- // or with parameter length shorter than specified in the standard.
- if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
- return Constants.ABORT_INVALID_OPERAND;
- }
+
+ @HdmiCecMessageValidator.ValidationResult
+ int validationResult = message.getValidationResult();
+ if (validationResult == HdmiCecMessageValidator.ERROR_PARAMETER
+ || !verifyPhysicalAddresses(message)) {
+ return Constants.ABORT_INVALID_OPERAND;
+ } else if (validationResult != HdmiCecMessageValidator.OK
+ || sourceAddressIsLocal(message)) {
return Constants.HANDLED;
}
+
getHdmiCecNetwork().handleCecMessage(message);
@Constants.HandleMessageResult int handleMessageResult =
@@ -1409,9 +1479,16 @@
private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus,
int cecVersion) {
String displayName = readStringSetting(Global.DEVICE_NAME, Build.MODEL);
- return new HdmiDeviceInfo(logicalAddress,
- getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
- getVendorId(), displayName, powerStatus, cecVersion);
+ return HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(logicalAddress)
+ .setPhysicalAddress(getPhysicalAddress())
+ .setPortId(pathToPortId(getPhysicalAddress()))
+ .setDeviceType(deviceType)
+ .setVendorId(getVendorId())
+ .setDisplayName(displayName)
+ .setDevicePowerStatus(powerStatus)
+ .setCecVersion(cecVersion)
+ .build();
}
// Set the display name in HdmiDeviceInfo of the current devices to content provided by
@@ -1422,10 +1499,9 @@
if (deviceInfo.getDisplayName().equals(newDisplayName)) {
continue;
}
- device.setDeviceInfo(new HdmiDeviceInfo(
- deviceInfo.getLogicalAddress(), deviceInfo.getPhysicalAddress(),
- deviceInfo.getPortId(), deviceInfo.getDeviceType(), deviceInfo.getVendorId(),
- newDisplayName, deviceInfo.getDevicePowerStatus(), deviceInfo.getCecVersion()));
+ synchronized (device.mLock) {
+ device.setDeviceInfo(deviceInfo.toBuilder().setDisplayName(newDisplayName).build());
+ }
sendCecCommand(
HdmiCecMessageBuilder.buildSetOsdNameCommand(
deviceInfo.getLogicalAddress(), Constants.ADDR_TV, newDisplayName));
@@ -2629,7 +2705,7 @@
return activeSourceInfo;
}
- return new HdmiDeviceInfo(activeSource.physicalAddress,
+ return HdmiDeviceInfo.hardwarePort(activeSource.physicalAddress,
pathToPortId(activeSource.physicalAddress));
}
@@ -2637,7 +2713,7 @@
int activePath = tv().getActivePath();
if (activePath != HdmiDeviceInfo.PATH_INVALID) {
HdmiDeviceInfo info = mHdmiCecNetwork.getSafeDeviceInfoByPath(activePath);
- return (info != null) ? info : new HdmiDeviceInfo(activePath,
+ return (info != null) ? info : HdmiDeviceInfo.hardwarePort(activePath,
tv().getActivePortId());
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiMhlLocalDeviceStub.java b/services/core/java/com/android/server/hdmi/HdmiMhlLocalDeviceStub.java
index 06ecb5a..43469b5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiMhlLocalDeviceStub.java
+++ b/services/core/java/com/android/server/hdmi/HdmiMhlLocalDeviceStub.java
@@ -8,7 +8,7 @@
*/
final class HdmiMhlLocalDeviceStub {
- private static final HdmiDeviceInfo INFO = new HdmiDeviceInfo(
+ private static final HdmiDeviceInfo INFO = HdmiDeviceInfo.mhlDevice(
Constants.INVALID_PHYSICAL_ADDRESS, Constants.INVALID_PORT_ID, -1, -1);
private final HdmiControlService mService;
private final int mPortId;
diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java
index 03e5de8..ba19cf0 100644
--- a/services/core/java/com/android/server/hdmi/HdmiUtils.java
+++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java
@@ -391,15 +391,6 @@
}
/**
- * Clone {@link HdmiDeviceInfo} with new power status.
- */
- static HdmiDeviceInfo cloneHdmiDeviceInfo(HdmiDeviceInfo info, int newPowerStatus) {
- return new HdmiDeviceInfo(info.getLogicalAddress(),
- info.getPhysicalAddress(), info.getPortId(), info.getDeviceType(),
- info.getVendorId(), info.getDisplayName(), newPowerStatus, info.getCecVersion());
- }
-
- /**
* Dump a {@link SparseArray} to the print writer.
*
* <p>The dump is formatted:
@@ -470,7 +461,7 @@
}
/**
- * Method to parse target physical address to the port number on the current device.
+ * Method to build target physical address to the port number on the current device.
*
* <p>This check assumes target address is valid.
*
@@ -562,7 +553,28 @@
for (int i = 0; i < params.length; i++) {
params[i] = (byte) Integer.parseInt(parts[i + 2], 16);
}
- return new HdmiCecMessage(src, dest, opcode, params);
+ return HdmiCecMessage.build(src, dest, opcode, params);
+ }
+
+ /**
+ * Some operands in the CEC spec consist of a variable number of bytes, where each byte except
+ * the last one has bit 7 set to 1.
+ * Given the index of a byte in such an operand, this method returns the index of the last byte
+ * in the operand, or -1 if the input is invalid (e.g. operand not terminated properly).
+ * @param params Byte array representing a CEC message's parameters
+ * @param offset Index of a byte in the operand to find the end of
+ */
+ public static int getEndOfSequence(byte[] params, int offset) {
+ if (offset < 0) {
+ return -1;
+ }
+ while (offset < params.length && ((params[offset] >> 7) & 1) == 1) {
+ offset++;
+ }
+ if (offset >= params.length) {
+ return -1;
+ }
+ return offset;
}
public static class ShortAudioDescriptorXmlParser {
diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
index a307ea3..c4b98c2 100644
--- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java
+++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
@@ -68,7 +68,7 @@
mDeviceLogicalAddress = deviceLogicalAddress;
mDevicePhysicalAddress = devicePhysicalAddress;
mDeviceType = deviceType;
- mVendorId = Constants.UNKNOWN_VENDOR_ID;
+ mVendorId = Constants.VENDOR_ID_UNKNOWN;
}
@Override
@@ -174,10 +174,14 @@
if (mDisplayName == null) {
mDisplayName = HdmiUtils.getDefaultDeviceName(mDeviceLogicalAddress);
}
- HdmiDeviceInfo deviceInfo = new HdmiDeviceInfo(
- mDeviceLogicalAddress, mDevicePhysicalAddress,
- tv().getPortId(mDevicePhysicalAddress),
- mDeviceType, mVendorId, mDisplayName);
+ HdmiDeviceInfo deviceInfo = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(mDeviceLogicalAddress)
+ .setPhysicalAddress(mDevicePhysicalAddress)
+ .setPortId(tv().getPortId(mDevicePhysicalAddress))
+ .setDeviceType(mDeviceType)
+ .setVendorId(mVendorId)
+ .setDisplayName(mDisplayName)
+ .build();
localDevice().mService.getHdmiCecNetwork().addCecDevice(deviceInfo);
// Consume CEC messages we already got for this newly found device.
diff --git a/services/core/java/com/android/server/hdmi/ReportFeaturesMessage.java b/services/core/java/com/android/server/hdmi/ReportFeaturesMessage.java
new file mode 100644
index 0000000..80bfb96
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/ReportFeaturesMessage.java
@@ -0,0 +1,189 @@
+/*
+ * 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.hdmi;
+
+import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER_SHORT;
+import static com.android.server.hdmi.HdmiCecMessageValidator.OK;
+import static com.android.server.hdmi.HdmiCecMessageValidator.ValidationResult;
+
+import android.annotation.NonNull;
+import android.hardware.hdmi.DeviceFeatures;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * Represents a validated <Report Features> message with parsed parameters.
+ *
+ * Only parses the [CEC Version] and [Device Features] operands.
+ * [All Device Types] and [RC Profile] are not parsed, but can be specified in construction.
+ */
+public class ReportFeaturesMessage extends HdmiCecMessage {
+
+ @HdmiControlManager.HdmiCecVersion
+ private final int mCecVersion;
+
+ @NonNull
+ private final DeviceFeatures mDeviceFeatures;
+
+ private ReportFeaturesMessage(int source, int destination, byte[] params, int cecVersion,
+ DeviceFeatures deviceFeatures) {
+ super(source, destination, Constants.MESSAGE_REPORT_FEATURES, params, OK);
+ mCecVersion = cecVersion;
+ mDeviceFeatures = deviceFeatures;
+ }
+
+ /**
+ * Static factory method. Intended for constructing outgoing or test messages, as it uses
+ * structured types instead of raw bytes to construct the parameters.
+ */
+ public static HdmiCecMessage build(
+ int source,
+ @HdmiControlManager.HdmiCecVersion int cecVersion,
+ List<Integer> allDeviceTypes,
+ @Constants.RcProfile int rcProfile,
+ List<Integer> rcFeatures,
+ DeviceFeatures deviceFeatures) {
+
+ byte cecVersionByte = (byte) (cecVersion & 0xFF);
+ byte deviceTypes = 0;
+ for (Integer deviceType : allDeviceTypes) {
+ deviceTypes |= (byte) (1 << hdmiDeviceInfoDeviceTypeToShiftValue(deviceType));
+ }
+
+ byte rcProfileByte = 0;
+ rcProfileByte |= (byte) (rcProfile << 6);
+ if (rcProfile == Constants.RC_PROFILE_SOURCE) {
+ for (@Constants.RcProfileSource Integer rcFeature : rcFeatures) {
+ rcProfileByte |= (byte) (1 << rcFeature);
+ }
+ } else {
+ @Constants.RcProfileTv byte rcProfileTv = (byte) (rcFeatures.get(0) & 0xFFFF);
+ rcProfileByte |= rcProfileTv;
+ }
+
+ byte[] fixedOperands = {cecVersionByte, deviceTypes, rcProfileByte};
+ byte[] deviceFeaturesBytes = deviceFeatures.toOperand();
+
+ // Concatenate fixed length operands and [Device Features]
+ byte[] params = Arrays.copyOf(fixedOperands,
+ fixedOperands.length + deviceFeaturesBytes.length);
+ System.arraycopy(deviceFeaturesBytes, 0, params,
+ fixedOperands.length, deviceFeaturesBytes.length);
+
+ @ValidationResult
+ int addressValidationResult = validateAddress(source, Constants.ADDR_BROADCAST);
+ if (addressValidationResult != OK) {
+ return new HdmiCecMessage(source, Constants.ADDR_BROADCAST,
+ Constants.MESSAGE_REPORT_FEATURES, params, addressValidationResult);
+ } else {
+ return new ReportFeaturesMessage(source, Constants.ADDR_BROADCAST, params,
+ cecVersion, deviceFeatures);
+ }
+ }
+
+ @Constants.DeviceType
+ private static int hdmiDeviceInfoDeviceTypeToShiftValue(int deviceType) {
+ switch (deviceType) {
+ case HdmiDeviceInfo.DEVICE_TV:
+ return Constants.ALL_DEVICE_TYPES_TV;
+ case HdmiDeviceInfo.DEVICE_RECORDER:
+ return Constants.ALL_DEVICE_TYPES_RECORDER;
+ case HdmiDeviceInfo.DEVICE_TUNER:
+ return Constants.ALL_DEVICE_TYPES_TUNER;
+ case HdmiDeviceInfo.DEVICE_PLAYBACK:
+ return Constants.ALL_DEVICE_TYPES_PLAYBACK;
+ case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM:
+ return Constants.ALL_DEVICE_TYPES_AUDIO_SYSTEM;
+ case HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH:
+ return Constants.ALL_DEVICE_TYPES_SWITCH;
+ default:
+ throw new IllegalArgumentException("Unhandled device type: " + deviceType);
+ }
+ }
+
+ /**
+ * Must only be called from {@link HdmiCecMessage#build}.
+ *
+ * Parses and validates CEC message data as a <Report Features> message. Intended for
+ * constructing a representation of an incoming message, as it takes raw bytes for parameters.
+ *
+ * If successful, returns an instance of {@link ReportFeaturesMessage}.
+ * If unsuccessful, returns an {@link HdmiCecMessage} with the reason for validation failure
+ * accessible through {@link HdmiCecMessage#getValidationResult}.
+ */
+ static HdmiCecMessage build(int source, int destination, byte[] params) {
+ // Helper function for building a message that failed validation
+ Function<Integer, HdmiCecMessage> invalidMessage =
+ validationResult -> new HdmiCecMessage(source, destination,
+ Constants.MESSAGE_REPORT_FEATURES, params, validationResult);
+
+ @ValidationResult int addressValidationResult = validateAddress(source, destination);
+ if (addressValidationResult != OK) {
+ return invalidMessage.apply(addressValidationResult);
+ }
+
+ if (params.length < 4) {
+ return invalidMessage.apply(ERROR_PARAMETER_SHORT);
+ }
+
+ int cecVersion = Byte.toUnsignedInt(params[0]);
+
+ int rcProfileEnd = HdmiUtils.getEndOfSequence(params, 2);
+ if (rcProfileEnd == -1) {
+ return invalidMessage.apply(ERROR_PARAMETER_SHORT);
+ }
+ int deviceFeaturesEnd = HdmiUtils.getEndOfSequence(
+ params, rcProfileEnd + 1);
+ if (deviceFeaturesEnd == -1) {
+ return invalidMessage.apply(ERROR_PARAMETER_SHORT);
+ }
+ int deviceFeaturesStart = HdmiUtils.getEndOfSequence(params, 2) + 1;
+ byte[] deviceFeaturesBytes = Arrays.copyOfRange(params, deviceFeaturesStart, params.length);
+ DeviceFeatures deviceFeatures = DeviceFeatures.fromOperand(deviceFeaturesBytes);
+
+ return new ReportFeaturesMessage(source, destination, params, cecVersion, deviceFeatures);
+ }
+
+ /**
+ * Validates the source and destination addresses for a <Report Features> message.
+ */
+ public static int validateAddress(int source, int destination) {
+ return HdmiCecMessageValidator.validateAddress(source, destination,
+ HdmiCecMessageValidator.DEST_BROADCAST);
+ }
+
+ /**
+ * Returns the contents of the [CEC Version] operand: the version number of the CEC
+ * specification which was used to design the device.
+ */
+ @HdmiControlManager.HdmiCecVersion
+ public int getCecVersion() {
+ return mCecVersion;
+ }
+
+ /**
+ * Returns the contents of the [Device Features] operand: the set of features supported by
+ * the device.
+ */
+ public DeviceFeatures getDeviceFeatures() {
+ return mDeviceFeatures;
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java
new file mode 100644
index 0000000..5154669
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java
@@ -0,0 +1,126 @@
+/*
+ * 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.hdmi;
+
+import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED;
+import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED;
+
+import static com.android.server.hdmi.Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL;
+
+import android.hardware.hdmi.DeviceFeatures;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.hardware.tv.cec.V1_0.SendMessageResult;
+
+/**
+ * Determines whether a target device supports the <Set Audio Volume Level> message.
+ *
+ * Sends the device <Set Audio Volume Level>[0x7F]. The value 0x7F is defined by the spec such that
+ * setting the volume to this level results in no change to the current volume level.
+ *
+ * The target device supports <Set Audio Volume Level> only if it does not respond with
+ * <Feature Abort> within {@link HdmiConfig.TIMEOUT_MS} milliseconds.
+ */
+public class SetAudioVolumeLevelDiscoveryAction extends HdmiCecFeatureAction {
+ private static final String TAG = "SetAudioVolumeLevelDiscoveryAction";
+
+ private static final int STATE_WAITING_FOR_FEATURE_ABORT = 1;
+
+ private final int mTargetAddress;
+
+ public SetAudioVolumeLevelDiscoveryAction(HdmiCecLocalDevice source,
+ int targetAddress, IHdmiControlCallback callback) {
+ super(source, callback);
+
+ mTargetAddress = targetAddress;
+ }
+
+ boolean start() {
+ sendCommand(SetAudioVolumeLevelMessage.build(
+ getSourceAddress(), mTargetAddress, Constants.AUDIO_VOLUME_STATUS_UNKNOWN),
+ result -> {
+ if (result == SendMessageResult.SUCCESS) {
+ // Message sent successfully; wait for <Feature Abort> in response
+ mState = STATE_WAITING_FOR_FEATURE_ABORT;
+ addTimer(mState, HdmiConfig.TIMEOUT_MS);
+ } else {
+ finishWithCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED);
+ }
+ });
+ return true;
+ }
+
+ boolean processCommand(HdmiCecMessage cmd) {
+ if (mState != STATE_WAITING_FOR_FEATURE_ABORT) {
+ return false;
+ }
+ switch (cmd.getOpcode()) {
+ case Constants.MESSAGE_FEATURE_ABORT:
+ return handleFeatureAbort(cmd);
+ default:
+ return false;
+ }
+ }
+
+ private boolean handleFeatureAbort(HdmiCecMessage cmd) {
+ int originalOpcode = cmd.getParams()[0] & 0xFF;
+ if (originalOpcode == MESSAGE_SET_AUDIO_VOLUME_LEVEL && cmd.getSource() == mTargetAddress) {
+ if (updateAvcSupport(FEATURE_NOT_SUPPORTED)) {
+ finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
+ } else {
+ finishWithCallback(HdmiControlManager.RESULT_EXCEPTION);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ void handleTimerEvent(int state) {
+ if (updateAvcSupport(FEATURE_SUPPORTED)) {
+ finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
+ } else {
+ finishWithCallback(HdmiControlManager.RESULT_EXCEPTION);
+ }
+ }
+
+ /**
+ * Updates the System Audio device's support for <Set Audio Volume Level> in the
+ * {@link HdmiCecNetwork}. Can fail if the System Audio device is not in our
+ * {@link HdmiCecNetwork}.
+ *
+ * @return Whether support was successfully updated in the network.
+ */
+ private boolean updateAvcSupport(
+ @DeviceFeatures.FeatureSupportStatus int setAudioVolumeLevelSupport) {
+ HdmiCecNetwork network = source().mService.getHdmiCecNetwork();
+ HdmiDeviceInfo currentDeviceInfo = network.getCecDeviceInfo(mTargetAddress);
+
+ if (currentDeviceInfo == null) {
+ return false;
+ } else {
+ network.updateCecDevice(
+ currentDeviceInfo.toBuilder()
+ .setDeviceFeatures(currentDeviceInfo.getDeviceFeatures().toBuilder()
+ .setSetAudioVolumeLevelSupport(setAudioVolumeLevelSupport)
+ .build())
+ .build()
+ );
+ return true;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelMessage.java b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelMessage.java
new file mode 100644
index 0000000..2ec0e7f
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelMessage.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.server.hdmi;
+
+import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER_SHORT;
+import static com.android.server.hdmi.HdmiCecMessageValidator.OK;
+import static com.android.server.hdmi.HdmiCecMessageValidator.ValidationResult;
+
+/**
+ * Represents a validated <Set Audio Volume Level> message with parsed parameters.
+ */
+public class SetAudioVolumeLevelMessage extends HdmiCecMessage {
+ private final int mAudioVolumeLevel;
+
+ private SetAudioVolumeLevelMessage(int source, int destination, byte[] params,
+ int audioVolumeLevel) {
+ super(source, destination, Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, params, OK);
+ mAudioVolumeLevel = audioVolumeLevel;
+ }
+
+ /**
+ * Static factory method. Intended for constructing outgoing or test messages, as it uses
+ * structured types instead of raw bytes to construct the parameters.
+ *
+ * @param source Initiator address. Cannot be {@link Constants#ADDR_UNREGISTERED}
+ * @param destination Destination address. Cannot be {@link Constants#ADDR_BROADCAST}
+ * @param audioVolumeLevel [Audio Volume Level]. Either 0x7F (representing no volume change)
+ * or between 0 and 100 inclusive (representing volume percentage).
+ */
+ public static HdmiCecMessage build(int source, int destination, int audioVolumeLevel) {
+ byte[] params = { (byte) (audioVolumeLevel & 0xFF) };
+
+ @ValidationResult
+ int addressValidationResult = validateAddress(source, destination);
+ if (addressValidationResult == OK) {
+ return new SetAudioVolumeLevelMessage(source, destination, params, audioVolumeLevel);
+ } else {
+ return new HdmiCecMessage(source, destination, Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL,
+ params, addressValidationResult);
+ }
+ }
+
+ /**
+ * Must only be called from {@link HdmiCecMessage#build}.
+ *
+ * Parses and validates CEC message data as a <SetAudioVolumeLevel> message. Intended for
+ * constructing a representation of an incoming message, as it takes raw bytes for
+ * parameters.
+ *
+ * If successful, returns an instance of {@link SetAudioVolumeLevelMessage}.
+ * If unsuccessful, returns an {@link HdmiCecMessage} with the reason for validation failure
+ * accessible through {@link HdmiCecMessage#getValidationResult}.
+ */
+ public static HdmiCecMessage build(int source, int destination, byte[] params) {
+ if (params.length == 0) {
+ return new HdmiCecMessage(source, destination, Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL,
+ params, ERROR_PARAMETER_SHORT);
+ }
+
+ int audioVolumeLevel = params[0];
+
+ @ValidationResult
+ int addressValidationResult = validateAddress(source, destination);
+ if (addressValidationResult == OK) {
+ return new SetAudioVolumeLevelMessage(source, destination, params, audioVolumeLevel);
+ } else {
+ return new HdmiCecMessage(source, destination, Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL,
+ params, addressValidationResult);
+ }
+ }
+
+ /**
+ * Validates the source and destination addresses for a <Set Audio Volume Level> message.
+ */
+ public static int validateAddress(int source, int destination) {
+ return HdmiCecMessageValidator.validateAddress(source, destination,
+ HdmiCecMessageValidator.DEST_DIRECT);
+ }
+
+ /**
+ * Returns the contents of the [Audio Volume Level] operand:
+ * either 0x7F, indicating no change to the current volume level,
+ * or a percentage between 0 and 100 (inclusive).
+ */
+ public int getAudioVolumeLevel() {
+ return mAudioVolumeLevel;
+ }
+}
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/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..9bc090f 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -151,6 +151,8 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.usage.NetworkStats;
+import android.app.usage.NetworkStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -181,7 +183,6 @@
import android.net.NetworkSpecifier;
import android.net.NetworkStack;
import android.net.NetworkStateSnapshot;
-import android.net.NetworkStats;
import android.net.NetworkTemplate;
import android.net.TelephonyNetworkSpecifier;
import android.net.TrafficStats;
@@ -441,7 +442,7 @@
private final Context mContext;
private final IActivityManager mActivityManager;
- private NetworkStatsManagerInternal mNetworkStats;
+ private NetworkStatsManager mNetworkStats;
private final INetworkManagementService mNetworkManager;
private UsageStatsManagerInternal mUsageStats;
private AppStandbyInternal mAppStandby;
@@ -453,6 +454,8 @@
private ConnectivityManager mConnManager;
private PowerManagerInternal mPowerManagerInternal;
private PowerWhitelistManager mPowerWhitelistManager;
+ @NonNull
+ private final Dependencies mDeps;
/** Current cached value of the current Battery Saver mode's setting for restrict background. */
@GuardedBy("mUidRulesFirstLock")
@@ -704,7 +707,7 @@
public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
INetworkManagementService networkManagement) {
this(context, activityManager, networkManagement, AppGlobals.getPackageManager(),
- getDefaultClock(), getDefaultSystemDir(), false);
+ getDefaultClock(), getDefaultSystemDir(), false, new Dependencies(context));
}
private static @NonNull File getDefaultSystemDir() {
@@ -716,9 +719,59 @@
Clock.systemUTC());
}
+ static class Dependencies {
+ final Context mContext;
+ final NetworkStatsManager mNetworkStatsManager;
+ Dependencies(Context context) {
+ mContext = context;
+ mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class);
+ // Query stats from NetworkStatsService will trigger a poll by default.
+ // But since NPMS listens stats updated event, and will query stats
+ // after the event. A polling -> updated -> query -> polling loop will be introduced
+ // if polls on open. Hence, while NPMS manages it's poll requests explicitly, set
+ // flag to false to prevent a polling loop.
+ mNetworkStatsManager.setPollOnOpen(false);
+ }
+
+ long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
+ Trace.traceBegin(TRACE_TAG_NETWORK, "getNetworkTotalBytes");
+ try {
+ final NetworkStats.Bucket ret = mNetworkStatsManager
+ .querySummaryForDevice(template, start, end);
+ return ret.getRxBytes() + ret.getTxBytes();
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Failed to read network stats: " + e);
+ return 0;
+ } finally {
+ Trace.traceEnd(TRACE_TAG_NETWORK);
+ }
+ }
+
+ @NonNull
+ List<NetworkStats.Bucket> getNetworkUidBytes(
+ @NonNull NetworkTemplate template, long start, long end) {
+ Trace.traceBegin(TRACE_TAG_NETWORK, "getNetworkUidBytes");
+ final List<NetworkStats.Bucket> buckets = new ArrayList<>();
+ try {
+ final NetworkStats stats = mNetworkStatsManager.querySummary(template, start, end);
+ while (stats.hasNextBucket()) {
+ final NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+ stats.getNextBucket(bucket);
+ buckets.add(bucket);
+ }
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Failed to read network stats: " + e);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_NETWORK);
+ }
+ return buckets;
+ }
+ }
+
+ @VisibleForTesting
public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
INetworkManagementService networkManagement, IPackageManager pm, Clock clock,
- File systemDir, boolean suppressDefaultPolicy) {
+ File systemDir, boolean suppressDefaultPolicy, Dependencies deps) {
mContext = Objects.requireNonNull(context, "missing context");
mActivityManager = Objects.requireNonNull(activityManager, "missing activityManager");
mNetworkManager = Objects.requireNonNull(networkManagement, "missing networkManagement");
@@ -739,10 +792,12 @@
mUidEventHandler = new Handler(mUidEventThread.getLooper(), mUidEventHandlerCallback);
mSuppressDefaultPolicy = suppressDefaultPolicy;
+ mDeps = Objects.requireNonNull(deps, "missing Dependencies");
mPolicyFile = new AtomicFile(new File(systemDir, "netpolicy.xml"), "net-policy");
mAppOps = context.getSystemService(AppOpsManager.class);
+ mNetworkStats = context.getSystemService(NetworkStatsManager.class);
mMultipathPolicyTracker = new MultipathPolicyTracker(mContext, mHandler);
// Expose private service for system components to use.
LocalServices.addService(NetworkPolicyManagerInternal.class,
@@ -842,7 +897,6 @@
mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
mAppStandby = LocalServices.getService(AppStandbyInternal.class);
- mNetworkStats = LocalServices.getService(NetworkStatsManagerInternal.class);
synchronized (mUidRulesFirstLock) {
synchronized (mNetworkPoliciesSecondLock) {
@@ -1161,21 +1215,34 @@
};
/**
- * Receiver that watches for {@link INetworkStatsService} updates, which we
+ * Receiver that watches for {@link NetworkStatsManager} updates, which we
* use to check against {@link NetworkPolicy#warningBytes}.
*/
- final private BroadcastReceiver mStatsReceiver = new BroadcastReceiver() {
+ private final NetworkStatsBroadcastReceiver mStatsReceiver =
+ new NetworkStatsBroadcastReceiver();
+ private class NetworkStatsBroadcastReceiver extends BroadcastReceiver {
+ private boolean mIsAnyIntentReceived = false;
@Override
public void onReceive(Context context, Intent intent) {
// on background handler thread, and verified
// READ_NETWORK_USAGE_HISTORY permission above.
+ mIsAnyIntentReceived = true;
+
synchronized (mNetworkPoliciesSecondLock) {
updateNetworkRulesNL();
updateNetworkEnabledNL();
updateNotificationsNL();
}
}
+
+ /**
+ * Return whether any {@code ACTION_NETWORK_STATS_UPDATED} intent is received.
+ * Used to determine if NetworkStatsService is ready.
+ */
+ public boolean isAnyIntentReceived() {
+ return mIsAnyIntentReceived;
+ }
};
/**
@@ -1385,15 +1452,17 @@
long maxBytes = 0;
int maxUid = 0;
- final NetworkStats stats = getNetworkUidBytes(template, start, end);
- NetworkStats.Entry entry = null;
- for (int i = 0; i < stats.size(); i++) {
- entry = stats.getValues(i, entry);
- final long bytes = entry.rxBytes + entry.txBytes;
+ // Skip if not ready. NetworkStatsService will block public API calls until it is
+ // ready. To prevent NPMS be blocked on that, skip and fail fast instead.
+ if (!mStatsReceiver.isAnyIntentReceived()) return null;
+
+ final List<NetworkStats.Bucket> stats = mDeps.getNetworkUidBytes(template, start, end);
+ for (final NetworkStats.Bucket entry : stats) {
+ final long bytes = entry.getRxBytes() + entry.getTxBytes();
totalBytes += bytes;
if (bytes > maxBytes) {
maxBytes = bytes;
- maxUid = entry.uid;
+ maxUid = entry.getUid();
}
}
@@ -3363,6 +3432,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);
@@ -5349,25 +5447,10 @@
@Deprecated
private long getTotalBytes(NetworkTemplate template, long start, long end) {
- return getNetworkTotalBytes(template, start, end);
- }
-
- private long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
- try {
- return mNetworkStats.getNetworkTotalBytes(template, start, end);
- } catch (RuntimeException e) {
- Slog.w(TAG, "Failed to read network stats: " + e);
- return 0;
- }
- }
-
- private NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end) {
- try {
- return mNetworkStats.getNetworkUidBytes(template, start, end);
- } catch (RuntimeException e) {
- Slog.w(TAG, "Failed to read network stats: " + e);
- return new NetworkStats(SystemClock.elapsedRealtime(), 0);
- }
+ // Skip if not ready. NetworkStatsService will block public API calls until it is
+ // ready. To prevent NPMS be blocked on that, skip and fail fast instead.
+ if (!mStatsReceiver.isAnyIntentReceived()) return 0;
+ return mDeps.getNetworkTotalBytes(template, start, end);
}
private boolean isBandwidthControlEnabled() {
@@ -5583,14 +5666,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 +5707,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 a5edfed..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();
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 258ae8c..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) {
@@ -848,6 +865,9 @@
if (r == null) {
throw new IllegalArgumentException("Invalid package");
}
+ if (fromTargetApp) {
+ group.setBlocked(false);
+ }
final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
if (oldGroup != null) {
group.setChannels(oldGroup.getChannels());
@@ -2813,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/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/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 f0bf86f..16176f0 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -3436,7 +3436,10 @@
metricsState.getLatestTelephonySuggestion()),
convertTimeZoneSuggestionToProtoBytes(
metricsState.getLatestGeolocationSuggestion()),
- metricsState.isTelephonyTimeZoneFallbackSupported()
+ metricsState.isTelephonyTimeZoneFallbackSupported(),
+ metricsState.getDeviceTimeZoneId(),
+ metricsState.isEnhancedMetricsCollectionEnabled(),
+ metricsState.getGeoDetectionRunInBackgroundEnabled()
));
} catch (RuntimeException e) {
Slog.e(TAG, "Getting time zone detection state failed: ", e);
@@ -3483,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);
@@ -4588,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/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/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 4b33f0e..a8dd856 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -11,8 +11,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_ACTIVITY_START;
@@ -483,10 +481,6 @@
case WINDOWING_MODE_FULLSCREEN:
mWindowState = WINDOW_STATE_STANDARD;
break;
- case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
- case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY:
- mWindowState = WINDOW_STATE_SIDE_BY_SIDE;
- break;
case WINDOWING_MODE_FREEFORM:
mWindowState = WINDOW_STATE_FREEFORM;
break;
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/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
index 9353f6d..1681348 100644
--- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -26,6 +26,7 @@
import android.os.IBinder;
import android.os.InputConstants;
import android.os.Looper;
+import android.os.Process;
import android.util.Slog;
import android.view.InputChannel;
import android.view.InputEvent;
@@ -113,14 +114,13 @@
}
private InputWindowHandle createInputWindowHandle() {
- InputWindowHandle inputWindowHandle = new InputWindowHandle(
- mActivityRecord.getInputApplicationHandle(false),
+ InputWindowHandle inputWindowHandle = new InputWindowHandle(null,
mActivityRecord.getDisplayId());
inputWindowHandle.replaceTouchableRegionWithCrop(
mActivityRecord.getParentSurfaceControl());
inputWindowHandle.name = mName;
- inputWindowHandle.ownerUid = mActivityRecord.getUid();
- inputWindowHandle.ownerPid = mActivityRecord.getPid();
+ inputWindowHandle.ownerUid = Process.myUid();
+ inputWindowHandle.ownerPid = Process.myPid();
inputWindowHandle.layoutParamsFlags =
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 65f9e83..e119a9a 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -30,8 +30,6 @@
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
@@ -1926,27 +1924,6 @@
private void computeLaunchParams(ActivityRecord r, ActivityRecord sourceRecord,
Task targetTask) {
- final Task sourceRootTask = mSourceRootTask != null ? mSourceRootTask
- : mRootWindowContainer.getTopDisplayFocusedRootTask();
- if (sourceRootTask != null && sourceRootTask.inSplitScreenWindowingMode()
- && (mOptions == null
- || mOptions.getLaunchWindowingMode() == WINDOWING_MODE_UNDEFINED)) {
- int windowingMode =
- targetTask != null ? targetTask.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
- if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
- if (sourceRootTask.inSplitScreenPrimaryWindowingMode()) {
- windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
- } else if (sourceRootTask.inSplitScreenSecondaryWindowingMode()) {
- windowingMode = WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
- }
- }
-
- if (mOptions == null) {
- mOptions = ActivityOptions.makeBasic();
- }
- mOptions.setLaunchWindowingMode(windowingMode);
- }
-
mSupervisor.getLaunchParamsController().calculate(targetTask, r.info.windowLayout, r,
sourceRecord, mOptions, mRequest, PHASE_BOUNDS, mLaunchParams);
mPreferredTaskDisplayArea = mLaunchParams.hasPreferredTaskDisplayArea()
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 3cecce2..dd394ca 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -36,7 +36,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.pm.PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
@@ -364,6 +363,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 +382,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;
}
@@ -2169,10 +2198,7 @@
boolean forceNonResizable) {
final boolean isSecondaryDisplayPreferred = preferredTaskDisplayArea != null
&& preferredTaskDisplayArea.getDisplayId() != DEFAULT_DISPLAY;
- final boolean inSplitScreenMode = actualRootTask != null
- && actualRootTask.getDisplayArea().isSplitScreenModeActivated();
- if (((!inSplitScreenMode && preferredWindowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
- && !isSecondaryDisplayPreferred) || !task.isActivityTypeStandardOrUndefined()) {
+ if (!task.isActivityTypeStandardOrUndefined()) {
return;
}
@@ -2513,10 +2539,8 @@
== PERMISSION_GRANTED) {
mRecentTasks.setFreezeTaskListReordering();
}
- if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- || activityOptions.getLaunchRootTask() != null) {
- // Don't move home activity forward if we are launching into primary split or
- // there is a launch root set.
+ if (activityOptions.getLaunchRootTask() != null) {
+ // Don't move home activity forward if there is a launch root set.
moveHomeTaskForward = false;
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 217fc09..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.
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index f0e8b8f..f71fd1b 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -18,7 +18,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.view.Display.TYPE_INTERNAL;
import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES;
import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
@@ -1122,6 +1121,7 @@
if (!mNavButtonForcedVisible) {
inOutFrame.inset(windowState.getLayoutingAttrs(
displayFrames.mRotation).providedInternalInsets);
+ inOutFrame.inset(win.mGivenContentInsets);
}
},
@@ -1190,9 +1190,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);
}
}
@@ -1715,8 +1718,7 @@
// and mTopIsFullscreen is that mTopIsFullscreen is set only if the window
// requests to hide the status bar. Not sure if there is another way that to be the
// case though.
- if (!topIsFullscreen || mDisplayContent.getDefaultTaskDisplayArea()
- .isRootTaskVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) {
+ if (!topIsFullscreen) {
topAppHidesStatusBar = false;
}
}
@@ -2423,8 +2425,7 @@
private int updateSystemBarsLw(WindowState win, int disableFlags) {
final TaskDisplayArea defaultTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
final boolean multiWindowTaskVisible =
- defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
- || defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_MULTI_WINDOW);
+ defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_MULTI_WINDOW);
final boolean freeformRootTaskVisible =
defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM);
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/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/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index dca0bbd..a049d65 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -27,7 +27,6 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
@@ -1370,16 +1369,6 @@
switch (task.getWindowingMode()) {
case WINDOWING_MODE_PINNED:
return false;
- case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
- if (DEBUG_RECENTS_TRIM_TASKS) {
- Slog.d(TAG, "\ttop=" + task.getRootTask().getTopMostTask());
- }
- final Task rootTask = task.getRootTask();
- if (rootTask != null && rootTask.getTopMostTask() == task) {
- // Only the non-top task of the primary split screen mode is visible
- return false;
- }
- break;
case WINDOWING_MODE_MULTI_WINDOW:
// Ignore tasks that are always on top
if (task.isAlwaysOnTopWhenVisible()) {
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..6d5957e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -30,7 +30,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
import static android.app.WindowConfiguration.windowingModeToString;
@@ -125,6 +124,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 +610,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,
@@ -925,13 +928,6 @@
if (!animate) {
mTaskSupervisor.mNoAnimActivities.add(topActivity);
}
-
- if (toRootTaskWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- && moveRootTaskMode == REPARENT_KEEP_ROOT_TASK_AT_FRONT) {
- // Move recents to front so it is not behind root home task when going into docked
- // mode
- mTaskSupervisor.moveRecentsRootTaskToFront(reason);
- }
} finally {
mAtmService.continueWindowLayout();
}
@@ -1749,7 +1745,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);
}
/**
@@ -2326,8 +2322,7 @@
final int windowingMode = getWindowingMode();
if (!isActivityTypeStandardOrUndefined()
- || windowingMode == WINDOWING_MODE_FULLSCREEN
- || (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && !isResizeable())) {
+ || windowingMode == WINDOWING_MODE_FULLSCREEN) {
return isResizeable() ? rootTask.getRequestedOverrideBounds() : null;
} else if (!getWindowConfiguration().persistTaskBounds()) {
return rootTask.getRequestedOverrideBounds();
@@ -3308,6 +3303,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 +5977,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..4006848 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) {
@@ -387,6 +393,8 @@
if (mParent != null) {
mParent.onChildAdded(this);
+ } else if (mSurfaceAnimator.hasLeash()) {
+ mSurfaceAnimator.cancelAnimation();
}
if (!mReparenting) {
onSyncReparent(oldParent, mParent);
@@ -487,6 +495,11 @@
t.reparent(sc, mSurfaceControl);
}
}
+
+ if (mOverlayHost != null) {
+ mOverlayHost.setParent(t, mSurfaceControl);
+ }
+
scheduleAnimation();
}
@@ -632,6 +645,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 +2325,9 @@
wc.assignLayer(t, layer++);
}
}
+ if (mOverlayHost != null) {
+ mOverlayHost.setLayer(t, layer++);
+ }
}
void assignChildLayers() {
@@ -3570,4 +3590,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 c239e68..e4216bf 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -267,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;
@@ -2245,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
@@ -5064,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.
@@ -5381,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");
@@ -7891,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) {
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..9b346312 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/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/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index d7e3195..8cf6fe3 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -130,6 +130,7 @@
AndroidPackage::getLabelRes,
AndroidPackage::getLargestWidthLimitDp,
AndroidPackage::getLogo,
+ AndroidPackage::getLocaleConfigRes,
AndroidPackage::getManageSpaceActivityName,
AndroidPackage::getMemtagMode,
AndroidPackage::getMinSdkVersion,
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 d79a833..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
@@ -71,6 +71,8 @@
private InputController.NativeWrapper mNativeWrapperMock;
@Mock
private DisplayManagerInternal mDisplayManagerInternalMock;
+ @Mock
+ private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback;
@Before
public void setUp() {
@@ -85,7 +87,8 @@
mInputController = new InputController(new Object(), mNativeWrapperMock);
mDeviceImpl = new VirtualDeviceImpl(mContext,
/* association info */ null, new Binder(), /* uid */ 0, mInputController,
- (int associationId) -> {}, new VirtualDeviceParams.Builder().build());
+ (int associationId) -> {}, mPendingTrampolineCallback,
+ new VirtualDeviceParams.Builder().build());
}
@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/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
index b588db6..18f2642 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
@@ -92,7 +92,6 @@
this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(hdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mHdmiControlService.initService();
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
index ff01cb1..e4c5ad67 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
@@ -109,7 +109,6 @@
hdmiControlService, mNativeWrapper, hdmiControlService.getAtomWriter());
hdmiControlService.setCecController(hdmiCecController);
hdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(hdmiControlService));
- hdmiControlService.setMessageValidator(new HdmiCecMessageValidator(hdmiControlService));
hdmiControlService.initService();
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
hdmiControlService.setPowerManager(mPowerManager);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index a44a5cd..d73cdb5 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -102,7 +102,6 @@
hdmiControlService, mNativeWrapper, hdmiControlService.getAtomWriter());
hdmiControlService.setCecController(hdmiCecController);
hdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(hdmiControlService));
- hdmiControlService.setMessageValidator(new HdmiCecMessageValidator(hdmiControlService));
hdmiControlService.initService();
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
hdmiControlService.setPowerManager(mPowerManager);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
index 9c99240..5cec8ad 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
@@ -53,7 +53,7 @@
@Before
public void SetUp() {
- mDeviceInfoForTests = new HdmiDeviceInfo(1001, 1234);
+ mDeviceInfoForTests = HdmiDeviceInfo.hardwarePort(1001, 1234);
HdmiControlService hdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
Collections.emptyList()) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
index 638b386..52a0b6c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
@@ -109,7 +109,6 @@
this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(hdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mHdmiControlService.initService();
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
index 41231e0..35432ed 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
@@ -128,10 +128,8 @@
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiMhlControllerStub = HdmiMhlControllerStub.create(mHdmiControlService);
mHdmiControlService.setHdmiMhlController(mHdmiMhlControllerStub);
- mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(mHdmiMhlControllerStub);
- mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mHdmiCecNetwork = new HdmiCecNetwork(mHdmiControlService,
mHdmiCecController, mHdmiMhlControllerStub);
mHdmiControlService.setHdmiCecNetwork(mHdmiCecNetwork);
@@ -156,13 +154,13 @@
mPlaybackLogicalAddress3 = mPlaybackLogicalAddress1 == ADDR_PLAYBACK_3
? ADDR_PLAYBACK_1 : ADDR_PLAYBACK_3;
- mReportPowerStatusOn = new HdmiCecMessage(
+ mReportPowerStatusOn = HdmiCecMessage.build(
mPlaybackLogicalAddress2, mPlaybackLogicalAddress1,
Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON);
- mReportPowerStatusStandby = new HdmiCecMessage(
+ mReportPowerStatusStandby = HdmiCecMessage.build(
mPlaybackLogicalAddress2, mPlaybackLogicalAddress1,
Constants.MESSAGE_REPORT_POWER_STATUS, POWER_STANDBY);
- mReportPowerStatusTransientToOn = new HdmiCecMessage(
+ mReportPowerStatusTransientToOn = HdmiCecMessage.build(
mPlaybackLogicalAddress2, mPlaybackLogicalAddress1,
Constants.MESSAGE_REPORT_POWER_STATUS, POWER_TRANSIENT_TO_ON);
mSetStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(
@@ -173,21 +171,36 @@
mActiveSource = HdmiCecMessageBuilder.buildActiveSource(
mPlaybackLogicalAddress2, PHYSICAL_ADDRESS_PLAYBACK_2);
- HdmiDeviceInfo infoPlayback1 = new HdmiDeviceInfo(
- mPlaybackLogicalAddress1, PHYSICAL_ADDRESS_PLAYBACK_1, PORT_1,
- HdmiDeviceInfo.DEVICE_PLAYBACK,
- 0x1234, "Playback 1",
- HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- HdmiDeviceInfo infoPlayback2 = new HdmiDeviceInfo(
- mPlaybackLogicalAddress2, PHYSICAL_ADDRESS_PLAYBACK_2, PORT_2,
- HdmiDeviceInfo.DEVICE_PLAYBACK,
- 0x1234, "Playback 2",
- HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- HdmiDeviceInfo infoPlayback3 = new HdmiDeviceInfo(
- mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3, PORT_3,
- HdmiDeviceInfo.DEVICE_PLAYBACK,
- 0x1234, "Playback 3",
- HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+ HdmiDeviceInfo infoPlayback1 = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(mPlaybackLogicalAddress1)
+ .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYBACK_1)
+ .setPortId(PORT_1)
+ .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK)
+ .setVendorId(0x1234)
+ .setDisplayName("Playback 1")
+ .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON)
+ .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B)
+ .build();
+ HdmiDeviceInfo infoPlayback2 = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(mPlaybackLogicalAddress2)
+ .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYBACK_2)
+ .setPortId(PORT_2)
+ .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK)
+ .setVendorId(0x1234)
+ .setDisplayName("Playback 2")
+ .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON)
+ .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B)
+ .build();
+ HdmiDeviceInfo infoPlayback3 = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(mPlaybackLogicalAddress3)
+ .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYBACK_3)
+ .setPortId(PORT_3)
+ .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK)
+ .setVendorId(0x1234)
+ .setDisplayName("Playback 3")
+ .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON)
+ .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B)
+ .build();
mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoPlayback1);
mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoPlayback2);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
index dd74864..e77cd91 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
@@ -64,22 +64,34 @@
private static final byte[] POWER_ON = new byte[] { POWER_STATUS_ON };
private static final byte[] POWER_STANDBY = new byte[] { POWER_STATUS_STANDBY };
private static final byte[] POWER_TRANSIENT_TO_ON = new byte[] { POWER_STATUS_TRANSIENT_TO_ON };
- private static final HdmiCecMessage REPORT_POWER_STATUS_ON = new HdmiCecMessage(
+ private static final HdmiCecMessage REPORT_POWER_STATUS_ON = HdmiCecMessage.build(
ADDR_PLAYBACK_1, ADDR_TV, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON);
- private static final HdmiCecMessage REPORT_POWER_STATUS_STANDBY = new HdmiCecMessage(
+ private static final HdmiCecMessage REPORT_POWER_STATUS_STANDBY = HdmiCecMessage.build(
ADDR_PLAYBACK_1, ADDR_TV, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_STANDBY);
- private static final HdmiCecMessage REPORT_POWER_STATUS_TRANSIENT_TO_ON = new HdmiCecMessage(
+ private static final HdmiCecMessage REPORT_POWER_STATUS_TRANSIENT_TO_ON = HdmiCecMessage.build(
ADDR_PLAYBACK_1, ADDR_TV, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_TRANSIENT_TO_ON);
private static final HdmiCecMessage SET_STREAM_PATH = HdmiCecMessageBuilder.buildSetStreamPath(
ADDR_TV, PHYSICAL_ADDRESS_PLAYBACK_1);
- private static final HdmiDeviceInfo INFO_PLAYBACK_1 = new HdmiDeviceInfo(
- ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYBACK_1, PORT_1, HdmiDeviceInfo.DEVICE_PLAYBACK,
- 0x1234, "Playback 1",
- HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- private static final HdmiDeviceInfo INFO_PLAYBACK_2 = new HdmiDeviceInfo(
- ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2, PORT_2, HdmiDeviceInfo.DEVICE_PLAYBACK,
- 0x1234, "Playback 2",
- HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+ private static final HdmiDeviceInfo INFO_PLAYBACK_1 = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(ADDR_PLAYBACK_1)
+ .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYBACK_1)
+ .setPortId(PORT_1)
+ .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK)
+ .setVendorId(0x1234)
+ .setDisplayName("Plyback 1")
+ .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON)
+ .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B)
+ .build();
+ private static final HdmiDeviceInfo INFO_PLAYBACK_2 = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(ADDR_PLAYBACK_2)
+ .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYBACK_2)
+ .setPortId(PORT_2)
+ .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK)
+ .setVendorId(0x1234)
+ .setDisplayName("Playback 2")
+ .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON)
+ .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B)
+ .build();
private HdmiControlService mHdmiControlService;
private HdmiCecController mHdmiCecController;
@@ -123,7 +135,6 @@
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mLocalDevices.add(mHdmiCecLocalDeviceTv);
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2];
hdmiPortInfos[0] =
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
index d630ef6..559a2c0 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
@@ -25,6 +25,7 @@
import com.google.common.collect.Iterables;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -77,7 +78,8 @@
if (body.length == 0) {
return mPollAddressResponse[dstAddress];
} else {
- HdmiCecMessage message = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body);
+ HdmiCecMessage message = HdmiCecMessage.build(srcAddress, dstAddress, body[0],
+ Arrays.copyOfRange(body, 1, body.length));
mResultMessages.add(message);
return mMessageSendResult.getOrDefault(message.getOpcode(), SendMessageResult.SUCCESS);
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/GiveFeaturesActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/GiveFeaturesActionTest.java
new file mode 100644
index 0000000..0b31db6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/GiveFeaturesActionTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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.hdmi;
+
+import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED;
+import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORT_UNKNOWN;
+import static android.hardware.hdmi.HdmiControlManager.HDMI_CEC_VERSION_2_0;
+import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
+
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.hdmi.DeviceFeatures;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.SystemService;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class GiveFeaturesActionTest {
+ private HdmiControlService mHdmiControlServiceSpy;
+ private HdmiCecController mHdmiCecController;
+ private HdmiCecLocalDevicePlayback mPlaybackDevice;
+ private FakeNativeWrapper mNativeWrapper;
+ private FakePowerManagerWrapper mPowerManager;
+ private Looper mLooper;
+ private Context mContextSpy;
+ private TestLooper mTestLooper = new TestLooper();
+ private int mPhysicalAddress = 0x1100;
+ private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+ private int mPlaybackLogicalAddress;
+
+ private TestCallback mTestCallback;
+ private GiveFeaturesAction mAction;
+
+ /**
+ * Setup: Local Playback device queries the features of a connected TV.
+ */
+ @Before
+ public void setUp() throws RemoteException {
+ mContextSpy = spy(new ContextWrapper(
+ InstrumentationRegistry.getInstrumentation().getTargetContext()));
+
+ mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList()));
+ doNothing().when(mHdmiControlServiceSpy)
+ .writeStringSystemProperty(anyString(), anyString());
+
+ mLooper = mTestLooper.getLooper();
+ mHdmiControlServiceSpy.setIoLooper(mLooper);
+ mHdmiControlServiceSpy.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy));
+
+ mNativeWrapper = new FakeNativeWrapper();
+ mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
+
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+ mHdmiControlServiceSpy, mNativeWrapper, mHdmiControlServiceSpy.getAtomWriter());
+ mHdmiControlServiceSpy.setCecController(mHdmiCecController);
+ mHdmiControlServiceSpy.setHdmiMhlController(
+ HdmiMhlControllerStub.create(mHdmiControlServiceSpy));
+ mHdmiControlServiceSpy.initService();
+ mPowerManager = new FakePowerManagerWrapper(mContextSpy);
+ mHdmiControlServiceSpy.setPowerManager(mPowerManager);
+
+ mPlaybackDevice = new HdmiCecLocalDevicePlayback(mHdmiControlServiceSpy);
+ mPlaybackDevice.init();
+ mLocalDevices.add(mPlaybackDevice);
+
+ mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mHdmiControlServiceSpy.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ mTestLooper.dispatchAll();
+
+ synchronized (mPlaybackDevice.mLock) {
+ mPlaybackLogicalAddress = mPlaybackDevice.getDeviceInfo().getLogicalAddress();
+ }
+
+ // Setup specific to these tests
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ Constants.ADDR_TV, 0x0000, HdmiDeviceInfo.DEVICE_TV));
+ mTestLooper.dispatchAll();
+
+ mTestCallback = new TestCallback();
+ mAction = new GiveFeaturesAction(mPlaybackDevice, Constants.ADDR_TV, mTestCallback);
+ }
+
+ @Test
+ public void sendsGiveFeaturesMessage() {
+ mPlaybackDevice.addAndStartAction(mAction);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage giveFeatures = HdmiCecMessageBuilder.buildGiveFeatures(
+ mPlaybackLogicalAddress, Constants.ADDR_TV);
+ assertThat(mNativeWrapper.getResultMessages()).contains(giveFeatures);
+ }
+
+ @Test
+ public void noMatchingReportFeaturesReceived_actionFailsAndNetworkIsNotUpdated() {
+ mPlaybackDevice.addAndStartAction(mAction);
+ mTestLooper.dispatchAll();
+
+ // Wrong source
+ mNativeWrapper.onCecMessage(ReportFeaturesMessage.build(
+ Constants.ADDR_AUDIO_SYSTEM, HdmiControlManager.HDMI_CEC_VERSION_2_0,
+ Arrays.asList(DEVICE_AUDIO_SYSTEM), Constants.RC_PROFILE_SOURCE,
+ Collections.emptyList(), DeviceFeatures.NO_FEATURES_SUPPORTED));
+ mTestLooper.dispatchAll();
+
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ @DeviceFeatures.FeatureSupportStatus int avcSupport =
+ mHdmiControlServiceSpy.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_TV)
+ .getDeviceFeatures().getSetAudioVolumeLevelSupport();
+
+ assertThat(avcSupport).isEqualTo(FEATURE_SUPPORT_UNKNOWN);
+ assertThat(mTestCallback.getResult()).isEqualTo(
+ HdmiControlManager.RESULT_COMMUNICATION_FAILED);
+ }
+
+ @Test
+ public void matchingReportFeaturesReceived_actionSucceedsAndNetworkIsUpdated() {
+ mPlaybackDevice.addAndStartAction(mAction);
+ mTestLooper.dispatchAll();
+
+ mNativeWrapper.onCecMessage(
+ ReportFeaturesMessage.build(
+ Constants.ADDR_TV, HDMI_CEC_VERSION_2_0, Collections.emptyList(),
+ Constants.RC_PROFILE_TV, Lists.newArrayList(Constants.RC_PROFILE_TV_NONE),
+ DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
+ .setSetAudioVolumeLevelSupport(FEATURE_SUPPORTED)
+ .build()
+ )
+ );
+ mTestLooper.dispatchAll();
+
+ @DeviceFeatures.FeatureSupportStatus int avcSupport =
+ mHdmiControlServiceSpy.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_TV)
+ .getDeviceFeatures().getSetAudioVolumeLevelSupport();
+
+ assertThat(avcSupport).isEqualTo(FEATURE_SUPPORTED);
+ assertThat(mTestCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+ }
+
+ private static class TestCallback extends IHdmiControlCallback.Stub {
+ private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>();
+
+ @Override
+ public void onComplete(int result) {
+ mCallbackResult.add(result);
+ }
+
+ private int getResult() {
+ assertThat(mCallbackResult.size()).isEqualTo(1);
+ return mCallbackResult.get(0);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index f30e97a..30bcc7e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -98,8 +98,6 @@
doReturn(hdmiCecConfig).when(mHdmiControlServiceSpy).getHdmiCecConfig();
mHdmiControlServiceSpy.setIoLooper(mLooper);
- mHdmiControlServiceSpy.setMessageValidator(
- new HdmiCecMessageValidator(mHdmiControlServiceSpy));
mHdmiControlServiceSpy.setCecMessageBuffer(
new CecMessageBuffer(mHdmiControlServiceSpy));
@@ -226,7 +224,7 @@
@Test
public void testMessageReported_writesAtom_userControlPressed_noParams() {
- HdmiCecMessage message = new HdmiCecMessage(
+ HdmiCecMessage message = HdmiCecMessage.build(
Constants.ADDR_TV,
Constants.ADDR_PLAYBACK_1,
Constants.MESSAGE_USER_CONTROL_PRESSED,
@@ -279,7 +277,7 @@
@Test
public void testMessageReported_writesAtom_featureAbort_noParams() {
- HdmiCecMessage message = new HdmiCecMessage(
+ HdmiCecMessage message = HdmiCecMessage.build(
Constants.ADDR_TV,
Constants.ADDR_PLAYBACK_1,
Constants.MESSAGE_FEATURE_ABORT,
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index a411392..70bc460 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -188,7 +188,6 @@
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem);
mLocalDevices.add(mHdmiCecLocalDevicePlayback);
mHdmiCecLocalDeviceAudioSystem.setRoutingControlFeatureEnabled(true);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 2d13e69..6fc3354 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -59,10 +59,16 @@
HotplugDetectionAction.POLLING_INTERVAL_MS_FOR_PLAYBACK;
private static final int PORT_1 = 1;
- private static final HdmiDeviceInfo INFO_TV = new HdmiDeviceInfo(
- ADDR_TV, 0x0000, PORT_1, HdmiDeviceInfo.DEVICE_TV,
- 0x1234, "TV",
- HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+ private static final HdmiDeviceInfo INFO_TV = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(ADDR_TV)
+ .setPhysicalAddress(0x0000)
+ .setPortId(PORT_1)
+ .setDeviceType(HdmiDeviceInfo.DEVICE_TV)
+ .setVendorId(0x1234)
+ .setDisplayName("TV")
+ .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON)
+ .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B)
+ .build();
private HdmiControlService mHdmiControlService;
private HdmiCecController mHdmiCecController;
@@ -138,7 +144,6 @@
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mLocalDevices.add(mHdmiCecLocalDevicePlayback);
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
hdmiPortInfos[0] =
@@ -1691,10 +1696,16 @@
public void hotplugDetectionAction_removeDevice() {
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mHdmiControlService.getHdmiCecNetwork().clearDeviceList();
- HdmiDeviceInfo infoPlayback = new HdmiDeviceInfo(
- Constants.ADDR_PLAYBACK_2, 0x1234, PORT_1,
- HdmiDeviceInfo.DEVICE_PLAYBACK, 0x1234, "Playback 2",
- HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+ HdmiDeviceInfo infoPlayback = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(Constants.ADDR_PLAYBACK_2)
+ .setPhysicalAddress(0x1234)
+ .setPortId(PORT_1)
+ .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK)
+ .setVendorId(0x1234)
+ .setDisplayName("Playback 2")
+ .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON)
+ .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B)
+ .build();
mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoPlayback);
// This logical address (ADDR_PLAYBACK_2) won't acknowledge the poll message sent by the
// HotplugDetectionAction so it shall be removed.
@@ -1726,8 +1737,15 @@
@Test
public void getActiveSource_deviceInNetworkIsActiveSource() {
- HdmiDeviceInfo externalDevice = new HdmiDeviceInfo(Constants.ADDR_PLAYBACK_3, 0x3000, 0,
- Constants.ADDR_PLAYBACK_1, 0, "Test Device");
+ HdmiDeviceInfo externalDevice = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(Constants.ADDR_PLAYBACK_3)
+ .setPhysicalAddress(0x3000)
+ .setPortId(0)
+ .setDeviceType(Constants.ADDR_PLAYBACK_1)
+ .setVendorId(0)
+ .setDisplayName("Test Device")
+ .build();
+
mHdmiControlService.getHdmiCecNetwork().addCecDevice(externalDevice);
mTestLooper.dispatchAll();
@@ -1739,8 +1757,14 @@
@Test
public void getActiveSource_unknownDeviceIsActiveSource() {
- HdmiDeviceInfo externalDevice = new HdmiDeviceInfo(Constants.ADDR_PLAYBACK_3, 0x3000, 0,
- Constants.ADDR_PLAYBACK_1, 0, "Test Device");
+ HdmiDeviceInfo externalDevice = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(Constants.ADDR_PLAYBACK_3)
+ .setPhysicalAddress(0x3000)
+ .setPortId(0)
+ .setDeviceType(Constants.ADDR_PLAYBACK_1)
+ .setVendorId(0)
+ .setDisplayName("Test Device")
+ .build();
mHdmiControlService.setActiveSource(externalDevice.getLogicalAddress(),
externalDevice.getPhysicalAddress(), "HdmiControlServiceTest");
@@ -1933,7 +1957,7 @@
@Test
public void doesNotSupportRecordTvScreen() {
- HdmiCecMessage recordTvScreen = new HdmiCecMessage(ADDR_TV, mPlaybackLogicalAddress,
+ HdmiCecMessage recordTvScreen = HdmiCecMessage.build(ADDR_TV, mPlaybackLogicalAddress,
Constants.MESSAGE_RECORD_TV_SCREEN, HdmiCecMessage.EMPTY_PARAM);
mNativeWrapper.onCecMessage(recordTvScreen);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index f5af6df..fb8baa3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -109,11 +109,6 @@
protected List<Integer> getRcFeatures() {
return Collections.emptyList();
}
-
- @Override
- protected List<Integer> getDeviceFeatures() {
- return Collections.emptyList();
- }
}
private MyHdmiCecLocalDevice mHdmiLocalDevice;
@@ -187,14 +182,6 @@
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiLocalDevice = new MyHdmiCecLocalDevice(mHdmiControlService, DEVICE_TV);
- mMessageValidator =
- new HdmiCecMessageValidator(mHdmiControlService) {
- @Override
- int isValid(HdmiCecMessage message, boolean isMessageReceived) {
- return HdmiCecMessageValidator.OK;
- }
- };
- mHdmiControlService.setMessageValidator(mMessageValidator);
mLocalDevices.add(mHdmiLocalDevice);
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
@@ -230,7 +217,7 @@
@Test
public void dispatchMessage_logicalAddressDoesNotMatch() {
HdmiCecMessage msg =
- new HdmiCecMessage(
+ HdmiCecMessage.build(
ADDR_TV,
ADDR_PLAYBACK_1,
Constants.MESSAGE_CEC_VERSION,
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index a260a6d..b6c4bc2 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -121,7 +121,6 @@
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mLocalDevices.add(mHdmiCecLocalDeviceTv);
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2];
hdmiPortInfos[0] =
@@ -172,8 +171,14 @@
@Test
public void getActiveSource_deviceInNetworkIsActiveSource() {
- HdmiDeviceInfo externalDevice = new HdmiDeviceInfo(Constants.ADDR_PLAYBACK_3, 0x1000, 0,
- Constants.ADDR_PLAYBACK_1, 0, "Test Device");
+ HdmiDeviceInfo externalDevice = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(Constants.ADDR_PLAYBACK_3)
+ .setPhysicalAddress(0x3000)
+ .setPortId(0)
+ .setDeviceType(Constants.ADDR_PLAYBACK_1)
+ .setVendorId(0)
+ .setDisplayName("Test Device")
+ .build();
mHdmiControlService.getHdmiCecNetwork().addCecDevice(externalDevice);
mTestLooper.dispatchAll();
@@ -185,7 +190,7 @@
@Test
public void getActiveSource_unknownLogicalAddressInNetworkIsActiveSource() {
- HdmiDeviceInfo externalDevice = new HdmiDeviceInfo(0x1000, 1);
+ HdmiDeviceInfo externalDevice = HdmiDeviceInfo.hardwarePort(0x1000, 1);
mHdmiControlService.setActiveSource(Constants.ADDR_UNREGISTERED,
externalDevice.getPhysicalAddress(), "HdmiControlServiceTest");
@@ -197,8 +202,14 @@
@Test
public void getActiveSource_unknownDeviceIsActiveSource() {
- HdmiDeviceInfo externalDevice = new HdmiDeviceInfo(Constants.ADDR_PLAYBACK_3, 0x1000, 0,
- Constants.ADDR_PLAYBACK_1, 0, "Test Device");
+ HdmiDeviceInfo externalDevice = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(Constants.ADDR_PLAYBACK_3)
+ .setPhysicalAddress(0x0000)
+ .setPortId(0)
+ .setDeviceType(ADDR_PLAYBACK_1)
+ .setVendorId(0)
+ .setDisplayName("Test Device")
+ .build();
mHdmiControlService.setActiveSource(externalDevice.getLogicalAddress(),
externalDevice.getPhysicalAddress(), "HdmiControlServiceTest");
@@ -240,7 +251,7 @@
HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED);
mTestLooper.dispatchAll();
mPowerManager.setInteractive(false);
- HdmiCecMessage imageViewOn = new HdmiCecMessage(ADDR_PLAYBACK_1, mTvLogicalAddress,
+ HdmiCecMessage imageViewOn = HdmiCecMessage.build(ADDR_PLAYBACK_1, mTvLogicalAddress,
Constants.MESSAGE_IMAGE_VIEW_ON, HdmiCecMessage.EMPTY_PARAM);
assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
@@ -268,7 +279,7 @@
HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_DISABLED);
mTestLooper.dispatchAll();
mPowerManager.setInteractive(false);
- HdmiCecMessage imageViewOn = new HdmiCecMessage(ADDR_PLAYBACK_1, mTvLogicalAddress,
+ HdmiCecMessage imageViewOn = HdmiCecMessage.build(ADDR_PLAYBACK_1, mTvLogicalAddress,
Constants.MESSAGE_IMAGE_VIEW_ON, HdmiCecMessage.EMPTY_PARAM);
assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
@@ -478,7 +489,7 @@
@Test
public void supportsRecordTvScreen() {
- HdmiCecMessage recordTvScreen = new HdmiCecMessage(ADDR_RECORDER_1, mTvLogicalAddress,
+ HdmiCecMessage recordTvScreen = HdmiCecMessage.build(ADDR_RECORDER_1, mTvLogicalAddress,
Constants.MESSAGE_RECORD_TV_SCREEN, HdmiCecMessage.EMPTY_PARAM);
mNativeWrapper.onCecMessage(recordTvScreen);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
index 453303e..f869462 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
@@ -22,20 +22,15 @@
import static com.google.common.truth.Truth.assertThat;
-import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
-import com.google.android.collect.Lists;
-
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import java.util.Collections;
-
@SmallTest
@Presubmit
@RunWith(JUnit4.class)
@@ -100,89 +95,4 @@
assertThat(message).isEqualTo(buildMessage("40:A5"));
}
-
- @Test
- public void buildReportFeatures_basicTv_1_4() {
- HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV,
- HdmiControlManager.HDMI_CEC_VERSION_1_4_B,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
- Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), Collections.emptyList());
-
- assertThat(message).isEqualTo(buildMessage("0F:A6:05:80:00:00"));
- }
-
- @Test
- public void buildReportFeatures_basicPlayback_1_4() {
- HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_PLAYBACK_1,
- HdmiControlManager.HDMI_CEC_VERSION_1_4_B,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_TV,
- Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), Collections.emptyList());
-
- assertThat(message).isEqualTo(buildMessage("4F:A6:05:10:00:00"));
- }
-
- @Test
- public void buildReportFeatures_basicPlaybackAudioSystem_1_4() {
- HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_PLAYBACK_1,
- HdmiControlManager.HDMI_CEC_VERSION_1_4_B,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK,
- HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM), Constants.RC_PROFILE_TV,
- Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), Collections.emptyList());
-
- assertThat(message).isEqualTo(buildMessage("4F:A6:05:18:00:00"));
- }
-
- @Test
- public void buildReportFeatures_basicTv_2_0() {
- HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV,
- HdmiControlManager.HDMI_CEC_VERSION_2_0,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
- Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), Collections.emptyList());
-
- assertThat(message).isEqualTo(buildMessage("0F:A6:06:80:00:00"));
- }
-
- @Test
- public void buildReportFeatures_remoteControlTv_2_0() {
- HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV,
- HdmiControlManager.HDMI_CEC_VERSION_2_0,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
- Lists.newArrayList(Constants.RC_PROFILE_TV_ONE), Collections.emptyList());
-
- assertThat(message).isEqualTo(buildMessage("0F:A6:06:80:02:00"));
- }
-
- @Test
- public void buildReportFeatures_remoteControlPlayback_2_0() {
- HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV,
- HdmiControlManager.HDMI_CEC_VERSION_2_0,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_SOURCE,
- Lists.newArrayList(Constants.RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
- Constants.RC_PROFILE_SOURCE_HANDLES_SETUP_MENU), Collections.emptyList());
-
- assertThat(message).isEqualTo(buildMessage("0F:A6:06:10:4A:00"));
- }
-
- @Test
- public void buildReportFeatures_deviceFeaturesTv_2_0() {
- HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV,
- HdmiControlManager.HDMI_CEC_VERSION_2_0,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
- Lists.newArrayList(Constants.RC_PROFILE_TV_NONE),
- Lists.newArrayList(Constants.DEVICE_FEATURE_TV_SUPPORTS_RECORD_TV_SCREEN));
-
- assertThat(message).isEqualTo(buildMessage("0F:A6:06:80:00:40"));
- }
-
- @Test
- public void buildReportFeatures_deviceFeaturesPlayback_2_0() {
- HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV,
- HdmiControlManager.HDMI_CEC_VERSION_2_0,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_SOURCE,
- Lists.newArrayList(Constants.RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
- Constants.RC_PROFILE_SOURCE_HANDLES_SETUP_MENU),
- Lists.newArrayList(Constants.DEVICE_FEATURE_SUPPORTS_DECK_CONTROL));
-
- assertThat(message).isEqualTo(buildMessage("0F:A6:06:10:4A:10"));
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageTest.java
index cca5094..2984cfa 100755
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageTest.java
@@ -41,12 +41,12 @@
new EqualsTester()
.addEqualityGroup(
- new HdmiCecMessage(source, destination, opcode, params1),
- new HdmiCecMessage(source, destination, opcode, params1))
- .addEqualityGroup(new HdmiCecMessage(source, destination, opcode, params2))
- .addEqualityGroup(new HdmiCecMessage(source + 1, destination, opcode, params1))
- .addEqualityGroup(new HdmiCecMessage(source, destination + 1, opcode, params1))
- .addEqualityGroup(new HdmiCecMessage(source, destination, opcode + 1, params1))
+ HdmiCecMessage.build(source, destination, opcode, params1),
+ HdmiCecMessage.build(source, destination, opcode, params1))
+ .addEqualityGroup(HdmiCecMessage.build(source, destination, opcode, params2))
+ .addEqualityGroup(HdmiCecMessage.build(source + 1, destination, opcode, params1))
+ .addEqualityGroup(HdmiCecMessage.build(source, destination + 1, opcode, params1))
+ .addEqualityGroup(HdmiCecMessage.build(source, destination, opcode + 1, params1))
.testEquals();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index 548a439..50c9f70 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -54,7 +54,6 @@
InstrumentationRegistry.getTargetContext(), Collections.emptyList());
mHdmiControlService.setIoLooper(mTestLooper.getLooper());
- mHdmiCecMessageValidator = new HdmiCecMessageValidator(mHdmiControlService);
}
@Test
@@ -400,16 +399,6 @@
}
@Test
- public void isValid_reportFeatures() {
- assertMessageValidity("0F:A6:05:80:00:00").isEqualTo(OK);
-
- assertMessageValidity("04:A6:05:80:00:00").isEqualTo(ERROR_DESTINATION);
- assertMessageValidity("FF:A6:05:80:00:00").isEqualTo(ERROR_SOURCE);
-
- assertMessageValidity("0F:A6").isEqualTo(ERROR_PARAMETER_SHORT);
- }
-
- @Test
public void isValid_deckControl() {
assertMessageValidity("40:42:01:6E").isEqualTo(OK);
assertMessageValidity("40:42:04").isEqualTo(OK);
@@ -649,6 +638,6 @@
}
private IntegerSubject assertMessageValidity(String message) {
- return assertThat(mHdmiCecMessageValidator.isValid(HdmiUtils.buildMessage(message), false));
+ return assertThat(HdmiUtils.buildMessage(message).getValidationResult());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
index 1048eb5..42fa32c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
@@ -17,9 +17,12 @@
package com.android.server.hdmi;
+import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED;
+
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
+import android.hardware.hdmi.DeviceFeatures;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
@@ -80,7 +83,6 @@
mHdmiMhlControllerStub = HdmiMhlControllerStub.create(mHdmiControlService);
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(mHdmiMhlControllerStub);
- mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mHdmiCecNetwork = new HdmiCecNetwork(mHdmiControlService,
mHdmiCecController, mHdmiMhlControllerStub);
@@ -178,7 +180,7 @@
assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED);
assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(
HdmiUtils.getDefaultDeviceName(logicalAddress));
- assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID);
+ assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.VENDOR_ID_UNKNOWN);
assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(
HdmiControlManager.POWER_STATUS_UNKNOWN);
@@ -216,7 +218,7 @@
assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(type);
assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(
HdmiUtils.getDefaultDeviceName(logicalAddress));
- assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID);
+ assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.VENDOR_ID_UNKNOWN);
assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(
HdmiControlManager.POWER_STATUS_UNKNOWN);
}
@@ -258,7 +260,7 @@
assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
Constants.INVALID_PHYSICAL_ADDRESS);
assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED);
- assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID);
+ assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.VENDOR_ID_UNKNOWN);
assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(
HdmiUtils.getDefaultDeviceName(logicalAddress));
assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus);
@@ -279,7 +281,7 @@
assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
Constants.INVALID_PHYSICAL_ADDRESS);
assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED);
- assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID);
+ assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.VENDOR_ID_UNKNOWN);
assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(osdName);
assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(
HdmiControlManager.POWER_STATUS_UNKNOWN);
@@ -471,7 +473,7 @@
assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
Constants.INVALID_PHYSICAL_ADDRESS);
assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED);
- assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID);
+ assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.VENDOR_ID_UNKNOWN);
assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(
HdmiUtils.getDefaultDeviceName(logicalAddress));
assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus);
@@ -514,12 +516,14 @@
int logicalAddress = Constants.ADDR_PLAYBACK_1;
int cecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B;
mHdmiCecNetwork.handleCecMessage(
- HdmiCecMessageBuilder.buildReportFeatures(logicalAddress,
+ ReportFeaturesMessage.build(logicalAddress,
cecVersion, Collections.emptyList(),
Constants.RC_PROFILE_SOURCE, Collections.emptyList(),
- Collections.emptyList()));
+ DeviceFeatures.NO_FEATURES_SUPPORTED));
- assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+ synchronized (mHdmiCecNetwork.mLock) {
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+ }
HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
@@ -531,12 +535,14 @@
int logicalAddress = Constants.ADDR_PLAYBACK_1;
int cecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0;
mHdmiCecNetwork.handleCecMessage(
- HdmiCecMessageBuilder.buildReportFeatures(logicalAddress,
+ ReportFeaturesMessage.build(logicalAddress,
cecVersion, Collections.emptyList(),
Constants.RC_PROFILE_SOURCE, Collections.emptyList(),
- Collections.emptyList()));
+ DeviceFeatures.NO_FEATURES_SUPPORTED));
- assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+ synchronized (mHdmiCecNetwork.mLock) {
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+ }
HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
@@ -544,10 +550,33 @@
}
@Test
- public void getSafeCecDevicesLocked_addDevice_sizeOne() {
- HdmiDeviceInfo cecDeviceInfo = new HdmiDeviceInfo();
+ public void cecDevices_tracking_reportFeatures_updatesDeviceFeatures() {
+ // Features should be set correctly with the initial <Report Features>
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ int cecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0;
+ DeviceFeatures deviceFeatures = DeviceFeatures.NO_FEATURES_SUPPORTED;
+ mHdmiCecNetwork.handleCecMessage(
+ ReportFeaturesMessage.build(logicalAddress,
+ cecVersion, Collections.emptyList(),
+ Constants.RC_PROFILE_SOURCE, Collections.emptyList(), deviceFeatures));
- mHdmiCecNetwork.addCecDevice(cecDeviceInfo);
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getDeviceFeatures()).isEqualTo(deviceFeatures);
+
+ // New information from <Report Features> should override old information
+ DeviceFeatures updatedFeatures = DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
+ .setSetAudioVolumeLevelSupport(FEATURE_SUPPORTED).build();
+ mHdmiCecNetwork.handleCecMessage(
+ ReportFeaturesMessage.build(logicalAddress,
+ cecVersion, Collections.emptyList(),
+ Constants.RC_PROFILE_SOURCE, Collections.emptyList(), updatedFeatures));
+ cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getDeviceFeatures()).isEqualTo(updatedFeatures);
+ }
+
+ @Test
+ public void getSafeCecDevicesLocked_addDevice_sizeOne() {
+ mHdmiCecNetwork.addCecDevice(HdmiDeviceInfo.INACTIVE_DEVICE);
assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
index bff1296..7a68285 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
@@ -98,7 +98,6 @@
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(hdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mLocalDevices.add(mHdmiCecLocalDevicePlayback);
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
hdmiPortInfos[0] =
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index a44bd8e..7751ef5 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -111,8 +111,6 @@
mHdmiControlServiceSpy.setCecController(mHdmiCecController);
mHdmiControlServiceSpy.setHdmiMhlController(HdmiMhlControllerStub.create(
mHdmiControlServiceSpy));
- mHdmiControlServiceSpy.setMessageValidator(new HdmiCecMessageValidator(
- mHdmiControlServiceSpy));
mLocalDevices.add(mAudioSystemDeviceSpy);
mLocalDevices.add(mPlaybackDeviceSpy);
@@ -487,7 +485,7 @@
Constants.ADDR_PLAYBACK_1));
mTestLooper.dispatchAll();
- HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures(
+ HdmiCecMessage reportFeatures = ReportFeaturesMessage.build(
Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0,
Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM),
mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(),
@@ -505,7 +503,7 @@
mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
- HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures(
+ HdmiCecMessage reportFeatures = ReportFeaturesMessage.build(
Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0,
Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM),
mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(),
@@ -522,7 +520,7 @@
mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
- HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures(
+ HdmiCecMessage reportFeatures = ReportFeaturesMessage.build(
Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0,
Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM),
mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(),
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index 22ad956..561e6a5 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -57,10 +57,16 @@
new byte[]{HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON};
private static final int PORT_1 = 1;
- private static final HdmiDeviceInfo INFO_TV = new HdmiDeviceInfo(
- ADDR_TV, 0x0000, PORT_1, HdmiDeviceInfo.DEVICE_TV,
- 0x1234, "TV",
- HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+ private static final HdmiDeviceInfo INFO_TV = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(ADDR_TV)
+ .setPhysicalAddress(0x0000)
+ .setPortId(PORT_1)
+ .setDeviceType(HdmiDeviceInfo.DEVICE_TV)
+ .setVendorId(0x1234)
+ .setDisplayName("TV")
+ .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON)
+ .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B)
+ .build();
private Context mContextSpy;
private HdmiControlService mHdmiControlService;
@@ -113,7 +119,6 @@
this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(hdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mHdmiControlService.initService();
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
@@ -165,7 +170,7 @@
mNativeWrapper.clearResultMessages();
assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS);
HdmiCecMessage reportPowerStatusOn =
- new HdmiCecMessage(
+ HdmiCecMessage.build(
ADDR_TV,
playbackDevice.getDeviceInfo().getLogicalAddress(),
Constants.MESSAGE_REPORT_POWER_STATUS,
@@ -218,7 +223,7 @@
mNativeWrapper.clearResultMessages();
assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS);
HdmiCecMessage reportPowerStatusOn =
- new HdmiCecMessage(
+ HdmiCecMessage.build(
ADDR_TV,
playbackDevice.getDeviceInfo().getLogicalAddress(),
Constants.MESSAGE_REPORT_POWER_STATUS,
@@ -271,7 +276,7 @@
mNativeWrapper.clearResultMessages();
assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS);
HdmiCecMessage reportPowerStatusTransientToOn =
- new HdmiCecMessage(
+ HdmiCecMessage.build(
ADDR_TV,
playbackDevice.getDeviceInfo().getLogicalAddress(),
Constants.MESSAGE_REPORT_POWER_STATUS,
@@ -284,7 +289,7 @@
mNativeWrapper.clearResultMessages();
HdmiCecMessage reportPowerStatusOn =
- new HdmiCecMessage(
+ HdmiCecMessage.build(
ADDR_TV,
playbackDevice.getDeviceInfo().getLogicalAddress(),
Constants.MESSAGE_REPORT_POWER_STATUS,
@@ -428,7 +433,7 @@
mNativeWrapper.clearResultMessages();
assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS);
HdmiCecMessage reportPowerStatusOn =
- new HdmiCecMessage(
+ HdmiCecMessage.build(
ADDR_TV,
playbackDevice.getDeviceInfo().getLogicalAddress(),
Constants.MESSAGE_REPORT_POWER_STATUS,
@@ -482,7 +487,7 @@
mNativeWrapper.clearResultMessages();
assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS);
HdmiCecMessage reportPowerStatusOn =
- new HdmiCecMessage(
+ HdmiCecMessage.build(
ADDR_TV,
playbackDevice.getDeviceInfo().getLogicalAddress(),
Constants.MESSAGE_REPORT_POWER_STATUS,
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
index 2f22bce..c878f99 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -99,7 +99,6 @@
this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(hdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mTvDevice = new HdmiCecLocalDeviceTv(mHdmiControlService);
mTvDevice.init();
mLocalDevices.add(mTvDevice);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ReportFeaturesMessageTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ReportFeaturesMessageTest.java
new file mode 100644
index 0000000..22f1f43
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ReportFeaturesMessageTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.hdmi;
+
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
+import static com.android.server.hdmi.Constants.ADDR_TV;
+import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_DESTINATION;
+import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER_SHORT;
+import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_SOURCE;
+import static com.android.server.hdmi.HdmiCecMessageValidator.OK;
+import static com.android.server.hdmi.HdmiUtils.buildMessage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hardware.hdmi.DeviceFeatures;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class ReportFeaturesMessageTest {
+ @Test
+ public void build_invalidMessages() {
+ assertThat(HdmiUtils.buildMessage("FF:A6:05:80:00:00")
+ .getValidationResult()).isEqualTo(ERROR_SOURCE);
+ assertThat(HdmiUtils.buildMessage("04:A6:05:80:00:00")
+ .getValidationResult()).isEqualTo(ERROR_DESTINATION);
+ assertThat(HdmiUtils.buildMessage("0F:A6")
+ .getValidationResult()).isEqualTo(ERROR_PARAMETER_SHORT);
+ assertThat(HdmiUtils.buildMessage("4F:A6:06:00:80:80:00")
+ .getValidationResult()).isEqualTo(ERROR_PARAMETER_SHORT);
+ }
+
+ @Test
+ public void build_longMessage() {
+ HdmiCecMessage longMessage = HdmiUtils.buildMessage("4F:A6:05:00:80:80:00:81:80:00");
+ assertThat(longMessage).isInstanceOf(ReportFeaturesMessage.class);
+ ReportFeaturesMessage longReportFeaturesMessage = (ReportFeaturesMessage) longMessage;
+
+ HdmiCecMessage shortMessage = HdmiUtils.buildMessage("4F:A6:05:00:00:01");
+ assertThat(shortMessage).isInstanceOf(ReportFeaturesMessage.class);
+ ReportFeaturesMessage shortReportFeaturesMessage = (ReportFeaturesMessage) shortMessage;
+
+ assertThat(longReportFeaturesMessage.getDeviceFeatures()).isEqualTo(
+ shortReportFeaturesMessage.getDeviceFeatures());
+ assertThat(longReportFeaturesMessage.getCecVersion()).isEqualTo(
+ shortReportFeaturesMessage.getCecVersion());
+ }
+
+ @Test
+ public void build_basicTv_1_4() {
+ HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_TV,
+ HdmiControlManager.HDMI_CEC_VERSION_1_4_B,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
+ Lists.newArrayList(Constants.RC_PROFILE_TV_NONE),
+ DeviceFeatures.NO_FEATURES_SUPPORTED);
+
+ assertThat(message.getValidationResult()).isEqualTo(OK);
+ assertThat(message).isEqualTo(buildMessage("0F:A6:05:80:00:00"));
+ }
+
+ @Test
+ public void build_basicPlayback_1_4() {
+ HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_PLAYBACK_1,
+ HdmiControlManager.HDMI_CEC_VERSION_1_4_B,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_TV,
+ Lists.newArrayList(Constants.RC_PROFILE_TV_NONE),
+ DeviceFeatures.NO_FEATURES_SUPPORTED);
+
+ assertThat(message.getValidationResult()).isEqualTo(OK);
+ assertThat(message).isEqualTo(buildMessage("4F:A6:05:10:00:00"));
+ }
+
+ @Test
+ public void build_basicPlaybackAudioSystem_1_4() {
+ HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_PLAYBACK_1,
+ HdmiControlManager.HDMI_CEC_VERSION_1_4_B,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK,
+ HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM), Constants.RC_PROFILE_TV,
+ Lists.newArrayList(Constants.RC_PROFILE_TV_NONE),
+ DeviceFeatures.NO_FEATURES_SUPPORTED);
+
+ assertThat(message.getValidationResult()).isEqualTo(OK);
+ assertThat(message).isEqualTo(buildMessage("4F:A6:05:18:00:00"));
+ }
+
+ @Test
+ public void build_basicTv_2_0() {
+ HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_TV,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
+ Lists.newArrayList(Constants.RC_PROFILE_TV_NONE),
+ DeviceFeatures.NO_FEATURES_SUPPORTED);
+
+ assertThat(message.getValidationResult()).isEqualTo(OK);
+ assertThat(message).isEqualTo(buildMessage("0F:A6:06:80:00:00"));
+ }
+
+ @Test
+ public void build_remoteControlTv_2_0() {
+ HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_TV,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
+ Lists.newArrayList(Constants.RC_PROFILE_TV_ONE),
+ DeviceFeatures.NO_FEATURES_SUPPORTED);
+
+ assertThat(message.getValidationResult()).isEqualTo(OK);
+ assertThat(message).isEqualTo(buildMessage("0F:A6:06:80:02:00"));
+ }
+
+ @Test
+ public void build_remoteControlPlayback_2_0() {
+ HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_TV,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_SOURCE,
+ Lists.newArrayList(Constants.RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
+ Constants.RC_PROFILE_SOURCE_HANDLES_SETUP_MENU),
+ DeviceFeatures.NO_FEATURES_SUPPORTED);
+
+ assertThat(message.getValidationResult()).isEqualTo(OK);
+ assertThat(message).isEqualTo(buildMessage("0F:A6:06:10:4A:00"));
+ }
+
+ @Test
+ public void build_deviceFeaturesTv_2_0() {
+ HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_TV,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
+ Lists.newArrayList(Constants.RC_PROFILE_TV_NONE),
+ DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
+ .setRecordTvScreenSupport(DeviceFeatures.FEATURE_SUPPORTED)
+ .build());
+
+ assertThat(message.getValidationResult()).isEqualTo(OK);
+ assertThat(message).isEqualTo(buildMessage("0F:A6:06:80:00:40"));
+ }
+
+ @Test
+ public void build_deviceFeaturesPlayback_2_0() {
+ HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_TV,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_SOURCE,
+ Lists.newArrayList(Constants.RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
+ Constants.RC_PROFILE_SOURCE_HANDLES_SETUP_MENU),
+ DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
+ .setDeckControlSupport(DeviceFeatures.FEATURE_SUPPORTED)
+ .build());
+
+ assertThat(message.getValidationResult()).isEqualTo(OK);
+ assertThat(message).isEqualTo(buildMessage("0F:A6:06:10:4A:10"));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
index 2d81fc9..6184c21 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -123,7 +123,6 @@
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mLocalDevices.add(mHdmiCecLocalDeviceTv);
mHdmiControlService.initService();
mPowerManager = new FakePowerManagerWrapper(context);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
index 302c0ac..0587864 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
@@ -101,19 +101,29 @@
private static final byte[] PLAYER_PARAM =
new byte[]{(PHYSICAL_ADDRESS_PLAYER >> 8) & 0xFF, PHYSICAL_ADDRESS_PLAYER & 0xFF};
- private static final HdmiDeviceInfo DEVICE_INFO_AVR =
- new HdmiDeviceInfo(ADDR_AUDIO_SYSTEM, PHYSICAL_ADDRESS_AVR, PORT_1,
- HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM, VENDOR_ID_AVR, "Audio");
- private static final HdmiDeviceInfo DEVICE_INFO_PLAYER =
- new HdmiDeviceInfo(ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYER, PORT_1,
- HdmiDeviceInfo.DEVICE_PLAYBACK, VENDOR_ID_AVR, "Player");
- private static final HdmiCecMessage ROUTING_INFORMATION_TUNER = new HdmiCecMessage(
+ private static final HdmiDeviceInfo DEVICE_INFO_AVR = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(ADDR_AUDIO_SYSTEM)
+ .setPhysicalAddress(PHYSICAL_ADDRESS_AVR)
+ .setPortId(PORT_1)
+ .setDeviceType(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
+ .setVendorId(VENDOR_ID_AVR)
+ .setDisplayName("Audio")
+ .build();
+ private static final HdmiDeviceInfo DEVICE_INFO_PLAYER = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(ADDR_PLAYBACK_1)
+ .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYER)
+ .setPortId(PORT_1)
+ .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK)
+ .setVendorId(VENDOR_ID_AVR)
+ .setDisplayName("Player")
+ .build();
+ private static final HdmiCecMessage ROUTING_INFORMATION_TUNER = HdmiCecMessage.build(
ADDR_UNREGISTERED, ADDR_BROADCAST, MESSAGE_ROUTING_INFORMATION, TUNER_PARAM);
- private static final HdmiCecMessage ROUTING_INFORMATION_PLAYER = new HdmiCecMessage(
+ private static final HdmiCecMessage ROUTING_INFORMATION_PLAYER = HdmiCecMessage.build(
ADDR_UNREGISTERED, ADDR_BROADCAST, MESSAGE_ROUTING_INFORMATION, PLAYER_PARAM);
- private static final HdmiCecMessage ACTIVE_SOURCE_TUNER = new HdmiCecMessage(
+ private static final HdmiCecMessage ACTIVE_SOURCE_TUNER = HdmiCecMessage.build(
ADDR_TUNER_1, ADDR_BROADCAST, MESSAGE_ACTIVE_SOURCE, TUNER_PARAM);
- private static final HdmiCecMessage ACTIVE_SOURCE_PLAYER = new HdmiCecMessage(
+ private static final HdmiCecMessage ACTIVE_SOURCE_PLAYER = HdmiCecMessage.build(
ADDR_PLAYBACK_1, ADDR_BROADCAST, MESSAGE_ACTIVE_SOURCE, PLAYER_PARAM);
private HdmiControlService mHdmiControlService;
@@ -169,7 +179,6 @@
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mLocalDevices.add(mHdmiCecLocalDeviceTv);
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
hdmiPortInfos[0] =
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
new file mode 100644
index 0000000..a34b55c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
@@ -0,0 +1,218 @@
+/*
+ * 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.hdmi;
+
+import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED;
+import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED;
+import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORT_UNKNOWN;
+
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.hdmi.DeviceFeatures;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.SystemService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class SetAudioVolumeLevelDiscoveryActionTest {
+ private HdmiControlService mHdmiControlServiceSpy;
+ private HdmiCecController mHdmiCecController;
+ private HdmiCecLocalDevicePlayback mPlaybackDevice;
+ private FakeNativeWrapper mNativeWrapper;
+ private FakePowerManagerWrapper mPowerManager;
+ private Looper mLooper;
+ private Context mContextSpy;
+ private TestLooper mTestLooper = new TestLooper();
+ private int mPhysicalAddress = 0x1100;
+ private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+ private int mPlaybackLogicalAddress;
+
+ private TestCallback mTestCallback;
+ private SetAudioVolumeLevelDiscoveryAction mAction;
+
+ /**
+ * Setup: Local Playback device attempts to determine whether a connected TV supports
+ * <Set Audio Volume Level>.
+ */
+ @Before
+ public void setUp() throws RemoteException {
+ mContextSpy = spy(new ContextWrapper(
+ InstrumentationRegistry.getInstrumentation().getTargetContext()));
+
+ mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList()));
+ doNothing().when(mHdmiControlServiceSpy)
+ .writeStringSystemProperty(anyString(), anyString());
+
+ mLooper = mTestLooper.getLooper();
+ mHdmiControlServiceSpy.setIoLooper(mLooper);
+ mHdmiControlServiceSpy.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy));
+
+ mNativeWrapper = new FakeNativeWrapper();
+ mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
+
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+ mHdmiControlServiceSpy, mNativeWrapper, mHdmiControlServiceSpy.getAtomWriter());
+ mHdmiControlServiceSpy.setCecController(mHdmiCecController);
+ mHdmiControlServiceSpy.setHdmiMhlController(
+ HdmiMhlControllerStub.create(mHdmiControlServiceSpy));
+ mHdmiControlServiceSpy.initService();
+ mPowerManager = new FakePowerManagerWrapper(mContextSpy);
+ mHdmiControlServiceSpy.setPowerManager(mPowerManager);
+
+ mPlaybackDevice = new HdmiCecLocalDevicePlayback(mHdmiControlServiceSpy);
+ mPlaybackDevice.init();
+ mLocalDevices.add(mPlaybackDevice);
+
+ mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mHdmiControlServiceSpy.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ mTestLooper.dispatchAll();
+
+ synchronized (mPlaybackDevice.mLock) {
+ mPlaybackLogicalAddress = mPlaybackDevice.getDeviceInfo().getLogicalAddress();
+ }
+
+ // Setup specific to these tests
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ Constants.ADDR_TV, 0x0000, HdmiDeviceInfo.DEVICE_TV));
+ mTestLooper.dispatchAll();
+
+ mTestCallback = new TestCallback();
+ mAction = new SetAudioVolumeLevelDiscoveryAction(mPlaybackDevice,
+ Constants.ADDR_TV, mTestCallback);
+ }
+
+ @Test
+ public void sendsSetAudioVolumeLevel() {
+ mPlaybackDevice.addAndStartAction(mAction);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage setAudioVolumeLevel = SetAudioVolumeLevelMessage.build(
+ mPlaybackLogicalAddress, Constants.ADDR_TV,
+ Constants.AUDIO_VOLUME_STATUS_UNKNOWN);
+ assertThat(mNativeWrapper.getResultMessages()).contains(setAudioVolumeLevel);
+ }
+
+ @Test
+ public void noMatchingFeatureAbortReceived_actionSucceedsAndSetsFeatureSupported() {
+ mPlaybackDevice.addAndStartAction(mAction);
+ mTestLooper.dispatchAll();
+
+ // Wrong opcode
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ Constants.ADDR_TV,
+ mPlaybackLogicalAddress,
+ Constants.MESSAGE_GIVE_DECK_STATUS,
+ Constants.ABORT_UNRECOGNIZED_OPCODE));
+ // Wrong source
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ Constants.ADDR_AUDIO_SYSTEM,
+ mPlaybackLogicalAddress,
+ Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL,
+ Constants.ABORT_UNRECOGNIZED_OPCODE));
+ mTestLooper.dispatchAll();
+
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ @DeviceFeatures.FeatureSupportStatus int avcSupport =
+ mHdmiControlServiceSpy.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_TV)
+ .getDeviceFeatures().getSetAudioVolumeLevelSupport();
+
+ assertThat(avcSupport).isEqualTo(FEATURE_SUPPORTED);
+ assertThat(mTestCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+ }
+
+ @Test
+ public void matchingFeatureAbortReceived_actionSucceedsAndSetsFeatureNotSupported() {
+ mPlaybackDevice.addAndStartAction(mAction);
+ mTestLooper.dispatchAll();
+
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ Constants.ADDR_TV,
+ mPlaybackLogicalAddress,
+ Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL,
+ Constants.ABORT_UNRECOGNIZED_OPCODE));
+ mTestLooper.dispatchAll();
+
+ @DeviceFeatures.FeatureSupportStatus int avcSupport =
+ mHdmiControlServiceSpy.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_TV)
+ .getDeviceFeatures().getSetAudioVolumeLevelSupport();
+
+ assertThat(avcSupport).isEqualTo(FEATURE_NOT_SUPPORTED);
+ assertThat(mTestCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+ }
+
+ @Test
+ public void messageFailedToSend_actionFailsAndDoesNotUpdateFeatureSupport() {
+ mNativeWrapper.setMessageSendResult(Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL,
+ SendMessageResult.FAIL);
+ mTestLooper.dispatchAll();
+
+ mPlaybackDevice.addAndStartAction(mAction);
+ mTestLooper.dispatchAll();
+
+ @DeviceFeatures.FeatureSupportStatus int avcSupport =
+ mHdmiControlServiceSpy.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_TV)
+ .getDeviceFeatures().getSetAudioVolumeLevelSupport();
+
+ assertThat(avcSupport).isEqualTo(FEATURE_SUPPORT_UNKNOWN);
+ assertThat(mTestCallback.getResult()).isEqualTo(
+ HdmiControlManager.RESULT_COMMUNICATION_FAILED);
+ }
+
+ private static class TestCallback extends IHdmiControlCallback.Stub {
+ private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>();
+
+ @Override
+ public void onComplete(int result) {
+ mCallbackResult.add(result);
+ }
+
+ private int getResult() {
+ assertThat(mCallbackResult.size()).isEqualTo(1);
+ return mCallbackResult.get(0);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelMessageTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelMessageTest.java
new file mode 100644
index 0000000..0201c68
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelMessageTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.hdmi;
+
+import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_DESTINATION;
+import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER_SHORT;
+import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_SOURCE;
+import static com.android.server.hdmi.HdmiUtils.buildMessage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class SetAudioVolumeLevelMessageTest {
+ @Test
+ public void build_maxVolume() {
+ HdmiCecMessage message = SetAudioVolumeLevelMessage.build(
+ Constants.ADDR_TV, Constants.ADDR_PLAYBACK_1, 100);
+ assertThat(message.getValidationResult()).isEqualTo(HdmiCecMessageValidator.OK);
+ assertThat(message).isEqualTo(buildMessage("04:73:64"));
+ }
+
+ @Test
+ public void build_noVolumeChange() {
+ HdmiCecMessage message = SetAudioVolumeLevelMessage.build(
+ Constants.ADDR_TV, Constants.ADDR_AUDIO_SYSTEM, 0x7F);
+ assertThat(message.getValidationResult()).isEqualTo(HdmiCecMessageValidator.OK);
+ assertThat(message).isEqualTo(buildMessage("05:73:7F"));
+ }
+
+ @Test
+ public void build_invalid() {
+ assertThat(SetAudioVolumeLevelMessage
+ .build(Constants.ADDR_UNREGISTERED, Constants.ADDR_AUDIO_SYSTEM, 50)
+ .getValidationResult())
+ .isEqualTo(ERROR_SOURCE);
+ assertThat(SetAudioVolumeLevelMessage
+ .build(Constants.ADDR_TV, Constants.ADDR_BROADCAST, 50)
+ .getValidationResult())
+ .isEqualTo(ERROR_DESTINATION);
+ assertThat(HdmiUtils.buildMessage("04:73")
+ .getValidationResult())
+ .isEqualTo(ERROR_PARAMETER_SHORT);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
index b34b853..9d14341 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
@@ -100,7 +100,6 @@
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(hdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mLocalDevices.add(mHdmiCecLocalDeviceTv);
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2];
hdmiPortInfos[0] =
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
index b40650e..095c69c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
@@ -64,7 +64,7 @@
@Before
public void SetUp() {
- mDeviceInfoForTests = new HdmiDeviceInfo(1001, 1234);
+ mDeviceInfoForTests = HdmiDeviceInfo.hardwarePort(1001, 1234);
Context context = InstrumentationRegistry.getTargetContext();
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..9a6f61e 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -49,12 +49,8 @@
import static android.net.NetworkPolicyManager.uidPoliciesToString;
import static android.net.NetworkPolicyManager.uidRulesToString;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
-import static android.net.NetworkStats.IFACE_ALL;
import static android.net.NetworkStats.METERED_NO;
import static android.net.NetworkStats.METERED_YES;
-import static android.net.NetworkStats.SET_ALL;
-import static android.net.NetworkStats.TAG_ALL;
-import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkTemplate.buildTemplateCarrierMetered;
import static android.net.NetworkTemplate.buildTemplateWifi;
import static android.net.TrafficStats.MB_IN_BYTES;
@@ -75,6 +71,7 @@
import static com.android.server.net.NetworkPolicyManagerService.TYPE_RAPID;
import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING;
import static com.android.server.net.NetworkPolicyManagerService.UidBlockedState.getEffectiveBlockedReasons;
+import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -108,6 +105,8 @@
import android.app.IUidObserver;
import android.app.Notification;
import android.app.NotificationManager;
+import android.app.usage.NetworkStats;
+import android.app.usage.NetworkStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.Context;
import android.content.Intent;
@@ -125,8 +124,6 @@
import android.net.NetworkCapabilities;
import android.net.NetworkPolicy;
import android.net.NetworkStateSnapshot;
-import android.net.NetworkStats;
-import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.net.TelephonyNetworkSpecifier;
import android.net.wifi.WifiInfo;
@@ -138,7 +135,6 @@
import android.os.PowerSaveState;
import android.os.RemoteException;
import android.os.SimpleClock;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
@@ -263,12 +259,13 @@
private @Mock CarrierConfigManager mCarrierConfigManager;
private @Mock TelephonyManager mTelephonyManager;
private @Mock UserManager mUserManager;
+ private @Mock NetworkStatsManager mStatsManager;
+ private TestDependencies mDeps;
private ArgumentCaptor<ConnectivityManager.NetworkCallback> mNetworkCallbackCaptor =
ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
private ActivityManagerInternal mActivityManagerInternal;
- private NetworkStatsManagerInternal mStatsService;
private IUidObserver mUidObserver;
private INetworkManagementEventObserver mNetworkObserver;
@@ -335,8 +332,47 @@
.setBatterySaverEnabled(false).build();
final PowerManagerInternal pmInternal = addLocalServiceMock(PowerManagerInternal.class);
when(pmInternal.getLowPowerState(anyInt())).thenReturn(state);
+ }
- mStatsService = addLocalServiceMock(NetworkStatsManagerInternal.class);
+ private class TestDependencies extends NetworkPolicyManagerService.Dependencies {
+ private final SparseArray<NetworkStats.Bucket> mMockedStats = new SparseArray<>();
+
+ TestDependencies(Context context) {
+ super(context);
+ }
+
+ @Override
+ long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
+ int total = 0;
+ for (int i = 0; i < mMockedStats.size(); i++) {
+ NetworkStats.Bucket bucket = mMockedStats.valueAt(i);
+ total += bucket.getRxBytes() + bucket.getTxBytes();
+ }
+ return total;
+ }
+
+ @Override
+ List<NetworkStats.Bucket> getNetworkUidBytes(NetworkTemplate template, long start,
+ long end) {
+ final List<NetworkStats.Bucket> ret = new ArrayList<>();
+ for (int i = 0; i < mMockedStats.size(); i++) {
+ ret.add(mMockedStats.valueAt(i));
+ }
+ return ret;
+ }
+
+ private void setMockedTotalBytes(int uid, long rxBytes, long txBytes) {
+ final NetworkStats.Bucket bucket = mock(NetworkStats.Bucket.class);
+ when(bucket.getUid()).thenReturn(uid);
+ when(bucket.getRxBytes()).thenReturn(rxBytes);
+ when(bucket.getTxBytes()).thenReturn(txBytes);
+ mMockedStats.set(uid, bucket);
+ }
+
+ private void increaseMockedTotalBytes(int uid, long rxBytes, long txBytes) {
+ final NetworkStats.Bucket bucket = mMockedStats.get(uid);
+ setMockedTotalBytes(uid, bucket.getRxBytes() + rxBytes, bucket.getTxBytes() + txBytes);
+ }
}
@Before
@@ -376,6 +412,8 @@
return mConnManager;
case Context.USER_SERVICE:
return mUserManager;
+ case Context.NETWORK_STATS_SERVICE:
+ return mStatsManager;
default:
return super.getSystemService(name);
}
@@ -400,8 +438,9 @@
}).when(mActivityManager).registerUidObserver(any(), anyInt(), anyInt(), any(String.class));
mFutureIntent = newRestrictBackgroundChangedFuture();
+ mDeps = new TestDependencies(mServiceContext);
mService = new NetworkPolicyManagerService(mServiceContext, mActivityManager,
- mNetworkManager, mIpm, mClock, mPolicyDir, true);
+ mNetworkManager, mIpm, mClock, mPolicyDir, true, mDeps);
mService.bindConnectivityManager();
mPolicyListener = new NetworkPolicyListenerAnswer(mService);
@@ -456,6 +495,9 @@
verify(mNetworkManager).registerObserver(networkObserver.capture());
mNetworkObserver = networkObserver.getValue();
+ // Simulate NetworkStatsService broadcast stats updated to signal its readiness.
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_UPDATED));
+
NetworkPolicy defaultPolicy = mService.buildDefaultCarrierPolicy(0, "");
mDefaultWarningBytes = defaultPolicy.warningBytes;
mDefaultLimitBytes = defaultPolicy.limitBytes;
@@ -479,7 +521,6 @@
LocalServices.removeServiceForTest(DeviceIdleInternal.class);
LocalServices.removeServiceForTest(AppStandbyInternal.class);
LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
- LocalServices.removeServiceForTest(NetworkStatsManagerInternal.class);
}
@After
@@ -1108,10 +1149,7 @@
when(mConnManager.getAllNetworkStateSnapshots()).thenReturn(snapshots);
// pretend that 512 bytes total have happened
- stats = new NetworkStats(getElapsedRealtime(), 1)
- .insertEntry(TEST_IFACE, 256L, 2L, 256L, 2L);
- when(mStatsService.getNetworkTotalBytes(sTemplateWifi, CYCLE_START, CYCLE_END))
- .thenReturn(stats.getTotalBytes());
+ mDeps.setMockedTotalBytes(UID_A, 256L, 256L);
mPolicyListener.expect().onMeteredIfacesChanged(any());
setNetworkPolicies(new NetworkPolicy(
@@ -1124,26 +1162,6 @@
@Test
public void testNotificationWarningLimitSnooze() throws Exception {
- // Create a place to store fake usage
- final NetworkStatsHistory history = new NetworkStatsHistory(TimeUnit.HOURS.toMillis(1));
- final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
- when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
- .thenAnswer(new Answer<Long>() {
- @Override
- public Long answer(InvocationOnMock invocation) throws Throwable {
- final NetworkStatsHistory.Entry entry = history.getValues(
- invocation.getArgument(1), invocation.getArgument(2), null);
- return entry.rxBytes + entry.txBytes;
- }
- });
- when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
- .thenAnswer(new Answer<NetworkStats>() {
- @Override
- public NetworkStats answer(InvocationOnMock invocation) throws Throwable {
- return stats;
- }
- });
-
// Get active mobile network in place
expectMobileDefaults();
mService.updateNetworks();
@@ -1161,9 +1179,7 @@
// Normal usage means no notification
{
- history.clear();
- history.recordData(start, end,
- new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0));
+ mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(360), 0);
reset(mTelephonyManager, mNetworkManager, mNotifManager);
TelephonyManager tmSub = expectMobileDefaults();
@@ -1178,9 +1194,7 @@
// Push over warning
{
- history.clear();
- history.recordData(start, end,
- new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1799), 0L, 0L, 0L, 0));
+ mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(1799), 0);
reset(mTelephonyManager, mNetworkManager, mNotifManager);
TelephonyManager tmSub = expectMobileDefaults();
@@ -1196,9 +1210,7 @@
// Push over warning, but with a config that isn't from an identified carrier
{
- history.clear();
- history.recordData(start, end,
- new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1799), 0L, 0L, 0L, 0));
+ mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(1799), 0);
reset(mTelephonyManager, mNetworkManager, mNotifManager);
TelephonyManager tmSub = expectMobileDefaults();
@@ -1215,9 +1227,7 @@
// Push over limit
{
- history.clear();
- history.recordData(start, end,
- new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1810), 0L, 0L, 0L, 0));
+ mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(1810), 0);
reset(mTelephonyManager, mNetworkManager, mNotifManager);
TelephonyManager tmSub = expectMobileDefaults();
@@ -1248,26 +1258,6 @@
@Test
public void testNotificationRapid() throws Exception {
- // Create a place to store fake usage
- final NetworkStatsHistory history = new NetworkStatsHistory(TimeUnit.HOURS.toMillis(1));
- final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
- when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
- .thenAnswer(new Answer<Long>() {
- @Override
- public Long answer(InvocationOnMock invocation) throws Throwable {
- final NetworkStatsHistory.Entry entry = history.getValues(
- invocation.getArgument(1), invocation.getArgument(2), null);
- return entry.rxBytes + entry.txBytes;
- }
- });
- when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
- .thenAnswer(new Answer<NetworkStats>() {
- @Override
- public NetworkStats answer(InvocationOnMock invocation) throws Throwable {
- return stats;
- }
- });
-
// Get active mobile network in place
expectMobileDefaults();
mService.updateNetworks();
@@ -1285,9 +1275,7 @@
// Using 20% data in 20% time is normal
{
- history.clear();
- history.recordData(start, end,
- new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0));
+ mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(360), 0);
reset(mNotifManager);
mService.updateNetworks();
@@ -1297,16 +1285,9 @@
// Using 80% data in 20% time is alarming; but spread equally among
// three UIDs means we get generic alert
{
- history.clear();
- history.recordData(start, end,
- new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0));
- stats.clear();
- stats.insertEntry(IFACE_ALL, UID_A, SET_ALL, TAG_ALL,
- DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
- stats.insertEntry(IFACE_ALL, UID_B, SET_ALL, TAG_ALL,
- DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
- stats.insertEntry(IFACE_ALL, UID_C, SET_ALL, TAG_ALL,
- DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
+ mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(480), 0);
+ mDeps.setMockedTotalBytes(UID_B, DataUnit.MEGABYTES.toBytes(480), 0);
+ mDeps.setMockedTotalBytes(UID_C, DataUnit.MEGABYTES.toBytes(480), 0);
reset(mNotifManager);
mService.updateNetworks();
@@ -1325,14 +1306,9 @@
// Using 80% data in 20% time is alarming; but mostly done by one UID
// means we get specific alert
{
- history.clear();
- history.recordData(start, end,
- new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0));
- stats.clear();
- stats.insertEntry(IFACE_ALL, UID_A, SET_ALL, TAG_ALL,
- DataUnit.MEGABYTES.toBytes(960), 0, 0, 0, 0);
- stats.insertEntry(IFACE_ALL, UID_B, SET_ALL, TAG_ALL,
- DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
+ mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(960), 0);
+ mDeps.setMockedTotalBytes(UID_B, DataUnit.MEGABYTES.toBytes(480), 0);
+ mDeps.setMockedTotalBytes(UID_C, 0, 0);
reset(mNotifManager);
mService.updateNetworks();
@@ -1362,13 +1338,10 @@
// bring up wifi network with metered policy
snapshots = List.of(buildWifi());
- stats = new NetworkStats(getElapsedRealtime(), 1)
- .insertEntry(TEST_IFACE, 0L, 0L, 0L, 0L);
+ mDeps.setMockedTotalBytes(UID_A, 0L, 0L);
{
when(mConnManager.getAllNetworkStateSnapshots()).thenReturn(snapshots);
- when(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15,
- currentTimeMillis())).thenReturn(stats.getTotalBytes());
mPolicyListener.expect().onMeteredIfacesChanged(any());
setNetworkPolicies(new NetworkPolicy(
@@ -1647,18 +1620,6 @@
final NetworkPolicyManagerInternal internal = LocalServices
.getService(NetworkPolicyManagerInternal.class);
- // Create a place to store fake usage
- final NetworkStatsHistory history = new NetworkStatsHistory(TimeUnit.HOURS.toMillis(1));
- final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
- when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
- .thenAnswer(invocation -> {
- final NetworkStatsHistory.Entry entry = history.getValues(
- invocation.getArgument(1), invocation.getArgument(2), null);
- return entry.rxBytes + entry.txBytes;
- });
- when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
- .thenReturn(stats);
-
// Get active mobile network in place
expectMobileDefaults();
mService.updateNetworks();
@@ -1669,9 +1630,7 @@
setCurrentTimeMillis(end);
// Get some data usage in place
- history.clear();
- history.recordData(start, end,
- new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0));
+ mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(360), 0);
// No data plan
{
@@ -1786,22 +1745,11 @@
true);
}
- private void increaseMockedTotalBytes(NetworkStats stats, long rxBytes, long txBytes) {
- stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE,
- rxBytes, 1, txBytes, 1, 0);
- when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
- .thenReturn(stats.getTotalBytes());
- when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
- .thenReturn(stats);
- }
-
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();
+ verify(mStatsManager).forceUpdate();
// Wait for processing of MSG_*_INTERFACE_QUOTAS.
postMsgAndWaitForCompletion();
}
@@ -1814,13 +1762,12 @@
public void testStatsProviderWarningAndLimitReached() throws Exception {
final int CYCLE_DAY = 15;
- final NetworkStats stats = new NetworkStats(0L, 1);
- increaseMockedTotalBytes(stats, 2999, 2000);
+ mDeps.setMockedTotalBytes(UID_A, 2999, 2000);
// Get active mobile network in place
expectMobileDefaults();
mService.updateNetworks();
- verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE,
+ verify(mStatsManager).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE,
Long.MAX_VALUE);
// Set warning to 7KB and limit to 10KB.
@@ -1830,32 +1777,32 @@
postMsgAndWaitForCompletion();
// Verifies that remaining quotas are set to providers.
- verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2001L, 5001L);
- reset(mStatsService);
+ verify(mStatsManager).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2001L, 5001L);
+ reset(mStatsManager);
// Increase the usage and simulates that limit reached fires earlier by provider,
// but actually the quota is not yet reached. Verifies that the limit reached leads to
// a force update and new quotas should be set.
- increaseMockedTotalBytes(stats, 1000, 999);
+ mDeps.increaseMockedTotalBytes(UID_A, 1000, 999);
triggerOnStatsProviderWarningOrLimitReached();
- verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2L, 3002L);
- reset(mStatsService);
+ verify(mStatsManager).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2L, 3002L);
+ reset(mStatsManager);
// Increase the usage and simulate warning reached, the new warning should be unlimited
// since service will disable warning quota to stop lower layer from keep triggering
// warning reached event.
- increaseMockedTotalBytes(stats, 1000L, 1000);
+ mDeps.increaseMockedTotalBytes(UID_A, 1000L, 1000);
triggerOnStatsProviderWarningOrLimitReached();
- verify(mStatsService).setStatsProviderWarningAndLimitAsync(
+ verify(mStatsManager).setStatsProviderWarningAndLimitAsync(
TEST_IFACE, Long.MAX_VALUE, 1002L);
- reset(mStatsService);
+ reset(mStatsManager);
// Increase the usage that over the warning and limit, the new limit should set to 1 to
// block the network traffic.
- increaseMockedTotalBytes(stats, 1000L, 1000);
+ mDeps.increaseMockedTotalBytes(UID_A, 1000L, 1000);
triggerOnStatsProviderWarningOrLimitReached();
- verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, 1L);
- reset(mStatsService);
+ verify(mStatsManager).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, 1L);
+ reset(mStatsManager);
}
private void enableRestrictedMode(boolean enable) throws Exception {
@@ -2145,7 +2092,7 @@
}
private void verifyAdvisePersistThreshold() throws Exception {
- verify(mStatsService).advisePersistThreshold(anyLong());
+ verify(mStatsManager).advisePersistThreshold(anyLong());
}
private static class TestAbstractFuture<T> extends AbstractFuture<T> {
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 d49cf67..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,
@@ -3174,6 +3176,19 @@
}
@Test
+ public void testIsGroupBlocked_appCannotCreateAsBlocked() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+ group.setBlocked(true);
+ mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
+ assertFalse(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, group.getId()));
+
+ NotificationChannelGroup group3 = group.clone();
+ group3.setBlocked(false);
+ mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group3, true);
+ assertFalse(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, group.getId()));
+ }
+
+ @Test
public void testIsGroup_appCannotResetBlock() throws Exception {
NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
@@ -4706,7 +4721,7 @@
public void testGetConversations_noDisabledGroups() {
NotificationChannelGroup group = new NotificationChannelGroup("a", "a");
group.setBlocked(true);
- mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true);
+ mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, false);
NotificationChannel parent = new NotificationChannel("parent", "p", 1);
mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false);
@@ -4927,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/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/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index ea5bf52..d3282b9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -19,7 +19,6 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -82,19 +81,6 @@
}
@Test
- public void testControlsForDispatch_dockedTaskVisible() {
- addWindow(TYPE_STATUS_BAR, "statusBar");
- addWindow(TYPE_NAVIGATION_BAR, "navBar");
-
- final WindowState win = createWindow(null, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
- ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
- final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win);
-
- // The app must not control any system bars.
- assertNull(controls);
- }
-
- @Test
public void testControlsForDispatch_multiWindowTaskVisible() {
addWindow(TYPE_STATUS_BAR, "statusBar");
addWindow(TYPE_NAVIGATION_BAR, "navBar");
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index b4c449a..632a59d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -117,8 +117,7 @@
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_RECENTS,
mFinishedCallback);
- // Remove the app window so that the animation target can not be created
- activity.removeImmediately();
+ // The activity doesn't contain window so the animation target cannot be created.
mController.startAnimation();
// Verify that the finish callback to reparent the leash is called
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/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index bec53d7..8b14e98 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -22,6 +22,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
@@ -77,6 +78,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import java.util.ArrayList;
@@ -271,6 +273,22 @@
}
@Test
+ public void testRemoveImmediatelyClearsLeash() {
+ final AnimationAdapter animAdapter = mock(AnimationAdapter.class);
+ final WindowToken token = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDisplayContent);
+ final SurfaceControl.Transaction t = token.getPendingTransaction();
+ token.startAnimation(t, animAdapter, false /* hidden */,
+ SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION);
+ final ArgumentCaptor<SurfaceControl> leashCaptor =
+ ArgumentCaptor.forClass(SurfaceControl.class);
+ verify(animAdapter).startAnimation(leashCaptor.capture(), eq(t), anyInt(), any());
+ assertTrue(token.mSurfaceAnimator.hasLeash());
+ token.removeImmediately();
+ assertFalse(token.mSurfaceAnimator.hasLeash());
+ verify(t).remove(eq(leashCaptor.getValue()));
+ }
+
+ @Test
public void testAddChildByIndex() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).build();
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/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 5ad95b8..0c56de8 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -12615,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/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 0879b98..3f0de7f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -25,13 +25,11 @@
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
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.Rule
import org.junit.Test
@@ -167,11 +165,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()
/** {@inheritDoc} */
@FlakyTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index e44bee6..3ae484b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -26,10 +26,8 @@
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.testapp.ActivityOptions
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
@@ -102,8 +100,6 @@
@Presubmit
@Test
fun appWindowFullScreen() {
- // This test doesn't work in shell transitions because of b/206101151
- assumeFalse(isShellTransitionsEnabled)
testSpec.assertWm {
this.invoke("isFullScreen") {
val appWindow = it.windowState(testApp.`package`)
@@ -139,8 +135,6 @@
@Presubmit
@Test
fun appLayerAlwaysVisible() {
- // This test doesn't work in shell transitions because of b/206101151
- assumeFalse(isShellTransitionsEnabled)
testSpec.assertLayers {
isVisible(testApp.component)
}
@@ -152,8 +146,6 @@
@Presubmit
@Test
fun appLayerRotates() {
- // This test doesn't work in shell transitions because of b/206101151
- assumeFalse(isShellTransitionsEnabled)
testSpec.assertLayers {
this.invoke("entireScreenCovered") { entry ->
entry.entry.displays.map { display ->
@@ -193,8 +185,6 @@
@Presubmit
@Test
fun focusDoesNotChange() {
- // This test doesn't work in shell transitions because of b/206101151
- assumeFalse(isShellTransitionsEnabled)
testSpec.assertEventLog {
this.focusDoesNotChange()
}
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/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/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