Merge "Rename Do Not Disturb -> Priority Mode"
diff --git a/Android.bp b/Android.bp
index cc754f2..8d82b38 100644
--- a/Android.bp
+++ b/Android.bp
@@ -136,6 +136,7 @@
":libcamera_client_aidl",
":libcamera_client_framework_aidl",
":libupdate_engine_aidl",
+ ":logd_aidl",
":resourcemanager_aidl",
":storaged_aidl",
":vold_aidl",
@@ -538,7 +539,9 @@
visibility: ["//visibility:private"],
}
-// These defaults are used for both the jar stubs and the doc stubs.
+// Defaults for all stubs that include the non-updatable framework. These defaults do not include
+// module symbols, so will not compile correctly on their own. Users must add module APIs to the
+// classpath (or sources) somehow.
stubs_defaults {
name: "android-non-updatable-stubs-defaults",
srcs: [":android-non-updatable-stub-sources"],
@@ -546,17 +549,14 @@
system_modules: "none",
java_version: "1.8",
arg_files: ["core/res/AndroidManifest.xml"],
- // TODO(b/147699819): remove below aidl includes.
aidl: {
local_include_dirs: [
- "apex/media/aidl/stable",
"media/aidl",
"telephony/java",
],
include_dirs: [
"frameworks/av/aidl",
"frameworks/native/libs/permission/aidl",
- "packages/modules/Connectivity/framework/aidl-export",
],
},
// These are libs from framework-internal-utils that are required (i.e. being referenced)
@@ -576,6 +576,30 @@
"android.hardware.usb.gadget-V1.0-java",
"android.hardware.vibrator-V1.3-java",
"framework-protos",
+ ],
+ filter_packages: packages_to_document,
+ high_mem: true, // Lots of sources => high memory use, see b/170701554
+ installable: false,
+ annotations_enabled: true,
+ previous_api: ":android.api.public.latest",
+ merge_annotations_dirs: ["metalava-manual"],
+ defaults_visibility: ["//visibility:private"],
+ visibility: ["//frameworks/base/api"],
+}
+
+// Defaults with module APIs in the classpath (mostly from prebuilts).
+// Suitable for compiling android-non-updatable.
+stubs_defaults {
+ name: "module-classpath-stubs-defaults",
+ aidl: {
+ local_include_dirs: [
+ "apex/media/aidl/stable",
+ ],
+ include_dirs: [
+ "packages/modules/Connectivity/framework/aidl-export",
+ ],
+ },
+ libs: [
"art.module.public.api",
"sdk_module-lib_current_framework-tethering",
// There are a few classes from modules used by the core that
@@ -586,14 +610,7 @@
// NOTE: The below can be removed once the prebuilt stub contains IKE.
"sdk_system_current_android.net.ipsec.ike",
],
- filter_packages: packages_to_document,
- high_mem: true, // Lots of sources => high memory use, see b/170701554
- installable: false,
- annotations_enabled: true,
- previous_api: ":android.api.public.latest",
- merge_annotations_dirs: ["metalava-manual"],
defaults_visibility: ["//visibility:private"],
- visibility: ["//frameworks/base/api"],
}
build = [
diff --git a/ApiDocs.bp b/ApiDocs.bp
index 4aecc8f..3d6b52f 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -57,7 +57,10 @@
stubs_defaults {
name: "android-non-updatable-doc-stubs-defaults",
- defaults: ["android-non-updatable-stubs-defaults"],
+ defaults: [
+ "android-non-updatable-stubs-defaults",
+ "module-classpath-stubs-defaults",
+ ],
srcs: [
// No longer part of the stubs, but are included in the docs.
":android-test-base-sources",
@@ -71,39 +74,21 @@
stubs_defaults {
name: "framework-doc-stubs-default",
+ defaults: ["android-non-updatable-stubs-defaults"],
srcs: [
- ":android-non-updatable-stub-sources",
-
// No longer part of the stubs, but are included in the docs.
":android-test-base-sources",
":android-test-mock-sources",
":android-test-runner-sources",
],
- arg_files: [
- "core/res/AndroidManifest.xml",
- ],
libs: framework_docs_only_libs,
create_doc_stubs: true,
- annotations_enabled: true,
- filter_packages: packages_to_document,
api_levels_annotations_enabled: true,
api_levels_annotations_dirs: [
"sdk-dir",
"api-versions-jars-dir",
],
- previous_api: ":android.api.public.latest",
- merge_annotations_dirs: [
- "metalava-manual",
- ],
write_sdk_values: true,
- // TODO(b/169090544): remove below aidl includes.
- aidl: {
- local_include_dirs: ["media/aidl"],
- include_dirs: [
- "frameworks/av/aidl",
- "frameworks/native/libs/permission/aidl",
- ],
- },
}
// Defaults module for doc-stubs targets that use module source code as input.
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 131d5e2..8a15758 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -37,7 +37,10 @@
droidstubs {
name: "api-stubs-docs-non-updatable",
- defaults: ["android-non-updatable-stubs-defaults"],
+ defaults: [
+ "android-non-updatable-stubs-defaults",
+ "module-classpath-stubs-defaults",
+ ],
args: metalava_framework_docs_args,
check_api: {
current: {
@@ -86,7 +89,10 @@
droidstubs {
name: "system-api-stubs-docs-non-updatable",
- defaults: ["android-non-updatable-stubs-defaults"],
+ defaults: [
+ "android-non-updatable-stubs-defaults",
+ "module-classpath-stubs-defaults",
+ ],
args: metalava_framework_docs_args + priv_apps,
check_api: {
current: {
@@ -122,7 +128,10 @@
droidstubs {
name: "test-api-stubs-docs-non-updatable",
- defaults: ["android-non-updatable-stubs-defaults"],
+ defaults: [
+ "android-non-updatable-stubs-defaults",
+ "module-classpath-stubs-defaults",
+ ],
args: metalava_framework_docs_args + test + priv_apps_in_stubs,
check_api: {
current: {
@@ -164,7 +173,10 @@
droidstubs {
name: "module-lib-api-stubs-docs-non-updatable",
- defaults: ["android-non-updatable-stubs-defaults"],
+ defaults: [
+ "android-non-updatable-stubs-defaults",
+ "module-classpath-stubs-defaults",
+ ],
args: metalava_framework_docs_args + priv_apps_in_stubs + module_libs,
check_api: {
current: {
diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
index e873514..67a3380 100644
--- a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
+++ b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
@@ -218,7 +218,7 @@
})
override fun parseImpl(file: File) =
- parser.parsePackage(input.get()!!.reset(), file, 0).result
+ parser.parsePackage(input.get()!!.reset(), file, 0, null).result
as ParsingPackageImpl
}
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
index 1be68f5..4ad015d 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
@@ -20,7 +20,6 @@
import android.app.Activity;
import android.content.Context;
-import android.graphics.Point;
import android.os.RemoteException;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
@@ -133,12 +132,10 @@
final WindowManager.LayoutParams mParams;
final int mWidth;
final int mHeight;
- final Point mOutSurfaceSize = new Point();
final SurfaceControl mOutSurfaceControl;
final IntSupplier mViewVisibility;
- int mFrameNumber;
int mFlags;
RelayoutRunner(Activity activity, IWindow window, IntSupplier visibilitySupplier) {
@@ -155,9 +152,8 @@
final IWindowSession session = WindowManagerGlobal.getWindowSession();
while (state.keepRunning()) {
session.relayout(mWindow, mParams, mWidth, mHeight,
- mViewVisibility.getAsInt(), mFlags, mFrameNumber, mOutFrames,
- mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState, mOutControls,
- mOutSurfaceSize);
+ mViewVisibility.getAsInt(), mFlags, mOutFrames,
+ mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState, mOutControls);
}
}
}
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 12a8654..d93ad3c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1633,12 +1633,9 @@
for (int i=0; i<mActiveServices.size(); i++) {
final JobServiceContext jsc = mActiveServices.get(i);
final JobStatus job = jsc.getRunningJobLocked();
- if (job != null
- && !job.canRunInDoze()
- && !job.dozeWhitelisted
- && !job.uidActive) {
- // We will report active if we have a job running and it is not an exception
- // due to being in the foreground or whitelisted.
+ if (job != null && !job.canRunInDoze()) {
+ // We will report active if we have a job running and it does not have an
+ // exception that allows it to run in Doze.
active = true;
break;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index 090260c..f6de109 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -246,7 +246,7 @@
pw.print((jobStatus.satisfiedConstraints
& JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0
? " RUNNABLE" : " WAITING");
- if (jobStatus.dozeWhitelisted) {
+ if (jobStatus.appHasDozeExemption) {
pw.print(" WHITELISTED");
}
if (mAllowInIdleJobs.contains(jobStatus)) {
@@ -273,7 +273,7 @@
proto.write(TrackedJob.SOURCE_PACKAGE_NAME, jobStatus.getSourcePackageName());
proto.write(TrackedJob.ARE_CONSTRAINTS_SATISFIED,
(jobStatus.satisfiedConstraints & JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0);
- proto.write(TrackedJob.IS_DOZE_WHITELISTED, jobStatus.dozeWhitelisted);
+ proto.write(TrackedJob.IS_DOZE_WHITELISTED, jobStatus.appHasDozeExemption);
proto.write(TrackedJob.IS_ALLOWED_IN_DOZE, mAllowInIdleJobs.contains(jobStatus));
proto.end(jsToken);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index f74a4fa..0eea701 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -267,7 +267,7 @@
private final boolean mHasMediaBackupExemption;
// Set to true if doze constraint was satisfied due to app being whitelisted.
- public boolean dozeWhitelisted;
+ boolean appHasDozeExemption;
// Set to true when the app is "active" per AppStateTracker
public boolean uidActive;
@@ -1179,7 +1179,8 @@
* in Doze.
*/
public boolean canRunInDoze() {
- return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0
+ return appHasDozeExemption
+ || (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0
|| ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob)
&& (mDynamicConstraints & CONSTRAINT_DEVICE_NOT_DOZING) == 0);
}
@@ -1243,7 +1244,7 @@
/** @return true if the constraint was changed, false otherwise. */
boolean setDeviceNotDozingConstraintSatisfied(final long nowElapsed,
boolean state, boolean whitelisted) {
- dozeWhitelisted = whitelisted;
+ appHasDozeExemption = whitelisted;
if (setConstraintSatisfied(CONSTRAINT_DEVICE_NOT_DOZING, nowElapsed, state)) {
// The constraint was changed. Update the ready flag.
mReadyNotDozing = state || canRunInDoze();
@@ -2110,7 +2111,7 @@
}
pw.decreaseIndent();
- if (dozeWhitelisted) {
+ if (appHasDozeExemption) {
pw.println("Doze whitelisted: true");
}
if (uidActive) {
@@ -2323,7 +2324,7 @@
dumpConstraints(proto, JobStatusDumpProto.SATISFIED_CONSTRAINTS, satisfiedConstraints);
dumpConstraints(proto, JobStatusDumpProto.UNSATISFIED_CONSTRAINTS,
((requiredConstraints | CONSTRAINT_WITHIN_QUOTA) & ~satisfiedConstraints));
- proto.write(JobStatusDumpProto.IS_DOZE_WHITELISTED, dozeWhitelisted);
+ proto.write(JobStatusDumpProto.IS_DOZE_WHITELISTED, appHasDozeExemption);
proto.write(JobStatusDumpProto.IS_UID_ACTIVE, uidActive);
proto.write(JobStatusDumpProto.IS_EXEMPTED_FROM_APP_STANDBY,
job.isExemptedFromAppStandby());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 65e1d49..dd5246a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -36,6 +36,7 @@
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.IUidObserver;
+import android.app.job.JobInfo;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
@@ -161,6 +162,28 @@
public long inQuotaTimeElapsed;
/**
+ * The time after which the app will be under the bucket quota and can start running
+ * low priority jobs again. This is only valid if
+ * {@link #executionTimeInWindowMs} >=
+ * {@link #mAllowedTimePerPeriodMs} * (1 - {@link #mAllowedTimeSurplusPriorityLow}),
+ * {@link #executionTimeInMaxPeriodMs} >= {@link #mMaxExecutionTimeMs},
+ * {@link #bgJobCountInWindow} >= {@link #jobCountLimit}, or
+ * {@link #sessionCountInWindow} >= {@link #sessionCountLimit}.
+ */
+ public long inQuotaTimeLowElapsed;
+
+ /**
+ * The time after which the app will be under the bucket quota and can start running
+ * min priority jobs again. This is only valid if
+ * {@link #executionTimeInWindowMs} >=
+ * {@link #mAllowedTimePerPeriodMs} * (1 - {@link #mAllowedTimeSurplusPriorityMin}),
+ * {@link #executionTimeInMaxPeriodMs} >= {@link #mMaxExecutionTimeMs},
+ * {@link #bgJobCountInWindow} >= {@link #jobCountLimit}, or
+ * {@link #sessionCountInWindow} >= {@link #sessionCountLimit}.
+ */
+ public long inQuotaTimeMinElapsed;
+
+ /**
* The time after which {@link #jobCountInRateLimitingWindow} should be considered invalid,
* in the elapsed realtime timebase.
*/
@@ -199,6 +222,8 @@
+ "bgJobCountInMaxPeriod=" + bgJobCountInMaxPeriod + ", "
+ "sessionCountInWindow=" + sessionCountInWindow + ", "
+ "inQuotaTime=" + inQuotaTimeElapsed + ", "
+ + "inQuotaTimeLow=" + inQuotaTimeLowElapsed + ", "
+ + "inQuotaTimeMin=" + inQuotaTimeMinElapsed + ", "
+ "rateLimitJobCountExpirationTime=" + jobRateLimitExpirationTimeElapsed + ", "
+ "rateLimitJobCountWindow=" + jobCountInRateLimitingWindow + ", "
+ "rateLimitSessionCountExpirationTime="
@@ -351,6 +376,24 @@
*/
private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
+ /**
+ * The percentage of {@link #mAllowedTimePerPeriodMs} that should not be used by
+ * {@link JobInfo#PRIORITY_LOW low priority} jobs. In other words, there must be a minimum
+ * surplus of this amount of remaining allowed time before we start running low priority
+ * jobs.
+ */
+ private float mAllowedTimeSurplusPriorityLow =
+ QcConstants.DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW;
+
+ /**
+ * The percentage of {@link #mAllowedTimePerPeriodMs} that should not be used by
+ * {@link JobInfo#PRIORITY_MIN min priority} jobs. In other words, there must be a minimum
+ * surplus of this amount of remaining allowed time before we start running low priority
+ * jobs.
+ */
+ private float mAllowedTimeSurplusPriorityMin =
+ QcConstants.DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_MIN;
+
/** The period of time used to rate limit recently run jobs. */
private long mRateLimitingWindowMs = QcConstants.DEFAULT_RATE_LIMITING_WINDOW_MS;
@@ -653,10 +696,11 @@
boolean forUpdate) {
if (jobStatus.clearTrackingController(JobStatus.TRACKING_QUOTA)) {
unprepareFromExecutionLocked(jobStatus);
- ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUserId(),
- jobStatus.getSourcePackageName());
- if (jobs != null) {
- jobs.remove(jobStatus);
+ final int userId = jobStatus.getSourceUserId();
+ final String pkgName = jobStatus.getSourcePackageName();
+ ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
+ if (jobs != null && jobs.remove(jobStatus) && jobs.size() == 0) {
+ mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
}
}
}
@@ -771,7 +815,8 @@
return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
}
return getTimeUntilQuotaConsumedLocked(
- jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
+ jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
+ jobStatus.getEffectivePriority());
}
// Expedited job.
@@ -856,7 +901,8 @@
return isTopStartedJobLocked(jobStatus)
|| isUidInForeground(jobStatus.getSourceUid())
|| isWithinQuotaLocked(
- jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
+ jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket,
+ jobStatus.getEffectivePriority());
}
@GuardedBy("mLock")
@@ -873,7 +919,7 @@
@VisibleForTesting
@GuardedBy("mLock")
boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
- final int standbyBucket) {
+ final int standbyBucket, final int priority) {
if (!mIsEnabled) {
return true;
}
@@ -881,9 +927,16 @@
if (isQuotaFreeLocked(standbyBucket)) return true;
+ final long minSurplus;
+ if (priority <= JobInfo.PRIORITY_MIN) {
+ minSurplus = (long) (mAllowedTimePerPeriodMs * mAllowedTimeSurplusPriorityMin);
+ } else if (priority <= JobInfo.PRIORITY_LOW) {
+ minSurplus = (long) (mAllowedTimePerPeriodMs * mAllowedTimeSurplusPriorityLow);
+ } else {
+ minSurplus = 0;
+ }
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
- // TODO: use a higher minimum remaining time for jobs with MINIMUM priority
- return getRemainingExecutionTimeLocked(stats) > 0
+ return getRemainingExecutionTimeLocked(stats) > minSurplus
&& isUnderJobCountQuotaLocked(stats, standbyBucket)
&& isUnderSessionCountQuotaLocked(stats, standbyBucket);
}
@@ -1001,7 +1054,8 @@
* job is running.
*/
@VisibleForTesting
- long getTimeUntilQuotaConsumedLocked(final int userId, @NonNull final String packageName) {
+ long getTimeUntilQuotaConsumedLocked(final int userId, @NonNull final String packageName,
+ @JobInfo.Priority int jobPriority) {
final long nowElapsed = sElapsedRealtimeClock.millis();
final int standbyBucket = JobSchedulerService.standbyBucketForPackage(
packageName, userId, nowElapsed);
@@ -1022,10 +1076,15 @@
final long startWindowElapsed = nowElapsed - stats.windowSizeMs;
final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS;
- final long allowedTimeRemainingMs = mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs;
+ final long allowedTimePerPeriodMs = getAllowedTimePerPeriodMs(jobPriority);
+ final long allowedTimeRemainingMs = allowedTimePerPeriodMs - stats.executionTimeInWindowMs;
final long maxExecutionTimeRemainingMs =
mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs;
+ if (allowedTimeRemainingMs <= 0 || maxExecutionTimeRemainingMs <= 0) {
+ return 0;
+ }
+
// Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can
// essentially run until they reach the maximum limit.
if (stats.windowSizeMs == mAllowedTimePerPeriodMs) {
@@ -1044,6 +1103,16 @@
sessions, startWindowElapsed, allowedTimeRemainingMs));
}
+ private long getAllowedTimePerPeriodMs(@JobInfo.Priority int jobPriority) {
+ if (jobPriority <= JobInfo.PRIORITY_MIN) {
+ return (long) (mAllowedTimePerPeriodMs * (1 - mAllowedTimeSurplusPriorityMin));
+ }
+ if (jobPriority <= JobInfo.PRIORITY_LOW) {
+ return (long) (mAllowedTimePerPeriodMs * (1 - mAllowedTimeSurplusPriorityLow));
+ }
+ return mAllowedTimePerPeriodMs;
+ }
+
/**
* Calculates how much time it will take, in milliseconds, until the quota is fully consumed.
*
@@ -1198,10 +1267,13 @@
stats.sessionCountInWindow = 0;
if (stats.jobCountLimit == 0 || stats.sessionCountLimit == 0) {
// App won't be in quota until configuration changes.
- stats.inQuotaTimeElapsed = Long.MAX_VALUE;
+ stats.inQuotaTimeElapsed = stats.inQuotaTimeLowElapsed = stats.inQuotaTimeMinElapsed =
+ Long.MAX_VALUE;
} else {
stats.inQuotaTimeElapsed = 0;
}
+ final long allowedTimeMinMs = getAllowedTimePerPeriodMs(JobInfo.PRIORITY_MIN);
+ final long allowedTimeLowMs = getAllowedTimePerPeriodMs(JobInfo.PRIORITY_LOW);
Timer timer = mPkgTimers.get(userId, packageName);
final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -1219,13 +1291,25 @@
stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
nowElapsed - mAllowedTimeIntoQuotaMs + stats.windowSizeMs);
}
+ if (stats.executionTimeInWindowMs >= allowedTimeLowMs) {
+ stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed,
+ nowElapsed - allowedTimeLowMs + stats.windowSizeMs);
+ }
+ if (stats.executionTimeInWindowMs >= allowedTimeMinMs) {
+ stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed,
+ nowElapsed - allowedTimeMinMs + stats.windowSizeMs);
+ }
if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
- stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
- nowElapsed - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
+ final long inQuotaTime = nowElapsed - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS;
+ stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime);
+ stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed, inQuotaTime);
+ stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed, inQuotaTime);
}
if (stats.bgJobCountInWindow >= stats.jobCountLimit) {
- stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
- nowElapsed + stats.windowSizeMs);
+ final long inQuotaTime = nowElapsed + stats.windowSizeMs;
+ stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime);
+ stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed, inQuotaTime);
+ stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed, inQuotaTime);
}
}
@@ -1267,9 +1351,23 @@
start + stats.executionTimeInWindowMs - mAllowedTimeIntoQuotaMs
+ stats.windowSizeMs);
}
+ if (stats.executionTimeInWindowMs >= allowedTimeLowMs) {
+ stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed,
+ start + stats.executionTimeInWindowMs - allowedTimeLowMs
+ + stats.windowSizeMs);
+ }
+ if (stats.executionTimeInWindowMs >= allowedTimeMinMs) {
+ stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed,
+ start + stats.executionTimeInWindowMs - allowedTimeMinMs
+ + stats.windowSizeMs);
+ }
if (stats.bgJobCountInWindow >= stats.jobCountLimit) {
- stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
- session.endTimeElapsed + stats.windowSizeMs);
+ final long inQuotaTime = session.endTimeElapsed + stats.windowSizeMs;
+ stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime);
+ stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed,
+ inQuotaTime);
+ stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed,
+ inQuotaTime);
}
if (i == loopStart
|| (sessions.get(i + 1).startTimeElapsed - session.endTimeElapsed)
@@ -1278,8 +1376,12 @@
sessionCountInWindow++;
if (sessionCountInWindow >= stats.sessionCountLimit) {
- stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
- session.endTimeElapsed + stats.windowSizeMs);
+ final long inQuotaTime = session.endTimeElapsed + stats.windowSizeMs;
+ stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime);
+ stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed,
+ inQuotaTime);
+ stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed,
+ inQuotaTime);
}
}
}
@@ -1425,10 +1527,9 @@
synchronized (mLock) {
final long nowElapsed = sElapsedRealtimeClock.millis();
final ShrinkableDebits quota = getEJDebitsLocked(userId, packageName);
- if (transactQuotaLocked(userId, packageName, nowElapsed, quota, credit)
- && maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName)) {
- mStateChangedListener
- .onControllerStateChanged(mTrackedJobs.get(userId, packageName));
+ if (transactQuotaLocked(userId, packageName, nowElapsed, quota, credit)) {
+ mStateChangedListener.onControllerStateChanged(
+ maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName));
}
}
}
@@ -1558,9 +1659,8 @@
final int userId = mTrackedJobs.keyAt(u);
for (int p = 0; p < mTrackedJobs.numElementsForKey(userId); ++p) {
final String packageName = mTrackedJobs.keyAt(u, p);
- if (maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName)) {
- changedJobs.addAll(mTrackedJobs.valueAt(u, p));
- }
+ changedJobs.addAll(
+ maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName));
}
}
if (changedJobs.size() > 0) {
@@ -1573,18 +1673,20 @@
*
* @return true if at least one job had its bit changed
*/
- private boolean maybeUpdateConstraintForPkgLocked(final long nowElapsed, final int userId,
- @NonNull final String packageName) {
+ @NonNull
+ private ArraySet<JobStatus> maybeUpdateConstraintForPkgLocked(final long nowElapsed,
+ final int userId, @NonNull final String packageName) {
ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName);
+ final ArraySet<JobStatus> changedJobs = new ArraySet<>();
if (jobs == null || jobs.size() == 0) {
- return false;
+ return changedJobs;
}
// Quota is the same for all jobs within a package.
final int realStandbyBucket = jobs.valueAt(0).getStandbyBucket();
- final boolean realInQuota = isWithinQuotaLocked(userId, packageName, realStandbyBucket);
+ final boolean realInQuota = isWithinQuotaLocked(
+ userId, packageName, realStandbyBucket, JobInfo.PRIORITY_DEFAULT);
boolean outOfEJQuota = false;
- boolean changed = false;
for (int i = jobs.size() - 1; i >= 0; --i) {
final JobStatus js = jobs.valueAt(i);
final boolean isWithinEJQuota =
@@ -1592,21 +1694,30 @@
if (isTopStartedJobLocked(js)) {
// Job was started while the app was in the TOP state so we should allow it to
// finish.
- changed |= js.setQuotaConstraintSatisfied(nowElapsed, true);
+ if (js.setQuotaConstraintSatisfied(nowElapsed, true)) {
+ changedJobs.add(js);
+ }
} else if (realStandbyBucket != ACTIVE_INDEX
- && realStandbyBucket == js.getEffectiveStandbyBucket()) {
+ && realStandbyBucket == js.getEffectiveStandbyBucket()
+ && js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT) {
// An app in the ACTIVE bucket may be out of quota while the job could be in quota
// for some reason. Therefore, avoid setting the real value here and check each job
// individually.
- changed |= setConstraintSatisfied(js, nowElapsed, isWithinEJQuota || realInQuota);
+ if (setConstraintSatisfied(js, nowElapsed, isWithinEJQuota || realInQuota)) {
+ changedJobs.add(js);
+ }
} else {
// This job is somehow exempted. Need to determine its own quota status.
- changed |= setConstraintSatisfied(js, nowElapsed,
- isWithinEJQuota || isWithinQuotaLocked(js));
+ if (setConstraintSatisfied(js, nowElapsed,
+ isWithinEJQuota || isWithinQuotaLocked(js))) {
+ changedJobs.add(js);
+ }
}
if (js.isRequestedExpeditedJob()) {
- changed |= setExpeditedQuotaApproved(js, nowElapsed, isWithinEJQuota);
+ if (setExpeditedQuotaApproved(js, nowElapsed, isWithinEJQuota)) {
+ changedJobs.add(js);
+ }
outOfEJQuota |= !isWithinEJQuota;
}
}
@@ -1618,7 +1729,7 @@
} else {
mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
}
- return changed;
+ return changedJobs;
}
private class UidConstraintUpdater implements Consumer<JobStatus> {
@@ -1651,9 +1762,9 @@
final int userId = jobStatus.getSourceUserId();
final String packageName = jobStatus.getSourcePackageName();
final int realStandbyBucket = jobStatus.getStandbyBucket();
- if (isWithinQuotaLocked(userId, packageName, realStandbyBucket) && isWithinEJQuota) {
- // TODO(141645789): we probably shouldn't cancel the alarm until we've verified
- // that all jobs for the userId-package are within quota.
+ if (isWithinEJQuota
+ && isWithinQuotaLocked(userId, packageName, realStandbyBucket,
+ JobInfo.PRIORITY_MIN)) {
mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
} else {
mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket);
@@ -1700,16 +1811,41 @@
return;
}
+ ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName);
+ if (jobs == null || jobs.size() == 0) {
+ Slog.e(TAG, "maybeScheduleStartAlarmLocked called for "
+ + packageToString(userId, packageName) + " that has no jobs");
+ mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
+ return;
+ }
+
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
standbyBucket);
final long remainingEJQuota = getRemainingEJExecutionTimeLocked(userId, packageName);
- final boolean inRegularQuota = stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs
- && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
- && isUnderJobCountQuota
- && isUnderTimingSessionCountQuota;
+ int minPriority = JobInfo.PRIORITY_MAX;
+ boolean hasDefPlus = false, hasLow = false, hasMin = false;
+ for (int i = jobs.size() - 1; i >= 0; --i) {
+ final int priority = jobs.valueAt(i).getEffectivePriority();
+ minPriority = Math.min(minPriority, priority);
+ if (priority <= JobInfo.PRIORITY_MIN) {
+ hasMin = true;
+ } else if (priority <= JobInfo.PRIORITY_LOW) {
+ hasLow = true;
+ } else {
+ hasDefPlus = true;
+ }
+ if (hasMin && hasLow && hasDefPlus) {
+ break;
+ }
+ }
+ final boolean inRegularQuota =
+ stats.executionTimeInWindowMs < getAllowedTimePerPeriodMs(minPriority)
+ && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
+ && isUnderJobCountQuota
+ && isUnderTimingSessionCountQuota;
if (inRegularQuota && remainingEJQuota > 0) {
// Already in quota. Why was this method called?
if (DEBUG) {
@@ -1728,7 +1864,24 @@
long inEJQuotaTimeElapsed = Long.MAX_VALUE;
if (!inRegularQuota) {
// The time this app will have quota again.
- long inQuotaTimeElapsed = stats.inQuotaTimeElapsed;
+ long executionInQuotaTime = Long.MAX_VALUE;
+ boolean hasExecutionInQuotaTime = false;
+ if (hasMin && stats.inQuotaTimeMinElapsed > 0) {
+ executionInQuotaTime = Math.min(executionInQuotaTime, stats.inQuotaTimeMinElapsed);
+ hasExecutionInQuotaTime = true;
+ }
+ if (hasLow && stats.inQuotaTimeLowElapsed > 0) {
+ executionInQuotaTime = Math.min(executionInQuotaTime, stats.inQuotaTimeLowElapsed);
+ hasExecutionInQuotaTime = true;
+ }
+ if (hasDefPlus && stats.inQuotaTimeElapsed > 0) {
+ executionInQuotaTime = Math.min(executionInQuotaTime, stats.inQuotaTimeElapsed);
+ hasExecutionInQuotaTime = true;
+ }
+ long inQuotaTimeElapsed = 0;
+ if (hasExecutionInQuotaTime) {
+ inQuotaTimeElapsed = executionInQuotaTime;
+ }
if (!isUnderJobCountQuota && stats.bgJobCountInWindow < stats.jobCountLimit) {
// App hit the rate limit.
inQuotaTimeElapsed =
@@ -1941,6 +2094,7 @@
private final ArraySet<JobStatus> mRunningBgJobs = new ArraySet<>();
private long mStartTimeElapsed;
private int mBgJobCount;
+ private int mLowestPriority = JobInfo.PRIORITY_MAX;
private long mDebitAdjustment;
Timer(int uid, int userId, String packageName, boolean regularJobTimer) {
@@ -1963,6 +2117,7 @@
Slog.v(TAG, "Starting to track " + jobStatus.toShortString());
}
// Always maintain list of running jobs, even when quota is free.
+ mLowestPriority = Math.min(mLowestPriority, jobStatus.getEffectivePriority());
if (mRunningBgJobs.add(jobStatus) && shouldTrackLocked()) {
mBgJobCount++;
if (mRegularJobTimer) {
@@ -2002,6 +2157,13 @@
&& !isQuotaFreeLocked(standbyBucket)) {
emitSessionLocked(nowElapsed);
cancelCutoff();
+ mLowestPriority = JobInfo.PRIORITY_MAX;
+ } else if (mLowestPriority == jobStatus.getEffectivePriority()) {
+ mLowestPriority = JobInfo.PRIORITY_MAX;
+ for (int i = mRunningBgJobs.size() - 1; i >= 0; --i) {
+ mLowestPriority = Math.min(mLowestPriority,
+ mRunningBgJobs.valueAt(i).getEffectivePriority());
+ }
}
}
}
@@ -2128,9 +2290,14 @@
}
Message msg = mHandler.obtainMessage(
mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg);
- final long timeRemainingMs = mRegularJobTimer
- ? getTimeUntilQuotaConsumedLocked(mPkg.userId, mPkg.packageName)
- : getTimeUntilEJQuotaConsumedLocked(mPkg.userId, mPkg.packageName);
+ final long timeRemainingMs;
+ if (mRegularJobTimer) {
+ timeRemainingMs = getTimeUntilQuotaConsumedLocked(
+ mPkg.userId, mPkg.packageName, mLowestPriority);
+ } else {
+ timeRemainingMs =
+ getTimeUntilEJQuotaConsumedLocked(mPkg.userId, mPkg.packageName);
+ }
if (DEBUG) {
Slog.i(TAG,
(mRegularJobTimer ? "Regular job" : "EJ") + " for " + mPkg + " has "
@@ -2250,11 +2417,10 @@
final ShrinkableDebits debits =
getEJDebitsLocked(mPkg.userId, mPkg.packageName);
if (transactQuotaLocked(mPkg.userId, mPkg.packageName,
- nowElapsed, debits, pendingReward)
- && maybeUpdateConstraintForPkgLocked(nowElapsed,
- mPkg.userId, mPkg.packageName)) {
+ nowElapsed, debits, pendingReward)) {
mStateChangedListener.onControllerStateChanged(
- mTrackedJobs.get(mPkg.userId, mPkg.packageName));
+ maybeUpdateConstraintForPkgLocked(nowElapsed,
+ mPkg.userId, mPkg.packageName));
}
}
break;
@@ -2356,11 +2522,9 @@
if (timer != null && timer.isActive()) {
timer.rescheduleCutoff();
}
- if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
- userId, packageName)) {
- mStateChangedListener
- .onControllerStateChanged(mTrackedJobs.get(userId, packageName));
- }
+ mStateChangedListener.onControllerStateChanged(
+ maybeUpdateConstraintForPkgLocked(
+ sElapsedRealtimeClock.millis(), userId, packageName));
}
if (restrictedChanges.size() > 0) {
mStateChangedListener.onRestrictedBucketChanged(restrictedChanges);
@@ -2486,27 +2650,19 @@
Slog.d(TAG, "Checking if " + pkg + " has reached its quota.");
}
- long timeRemainingMs = getRemainingExecutionTimeLocked(pkg.userId,
- pkg.packageName);
- if (timeRemainingMs <= 50) {
- // Less than 50 milliseconds left. Start process of shutting down jobs.
+ final ArraySet<JobStatus> changedJobs = maybeUpdateConstraintForPkgLocked(
+ sElapsedRealtimeClock.millis(), pkg.userId, pkg.packageName);
+ if (changedJobs.size() > 0) {
if (DEBUG) Slog.d(TAG, pkg + " has reached its quota.");
- if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
- pkg.userId, pkg.packageName)) {
- mStateChangedListener.onControllerStateChanged(
- mTrackedJobs.get(pkg.userId, pkg.packageName));
- }
+ mStateChangedListener.onControllerStateChanged(changedJobs);
} else {
// This could potentially happen if an old session phases out while a
// job is currently running.
// Reschedule message
- Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg);
- timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId,
- pkg.packageName);
if (DEBUG) {
- Slog.d(TAG, pkg + " has " + timeRemainingMs + "ms left.");
+ Slog.d(TAG, pkg + " had early REACHED_QUOTA message");
}
- sendMessageDelayed(rescheduleMsg, timeRemainingMs);
+ mPkgTimers.get(pkg.userId, pkg.packageName).scheduleCutoff();
}
break;
}
@@ -2516,26 +2672,19 @@
Slog.d(TAG, "Checking if " + pkg + " has reached its EJ quota.");
}
- long timeRemainingMs = getRemainingEJExecutionTimeLocked(
- pkg.userId, pkg.packageName);
- if (timeRemainingMs <= 0) {
+ final ArraySet<JobStatus> changedJobs = maybeUpdateConstraintForPkgLocked(
+ sElapsedRealtimeClock.millis(), pkg.userId, pkg.packageName);
+ if (changedJobs.size() > 0) {
if (DEBUG) Slog.d(TAG, pkg + " has reached its EJ quota.");
- if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
- pkg.userId, pkg.packageName)) {
- mStateChangedListener.onControllerStateChanged(
- mTrackedJobs.get(pkg.userId, pkg.packageName));
- }
+ mStateChangedListener.onControllerStateChanged(changedJobs);
} else {
// This could potentially happen if an old session phases out while a
// job is currently running.
// Reschedule message
- Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_QUOTA, pkg);
- timeRemainingMs = getTimeUntilEJQuotaConsumedLocked(
- pkg.userId, pkg.packageName);
if (DEBUG) {
- Slog.d(TAG, pkg + " has " + timeRemainingMs + "ms left for EJ");
+ Slog.d(TAG, pkg + " had early REACHED_EJ_QUOTA message");
}
- sendMessageDelayed(rescheduleMsg, timeRemainingMs);
+ mEJPkgTimers.get(pkg.userId, pkg.packageName).scheduleCutoff();
}
break;
}
@@ -2553,11 +2702,9 @@
if (DEBUG) {
Slog.d(TAG, "Checking pkg " + packageToString(userId, packageName));
}
- if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
- userId, packageName)) {
- mStateChangedListener.onControllerStateChanged(
- mTrackedJobs.get(userId, packageName));
- }
+ mStateChangedListener.onControllerStateChanged(
+ maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
+ userId, packageName));
break;
}
case MSG_UID_PROCESS_STATE_CHANGED: {
@@ -2781,6 +2928,12 @@
static final String KEY_IN_QUOTA_BUFFER_MS =
QC_CONSTANT_PREFIX + "in_quota_buffer_ms";
@VisibleForTesting
+ static final String KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW =
+ QC_CONSTANT_PREFIX + "allowed_time_surplus_priority_low";
+ @VisibleForTesting
+ static final String KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN =
+ QC_CONSTANT_PREFIX + "allowed_time_surplus_priority_min";
+ @VisibleForTesting
static final String KEY_WINDOW_SIZE_ACTIVE_MS =
QC_CONSTANT_PREFIX + "window_size_active_ms";
@VisibleForTesting
@@ -2890,6 +3043,8 @@
10 * 60 * 1000L; // 10 minutes
private static final long DEFAULT_IN_QUOTA_BUFFER_MS =
30 * 1000L; // 30 seconds
+ private static final float DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW = .25f;
+ private static final float DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_MIN = .5f;
private static final long DEFAULT_WINDOW_SIZE_ACTIVE_MS =
DEFAULT_ALLOWED_TIME_PER_PERIOD_MS; // ACTIVE apps can run jobs at any time
private static final long DEFAULT_WINDOW_SIZE_WORKING_MS =
@@ -2951,6 +3106,22 @@
public long IN_QUOTA_BUFFER_MS = DEFAULT_IN_QUOTA_BUFFER_MS;
/**
+ * The percentage of {@link #ALLOWED_TIME_PER_PERIOD_MS} that should not be used by
+ * {@link JobInfo#PRIORITY_LOW low priority} jobs. In other words, there must be a minimum
+ * surplus of this amount of remaining allowed time before we start running low priority
+ * jobs.
+ */
+ public float ALLOWED_TIME_SURPLUS_PRIORITY_LOW = DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW;
+
+ /**
+ * The percentage of {@link #ALLOWED_TIME_PER_PERIOD_MS} that should not be used by
+ * {@link JobInfo#PRIORITY_MIN low priority} jobs. In other words, there must be a minimum
+ * surplus of this amount of remaining allowed time before we start running min priority
+ * jobs.
+ */
+ public float ALLOWED_TIME_SURPLUS_PRIORITY_MIN = DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_MIN;
+
+ /**
* The quota window size of the particular standby bucket. Apps in this standby bucket are
* expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
* WINDOW_SIZE_MS.
@@ -3188,6 +3359,8 @@
@NonNull String key) {
switch (key) {
case KEY_ALLOWED_TIME_PER_PERIOD_MS:
+ case KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW:
+ case KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN:
case KEY_IN_QUOTA_BUFFER_MS:
case KEY_MAX_EXECUTION_TIME_MS:
case KEY_WINDOW_SIZE_ACTIVE_MS:
@@ -3407,6 +3580,7 @@
final DeviceConfig.Properties properties = DeviceConfig.getProperties(
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_ALLOWED_TIME_PER_PERIOD_MS, KEY_IN_QUOTA_BUFFER_MS,
+ KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN,
KEY_MAX_EXECUTION_TIME_MS, KEY_WINDOW_SIZE_ACTIVE_MS,
KEY_WINDOW_SIZE_WORKING_MS,
KEY_WINDOW_SIZE_FREQUENT_MS, KEY_WINDOW_SIZE_RARE_MS,
@@ -3414,6 +3588,12 @@
ALLOWED_TIME_PER_PERIOD_MS =
properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_MS,
DEFAULT_ALLOWED_TIME_PER_PERIOD_MS);
+ ALLOWED_TIME_SURPLUS_PRIORITY_LOW =
+ properties.getFloat(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW,
+ DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW);
+ ALLOWED_TIME_SURPLUS_PRIORITY_MIN =
+ properties.getFloat(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN,
+ DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_MIN);
IN_QUOTA_BUFFER_MS = properties.getLong(KEY_IN_QUOTA_BUFFER_MS,
DEFAULT_IN_QUOTA_BUFFER_MS);
MAX_EXECUTION_TIME_MS = properties.getLong(KEY_MAX_EXECUTION_TIME_MS,
@@ -3455,6 +3635,23 @@
mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
mShouldReevaluateConstraints = true;
}
+ // Low priority surplus should be in the range [0, .9]. A value of 1 would essentially
+ // mean never run low priority jobs.
+ float newAllowedTimeSurplusPriorityLow =
+ Math.max(0f, Math.min(.9f, ALLOWED_TIME_SURPLUS_PRIORITY_LOW));
+ if (Float.compare(
+ mAllowedTimeSurplusPriorityLow, newAllowedTimeSurplusPriorityLow) != 0) {
+ mAllowedTimeSurplusPriorityLow = newAllowedTimeSurplusPriorityLow;
+ mShouldReevaluateConstraints = true;
+ }
+ // Min priority surplus should be in the range [0, mAllowedTimeSurplusPriorityLow].
+ float newAllowedTimeSurplusPriorityMin = Math.max(0f,
+ Math.min(mAllowedTimeSurplusPriorityLow, ALLOWED_TIME_SURPLUS_PRIORITY_MIN));
+ if (Float.compare(
+ mAllowedTimeSurplusPriorityMin, newAllowedTimeSurplusPriorityMin) != 0) {
+ mAllowedTimeSurplusPriorityMin = newAllowedTimeSurplusPriorityMin;
+ mShouldReevaluateConstraints = true;
+ }
long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs,
Math.min(MAX_PERIOD_MS, WINDOW_SIZE_ACTIVE_MS));
if (mBucketPeriodsMs[ACTIVE_INDEX] != newActivePeriodMs) {
@@ -3627,6 +3824,10 @@
pw.println("QuotaController:");
pw.increaseIndent();
pw.print(KEY_ALLOWED_TIME_PER_PERIOD_MS, ALLOWED_TIME_PER_PERIOD_MS).println();
+ pw.print(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, ALLOWED_TIME_SURPLUS_PRIORITY_LOW)
+ .println();
+ pw.print(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, ALLOWED_TIME_SURPLUS_PRIORITY_MIN)
+ .println();
pw.print(KEY_IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS).println();
pw.print(KEY_WINDOW_SIZE_ACTIVE_MS, WINDOW_SIZE_ACTIVE_MS).println();
pw.print(KEY_WINDOW_SIZE_WORKING_MS, WINDOW_SIZE_WORKING_MS).println();
@@ -3750,6 +3951,16 @@
}
@VisibleForTesting
+ float getAllowedTimeSurplusPriorityLow() {
+ return mAllowedTimeSurplusPriorityLow;
+ }
+
+ @VisibleForTesting
+ float getAllowedTimeSurplusPriorityMin() {
+ return mAllowedTimeSurplusPriorityMin;
+ }
+
+ @VisibleForTesting
@NonNull
int[] getBucketMaxJobCounts() {
return mMaxBucketJobCounts;
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 0ad70e4..ebf42b8 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -183,7 +183,7 @@
COMPRESS_TIME ? 1 * ONE_MINUTE : 12 * ONE_HOUR,
COMPRESS_TIME ? 4 * ONE_MINUTE : 24 * ONE_HOUR,
COMPRESS_TIME ? 16 * ONE_MINUTE : 48 * ONE_HOUR,
- COMPRESS_TIME ? 32 * ONE_MINUTE : 3 * ONE_DAY
+ COMPRESS_TIME ? 32 * ONE_MINUTE : 8 * ONE_DAY
};
/** The minimum allowed values for each index in {@link #DEFAULT_ELAPSED_TIME_THRESHOLDS}. */
diff --git a/cmds/app_process/Android.bp b/cmds/app_process/Android.bp
index a157517..6a685a7 100644
--- a/cmds/app_process/Android.bp
+++ b/cmds/app_process/Android.bp
@@ -64,6 +64,8 @@
"libwilhelm",
],
+ header_libs: ["bionic_libc_platform_headers"],
+
compile_multilib: "both",
cflags: [
diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp
index 12083b6..815f945 100644
--- a/cmds/app_process/app_main.cpp
+++ b/cmds/app_process/app_main.cpp
@@ -15,6 +15,7 @@
#include <android-base/macros.h>
#include <binder/IPCThreadState.h>
+#include <bionic/pac.h>
#include <hwbinder/IPCThreadState.h>
#include <utils/Log.h>
#include <cutils/memory.h>
@@ -182,6 +183,10 @@
ALOGV("app_process main with argv: %s", argv_String.string());
}
+ // Because of applications that are using PAC instructions incorrectly, PAC
+ // is disabled in application processes for now.
+ ScopedDisablePAC x;
+
AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
// Process command line arguments
// ignore argv[0]
diff --git a/core/api/current.txt b/core/api/current.txt
index 78d4b4d..421107e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -134,6 +134,9 @@
field public static final String READ_HOME_APP_SEARCH_DATA = "android.permission.READ_HOME_APP_SEARCH_DATA";
field @Deprecated public static final String READ_INPUT_STATE = "android.permission.READ_INPUT_STATE";
field public static final String READ_LOGS = "android.permission.READ_LOGS";
+ field public static final String READ_MEDIA_AUDIO = "android.permission.READ_MEDIA_AUDIO";
+ field public static final String READ_MEDIA_IMAGE = "android.permission.READ_MEDIA_IMAGE";
+ field public static final String READ_MEDIA_VIDEO = "android.permission.READ_MEDIA_VIDEO";
field public static final String READ_NEARBY_STREAMING_POLICY = "android.permission.READ_NEARBY_STREAMING_POLICY";
field public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
field public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
@@ -215,6 +218,8 @@
field public static final String NEARBY_DEVICES = "android.permission-group.NEARBY_DEVICES";
field public static final String NOTIFICATIONS = "android.permission-group.NOTIFICATIONS";
field public static final String PHONE = "android.permission-group.PHONE";
+ field public static final String READ_MEDIA_AURAL = "android.permission-group.READ_MEDIA_AURAL";
+ field public static final String READ_MEDIA_VISUAL = "android.permission-group.READ_MEDIA_VISUAL";
field public static final String SENSORS = "android.permission-group.SENSORS";
field public static final String SMS = "android.permission-group.SMS";
field public static final String STORAGE = "android.permission-group.STORAGE";
@@ -953,6 +958,7 @@
field public static final int letterSpacing = 16843958; // 0x10104b6
field public static final int level = 16844032; // 0x1010500
field public static final int lineBreakStyle = 16844365; // 0x101064d
+ field public static final int lineBreakWordStyle = 16844366; // 0x101064e
field public static final int lineHeight = 16844159; // 0x101057f
field public static final int lineSpacingExtra = 16843287; // 0x1010217
field public static final int lineSpacingMultiplier = 16843288; // 0x1010218
@@ -7295,10 +7301,10 @@
method @Nullable public java.util.List<java.lang.String> getDelegatePackages(@NonNull android.content.ComponentName, @NonNull String);
method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String);
method public CharSequence getDeviceOwnerLockScreenInfo();
- method @NonNull public android.graphics.drawable.Drawable getDrawable(int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
- method @NonNull public android.graphics.drawable.Drawable getDrawable(int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
- method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
- method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+ method @NonNull public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+ method @NonNull public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+ method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+ method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName);
method @NonNull public String getEnrollmentSpecificId();
method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName);
@@ -7342,6 +7348,7 @@
method @Nullable public java.util.List<java.lang.String> getPermittedCrossProfileNotificationListeners(@NonNull android.content.ComponentName);
method @Nullable public java.util.List<java.lang.String> getPermittedInputMethods(@NonNull android.content.ComponentName);
method public int getPersonalAppsSuspendedReasons(@NonNull android.content.ComponentName);
+ method @NonNull public android.app.admin.PreferentialNetworkServiceConfig getPreferentialNetworkServiceConfig();
method public int getRequiredPasswordComplexity();
method public long getRequiredStrongAuthTimeout(@Nullable android.content.ComponentName);
method public boolean getScreenCaptureDisabled(@Nullable android.content.ComponentName);
@@ -7486,6 +7493,7 @@
method public boolean setPermittedCrossProfileNotificationListeners(@NonNull android.content.ComponentName, @Nullable java.util.List<java.lang.String>);
method public boolean setPermittedInputMethods(@NonNull android.content.ComponentName, java.util.List<java.lang.String>);
method public void setPersonalAppsSuspended(@NonNull android.content.ComponentName, boolean);
+ method public void setPreferentialNetworkServiceConfig(@NonNull android.app.admin.PreferentialNetworkServiceConfig);
method public void setPreferentialNetworkServiceEnabled(boolean);
method public void setProfileEnabled(@NonNull android.content.ComponentName);
method public void setProfileName(@NonNull android.content.ComponentName, String);
@@ -7709,29 +7717,29 @@
ctor public DevicePolicyResources();
}
- public static final class DevicePolicyResources.Drawable {
- field public static final int INVALID_ID = -1; // 0xffffffff
- field public static final int WORK_PROFILE_ICON = 1; // 0x1
- field public static final int WORK_PROFILE_ICON_BADGE = 0; // 0x0
- field public static final int WORK_PROFILE_OFF_ICON = 2; // 0x2
- field public static final int WORK_PROFILE_USER_ICON = 3; // 0x3
+ public static final class DevicePolicyResources.Drawables {
+ field public static final String UNDEFINED = "UNDEFINED";
+ field public static final String WORK_PROFILE_ICON = "WORK_PROFILE_ICON";
+ field public static final String WORK_PROFILE_ICON_BADGE = "WORK_PROFILE_ICON_BADGE";
+ field public static final String WORK_PROFILE_OFF_ICON = "WORK_PROFILE_OFF_ICON";
+ field public static final String WORK_PROFILE_USER_ICON = "WORK_PROFILE_USER_ICON";
}
- public static final class DevicePolicyResources.Drawable.Source {
- field public static final int HOME_WIDGET = 2; // 0x2
- field public static final int LAUNCHER_OFF_BUTTON = 3; // 0x3
- field public static final int NOTIFICATION = 0; // 0x0
- field public static final int PROFILE_SWITCH_ANIMATION = 1; // 0x1
- field public static final int QUICK_SETTINGS = 4; // 0x4
- field public static final int STATUS_BAR = 5; // 0x5
- field public static final int UNDEFINED = -1; // 0xffffffff
+ public static final class DevicePolicyResources.Drawables.Source {
+ field public static final String HOME_WIDGET = "HOME_WIDGET";
+ field public static final String LAUNCHER_OFF_BUTTON = "LAUNCHER_OFF_BUTTON";
+ field public static final String NOTIFICATION = "NOTIFICATION";
+ field public static final String PROFILE_SWITCH_ANIMATION = "PROFILE_SWITCH_ANIMATION";
+ field public static final String QUICK_SETTINGS = "QUICK_SETTINGS";
+ field public static final String STATUS_BAR = "STATUS_BAR";
+ field public static final String UNDEFINED = "UNDEFINED";
}
- public static final class DevicePolicyResources.Drawable.Style {
- field public static final int DEFAULT = -1; // 0xffffffff
- field public static final int OUTLINE = 2; // 0x2
- field public static final int SOLID_COLORED = 0; // 0x0
- field public static final int SOLID_NOT_COLORED = 1; // 0x1
+ public static final class DevicePolicyResources.Drawables.Style {
+ field public static final String DEFAULT = "DEFAULT";
+ field public static final String OUTLINE = "OUTLINE";
+ field public static final String SOLID_COLORED = "SOLID_COLORED";
+ field public static final String SOLID_NOT_COLORED = "SOLID_NOT_COLORED";
}
public final class DnsEvent extends android.app.admin.NetworkEvent implements android.os.Parcelable {
@@ -7771,6 +7779,32 @@
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.NetworkEvent> CREATOR;
}
+ public final class PreferentialNetworkServiceConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public int[] getExcludedUids();
+ method @NonNull public int[] getIncludedUids();
+ method public int getNetworkId();
+ method public boolean isEnabled();
+ method public boolean isFallbackToDefaultConnectionAllowed();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.PreferentialNetworkServiceConfig> CREATOR;
+ field public static final int PREFERENTIAL_NETWORK_ID_1 = 1; // 0x1
+ field public static final int PREFERENTIAL_NETWORK_ID_2 = 2; // 0x2
+ field public static final int PREFERENTIAL_NETWORK_ID_3 = 3; // 0x3
+ field public static final int PREFERENTIAL_NETWORK_ID_4 = 4; // 0x4
+ field public static final int PREFERENTIAL_NETWORK_ID_5 = 5; // 0x5
+ }
+
+ public static final class PreferentialNetworkServiceConfig.Builder {
+ ctor public PreferentialNetworkServiceConfig.Builder();
+ method @NonNull public android.app.admin.PreferentialNetworkServiceConfig build();
+ method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setEnabled(boolean);
+ method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setExcludedUids(@NonNull int[]);
+ method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setFallbackToDefaultConnectionAllowed(boolean);
+ method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setIncludedUids(@NonNull int[]);
+ method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setNetworkId(int);
+ }
+
public class SecurityLog {
ctor public SecurityLog();
field public static final int LEVEL_ERROR = 3; // 0x3
@@ -8866,6 +8900,7 @@
method @Deprecated public static android.bluetooth.BluetoothAdapter getDefaultAdapter();
method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public java.time.Duration getDiscoverableTimeout();
method public int getLeMaximumAdvertisingDataLength();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getMaxConnectedAudioDevices();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public String getName();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getProfileConnectionState(int);
method public boolean getProfileProxy(android.content.Context, android.bluetooth.BluetoothProfile.ServiceListener, int);
@@ -9752,18 +9787,54 @@
field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED = "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED";
}
- public final class BluetoothLeAudioCodecConfig {
+ public final class BluetoothLeAudioCodecConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getBitsPerSample();
+ method public int getChannelMode();
method @NonNull public String getCodecName();
+ method public int getCodecPriority();
method public int getCodecType();
+ method public int getFrameDuration();
method public static int getMaxCodecType();
+ method public int getOctetsPerFrame();
+ method public int getSampleRate();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int BITS_PER_SAMPLE_16 = 1; // 0x1
+ field public static final int BITS_PER_SAMPLE_24 = 2; // 0x2
+ field public static final int BITS_PER_SAMPLE_32 = 3; // 0x3
+ field public static final int BITS_PER_SAMPLE_NONE = 0; // 0x0
+ field public static final int CHANNEL_MODE_MONO = 1; // 0x1
+ field public static final int CHANNEL_MODE_NONE = 0; // 0x0
+ field public static final int CHANNEL_MODE_STEREO = 2; // 0x2
+ field public static final int CODEC_PRIORITY_DEFAULT = 0; // 0x0
+ field public static final int CODEC_PRIORITY_DISABLED = -1; // 0xffffffff
+ field public static final int CODEC_PRIORITY_HIGHEST = 1000000; // 0xf4240
+ field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothLeAudioCodecConfig> CREATOR;
+ field public static final int FRAME_DURATION_10000 = 2; // 0x2
+ field public static final int FRAME_DURATION_7500 = 1; // 0x1
+ field public static final int FRAME_DURATION_NONE = 0; // 0x0
+ field public static final int SAMPLE_RATE_16000 = 2; // 0x2
+ field public static final int SAMPLE_RATE_24000 = 3; // 0x3
+ field public static final int SAMPLE_RATE_32000 = 4; // 0x4
+ field public static final int SAMPLE_RATE_44100 = 5; // 0x5
+ field public static final int SAMPLE_RATE_48000 = 6; // 0x6
+ field public static final int SAMPLE_RATE_8000 = 1; // 0x1
+ field public static final int SAMPLE_RATE_NONE = 0; // 0x0
field public static final int SOURCE_CODEC_TYPE_INVALID = 1000000; // 0xf4240
field public static final int SOURCE_CODEC_TYPE_LC3 = 0; // 0x0
}
public static final class BluetoothLeAudioCodecConfig.Builder {
ctor public BluetoothLeAudioCodecConfig.Builder();
+ ctor public BluetoothLeAudioCodecConfig.Builder(@NonNull android.bluetooth.BluetoothLeAudioCodecConfig);
method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig build();
+ method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setBitsPerSample(int);
+ method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setChannelMode(int);
+ method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setCodecPriority(int);
method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setCodecType(int);
+ method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setFrameDuration(int);
+ method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setOctetsPerFrame(int);
+ method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setSampleRate(int);
}
public final class BluetoothManager {
@@ -17618,12 +17689,16 @@
public final class LineBreakConfig {
ctor public LineBreakConfig();
method public int getLineBreakStyle();
- method public void set(@Nullable android.graphics.text.LineBreakConfig);
+ method public int getLineBreakWordStyle();
+ method public void set(@NonNull android.graphics.text.LineBreakConfig);
method public void setLineBreakStyle(int);
+ method public void setLineBreakWordStyle(int);
field public static final int LINE_BREAK_STYLE_LOOSE = 1; // 0x1
field public static final int LINE_BREAK_STYLE_NONE = 0; // 0x0
field public static final int LINE_BREAK_STYLE_NORMAL = 2; // 0x2
field public static final int LINE_BREAK_STYLE_STRICT = 3; // 0x3
+ field public static final int LINE_BREAK_WORD_STYLE_NONE = 0; // 0x0
+ field public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1; // 0x1
}
public class LineBreaker {
@@ -20956,6 +21031,7 @@
method public boolean isSink();
method public boolean isSource();
field public static final int TYPE_AUX_LINE = 19; // 0x13
+ field public static final int TYPE_BLE_BROADCAST = 30; // 0x1e
field public static final int TYPE_BLE_HEADSET = 26; // 0x1a
field public static final int TYPE_BLE_SPEAKER = 27; // 0x1b
field public static final int TYPE_BLUETOOTH_A2DP = 8; // 0x8
@@ -26171,6 +26247,15 @@
package android.media.tv {
+ public final class AitInfo implements android.os.Parcelable {
+ ctor public AitInfo(int, int);
+ method public int describeContents();
+ method public int getType();
+ method public int getVersion();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.AitInfo> CREATOR;
+ }
+
public final class TvContentRating {
method public boolean contains(@NonNull android.media.tv.TvContentRating);
method public static android.media.tv.TvContentRating createRating(String, String, String, java.lang.String...);
@@ -26741,12 +26826,14 @@
public abstract static class TvInputService.Session implements android.view.KeyEvent.Callback {
ctor public TvInputService.Session(android.content.Context);
method public void layoutSurface(int, int, int, int);
+ method public void notifyAitInfoUpdated(@NonNull android.media.tv.AitInfo);
method public void notifyChannelRetuned(android.net.Uri);
method public void notifyContentAllowed();
method public void notifyContentBlocked(@NonNull android.media.tv.TvContentRating);
method public void notifyTimeShiftStatusChanged(int);
method public void notifyTrackSelected(int, String);
method public void notifyTracksChanged(java.util.List<android.media.tv.TvTrackInfo>);
+ method public void notifyTuned(@NonNull android.net.Uri);
method public void notifyVideoAvailable();
method public void notifyVideoUnavailable(int);
method public void onAppPrivateCommand(@NonNull String, android.os.Bundle);
@@ -26760,6 +26847,7 @@
method public abstract void onRelease();
method public boolean onSelectTrack(int, @Nullable String);
method public abstract void onSetCaptionEnabled(boolean);
+ method public void onSetInteractiveAppNotificationEnabled(boolean);
method public abstract void onSetStreamVolume(@FloatRange(from=0.0, to=1.0) float);
method public abstract boolean onSetSurface(@Nullable android.view.Surface);
method public void onSurfaceChanged(int, int, int);
@@ -26861,6 +26949,7 @@
method public void sendAppPrivateCommand(@NonNull String, android.os.Bundle);
method public void setCallback(@Nullable android.media.tv.TvView.TvInputCallback);
method public void setCaptionEnabled(boolean);
+ method public void setInteractiveAppNotificationEnabled(boolean);
method public void setOnUnhandledInputEventListener(android.media.tv.TvView.OnUnhandledInputEventListener);
method public void setStreamVolume(@FloatRange(from=0.0, to=1.0) float);
method public void setTimeShiftPositionCallback(@Nullable android.media.tv.TvView.TimeShiftPositionCallback);
@@ -26887,6 +26976,7 @@
public abstract static class TvView.TvInputCallback {
ctor public TvView.TvInputCallback();
+ method public void onAitInfoUpdated(@NonNull String, @NonNull android.media.tv.AitInfo);
method public void onChannelRetuned(String, android.net.Uri);
method public void onConnectionFailed(String);
method public void onContentAllowed(String);
@@ -26919,26 +27009,76 @@
public final class TvInteractiveAppManager {
method @NonNull public java.util.List<android.media.tv.interactive.TvInteractiveAppInfo> getTvInteractiveAppServiceList();
+ method public void registerCallback(@NonNull android.media.tv.interactive.TvInteractiveAppManager.TvInteractiveAppCallback, @NonNull java.util.concurrent.Executor);
+ method public void unregisterCallback(@NonNull android.media.tv.interactive.TvInteractiveAppManager.TvInteractiveAppCallback);
+ field public static final int ERROR_BLOCKED = 5; // 0x5
+ field public static final int ERROR_ENCRYPTED = 6; // 0x6
+ field public static final int ERROR_NONE = 0; // 0x0
+ field public static final int ERROR_NOT_SUPPORTED = 2; // 0x2
+ field public static final int ERROR_RESOURCE_UNAVAILABLE = 4; // 0x4
+ field public static final int ERROR_UNKNOWN = 1; // 0x1
+ field public static final int ERROR_UNKNOWN_CHANNEL = 7; // 0x7
+ field public static final int ERROR_WEAK_SIGNAL = 3; // 0x3
+ field public static final int INTERACTIVE_APP_STATE_ERROR = 3; // 0x3
+ field public static final int INTERACTIVE_APP_STATE_RUNNING = 2; // 0x2
+ field public static final int INTERACTIVE_APP_STATE_STOPPED = 1; // 0x1
+ field public static final int SERVICE_STATE_ERROR = 4; // 0x4
+ field public static final int SERVICE_STATE_PREPARING = 2; // 0x2
+ field public static final int SERVICE_STATE_READY = 3; // 0x3
+ field public static final int SERVICE_STATE_UNREALIZED = 1; // 0x1
+ }
+
+ public abstract static class TvInteractiveAppManager.TvInteractiveAppCallback {
+ ctor public TvInteractiveAppManager.TvInteractiveAppCallback();
+ method public void onTvInteractiveAppServiceStateChanged(@NonNull String, int, int, int);
}
public abstract class TvInteractiveAppService extends android.app.Service {
ctor public TvInteractiveAppService();
+ method public final void notifyStateChanged(int, int, int);
method public final android.os.IBinder onBind(android.content.Intent);
+ method @Nullable public abstract android.media.tv.interactive.TvInteractiveAppService.Session onCreateSession(@NonNull String, int);
+ method public abstract void onPrepare(int);
field public static final String SERVICE_INTERFACE = "android.media.tv.interactive.TvInteractiveAppService";
field public static final String SERVICE_META_DATA = "android.media.tv.interactive.app";
}
+ public abstract static class TvInteractiveAppService.Session implements android.view.KeyEvent.Callback {
+ ctor public TvInteractiveAppService.Session(@NonNull android.content.Context);
+ method public void layoutSurface(int, int, int, int);
+ method public final void notifyBiInteractiveAppCreated(@NonNull android.net.Uri, @Nullable String);
+ method public void notifySessionStateChanged(int, int);
+ method public void onCreateBiInteractiveApp(@NonNull android.net.Uri, @Nullable android.os.Bundle);
+ method public void onDestroyBiInteractiveApp(@NonNull String);
+ method public boolean onKeyDown(int, @NonNull android.view.KeyEvent);
+ method public boolean onKeyLongPress(int, @NonNull android.view.KeyEvent);
+ method public boolean onKeyMultiple(int, int, @NonNull android.view.KeyEvent);
+ method public boolean onKeyUp(int, @NonNull android.view.KeyEvent);
+ method public abstract boolean onSetSurface(@Nullable android.view.Surface);
+ method public void onStartInteractiveApp();
+ method public void onStopInteractiveApp();
+ method public void onSurfaceChanged(int, int, int);
+ method public void onTuned(@NonNull android.net.Uri);
+ }
+
public class TvInteractiveAppView extends android.view.ViewGroup {
ctor public TvInteractiveAppView(@NonNull android.content.Context);
ctor public TvInteractiveAppView(@NonNull android.content.Context, @Nullable android.util.AttributeSet);
ctor public TvInteractiveAppView(@NonNull android.content.Context, @Nullable android.util.AttributeSet, int);
method public void clearCallback();
+ method public void createBiInteractiveApp(@NonNull android.net.Uri, @Nullable android.os.Bundle);
+ method public void destroyBiInteractiveApp(@NonNull String);
+ method public void prepareInteractiveApp(@NonNull String, int);
method public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.interactive.TvInteractiveAppView.TvInteractiveAppCallback);
+ method public int setTvView(@Nullable android.media.tv.TvView);
method public void startInteractiveApp();
+ method public void stopInteractiveApp();
}
public abstract static class TvInteractiveAppView.TvInteractiveAppCallback {
ctor public TvInteractiveAppView.TvInteractiveAppCallback();
+ method public void onBiInteractiveAppCreated(@NonNull String, @NonNull android.net.Uri, @Nullable String);
+ method public void onStateChanged(@NonNull String, int, int);
}
}
@@ -33039,9 +33179,13 @@
method @NonNull public int[] areEffectsSupported(@NonNull int...);
method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...);
method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel();
+ method @Nullable public android.os.vibrator.VibratorFrequencyProfile getFrequencyProfile();
method public int getId();
method @NonNull public int[] getPrimitiveDurations(@NonNull int...);
+ method public float getQFactor();
+ method public float getResonantFrequency();
method public abstract boolean hasAmplitudeControl();
+ method public boolean hasFrequencyControl();
method public abstract boolean hasVibrator();
method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long);
method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long, android.media.AudioAttributes);
@@ -33367,6 +33511,17 @@
}
+package android.os.vibrator {
+
+ public final class VibratorFrequencyProfile {
+ method public float getMaxAmplitudeMeasurementInterval();
+ method @FloatRange(from=0, to=1) @NonNull public float[] getMaxAmplitudeMeasurements();
+ method public float getMaxFrequency();
+ method public float getMinFrequency();
+ }
+
+}
+
package android.preference {
@Deprecated public class CheckBoxPreference extends android.preference.TwoStatePreference {
@@ -40517,6 +40672,7 @@
public final class Call {
method public void addConferenceParticipants(@NonNull java.util.List<android.net.Uri>);
method public void answer(int);
+ method public void answerCall(@NonNull android.telecom.CallEndpoint, int);
method public void conference(android.telecom.Call);
method public void deflect(android.net.Uri);
method public void disconnect();
@@ -40537,7 +40693,9 @@
method public void phoneAccountSelected(android.telecom.PhoneAccountHandle, boolean);
method public void playDtmfTone(char);
method public void postDialContinue(boolean);
- method public void pullExternalCall();
+ method public void pullCall();
+ method @Deprecated public void pullExternalCall();
+ method public void pushCall(@NonNull android.telecom.CallEndpoint);
method public void putExtras(android.os.Bundle);
method public void registerCallback(android.telecom.Call.Callback);
method public void registerCallback(android.telecom.Call.Callback, android.os.Handler);
@@ -40580,7 +40738,10 @@
public abstract static class Call.Callback {
ctor public Call.Callback();
+ method public void onAnswerFailed(@NonNull android.telecom.CallEndpoint, int);
method public void onCallDestroyed(android.telecom.Call);
+ method public void onCallPullFailed(int);
+ method public void onCallPushFailed(@NonNull android.telecom.CallEndpoint, int);
method public void onCannedTextResponsesLoaded(android.telecom.Call, java.util.List<java.lang.String>);
method public void onChildrenChanged(android.telecom.Call, java.util.List<android.telecom.Call>);
method public void onConferenceableCallsChanged(android.telecom.Call, java.util.List<android.telecom.Call>);
@@ -40596,11 +40757,22 @@
method public void onRttStatusChanged(android.telecom.Call, boolean, android.telecom.Call.RttCall);
method public void onStateChanged(android.telecom.Call, int);
method public void onVideoCallChanged(android.telecom.Call, android.telecom.InCallService.VideoCall);
+ field public static final int ANSWER_FAILED_ENDPOINT_REJECTED = 3; // 0x3
+ field public static final int ANSWER_FAILED_ENDPOINT_TIMEOUT = 2; // 0x2
+ field public static final int ANSWER_FAILED_ENDPOINT_UNAVAILABLE = 1; // 0x1
+ field public static final int ANSWER_FAILED_UNKNOWN_REASON = 0; // 0x0
field public static final int HANDOVER_FAILURE_DEST_APP_REJECTED = 1; // 0x1
field public static final int HANDOVER_FAILURE_NOT_SUPPORTED = 2; // 0x2
field public static final int HANDOVER_FAILURE_ONGOING_EMERGENCY_CALL = 4; // 0x4
field public static final int HANDOVER_FAILURE_UNKNOWN = 5; // 0x5
field public static final int HANDOVER_FAILURE_USER_REJECTED = 3; // 0x3
+ field public static final int PULL_FAILED_ENDPOINT_REJECTED = 2; // 0x2
+ field public static final int PULL_FAILED_ENDPOINT_TIMEOUT = 1; // 0x1
+ field public static final int PULL_FAILED_UNKNOWN_REASON = 0; // 0x0
+ field public static final int PUSH_FAILED_ENDPOINT_REJECTED = 3; // 0x3
+ field public static final int PUSH_FAILED_ENDPOINT_TIMEOUT = 2; // 0x2
+ field public static final int PUSH_FAILED_ENDPOINT_UNAVAILABLE = 1; // 0x1
+ field public static final int PUSH_FAILED_UNKNOWN_REASON = 0; // 0x0
}
public static class Call.Details {
@@ -40608,6 +40780,8 @@
method public boolean can(int);
method public static String capabilitiesToString(int);
method public android.telecom.PhoneAccountHandle getAccountHandle();
+ method @Nullable public android.telecom.CallEndpoint getActiveCallEndpoint();
+ method @NonNull public java.util.Set<android.telecom.CallEndpoint> getAvailableCallEndpoints();
method public int getCallCapabilities();
method public int getCallDirection();
method public int getCallProperties();
@@ -40701,6 +40875,34 @@
field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5
}
+ public final class CallEndpoint implements android.os.Parcelable {
+ ctor public CallEndpoint(@NonNull android.os.ParcelUuid, @NonNull CharSequence, int, @NonNull android.content.ComponentName);
+ method public int describeContents();
+ method @NonNull public CharSequence getDescription();
+ method @NonNull public android.os.ParcelUuid getIdentifier();
+ method public int getType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telecom.CallEndpoint> CREATOR;
+ field public static final int ENDPOINT_TYPE_TETHERED = 2; // 0x2
+ field public static final int ENDPOINT_TYPE_UNTETHERED = 1; // 0x1
+ }
+
+ public interface CallEndpointCallback {
+ method public void onCallEndpointSessionActivationTimeout();
+ method public void onCallEndpointSessionDeactivated();
+ }
+
+ public class CallEndpointSession {
+ method public void setCallEndpointSessionActivated();
+ method public void setCallEndpointSessionActivationFailed(int);
+ method public void setCallEndpointSessionDeactivated();
+ field public static final int ACTIVATION_FAILURE_REJECTED = 1; // 0x1
+ field public static final int ACTIVATION_FAILURE_UNAVAILABLE = 0; // 0x0
+ field public static final int ANSWER_REQUEST = 1; // 0x1
+ field public static final int PLACE_REQUEST = 3; // 0x3
+ field public static final int PUSH_REQUEST = 2; // 0x2
+ }
+
public abstract class CallRedirectionService extends android.app.Service {
ctor public CallRedirectionService();
method public final void cancelCall();
@@ -40970,7 +41172,6 @@
field public static final int PROPERTY_IS_RTT = 256; // 0x100
field public static final int PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL = 1024; // 0x400
field public static final int PROPERTY_SELF_MANAGED = 128; // 0x80
- field public static final int PROPERTY_TETHERED_CALL = 16384; // 0x4000
field public static final int PROPERTY_WIFI = 8; // 0x8
field public static final int STATE_ACTIVE = 4; // 0x4
field public static final int STATE_DIALING = 3; // 0x3
@@ -41104,6 +41305,8 @@
field public static final int OTHER = 9; // 0x9
field public static final String REASON_EMERGENCY_CALL_PLACED = "REASON_EMERGENCY_CALL_PLACED";
field public static final String REASON_EMULATING_SINGLE_CALL = "EMULATING_SINGLE_CALL";
+ field public static final String REASON_ENDPOINT_REJECTED = "REASON_ENDPOINT_REJECTED";
+ field public static final String REASON_ENDPOINT_SESSION_DEACTIVATED = "REASON_ENDPOINT_SESSION_DEACTIVATED";
field public static final String REASON_IMS_ACCESS_BLOCKED = "REASON_IMS_ACCESS_BLOCKED";
field public static final String REASON_WIFI_ON_BUT_WFC_OFF = "REASON_WIFI_ON_BUT_WFC_OFF";
field public static final int REJECTED = 6; // 0x6
@@ -41132,6 +41335,7 @@
method public void onBringToForeground(boolean);
method public void onCallAdded(android.telecom.Call);
method public void onCallAudioStateChanged(android.telecom.CallAudioState);
+ method @NonNull public android.telecom.CallEndpointCallback onCallEndpointActivationRequested(@NonNull android.telecom.CallEndpoint, @NonNull android.telecom.CallEndpointSession) throws java.lang.UnsupportedOperationException;
method public void onCallRemoved(android.telecom.Call);
method public void onCanAddCallChanged(boolean);
method public void onConnectionEvent(android.telecom.Call, String, android.os.Bundle);
@@ -41394,11 +41598,12 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.ANSWER_PHONE_CALLS) public boolean endCall();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.net.Uri getAdnUriForPhoneAccount(android.telecom.PhoneAccountHandle);
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts();
+ method @NonNull public java.util.Set<android.telecom.CallEndpoint> getCallEndpoints();
method public String getDefaultDialerPackage();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telecom.PhoneAccountHandle getDefaultOutgoingPhoneAccount(String);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}, conditional=true) public String getLine1Number(android.telecom.PhoneAccountHandle);
method public android.telecom.PhoneAccount getPhoneAccount(android.telecom.PhoneAccountHandle);
- method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts();
+ method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MANAGE_OWN_CALLS}) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts();
method public android.telecom.PhoneAccountHandle getSimCallManager();
method @Nullable public android.telecom.PhoneAccountHandle getSimCallManagerForSubscription(int);
method @Nullable public String getSystemDialerPackage();
@@ -41414,10 +41619,12 @@
method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public boolean isTtySupported();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, String);
method @RequiresPermission(anyOf={android.Manifest.permission.CALL_PHONE, android.Manifest.permission.MANAGE_OWN_CALLS}) public void placeCall(android.net.Uri, android.os.Bundle);
+ method public void registerCallEndpoints(@NonNull java.util.Set<android.telecom.CallEndpoint>);
method public void registerPhoneAccount(android.telecom.PhoneAccount);
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void showInCallScreen(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void silenceRinger();
method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void startConference(@NonNull java.util.List<android.net.Uri>, @NonNull android.os.Bundle);
+ method public void unregisterCallEndpoints(@NonNull java.util.Set<android.telecom.CallEndpoint>);
method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle);
field public static final String ACTION_CHANGE_DEFAULT_DIALER = "android.telecom.action.CHANGE_DEFAULT_DIALER";
field public static final String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS";
@@ -41461,6 +41668,7 @@
field public static final String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telecom.extra.PHONE_ACCOUNT_HANDLE";
field public static final String EXTRA_PICTURE_URI = "android.telecom.extra.PICTURE_URI";
field public static final String EXTRA_PRIORITY = "android.telecom.extra.PRIORITY";
+ field public static final String EXTRA_START_CALL_ON_ENDPOINT = "android.telecom.extra.START_CALL_ON_ENDPOINT";
field public static final String EXTRA_START_CALL_WITH_RTT = "android.telecom.extra.START_CALL_WITH_RTT";
field public static final String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE";
field public static final String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.telecom.extra.START_CALL_WITH_VIDEO_STATE";
@@ -41472,6 +41680,7 @@
field public static final String METADATA_IN_CALL_SERVICE_CAR_MODE_UI = "android.telecom.IN_CALL_SERVICE_CAR_MODE_UI";
field public static final String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING";
field public static final String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
+ field public static final String METADATA_STREAMING_TETHERED_CALLS = "android.telecom.STREAMING_TETHERED_CALLS";
field public static final int PRESENTATION_ALLOWED = 1; // 0x1
field public static final int PRESENTATION_PAYPHONE = 4; // 0x4
field public static final int PRESENTATION_RESTRICTED = 2; // 0x2
@@ -48308,6 +48517,7 @@
method @Nullable public android.view.SurfaceControl.Transaction buildReparentTransaction(@NonNull android.view.SurfaceControl);
method public default int getBufferTransformHint();
method public default void removeOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener);
+ method public default void setTouchableRegion(@Nullable android.graphics.Region);
}
@UiThread public static interface AttachedSurfaceControl.OnBufferTransformHintChangedListener {
@@ -48684,6 +48894,7 @@
method public static int[] getDeviceIds();
method public int getId();
method public android.view.KeyCharacterMap getKeyCharacterMap();
+ method public int getKeyCodeForKeyLocation(int);
method public int getKeyboardType();
method @NonNull public android.hardware.lights.LightsManager getLightsManager();
method public android.view.InputDevice.MotionRange getMotionRange(int);
@@ -55051,6 +55262,7 @@
public abstract class WebSettings {
ctor public WebSettings();
method @Deprecated public abstract boolean enableSmoothTransition();
+ method public boolean getAllowAlgorithmicDarkening();
method public abstract boolean getAllowContentAccess();
method public abstract boolean getAllowFileAccess();
method public abstract boolean getAllowFileAccessFromFileURLs();
@@ -55072,7 +55284,7 @@
method public abstract boolean getDomStorageEnabled();
method public abstract String getFantasyFontFamily();
method public abstract String getFixedFontFamily();
- method public int getForceDark();
+ method @Deprecated public int getForceDark();
method public abstract boolean getJavaScriptCanOpenWindowsAutomatically();
method public abstract boolean getJavaScriptEnabled();
method public abstract android.webkit.WebSettings.LayoutAlgorithm getLayoutAlgorithm();
@@ -55095,6 +55307,7 @@
method public abstract int getTextZoom();
method public abstract boolean getUseWideViewPort();
method public abstract String getUserAgentString();
+ method public void setAllowAlgorithmicDarkening(boolean);
method public abstract void setAllowContentAccess(boolean);
method public abstract void setAllowFileAccess(boolean);
method @Deprecated public abstract void setAllowFileAccessFromFileURLs(boolean);
@@ -55116,7 +55329,7 @@
method @Deprecated public abstract void setEnableSmoothTransition(boolean);
method public abstract void setFantasyFontFamily(String);
method public abstract void setFixedFontFamily(String);
- method public void setForceDark(int);
+ method @Deprecated public void setForceDark(int);
method @Deprecated public abstract void setGeolocationDatabasePath(String);
method public abstract void setGeolocationEnabled(boolean);
method public abstract void setJavaScriptCanOpenWindowsAutomatically(boolean);
@@ -55147,9 +55360,9 @@
method public abstract void setUserAgentString(@Nullable String);
method public abstract boolean supportMultipleWindows();
method public abstract boolean supportZoom();
- field public static final int FORCE_DARK_AUTO = 1; // 0x1
- field public static final int FORCE_DARK_OFF = 0; // 0x0
- field public static final int FORCE_DARK_ON = 2; // 0x2
+ field @Deprecated public static final int FORCE_DARK_AUTO = 1; // 0x1
+ field @Deprecated public static final int FORCE_DARK_OFF = 0; // 0x0
+ field @Deprecated public static final int FORCE_DARK_ON = 2; // 0x2
field public static final int LOAD_CACHE_ELSE_NETWORK = 1; // 0x1
field public static final int LOAD_CACHE_ONLY = 3; // 0x3
field public static final int LOAD_DEFAULT = -1; // 0xffffffff
@@ -57314,8 +57527,8 @@
method public void setViewOutlinePreferredRadiusDimen(@IdRes int, @DimenRes int);
method public void setViewPadding(@IdRes int, @Px int, @Px int, @Px int, @Px int);
method public void setViewVisibility(@IdRes int, int);
- method public void showNext(@IdRes int);
- method public void showPrevious(@IdRes int);
+ method @Deprecated public void showNext(@IdRes int);
+ method @Deprecated public void showPrevious(@IdRes int);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.widget.RemoteViews> CREATOR;
field public static final String EXTRA_CHECKED = "android.widget.extra.CHECKED";
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 2a4e7a6..ee31e13 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -63,6 +63,8 @@
public class DevicePolicyManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void acknowledgeNewUserDisclaimer();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void clearLogoutUser();
+ method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getLogoutUser();
field public static final String ACTION_SHOW_NEW_USER_DISCLAIMER = "android.app.action.SHOW_NEW_USER_DISCLAIMER";
}
@@ -78,16 +80,27 @@
method @NonNull @WorkerThread public android.app.usage.NetworkStats querySummary(@NonNull android.net.NetworkTemplate, long, long) throws java.lang.SecurityException;
method @NonNull @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForDevice(@NonNull android.net.NetworkTemplate, long, long);
method @NonNull @WorkerThread public android.app.usage.NetworkStats queryTaggedSummary(@NonNull android.net.NetworkTemplate, long, long) throws java.lang.SecurityException;
+ method public void registerUsageCallback(@NonNull android.net.NetworkTemplate, long, @NonNull java.util.concurrent.Executor, @NonNull android.app.usage.NetworkStatsManager.UsageCallback);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setDefaultGlobalAlert(long);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setPollOnOpen(boolean);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setStatsProviderWarningAndLimitAsync(@NonNull String, long, long);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setUidForeground(int, boolean);
}
+ public abstract static class NetworkStatsManager.UsageCallback {
+ method public void onThresholdReached(@NonNull android.net.NetworkTemplate);
+ }
+
}
package android.bluetooth {
+ public class BluetoothFrameworkInitializer {
+ method public static void registerServiceWrappers();
+ method public static void setBinderCallsStatsInitializer(@NonNull java.util.function.Consumer<android.content.Context>);
+ method public static void setBluetoothServiceManager(@NonNull android.os.BluetoothServiceManager);
+ }
+
public final class BluetoothPan implements android.bluetooth.BluetoothProfile {
method @Nullable @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED}) public android.net.TetheringManager.TetheredInterfaceRequest requestTetheredInterface(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheredInterfaceCallback);
}
@@ -266,6 +279,10 @@
method public int getResourceId();
}
+ public class LocalSocket implements java.io.Closeable {
+ ctor public LocalSocket(@NonNull java.io.FileDescriptor);
+ }
+
public class NetworkIdentity {
method public int getOemManaged();
method public int getRatType();
@@ -319,6 +336,20 @@
field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStateSnapshot> CREATOR;
}
+ public class NetworkStatsCollection {
+ method @NonNull public java.util.Map<android.net.NetworkStatsCollection.Key,android.net.NetworkStatsHistory> getEntries();
+ }
+
+ public static final class NetworkStatsCollection.Builder {
+ ctor public NetworkStatsCollection.Builder(long);
+ method @NonNull public android.net.NetworkStatsCollection.Builder addEntry(@NonNull android.net.NetworkStatsCollection.Key, @NonNull android.net.NetworkStatsHistory);
+ method @NonNull public android.net.NetworkStatsCollection build();
+ }
+
+ public static class NetworkStatsCollection.Key {
+ ctor public NetworkStatsCollection.Key(@NonNull java.util.Set<android.net.NetworkIdentity>, int, int, int);
+ }
+
public final class NetworkStatsHistory implements android.os.Parcelable {
method public int describeContents();
method @NonNull public java.util.List<android.net.NetworkStatsHistory.Entry> getEntries();
@@ -399,6 +430,7 @@
}
public class TrafficStats {
+ method public static void attachSocketTagger();
method public static void init(@NonNull android.content.Context);
}
@@ -432,6 +464,21 @@
method public final void markVintfStability();
}
+ public class BluetoothServiceManager {
+ method @NonNull public android.os.BluetoothServiceManager.ServiceRegisterer getBluetoothManagerServiceRegisterer();
+ }
+
+ public static class BluetoothServiceManager.ServiceNotFoundException extends java.lang.Exception {
+ ctor public BluetoothServiceManager.ServiceNotFoundException(@NonNull String);
+ }
+
+ public static final class BluetoothServiceManager.ServiceRegisterer {
+ method @Nullable public android.os.IBinder get();
+ method @NonNull public android.os.IBinder getOrThrow() throws android.os.BluetoothServiceManager.ServiceNotFoundException;
+ method public void register(@NonNull android.os.IBinder);
+ method @Nullable public android.os.IBinder tryGet();
+ }
+
public class Build {
method public static boolean isDebuggable();
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 34a8a8b..ea2a641 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -287,6 +287,7 @@
field public static final String SECURE_ELEMENT_PRIVILEGED_OPERATION = "android.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION";
field public static final String SEND_CATEGORY_CAR_NOTIFICATIONS = "android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS";
field public static final String SEND_DEVICE_CUSTOMIZATION_READY = "android.permission.SEND_DEVICE_CUSTOMIZATION_READY";
+ field public static final String SEND_LOST_MODE_LOCATION_UPDATES = "android.permission.SEND_LOST_MODE_LOCATION_UPDATES";
field public static final String SEND_SAFETY_CENTER_UPDATE = "android.permission.SEND_SAFETY_CENTER_UPDATE";
field public static final String SEND_SHOW_SUSPENDED_APP_DETAILS = "android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS";
field public static final String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION";
@@ -320,6 +321,7 @@
field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA";
field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
field public static final String TOGGLE_AUTOMOTIVE_PROJECTION = "android.permission.TOGGLE_AUTOMOTIVE_PROJECTION";
+ field public static final String TRIGGER_LOST_MODE = "android.permission.TRIGGER_LOST_MODE";
field public static final String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE";
field public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER";
field public static final String UNLIMITED_SHORTCUTS_API_CALLS = "android.permission.UNLIMITED_SHORTCUTS_API_CALLS";
@@ -1021,13 +1023,13 @@
package android.app.admin {
public final class DevicePolicyDrawableResource implements android.os.Parcelable {
- ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, int, int, int, @DrawableRes int);
- ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, int, int, @DrawableRes int);
+ ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, @NonNull String, @NonNull String, @NonNull String, @DrawableRes int);
+ ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, @NonNull String, @NonNull String, @DrawableRes int);
method public int describeContents();
method @DrawableRes public int getCallingPackageResourceId();
- method public int getDrawableId();
- method public int getDrawableSource();
- method public int getDrawableStyle();
+ method @NonNull public String getDrawableId();
+ method @NonNull public String getDrawableSource();
+ method @NonNull public String getDrawableStyle();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DevicePolicyDrawableResource> CREATOR;
}
@@ -1066,8 +1068,9 @@
method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean packageHasActiveAdmins(String);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
- method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void resetDrawables(@NonNull int[]);
+ method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void resetDrawables(@NonNull String[]);
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void resetStrings(@NonNull String[]);
+ method @RequiresPermission(android.Manifest.permission.SEND_LOST_MODE_LOCATION_UPDATES) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void setDrawables(@NonNull java.util.Set<android.app.admin.DevicePolicyDrawableResource>);
@@ -1079,6 +1082,7 @@
field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
field public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE = "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE";
field @RequiresPermission(android.Manifest.permission.DISPATCH_PROVISIONING_MESSAGE) public static final String ACTION_ESTABLISH_NETWORK_CONNECTION = "android.app.action.ESTABLISH_NETWORK_CONNECTION";
+ field public static final String ACTION_LOST_MODE_LOCATION_UPDATE = "android.app.action.LOST_MODE_LOCATION_UPDATE";
field public static final String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION";
field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE";
field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
@@ -1105,6 +1109,7 @@
field public static final int CODE_USER_NOT_RUNNING = 3; // 0x3
field public static final int CODE_USER_SETUP_COMPLETED = 4; // 0x4
field public static final String EXTRA_FORCE_UPDATE_ROLE_HOLDER = "android.app.extra.FORCE_UPDATE_ROLE_HOLDER";
+ field public static final String EXTRA_LOST_MODE_LOCATION = "android.app.extra.LOST_MODE_LOCATION";
field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
@@ -2373,9 +2378,11 @@
field public static final int ACCESS_REJECTED = 2; // 0x2
field public static final int ACCESS_UNKNOWN = 0; // 0x0
field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_SILENCE_MODE_CHANGED = "android.bluetooth.device.action.SILENCE_MODE_CHANGED";
+ field @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public static final String ACTION_SWITCH_BUFFER_SIZE = "android.bluetooth.device.action.SWITCH_BUFFER_SIZE";
field public static final String DEVICE_TYPE_DEFAULT = "Default";
field public static final String DEVICE_TYPE_UNTETHERED_HEADSET = "Untethered Headset";
field public static final String DEVICE_TYPE_WATCH = "Watch";
+ field public static final String EXTRA_LOW_LATENCY_BUFFER_SIZE = "android.bluetooth.device.extra.LOW_LATENCY_BUFFER_SIZE";
field public static final int METADATA_COMPANION_APP = 4; // 0x4
field public static final int METADATA_DEVICE_TYPE = 17; // 0x11
field public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; // 0x10
@@ -2416,6 +2423,15 @@
method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean stopScoUsingVirtualVoiceCall();
}
+ public final class BluetoothHeadsetClient implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
+ method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
+ method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+ field @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.headsetprofile.action.CONNECTION_STATE_CHANGED";
+ }
+
public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile {
method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public long getHiSyncId(@NonNull android.bluetooth.BluetoothDevice);
@@ -2447,8 +2463,14 @@
field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED";
}
- public final class BluetoothMapClient implements android.bluetooth.BluetoothProfile {
+ public final class BluetoothMapClient implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
+ method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
+ method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]);
method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.SEND_SMS}) public boolean sendMessage(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.Collection<android.net.Uri>, @NonNull String, @Nullable android.app.PendingIntent, @Nullable android.app.PendingIntent);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+ field @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED";
}
public final class BluetoothPan implements android.bluetooth.BluetoothProfile {
@@ -5221,6 +5243,7 @@
public final class UsbPort {
method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int enableLimitPowerTransfer(boolean);
method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int enableUsbData(boolean);
+ method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int enableUsbDataWhileDocked();
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USB) public android.hardware.usb.UsbPortStatus getStatus();
method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setRoles(int, int);
field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL = 1; // 0x1
@@ -5233,6 +5256,12 @@
field public static final int ENABLE_USB_DATA_ERROR_OTHER = 4; // 0x4
field public static final int ENABLE_USB_DATA_ERROR_PORT_MISMATCH = 3; // 0x3
field public static final int ENABLE_USB_DATA_SUCCESS = 0; // 0x0
+ field public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_DATA_ENABLED = 4; // 0x4
+ field public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_INTERNAL = 1; // 0x1
+ field public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_NOT_SUPPORTED = 2; // 0x2
+ field public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_OTHER = 5; // 0x5
+ field public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_PORT_MISMATCH = 3; // 0x3
+ field public static final int ENABLE_USB_DATA_WHILE_DOCKED_SUCCESS = 0; // 0x0
}
public final class UsbPortStatus implements android.os.Parcelable {
@@ -5240,7 +5269,9 @@
method public int getCurrentDataRole();
method public int getCurrentMode();
method public int getCurrentPowerRole();
+ method public int getPowerBrickStatus();
method public int getSupportedRoleCombinations();
+ method @Nullable public int[] getUsbDataStatus();
method public boolean isConnected();
method public boolean isPowerTransferLimited();
method public boolean isRoleCombinationSupported(int, int);
@@ -5254,9 +5285,19 @@
field public static final int MODE_DFP = 2; // 0x2
field public static final int MODE_NONE = 0; // 0x0
field public static final int MODE_UFP = 1; // 0x1
+ field public static final int POWER_BRICK_STATUS_CONNECTED = 1; // 0x1
+ field public static final int POWER_BRICK_STATUS_DISCONNECTED = 2; // 0x2
+ field public static final int POWER_BRICK_STATUS_UNKNOWN = 0; // 0x0
field public static final int POWER_ROLE_NONE = 0; // 0x0
field public static final int POWER_ROLE_SINK = 2; // 0x2
field public static final int POWER_ROLE_SOURCE = 1; // 0x1
+ field public static final int USB_DATA_STATUS_DISABLED_CONTAMINANT = 3; // 0x3
+ field public static final int USB_DATA_STATUS_DISABLED_DEBUG = 6; // 0x6
+ field public static final int USB_DATA_STATUS_DISABLED_DOCK = 4; // 0x4
+ field public static final int USB_DATA_STATUS_DISABLED_FORCE = 5; // 0x5
+ field public static final int USB_DATA_STATUS_DISABLED_OVERHEAT = 2; // 0x2
+ field public static final int USB_DATA_STATUS_ENABLED = 1; // 0x1
+ field public static final int USB_DATA_STATUS_UNKNOWN = 0; // 0x0
}
}
@@ -6761,6 +6802,7 @@
method @Nullable public android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities();
method @Nullable public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
+ method @Nullable public android.media.tv.tuner.frontend.FrontendStatusReadiness[] getFrontendStatusReadiness(@NonNull int[]);
method @IntRange(from=0xffffffff) public int getMaxNumberOfFrontends(int);
method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean hasUnusedFrontend(int);
method public boolean isLowestPriority(int);
@@ -8005,6 +8047,16 @@
method public boolean isLocked();
}
+ public class FrontendStatusReadiness {
+ method public int getStatusReadiness();
+ method public int getStatusType();
+ field public static final int FRONTEND_STATUS_READINESS_STABLE = 3; // 0x3
+ field public static final int FRONTEND_STATUS_READINESS_UNAVAILABLE = 1; // 0x1
+ field public static final int FRONTEND_STATUS_READINESS_UNDEFINED = 0; // 0x0
+ field public static final int FRONTEND_STATUS_READINESS_UNSTABLE = 2; // 0x2
+ field public static final int FRONTEND_STATUS_READINESS_UNSUPPORTED = 4; // 0x4
+ }
+
public class Isdbs3FrontendCapabilities extends android.media.tv.tuner.frontend.FrontendCapabilities {
method public int getCodeRateCapability();
method public int getModulationCapability();
@@ -9846,12 +9898,17 @@
public final class PermissionControllerManager {
method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void getHibernationEligibility(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>);
method public void getUnusedAppCount(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public void revokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull java.util.concurrent.Executor, @NonNull android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback);
method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[], @NonNull android.os.UserHandle);
field public static final int COUNT_ONLY_WHEN_GRANTED = 1; // 0x1
field public static final int COUNT_WHEN_SYSTEM = 2; // 0x2
+ field public static final int HIBERNATION_ELIGIBILITY_ELIGIBLE = 0; // 0x0
+ field public static final int HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM = 1; // 0x1
+ field public static final int HIBERNATION_ELIGIBILITY_EXEMPT_BY_USER = 2; // 0x2
+ field public static final int HIBERNATION_ELIGIBILITY_UNKNOWN = -1; // 0xffffffff
field public static final int REASON_INSTALLER_POLICY_VIOLATION = 2; // 0x2
field public static final int REASON_MALWARE = 1; // 0x1
}
@@ -9869,6 +9926,7 @@
method @BinderThread public abstract void onCountPermissionApps(@NonNull java.util.List<java.lang.String>, int, @NonNull java.util.function.IntConsumer);
method @BinderThread public abstract void onGetAppPermissions(@NonNull String, @NonNull java.util.function.Consumer<java.util.List<android.permission.RuntimePermissionPresentationInfo>>);
method @BinderThread public void onGetGroupOfPlatformPermission(@NonNull String, @NonNull java.util.function.Consumer<java.lang.String>);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void onGetHibernationEligibility(@NonNull String, @NonNull java.util.function.IntConsumer);
method @BinderThread public abstract void onGetPermissionUsages(boolean, long, @NonNull java.util.function.Consumer<java.util.List<android.permission.RuntimePermissionUsageInfo>>);
method @BinderThread public void onGetPlatformPermissionsForGroup(@NonNull String, @NonNull java.util.function.Consumer<java.util.List<java.lang.String>>);
method @BinderThread public abstract void onGetRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.OutputStream, @NonNull Runnable);
@@ -9893,6 +9951,8 @@
method public int checkPermissionForDataDelivery(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String);
method public int checkPermissionForDataDeliveryFromDataSource(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String);
method public int checkPermissionForPreflight(@NonNull String, @NonNull android.content.AttributionSource);
+ method public int checkPermissionForStartDataDelivery(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String);
+ method public void finishDataDelivery(@NonNull String, @NonNull android.content.AttributionSource);
method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionGrantedPackages();
method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionRequestedPackages();
method @IntRange(from=0) @RequiresPermission(anyOf={android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY, android.Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS}) public int getRuntimePermissionsVersion();
@@ -14722,6 +14782,7 @@
field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_3G = 6; // 0x6
field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_EHRPD = 4; // 0x4
field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_HSPAPLUS = 5; // 0x5
+ field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_INTERNET_PDN = 12; // 0xc
field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_IWLAN = 9; // 0x9
field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_DISABLED = 2; // 0x2
field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_ENABLED = 3; // 0x3
@@ -15690,6 +15751,7 @@
method @Deprecated public abstract void setUseWebViewBackgroundForOverscrollBackground(boolean);
method @Deprecated public abstract void setUserAgent(int);
method public abstract void setVideoOverlayForEmbeddedEncryptedVideoEnabled(boolean);
+ field public static final long ENABLE_SIMPLIFIED_DARK_MODE = 214741472L; // 0xcccb1e0L
}
public class WebStorage {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e97ef6c..ff26ae8 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -38,6 +38,7 @@
field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS";
field public static final String REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL = "android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL";
field public static final String SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS = "android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS";
+ field public static final String SET_KEYBOARD_LAYOUT = "android.permission.SET_KEYBOARD_LAYOUT";
field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS";
field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC";
@@ -1180,9 +1181,23 @@
package android.hardware.input {
+ public final class InputDeviceIdentifier implements android.os.Parcelable {
+ ctor public InputDeviceIdentifier(@NonNull String, int, int);
+ method public int describeContents();
+ method @NonNull public String getDescriptor();
+ method public int getProductId();
+ method public int getVendorId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.InputDeviceIdentifier> CREATOR;
+ }
+
public final class InputManager {
method public int getBlockUntrustedTouchesMode(@NonNull android.content.Context);
+ method @Nullable public String getCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier);
+ method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull android.view.InputDevice);
+ method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void removeKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setBlockUntrustedTouchesMode(@NonNull android.content.Context, int);
+ method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void setCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setMaximumObscuringOpacityForTouch(@FloatRange(from=0, to=1) float);
field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL
}
@@ -2740,6 +2755,7 @@
public final class InputDevice implements android.os.Parcelable {
method @RequiresPermission("android.permission.DISABLE_INPUT_DEVICE") public void disable();
method @RequiresPermission("android.permission.DISABLE_INPUT_DEVICE") public void enable();
+ method @NonNull public android.hardware.input.InputDeviceIdentifier getIdentifier();
}
public class KeyEvent extends android.view.InputEvent implements android.os.Parcelable {
diff --git a/core/java/Android.bp b/core/java/Android.bp
index c9cbef2..897bab4 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -121,6 +121,11 @@
],
}
+filegroup {
+ name: "ILogcatManagerService_aidl",
+ srcs: ["android/os/logcat/ILogcatManagerService.aidl"],
+}
+
genrule {
name: "statslog-framework-java-gen",
tools: ["stats-log-api-gen"],
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3ddbe9e..ea62714 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -61,6 +61,7 @@
import android.app.servertransaction.ResumeActivityItem;
import android.app.servertransaction.TransactionExecutor;
import android.app.servertransaction.TransactionExecutorHelper;
+import android.bluetooth.BluetoothFrameworkInitializer;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.AttributionSource;
import android.content.AutofillOptions;
@@ -109,6 +110,7 @@
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
+import android.os.BluetoothServiceManager;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -7919,6 +7921,7 @@
StatsFrameworkInitializer.setStatsServiceManager(new StatsServiceManager());
MediaFrameworkPlatformInitializer.setMediaServiceManager(new MediaServiceManager());
MediaFrameworkInitializer.setMediaServiceManager(new MediaServiceManager());
+ BluetoothFrameworkInitializer.setBluetoothServiceManager(new BluetoothServiceManager());
}
private void purgePendingResources() {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 68c69e5..0081234 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2344,11 +2344,11 @@
Manifest.permission.USE_BIOMETRIC,
Manifest.permission.ACTIVITY_RECOGNITION,
Manifest.permission.SMS_FINANCIAL_TRANSACTIONS,
- null,
+ Manifest.permission.READ_MEDIA_AUDIO,
null, // no permission for OP_WRITE_MEDIA_AUDIO
- null,
+ Manifest.permission.READ_MEDIA_VIDEO,
null, // no permission for OP_WRITE_MEDIA_VIDEO
- null,
+ Manifest.permission.READ_MEDIA_IMAGE,
null, // no permission for OP_WRITE_MEDIA_IMAGES
null, // no permission for OP_LEGACY_STORAGE
null, // no permission for OP_ACCESS_ACCESSIBILITY
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 7f849ef..9eb3e8f 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -29,9 +29,13 @@
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
+import android.util.ArrayMap;
import android.util.Log;
+import android.util.Slog;
import android.view.autofill.AutofillManager;
+import com.android.internal.annotations.GuardedBy;
+
import java.util.ArrayList;
/**
@@ -53,6 +57,10 @@
*/
public class Application extends ContextWrapper implements ComponentCallbacks2 {
private static final String TAG = "Application";
+
+ /** Whether to enable the check to detect "duplicate application instances". */
+ private static final boolean DEBUG_DUP_APP_INSTANCES = true;
+
@UnsupportedAppUsage
private ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
new ArrayList<ActivityLifecycleCallbacks>();
@@ -66,6 +74,13 @@
@UnsupportedAppUsage
public LoadedApk mLoadedApk;
+ @GuardedBy("sInstances")
+ private static final ArrayMap<Class<?>, Application> sInstances =
+ DEBUG_DUP_APP_INSTANCES ? new ArrayMap<>(1) : null;
+
+ // Only set when DEBUG_DUP_APP_INSTANCES is true.
+ private StackTrace mConstructorStackTrace;
+
public interface ActivityLifecycleCallbacks {
/**
@@ -231,6 +246,41 @@
public Application() {
super(null);
+ if (DEBUG_DUP_APP_INSTANCES) {
+ checkDuplicateInstances();
+ }
+ }
+
+ private void checkDuplicateInstances() {
+ final Class<?> myClass = this.getClass();
+
+ // We only activate this check for custom application classes.
+ // Otherwise, it'd misfire if multiple apps share the same process, if all of them use
+ // the same Application class (on the same classloader).
+ if (myClass == Application.class) {
+ return;
+ }
+ synchronized (sInstances) {
+ final Application firstInstance = sInstances.get(myClass);
+ if (firstInstance == null) {
+ this.mConstructorStackTrace = new StackTrace("First ctor was called here");
+ sInstances.put(myClass, this);
+ return;
+ }
+ final StackTrace currentStackTrace = new StackTrace("Current ctor was called here",
+ firstInstance.mConstructorStackTrace);
+ this.mConstructorStackTrace = currentStackTrace;
+ Slog.wtf(TAG, "Application ctor called twice for " + myClass
+ + " first LoadedApk=" + firstInstance.getLoadedApkInfo(),
+ currentStackTrace);
+ }
+ }
+
+ private String getLoadedApkInfo() {
+ if (mLoadedApk == null) {
+ return "null";
+ }
+ return mLoadedApk + "/pkg=" + mLoadedApk.mPackageName;
}
/**
diff --git a/core/java/android/app/StackTrace.java b/core/java/android/app/StackTrace.java
index ec058f8..6a77abd 100644
--- a/core/java/android/app/StackTrace.java
+++ b/core/java/android/app/StackTrace.java
@@ -24,4 +24,8 @@
public StackTrace(String message) {
super(message);
}
+
+ public StackTrace(String message, Throwable innerStackTrace) {
+ super(message, innerStackTrace);
+ }
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index f0208c6..7f8e46e 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -51,7 +51,7 @@
import android.app.usage.UsageStatsManager;
import android.apphibernation.AppHibernationManager;
import android.appwidget.AppWidgetManager;
-import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothFrameworkInitializer;
import android.companion.CompanionDeviceManager;
import android.companion.ICompanionDeviceManager;
import android.companion.virtual.IVirtualDeviceManager;
@@ -348,13 +348,6 @@
return new MediaRouter(ctx);
}});
- registerService(Context.BLUETOOTH_SERVICE, BluetoothManager.class,
- new CachedServiceFetcher<BluetoothManager>() {
- @Override
- public BluetoothManager createService(ContextImpl ctx) {
- return new BluetoothManager(ctx);
- }});
-
registerService(Context.HDMI_CONTROL_SERVICE, HdmiControlManager.class,
new StaticServiceFetcher<HdmiControlManager>() {
@Override
@@ -1544,6 +1537,7 @@
ConnectivityFrameworkInitializer.registerServiceWrappers();
JobSchedulerFrameworkInitializer.registerServiceWrappers();
BlobStoreManagerFrameworkInitializer.initialize();
+ BluetoothFrameworkInitializer.registerServiceWrappers();
TelephonyFrameworkInitializer.registerServiceWrappers();
AppSearchManagerFrameworkInitializer.initialize();
WifiFrameworkInitializer.registerServiceWrappers();
diff --git a/core/java/android/app/admin/DevicePolicyDrawableResource.java b/core/java/android/app/admin/DevicePolicyDrawableResource.java
index d32ff84..61ff11b 100644
--- a/core/java/android/app/admin/DevicePolicyDrawableResource.java
+++ b/core/java/android/app/admin/DevicePolicyDrawableResource.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.app.admin.DevicePolicyResources.Drawables;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
@@ -34,9 +35,9 @@
*/
@SystemApi
public final class DevicePolicyDrawableResource implements Parcelable {
- private final @DevicePolicyResources.UpdatableDrawableId int mDrawableId;
- private final @DevicePolicyResources.UpdatableDrawableStyle int mDrawableStyle;
- private final @DevicePolicyResources.UpdatableDrawableSource int mDrawableSource;
+ @NonNull private final @DevicePolicyResources.UpdatableDrawableId String mDrawableId;
+ @NonNull private final @DevicePolicyResources.UpdatableDrawableStyle String mDrawableStyle;
+ @NonNull private final @DevicePolicyResources.UpdatableDrawableSource String mDrawableSource;
private final @DrawableRes int mCallingPackageResourceId;
@NonNull private ParcelableResource mResource;
@@ -59,9 +60,9 @@
*/
public DevicePolicyDrawableResource(
@NonNull Context context,
- @DevicePolicyResources.UpdatableDrawableId int drawableId,
- @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
- @DevicePolicyResources.UpdatableDrawableSource int drawableSource,
+ @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+ @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
+ @NonNull @DevicePolicyResources.UpdatableDrawableSource String drawableSource,
@DrawableRes int callingPackageResourceId) {
this(drawableId, drawableStyle, drawableSource, callingPackageResourceId,
new ParcelableResource(context, callingPackageResourceId,
@@ -69,11 +70,17 @@
}
private DevicePolicyDrawableResource(
- @DevicePolicyResources.UpdatableDrawableId int drawableId,
- @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
- @DevicePolicyResources.UpdatableDrawableSource int drawableSource,
+ @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+ @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
+ @NonNull @DevicePolicyResources.UpdatableDrawableSource String drawableSource,
@DrawableRes int callingPackageResourceId,
@NonNull ParcelableResource resource) {
+
+ Objects.requireNonNull(drawableId);
+ Objects.requireNonNull(drawableStyle);
+ Objects.requireNonNull(drawableSource);
+ Objects.requireNonNull(resource);
+
this.mDrawableId = drawableId;
this.mDrawableStyle = drawableStyle;
this.mDrawableSource = drawableSource;
@@ -98,34 +105,37 @@
*/
public DevicePolicyDrawableResource(
@NonNull Context context,
- @DevicePolicyResources.UpdatableDrawableId int drawableId,
- @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
+ @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+ @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
@DrawableRes int callingPackageResourceId) {
- this(context, drawableId, drawableStyle, DevicePolicyResources.Drawable.Source.UNDEFINED,
+ this(context, drawableId, drawableStyle, Drawables.Source.UNDEFINED,
callingPackageResourceId);
}
/**
* Returns the ID of the drawable to update.
*/
+ @NonNull
@DevicePolicyResources.UpdatableDrawableId
- public int getDrawableId() {
+ public String getDrawableId() {
return mDrawableId;
}
/**
* Returns the style of the drawable to update
*/
+ @NonNull
@DevicePolicyResources.UpdatableDrawableStyle
- public int getDrawableStyle() {
+ public String getDrawableStyle() {
return mDrawableStyle;
}
/**
* Returns the source of the drawable to update.
*/
+ @NonNull
@DevicePolicyResources.UpdatableDrawableSource
- public int getDrawableSource() {
+ public String getDrawableSource() {
return mDrawableSource;
}
@@ -153,9 +163,9 @@
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DevicePolicyDrawableResource other = (DevicePolicyDrawableResource) o;
- return mDrawableId == other.mDrawableId
- && mDrawableStyle == other.mDrawableStyle
- && mDrawableSource == other.mDrawableSource
+ return mDrawableId.equals(other.mDrawableId)
+ && mDrawableStyle.equals(other.mDrawableStyle)
+ && mDrawableSource.equals(other.mDrawableSource)
&& mCallingPackageResourceId == other.mCallingPackageResourceId
&& mResource.equals(other.mResource);
}
@@ -173,9 +183,9 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeInt(mDrawableId);
- dest.writeInt(mDrawableStyle);
- dest.writeInt(mDrawableSource);
+ dest.writeString(mDrawableId);
+ dest.writeString(mDrawableStyle);
+ dest.writeString(mDrawableSource);
dest.writeInt(mCallingPackageResourceId);
dest.writeTypedObject(mResource, flags);
}
@@ -184,9 +194,9 @@
new Creator<DevicePolicyDrawableResource>() {
@Override
public DevicePolicyDrawableResource createFromParcel(Parcel in) {
- int drawableId = in.readInt();
- int drawableStyle = in.readInt();
- int drawableSource = in.readInt();
+ String drawableId = in.readString();
+ String drawableStyle = in.readString();
+ String drawableSource = in.readString();
int callingPackageResourceId = in.readInt();
ParcelableResource resource = in.readTypedObject(ParcelableResource.CREATOR);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index fa0af2d..cbce8ac 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -16,9 +16,6 @@
package android.app.admin;
-import static android.app.admin.DevicePolicyResources.Drawable.INVALID_ID;
-import static android.app.admin.DevicePolicyResources.Drawable.Source.UNDEFINED;
-
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.Manifest.permission;
@@ -42,6 +39,7 @@
import android.app.Activity;
import android.app.IServiceConnection;
import android.app.KeyguardManager;
+import android.app.admin.DevicePolicyResources.Drawables;
import android.app.admin.SecurityLog.SecurityEvent;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
@@ -62,6 +60,7 @@
import android.net.ProxyInfo;
import android.net.Uri;
import android.nfc.NfcAdapter;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
@@ -99,6 +98,7 @@
import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.net.NetworkUtilsInternal;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.Preconditions;
@@ -133,6 +133,7 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
// TODO(b/172376923) - add CarDevicePolicyManager examples below (or remove reference to it).
/**
@@ -1795,6 +1796,28 @@
"android.app.action.RESET_PROTECTION_POLICY_CHANGED";
/**
+ * Broadcast action: sent when there is a location update on a device in lost mode. This
+ * broadcast is explicitly sent to the device policy controller app only.
+ *
+ * @see DevicePolicyManager#sendLostModeLocationUpdate
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_LOST_MODE_LOCATION_UPDATE =
+ "android.app.action.LOST_MODE_LOCATION_UPDATE";
+
+ /**
+ * Extra used with {@link #ACTION_LOST_MODE_LOCATION_UPDATE} to send the location of a device
+ * in lost mode. Value is {@code Location}.
+ *
+ * @see DevicePolicyManager#sendLostModeLocationUpdate
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_LOST_MODE_LOCATION =
+ "android.app.extra.LOST_MODE_LOCATION";
+
+ /**
* The ComponentName of the administrator component.
*
* @see #ACTION_ADD_DEVICE_ADMIN
@@ -5824,6 +5847,56 @@
}
/**
+ * Send a lost mode location update to the admin. This API is limited to organization-owned
+ * devices, which includes devices with a device owner or devices with a profile owner on an
+ * organization-owned managed profile.
+ *
+ * <p>The caller must hold the
+ * {@link android.Manifest.permission#SEND_LOST_MODE_LOCATION_UPDATES} permission.
+ *
+ * <p> Not for use by third-party applications.
+ *
+ * @param executor The executor through which the callback should be invoked.
+ * @param callback A callback object that will inform the caller whether a lost mode location
+ * update was successfully sent
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.SEND_LOST_MODE_LOCATION_UPDATES)
+ public void sendLostModeLocationUpdate(@NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<Boolean> callback) {
+ throwIfParentInstance("sendLostModeLocationUpdate");
+ if (mService == null) {
+ executeCallback(AndroidFuture.completedFuture(false), executor, callback);
+ return;
+ }
+ try {
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
+ mService.sendLostModeLocationUpdate(future);
+ executeCallback(future, executor, callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private void executeCallback(AndroidFuture<Boolean> future,
+ @CallbackExecutor @NonNull Executor executor,
+ Consumer<Boolean> callback) {
+ future.whenComplete((result, error) -> executor.execute(() -> {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (error != null) {
+ callback.accept(false);
+ } else {
+ callback.accept(result);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }));
+ }
+
+ /**
* Called by an application that is administering the device to set the
* global proxy and exclusion list.
* <p>
@@ -10019,14 +10092,17 @@
/**
* Gets the user a {@link #logoutUser(ComponentName)} call would switch to,
- * or {@link UserHandle#USER_NULL} if the current user is not in a session.
+ * or {@code null} if the current user is not in a session.
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_USERS)
- public @UserIdInt int getLogoutUserId() {
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public @Nullable UserHandle getLogoutUser() {
+ // TODO(b/214336184): add CTS test
try {
- return mService.getLogoutUserId();
+ int userId = mService.getLogoutUserId();
+ return userId == UserHandle.USER_NULL ? null : UserHandle.of(userId);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -10040,7 +10116,9 @@
* @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public void clearLogoutUser() {
+ // TODO(b/214336184): add CTS test
try {
mService.clearLogoutUser();
} catch (RemoteException re) {
@@ -10677,6 +10755,9 @@
* On fully-managed devices this method is unsupported because all traffic is considered
* work traffic.
*
+ * <p> This method enables preferential network service with a default configuration.
+ * To fine-tune the configuration, use {@link #setPreferentialNetworkServiceConfig) instead.
+ *
* <p>This method can only be called by the profile owner of a managed profile.
* @param enabled whether preferential network service should be enabled.
* @throws SecurityException if the caller is not the profile owner.
@@ -10715,6 +10796,56 @@
}
/**
+ * Sets preferential network configuration on the work profile.
+ * {@see PreferentialNetworkServiceConfig}
+ *
+ * An example of a supported preferential network service is the Enterprise
+ * slice on 5G networks.
+ *
+ * By default, preferential network service is disabled on the work profile on supported
+ * carriers and devices. Admins can explicitly enable it with this API.
+ * On fully-managed devices this method is unsupported because all traffic is considered
+ * work traffic.
+ *
+ * <p>This method can only be called by the profile owner of a managed profile.
+ * @param preferentialNetworkServiceConfig preferential network configuration.
+ * @throws SecurityException if the caller is not the profile owner.
+ **/
+ public void setPreferentialNetworkServiceConfig(
+ @NonNull PreferentialNetworkServiceConfig preferentialNetworkServiceConfig) {
+ throwIfParentInstance("setPreferentialNetworkServiceConfig");
+ if (mService == null) {
+ return;
+ }
+ try {
+ mService.setPreferentialNetworkServiceConfig(preferentialNetworkServiceConfig);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get preferential network configuration
+ * {@see PreferentialNetworkServiceConfig}
+ *
+ * <p>This method can be called by the profile owner of a managed profile.
+ *
+ * @return preferential network configuration.
+ * @throws SecurityException if the caller is not the profile owner.
+ */
+ public @NonNull PreferentialNetworkServiceConfig getPreferentialNetworkServiceConfig() {
+ throwIfParentInstance("getPreferentialNetworkServiceConfig");
+ if (mService == null) {
+ return PreferentialNetworkServiceConfig.DEFAULT;
+ }
+ try {
+ return mService.getPreferentialNetworkServiceConfig();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* This method is mostly deprecated.
* Most of the settings that still have an effect have dedicated setter methods or user
* restrictions. See individual settings for details.
@@ -14733,17 +14864,17 @@
/**
* For each {@link DevicePolicyDrawableResource} item in {@code drawables}, if
* {@link DevicePolicyDrawableResource#getDrawableSource()} is not set or is set to
- * {@link DevicePolicyResources.Drawable.Source#UNDEFINED}, it updates the drawable resource for
+ * {@link DevicePolicyResources.Drawables.Source#UNDEFINED}, it updates the drawable resource for
* the combination of {@link DevicePolicyDrawableResource#getDrawableId()} and
* {@link DevicePolicyDrawableResource#getDrawableStyle()}, (see
- * {@link DevicePolicyResources.Drawable} and {@link DevicePolicyResources.Drawable.Style}) to
+ * {@link DevicePolicyResources.Drawables} and {@link DevicePolicyResources.Drawables.Style}) to
* the drawable with ID {@link DevicePolicyDrawableResource#getCallingPackageResourceId()},
* meaning any system UI surface calling {@link #getDrawable}
* with {@code drawableId} and {@code drawableStyle} will get the new resource after this API
* is called.
*
* <p>Otherwise, if {@link DevicePolicyDrawableResource#getDrawableSource()} is set (see
- * {@link DevicePolicyResources.Drawable.Source}, it overrides any drawables that was set for
+ * {@link DevicePolicyResources.Drawables.Source}, it overrides any drawables that was set for
* the same {@code drawableId} and {@code drawableStyle} for the provided source.
*
* <p>Sends a broadcast with action {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to
@@ -14779,10 +14910,10 @@
/**
* Removes all updated drawables for the list of {@code drawableIds} (see
- * {@link DevicePolicyResources.Drawable} that was previously set by calling
+ * {@link DevicePolicyResources.Drawables} that was previously set by calling
* {@link #setDrawables}, meaning any subsequent calls to {@link #getDrawable} for the provided
- * IDs with any {@link DevicePolicyResources.Drawable.Style} and any
- * {@link DevicePolicyResources.Drawable.Source} will return the default drawable from
+ * IDs with any {@link DevicePolicyResources.Drawables.Style} and any
+ * {@link DevicePolicyResources.Drawables.Source} will return the default drawable from
* {@code defaultDrawableLoader}.
*
* <p>Sends a broadcast with action {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to
@@ -14794,7 +14925,7 @@
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES)
- public void resetDrawables(@NonNull int[] drawableIds) {
+ public void resetDrawables(@NonNull String[] drawableIds) {
if (mService != null) {
try {
mService.resetDrawables(drawableIds);
@@ -14806,19 +14937,19 @@
/**
* Returns the appropriate updated drawable for the {@code drawableId}
- * (see {@link DevicePolicyResources.Drawable}), with style {@code drawableStyle}
- * (see {@link DevicePolicyResources.Drawable.Style}) if one was set using
+ * (see {@link DevicePolicyResources.Drawables}), with style {@code drawableStyle}
+ * (see {@link DevicePolicyResources.Drawables.Style}) if one was set using
* {@code setDrawables}, otherwise returns the drawable from {@code defaultDrawableLoader}.
*
* <p>Also returns the drawable from {@code defaultDrawableLoader} if
- * {@link DevicePolicyResources.Drawable#INVALID_ID} was passed.
+ * {@link DevicePolicyResources.Drawables#UNDEFINED} was passed.
*
* <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
* {@link NullPointerException} is thrown.
*
* <p>This API uses the screen density returned from {@link Resources#getConfiguration()}, to
* set a different value use
- * {@link #getDrawableForDensity(int, int, int, Callable)}.
+ * {@link #getDrawableForDensity(String, String, int, Callable)}.
*
* <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
* notified when a resource has been updated.
@@ -14833,16 +14964,18 @@
*/
@NonNull
public Drawable getDrawable(
- @DevicePolicyResources.UpdatableDrawableId int drawableId,
- @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
+ @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+ @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
@NonNull Callable<Drawable> defaultDrawableLoader) {
- return getDrawable(drawableId, drawableStyle, UNDEFINED, defaultDrawableLoader);
+ return getDrawable(
+ drawableId, drawableStyle, Drawables.Source.UNDEFINED, defaultDrawableLoader);
}
/**
- * Similar to {@link #getDrawable(int, int, Callable)}, but also accepts
- * a {@code drawableSource} (see {@link DevicePolicyResources.Drawable.Source}) which
- * could result in returning a different drawable than {@link #getDrawable(int, int, Callable)}
+ * Similar to {@link #getDrawable(String, String, Callable)}, but also accepts
+ * a {@code drawableSource} (see {@link DevicePolicyResources.Drawables.Source}) which
+ * could result in returning a different drawable than
+ * {@link #getDrawable(String, String, Callable)}
* if an override was set for that specific source.
*
* <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
@@ -14859,12 +14992,17 @@
*/
@NonNull
public Drawable getDrawable(
- @DevicePolicyResources.UpdatableDrawableId int drawableId,
- @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
- @DevicePolicyResources.UpdatableDrawableSource int drawableSource,
+ @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+ @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
+ @NonNull @DevicePolicyResources.UpdatableDrawableSource String drawableSource,
@NonNull Callable<Drawable> defaultDrawableLoader) {
+
+ Objects.requireNonNull(drawableId, "drawableId can't be null");
+ Objects.requireNonNull(drawableStyle, "drawableStyle can't be null");
+ Objects.requireNonNull(drawableSource, "drawableSource can't be null");
Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null");
- if (drawableId == INVALID_ID) {
+
+ if (Drawables.UNDEFINED.equals(drawableId)) {
return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader);
}
if (mService != null) {
@@ -14892,7 +15030,7 @@
}
/**
- * Similar to {@link #getDrawable(int, int, Callable)}, but also accepts
+ * Similar to {@link #getDrawable(String, String, Callable)}, but also accepts
* {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}.
*
* <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
@@ -14911,20 +15049,20 @@
*/
@NonNull
public Drawable getDrawableForDensity(
- @DevicePolicyResources.UpdatableDrawableId int drawableId,
- @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
+ @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+ @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
int density,
@NonNull Callable<Drawable> defaultDrawableLoader) {
return getDrawableForDensity(
drawableId,
drawableStyle,
- UNDEFINED,
+ Drawables.Source.UNDEFINED,
density,
defaultDrawableLoader);
}
/**
- * Similar to {@link #getDrawable(int, int, int, Callable)}, but also accepts
+ * Similar to {@link #getDrawable(String, String, String, Callable)}, but also accepts
* {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}.
*
* <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
@@ -14944,13 +15082,18 @@
*/
@NonNull
public Drawable getDrawableForDensity(
- @DevicePolicyResources.UpdatableDrawableId int drawableId,
- @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
- @DevicePolicyResources.UpdatableDrawableSource int drawableSource,
+ @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+ @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
+ @NonNull @DevicePolicyResources.UpdatableDrawableSource String drawableSource,
int density,
@NonNull Callable<Drawable> defaultDrawableLoader) {
+
+ Objects.requireNonNull(drawableId, "drawableId can't be null");
+ Objects.requireNonNull(drawableStyle, "drawableStyle can't be null");
+ Objects.requireNonNull(drawableSource, "drawableSource can't be null");
Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null");
- if (drawableId == INVALID_ID) {
+
+ if (Drawables.UNDEFINED.equals(drawableId)) {
return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader);
}
if (mService != null) {
diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index 46e2cce..2ad2010 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -128,7 +128,6 @@
import static android.app.admin.DevicePolicyResources.Strings.SystemUi.STATUS_BAR_WORK_ICON_ACCESSIBILITY;
import static android.app.admin.DevicePolicyResources.Strings.SystemUi.WORK_LOCK_ACCESSIBILITY;
-import android.annotation.IntDef;
import android.annotation.StringDef;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
@@ -153,12 +152,12 @@
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @IntDef(value = {
- Drawable.INVALID_ID,
- Drawable.WORK_PROFILE_ICON_BADGE,
- Drawable.WORK_PROFILE_ICON,
- Drawable.WORK_PROFILE_OFF_ICON,
- Drawable.WORK_PROFILE_USER_ICON
+ @StringDef(value = {
+ Drawables.UNDEFINED,
+ Drawables.WORK_PROFILE_ICON_BADGE,
+ Drawables.WORK_PROFILE_ICON,
+ Drawables.WORK_PROFILE_OFF_ICON,
+ Drawables.WORK_PROFILE_USER_ICON
})
public @interface UpdatableDrawableId {}
@@ -169,10 +168,11 @@
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @IntDef(value = {
- Drawable.Style.SOLID_COLORED,
- Drawable.Style.SOLID_NOT_COLORED,
- Drawable.Style.OUTLINE,
+ @StringDef(value = {
+ Drawables.Style.SOLID_COLORED,
+ Drawables.Style.SOLID_NOT_COLORED,
+ Drawables.Style.OUTLINE,
+ Drawables.Style.DEFAULT
})
public @interface UpdatableDrawableStyle {}
@@ -182,14 +182,14 @@
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @IntDef(value = {
- Drawable.Source.UNDEFINED,
- Drawable.Source.NOTIFICATION,
- Drawable.Source.PROFILE_SWITCH_ANIMATION,
- Drawable.Source.HOME_WIDGET,
- Drawable.Source.LAUNCHER_OFF_BUTTON,
- Drawable.Source.QUICK_SETTINGS,
- Drawable.Source.STATUS_BAR
+ @StringDef(value = {
+ Drawables.Source.UNDEFINED,
+ Drawables.Source.NOTIFICATION,
+ Drawables.Source.PROFILE_SWITCH_ANIMATION,
+ Drawables.Source.HOME_WIDGET,
+ Drawables.Source.LAUNCHER_OFF_BUTTON,
+ Drawables.Source.QUICK_SETTINGS,
+ Drawables.Source.STATUS_BAR
})
public @interface UpdatableDrawableSource {}
@@ -265,44 +265,44 @@
/**
* Class containing the identifiers used to update device management-related system drawable.
*/
- public static final class Drawable {
+ public static final class Drawables {
- private Drawable() {
+ private Drawables() {
}
/**
* An ID for any drawable that can't be updated.
*/
- public static final int INVALID_ID = -1;
+ public static final String UNDEFINED = "UNDEFINED";
/**
* Specifically used to badge work profile app icons.
*/
- public static final int WORK_PROFILE_ICON_BADGE = 0;
+ public static final String WORK_PROFILE_ICON_BADGE = "WORK_PROFILE_ICON_BADGE";
/**
* General purpose work profile icon (i.e. generic icon badging). For badging app icons
* specifically, see {@link #WORK_PROFILE_ICON_BADGE}.
*/
- public static final int WORK_PROFILE_ICON = 1;
+ public static final String WORK_PROFILE_ICON = "WORK_PROFILE_ICON";
/**
* General purpose icon representing the work profile off state.
*/
- public static final int WORK_PROFILE_OFF_ICON = 2;
+ public static final String WORK_PROFILE_OFF_ICON = "WORK_PROFILE_OFF_ICON";
/**
* General purpose icon for the work profile user avatar.
*/
- public static final int WORK_PROFILE_USER_ICON = 3;
+ public static final String WORK_PROFILE_USER_ICON = "WORK_PROFILE_USER_ICON";
/**
* @hide
*/
- public static final Set<Integer> UPDATABLE_DRAWABLE_IDS = buildDrawablesSet();
+ public static final Set<String> UPDATABLE_DRAWABLE_IDS = buildDrawablesSet();
- private static Set<Integer> buildDrawablesSet() {
- Set<Integer> drawables = new HashSet<>();
+ private static Set<String> buildDrawablesSet() {
+ Set<String> drawables = new HashSet<>();
drawables.add(WORK_PROFILE_ICON_BADGE);
drawables.add(WORK_PROFILE_ICON);
drawables.add(WORK_PROFILE_OFF_ICON);
@@ -323,48 +323,48 @@
* A source identifier indicating that the updatable resource is used in a generic
* undefined location.
*/
- public static final int UNDEFINED = -1;
+ public static final String UNDEFINED = "UNDEFINED";
/**
* A source identifier indicating that the updatable drawable is used in notifications.
*/
- public static final int NOTIFICATION = 0;
+ public static final String NOTIFICATION = "NOTIFICATION";
/**
* A source identifier indicating that the updatable drawable is used in a cross
* profile switching animation.
*/
- public static final int PROFILE_SWITCH_ANIMATION = 1;
+ public static final String PROFILE_SWITCH_ANIMATION = "PROFILE_SWITCH_ANIMATION";
/**
* A source identifier indicating that the updatable drawable is used in a work
* profile home screen widget.
*/
- public static final int HOME_WIDGET = 2;
+ public static final String HOME_WIDGET = "HOME_WIDGET";
/**
* A source identifier indicating that the updatable drawable is used in the launcher
* turn off work button.
*/
- public static final int LAUNCHER_OFF_BUTTON = 3;
+ public static final String LAUNCHER_OFF_BUTTON = "LAUNCHER_OFF_BUTTON";
/**
* A source identifier indicating that the updatable drawable is used in quick settings.
*/
- public static final int QUICK_SETTINGS = 4;
+ public static final String QUICK_SETTINGS = "QUICK_SETTINGS";
/**
* A source identifier indicating that the updatable drawable is used in the status bar.
*/
- public static final int STATUS_BAR = 5;
+ public static final String STATUS_BAR = "STATUS_BAR";
/**
* @hide
*/
- public static final Set<Integer> UPDATABLE_DRAWABLE_SOURCES = buildSourcesSet();
+ public static final Set<String> UPDATABLE_DRAWABLE_SOURCES = buildSourcesSet();
- private static Set<Integer> buildSourcesSet() {
- Set<Integer> sources = new HashSet<>();
+ private static Set<String> buildSourcesSet() {
+ Set<String> sources = new HashSet<>();
sources.add(UNDEFINED);
sources.add(NOTIFICATION);
sources.add(PROFILE_SWITCH_ANIMATION);
@@ -390,31 +390,31 @@
* A style identifier indicating that the updatable drawable should use the default
* style.
*/
- public static final int DEFAULT = -1;
+ public static final String DEFAULT = "DEFAULT";
/**
* A style identifier indicating that the updatable drawable has a solid color fill.
*/
- public static final int SOLID_COLORED = 0;
+ public static final String SOLID_COLORED = "SOLID_COLORED";
/**
* A style identifier indicating that the updatable drawable has a solid non-colored
* fill.
*/
- public static final int SOLID_NOT_COLORED = 1;
+ public static final String SOLID_NOT_COLORED = "SOLID_NOT_COLORED";
/**
* A style identifier indicating that the updatable drawable is an outline.
*/
- public static final int OUTLINE = 2;
+ public static final String OUTLINE = "OUTLINE";
/**
* @hide
*/
- public static final Set<Integer> UPDATABLE_DRAWABLE_STYLES = buildStylesSet();
+ public static final Set<String> UPDATABLE_DRAWABLE_STYLES = buildStylesSet();
- private static Set<Integer> buildStylesSet() {
- Set<Integer> styles = new HashSet<>();
+ private static Set<String> buildStylesSet() {
+ Set<String> styles = new HashSet<>();
styles.add(DEFAULT);
styles.add(SOLID_COLORED);
styles.add(SOLID_NOT_COLORED);
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index f663c17..7525d54 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -24,6 +24,7 @@
import android.app.IApplicationThread;
import android.app.IServiceConnection;
import android.app.admin.ParcelableGranteeMap;
+import android.app.admin.PreferentialNetworkServiceConfig;
import android.app.admin.StartInstallingUpdateCallback;
import android.app.admin.SystemUpdateInfo;
import android.app.admin.SystemUpdatePolicy;
@@ -47,6 +48,7 @@
import android.security.keymaster.KeymasterCertificateChain;
import android.security.keystore.ParcelableKeyGenParameterSpec;
import android.telephony.data.ApnSetting;
+import com.android.internal.infra.AndroidFuture;
import java.util.List;
@@ -119,6 +121,8 @@
FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(in ComponentName who);
boolean isFactoryResetProtectionPolicySupported();
+ void sendLostModeLocationUpdate(in AndroidFuture<boolean> future);
+
ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList);
ComponentName getGlobalProxyAdmin(int userHandle);
void setRecommendedGlobalProxy(in ComponentName admin, in ProxyInfo proxyInfo);
@@ -283,6 +287,10 @@
void setPreferentialNetworkServiceEnabled(in boolean enabled);
boolean isPreferentialNetworkServiceEnabled(int userHandle);
+ void setPreferentialNetworkServiceConfig(
+ in PreferentialNetworkServiceConfig preferentialNetworkServiceConfig);
+ PreferentialNetworkServiceConfig getPreferentialNetworkServiceConfig();
+
void setLockTaskPackages(in ComponentName who, in String[] packages);
String[] getLockTaskPackages(in ComponentName who);
boolean isLockTaskPermitted(in String pkg);
@@ -543,8 +551,8 @@
List<UserHandle> listForegroundAffiliatedUsers();
void setDrawables(in List<DevicePolicyDrawableResource> drawables);
- void resetDrawables(in int[] drawableIds);
- ParcelableResource getDrawable(int drawableId, int drawableStyle, int drawableSource);
+ void resetDrawables(in String[] drawableIds);
+ ParcelableResource getDrawable(String drawableId, String drawableStyle, String drawableSource);
void setStrings(in List<DevicePolicyStringResource> strings);
void resetStrings(in String[] stringIds);
diff --git a/core/java/android/app/admin/PreferentialNetworkServiceConfig.aidl b/core/java/android/app/admin/PreferentialNetworkServiceConfig.aidl
new file mode 100644
index 0000000..6b6ee7d
--- /dev/null
+++ b/core/java/android/app/admin/PreferentialNetworkServiceConfig.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2022, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.app.admin;
+
+parcelable PreferentialNetworkServiceConfig;
\ No newline at end of file
diff --git a/core/java/android/app/admin/PreferentialNetworkServiceConfig.java b/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
new file mode 100644
index 0000000..2849139
--- /dev/null
+++ b/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Network configuration to be set for the user profile
+ * {@see DevicePolicyManager#setPreferentialNetworkServiceConfig}.
+ */
+public final class PreferentialNetworkServiceConfig implements Parcelable {
+ final boolean mIsEnabled;
+ final int mNetworkId;
+ final boolean mAllowFallbackToDefaultConnection;
+ final int[] mIncludedUids;
+ final int[] mExcludedUids;
+
+ /** @hide */
+ public static final PreferentialNetworkServiceConfig DEFAULT =
+ (new PreferentialNetworkServiceConfig.Builder()).build();
+
+ /**
+ * Preferential network identifier 1.
+ */
+ public static final int PREFERENTIAL_NETWORK_ID_1 = 1;
+
+ /**
+ * Preferential network identifier 2.
+ */
+ public static final int PREFERENTIAL_NETWORK_ID_2 = 2;
+
+ /**
+ * Preferential network identifier 3.
+ */
+ public static final int PREFERENTIAL_NETWORK_ID_3 = 3;
+
+ /**
+ * Preferential network identifier 4.
+ */
+ public static final int PREFERENTIAL_NETWORK_ID_4 = 4;
+
+ /**
+ * Preferential network identifier 5.
+ */
+ public static final int PREFERENTIAL_NETWORK_ID_5 = 5;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "PREFERENTIAL_NETWORK_ID_" }, value = {
+ PREFERENTIAL_NETWORK_ID_1,
+ PREFERENTIAL_NETWORK_ID_2,
+ PREFERENTIAL_NETWORK_ID_3,
+ PREFERENTIAL_NETWORK_ID_4,
+ PREFERENTIAL_NETWORK_ID_5,
+ })
+
+ public @interface PreferentialNetworkPreferenceId {
+ }
+
+ private PreferentialNetworkServiceConfig(boolean isEnabled,
+ boolean allowFallbackToDefaultConnection, int[] includedUids,
+ int[] excludedUids, @PreferentialNetworkPreferenceId int networkId) {
+ mIsEnabled = isEnabled;
+ mAllowFallbackToDefaultConnection = allowFallbackToDefaultConnection;
+ mIncludedUids = includedUids;
+ mExcludedUids = excludedUids;
+ mNetworkId = networkId;
+ }
+
+ private PreferentialNetworkServiceConfig(Parcel in) {
+ mIsEnabled = in.readBoolean();
+ mAllowFallbackToDefaultConnection = in.readBoolean();
+ mNetworkId = in.readInt();
+ mIncludedUids = in.createIntArray();
+ mExcludedUids = in.createIntArray();
+ }
+
+ /**
+ * Is the preferential network enabled.
+ * @return true if enabled else false
+ */
+ public boolean isEnabled() {
+ return mIsEnabled;
+ }
+
+ /**
+ * is fallback to default network allowed. This boolean configures whether default connection
+ * (default internet or wifi) should be used or not if a preferential network service
+ * connection is not available.
+ * @return true if fallback is allowed, else false.
+ */
+ public boolean isFallbackToDefaultConnectionAllowed() {
+ return mAllowFallbackToDefaultConnection;
+ }
+
+ /**
+ * Get the array of uids that are applicable for the profile preference.
+ *
+ * {@see #getExcludedUids()}
+ * Included UIDs and Excluded UIDs can't both be non-empty.
+ * if both are empty, it means this request applies to all uids in the user profile.
+ * if included is not empty, then only included UIDs are applied.
+ * if excluded is not empty, then it is all uids in the user profile except these UIDs.
+ * @return Array of uids applicable for the profile preference.
+ * Empty array would mean that this request applies to all uids in the profile.
+ */
+ public @NonNull int[] getIncludedUids() {
+ return mIncludedUids;
+ }
+
+ /**
+ * Get the array of uids that are excluded for the profile preference.
+ *
+ * {@see #getIncludedUids()}
+ * Included UIDs and Excluded UIDs can't both be non-empty.
+ * if both are empty, it means this request applies to all uids in the user profile.
+ * if included is not empty, then only included UIDs are applied.
+ * if excluded is not empty, then it is all uids in the user profile except these UIDs.
+ * @return Array of uids that are excluded for the profile preference.
+ * Empty array would mean that this request applies to all uids in the profile.
+ */
+ public @NonNull int[] getExcludedUids() {
+ return mExcludedUids;
+ }
+
+ /**
+ * @return preference enterprise identifier.
+ * valid values starts from
+ * {@link #PREFERENTIAL_NETWORK_ID_1} to {@link #PREFERENTIAL_NETWORK_ID_5}.
+ * preference identifier is applicable only if preference network service is enabled
+ *
+ */
+ public @PreferentialNetworkPreferenceId int getNetworkId() {
+ return mNetworkId;
+ }
+
+ @Override
+ public String toString() {
+ return "PreferentialNetworkServiceConfig{"
+ + "mIsEnabled=" + isEnabled()
+ + "mAllowFallbackToDefaultConnection=" + isFallbackToDefaultConnectionAllowed()
+ + "mIncludedUids=" + mIncludedUids.toString()
+ + "mExcludedUids=" + mExcludedUids.toString()
+ + "mNetworkId=" + mNetworkId
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ final PreferentialNetworkServiceConfig that = (PreferentialNetworkServiceConfig) o;
+ return mIsEnabled == that.mIsEnabled
+ && mAllowFallbackToDefaultConnection == that.mAllowFallbackToDefaultConnection
+ && mNetworkId == that.mNetworkId
+ && Objects.equals(mIncludedUids, that.mIncludedUids)
+ && Objects.equals(mExcludedUids, that.mExcludedUids);
+ }
+
+ @Override
+ public int hashCode() {
+ return ((Objects.hashCode(mIsEnabled) * 17)
+ + (Objects.hashCode(mAllowFallbackToDefaultConnection) * 19)
+ + (Objects.hashCode(mIncludedUids) * 23)
+ + (Objects.hashCode(mExcludedUids) * 29)
+ + mNetworkId * 31);
+ }
+
+ /**
+ * Builder used to create {@link PreferentialNetworkServiceConfig} objects.
+ * Specify the preferred Network preference
+ */
+ public static final class Builder {
+ boolean mIsEnabled = false;
+ int mNetworkId = 0;
+ boolean mAllowFallbackToDefaultConnection = true;
+ int[] mIncludedUids = new int[0];
+ int[] mExcludedUids = new int[0];
+
+ /**
+ * Constructs an empty Builder with preferential network disabled by default.
+ */
+ public Builder() {}
+
+ /**
+ * Set the preferential network service enabled state.
+ * Default value is false.
+ * @param isEnabled the desired network preference to use, true to enable else false
+ * @return The builder to facilitate chaining.
+ */
+ @NonNull
+ public PreferentialNetworkServiceConfig.Builder setEnabled(boolean isEnabled) {
+ mIsEnabled = isEnabled;
+ return this;
+ }
+
+ /**
+ * Set whether the default connection should be used as fallback.
+ * This boolean configures whether the default connection (default internet or wifi)
+ * should be used if a preferential network service connection is not available.
+ * Default value is true
+ * @param allowFallbackToDefaultConnection true if fallback is allowed else false
+ * @return The builder to facilitate chaining.
+ */
+ @NonNull
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public PreferentialNetworkServiceConfig.Builder setFallbackToDefaultConnectionAllowed(
+ boolean allowFallbackToDefaultConnection) {
+ mAllowFallbackToDefaultConnection = allowFallbackToDefaultConnection;
+ return this;
+ }
+
+ /**
+ * Set the array of uids whose network access will go through this preferential
+ * network service.
+ * {@see #setExcludedUids(int[])}
+ * Included UIDs and Excluded UIDs can't both be non-empty.
+ * if both are empty, it means this request applies to all uids in the user profile.
+ * if included is not empty, then only included UIDs are applied.
+ * if excluded is not empty, then it is all uids in the user profile except these UIDs.
+ * @param uids array of included uids
+ * @return The builder to facilitate chaining.
+ */
+ @NonNull
+ public PreferentialNetworkServiceConfig.Builder setIncludedUids(
+ @NonNull int[] uids) {
+ Objects.requireNonNull(uids);
+ mIncludedUids = uids;
+ return this;
+ }
+
+ /**
+ * Set the array of uids who are not allowed through this preferential
+ * network service.
+ * {@see #setIncludedUids(int[])}
+ * Included UIDs and Excluded UIDs can't both be non-empty.
+ * if both are empty, it means this request applies to all uids in the user profile.
+ * if included is not empty, then only included UIDs are applied.
+ * if excluded is not empty, then it is all uids in the user profile except these UIDs.
+ * @param uids array of excluded uids
+ * @return The builder to facilitate chaining.
+ */
+ @NonNull
+ public PreferentialNetworkServiceConfig.Builder setExcludedUids(
+ @NonNull int[] uids) {
+ Objects.requireNonNull(uids);
+ mExcludedUids = uids;
+ return this;
+ }
+
+ /**
+ * Returns an instance of {@link PreferentialNetworkServiceConfig} created from the
+ * fields set on this builder.
+ */
+ @NonNull
+ public PreferentialNetworkServiceConfig build() {
+ if (mIncludedUids.length > 0 && mExcludedUids.length > 0) {
+ throw new IllegalStateException("Both includedUids and excludedUids "
+ + "cannot be nonempty");
+ }
+ return new PreferentialNetworkServiceConfig(mIsEnabled,
+ mAllowFallbackToDefaultConnection, mIncludedUids, mExcludedUids, mNetworkId);
+ }
+
+ /**
+ * Set the preferential network identifier.
+ * Valid values starts from {@link #PREFERENTIAL_NETWORK_ID_1} to
+ * {@link #PREFERENTIAL_NETWORK_ID_5}.
+ * preference identifier is applicable only if preferential network service is enabled.
+ * @param preferenceId preference Id
+ * @return The builder to facilitate chaining.
+ */
+ @NonNull
+ public PreferentialNetworkServiceConfig.Builder setNetworkId(
+ @PreferentialNetworkPreferenceId int preferenceId) {
+ if ((preferenceId < PREFERENTIAL_NETWORK_ID_1)
+ || (preferenceId > PREFERENTIAL_NETWORK_ID_5)) {
+ throw new IllegalArgumentException("Invalid preference identifier");
+ }
+ mNetworkId = preferenceId;
+ return this;
+ }
+ }
+
+ @Override
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ dest.writeBoolean(mIsEnabled);
+ dest.writeBoolean(mAllowFallbackToDefaultConnection);
+ dest.writeInt(mNetworkId);
+ dest.writeIntArray(mIncludedUids);
+ dest.writeIntArray(mExcludedUids);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Creator<PreferentialNetworkServiceConfig> CREATOR =
+ new Creator<PreferentialNetworkServiceConfig>() {
+ @Override
+ public PreferentialNetworkServiceConfig[] newArray(int size) {
+ return new PreferentialNetworkServiceConfig[size];
+ }
+
+ @Override
+ public PreferentialNetworkServiceConfig createFromParcel(
+ @NonNull android.os.Parcel in) {
+ return new PreferentialNetworkServiceConfig(in);
+ }
+ };
+}
diff --git a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
new file mode 100644
index 0000000..53af4c5
--- /dev/null
+++ b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.companion.virtual;
+
+import android.content.ComponentName;
+
+/**
+ * Interface to listen for activity changes in a virtual device.
+ *
+ * @hide
+ */
+interface IVirtualDeviceActivityListener {
+
+ /**
+ * Called when the top activity is changed.
+ *
+ * @param displayId The display ID on which the activity change happened.
+ * @param topActivity The component name of the top activity.
+ */
+ void onTopActivityChanged(int displayId, in ComponentName topActivity);
+
+ /**
+ * Called when the display becomes empty (e.g. if the user hits back on the last
+ * activity of the root task).
+ *
+ * @param displayId The display ID that became empty.
+ */
+ void onDisplayEmpty(int displayId);
+}
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index d80bee6..b7f826a 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -17,6 +17,7 @@
package android.companion.virtual;
import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.VirtualDeviceParams;
/**
@@ -39,5 +40,5 @@
*/
IVirtualDevice createVirtualDevice(
in IBinder token, String packageName, int associationId,
- in VirtualDeviceParams params);
+ in VirtualDeviceParams params, in IVirtualDeviceActivityListener activityListener);
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index c723468..64f16ac 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -25,6 +25,7 @@
import android.app.Activity;
import android.app.PendingIntent;
import android.companion.AssociationInfo;
+import android.content.ComponentName;
import android.content.Context;
import android.graphics.Point;
import android.hardware.display.DisplayManager;
@@ -40,6 +41,7 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.util.ArrayMap;
import android.view.Surface;
import java.util.concurrent.Executor;
@@ -87,9 +89,7 @@
int associationId,
@NonNull VirtualDeviceParams params) {
try {
- IVirtualDevice virtualDevice = mService.createVirtualDevice(
- new Binder(), mContext.getPackageName(), associationId, params);
- return new VirtualDevice(mContext, virtualDevice);
+ return new VirtualDevice(mService, mContext, associationId, params);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -106,10 +106,49 @@
private final Context mContext;
private final IVirtualDevice mVirtualDevice;
+ private final ArrayMap<ActivityListener, ActivityListenerDelegate> mActivityListeners =
+ new ArrayMap<>();
+ private final IVirtualDeviceActivityListener mActivityListenerBinder =
+ new IVirtualDeviceActivityListener.Stub() {
- private VirtualDevice(Context context, IVirtualDevice virtualDevice) {
+ @Override
+ public void onTopActivityChanged(int displayId, ComponentName topActivity) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ for (int i = 0; i < mActivityListeners.size(); i++) {
+ mActivityListeners.valueAt(i)
+ .onTopActivityChanged(displayId, topActivity);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void onDisplayEmpty(int displayId) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ for (int i = 0; i < mActivityListeners.size(); i++) {
+ mActivityListeners.valueAt(i).onDisplayEmpty(displayId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ };
+
+ private VirtualDevice(
+ IVirtualDeviceManager service,
+ Context context,
+ int associationId,
+ VirtualDeviceParams params) throws RemoteException {
mContext = context.getApplicationContext();
- mVirtualDevice = virtualDevice;
+ mVirtualDevice = service.createVirtualDevice(
+ new Binder(),
+ mContext.getPackageName(),
+ associationId,
+ params,
+ mActivityListenerBinder);
}
/**
@@ -137,7 +176,7 @@
mVirtualDevice.launchPendingIntent(
displayId,
pendingIntent,
- new ResultReceiver(new Handler(Looper.myLooper())) {
+ new ResultReceiver(new Handler(Looper.getMainLooper())) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
super.onReceiveResult(resultCode, resultData);
@@ -323,6 +362,47 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Adds an activity listener to listen for events such as top activity change or virtual
+ * display task stack became empty.
+ *
+ * @param listener The listener to add.
+ * @see #removeActivityListener(ActivityListener)
+ * @hide
+ */
+ // TODO(b/194949534): Unhide this API
+ public void addActivityListener(@NonNull ActivityListener listener) {
+ addActivityListener(listener, mContext.getMainExecutor());
+ }
+
+ /**
+ * Adds an activity listener to listen for events such as top activity change or virtual
+ * display task stack became empty.
+ *
+ * @param listener The listener to add.
+ * @param executor The executor where the callback is executed on.
+ * @see #removeActivityListener(ActivityListener)
+ * @hide
+ */
+ // TODO(b/194949534): Unhide this API
+ public void addActivityListener(
+ @NonNull ActivityListener listener, @NonNull Executor executor) {
+ mActivityListeners.put(listener, new ActivityListenerDelegate(listener, executor));
+ }
+
+ /**
+ * Removes an activity listener previously added with
+ * {@link #addActivityListener}.
+ *
+ * @param listener The listener to remove.
+ * @see #addActivityListener(ActivityListener, Executor)
+ * @hide
+ */
+ // TODO(b/194949534): Unhide this API
+ public void removeActivityListener(@NonNull ActivityListener listener) {
+ mActivityListeners.remove(listener);
+ }
}
/**
@@ -342,4 +422,50 @@
*/
void onLaunchFailed();
}
+
+ /**
+ * Listener for activity changes in this virtual device.
+ *
+ * @hide
+ */
+ // TODO(b/194949534): Unhide this API
+ public interface ActivityListener {
+
+ /**
+ * Called when the top activity is changed.
+ *
+ * @param displayId The display ID on which the activity change happened.
+ * @param topActivity The component name of the top activity.
+ */
+ void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity);
+
+ /**
+ * Called when the display becomes empty (e.g. if the user hits back on the last
+ * activity of the root task).
+ *
+ * @param displayId The display ID that became empty.
+ */
+ void onDisplayEmpty(int displayId);
+ }
+
+ /**
+ * A wrapper for {@link ActivityListener} that executes callbacks on the given executor.
+ */
+ private static class ActivityListenerDelegate {
+ @NonNull private final ActivityListener mActivityListener;
+ @NonNull private final Executor mExecutor;
+
+ ActivityListenerDelegate(@NonNull ActivityListener listener, @NonNull Executor executor) {
+ mActivityListener = listener;
+ mExecutor = executor;
+ }
+
+ public void onTopActivityChanged(int displayId, ComponentName topActivity) {
+ mExecutor.execute(() -> mActivityListener.onTopActivityChanged(displayId, topActivity));
+ }
+
+ public void onDisplayEmpty(int displayId) {
+ mExecutor.execute(() -> mActivityListener.onDisplayEmpty(displayId));
+ }
+ }
}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index b4f2302..5584f45 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.os.Process.myUserHandle;
import static android.os.Trace.TRACE_TAG_DATABASE;
import android.annotation.NonNull;
@@ -738,7 +739,7 @@
}
boolean checkUser(int pid, int uid, Context context) {
- int callingUserId = UserHandle.getUserId(uid);
+ final int callingUserId = UserHandle.getUserId(uid);
if (callingUserId == context.getUserId() || mSingleUser) {
return true;
@@ -765,8 +766,8 @@
if (um != null && um.isCloneProfile()) {
UserHandle parent = um.getProfileParent(callingUser);
- if (parent != null && parent.equals(context.getUser())) {
- mUsersRedirectedToOwner.put(callingUser.getIdentifier(), true);
+ if (parent != null && parent.equals(myUserHandle())) {
+ mUsersRedirectedToOwner.put(callingUserId, true);
return true;
}
}
@@ -774,7 +775,7 @@
// ignore
}
- mUsersRedirectedToOwner.put(UserHandle.getUserId(uid), false);
+ mUsersRedirectedToOwner.put(callingUserId, false);
return false;
}
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 6ae768a..98ced6d 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -24,6 +24,9 @@
import android.annotation.UiContext;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -47,12 +50,16 @@
import android.view.WindowManager.LayoutParams.WindowType;
import android.view.autofill.AutofillManager.AutofillClient;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
@@ -66,6 +73,31 @@
@UnsupportedAppUsage
Context mBase;
+ /**
+ * After {@link Build.VERSION_CODES#TIRAMISU},
+ * {@link #registerComponentCallbacks(ComponentCallbacks)} will delegate to
+ * {@link #getBaseContext()} instead of {@link #getApplicationContext()}.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ @VisibleForTesting
+ static final long COMPONENT_CALLBACK_ON_WRAPPER = 193247900L;
+
+ /**
+ * A list to store {@link ComponentCallbacks} which
+ * passes to {@link #registerComponentCallbacks(ComponentCallbacks)} before
+ * {@link #attachBaseContext(Context)}.
+ * It is to provide compatibility behavior for Application targeted prior to
+ * {@link Build.VERSION_CODES#TIRAMISU}.
+ *
+ * @hide
+ */
+ @GuardedBy("mLock")
+ @VisibleForTesting
+ public List<ComponentCallbacks> mCallbacksRegisteredToSuper;
+
+ private final Object mLock = new Object();
+
public ContextWrapper(Context base) {
mBase = base;
}
@@ -1301,4 +1333,77 @@
}
return mBase.isConfigurationContext();
}
+
+ /**
+ * Add a new {@link ComponentCallbacks} to the base application of the
+ * Context, which will be called at the same times as the ComponentCallbacks
+ * methods of activities and other components are called. Note that you
+ * <em>must</em> be sure to use {@link #unregisterComponentCallbacks} when
+ * appropriate in the future; this will not be removed for you.
+ * <p>
+ * After {@link Build.VERSION_CODES#TIRAMISU}, the {@link ComponentCallbacks} will be registered
+ * to {@link #getBaseContext() the base Context}, and can be only used after
+ * {@link #attachBaseContext(Context)}. Users can still call to
+ * {@code getApplicationContext().registerComponentCallbacks(ComponentCallbacks)} to add
+ * {@link ComponentCallbacks} to the base application.
+ *
+ * @param callback The interface to call. This can be either a
+ * {@link ComponentCallbacks} or {@link ComponentCallbacks2} interface.
+ * @throws IllegalStateException if this method calls before {@link #attachBaseContext(Context)}
+ */
+ @Override
+ public void registerComponentCallbacks(ComponentCallbacks callback) {
+ if (mBase != null) {
+ mBase.registerComponentCallbacks(callback);
+ } else if (!CompatChanges.isChangeEnabled(COMPONENT_CALLBACK_ON_WRAPPER)) {
+ super.registerComponentCallbacks(callback);
+ synchronized (mLock) {
+ // Also register ComponentCallbacks to ContextWrapper, so we can find the correct
+ // Context to unregister it for compatibility.
+ if (mCallbacksRegisteredToSuper == null) {
+ mCallbacksRegisteredToSuper = new ArrayList<>();
+ }
+ mCallbacksRegisteredToSuper.add(callback);
+ }
+ } else {
+ // Throw exception for Application targeting T+
+ throw new IllegalStateException("ComponentCallbacks must be registered after "
+ + "this ContextWrapper is attached to a base Context.");
+ }
+ }
+
+ /**
+ * Remove a {@link ComponentCallbacks} object that was previously registered
+ * with {@link #registerComponentCallbacks(ComponentCallbacks)}.
+ * <p>
+ * After {@link Build.VERSION_CODES#TIRAMISU}, the {@link ComponentCallbacks} will be
+ * unregistered to {@link #getBaseContext() the base Context}, and can be only used after
+ * {@link #attachBaseContext(Context)}
+ * </p>
+ *
+ * @param callback The interface to call. This can be either a
+ * {@link ComponentCallbacks} or {@link ComponentCallbacks2} interface.
+ * @throws IllegalStateException if this method calls before {@link #attachBaseContext(Context)}
+ */
+ @Override
+ public void unregisterComponentCallbacks(ComponentCallbacks callback) {
+ // It usually means the ComponentCallbacks is registered before this ContextWrapper attaches
+ // to a base Context and Application is targeting prior to S-v2. We should unregister the
+ // ComponentCallbacks to the Application Context instead to prevent leak.
+ synchronized (mLock) {
+ if (mCallbacksRegisteredToSuper != null
+ && mCallbacksRegisteredToSuper.contains(callback)) {
+ super.unregisterComponentCallbacks(callback);
+ mCallbacksRegisteredToSuper.remove(callback);
+ } else if (mBase != null) {
+ mBase.unregisterComponentCallbacks(callback);
+ } else if (CompatChanges.isChangeEnabled(COMPONENT_CALLBACK_ON_WRAPPER)) {
+ // Throw exception for Application that is targeting S-v2+
+ throw new IllegalStateException("ComponentCallbacks must be unregistered after "
+ + "this ContextWrapper is attached to a base Context.");
+ }
+ }
+ // Do nothing if the callback hasn't been registered to Application Context by
+ // super.unregisterComponentCallbacks() for Application that is targeting prior to T.
+ }
}
diff --git a/core/java/android/content/TEST_MAPPING b/core/java/android/content/TEST_MAPPING
index 7f1d0d1..5bb845d 100644
--- a/core/java/android/content/TEST_MAPPING
+++ b/core/java/android/content/TEST_MAPPING
@@ -32,6 +32,9 @@
},
{
"include-filter": "android.content.ComponentCallbacksControllerTest"
+ },
+ {
+ "include-filter": "android.content.ContextWrapperTest"
}
],
"file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java", "(/|^)ComponentCallbacksController.java"]
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 1021d3e..5b0c275 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -329,7 +329,8 @@
* @hide
*/
public Bundle toBundle(Bundle outBundle) {
- final Bundle b = outBundle == null ? new Bundle() : outBundle;
+ final Bundle b = outBundle == null || outBundle == Bundle.EMPTY
+ ? new Bundle() : outBundle;
if (mType == TYPE_BOOLEAN) {
b.putBoolean(mName, mBooleanValue);
} else if (mType == TYPE_FLOAT) {
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index d5498a0..165cae8 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -78,6 +78,7 @@
public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
private static final int PARSE_IS_SYSTEM_DIR = 1 << 4;
private static final int PARSE_COLLECT_CERTIFICATES = 1 << 5;
+ private static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8;
private static final String TAG_APPLICATION = "application";
private static final String TAG_PACKAGE_VERIFIER = "package-verifier";
private static final String TAG_PROFILEABLE = "profileable";
@@ -101,7 +102,7 @@
public static ParseResult<PackageLite> parsePackageLite(ParseInput input,
File packageFile, int flags) {
if (packageFile.isDirectory()) {
- return parseClusterPackageLite(input, packageFile, flags);
+ return parseClusterPackageLite(input, packageFile, /* frameworkSplits= */ null, flags);
} else {
return parseMonolithicPackageLite(input, packageFile, flags);
}
@@ -134,21 +135,44 @@
/**
* Parse lightweight details about a directory of APKs.
+ *
+ * @param packageDirOrApk is the folder that contains split apks for a regular app or the
+ * framework-res.apk for framwork-res splits (in which case the
+ * splits come in the <code>frameworkSplits</code> parameter)
*/
public static ParseResult<PackageLite> parseClusterPackageLite(ParseInput input,
- File packageDir, int flags) {
- final File[] files = packageDir.listFiles();
- if (ArrayUtils.isEmpty(files)) {
- return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
- "No packages found in split");
+ File packageDirOrApk, List<File> frameworkSplits, int flags) {
+ final File[] files;
+ final boolean parsingFrameworkSplits = (flags & PARSE_FRAMEWORK_RES_SPLITS) != 0;
+ if (parsingFrameworkSplits) {
+ if (ArrayUtils.isEmpty(frameworkSplits)) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
+ "No packages found in split");
+ }
+ files = frameworkSplits.toArray(new File[frameworkSplits.size() + 1]);
+ // we also want to process the base apk so add it to the array
+ files[files.length - 1] = packageDirOrApk;
+ } else {
+ files = packageDirOrApk.listFiles();
+ if (ArrayUtils.isEmpty(files)) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
+ "No packages found in split");
+ }
+ // Apk directory is directly nested under the current directory
+ if (files.length == 1 && files[0].isDirectory()) {
+ return parseClusterPackageLite(input, files[0], frameworkSplits, flags);
+ }
}
- // Apk directory is directly nested under the current directory
- if (files.length == 1 && files[0].isDirectory()) {
- return parseClusterPackageLite(input, files[0], flags);
+
+ if (parsingFrameworkSplits) {
+ // disable the flag for checking the certificates of the splits. We know they
+ // won't match, but we rely on the mainline apex to be safe if it was installed
+ flags = flags & ~PARSE_COLLECT_CERTIFICATES;
}
String packageName = null;
int versionCode = 0;
+ ApkLite baseApk = null;
final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
@@ -161,6 +185,10 @@
}
final ApkLite lite = result.getResult();
+ if (parsingFrameworkSplits && file == files[files.length - 1]) {
+ baseApk = lite;
+ break;
+ }
// Assert that all package names and version codes are
// consistent with the first one we encounter.
if (packageName == null) {
@@ -172,7 +200,8 @@
"Inconsistent package " + lite.getPackageName() + " in " + file
+ "; expected " + packageName);
}
- if (versionCode != lite.getVersionCode()) {
+ // we allow version codes that do not match for framework splits
+ if (!parsingFrameworkSplits && versionCode != lite.getVersionCode()) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Inconsistent version " + lite.getVersionCode() + " in " + file
+ "; expected " + versionCode);
@@ -187,12 +216,15 @@
}
}
}
+ // baseApk is set in the last iteration of the for each loop when we are parsing
+ // frameworkRes splits or needs to be done now otherwise
+ if (!parsingFrameworkSplits) {
+ baseApk = apks.remove(null);
+ }
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
-
- final ApkLite baseApk = apks.remove(null);
- return composePackageLiteFromApks(input, packageDir, baseApk, apks);
+ return composePackageLiteFromApks(input, packageDirOrApk, baseApk, apks);
}
/**
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 2ac194b..0304815 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -50,6 +50,10 @@
// Reports whether the hardware supports the given keys; returns true if successful
boolean hasKeys(int deviceId, int sourceMask, in int[] keyCodes, out boolean[] keyExists);
+ // Returns the keyCode produced when pressing the key at the specified location, given the
+ // active keyboard layout.
+ int getKeyCodeForKeyLocation(int deviceId, in int locationKeyCode);
+
// Temporarily changes the pointer speed.
void tryPointerSpeed(int speed);
diff --git a/core/java/android/hardware/input/InputDeviceIdentifier.java b/core/java/android/hardware/input/InputDeviceIdentifier.java
index c673e7a..a5b9a2a 100644
--- a/core/java/android/hardware/input/InputDeviceIdentifier.java
+++ b/core/java/android/hardware/input/InputDeviceIdentifier.java
@@ -16,7 +16,9 @@
package android.hardware.input;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -28,12 +30,13 @@
*
* @hide
*/
+@TestApi
public final class InputDeviceIdentifier implements Parcelable {
private final String mDescriptor;
private final int mVendorId;
private final int mProductId;
- public InputDeviceIdentifier(String descriptor, int vendorId, int productId) {
+ public InputDeviceIdentifier(@NonNull String descriptor, int vendorId, int productId) {
this.mDescriptor = descriptor;
this.mVendorId = vendorId;
this.mProductId = productId;
@@ -51,12 +54,13 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mDescriptor);
dest.writeInt(mVendorId);
dest.writeInt(mProductId);
}
+ @NonNull
public String getDescriptor() {
return mDescriptor;
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index ef349a9..cbc8373 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -58,6 +58,7 @@
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputMonitor;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.VerifiedInputEvent;
@@ -637,6 +638,30 @@
}
/**
+ * Returns the descriptors of all supported keyboard layouts appropriate for the specified
+ * input device.
+ * <p>
+ * The input manager consults the built-in keyboard layouts as well as all keyboard layouts
+ * advertised by applications using a {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver.
+ * </p>
+ *
+ * @param device The input device to query.
+ * @return The ids of all keyboard layouts which are supported by the specified input device.
+ *
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public List<String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull InputDevice device) {
+ KeyboardLayout[] layouts = getKeyboardLayoutsForInputDevice(device.getIdentifier());
+ List<String> res = new ArrayList<>();
+ for (KeyboardLayout kl : layouts) {
+ res.add(kl.getDescriptor());
+ }
+ return res;
+ }
+
+ /**
* Gets information about all supported keyboard layouts appropriate
* for a specific input device.
* <p>
@@ -650,7 +675,9 @@
*
* @hide
*/
- public KeyboardLayout[] getKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
+ @NonNull
+ public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
+ @NonNull InputDeviceIdentifier identifier) {
try {
return mIm.getKeyboardLayoutsForInputDevice(identifier);
} catch (RemoteException ex) {
@@ -680,15 +707,17 @@
}
/**
- * Gets the current keyboard layout descriptor for the specified input
- * device.
+ * Gets the current keyboard layout descriptor for the specified input device.
*
* @param identifier Identifier for the input device
- * @return The keyboard layout descriptor, or null if no keyboard layout has
- * been set.
+ * @return The keyboard layout descriptor, or null if no keyboard layout has been set.
+ *
* @hide
*/
- public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
+ @TestApi
+ @Nullable
+ public String getCurrentKeyboardLayoutForInputDevice(
+ @NonNull InputDeviceIdentifier identifier) {
try {
return mIm.getCurrentKeyboardLayoutForInputDevice(identifier);
} catch (RemoteException ex) {
@@ -697,20 +726,21 @@
}
/**
- * Sets the current keyboard layout descriptor for the specified input
- * device.
+ * Sets the current keyboard layout descriptor for the specified input device.
* <p>
- * This method may have the side-effect of causing the input device in
- * question to be reconfigured.
+ * This method may have the side-effect of causing the input device in question to be
+ * reconfigured.
* </p>
*
* @param identifier The identifier for the input device.
- * @param keyboardLayoutDescriptor The keyboard layout descriptor to use,
- * must not be null.
+ * @param keyboardLayoutDescriptor The keyboard layout descriptor to use, must not be null.
+ *
* @hide
*/
- public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor) {
+ @TestApi
+ @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
+ public void setCurrentKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
+ @NonNull String keyboardLayoutDescriptor) {
if (identifier == null) {
throw new IllegalArgumentException("identifier must not be null");
}
@@ -727,11 +757,11 @@
}
/**
- * Gets all keyboard layout descriptors that are enabled for the specified
- * input device.
+ * Gets all keyboard layout descriptors that are enabled for the specified input device.
*
* @param identifier The identifier for the input device.
* @return The keyboard layout descriptors.
+ *
* @hide
*/
public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
@@ -749,15 +779,16 @@
/**
* Adds the keyboard layout descriptor for the specified input device.
* <p>
- * This method may have the side-effect of causing the input device in
- * question to be reconfigured.
+ * This method may have the side-effect of causing the input device in question to be
+ * reconfigured.
* </p>
*
* @param identifier The identifier for the input device.
- * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to
- * add.
+ * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to add.
+ *
* @hide
*/
+ @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
String keyboardLayoutDescriptor) {
if (identifier == null) {
@@ -777,17 +808,19 @@
/**
* Removes the keyboard layout descriptor for the specified input device.
* <p>
- * This method may have the side-effect of causing the input device in
- * question to be reconfigured.
+ * This method may have the side-effect of causing the input device in question to be
+ * reconfigured.
* </p>
*
* @param identifier The identifier for the input device.
- * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to
- * remove.
+ * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to remove.
+ *
* @hide
*/
- public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor) {
+ @TestApi
+ @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
+ public void removeKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
+ @NonNull String keyboardLayoutDescriptor) {
if (identifier == null) {
throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
}
@@ -1044,6 +1077,27 @@
return ret;
}
+ /**
+ * Gets the key code produced by the specified location on a US keyboard layout.
+ * Key code as defined in {@link android.view.KeyEvent}.
+ * This API is only functional for devices with {@link InputDevice#SOURCE_KEYBOARD} available
+ * which can alter their key mapping using country specific keyboard layouts.
+ *
+ * @param deviceId The input device id.
+ * @param locationKeyCode The location of a key on a US keyboard layout.
+ * @return The key code produced when pressing the key at the specified location, given the
+ * active keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the requested
+ * mapping could not be determined, or if an error occurred.
+ * @hide
+ */
+ public int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode) {
+ try {
+ return mIm.getKeyCodeForKeyLocation(deviceId, locationKeyCode);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Injects an input event into the event system on behalf of an application.
diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java
index f866a2e..ad6c12b 100644
--- a/core/java/android/hardware/input/InputManagerInternal.java
+++ b/core/java/android/hardware/input/InputManagerInternal.java
@@ -90,6 +90,12 @@
public abstract PointF getCursorPosition();
/**
+ * Sets the pointer acceleration.
+ * See {@code frameworks/native/include/input/VelocityControl.h#VelocityControlParameters}.
+ */
+ public abstract void setPointerAcceleration(float acceleration);
+
+ /**
* Sets the eligibility of windows on a given display for pointer capture. If a display is
* marked ineligible, requests to enable pointer capture for windows on that display will be
* ignored.
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 1d0837e..459dab1 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -139,6 +139,9 @@
/* Set USB data on or off */
boolean enableUsbData(in String portId, boolean enable, int operationId, in IUsbOperationInternal callback);
+ /* Enable USB data when disabled due to docking event */
+ void enableUsbDataWhileDocked(in String portId, int operationId, in IUsbOperationInternal callback);
+
/* Gets the USB Hal Version. */
int getUsbHalVersion();
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index eb3e84d..f0e040e 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -1359,6 +1359,35 @@
}
/**
+ * Should only be called by {@link UsbPort#enableUsbDataWhileDocked}.
+ * <p>
+ * Enables or disables USB data when disabled due to docking event.
+ *
+ * @param port USB port for which USB data needs to be enabled.
+ * @param operationId operationId for the request.
+ * @param callback callback object to be invoked when the operation is complete.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_USB)
+ void enableUsbDataWhileDocked(@NonNull UsbPort port, int operationId,
+ IUsbOperationInternal callback) {
+ Objects.requireNonNull(port, "enableUsbDataWhileDocked: port must not be null. opId:"
+ + operationId);
+ try {
+ mService.enableUsbDataWhileDocked(port.getId(), operationId, callback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "enableUsbDataWhileDocked: failed. opId:" + operationId, e);
+ try {
+ callback.onOperationComplete(UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL);
+ } catch (RemoteException r) {
+ Log.e(TAG, "enableUsbDataWhileDocked: failed to call onOperationComplete. opId:"
+ + operationId, r);
+ }
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Sets the component that will handle USB device connection.
* <p>
* Setting component allows to specify external USB host manager to handle use cases, where
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
index e908c24..bef4dea 100644
--- a/core/java/android/hardware/usb/UsbPort.java
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -33,9 +33,19 @@
import static android.hardware.usb.UsbPortStatus.MODE_DUAL;
import static android.hardware.usb.UsbPortStatus.MODE_NONE;
import static android.hardware.usb.UsbPortStatus.MODE_UFP;
+import static android.hardware.usb.UsbPortStatus.POWER_BRICK_STATUS_DISCONNECTED;
+import static android.hardware.usb.UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN;
+import static android.hardware.usb.UsbPortStatus.POWER_BRICK_STATUS_CONNECTED;
import static android.hardware.usb.UsbPortStatus.POWER_ROLE_NONE;
import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK;
import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE;
+import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_UNKNOWN;
+import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_ENABLED;
+import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_DISABLED_OVERHEAT;
+import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_DISABLED_CONTAMINANT;
+import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_DISABLED_DOCK;
+import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_DISABLED_FORCE;
+import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_DISABLED_DEBUG;
import android.Manifest;
import android.annotation.CheckResult;
@@ -154,6 +164,48 @@
@Retention(RetentionPolicy.SOURCE)
@interface EnableLimitPowerTransferStatus{}
+ /**
+ * The {@link #enableUsbDataWhileDocked} request was successfully completed.
+ */
+ public static final int ENABLE_USB_DATA_WHILE_DOCKED_SUCCESS = 0;
+
+ /**
+ * The {@link #enableUsbDataWhileDocked} request failed due to internal error.
+ */
+ public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_INTERNAL = 1;
+
+ /**
+ * The {@link #enableUsbDataWhileDocked} request failed as it's not supported.
+ */
+ public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_NOT_SUPPORTED = 2;
+
+ /**
+ * The {@link #enableUsbDataWhileDocked} request failed as port id mismatched.
+ */
+ public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_PORT_MISMATCH = 3;
+
+ /**
+ * The {@link #enableUsbDataWhileDocked} request failed as data is still enabled.
+ */
+ public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_DATA_ENABLED = 4;
+
+ /**
+ * The {@link #enableUsbDataWhileDocked} request failed due to other reasons.
+ */
+ public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_OTHER = 5;
+
+ /** @hide */
+ @IntDef(prefix = { "ENABLE_USB_DATA_WHILE_DOCKED_" }, value = {
+ ENABLE_USB_DATA_WHILE_DOCKED_SUCCESS,
+ ENABLE_USB_DATA_WHILE_DOCKED_ERROR_INTERNAL,
+ ENABLE_USB_DATA_WHILE_DOCKED_ERROR_NOT_SUPPORTED,
+ ENABLE_USB_DATA_WHILE_DOCKED_ERROR_PORT_MISMATCH,
+ ENABLE_USB_DATA_WHILE_DOCKED_ERROR_DATA_ENABLED,
+ ENABLE_USB_DATA_WHILE_DOCKED_ERROR_OTHER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface EnableUsbDataWhileDockedStatus{}
+
/** @hide */
public UsbPort(@NonNull UsbManager usbManager, @NonNull String id, int supportedModes,
int supportedContaminantProtectionModes,
@@ -308,6 +360,51 @@
}
/**
+ * Enables Usb data when disabled due to {@link UsbPort#USB_DATA_STATUS_DISABLED_DOCK}
+ *
+ * @return {@link #ENABLE_USB_DATA_WHILE_DOCKED_SUCCESS} when request completes successfully or
+ * {@link #ENABLE_USB_DATA_WHILE_DOCKED_ERROR_INTERNAL} when request fails due to
+ * internal error or
+ * {@link ENABLE_USB_DATA_WHILE_DOCKED_ERROR_NOT_SUPPORTED} when not supported or
+ * {@link ENABLE_USB_DATA_WHILE_DOCKED_ERROR_PORT_MISMATCH} when request fails due to
+ * port id mismatch or
+ * {@link ENABLE_USB_DATA_WHILE_DOCKED_ERROR_DATA_ENABLED} when request fails as data
+ * is still enabled or
+ * {@link ENABLE_USB_DATA_WHILE_DOCKED_ERROR_OTHER} when fails due to other reasons.
+ */
+ @CheckResult
+ @RequiresPermission(Manifest.permission.MANAGE_USB)
+ public @EnableUsbDataWhileDockedStatus int enableUsbDataWhileDocked() {
+ // UID is added To minimize operationID overlap between two different packages.
+ int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
+ Log.i(TAG, "enableUsbData opId:" + operationId
+ + " callingUid:" + Binder.getCallingUid());
+ UsbPortStatus portStatus = getStatus();
+ if (portStatus != null &&
+ !usbDataStatusToString(portStatus.getUsbDataStatus()).contains("disabled-dock")) {
+ return ENABLE_USB_DATA_WHILE_DOCKED_ERROR_DATA_ENABLED;
+ }
+
+ UsbOperationInternal opCallback =
+ new UsbOperationInternal(operationId, mId);
+ mUsbManager.enableUsbDataWhileDocked(this, operationId, opCallback);
+ opCallback.waitForOperationComplete();
+ int result = opCallback.getStatus();
+ switch (result) {
+ case USB_OPERATION_SUCCESS:
+ return ENABLE_USB_DATA_WHILE_DOCKED_SUCCESS;
+ case USB_OPERATION_ERROR_INTERNAL:
+ return ENABLE_USB_DATA_WHILE_DOCKED_ERROR_INTERNAL;
+ case USB_OPERATION_ERROR_NOT_SUPPORTED:
+ return ENABLE_USB_DATA_WHILE_DOCKED_ERROR_NOT_SUPPORTED;
+ case USB_OPERATION_ERROR_PORT_MISMATCH:
+ return ENABLE_USB_DATA_WHILE_DOCKED_ERROR_PORT_MISMATCH;
+ default:
+ return ENABLE_USB_DATA_WHILE_DOCKED_ERROR_OTHER;
+ }
+ }
+
+ /**
* Limits power transfer In and out of the port.
* <p>
* Disables charging and limits sourcing power(when permitted by the USB spec) until
@@ -446,6 +543,57 @@
}
/** @hide */
+ public static String usbDataStatusToString(int usbDataStatus) {
+ switch (usbDataStatus) {
+ case USB_DATA_STATUS_UNKNOWN:
+ return "unknown";
+ case USB_DATA_STATUS_ENABLED:
+ return "enabled";
+ case USB_DATA_STATUS_DISABLED_OVERHEAT:
+ return "disabled-overheat";
+ case USB_DATA_STATUS_DISABLED_CONTAMINANT:
+ return "disabled-contaminant";
+ case USB_DATA_STATUS_DISABLED_DOCK:
+ return "disabled-dock";
+ case USB_DATA_STATUS_DISABLED_FORCE:
+ return "disabled-force";
+ case USB_DATA_STATUS_DISABLED_DEBUG:
+ return "disabled-debug";
+ default:
+ return Integer.toString(usbDataStatus);
+ }
+ }
+
+ /** @hide */
+ public static String usbDataStatusToString(int[] usbDataStatus) {
+ StringBuilder modeString = new StringBuilder();
+ if (usbDataStatus == null) {
+ return "unknown";
+ }
+ for (int i = 0; i < usbDataStatus.length; i++) {
+ modeString.append(usbDataStatusToString(usbDataStatus[i]));
+ if (i < usbDataStatus.length - 1) {
+ modeString.append(", ");
+ }
+ }
+ return modeString.toString();
+ }
+
+ /** @hide */
+ public static String powerBrickStatusToString(int powerBrickStatus) {
+ switch (powerBrickStatus) {
+ case POWER_BRICK_STATUS_UNKNOWN:
+ return "unknown";
+ case POWER_BRICK_STATUS_CONNECTED:
+ return "connected";
+ case POWER_BRICK_STATUS_DISCONNECTED:
+ return "disconnected";
+ default:
+ return Integer.toString(powerBrickStatus);
+ }
+ }
+
+ /** @hide */
public static String roleCombinationsToString(int combo) {
StringBuilder result = new StringBuilder();
result.append("[");
diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java
index 934c506..d1f4246 100644
--- a/core/java/android/hardware/usb/UsbPortStatus.java
+++ b/core/java/android/hardware/usb/UsbPortStatus.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -42,8 +43,9 @@
private final int mSupportedRoleCombinations;
private final @ContaminantProtectionStatus int mContaminantProtectionStatus;
private final @ContaminantDetectionStatus int mContaminantDetectionStatus;
- private final boolean mUsbDataEnabled;
private final boolean mPowerTransferLimited;
+ private final @UsbDataStatus int[] mUsbDataStatus;
+ private final @PowerBrickStatus int mPowerBrickStatus;
/**
* Power role: This USB port does not have a power role.
@@ -193,6 +195,57 @@
*/
public static final int CONTAMINANT_PROTECTION_DISABLED = 1 << 3;
+ /**
+ * USB data status is not known.
+ */
+ public static final int USB_DATA_STATUS_UNKNOWN = 0;
+
+ /**
+ * USB data is enabled.
+ */
+ public static final int USB_DATA_STATUS_ENABLED = 1;
+
+ /**
+ * USB data is disabled as the port is too hot.
+ */
+ public static final int USB_DATA_STATUS_DISABLED_OVERHEAT = 2;
+
+ /**
+ * USB data is disabled due to contaminated port.
+ */
+ public static final int USB_DATA_STATUS_DISABLED_CONTAMINANT = 3;
+
+ /**
+ * USB data is disabled due to docking event.
+ */
+ public static final int USB_DATA_STATUS_DISABLED_DOCK = 4;
+
+ /**
+ * USB data is disabled by
+ * {@link UsbPort#enableUsbData UsbPort.enableUsbData}.
+ */
+ public static final int USB_DATA_STATUS_DISABLED_FORCE = 5;
+
+ /**
+ * USB data is disabled for debug.
+ */
+ public static final int USB_DATA_STATUS_DISABLED_DEBUG = 6;
+
+ /**
+ * Unknown whether a power brick is connected.
+ */
+ public static final int POWER_BRICK_STATUS_UNKNOWN = 0;
+
+ /**
+ * The connected device is a power brick.
+ */
+ public static final int POWER_BRICK_STATUS_CONNECTED = 1;
+
+ /**
+ * The connected device is not power brick.
+ */
+ public static final int POWER_BRICK_STATUS_DISCONNECTED = 2;
+
@IntDef(prefix = { "CONTAMINANT_DETECTION_" }, value = {
CONTAMINANT_DETECTION_NOT_SUPPORTED,
CONTAMINANT_DETECTION_DISABLED,
@@ -223,18 +276,41 @@
@interface UsbPortMode{}
/** @hide */
+ @IntDef(prefix = { "USB_DATA_STATUS_" }, value = {
+ USB_DATA_STATUS_UNKNOWN,
+ USB_DATA_STATUS_ENABLED,
+ USB_DATA_STATUS_DISABLED_OVERHEAT,
+ USB_DATA_STATUS_DISABLED_CONTAMINANT,
+ USB_DATA_STATUS_DISABLED_DOCK,
+ USB_DATA_STATUS_DISABLED_FORCE,
+ USB_DATA_STATUS_DISABLED_DEBUG
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface UsbDataStatus{}
+
+ /** @hide */
+ @IntDef(prefix = { "POWER_BRICK_STATUS_" }, value = {
+ POWER_BRICK_STATUS_UNKNOWN,
+ POWER_BRICK_STATUS_DISCONNECTED,
+ POWER_BRICK_STATUS_CONNECTED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface PowerBrickStatus{}
+
+ /** @hide */
public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole,
int supportedRoleCombinations, int contaminantProtectionStatus,
- int contaminantDetectionStatus, boolean usbDataEnabled,
- boolean powerTransferLimited) {
+ int contaminantDetectionStatus, @UsbDataStatus int[] usbDataStatus,
+ boolean powerTransferLimited, @PowerBrickStatus int powerBrickStatus) {
mCurrentMode = currentMode;
mCurrentPowerRole = currentPowerRole;
mCurrentDataRole = currentDataRole;
mSupportedRoleCombinations = supportedRoleCombinations;
mContaminantProtectionStatus = contaminantProtectionStatus;
mContaminantDetectionStatus = contaminantDetectionStatus;
- mUsbDataEnabled = usbDataEnabled;
+ mUsbDataStatus = usbDataStatus;
mPowerTransferLimited = powerTransferLimited;
+ mPowerBrickStatus = powerBrickStatus;
}
/** @hide */
@@ -247,7 +323,8 @@
mSupportedRoleCombinations = supportedRoleCombinations;
mContaminantProtectionStatus = contaminantProtectionStatus;
mContaminantDetectionStatus = contaminantDetectionStatus;
- mUsbDataEnabled = true;
+ mUsbDataStatus = new int[]{USB_DATA_STATUS_UNKNOWN};
+ mPowerBrickStatus = POWER_BRICK_STATUS_UNKNOWN;
mPowerTransferLimited = false;
}
@@ -334,10 +411,14 @@
/**
* Returns UsbData status.
*
- * @hide
+ * @return Current USB data status of the port: {@link #USB_DATA_STATUS_UNKNOWN}
+ * or {@link #USB_DATA_STATUS_ENABLED} or {@link #USB_DATA_STATUS_DIASBLED_OVERHEAT}
+ * or {@link #USB_DATA_STATUS_DISABLED_CONTAMINANT}
+ * or {@link #USB_DATA_STATUS_DISABLED_DOCK} or {@link #USB_DATA_STATUS_DISABLED_FORCE}
+ * or {@link #USB_DATA_STATUS_DISABLED_DEBUG}
*/
- public boolean getUsbDataStatus() {
- return mUsbDataEnabled;
+ public @UsbDataStatus @Nullable int[] getUsbDataStatus() {
+ return mUsbDataStatus;
}
/**
@@ -350,6 +431,17 @@
return mPowerTransferLimited;
}
+ /**
+ * Let's the caller know if a power brick is connected to the USB port.
+ *
+ * @return {@link #POWER_BRICK_STATUS_UNKNOWN}
+ * or {@link #POWER_BRICK_STATUS_CONNECTED}
+ * or {@link #POWER_BRICK_STATUS_DISCONNECTED}
+ */
+ public @PowerBrickStatus int getPowerBrickStatus() {
+ return mPowerBrickStatus;
+ }
+
@NonNull
@Override
public String toString() {
@@ -363,10 +455,12 @@
+ getContaminantDetectionStatus()
+ ", contaminantProtectionStatus="
+ getContaminantProtectionStatus()
- + ", usbDataEnabled="
- + getUsbDataStatus()
+ + ", usbDataStatus="
+ + UsbPort.usbDataStatusToString(getUsbDataStatus())
+ ", isPowerTransferLimited="
+ isPowerTransferLimited()
+ +", powerBrickStatus="
+ + UsbPort.powerBrickStatusToString(getPowerBrickStatus())
+ "}";
}
@@ -383,8 +477,10 @@
dest.writeInt(mSupportedRoleCombinations);
dest.writeInt(mContaminantProtectionStatus);
dest.writeInt(mContaminantDetectionStatus);
- dest.writeBoolean(mUsbDataEnabled);
+ dest.writeInt(mUsbDataStatus.length);
+ dest.writeIntArray(mUsbDataStatus);
dest.writeBoolean(mPowerTransferLimited);
+ dest.writeInt(mPowerBrickStatus);
}
public static final @NonNull Parcelable.Creator<UsbPortStatus> CREATOR =
@@ -397,11 +493,14 @@
int supportedRoleCombinations = in.readInt();
int contaminantProtectionStatus = in.readInt();
int contaminantDetectionStatus = in.readInt();
- boolean usbDataEnabled = in.readBoolean();
+ int[] usbDataStatus = new int[in.readInt()];
+ in.readIntArray(usbDataStatus);
boolean powerTransferLimited = in.readBoolean();
+ int powerBrickStatus = in.readInt();
return new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
supportedRoleCombinations, contaminantProtectionStatus,
- contaminantDetectionStatus, usbDataEnabled, powerTransferLimited);
+ contaminantDetectionStatus, usbDataStatus, powerTransferLimited,
+ powerBrickStatus);
}
@Override
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 09d5085..5d2d8ea 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -469,6 +469,10 @@
InputMethodManager mImm;
private InputMethodPrivilegedOperations mPrivOps = new InputMethodPrivilegedOperations();
+ @NonNull
+ private final NavigationBarController mNavigationBarController =
+ new NavigationBarController(this);
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
int mTheme = 0;
@@ -611,6 +615,7 @@
info.touchableRegion.set(mTmpInsets.touchableRegion);
info.setTouchableInsets(mTmpInsets.touchableInsets);
}
+ mNavigationBarController.updateTouchableInsets(mTmpInsets, info);
if (mInputFrame != null) {
setImeExclusionRect(mTmpInsets.visibleTopInsets);
@@ -1534,6 +1539,7 @@
mCandidatesVisibility = getCandidatesHiddenVisibility();
mCandidatesFrame.setVisibility(mCandidatesVisibility);
mInputFrame.setVisibility(View.GONE);
+ mNavigationBarController.onViewInitialized();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@@ -1543,6 +1549,7 @@
mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
mInsetsComputer);
doFinishInput();
+ mNavigationBarController.onDestroy();
mWindow.dismissForDestroyIfNecessary();
if (mSettingsObserver != null) {
mSettingsObserver.unregister();
@@ -2451,6 +2458,7 @@
setImeWindowStatus(nextImeWindowStatus, mBackDisposition);
}
+ mNavigationBarController.onWindowShown();
// compute visibility
onWindowShown();
mWindowVisible = true;
@@ -3656,6 +3664,7 @@
+ " touchableInsets=" + mTmpInsets.touchableInsets
+ " touchableRegion=" + mTmpInsets.touchableRegion);
p.println(" mSettingsObserver=" + mSettingsObserver);
+ p.println(" mNavigationBarController=" + mNavigationBarController.toDebugString());
}
private final ImeTracing.ServiceDumper mDumper = new ImeTracing.ServiceDumper() {
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
new file mode 100644
index 0000000..7295b72
--- /dev/null
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -0,0 +1,328 @@
+/*
+ * 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.inputmethodservice;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.StatusBarManager;
+import android.content.res.Resources;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.inputmethodservice.navigationbar.NavigationBarFrame;
+import android.inputmethodservice.navigationbar.NavigationBarView;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowManagerPolicyConstants;
+import android.widget.FrameLayout;
+
+import java.util.Objects;
+
+/**
+ * This class hides details behind {@link InputMethodService#canImeRenderGesturalNavButtons()} from
+ * {@link InputMethodService}.
+ *
+ * <p>All the package-private methods are no-op when
+ * {@link InputMethodService#canImeRenderGesturalNavButtons()} returns {@code false}.</p>
+ */
+final class NavigationBarController {
+
+ private interface Callback {
+ default void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets,
+ @NonNull ViewTreeObserver.InternalInsetsInfo dest) {
+ }
+
+ default void onViewInitialized() {
+ }
+
+ default void onWindowShown() {
+ }
+
+ default void onDestroy() {
+ }
+
+ default String toDebugString() {
+ return "No-op implementation";
+ }
+
+ Callback NOOP = new Callback() {
+ };
+ }
+
+ private final Callback mImpl;
+
+ NavigationBarController(@NonNull InputMethodService inputMethodService) {
+ mImpl = InputMethodService.canImeRenderGesturalNavButtons()
+ ? new Impl(inputMethodService) : Callback.NOOP;
+ }
+
+ void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets,
+ @NonNull ViewTreeObserver.InternalInsetsInfo dest) {
+ mImpl.updateTouchableInsets(originalInsets, dest);
+ }
+
+ void onViewInitialized() {
+ mImpl.onViewInitialized();
+ }
+
+ void onWindowShown() {
+ mImpl.onWindowShown();
+ }
+
+ void onDestroy() {
+ mImpl.onDestroy();
+ }
+
+ String toDebugString() {
+ return mImpl.toDebugString();
+ }
+
+ private static final class Impl implements Callback {
+ @NonNull
+ private final InputMethodService mService;
+
+ private boolean mDestroyed = false;
+
+ private boolean mRenderGesturalNavButtons;
+
+ @Nullable
+ private NavigationBarFrame mNavigationBarFrame;
+ @Nullable
+ Insets mLastInsets;
+
+ Impl(@NonNull InputMethodService inputMethodService) {
+ mService = inputMethodService;
+ }
+
+ @Nullable
+ private Insets getSystemInsets() {
+ if (mService.mWindow == null) {
+ return null;
+ }
+ final View decorView = mService.mWindow.getWindow().getDecorView();
+ if (decorView == null) {
+ return null;
+ }
+ final WindowInsets windowInsets = decorView.getRootWindowInsets();
+ if (windowInsets == null) {
+ return null;
+ }
+ final Insets stableBarInsets =
+ windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars());
+ return Insets.min(windowInsets.getInsets(WindowInsets.Type.systemBars()
+ | WindowInsets.Type.displayCutout()), stableBarInsets);
+ }
+
+ private void installNavigationBarFrameIfNecessary() {
+ if (!mRenderGesturalNavButtons) {
+ return;
+ }
+ final View rawDecorView = mService.mWindow.getWindow().getDecorView();
+ if (!(rawDecorView instanceof ViewGroup)) {
+ return;
+ }
+ final ViewGroup decorView = (ViewGroup) rawDecorView;
+ mNavigationBarFrame = decorView.findViewByPredicate(
+ NavigationBarFrame.class::isInstance);
+ final Insets systemInsets = getSystemInsets();
+ if (mNavigationBarFrame == null) {
+ mNavigationBarFrame = new NavigationBarFrame(mService);
+ LayoutInflater.from(mService).inflate(
+ com.android.internal.R.layout.input_method_navigation_bar,
+ mNavigationBarFrame);
+ if (systemInsets != null) {
+ decorView.addView(mNavigationBarFrame, new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ systemInsets.bottom, Gravity.BOTTOM));
+ mLastInsets = systemInsets;
+ } else {
+ decorView.addView(mNavigationBarFrame);
+ }
+ final NavigationBarView navigationBarView = mNavigationBarFrame.findViewByPredicate(
+ NavigationBarView.class::isInstance);
+ if (navigationBarView != null) {
+ // TODO(b/213337792): Support InputMethodService#setBackDisposition().
+ // TODO(b/213337792): Set NAVIGATION_HINT_IME_SHOWN only when necessary.
+ final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT
+ | StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
+ navigationBarView.setNavigationIconHints(hints);
+ }
+ } else {
+ mNavigationBarFrame.setLayoutParams(new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, systemInsets.bottom, Gravity.BOTTOM));
+ mLastInsets = systemInsets;
+ }
+
+ mNavigationBarFrame.setBackground(null);
+ }
+
+ @Override
+ public void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets,
+ @NonNull ViewTreeObserver.InternalInsetsInfo dest) {
+ if (!mRenderGesturalNavButtons || mNavigationBarFrame == null
+ || mService.isExtractViewShown()) {
+ return;
+ }
+
+ final Insets systemInsets = getSystemInsets();
+ if (systemInsets != null) {
+ final Window window = mService.mWindow.getWindow();
+ final View decor = window.getDecorView();
+ Region touchableRegion = null;
+ final View inputFrame = mService.mInputFrame;
+ switch (originalInsets.touchableInsets) {
+ case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME:
+ if (inputFrame.getVisibility() == View.VISIBLE) {
+ touchableRegion = new Region(inputFrame.getLeft(),
+ inputFrame.getTop(), inputFrame.getRight(),
+ inputFrame.getBottom());
+ }
+ break;
+ case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT:
+ if (inputFrame.getVisibility() == View.VISIBLE) {
+ touchableRegion = new Region(inputFrame.getLeft(),
+ originalInsets.contentTopInsets, inputFrame.getRight(),
+ inputFrame.getBottom());
+ }
+ break;
+ case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE:
+ if (inputFrame.getVisibility() == View.VISIBLE) {
+ touchableRegion = new Region(inputFrame.getLeft(),
+ originalInsets.visibleTopInsets, inputFrame.getRight(),
+ inputFrame.getBottom());
+ }
+ break;
+ case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION:
+ touchableRegion = new Region();
+ touchableRegion.set(originalInsets.touchableRegion);
+ break;
+ }
+ final Rect navBarRect = new Rect(decor.getLeft(),
+ decor.getBottom() - systemInsets.bottom,
+ decor.getRight(), decor.getBottom());
+ if (touchableRegion == null) {
+ touchableRegion = new Region(navBarRect);
+ } else {
+ touchableRegion.union(navBarRect);
+ }
+
+ dest.touchableRegion.set(touchableRegion);
+ dest.setTouchableInsets(
+ ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+
+ // TODO(b/205803355): See if we can use View#OnLayoutChangeListener().
+ // TODO(b/205803355): See if we can replace DecorView#mNavigationColorViewState.view
+ boolean zOrderChanged = false;
+ if (decor instanceof ViewGroup) {
+ ViewGroup decorGroup = (ViewGroup) decor;
+ final View navbarBackgroundView = window.getNavigationBarBackgroundView();
+ zOrderChanged = navbarBackgroundView != null
+ && decorGroup.indexOfChild(navbarBackgroundView)
+ > decorGroup.indexOfChild(mNavigationBarFrame);
+ }
+ final boolean insetChanged = !Objects.equals(systemInsets, mLastInsets);
+ if (zOrderChanged || insetChanged) {
+ final NavigationBarFrame that = mNavigationBarFrame;
+ that.post(() -> {
+ if (!that.isAttachedToWindow()) {
+ return;
+ }
+ final Insets currentSystemInsets = getSystemInsets();
+ if (!Objects.equals(currentSystemInsets, mLastInsets)) {
+ that.setLayoutParams(
+ new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ currentSystemInsets.bottom, Gravity.BOTTOM));
+ mLastInsets = currentSystemInsets;
+ }
+ if (decor instanceof ViewGroup) {
+ ViewGroup decorGroup = (ViewGroup) decor;
+ final View navbarBackgroundView =
+ window.getNavigationBarBackgroundView();
+ if (navbarBackgroundView != null
+ && decorGroup.indexOfChild(navbarBackgroundView)
+ > decorGroup.indexOfChild(that)) {
+ decorGroup.bringChildToFront(that);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ private boolean isGesturalNavigationEnabled() {
+ final Resources resources = mService.getResources();
+ if (resources == null) {
+ return false;
+ }
+ return resources.getInteger(com.android.internal.R.integer.config_navBarInteractionMode)
+ == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+ }
+
+ @Override
+ public void onViewInitialized() {
+ if (mDestroyed) {
+ return;
+ }
+ mRenderGesturalNavButtons = isGesturalNavigationEnabled();
+ installNavigationBarFrameIfNecessary();
+ }
+
+ @Override
+ public void onDestroy() {
+ mDestroyed = true;
+ }
+
+ @Override
+ public void onWindowShown() {
+ if (mDestroyed || !mRenderGesturalNavButtons || mNavigationBarFrame == null) {
+ return;
+ }
+ final Insets systemInsets = getSystemInsets();
+ if (systemInsets != null) {
+ if (!Objects.equals(systemInsets, mLastInsets)) {
+ mNavigationBarFrame.setLayoutParams(new NavigationBarFrame.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ systemInsets.bottom, Gravity.BOTTOM));
+ mLastInsets = systemInsets;
+ }
+ final Window window = mService.mWindow.getWindow();
+ View rawDecorView = window.getDecorView();
+ if (rawDecorView instanceof ViewGroup) {
+ final ViewGroup decor = (ViewGroup) rawDecorView;
+ final View navbarBackgroundView = window.getNavigationBarBackgroundView();
+ if (navbarBackgroundView != null
+ && decor.indexOfChild(navbarBackgroundView)
+ > decor.indexOfChild(mNavigationBarFrame)) {
+ decor.bringChildToFront(mNavigationBarFrame);
+ }
+ }
+ mNavigationBarFrame.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public String toDebugString() {
+ return "{mRenderGesturalNavButtons=" + mRenderGesturalNavButtons + "}";
+ }
+ }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java b/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java
new file mode 100644
index 0000000..3f26fa4
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java
@@ -0,0 +1,302 @@
+/*
+ * 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.inputmethodservice.navigationbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+
+import java.util.ArrayList;
+
+/**
+ * Dispatches common view calls to multiple views. This is used to handle
+ * multiples of the same nav bar icon appearing.
+ */
+final class ButtonDispatcher {
+ private static final int FADE_DURATION_IN = 150;
+ private static final int FADE_DURATION_OUT = 250;
+ public static final Interpolator LINEAR = new LinearInterpolator();
+
+ private final ArrayList<View> mViews = new ArrayList<>();
+
+ private final int mId;
+
+ private View.OnClickListener mClickListener;
+ private View.OnTouchListener mTouchListener;
+ private View.OnLongClickListener mLongClickListener;
+ private View.OnHoverListener mOnHoverListener;
+ private Boolean mLongClickable;
+ private float mAlpha = 1.0f;
+ private Float mDarkIntensity;
+ private int mVisibility = View.VISIBLE;
+ private Boolean mDelayTouchFeedback;
+ private KeyButtonDrawable mImageDrawable;
+ private View mCurrentView;
+ private ValueAnimator mFadeAnimator;
+ private AccessibilityDelegate mAccessibilityDelegate;
+
+ private final ValueAnimator.AnimatorUpdateListener mAlphaListener = animation ->
+ setAlpha(
+ (float) animation.getAnimatedValue(),
+ false /* animate */,
+ false /* cancelAnimator */);
+
+ private final AnimatorListenerAdapter mFadeListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mFadeAnimator = null;
+ setVisibility(getAlpha() == 1 ? View.VISIBLE : View.INVISIBLE);
+ }
+ };
+
+ public ButtonDispatcher(int id) {
+ mId = id;
+ }
+
+ public void clear() {
+ mViews.clear();
+ }
+
+ public void addView(View view) {
+ mViews.add(view);
+ view.setOnClickListener(mClickListener);
+ view.setOnTouchListener(mTouchListener);
+ view.setOnLongClickListener(mLongClickListener);
+ view.setOnHoverListener(mOnHoverListener);
+ if (mLongClickable != null) {
+ view.setLongClickable(mLongClickable);
+ }
+ view.setAlpha(mAlpha);
+ view.setVisibility(mVisibility);
+ if (mAccessibilityDelegate != null) {
+ view.setAccessibilityDelegate(mAccessibilityDelegate);
+ }
+ if (view instanceof ButtonInterface) {
+ final ButtonInterface button = (ButtonInterface) view;
+ if (mDarkIntensity != null) {
+ button.setDarkIntensity(mDarkIntensity);
+ }
+ if (mImageDrawable != null) {
+ button.setImageDrawable(mImageDrawable);
+ }
+ if (mDelayTouchFeedback != null) {
+ button.setDelayTouchFeedback(mDelayTouchFeedback);
+ }
+ }
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ public int getVisibility() {
+ return mVisibility;
+ }
+
+ public boolean isVisible() {
+ return getVisibility() == View.VISIBLE;
+ }
+
+ public float getAlpha() {
+ return mAlpha;
+ }
+
+ public KeyButtonDrawable getImageDrawable() {
+ return mImageDrawable;
+ }
+
+ public void setImageDrawable(KeyButtonDrawable drawable) {
+ mImageDrawable = drawable;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ if (mViews.get(i) instanceof ButtonInterface) {
+ ((ButtonInterface) mViews.get(i)).setImageDrawable(mImageDrawable);
+ }
+ }
+ if (mImageDrawable != null) {
+ mImageDrawable.setCallback(mCurrentView);
+ }
+ }
+
+ public void setVisibility(int visibility) {
+ if (mVisibility == visibility) return;
+ if (mFadeAnimator != null) {
+ mFadeAnimator.cancel();
+ }
+
+ mVisibility = visibility;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ mViews.get(i).setVisibility(mVisibility);
+ }
+ }
+
+ public void setAlpha(float alpha) {
+ setAlpha(alpha, false /* animate */);
+ }
+
+ public void setAlpha(float alpha, boolean animate) {
+ setAlpha(alpha, animate, true /* cancelAnimator */);
+ }
+
+ public void setAlpha(float alpha, boolean animate, long duration) {
+ setAlpha(alpha, animate, duration, true /* cancelAnimator */);
+ }
+
+ public void setAlpha(float alpha, boolean animate, boolean cancelAnimator) {
+ setAlpha(
+ alpha,
+ animate,
+ (getAlpha() < alpha) ? FADE_DURATION_IN : FADE_DURATION_OUT,
+ cancelAnimator);
+ }
+
+ public void setAlpha(float alpha, boolean animate, long duration, boolean cancelAnimator) {
+ if (mFadeAnimator != null && (cancelAnimator || animate)) {
+ mFadeAnimator.cancel();
+ }
+ if (animate) {
+ setVisibility(View.VISIBLE);
+ mFadeAnimator = ValueAnimator.ofFloat(getAlpha(), alpha);
+ mFadeAnimator.setDuration(duration);
+ mFadeAnimator.setInterpolator(LINEAR);
+ mFadeAnimator.addListener(mFadeListener);
+ mFadeAnimator.addUpdateListener(mAlphaListener);
+ mFadeAnimator.start();
+ } else {
+ // Discretize the alpha updates to prevent too frequent updates when there is a long
+ // alpha animation
+ int prevAlpha = (int) (getAlpha() * 255);
+ int nextAlpha = (int) (alpha * 255);
+ if (prevAlpha != nextAlpha) {
+ mAlpha = nextAlpha / 255f;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ mViews.get(i).setAlpha(mAlpha);
+ }
+ }
+ }
+ }
+
+ public void setDarkIntensity(float darkIntensity) {
+ mDarkIntensity = darkIntensity;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ if (mViews.get(i) instanceof ButtonInterface) {
+ ((ButtonInterface) mViews.get(i)).setDarkIntensity(darkIntensity);
+ }
+ }
+ }
+
+ public void setDelayTouchFeedback(boolean delay) {
+ mDelayTouchFeedback = delay;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ if (mViews.get(i) instanceof ButtonInterface) {
+ ((ButtonInterface) mViews.get(i)).setDelayTouchFeedback(delay);
+ }
+ }
+ }
+
+ public void setOnClickListener(View.OnClickListener clickListener) {
+ mClickListener = clickListener;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ mViews.get(i).setOnClickListener(mClickListener);
+ }
+ }
+
+ public void setOnTouchListener(View.OnTouchListener touchListener) {
+ mTouchListener = touchListener;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ mViews.get(i).setOnTouchListener(mTouchListener);
+ }
+ }
+
+ public void setLongClickable(boolean isLongClickable) {
+ mLongClickable = isLongClickable;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ mViews.get(i).setLongClickable(mLongClickable);
+ }
+ }
+
+ public void setOnLongClickListener(View.OnLongClickListener longClickListener) {
+ mLongClickListener = longClickListener;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ mViews.get(i).setOnLongClickListener(mLongClickListener);
+ }
+ }
+
+ public void setOnHoverListener(View.OnHoverListener hoverListener) {
+ mOnHoverListener = hoverListener;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ mViews.get(i).setOnHoverListener(mOnHoverListener);
+ }
+ }
+
+ public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
+ mAccessibilityDelegate = delegate;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ mViews.get(i).setAccessibilityDelegate(delegate);
+ }
+ }
+
+ public void setTranslation(int x, int y, int z) {
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ final View view = mViews.get(i);
+ view.setTranslationX(x);
+ view.setTranslationY(y);
+ view.setTranslationZ(z);
+ }
+ }
+
+ public ArrayList<View> getViews() {
+ return mViews;
+ }
+
+ public View getCurrentView() {
+ return mCurrentView;
+ }
+
+ public void setCurrentView(View currentView) {
+ mCurrentView = currentView.findViewById(mId);
+ if (mImageDrawable != null) {
+ mImageDrawable.setCallback(mCurrentView);
+ }
+ if (mCurrentView != null) {
+ mCurrentView.setTranslationX(0);
+ mCurrentView.setTranslationY(0);
+ mCurrentView.setTranslationZ(0);
+ }
+ }
+
+ /**
+ * Executes when button is detached from window.
+ */
+ public void onDestroy() {
+ }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/ButtonInterface.java b/core/java/android/inputmethodservice/navigationbar/ButtonInterface.java
new file mode 100644
index 0000000..1c9c86d
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/ButtonInterface.java
@@ -0,0 +1,29 @@
+/*
+ * 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.inputmethodservice.navigationbar;
+
+import android.annotation.Nullable;
+import android.graphics.drawable.Drawable;
+
+interface ButtonInterface {
+
+ void setImageDrawable(@Nullable Drawable drawable);
+
+ void setDarkIntensity(float intensity);
+
+ void setDelayTouchFeedback(boolean shouldDelay);
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/DeadZone.java b/core/java/android/inputmethodservice/navigationbar/DeadZone.java
new file mode 100644
index 0000000..cd85736
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/DeadZone.java
@@ -0,0 +1,203 @@
+/*
+ * 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.inputmethodservice.navigationbar;
+
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVIGATION_BAR_DEADZONE_DECAY;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVIGATION_BAR_DEADZONE_HOLD;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVIGATION_BAR_DEADZONE_SIZE;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVIGATION_BAR_DEADZONE_SIZE_MAX;
+import static android.inputmethodservice.navigationbar.NavigationBarUtils.dpToPx;
+
+import android.animation.ObjectAnimator;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.Surface;
+
+/**
+ * The "dead zone" consumes unintentional taps along the top edge of the navigation bar.
+ * When users are typing quickly on an IME they may attempt to hit the space bar, overshoot, and
+ * accidentally hit the home button. The DeadZone expands temporarily after each tap in the UI
+ * outside the navigation bar (since this is when accidental taps are more likely), then contracts
+ * back over time (since a later tap might be intended for the top of the bar).
+ */
+final class DeadZone {
+ public static final String TAG = "DeadZone";
+
+ public static final boolean DEBUG = false;
+ public static final int HORIZONTAL = 0; // Consume taps along the top edge.
+ public static final int VERTICAL = 1; // Consume taps along the left edge.
+
+ private static final boolean CHATTY = true; // print to logcat when we eat a click
+ private final NavigationBarView mNavigationBarView;
+
+ private boolean mShouldFlash;
+ private float mFlashFrac = 0f;
+
+ private int mSizeMax;
+ private int mSizeMin;
+ // Upon activity elsewhere in the UI, the dead zone will hold steady for
+ // mHold ms, then move back over the course of mDecay ms
+ private int mHold, mDecay;
+ private boolean mVertical;
+ private long mLastPokeTime;
+ private int mDisplayRotation;
+
+ private final Runnable mDebugFlash = new Runnable() {
+ @Override
+ public void run() {
+ ObjectAnimator.ofFloat(DeadZone.this, "flash", 1f, 0f).setDuration(150).start();
+ }
+ };
+
+ public DeadZone(NavigationBarView view) {
+ mNavigationBarView = view;
+ onConfigurationChanged(Surface.ROTATION_0);
+ }
+
+ static float lerp(float a, float b, float f) {
+ return (b - a) * f + a;
+ }
+
+ private float getSize(long now) {
+ if (mSizeMax == 0)
+ return 0;
+ long dt = (now - mLastPokeTime);
+ if (dt > mHold + mDecay)
+ return mSizeMin;
+ if (dt < mHold)
+ return mSizeMax;
+ return (int) lerp(mSizeMax, mSizeMin, (float) (dt - mHold) / mDecay);
+ }
+
+ public void setFlashOnTouchCapture(boolean dbg) {
+ mShouldFlash = dbg;
+ mFlashFrac = 0f;
+ mNavigationBarView.postInvalidate();
+ }
+
+ public void onConfigurationChanged(int rotation) {
+ mDisplayRotation = rotation;
+
+ final Resources res = mNavigationBarView.getResources();
+ mHold = NAVIGATION_BAR_DEADZONE_HOLD;
+ mDecay = NAVIGATION_BAR_DEADZONE_DECAY;
+
+ mSizeMin = dpToPx(NAVIGATION_BAR_DEADZONE_SIZE, res);
+ mSizeMax = dpToPx(NAVIGATION_BAR_DEADZONE_SIZE_MAX, res);
+ mVertical = (res.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);
+
+ if (DEBUG) {
+ Log.v(TAG, this + " size=[" + mSizeMin + "-" + mSizeMax + "] hold=" + mHold
+ + (mVertical ? " vertical" : " horizontal"));
+ }
+ setFlashOnTouchCapture(false); // hard-coded from "bool/config_dead_zone_flash"
+ }
+
+ // I made you a touch event...
+ public boolean onTouchEvent(MotionEvent event) {
+ if (DEBUG) {
+ Log.v(TAG, this + " onTouch: " + MotionEvent.actionToString(event.getAction()));
+ }
+
+ // Don't consume events for high precision pointing devices. For this purpose a stylus is
+ // considered low precision (like a finger), so its events may be consumed.
+ if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
+ return false;
+ }
+
+ final int action = event.getAction();
+ if (action == MotionEvent.ACTION_OUTSIDE) {
+ poke(event);
+ return true;
+ } else if (action == MotionEvent.ACTION_DOWN) {
+ if (DEBUG) {
+ Log.v(TAG, this + " ACTION_DOWN: " + event.getX() + "," + event.getY());
+ }
+ //TODO(b/205803355): call mNavBarController.touchAutoDim(mDisplayId); here
+ int size = (int) getSize(event.getEventTime());
+ // In the vertical orientation consume taps along the left edge.
+ // In horizontal orientation consume taps along the top edge.
+ final boolean consumeEvent;
+ if (mVertical) {
+ if (mDisplayRotation == Surface.ROTATION_270) {
+ consumeEvent = event.getX() > mNavigationBarView.getWidth() - size;
+ } else {
+ consumeEvent = event.getX() < size;
+ }
+ } else {
+ consumeEvent = event.getY() < size;
+ }
+ if (consumeEvent) {
+ if (CHATTY) {
+ Log.v(TAG, "consuming errant click: (" + event.getX() + ","
+ + event.getY() + ")");
+ }
+ if (mShouldFlash) {
+ mNavigationBarView.post(mDebugFlash);
+ mNavigationBarView.postInvalidate();
+ }
+ return true; // ...but I eated it
+ }
+ }
+ return false;
+ }
+
+ private void poke(MotionEvent event) {
+ mLastPokeTime = event.getEventTime();
+ if (DEBUG)
+ Log.v(TAG, "poked! size=" + getSize(mLastPokeTime));
+ if (mShouldFlash) mNavigationBarView.postInvalidate();
+ }
+
+ public void setFlash(float f) {
+ mFlashFrac = f;
+ mNavigationBarView.postInvalidate();
+ }
+
+ public float getFlash() {
+ return mFlashFrac;
+ }
+
+ public void onDraw(Canvas can) {
+ if (!mShouldFlash || mFlashFrac <= 0f) {
+ return;
+ }
+
+ final int size = (int) getSize(SystemClock.uptimeMillis());
+ if (mVertical) {
+ if (mDisplayRotation == Surface.ROTATION_270) {
+ can.clipRect(can.getWidth() - size, 0, can.getWidth(), can.getHeight());
+ } else {
+ can.clipRect(0, 0, size, can.getHeight());
+ }
+ } else {
+ can.clipRect(0, 0, can.getWidth(), size);
+ }
+
+ final float frac = DEBUG ? (mFlashFrac - 0.5f) + 0.5f : mFlashFrac;
+ can.drawARGB((int) (frac * 0xFF), 0xDD, 0xEE, 0xAA);
+
+ if (DEBUG && size > mSizeMin) {
+ // Very aggressive redrawing here, for debugging only
+ mNavigationBarView.postInvalidateDelayed(100);
+ }
+ }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java
new file mode 100644
index 0000000..25a443d
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java
@@ -0,0 +1,483 @@
+/*
+ * 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.inputmethodservice.navigationbar;
+
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAV_KEY_BUTTON_SHADOW_COLOR;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAV_KEY_BUTTON_SHADOW_OFFSET_X;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAV_KEY_BUTTON_SHADOW_OFFSET_Y;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAV_KEY_BUTTON_SHADOW_RADIUS;
+import static android.inputmethodservice.navigationbar.NavigationBarUtils.dpToPx;
+
+import android.animation.ArgbEvaluator;
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
+import android.graphics.BlurMaskFilter.Blur;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.FloatProperty;
+import android.view.View;
+
+
+/**
+ * Drawable for {@link KeyButtonView}s that supports tinting between two colors, rotation and shows
+ * a shadow. AnimatedVectorDrawable will only support tinting from intensities but has no support
+ * for shadows nor rotations.
+ */
+final class KeyButtonDrawable extends Drawable {
+
+ public static final FloatProperty<KeyButtonDrawable> KEY_DRAWABLE_ROTATE =
+ new FloatProperty<KeyButtonDrawable>("KeyButtonRotation") {
+ @Override
+ public void setValue(KeyButtonDrawable drawable, float degree) {
+ drawable.setRotation(degree);
+ }
+
+ @Override
+ public Float get(KeyButtonDrawable drawable) {
+ return drawable.getRotation();
+ }
+ };
+
+ public static final FloatProperty<KeyButtonDrawable> KEY_DRAWABLE_TRANSLATE_Y =
+ new FloatProperty<KeyButtonDrawable>("KeyButtonTranslateY") {
+ @Override
+ public void setValue(KeyButtonDrawable drawable, float y) {
+ drawable.setTranslationY(y);
+ }
+
+ @Override
+ public Float get(KeyButtonDrawable drawable) {
+ return drawable.getTranslationY();
+ }
+ };
+
+ private final Paint mIconPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ private final Paint mShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ private final ShadowDrawableState mState;
+ private AnimatedVectorDrawable mAnimatedDrawable;
+ private final Callback mAnimatedDrawableCallback = new Callback() {
+ @Override
+ public void invalidateDrawable(@NonNull Drawable who) {
+ invalidateSelf();
+ }
+
+ @Override
+ public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
+ scheduleSelf(what, when);
+ }
+
+ @Override
+ public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+ unscheduleSelf(what);
+ }
+ };
+
+ public KeyButtonDrawable(Drawable d, @ColorInt int lightColor, @ColorInt int darkColor,
+ boolean horizontalFlip, Color ovalBackgroundColor) {
+ this(d, new ShadowDrawableState(lightColor, darkColor,
+ d instanceof AnimatedVectorDrawable, horizontalFlip, ovalBackgroundColor));
+ }
+
+ private KeyButtonDrawable(Drawable d, ShadowDrawableState state) {
+ mState = state;
+ if (d != null) {
+ mState.mBaseHeight = d.getIntrinsicHeight();
+ mState.mBaseWidth = d.getIntrinsicWidth();
+ mState.mChangingConfigurations = d.getChangingConfigurations();
+ mState.mChildState = d.getConstantState();
+ }
+ if (canAnimate()) {
+ mAnimatedDrawable = (AnimatedVectorDrawable) mState.mChildState.newDrawable().mutate();
+ mAnimatedDrawable.setCallback(mAnimatedDrawableCallback);
+ setDrawableBounds(mAnimatedDrawable);
+ }
+ }
+
+ public void setDarkIntensity(float intensity) {
+ mState.mDarkIntensity = intensity;
+ final int color = (int) ArgbEvaluator.getInstance()
+ .evaluate(intensity, mState.mLightColor, mState.mDarkColor);
+ updateShadowAlpha();
+ setColorFilter(new PorterDuffColorFilter(color, Mode.SRC_ATOP));
+ }
+
+ public void setRotation(float degrees) {
+ if (canAnimate()) {
+ // AnimatedVectorDrawables will not support rotation
+ return;
+ }
+ if (mState.mRotateDegrees != degrees) {
+ mState.mRotateDegrees = degrees;
+ invalidateSelf();
+ }
+ }
+
+ public void setTranslationX(float x) {
+ setTranslation(x, mState.mTranslationY);
+ }
+
+ public void setTranslationY(float y) {
+ setTranslation(mState.mTranslationX, y);
+ }
+
+ public void setTranslation(float x, float y) {
+ if (mState.mTranslationX != x || mState.mTranslationY != y) {
+ mState.mTranslationX = x;
+ mState.mTranslationY = y;
+ invalidateSelf();
+ }
+ }
+
+ public void setShadowProperties(int x, int y, int size, int color) {
+ if (canAnimate()) {
+ // AnimatedVectorDrawables will not support shadows
+ return;
+ }
+ if (mState.mShadowOffsetX != x || mState.mShadowOffsetY != y
+ || mState.mShadowSize != size || mState.mShadowColor != color) {
+ mState.mShadowOffsetX = x;
+ mState.mShadowOffsetY = y;
+ mState.mShadowSize = size;
+ mState.mShadowColor = color;
+ mShadowPaint.setColorFilter(
+ new PorterDuffColorFilter(mState.mShadowColor, Mode.SRC_ATOP));
+ updateShadowAlpha();
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public boolean setVisible(boolean visible, boolean restart) {
+ boolean changed = super.setVisible(visible, restart);
+ if (changed) {
+ // End any existing animations when the visibility changes
+ jumpToCurrentState();
+ }
+ return changed;
+ }
+
+ @Override
+ public void jumpToCurrentState() {
+ super.jumpToCurrentState();
+ if (mAnimatedDrawable != null) {
+ mAnimatedDrawable.jumpToCurrentState();
+ }
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ mState.mAlpha = alpha;
+ mIconPaint.setAlpha(alpha);
+ updateShadowAlpha();
+ invalidateSelf();
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ mIconPaint.setColorFilter(colorFilter);
+ if (mAnimatedDrawable != null) {
+ if (hasOvalBg()) {
+ mAnimatedDrawable.setColorFilter(
+ new PorterDuffColorFilter(mState.mLightColor, PorterDuff.Mode.SRC_IN));
+ } else {
+ mAnimatedDrawable.setColorFilter(colorFilter);
+ }
+ }
+ invalidateSelf();
+ }
+
+ public float getDarkIntensity() {
+ return mState.mDarkIntensity;
+ }
+
+ public float getRotation() {
+ return mState.mRotateDegrees;
+ }
+
+ public float getTranslationX() {
+ return mState.mTranslationX;
+ }
+
+ public float getTranslationY() {
+ return mState.mTranslationY;
+ }
+
+ @Override
+ public ConstantState getConstantState() {
+ return mState;
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mState.mBaseHeight + (mState.mShadowSize + Math.abs(mState.mShadowOffsetY)) * 2;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mState.mBaseWidth + (mState.mShadowSize + Math.abs(mState.mShadowOffsetX)) * 2;
+ }
+
+ public boolean canAnimate() {
+ return mState.mSupportsAnimation;
+ }
+
+ public void startAnimation() {
+ if (mAnimatedDrawable != null) {
+ mAnimatedDrawable.start();
+ }
+ }
+
+ public void resetAnimation() {
+ if (mAnimatedDrawable != null) {
+ mAnimatedDrawable.reset();
+ }
+ }
+
+ public void clearAnimationCallbacks() {
+ if (mAnimatedDrawable != null) {
+ mAnimatedDrawable.clearAnimationCallbacks();
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ Rect bounds = getBounds();
+ if (bounds.isEmpty()) {
+ return;
+ }
+
+ if (mAnimatedDrawable != null) {
+ mAnimatedDrawable.draw(canvas);
+ } else {
+ // If no cache or previous cached bitmap is hardware/software acceleration does not
+ // match the current canvas on draw then regenerate
+ boolean hwBitmapChanged = mState.mIsHardwareBitmap != canvas.isHardwareAccelerated();
+ if (hwBitmapChanged) {
+ mState.mIsHardwareBitmap = canvas.isHardwareAccelerated();
+ }
+ if (mState.mLastDrawnIcon == null || hwBitmapChanged) {
+ regenerateBitmapIconCache();
+ }
+ canvas.save();
+ canvas.translate(mState.mTranslationX, mState.mTranslationY);
+ canvas.rotate(mState.mRotateDegrees, getIntrinsicWidth() / 2, getIntrinsicHeight() / 2);
+
+ if (mState.mShadowSize > 0) {
+ if (mState.mLastDrawnShadow == null || hwBitmapChanged) {
+ regenerateBitmapShadowCache();
+ }
+
+ // Translate (with rotation offset) before drawing the shadow
+ final float radians = (float) (mState.mRotateDegrees * Math.PI / 180);
+ final float shadowOffsetX = (float) (Math.sin(radians) * mState.mShadowOffsetY
+ + Math.cos(radians) * mState.mShadowOffsetX) - mState.mTranslationX;
+ final float shadowOffsetY = (float) (Math.cos(radians) * mState.mShadowOffsetY
+ - Math.sin(radians) * mState.mShadowOffsetX) - mState.mTranslationY;
+ canvas.drawBitmap(mState.mLastDrawnShadow, shadowOffsetX, shadowOffsetY,
+ mShadowPaint);
+ }
+ canvas.drawBitmap(mState.mLastDrawnIcon, null, bounds, mIconPaint);
+ canvas.restore();
+ }
+ }
+
+ @Override
+ public boolean canApplyTheme() {
+ return mState.canApplyTheme();
+ }
+
+ @ColorInt int getDrawableBackgroundColor() {
+ return mState.mOvalBackgroundColor.toArgb();
+ }
+
+ boolean hasOvalBg() {
+ return mState.mOvalBackgroundColor != null;
+ }
+
+ private void regenerateBitmapIconCache() {
+ final int width = getIntrinsicWidth();
+ final int height = getIntrinsicHeight();
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+
+ // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared.
+ final Drawable d = mState.mChildState.newDrawable().mutate();
+ setDrawableBounds(d);
+ canvas.save();
+ if (mState.mHorizontalFlip) {
+ canvas.scale(-1f, 1f, width * 0.5f, height * 0.5f);
+ }
+ d.draw(canvas);
+ canvas.restore();
+
+ if (mState.mIsHardwareBitmap) {
+ bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false);
+ }
+ mState.mLastDrawnIcon = bitmap;
+ }
+
+ private void regenerateBitmapShadowCache() {
+ if (mState.mShadowSize == 0) {
+ // No shadow
+ mState.mLastDrawnIcon = null;
+ return;
+ }
+
+ final int width = getIntrinsicWidth();
+ final int height = getIntrinsicHeight();
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+
+ // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared.
+ final Drawable d = mState.mChildState.newDrawable().mutate();
+ setDrawableBounds(d);
+ canvas.save();
+ if (mState.mHorizontalFlip) {
+ canvas.scale(-1f, 1f, width * 0.5f, height * 0.5f);
+ }
+ d.draw(canvas);
+ canvas.restore();
+
+ // Draws the shadow from original drawable
+ Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ paint.setMaskFilter(new BlurMaskFilter(mState.mShadowSize, Blur.NORMAL));
+ int[] offset = new int[2];
+ final Bitmap shadow = bitmap.extractAlpha(paint, offset);
+ paint.setMaskFilter(null);
+ bitmap.eraseColor(Color.TRANSPARENT);
+ canvas.drawBitmap(shadow, offset[0], offset[1], paint);
+
+ if (mState.mIsHardwareBitmap) {
+ bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false);
+ }
+ mState.mLastDrawnShadow = bitmap;
+ }
+
+ /**
+ * Set the alpha of the shadow. As dark intensity increases, drop the alpha of the shadow since
+ * dark color and shadow should not be visible at the same time.
+ */
+ private void updateShadowAlpha() {
+ // Update the color from the original color's alpha as the max
+ int alpha = Color.alpha(mState.mShadowColor);
+ mShadowPaint.setAlpha(
+ Math.round(alpha * (mState.mAlpha / 255f) * (1 - mState.mDarkIntensity)));
+ }
+
+ /**
+ * Prevent shadow clipping by offsetting the drawable bounds by the shadow and its offset
+ * @param d the drawable to set the bounds
+ */
+ private void setDrawableBounds(Drawable d) {
+ final int offsetX = mState.mShadowSize + Math.abs(mState.mShadowOffsetX);
+ final int offsetY = mState.mShadowSize + Math.abs(mState.mShadowOffsetY);
+ d.setBounds(offsetX, offsetY, getIntrinsicWidth() - offsetX,
+ getIntrinsicHeight() - offsetY);
+ }
+
+ private static class ShadowDrawableState extends ConstantState {
+ int mChangingConfigurations;
+ int mBaseWidth;
+ int mBaseHeight;
+ float mRotateDegrees;
+ float mTranslationX;
+ float mTranslationY;
+ int mShadowOffsetX;
+ int mShadowOffsetY;
+ int mShadowSize;
+ int mShadowColor;
+ float mDarkIntensity;
+ int mAlpha;
+ boolean mHorizontalFlip;
+
+ boolean mIsHardwareBitmap;
+ Bitmap mLastDrawnIcon;
+ Bitmap mLastDrawnShadow;
+ ConstantState mChildState;
+
+ final int mLightColor;
+ final int mDarkColor;
+ final boolean mSupportsAnimation;
+ final Color mOvalBackgroundColor;
+
+ public ShadowDrawableState(@ColorInt int lightColor, @ColorInt int darkColor,
+ boolean animated, boolean horizontalFlip, Color ovalBackgroundColor) {
+ mLightColor = lightColor;
+ mDarkColor = darkColor;
+ mSupportsAnimation = animated;
+ mAlpha = 255;
+ mHorizontalFlip = horizontalFlip;
+ mOvalBackgroundColor = ovalBackgroundColor;
+ }
+
+ @Override
+ public Drawable newDrawable() {
+ return new KeyButtonDrawable(null, this);
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mChangingConfigurations;
+ }
+
+ @Override
+ public boolean canApplyTheme() {
+ return true;
+ }
+ }
+
+ /**
+ * Creates a KeyButtonDrawable with a shadow given its icon. For more information, see
+ * {@link #create(Context, int, boolean, boolean)}.
+ */
+ public static KeyButtonDrawable create(Context context, @ColorInt int lightColor,
+ @ColorInt int darkColor, @DrawableRes int iconResId, boolean hasShadow,
+ Color ovalBackgroundColor) {
+ final Resources res = context.getResources();
+ boolean isRtl = res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ Drawable d = context.getDrawable(iconResId);
+ final KeyButtonDrawable drawable = new KeyButtonDrawable(d, lightColor, darkColor,
+ isRtl && d.isAutoMirrored(), ovalBackgroundColor);
+ if (hasShadow) {
+ int offsetX = dpToPx(NAV_KEY_BUTTON_SHADOW_OFFSET_X, res);
+ int offsetY = dpToPx(NAV_KEY_BUTTON_SHADOW_OFFSET_Y, res);
+ int radius = dpToPx(NAV_KEY_BUTTON_SHADOW_RADIUS, res);
+ int color = NAV_KEY_BUTTON_SHADOW_COLOR;
+ drawable.setShadowProperties(offsetX, offsetY, radius, color);
+ }
+ return drawable;
+ }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java
new file mode 100644
index 0000000..38a63b6
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java
@@ -0,0 +1,525 @@
+/*
+ * 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.inputmethodservice.navigationbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.DimenRes;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.CanvasProperty;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RecordingCanvas;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Trace;
+import android.view.RenderNodeAnimator;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+final class KeyButtonRipple extends Drawable {
+
+ private static final float GLOW_MAX_SCALE_FACTOR = 1.35f;
+ private static final float GLOW_MAX_ALPHA = 0.2f;
+ private static final float GLOW_MAX_ALPHA_DARK = 0.1f;
+ private static final int ANIMATION_DURATION_SCALE = 350;
+ private static final int ANIMATION_DURATION_FADE = 450;
+ private static final Interpolator ALPHA_OUT_INTERPOLATOR =
+ new PathInterpolator(0f, 0f, 0.8f, 1f);
+
+ @DimenRes
+ private final int mMaxWidthResource;
+
+ private Paint mRipplePaint;
+ private CanvasProperty<Float> mLeftProp;
+ private CanvasProperty<Float> mTopProp;
+ private CanvasProperty<Float> mRightProp;
+ private CanvasProperty<Float> mBottomProp;
+ private CanvasProperty<Float> mRxProp;
+ private CanvasProperty<Float> mRyProp;
+ private CanvasProperty<Paint> mPaintProp;
+ private float mGlowAlpha = 0f;
+ private float mGlowScale = 1f;
+ private boolean mPressed;
+ private boolean mVisible;
+ private boolean mDrawingHardwareGlow;
+ private int mMaxWidth;
+ private boolean mLastDark;
+ private boolean mDark;
+ private boolean mDelayTouchFeedback;
+
+ private final Interpolator mInterpolator = new LogInterpolator();
+ private boolean mSupportHardware;
+ private final View mTargetView;
+ private final Handler mHandler = new Handler();
+
+ private final HashSet<Animator> mRunningAnimations = new HashSet<>();
+ private final ArrayList<Animator> mTmpArray = new ArrayList<>();
+
+ private final TraceAnimatorListener mExitHwTraceAnimator =
+ new TraceAnimatorListener("exitHardware");
+ private final TraceAnimatorListener mEnterHwTraceAnimator =
+ new TraceAnimatorListener("enterHardware");
+
+ public enum Type {
+ OVAL,
+ ROUNDED_RECT
+ }
+
+ private Type mType = Type.ROUNDED_RECT;
+
+ public KeyButtonRipple(Context ctx, View targetView, @DimenRes int maxWidthResource) {
+ mMaxWidthResource = maxWidthResource;
+ mMaxWidth = ctx.getResources().getDimensionPixelSize(maxWidthResource);
+ mTargetView = targetView;
+ }
+
+ public void updateResources() {
+ mMaxWidth = mTargetView.getContext().getResources()
+ .getDimensionPixelSize(mMaxWidthResource);
+ invalidateSelf();
+ }
+
+ public void setDarkIntensity(float darkIntensity) {
+ mDark = darkIntensity >= 0.5f;
+ }
+
+ public void setDelayTouchFeedback(boolean delay) {
+ mDelayTouchFeedback = delay;
+ }
+
+ public void setType(Type type) {
+ mType = type;
+ }
+
+ private Paint getRipplePaint() {
+ if (mRipplePaint == null) {
+ mRipplePaint = new Paint();
+ mRipplePaint.setAntiAlias(true);
+ mRipplePaint.setColor(mLastDark ? 0xff000000 : 0xffffffff);
+ }
+ return mRipplePaint;
+ }
+
+ private void drawSoftware(Canvas canvas) {
+ if (mGlowAlpha > 0f) {
+ final Paint p = getRipplePaint();
+ p.setAlpha((int)(mGlowAlpha * 255f));
+
+ final float w = getBounds().width();
+ final float h = getBounds().height();
+ final boolean horizontal = w > h;
+ final float diameter = getRippleSize() * mGlowScale;
+ final float radius = diameter * .5f;
+ final float cx = w * .5f;
+ final float cy = h * .5f;
+ final float rx = horizontal ? radius : cx;
+ final float ry = horizontal ? cy : radius;
+ final float corner = horizontal ? cy : cx;
+
+ if (mType == Type.ROUNDED_RECT) {
+ canvas.drawRoundRect(cx - rx, cy - ry, cx + rx, cy + ry, corner, corner, p);
+ } else {
+ canvas.save();
+ canvas.translate(cx, cy);
+ float r = Math.min(rx, ry);
+ canvas.drawOval(-r, -r, r, r, p);
+ canvas.restore();
+ }
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ mSupportHardware = canvas.isHardwareAccelerated();
+ if (mSupportHardware) {
+ drawHardware((RecordingCanvas) canvas);
+ } else {
+ drawSoftware(canvas);
+ }
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ // Not supported.
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ // Not supported.
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ private boolean isHorizontal() {
+ return getBounds().width() > getBounds().height();
+ }
+
+ private void drawHardware(RecordingCanvas c) {
+ if (mDrawingHardwareGlow) {
+ if (mType == Type.ROUNDED_RECT) {
+ c.drawRoundRect(mLeftProp, mTopProp, mRightProp, mBottomProp, mRxProp, mRyProp,
+ mPaintProp);
+ } else {
+ CanvasProperty<Float> cx = CanvasProperty.createFloat(getBounds().width() / 2);
+ CanvasProperty<Float> cy = CanvasProperty.createFloat(getBounds().height() / 2);
+ int d = Math.min(getBounds().width(), getBounds().height());
+ CanvasProperty<Float> r = CanvasProperty.createFloat(1.0f * d / 2);
+ c.drawCircle(cx, cy, r, mPaintProp);
+ }
+ }
+ }
+
+ /** Gets the glow alpha, used by {@link android.animation.ObjectAnimator} via reflection. */
+ public float getGlowAlpha() {
+ return mGlowAlpha;
+ }
+
+ /** Sets the glow alpha, used by {@link android.animation.ObjectAnimator} via reflection. */
+ public void setGlowAlpha(float x) {
+ mGlowAlpha = x;
+ invalidateSelf();
+ }
+
+ /** Gets the glow scale, used by {@link android.animation.ObjectAnimator} via reflection. */
+ public float getGlowScale() {
+ return mGlowScale;
+ }
+
+ /** Sets the glow scale, used by {@link android.animation.ObjectAnimator} via reflection. */
+ public void setGlowScale(float x) {
+ mGlowScale = x;
+ invalidateSelf();
+ }
+
+ private float getMaxGlowAlpha() {
+ return mLastDark ? GLOW_MAX_ALPHA_DARK : GLOW_MAX_ALPHA;
+ }
+
+ @Override
+ protected boolean onStateChange(int[] state) {
+ boolean pressed = false;
+ for (int i = 0; i < state.length; i++) {
+ if (state[i] == android.R.attr.state_pressed) {
+ pressed = true;
+ break;
+ }
+ }
+ if (pressed != mPressed) {
+ setPressed(pressed);
+ mPressed = pressed;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean setVisible(boolean visible, boolean restart) {
+ boolean changed = super.setVisible(visible, restart);
+ if (changed) {
+ // End any existing animations when the visibility changes
+ jumpToCurrentState();
+ }
+ return changed;
+ }
+
+ @Override
+ public void jumpToCurrentState() {
+ endAnimations("jumpToCurrentState", false /* cancel */);
+ }
+
+ @Override
+ public boolean isStateful() {
+ return true;
+ }
+
+ @Override
+ public boolean hasFocusStateSpecified() {
+ return true;
+ }
+
+ public void setPressed(boolean pressed) {
+ if (mDark != mLastDark && pressed) {
+ mRipplePaint = null;
+ mLastDark = mDark;
+ }
+ if (mSupportHardware) {
+ setPressedHardware(pressed);
+ } else {
+ setPressedSoftware(pressed);
+ }
+ }
+
+ /**
+ * Abort the ripple while it is delayed and before shown used only when setShouldDelayStartTouch
+ * is enabled.
+ */
+ public void abortDelayedRipple() {
+ mHandler.removeCallbacksAndMessages(null);
+ }
+
+ private void endAnimations(String reason, boolean cancel) {
+ Trace.beginSection("KeyButtonRipple.endAnim: reason=" + reason + " cancel=" + cancel);
+ Trace.endSection();
+ mVisible = false;
+ mTmpArray.addAll(mRunningAnimations);
+ int size = mTmpArray.size();
+ for (int i = 0; i < size; i++) {
+ Animator a = mTmpArray.get(i);
+ if (cancel) {
+ a.cancel();
+ } else {
+ a.end();
+ }
+ }
+ mTmpArray.clear();
+ mRunningAnimations.clear();
+ mHandler.removeCallbacksAndMessages(null);
+ }
+
+ private void setPressedSoftware(boolean pressed) {
+ if (pressed) {
+ if (mDelayTouchFeedback) {
+ if (mRunningAnimations.isEmpty()) {
+ mHandler.removeCallbacksAndMessages(null);
+ mHandler.postDelayed(this::enterSoftware, ViewConfiguration.getTapTimeout());
+ } else if (mVisible) {
+ enterSoftware();
+ }
+ } else {
+ enterSoftware();
+ }
+ } else {
+ exitSoftware();
+ }
+ }
+
+ private void enterSoftware() {
+ endAnimations("enterSoftware", true /* cancel */);
+ mVisible = true;
+ mGlowAlpha = getMaxGlowAlpha();
+ ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(this, "glowScale",
+ 0f, GLOW_MAX_SCALE_FACTOR);
+ scaleAnimator.setInterpolator(mInterpolator);
+ scaleAnimator.setDuration(ANIMATION_DURATION_SCALE);
+ scaleAnimator.addListener(mAnimatorListener);
+ scaleAnimator.start();
+ mRunningAnimations.add(scaleAnimator);
+
+ // With the delay, it could eventually animate the enter animation with no pressed state,
+ // then immediately show the exit animation. If this is skipped there will be no ripple.
+ if (mDelayTouchFeedback && !mPressed) {
+ exitSoftware();
+ }
+ }
+
+ private void exitSoftware() {
+ ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(this, "glowAlpha", mGlowAlpha, 0f);
+ alphaAnimator.setInterpolator(ALPHA_OUT_INTERPOLATOR);
+ alphaAnimator.setDuration(ANIMATION_DURATION_FADE);
+ alphaAnimator.addListener(mAnimatorListener);
+ alphaAnimator.start();
+ mRunningAnimations.add(alphaAnimator);
+ }
+
+ private void setPressedHardware(boolean pressed) {
+ if (pressed) {
+ if (mDelayTouchFeedback) {
+ if (mRunningAnimations.isEmpty()) {
+ mHandler.removeCallbacksAndMessages(null);
+ mHandler.postDelayed(this::enterHardware, ViewConfiguration.getTapTimeout());
+ } else if (mVisible) {
+ enterHardware();
+ }
+ } else {
+ enterHardware();
+ }
+ } else {
+ exitHardware();
+ }
+ }
+
+ /**
+ * Sets the left/top property for the round rect to {@code prop} depending on whether we are
+ * horizontal or vertical mode.
+ */
+ private void setExtendStart(CanvasProperty<Float> prop) {
+ if (isHorizontal()) {
+ mLeftProp = prop;
+ } else {
+ mTopProp = prop;
+ }
+ }
+
+ private CanvasProperty<Float> getExtendStart() {
+ return isHorizontal() ? mLeftProp : mTopProp;
+ }
+
+ /**
+ * Sets the right/bottom property for the round rect to {@code prop} depending on whether we are
+ * horizontal or vertical mode.
+ */
+ private void setExtendEnd(CanvasProperty<Float> prop) {
+ if (isHorizontal()) {
+ mRightProp = prop;
+ } else {
+ mBottomProp = prop;
+ }
+ }
+
+ private CanvasProperty<Float> getExtendEnd() {
+ return isHorizontal() ? mRightProp : mBottomProp;
+ }
+
+ private int getExtendSize() {
+ return isHorizontal() ? getBounds().width() : getBounds().height();
+ }
+
+ private int getRippleSize() {
+ int size = isHorizontal() ? getBounds().width() : getBounds().height();
+ return Math.min(size, mMaxWidth);
+ }
+
+ private void enterHardware() {
+ endAnimations("enterHardware", true /* cancel */);
+ mVisible = true;
+ mDrawingHardwareGlow = true;
+ setExtendStart(CanvasProperty.createFloat(getExtendSize() / 2));
+ final RenderNodeAnimator startAnim = new RenderNodeAnimator(getExtendStart(),
+ getExtendSize()/2 - GLOW_MAX_SCALE_FACTOR * getRippleSize()/2);
+ startAnim.setDuration(ANIMATION_DURATION_SCALE);
+ startAnim.setInterpolator(mInterpolator);
+ startAnim.addListener(mAnimatorListener);
+ startAnim.setTarget(mTargetView);
+
+ setExtendEnd(CanvasProperty.createFloat(getExtendSize() / 2));
+ final RenderNodeAnimator endAnim = new RenderNodeAnimator(getExtendEnd(),
+ getExtendSize()/2 + GLOW_MAX_SCALE_FACTOR * getRippleSize()/2);
+ endAnim.setDuration(ANIMATION_DURATION_SCALE);
+ endAnim.setInterpolator(mInterpolator);
+ endAnim.addListener(mAnimatorListener);
+ endAnim.addListener(mEnterHwTraceAnimator);
+ endAnim.setTarget(mTargetView);
+
+ if (isHorizontal()) {
+ mTopProp = CanvasProperty.createFloat(0f);
+ mBottomProp = CanvasProperty.createFloat(getBounds().height());
+ mRxProp = CanvasProperty.createFloat(getBounds().height()/2);
+ mRyProp = CanvasProperty.createFloat(getBounds().height()/2);
+ } else {
+ mLeftProp = CanvasProperty.createFloat(0f);
+ mRightProp = CanvasProperty.createFloat(getBounds().width());
+ mRxProp = CanvasProperty.createFloat(getBounds().width()/2);
+ mRyProp = CanvasProperty.createFloat(getBounds().width()/2);
+ }
+
+ mGlowScale = GLOW_MAX_SCALE_FACTOR;
+ mGlowAlpha = getMaxGlowAlpha();
+ mRipplePaint = getRipplePaint();
+ mRipplePaint.setAlpha((int) (mGlowAlpha * 255));
+ mPaintProp = CanvasProperty.createPaint(mRipplePaint);
+
+ startAnim.start();
+ endAnim.start();
+ mRunningAnimations.add(startAnim);
+ mRunningAnimations.add(endAnim);
+
+ invalidateSelf();
+
+ // With the delay, it could eventually animate the enter animation with no pressed state,
+ // then immediately show the exit animation. If this is skipped there will be no ripple.
+ if (mDelayTouchFeedback && !mPressed) {
+ exitHardware();
+ }
+ }
+
+ private void exitHardware() {
+ mPaintProp = CanvasProperty.createPaint(getRipplePaint());
+ final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPaintProp,
+ RenderNodeAnimator.PAINT_ALPHA, 0);
+ opacityAnim.setDuration(ANIMATION_DURATION_FADE);
+ opacityAnim.setInterpolator(ALPHA_OUT_INTERPOLATOR);
+ opacityAnim.addListener(mAnimatorListener);
+ opacityAnim.addListener(mExitHwTraceAnimator);
+ opacityAnim.setTarget(mTargetView);
+
+ opacityAnim.start();
+ mRunningAnimations.add(opacityAnim);
+
+ invalidateSelf();
+ }
+
+ private final AnimatorListenerAdapter mAnimatorListener =
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mRunningAnimations.remove(animation);
+ if (mRunningAnimations.isEmpty() && !mPressed) {
+ mVisible = false;
+ mDrawingHardwareGlow = false;
+ invalidateSelf();
+ }
+ }
+ };
+
+ private static final class TraceAnimatorListener extends AnimatorListenerAdapter {
+ private final String mName;
+ TraceAnimatorListener(String name) {
+ mName = name;
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ Trace.beginSection("KeyButtonRipple.start." + mName);
+ Trace.endSection();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ Trace.beginSection("KeyButtonRipple.cancel." + mName);
+ Trace.endSection();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ Trace.beginSection("KeyButtonRipple.end." + mName);
+ Trace.endSection();
+ }
+ }
+
+ /**
+ * Interpolator with a smooth log deceleration
+ */
+ private static final class LogInterpolator implements Interpolator {
+ @Override
+ public float getInterpolation(float input) {
+ return 1 - (float) Math.pow(400, -input * 1.4);
+ }
+ }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
new file mode 100644
index 0000000..74d30f8
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
@@ -0,0 +1,370 @@
+/*
+ * 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.inputmethodservice.navigationbar;
+
+import static android.view.Display.INVALID_DISPLAY;
+import static android.view.KeyEvent.KEYCODE_BACK;
+import static android.view.KeyEvent.KEYCODE_UNKNOWN;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.inputmethodservice.InputMethodService;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.view.HapticFeedbackConstants;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.inputmethod.InputConnection;
+import android.widget.ImageView;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * @hide
+ */
+public class KeyButtonView extends ImageView implements ButtonInterface {
+ private static final String TAG = KeyButtonView.class.getSimpleName();
+
+ private final boolean mPlaySounds;
+ private long mDownTime;
+ private boolean mTracking;
+ private int mCode;
+ private int mTouchDownX;
+ private int mTouchDownY;
+ private AudioManager mAudioManager;
+ private boolean mGestureAborted;
+ @VisibleForTesting boolean mLongClicked;
+ private OnClickListener mOnClickListener;
+ private final KeyButtonRipple mRipple;
+ private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ private float mDarkIntensity;
+ private boolean mHasOvalBg = false;
+
+ private final Runnable mCheckLongPress = new Runnable() {
+ public void run() {
+ if (isPressed()) {
+ // Log.d("KeyButtonView", "longpressed: " + this);
+ if (isLongClickable()) {
+ // Just an old-fashioned ImageView
+ performLongClick();
+ mLongClicked = true;
+ } else {
+ if (mCode != KEYCODE_UNKNOWN) {
+ sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+ }
+ mLongClicked = true;
+ }
+ }
+ }
+ };
+
+ public KeyButtonView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ // TODO(b/205803355): Figure out better place to set this.
+ switch (getId()) {
+ case com.android.internal.R.id.input_method_nav_back:
+ mCode = KEYCODE_BACK;
+ break;
+ default:
+ mCode = KEYCODE_UNKNOWN;
+ break;
+ }
+
+ mPlaySounds = true;
+
+ setClickable(true);
+ mAudioManager = context.getSystemService(AudioManager.class);
+
+ mRipple = new KeyButtonRipple(context, this,
+ com.android.internal.R.dimen.input_method_nav_key_button_ripple_max_width);
+ setBackground(mRipple);
+ setWillNotDraw(false);
+ forceHasOverlappingRendering(false);
+ }
+
+ @Override
+ public boolean isClickable() {
+ return mCode != KEYCODE_UNKNOWN || super.isClickable();
+ }
+
+ public void setCode(int code) {
+ mCode = code;
+ }
+
+ @Override
+ public void setOnClickListener(OnClickListener onClickListener) {
+ super.setOnClickListener(onClickListener);
+ mOnClickListener = onClickListener;
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ if (mCode != KEYCODE_UNKNOWN) {
+ info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, null));
+ if (isLongClickable()) {
+ info.addAction(
+ new AccessibilityNodeInfo.AccessibilityAction(ACTION_LONG_CLICK, null));
+ }
+ }
+ }
+
+ @Override
+ protected void onWindowVisibilityChanged(int visibility) {
+ super.onWindowVisibilityChanged(visibility);
+ if (visibility != View.VISIBLE) {
+ jumpDrawablesToCurrentState();
+ }
+ }
+
+ @Override
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (action == ACTION_CLICK && mCode != KEYCODE_UNKNOWN) {
+ sendEvent(KeyEvent.ACTION_DOWN, 0, SystemClock.uptimeMillis());
+ sendEvent(KeyEvent.ACTION_UP, mTracking ? KeyEvent.FLAG_TRACKING : 0);
+ mTracking = false;
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
+ playSoundEffect(SoundEffectConstants.CLICK);
+ return true;
+ } else if (action == ACTION_LONG_CLICK && mCode != KEYCODE_UNKNOWN) {
+ sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
+ sendEvent(KeyEvent.ACTION_UP, mTracking ? KeyEvent.FLAG_TRACKING : 0);
+ mTracking = false;
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+ return true;
+ }
+ return super.performAccessibilityActionInternal(action, arguments);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ final boolean showSwipeUI = false; // mOverviewProxyService.shouldShowSwipeUpUI();
+ final int action = ev.getAction();
+ int x, y;
+ if (action == MotionEvent.ACTION_DOWN) {
+ mGestureAborted = false;
+ }
+ if (mGestureAborted) {
+ setPressed(false);
+ return false;
+ }
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mDownTime = SystemClock.uptimeMillis();
+ mLongClicked = false;
+ setPressed(true);
+
+ // Use raw X and Y to detect gestures in case a parent changes the x and y values
+ mTouchDownX = (int) ev.getRawX();
+ mTouchDownY = (int) ev.getRawY();
+ if (mCode != KEYCODE_UNKNOWN) {
+ sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);
+ } else {
+ // Provide the same haptic feedback that the system offers for virtual keys.
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ }
+ if (!showSwipeUI) {
+ playSoundEffect(SoundEffectConstants.CLICK);
+ }
+ removeCallbacks(mCheckLongPress);
+ postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
+ break;
+ case MotionEvent.ACTION_MOVE:
+ x = (int)ev.getRawX();
+ y = (int)ev.getRawY();
+
+ float slop = getQuickStepTouchSlopPx(getContext());
+ if (Math.abs(x - mTouchDownX) > slop || Math.abs(y - mTouchDownY) > slop) {
+ // When quick step is enabled, prevent animating the ripple triggered by
+ // setPressed and decide to run it on touch up
+ setPressed(false);
+ removeCallbacks(mCheckLongPress);
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ setPressed(false);
+ if (mCode != KEYCODE_UNKNOWN) {
+ sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
+ }
+ removeCallbacks(mCheckLongPress);
+ break;
+ case MotionEvent.ACTION_UP:
+ final boolean doIt = isPressed() && !mLongClicked;
+ setPressed(false);
+ final boolean doHapticFeedback = (SystemClock.uptimeMillis() - mDownTime) > 150;
+ if (showSwipeUI) {
+ if (doIt) {
+ // Apply haptic feedback on touch up since there is none on touch down
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ playSoundEffect(SoundEffectConstants.CLICK);
+ }
+ } else if (doHapticFeedback && !mLongClicked) {
+ // Always send a release ourselves because it doesn't seem to be sent elsewhere
+ // and it feels weird to sometimes get a release haptic and other times not.
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE);
+ }
+ if (mCode != KEYCODE_UNKNOWN) {
+ if (doIt) {
+ sendEvent(KeyEvent.ACTION_UP, mTracking ? KeyEvent.FLAG_TRACKING : 0);
+ mTracking = false;
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
+ } else {
+ sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
+ }
+ } else {
+ // no key code, just a regular ImageView
+ if (doIt && mOnClickListener != null) {
+ mOnClickListener.onClick(this);
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
+ }
+ }
+ removeCallbacks(mCheckLongPress);
+ break;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void setImageDrawable(Drawable drawable) {
+ super.setImageDrawable(drawable);
+
+ if (drawable == null) {
+ return;
+ }
+ KeyButtonDrawable keyButtonDrawable = (KeyButtonDrawable) drawable;
+ keyButtonDrawable.setDarkIntensity(mDarkIntensity);
+ mHasOvalBg = keyButtonDrawable.hasOvalBg();
+ if (mHasOvalBg) {
+ mOvalBgPaint.setColor(keyButtonDrawable.getDrawableBackgroundColor());
+ }
+ mRipple.setType(keyButtonDrawable.hasOvalBg() ? KeyButtonRipple.Type.OVAL
+ : KeyButtonRipple.Type.ROUNDED_RECT);
+ }
+
+ public void playSoundEffect(int soundConstant) {
+ if (!mPlaySounds) return;
+ mAudioManager.playSoundEffect(soundConstant);
+ }
+
+ public void sendEvent(int action, int flags) {
+ sendEvent(action, flags, SystemClock.uptimeMillis());
+ }
+
+ private void sendEvent(int action, int flags, long when) {
+ if (mCode == KeyEvent.KEYCODE_BACK && flags != KeyEvent.FLAG_LONG_PRESS) {
+ if (action == MotionEvent.ACTION_UP) {
+ // TODO(b/205803355): Implement notifyBackAction();
+ }
+ }
+
+ // TODO(b/205803355): Consolidate this logic to somewhere else.
+ if (mContext instanceof InputMethodService) {
+ final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
+ final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
+ 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
+ flags | KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+ InputDevice.SOURCE_KEYBOARD);
+ int displayId = INVALID_DISPLAY;
+
+ // Make KeyEvent work on multi-display environment
+ if (getDisplay() != null) {
+ displayId = getDisplay().getDisplayId();
+ }
+ if (displayId != INVALID_DISPLAY) {
+ ev.setDisplayId(displayId);
+ }
+ final InputMethodService ims = (InputMethodService) mContext;
+ final boolean handled;
+ switch (action) {
+ case KeyEvent.ACTION_DOWN:
+ handled = ims.onKeyDown(ev.getKeyCode(), ev);
+ mTracking = handled && ev.getRepeatCount() == 0 &&
+ (ev.getFlags() & KeyEvent.FLAG_START_TRACKING) != 0;
+ break;
+ case KeyEvent.ACTION_UP:
+ handled = ims.onKeyUp(ev.getKeyCode(), ev);
+ break;
+ default:
+ handled = false;
+ break;
+ }
+ if (!handled) {
+ final InputConnection ic = ims.getCurrentInputConnection();
+ if (ic != null) {
+ ic.sendKeyEvent(ev);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void setDarkIntensity(float darkIntensity) {
+ mDarkIntensity = darkIntensity;
+
+ Drawable drawable = getDrawable();
+ if (drawable != null) {
+ ((KeyButtonDrawable) drawable).setDarkIntensity(darkIntensity);
+ // Since we reuse the same drawable for multiple views, we need to invalidate the view
+ // manually.
+ invalidate();
+ }
+ mRipple.setDarkIntensity(darkIntensity);
+ }
+
+ @Override
+ public void setDelayTouchFeedback(boolean shouldDelay) {
+ mRipple.setDelayTouchFeedback(shouldDelay);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mHasOvalBg) {
+ int d = Math.min(getWidth(), getHeight());
+ canvas.drawOval(0, 0, d, d, mOvalBgPaint);
+ }
+ super.draw(canvas);
+ }
+
+ /**
+ * Ratio of quickstep touch slop (when system takes over the touch) to view touch slop
+ */
+ public static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3;
+
+ /**
+ * Touch slop for quickstep gesture
+ */
+ private static float getQuickStepTouchSlopPx(Context context) {
+ return QUICKSTEP_TOUCH_SLOP_RATIO * ViewConfiguration.get(context).getScaledTouchSlop();
+ }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java
new file mode 100644
index 0000000..93c5439
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java
@@ -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 android.inputmethodservice.navigationbar;
+
+import android.annotation.ColorInt;
+
+final class NavigationBarConstants {
+ private NavigationBarConstants() {
+ // Not intended to be instantiated.
+ }
+
+ // Copied from "navbar_back_button_ime_offset"
+ // TODO(b/215443343): Handle this in the drawable then remove this constant.
+ static final float NAVBAR_BACK_BUTTON_IME_OFFSET = 2.0f;
+
+ // Copied from "light_mode_icon_color_single_tone" at packages/SettingsLib/res/values/colors.xml
+ @ColorInt
+ static final int LIGHT_MODE_ICON_COLOR_SINGLE_TONE = 0xffffffff;
+
+ // Copied from "dark_mode_icon_color_single_tone" at packages/SettingsLib/res/values/colors.xml
+ @ColorInt
+ static final int DARK_MODE_ICON_COLOR_SINGLE_TONE = 0x99000000;
+
+ // Copied from "navigation_bar_deadzone_hold"
+ static final int NAVIGATION_BAR_DEADZONE_HOLD = 333;
+
+ // Copied from "navigation_bar_deadzone_hold"
+ static final int NAVIGATION_BAR_DEADZONE_DECAY = 333;
+
+ // Copied from "navigation_bar_deadzone_size"
+ static final float NAVIGATION_BAR_DEADZONE_SIZE = 12.0f;
+
+ // Copied from "navigation_bar_deadzone_size_max"
+ static final float NAVIGATION_BAR_DEADZONE_SIZE_MAX = 32.0f;
+
+ // Copied from "nav_key_button_shadow_offset_x"
+ static final float NAV_KEY_BUTTON_SHADOW_OFFSET_X = 0.0f;
+
+ // Copied from "nav_key_button_shadow_offset_y"
+ static final float NAV_KEY_BUTTON_SHADOW_OFFSET_Y = 1.0f;
+
+ // Copied from "nav_key_button_shadow_radius"
+ static final float NAV_KEY_BUTTON_SHADOW_RADIUS = 0.5f;
+
+ // Copied from "nav_key_button_shadow_color"
+ @ColorInt
+ static final int NAV_KEY_BUTTON_SHADOW_COLOR = 0x30000000;
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java
new file mode 100644
index 0000000..f01173e
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java
@@ -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 android.inputmethodservice.navigationbar;
+
+import static android.view.MotionEvent.ACTION_OUTSIDE;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.FrameLayout;
+
+/**
+ * @hide
+ */
+public final class NavigationBarFrame extends FrameLayout {
+
+ private DeadZone mDeadZone = null;
+
+ public NavigationBarFrame(@NonNull Context context) {
+ super(context);
+ }
+
+ public NavigationBarFrame(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public NavigationBarFrame(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public void setDeadZone(@NonNull DeadZone deadZone) {
+ mDeadZone = deadZone;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ if (event.getAction() == ACTION_OUTSIDE) {
+ if (mDeadZone != null) {
+ return mDeadZone.onTouchEvent(event);
+ }
+ }
+ return super.dispatchTouchEvent(event);
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java
new file mode 100644
index 0000000..d488890
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java
@@ -0,0 +1,429 @@
+/*
+ * 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.inputmethodservice.navigationbar;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.inputmethodservice.navigationbar.ReverseLinearLayout.ReverseRelativeLayout;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.Space;
+
+/**
+ * @hide
+ */
+public final class NavigationBarInflaterView extends FrameLayout {
+
+ private static final String TAG = "NavBarInflater";
+
+ public static final String NAV_BAR_VIEWS = "sysui_nav_bar";
+ public static final String NAV_BAR_LEFT = "sysui_nav_bar_left";
+ public static final String NAV_BAR_RIGHT = "sysui_nav_bar_right";
+
+ public static final String MENU_IME_ROTATE = "menu_ime";
+ public static final String BACK = "back";
+ public static final String HOME = "home";
+ public static final String RECENT = "recent";
+ public static final String NAVSPACE = "space";
+ public static final String CLIPBOARD = "clipboard";
+ public static final String HOME_HANDLE = "home_handle";
+ public static final String KEY = "key";
+ public static final String LEFT = "left";
+ public static final String RIGHT = "right";
+ public static final String CONTEXTUAL = "contextual";
+ public static final String IME_SWITCHER = "ime_switcher";
+
+ public static final String GRAVITY_SEPARATOR = ";";
+ public static final String BUTTON_SEPARATOR = ",";
+
+ public static final String SIZE_MOD_START = "[";
+ public static final String SIZE_MOD_END = "]";
+
+ public static final String KEY_CODE_START = "(";
+ public static final String KEY_IMAGE_DELIM = ":";
+ public static final String KEY_CODE_END = ")";
+ private static final String WEIGHT_SUFFIX = "W";
+ private static final String WEIGHT_CENTERED_SUFFIX = "WC";
+ private static final String ABSOLUTE_SUFFIX = "A";
+ private static final String ABSOLUTE_VERTICAL_CENTERED_SUFFIX = "C";
+
+ // Copied from "config_navBarLayoutHandle:
+ private static final String CONFIG_NAV_BAR_LAYOUT_HANDLE =
+ "back[70AC];home_handle;ime_switcher[70AC]";
+
+ protected LayoutInflater mLayoutInflater;
+ protected LayoutInflater mLandscapeInflater;
+
+ protected FrameLayout mHorizontal;
+
+ SparseArray<ButtonDispatcher> mButtonDispatchers;
+
+ private View mLastPortrait;
+ private View mLastLandscape;
+
+ private boolean mAlternativeOrder;
+
+ public NavigationBarInflaterView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ createInflaters();
+ }
+
+ void createInflaters() {
+ mLayoutInflater = LayoutInflater.from(mContext);
+ Configuration landscape = new Configuration();
+ landscape.setTo(mContext.getResources().getConfiguration());
+ landscape.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mLandscapeInflater = LayoutInflater.from(mContext.createConfigurationContext(landscape));
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ inflateChildren();
+ clearViews();
+ inflateLayout(getDefaultLayout());
+ }
+
+ private void inflateChildren() {
+ removeAllViews();
+ mHorizontal = (FrameLayout) mLayoutInflater.inflate(
+ com.android.internal.R.layout.input_method_navigation_layout,
+ this /* root */, false /* attachToRoot */);
+ addView(mHorizontal);
+ updateAlternativeOrder();
+ }
+
+ String getDefaultLayout() {
+ return CONFIG_NAV_BAR_LAYOUT_HANDLE;
+ }
+
+ public void setButtonDispatchers(SparseArray<ButtonDispatcher> buttonDispatchers) {
+ mButtonDispatchers = buttonDispatchers;
+ for (int i = 0; i < buttonDispatchers.size(); i++) {
+ initiallyFill(buttonDispatchers.valueAt(i));
+ }
+ }
+
+ void updateButtonDispatchersCurrentView() {
+ if (mButtonDispatchers != null) {
+ View view = mHorizontal;
+ for (int i = 0; i < mButtonDispatchers.size(); i++) {
+ final ButtonDispatcher dispatcher = mButtonDispatchers.valueAt(i);
+ dispatcher.setCurrentView(view);
+ }
+ }
+ }
+
+ void setAlternativeOrder(boolean alternativeOrder) {
+ if (alternativeOrder != mAlternativeOrder) {
+ mAlternativeOrder = alternativeOrder;
+ updateAlternativeOrder();
+ }
+ }
+
+ private void updateAlternativeOrder() {
+ updateAlternativeOrder(mHorizontal.findViewById(
+ com.android.internal.R.id.input_method_nav_ends_group));
+ updateAlternativeOrder(mHorizontal.findViewById(
+ com.android.internal.R.id.input_method_nav_center_group));
+ }
+
+ private void updateAlternativeOrder(View v) {
+ if (v instanceof ReverseLinearLayout) {
+ ((ReverseLinearLayout) v).setAlternativeOrder(mAlternativeOrder);
+ }
+ }
+
+ private void initiallyFill(
+ ButtonDispatcher buttonDispatcher) {
+ addAll(buttonDispatcher, mHorizontal.findViewById(
+ com.android.internal.R.id.input_method_nav_ends_group));
+ addAll(buttonDispatcher, mHorizontal.findViewById(
+ com.android.internal.R.id.input_method_nav_center_group));
+ }
+
+ private void addAll(ButtonDispatcher buttonDispatcher, ViewGroup parent) {
+ for (int i = 0; i < parent.getChildCount(); i++) {
+ // Need to manually search for each id, just in case each group has more than one
+ // of a single id. It probably mostly a waste of time, but shouldn't take long
+ // and will only happen once.
+ if (parent.getChildAt(i).getId() == buttonDispatcher.getId()) {
+ buttonDispatcher.addView(parent.getChildAt(i));
+ }
+ if (parent.getChildAt(i) instanceof ViewGroup) {
+ addAll(buttonDispatcher, (ViewGroup) parent.getChildAt(i));
+ }
+ }
+ }
+
+ protected void inflateLayout(String newLayout) {
+ if (newLayout == null) {
+ newLayout = getDefaultLayout();
+ }
+ String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);
+ if (sets.length != 3) {
+ Log.d(TAG, "Invalid layout.");
+ newLayout = getDefaultLayout();
+ sets = newLayout.split(GRAVITY_SEPARATOR, 3);
+ }
+ String[] start = sets[0].split(BUTTON_SEPARATOR);
+ String[] center = sets[1].split(BUTTON_SEPARATOR);
+ String[] end = sets[2].split(BUTTON_SEPARATOR);
+ // Inflate these in start to end order or accessibility traversal will be messed up.
+ inflateButtons(start, mHorizontal.findViewById(
+ com.android.internal.R.id.input_method_nav_ends_group),
+ false /* landscape */, true /* start */);
+
+ inflateButtons(center, mHorizontal.findViewById(
+ com.android.internal.R.id.input_method_nav_center_group),
+ false /* landscape */, false /* start */);
+
+ addGravitySpacer(mHorizontal.findViewById(
+ com.android.internal.R.id.input_method_nav_ends_group));
+
+ inflateButtons(end, mHorizontal.findViewById(
+ com.android.internal.R.id.input_method_nav_ends_group),
+ false /* landscape */, false /* start */);
+
+ updateButtonDispatchersCurrentView();
+ }
+
+ private void addGravitySpacer(LinearLayout layout) {
+ layout.addView(new Space(mContext), new LinearLayout.LayoutParams(0, 0, 1));
+ }
+
+ private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape,
+ boolean start) {
+ for (int i = 0; i < buttons.length; i++) {
+ inflateButton(buttons[i], parent, landscape, start);
+ }
+ }
+
+ private ViewGroup.LayoutParams copy(ViewGroup.LayoutParams layoutParams) {
+ if (layoutParams instanceof LinearLayout.LayoutParams) {
+ return new LinearLayout.LayoutParams(layoutParams.width, layoutParams.height,
+ ((LinearLayout.LayoutParams) layoutParams).weight);
+ }
+ return new LayoutParams(layoutParams.width, layoutParams.height);
+ }
+
+ @Nullable
+ protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
+ boolean start) {
+ LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
+ View v = createView(buttonSpec, parent, inflater);
+ if (v == null) return null;
+
+ v = applySize(v, buttonSpec, landscape, start);
+ parent.addView(v);
+ addToDispatchers(v);
+ View lastView = landscape ? mLastLandscape : mLastPortrait;
+ View accessibilityView = v;
+ if (v instanceof ReverseRelativeLayout) {
+ accessibilityView = ((ReverseRelativeLayout) v).getChildAt(0);
+ }
+ if (lastView != null) {
+ accessibilityView.setAccessibilityTraversalAfter(lastView.getId());
+ }
+ if (landscape) {
+ mLastLandscape = accessibilityView;
+ } else {
+ mLastPortrait = accessibilityView;
+ }
+ return v;
+ }
+
+ private View applySize(View v, String buttonSpec, boolean landscape, boolean start) {
+ String sizeStr = extractSize(buttonSpec);
+ if (sizeStr == null) return v;
+
+ if (sizeStr.contains(WEIGHT_SUFFIX) || sizeStr.contains(ABSOLUTE_SUFFIX)) {
+ // To support gravity, wrap in RelativeLayout and apply gravity to it.
+ // Children wanting to use gravity must be smaller than the frame.
+ ReverseRelativeLayout frame = new ReverseRelativeLayout(mContext);
+ LayoutParams childParams = new LayoutParams(v.getLayoutParams());
+
+ // Compute gravity to apply
+ int gravity = (landscape) ? (start ? Gravity.TOP : Gravity.BOTTOM)
+ : (start ? Gravity.START : Gravity.END);
+ if (sizeStr.endsWith(WEIGHT_CENTERED_SUFFIX)) {
+ gravity = Gravity.CENTER;
+ } else if (sizeStr.endsWith(ABSOLUTE_VERTICAL_CENTERED_SUFFIX)) {
+ gravity = Gravity.CENTER_VERTICAL;
+ }
+
+ // Set default gravity, flipped if needed in reversed layouts (270 RTL and 90 LTR)
+ frame.setDefaultGravity(gravity);
+ frame.setGravity(gravity); // Apply gravity to root
+
+ frame.addView(v, childParams);
+
+ if (sizeStr.contains(WEIGHT_SUFFIX)) {
+ // Use weighting to set the width of the frame
+ float weight = Float.parseFloat(
+ sizeStr.substring(0, sizeStr.indexOf(WEIGHT_SUFFIX)));
+ frame.setLayoutParams(new LinearLayout.LayoutParams(0, MATCH_PARENT, weight));
+ } else {
+ int width = (int) convertDpToPx(mContext,
+ Float.parseFloat(sizeStr.substring(0, sizeStr.indexOf(ABSOLUTE_SUFFIX))));
+ frame.setLayoutParams(new LinearLayout.LayoutParams(width, MATCH_PARENT));
+ }
+
+ // Ensure ripples can be drawn outside bounds
+ frame.setClipChildren(false);
+ frame.setClipToPadding(false);
+
+ return frame;
+ }
+
+ float size = Float.parseFloat(sizeStr);
+ ViewGroup.LayoutParams params = v.getLayoutParams();
+ params.width = (int) (params.width * size);
+ return v;
+ }
+
+ View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
+ View v = null;
+ String button = extractButton(buttonSpec);
+ if (LEFT.equals(button)) {
+ button = extractButton(NAVSPACE);
+ } else if (RIGHT.equals(button)) {
+ button = extractButton(MENU_IME_ROTATE);
+ }
+ if (HOME.equals(button)) {
+ //v = inflater.inflate(R.layout.home, parent, false);
+ } else if (BACK.equals(button)) {
+ v = inflater.inflate(com.android.internal.R.layout.input_method_nav_back, parent,
+ false);
+ } else if (RECENT.equals(button)) {
+ //v = inflater.inflate(R.layout.recent_apps, parent, false);
+ } else if (MENU_IME_ROTATE.equals(button)) {
+ //v = inflater.inflate(R.layout.menu_ime, parent, false);
+ } else if (NAVSPACE.equals(button)) {
+ //v = inflater.inflate(R.layout.nav_key_space, parent, false);
+ } else if (CLIPBOARD.equals(button)) {
+ //v = inflater.inflate(R.layout.clipboard, parent, false);
+ } else if (CONTEXTUAL.equals(button)) {
+ //v = inflater.inflate(R.layout.contextual, parent, false);
+ } else if (HOME_HANDLE.equals(button)) {
+ v = inflater.inflate(com.android.internal.R.layout.input_method_nav_home_handle,
+ parent, false);
+ } else if (IME_SWITCHER.equals(button)) {
+ v = inflater.inflate(com.android.internal.R.layout.input_method_nav_ime_switcher,
+ parent, false);
+ } else if (button.startsWith(KEY)) {
+ /*
+ String uri = extractImage(button);
+ int code = extractKeycode(button);
+ v = inflater.inflate(R.layout.custom_key, parent, false);
+ ((KeyButtonView) v).setCode(code);
+ if (uri != null) {
+ if (uri.contains(":")) {
+ ((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri));
+ } else if (uri.contains("/")) {
+ int index = uri.indexOf('/');
+ String pkg = uri.substring(0, index);
+ int id = Integer.parseInt(uri.substring(index + 1));
+ ((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id));
+ }
+ }
+ */
+ }
+ return v;
+ }
+
+ /*
+ public static String extractImage(String buttonSpec) {
+ if (!buttonSpec.contains(KEY_IMAGE_DELIM)) {
+ return null;
+ }
+ final int start = buttonSpec.indexOf(KEY_IMAGE_DELIM);
+ String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_CODE_END));
+ return subStr;
+ }
+
+ public static int extractKeycode(String buttonSpec) {
+ if (!buttonSpec.contains(KEY_CODE_START)) {
+ return 1;
+ }
+ final int start = buttonSpec.indexOf(KEY_CODE_START);
+ String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_IMAGE_DELIM));
+ return Integer.parseInt(subStr);
+ }
+ */
+
+ public static String extractSize(String buttonSpec) {
+ if (!buttonSpec.contains(SIZE_MOD_START)) {
+ return null;
+ }
+ final int sizeStart = buttonSpec.indexOf(SIZE_MOD_START);
+ return buttonSpec.substring(sizeStart + 1, buttonSpec.indexOf(SIZE_MOD_END));
+ }
+
+ public static String extractButton(String buttonSpec) {
+ if (!buttonSpec.contains(SIZE_MOD_START)) {
+ return buttonSpec;
+ }
+ return buttonSpec.substring(0, buttonSpec.indexOf(SIZE_MOD_START));
+ }
+
+ private void addToDispatchers(View v) {
+ if (mButtonDispatchers != null) {
+ final int indexOfKey = mButtonDispatchers.indexOfKey(v.getId());
+ if (indexOfKey >= 0) {
+ mButtonDispatchers.valueAt(indexOfKey).addView(v);
+ }
+ if (v instanceof ViewGroup) {
+ final ViewGroup viewGroup = (ViewGroup)v;
+ final int N = viewGroup.getChildCount();
+ for (int i = 0; i < N; i++) {
+ addToDispatchers(viewGroup.getChildAt(i));
+ }
+ }
+ }
+ }
+
+ private void clearViews() {
+ if (mButtonDispatchers != null) {
+ for (int i = 0; i < mButtonDispatchers.size(); i++) {
+ mButtonDispatchers.valueAt(i).clear();
+ }
+ }
+ clearAllChildren(mHorizontal.findViewById(
+ com.android.internal.R.id.input_method_nav_buttons));
+ }
+
+ private void clearAllChildren(ViewGroup group) {
+ for (int i = 0; i < group.getChildCount(); i++) {
+ ((ViewGroup) group.getChildAt(i)).removeAllViews();
+ }
+ }
+
+ private static float convertDpToPx(Context context, float dp) {
+ return dp * context.getResources().getDisplayMetrics().density;
+ }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarUtils.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarUtils.java
new file mode 100644
index 0000000..c6096d7
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarUtils.java
@@ -0,0 +1,42 @@
+/*
+ * 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.inputmethodservice.navigationbar;
+
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+
+import android.content.res.Resources;
+import android.util.TypedValue;
+
+final class NavigationBarUtils {
+ private NavigationBarUtils() {
+ // Not intended to be instantiated.
+ }
+
+ /**
+ * A utility method to convert "dp" to "pixel".
+ *
+ * <p>TODO(b/215443343): Remove this method by migrating DP values from
+ * {@link NavigationBarConstants} to resource files.</p>
+ *
+ * @param dpValue "dp" value to be converted to "pixel"
+ * @param res {@link Resources} to be used when dealing with "dp".
+ * @return the pixels for a given dp value.
+ */
+ static int dpToPx(float dpValue, Resources res) {
+ return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, res.getDisplayMetrics());
+ }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
new file mode 100644
index 0000000..4284778
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
@@ -0,0 +1,380 @@
+/*
+ * 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.inputmethodservice.navigationbar;
+
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.DARK_MODE_ICON_COLOR_SINGLE_TONE;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.LIGHT_MODE_ICON_COLOR_SINGLE_TONE;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVBAR_BACK_BUTTON_IME_OFFSET;
+import static android.inputmethodservice.navigationbar.NavigationBarUtils.dpToPx;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.annotation.DrawableRes;
+import android.annotation.FloatRange;
+import android.app.StatusBarManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.FrameLayout;
+
+import java.util.function.Consumer;
+
+/**
+ * @hide
+ */
+public final class NavigationBarView extends FrameLayout {
+ final static boolean DEBUG = false;
+ final static String TAG = "NavBarView";
+
+ // Copied from com.android.systemui.animation.Interpolators#FAST_OUT_SLOW_IN
+ private static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+
+ // The current view is always mHorizontal.
+ View mCurrentView = null;
+ private View mHorizontal;
+
+ private int mCurrentRotation = -1;
+
+ int mDisabledFlags = 0;
+ int mNavigationIconHints = StatusBarManager.NAVIGATION_HINT_BACK_ALT;
+ private final int mNavBarMode = NAV_BAR_MODE_GESTURAL;
+
+ private KeyButtonDrawable mBackIcon;
+ private KeyButtonDrawable mImeSwitcherIcon;
+ private Context mLightContext;
+ private final int mLightIconColor;
+ private final int mDarkIconColor;
+
+ private final android.inputmethodservice.navigationbar.DeadZone mDeadZone;
+ private boolean mDeadZoneConsuming = false;
+
+ private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();
+ private Configuration mConfiguration;
+ private Configuration mTmpLastConfiguration;
+
+ private NavigationBarInflaterView mNavigationInflaterView;
+
+ public NavigationBarView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ mLightContext = context;
+ mLightIconColor = LIGHT_MODE_ICON_COLOR_SINGLE_TONE;
+ mDarkIconColor = DARK_MODE_ICON_COLOR_SINGLE_TONE;
+
+ mConfiguration = new Configuration();
+ mTmpLastConfiguration = new Configuration();
+ mConfiguration.updateFrom(context.getResources().getConfiguration());
+
+ mButtonDispatchers.put(com.android.internal.R.id.input_method_nav_back,
+ new ButtonDispatcher(com.android.internal.R.id.input_method_nav_back));
+ mButtonDispatchers.put(com.android.internal.R.id.input_method_nav_ime_switcher,
+ new ButtonDispatcher(com.android.internal.R.id.input_method_nav_ime_switcher));
+ mButtonDispatchers.put(com.android.internal.R.id.input_method_nav_home_handle,
+ new ButtonDispatcher(com.android.internal.R.id.input_method_nav_home_handle));
+
+ mDeadZone = new android.inputmethodservice.navigationbar.DeadZone(this);
+
+ getImeSwitchButton().setOnClickListener(view -> view.getContext()
+ .getSystemService(InputMethodManager.class).showInputMethodPicker());
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ return shouldDeadZoneConsumeTouchEvents(event) || super.onInterceptTouchEvent(event);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ shouldDeadZoneConsumeTouchEvents(event);
+ return super.onTouchEvent(event);
+ }
+
+ private boolean shouldDeadZoneConsumeTouchEvents(MotionEvent event) {
+ int action = event.getActionMasked();
+ if (action == MotionEvent.ACTION_DOWN) {
+ mDeadZoneConsuming = false;
+ }
+ if (mDeadZone.onTouchEvent(event) || mDeadZoneConsuming) {
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mDeadZoneConsuming = true;
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ mDeadZoneConsuming = false;
+ break;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public View getCurrentView() {
+ return mCurrentView;
+ }
+
+ /**
+ * Applies {@param consumer} to each of the nav bar views.
+ */
+ public void forEachView(Consumer<View> consumer) {
+ if (mHorizontal != null) {
+ consumer.accept(mHorizontal);
+ }
+ }
+
+ public ButtonDispatcher getBackButton() {
+ return mButtonDispatchers.get(com.android.internal.R.id.input_method_nav_back);
+ }
+
+ public ButtonDispatcher getImeSwitchButton() {
+ return mButtonDispatchers.get(com.android.internal.R.id.input_method_nav_ime_switcher);
+ }
+
+ public ButtonDispatcher getHomeHandle() {
+ return mButtonDispatchers.get(com.android.internal.R.id.input_method_nav_home_handle);
+ }
+
+ public SparseArray<ButtonDispatcher> getButtonDispatchers() {
+ return mButtonDispatchers;
+ }
+
+ private void reloadNavIcons() {
+ updateIcons(Configuration.EMPTY);
+ }
+
+ private void updateIcons(Configuration oldConfig) {
+ final boolean orientationChange = oldConfig.orientation != mConfiguration.orientation;
+ final boolean densityChange = oldConfig.densityDpi != mConfiguration.densityDpi;
+ final boolean dirChange =
+ oldConfig.getLayoutDirection() != mConfiguration.getLayoutDirection();
+
+ if (densityChange || dirChange) {
+ mImeSwitcherIcon = getDrawable(com.android.internal.R.drawable.ic_ime_switcher);
+ }
+ if (orientationChange || densityChange || dirChange) {
+ mBackIcon = getBackDrawable();
+ }
+ }
+
+ public KeyButtonDrawable getBackDrawable() {
+ KeyButtonDrawable drawable = getDrawable(com.android.internal.R.drawable.ic_ime_nav_back);
+ orientBackButton(drawable);
+ return drawable;
+ }
+
+ /**
+ * @return whether this nav bar mode is edge to edge
+ */
+ public static boolean isGesturalMode(int mode) {
+ return mode == NAV_BAR_MODE_GESTURAL;
+ }
+
+ private void orientBackButton(KeyButtonDrawable drawable) {
+ final boolean useAltBack =
+ (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
+ final boolean isRtl = mConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ float degrees = useAltBack ? (isRtl ? 90 : -90) : 0;
+ if (drawable.getRotation() == degrees) {
+ return;
+ }
+
+ if (isGesturalMode(mNavBarMode)) {
+ drawable.setRotation(degrees);
+ return;
+ }
+
+ // Animate the back button's rotation to the new degrees and only in portrait move up the
+ // back button to line up with the other buttons
+ float targetY = useAltBack
+ ? - dpToPx(NAVBAR_BACK_BUTTON_IME_OFFSET, getResources())
+ : 0;
+ ObjectAnimator navBarAnimator = ObjectAnimator.ofPropertyValuesHolder(drawable,
+ PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_ROTATE, degrees),
+ PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_TRANSLATE_Y, targetY));
+ navBarAnimator.setInterpolator(FAST_OUT_SLOW_IN);
+ navBarAnimator.setDuration(200);
+ navBarAnimator.start();
+ }
+
+ private KeyButtonDrawable getDrawable(@DrawableRes int icon) {
+ return KeyButtonDrawable.create(mLightContext, mLightIconColor, mDarkIconColor, icon,
+ true /* hasShadow */, null /* ovalBackgroundColor */);
+ }
+
+ @Override
+ public void setLayoutDirection(int layoutDirection) {
+ reloadNavIcons();
+
+ super.setLayoutDirection(layoutDirection);
+ }
+
+ public void setNavigationIconHints(int hints) {
+ if (hints == mNavigationIconHints) return;
+ final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
+ final boolean oldBackAlt =
+ (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
+ if (newBackAlt != oldBackAlt) {
+ //onImeVisibilityChanged(newBackAlt);
+ }
+
+ if (DEBUG) {
+ android.widget.Toast.makeText(getContext(), "Navigation icon hints = " + hints, 500)
+ .show();
+ }
+ mNavigationIconHints = hints;
+ updateNavButtonIcons();
+ }
+
+ public void setDisabledFlags(int disabledFlags) {
+ if (mDisabledFlags == disabledFlags) return;
+
+ mDisabledFlags = disabledFlags;
+
+ updateNavButtonIcons();
+ }
+
+ public void updateNavButtonIcons() {
+ // We have to replace or restore the back and home button icons when exiting or entering
+ // carmode, respectively. Recents are not available in CarMode in nav bar so change
+ // to recent icon is not required.
+ KeyButtonDrawable backIcon = mBackIcon;
+ orientBackButton(backIcon);
+ getBackButton().setImageDrawable(backIcon);
+
+ getImeSwitchButton().setImageDrawable(mImeSwitcherIcon);
+
+ // Update IME button visibility, a11y and rotate button always overrides the appearance
+ final boolean imeSwitcherVisible =
+ (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0;
+ getImeSwitchButton().setVisibility(imeSwitcherVisible ? View.VISIBLE : View.INVISIBLE);
+
+ getBackButton().setVisibility(View.VISIBLE);
+ getHomeHandle().setVisibility(View.INVISIBLE);
+
+ // We used to be reporting the touch regions via notifyActiveTouchRegions() here.
+ // TODO(b/215593010): Consider taking care of this in the Launcher side.
+ }
+
+ private Display getContextDisplay() {
+ return getContext().getDisplay();
+ }
+
+ @Override
+ public void onFinishInflate() {
+ super.onFinishInflate();
+ mNavigationInflaterView = findViewById(com.android.internal.R.id.input_method_nav_inflater);
+ mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
+
+ updateOrientationViews();
+ reloadNavIcons();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ mDeadZone.onDraw(canvas);
+ super.onDraw(canvas);
+ }
+
+ private void updateOrientationViews() {
+ mHorizontal = findViewById(com.android.internal.R.id.input_method_nav_horizontal);
+
+ updateCurrentView();
+ }
+
+ private void updateCurrentView() {
+ resetViews();
+ mCurrentView = mHorizontal;
+ mCurrentView.setVisibility(View.VISIBLE);
+ mCurrentRotation = getContextDisplay().getRotation();
+ mNavigationInflaterView.setAlternativeOrder(mCurrentRotation == Surface.ROTATION_90);
+ mNavigationInflaterView.updateButtonDispatchersCurrentView();
+ }
+
+ private void resetViews() {
+ mHorizontal.setVisibility(View.GONE);
+ }
+
+ public void reorient() {
+ updateCurrentView();
+
+ final android.inputmethodservice.navigationbar.NavigationBarFrame frame =
+ getRootView().findViewByPredicate(view -> view instanceof NavigationBarFrame);
+ frame.setDeadZone(mDeadZone);
+ mDeadZone.onConfigurationChanged(mCurrentRotation);
+
+ if (DEBUG) {
+ Log.d(TAG, "reorient(): rot=" + mCurrentRotation);
+ }
+
+ // Resolve layout direction if not resolved since components changing layout direction such
+ // as changing languages will recreate this view and the direction will be resolved later
+ if (!isLayoutDirectionResolved()) {
+ resolveLayoutDirection();
+ }
+ updateNavButtonIcons();
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mTmpLastConfiguration.updateFrom(mConfiguration);
+ final int changes = mConfiguration.updateFrom(newConfig);
+
+ updateIcons(mTmpLastConfiguration);
+ if (mTmpLastConfiguration.densityDpi != mConfiguration.densityDpi
+ || mTmpLastConfiguration.getLayoutDirection()
+ != mConfiguration.getLayoutDirection()) {
+ // If car mode or density changes, we need to reset the icons.
+ updateNavButtonIcons();
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ // This needs to happen first as it can changed the enabled state which can affect whether
+ // the back button is visible
+ requestApplyInsets();
+ reorient();
+ updateNavButtonIcons();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ for (int i = 0; i < mButtonDispatchers.size(); ++i) {
+ mButtonDispatchers.valueAt(i).onDestroy();
+ }
+ }
+
+ public void setDarkIntensity(@FloatRange(from = 0.0f, to = 1.0f) float intensity) {
+ for (int i = 0; i < mButtonDispatchers.size(); ++i) {
+ mButtonDispatchers.valueAt(i).setDarkIntensity(intensity);
+ }
+ }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationHandle.java b/core/java/android/inputmethodservice/navigationbar/NavigationHandle.java
new file mode 100644
index 0000000..273cafb
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationHandle.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.inputmethodservice.navigationbar;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * TODO(b/215443343): Remove this file, as IME actually doesn't use this.
+ *
+ * @hide
+ */
+public class NavigationHandle extends View implements ButtonInterface {
+
+ public NavigationHandle(Context context) {
+ this(context, null);
+ }
+
+ public NavigationHandle(Context context, AttributeSet attr) {
+ super(context, attr);
+ setFocusable(false);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ return false;
+ }
+
+ @Override
+ public void setImageDrawable(Drawable drawable) {
+ }
+
+ @Override
+ public void setDarkIntensity(float intensity) {
+ }
+
+ @Override
+ public void setDelayTouchFeedback(boolean shouldDelay) {
+ }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java b/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java
new file mode 100644
index 0000000..68163c3
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java
@@ -0,0 +1,172 @@
+/*
+ * 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.inputmethodservice.navigationbar;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+
+import java.util.ArrayList;
+
+/**
+ * Automatically reverses the order of children as they are added.
+ * Also reverse the width and height values of layout params
+ * @hide
+ */
+public class ReverseLinearLayout extends LinearLayout {
+
+ /** If true, the layout is reversed vs. a regular linear layout */
+ private boolean mIsLayoutReverse;
+
+ /** If true, the layout is opposite to it's natural reversity from the layout direction */
+ private boolean mIsAlternativeOrder;
+
+ public ReverseLinearLayout(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ updateOrder();
+ }
+
+ @Override
+ public void addView(View child) {
+ reverseParams(child.getLayoutParams(), child, mIsLayoutReverse);
+ if (mIsLayoutReverse) {
+ super.addView(child, 0);
+ } else {
+ super.addView(child);
+ }
+ }
+
+ @Override
+ public void addView(View child, ViewGroup.LayoutParams params) {
+ reverseParams(params, child, mIsLayoutReverse);
+ if (mIsLayoutReverse) {
+ super.addView(child, 0, params);
+ } else {
+ super.addView(child, params);
+ }
+ }
+
+ @Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+ updateOrder();
+ }
+
+ public void setAlternativeOrder(boolean alternative) {
+ mIsAlternativeOrder = alternative;
+ updateOrder();
+ }
+
+ /**
+ * In landscape, the LinearLayout is not auto mirrored since it is vertical. Therefore we
+ * have to do it manually
+ */
+ private void updateOrder() {
+ boolean isLayoutRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ boolean isLayoutReverse = isLayoutRtl ^ mIsAlternativeOrder;
+
+ if (mIsLayoutReverse != isLayoutReverse) {
+ // reversity changed, swap the order of all views.
+ int childCount = getChildCount();
+ ArrayList<View> childList = new ArrayList<>(childCount);
+ for (int i = 0; i < childCount; i++) {
+ childList.add(getChildAt(i));
+ }
+ removeAllViews();
+ for (int i = childCount - 1; i >= 0; i--) {
+ final View child = childList.get(i);
+ super.addView(child);
+ }
+ mIsLayoutReverse = isLayoutReverse;
+ }
+ }
+
+ private static void reverseParams(ViewGroup.LayoutParams params, View child,
+ boolean isLayoutReverse) {
+ if (child instanceof Reversible) {
+ ((Reversible) child).reverse(isLayoutReverse);
+ }
+ if (child.getPaddingLeft() == child.getPaddingRight()
+ && child.getPaddingTop() == child.getPaddingBottom()) {
+ child.setPadding(child.getPaddingTop(), child.getPaddingLeft(),
+ child.getPaddingTop(), child.getPaddingLeft());
+ }
+ if (params == null) {
+ return;
+ }
+ int width = params.width;
+ params.width = params.height;
+ params.height = width;
+ }
+
+ interface Reversible {
+ void reverse(boolean isLayoutReverse);
+ }
+
+ public static class ReverseRelativeLayout extends RelativeLayout implements Reversible {
+
+ public ReverseRelativeLayout(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void reverse(boolean isLayoutReverse) {
+ updateGravity(isLayoutReverse);
+ reverseGroup(this, isLayoutReverse);
+ }
+
+ private int mDefaultGravity = Gravity.NO_GRAVITY;
+ public void setDefaultGravity(int gravity) {
+ mDefaultGravity = gravity;
+ }
+
+ public void updateGravity(boolean isLayoutReverse) {
+ // Flip gravity if top of bottom is used
+ if (mDefaultGravity != Gravity.TOP && mDefaultGravity != Gravity.BOTTOM) return;
+
+ // Use the default (intended for 270 LTR and 90 RTL) unless layout is otherwise
+ int gravityToApply = mDefaultGravity;
+ if (isLayoutReverse) {
+ gravityToApply = mDefaultGravity == Gravity.TOP ? Gravity.BOTTOM : Gravity.TOP;
+ }
+
+ if (getGravity() != gravityToApply) setGravity(gravityToApply);
+ }
+ }
+
+ private static void reverseGroup(ViewGroup group, boolean isLayoutReverse) {
+ for (int i = 0; i < group.getChildCount(); i++) {
+ final View child = group.getChildAt(i);
+ reverseParams(child.getLayoutParams(), child, isLayoutReverse);
+
+ // Recursively reverse all children
+ if (child instanceof ViewGroup) {
+ reverseGroup((ViewGroup) child, isLayoutReverse);
+ }
+ }
+ }
+}
diff --git a/core/java/android/net/LocalServerSocket.java b/core/java/android/net/LocalServerSocket.java
index d1f49d2..506cbcb 100644
--- a/core/java/android/net/LocalServerSocket.java
+++ b/core/java/android/net/LocalServerSocket.java
@@ -55,7 +55,9 @@
* Create a LocalServerSocket from a file descriptor that's already
* been created and bound. listen() will be called immediately on it.
* Used for cases where file descriptors are passed in via environment
- * variables
+ * variables. The passed-in FileDescriptor is not managed by this class
+ * and must be closed by the caller. Calling {@link #close()} on a socket
+ * created by this method has no effect.
*
* @param fd bound file descriptor
* @throws IOException
diff --git a/core/java/android/net/LocalSocket.java b/core/java/android/net/LocalSocket.java
index 5b38f78..b69410c 100644
--- a/core/java/android/net/LocalSocket.java
+++ b/core/java/android/net/LocalSocket.java
@@ -16,7 +16,14 @@
package android.net;
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.system.ErrnoException;
+import android.system.Os;
import java.io.Closeable;
import java.io.FileDescriptor;
@@ -74,32 +81,39 @@
this.isBound = false;
}
+ private void checkConnected() {
+ try {
+ Os.getpeername(impl.getFileDescriptor());
+ } catch (ErrnoException e) {
+ throw new IllegalArgumentException("Not a connected socket", e);
+ }
+ isConnected = true;
+ isBound = true;
+ implCreated = true;
+ }
+
/**
- * Creates a LocalSocket instances using the FileDescriptor for an already-connected
- * AF_LOCAL/UNIX domain stream socket. Note: the FileDescriptor must be closed by the caller:
- * closing the LocalSocket will not close it.
+ * Creates a LocalSocket instance using the {@link FileDescriptor} for an already-connected
+ * AF_LOCAL/UNIX domain stream socket. The passed-in FileDescriptor is not managed by this class
+ * and must be closed by the caller. Calling {@link #close()} on a socket created by this
+ * method has no effect.
*
- * @hide - used by BluetoothSocket.
+ * @param fd the filedescriptor to adopt
+ *
+ * @hide
*/
- public static LocalSocket createConnectedLocalSocket(FileDescriptor fd) {
- return createConnectedLocalSocket(new LocalSocketImpl(fd), SOCKET_UNKNOWN);
+ @SystemApi(client = MODULE_LIBRARIES)
+ public LocalSocket(@NonNull @SuppressLint("UseParcelFileDescriptor") FileDescriptor fd) {
+ this(new LocalSocketImpl(fd), SOCKET_UNKNOWN);
+ checkConnected();
}
/**
* for use with LocalServerSocket.accept()
*/
static LocalSocket createLocalSocketForAccept(LocalSocketImpl impl) {
- return createConnectedLocalSocket(impl, SOCKET_UNKNOWN);
- }
-
- /**
- * Creates a LocalSocket from an existing LocalSocketImpl that is already connected.
- */
- private static LocalSocket createConnectedLocalSocket(LocalSocketImpl impl, int sockType) {
- LocalSocket socket = new LocalSocket(impl, sockType);
- socket.isConnected = true;
- socket.isBound = true;
- socket.implCreated = true;
+ LocalSocket socket = new LocalSocket(impl, SOCKET_UNKNOWN);
+ socket.checkConnected();
return socket;
}
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index a6830b7..2339656 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -549,8 +549,8 @@
* networks, a network will be chosen arbitrarily amongst the networks matching the highest
* priority rule.
*
- * <p>If all networks fail to match the rules provided, an underlying network will still be
- * selected (at random if necessary).
+ * <p>If all networks fail to match the rules provided, a carrier-owned underlying network
+ * will still be selected (if available, at random if necessary).
*
* @param underlyingNetworkTemplates a list of unique VcnUnderlyingNetworkTemplates that are
* ordered from most to least preferred, or an empty list to use the default
diff --git a/core/java/android/os/BluetoothServiceManager.java b/core/java/android/os/BluetoothServiceManager.java
new file mode 100644
index 0000000..12f7bc8
--- /dev/null
+++ b/core/java/android/os/BluetoothServiceManager.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.annotation.SystemApi;
+import android.annotation.SystemApi.Client;
+import android.os.BluetoothServiceManager;
+
+/**
+ * Provides a way to register and obtain the system service binder objects managed by the bluetooth
+ * service.
+ *
+ * @hide
+ */
+@SystemApi(client = Client.MODULE_LIBRARIES)
+public class BluetoothServiceManager {
+
+ /** @hide */
+ public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager";
+ /**
+ * @hide
+ */
+ public BluetoothServiceManager() {
+ }
+
+ /**
+ * A class that exposes the methods to register and obtain each system service.
+ */
+ public static final class ServiceRegisterer {
+ private final String mServiceName;
+
+ /**
+ * @hide
+ */
+ public ServiceRegisterer(String serviceName) {
+ mServiceName = serviceName;
+ }
+
+ /**
+ * Register a system server binding object for a service.
+ */
+ public void register(@NonNull IBinder service) {
+ ServiceManager.addService(mServiceName, service);
+ }
+
+ /**
+ * Get the system server binding object for a service.
+ *
+ * <p>This blocks until the service instance is ready,
+ * or a timeout happens, in which case it returns null.
+ */
+ @Nullable
+ public IBinder get() {
+ return ServiceManager.getService(mServiceName);
+ }
+
+ /**
+ * Get the system server binding object for a service.
+ *
+ * <p>This blocks until the service instance is ready,
+ * or a timeout happens, in which case it throws {@link ServiceNotFoundException}.
+ */
+ @NonNull
+ public IBinder getOrThrow() throws ServiceNotFoundException {
+ try {
+ return ServiceManager.getServiceOrThrow(mServiceName);
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ throw new ServiceNotFoundException(mServiceName);
+ }
+ }
+
+ /**
+ * Get the system server binding object for a service. If the specified service is
+ * not available, it returns null.
+ */
+ @Nullable
+ public IBinder tryGet() {
+ return ServiceManager.checkService(mServiceName);
+ }
+ }
+
+ /**
+ * See {@link ServiceRegisterer#getOrThrow}.
+ *
+ */
+ public static class ServiceNotFoundException extends ServiceManager.ServiceNotFoundException {
+ /**
+ * Constructor.
+ *
+ * @param name the name of the binder service that cannot be found.
+ *
+ */
+ public ServiceNotFoundException(@NonNull String name) {
+ super(name);
+ }
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the "bluetooth" service.
+ */
+ @NonNull
+ public ServiceRegisterer getBluetoothManagerServiceRegisterer() {
+ return new ServiceRegisterer(BLUETOOTH_MANAGER_SERVICE);
+ }
+}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index d4a338b..1810904 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -44,6 +44,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Duration;
+import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLong;
@@ -612,7 +613,7 @@
public static final int WAKE_REASON_PLUGGED_IN = 3;
/**
- * Wake up reason code: Waking up due to a user performed gesture (e.g. douple tapping on the
+ * Wake up reason code: Waking up due to a user performed gesture (e.g. double tapping on the
* screen).
* @hide
*/
@@ -1013,7 +1014,7 @@
private static final int MAX_CACHE_ENTRIES = 1;
- private PropertyInvalidatedCache<Void, Boolean> mPowerSaveModeCache =
+ private final PropertyInvalidatedCache<Void, Boolean> mPowerSaveModeCache =
new PropertyInvalidatedCache<Void, Boolean>(MAX_CACHE_ENTRIES,
CACHE_KEY_IS_POWER_SAVE_MODE_PROPERTY) {
@Override
@@ -1026,7 +1027,7 @@
}
};
- private PropertyInvalidatedCache<Void, Boolean> mInteractiveCache =
+ private final PropertyInvalidatedCache<Void, Boolean> mInteractiveCache =
new PropertyInvalidatedCache<Void, Boolean>(MAX_CACHE_ENTRIES,
CACHE_KEY_IS_INTERACTIVE_PROPERTY) {
@Override
@@ -1047,7 +1048,7 @@
final IThermalService mThermalService;
/** We lazily initialize it.*/
- private PowerWhitelistManager mPowerWhitelistManager;
+ private PowerExemptionManager mPowerExemptionManager;
private final ArrayMap<OnThermalStatusChangedListener, IThermalStatusListener>
mListenerMap = new ArrayMap<>();
@@ -1063,12 +1064,12 @@
mHandler = handler;
}
- private PowerWhitelistManager getPowerWhitelistManager() {
- if (mPowerWhitelistManager == null) {
+ private PowerExemptionManager getPowerExemptionManager() {
+ if (mPowerExemptionManager == null) {
// No need for synchronization; getSystemService() will return the same object anyway.
- mPowerWhitelistManager = mContext.getSystemService(PowerWhitelistManager.class);
+ mPowerExemptionManager = mContext.getSystemService(PowerExemptionManager.class);
}
- return mPowerWhitelistManager;
+ return mPowerExemptionManager;
}
/**
@@ -1339,12 +1340,12 @@
}
/**
- * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group}
+ * Forces the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group}
* to turn off.
*
- * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is
+ * <p>If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is
* turned on it will be turned off. If all displays are off as a result of this action the
- * device will be put to sleep. If the {@link com.android.server.display.DisplayGroup#DEFAULT
+ * device will be put to sleep. If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP
* default display group} is already off then nothing will happen.
*
* <p>If the device is an Android TV playback device and the current active source on the
@@ -1372,12 +1373,12 @@
}
/**
- * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group}
+ * Forces the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group}
* to turn off.
*
- * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is
+ * <p>If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is
* turned on it will be turned off. If all displays are off as a result of this action the
- * device will be put to sleep. If the {@link com.android.server.display.DisplayGroup#DEFAULT
+ * device will be put to sleep. If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP
* default display group} is already off then nothing will happen.
*
* <p>
@@ -1409,12 +1410,12 @@
}
/**
- * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group}
+ * Forces the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group}
* to turn on.
*
- * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is
+ * <p>If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is
* turned off it will be turned on. Additionally, if the device is asleep it will be awoken. If
- * the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is already
+ * the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is already
* on then nothing will happen.
*
* <p>
@@ -1440,12 +1441,12 @@
}
/**
- * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group}
+ * Forces the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group}
* to turn on.
*
- * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is
+ * <p>If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is
* turned off it will be turned on. Additionally, if the device is asleep it will be awoken. If
- * the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is already
+ * the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is already
* on then nothing will happen.
*
* <p>
@@ -2144,7 +2145,7 @@
* features to the app. Guardrails for extreme cases may still be applied.
*/
public boolean isIgnoringBatteryOptimizations(String packageName) {
- return getPowerWhitelistManager().isWhitelisted(packageName, true);
+ return getPowerExemptionManager().isAllowListed(packageName, true);
}
/**
@@ -2270,8 +2271,8 @@
* @param listener listener to be added,
*/
public void addThermalStatusListener(@NonNull OnThermalStatusChangedListener listener) {
- Preconditions.checkNotNull(listener, "listener cannot be null");
- this.addThermalStatusListener(mContext.getMainExecutor(), listener);
+ Objects.requireNonNull(listener, "listener cannot be null");
+ addThermalStatusListener(mContext.getMainExecutor(), listener);
}
/**
@@ -2282,8 +2283,8 @@
*/
public void addThermalStatusListener(@NonNull @CallbackExecutor Executor executor,
@NonNull OnThermalStatusChangedListener listener) {
- Preconditions.checkNotNull(listener, "listener cannot be null");
- Preconditions.checkNotNull(executor, "executor cannot be null");
+ Objects.requireNonNull(listener, "listener cannot be null");
+ Objects.requireNonNull(executor, "executor cannot be null");
Preconditions.checkArgument(!mListenerMap.containsKey(listener),
"Listener already registered: %s", listener);
IThermalStatusListener internalListener = new IThermalStatusListener.Stub() {
@@ -2291,9 +2292,7 @@
public void onStatusChange(int status) {
final long token = Binder.clearCallingIdentity();
try {
- executor.execute(() -> {
- listener.onThermalStatusChanged(status);
- });
+ executor.execute(() -> listener.onThermalStatusChanged(status));
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -2316,7 +2315,7 @@
* @param listener listener to be removed
*/
public void removeThermalStatusListener(@NonNull OnThermalStatusChangedListener listener) {
- Preconditions.checkNotNull(listener, "listener cannot be null");
+ Objects.requireNonNull(listener, "listener cannot be null");
IThermalStatusListener internalListener = mListenerMap.get(listener);
Preconditions.checkArgument(internalListener != null, "Listener was not added");
try {
@@ -2945,6 +2944,7 @@
*
* @hide
*/
+ @SuppressLint("WakelockTimeout")
public Runnable wrap(Runnable r) {
acquire();
return () -> {
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 0037761..0aafaf4 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -18,18 +18,25 @@
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.Range;
+import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.function.Function;
/**
* Vibrator implementation that controls the main system vibrator.
@@ -51,7 +58,7 @@
private final Object mLock = new Object();
@GuardedBy("mLock")
- private AllVibratorsInfo mVibratorInfo;
+ private VibratorInfo mVibratorInfo;
@UnsupportedAppUsage
public SystemVibrator(Context context) {
@@ -71,6 +78,11 @@
return VibratorInfo.EMPTY_VIBRATOR_INFO;
}
int[] vibratorIds = mVibratorManager.getVibratorIds();
+ if (vibratorIds.length == 0) {
+ // It is known that the device has no vibrator, so cache and return info that
+ // reflects the lack of support for effects/primitives.
+ return mVibratorInfo = new NoVibratorInfo();
+ }
VibratorInfo[] vibratorInfos = new VibratorInfo[vibratorIds.length];
for (int i = 0; i < vibratorIds.length; i++) {
Vibrator vibrator = mVibratorManager.getVibrator(vibratorIds[i]);
@@ -83,7 +95,12 @@
}
vibratorInfos[i] = vibrator.getInfo();
}
- return mVibratorInfo = new AllVibratorsInfo(vibratorInfos);
+ if (vibratorInfos.length == 1) {
+ // Device has a single vibrator info, cache and return successfully loaded info.
+ return mVibratorInfo = new VibratorInfo(/* id= */ -1, vibratorInfos[0]);
+ }
+ // Device has multiple vibrators, generate a single info representing all of them.
+ return mVibratorInfo = new MultiVibratorInfo(vibratorInfos);
}
}
@@ -257,77 +274,282 @@
}
/**
- * Represents all the vibrators information as a single {@link VibratorInfo}.
+ * Represents a device with no vibrator as a single {@link VibratorInfo}.
*
- * <p>This uses the first vibrator on the list as the default one for all hardware spec, but
- * uses an intersection of all vibrators to decide the capabilities and effect/primitive
+ * @hide
+ */
+ @VisibleForTesting
+ public static class NoVibratorInfo extends VibratorInfo {
+ public NoVibratorInfo() {
+ // Use empty arrays to indicate no support, while null would indicate support unknown.
+ super(/* id= */ -1,
+ /* capabilities= */ 0,
+ /* supportedEffects= */ new SparseBooleanArray(),
+ /* supportedBraking= */ new SparseBooleanArray(),
+ /* supportedPrimitives= */ new SparseIntArray(),
+ /* primitiveDelayMax= */ 0,
+ /* compositionSizeMax= */ 0,
+ /* pwlePrimitiveDurationMax= */ 0,
+ /* pwleSizeMax= */ 0,
+ /* qFactor= */ Float.NaN,
+ new FrequencyProfile(/* resonantFrequencyHz= */ Float.NaN,
+ /* minFrequencyHz= */ Float.NaN,
+ /* frequencyResolutionHz= */ Float.NaN,
+ /* maxAmplitudes= */ null));
+ }
+ }
+
+ /**
+ * Represents multiple vibrator information as a single {@link VibratorInfo}.
+ *
+ * <p>This uses an intersection of all vibrators to decide the capabilities and effect/primitive
* support.
*
* @hide
*/
@VisibleForTesting
- public static class AllVibratorsInfo extends VibratorInfo {
- private final VibratorInfo[] mVibratorInfos;
+ public static class MultiVibratorInfo extends VibratorInfo {
+ // Epsilon used for float comparison applied in calculations for the merged info.
+ private static final float EPSILON = 1e-5f;
- public AllVibratorsInfo(VibratorInfo[] vibrators) {
- super(/* id= */ -1, capabilitiesIntersection(vibrators),
- vibrators.length > 0 ? vibrators[0] : VibratorInfo.EMPTY_VIBRATOR_INFO);
- mVibratorInfos = vibrators;
- }
-
- @Override
- public int isEffectSupported(int effectId) {
- if (mVibratorInfos.length == 0) {
- return Vibrator.VIBRATION_EFFECT_SUPPORT_NO;
- }
- int supported = Vibrator.VIBRATION_EFFECT_SUPPORT_YES;
- for (VibratorInfo info : mVibratorInfos) {
- int effectSupported = info.isEffectSupported(effectId);
- if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_NO) {
- return effectSupported;
- } else if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN) {
- supported = effectSupported;
- }
- }
- return supported;
- }
-
- @Override
- public boolean isPrimitiveSupported(int primitiveId) {
- if (mVibratorInfos.length == 0) {
- return false;
- }
- for (VibratorInfo info : mVibratorInfos) {
- if (!info.isPrimitiveSupported(primitiveId)) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- public int getPrimitiveDuration(int primitiveId) {
- int maxDuration = 0;
- for (VibratorInfo info : mVibratorInfos) {
- int duration = info.getPrimitiveDuration(primitiveId);
- if (duration == 0) {
- return 0;
- }
- maxDuration = Math.max(maxDuration, duration);
- }
- return maxDuration;
+ public MultiVibratorInfo(VibratorInfo[] vibrators) {
+ super(/* id= */ -1,
+ capabilitiesIntersection(vibrators),
+ supportedEffectsIntersection(vibrators),
+ supportedBrakingIntersection(vibrators),
+ supportedPrimitivesAndDurationsIntersection(vibrators),
+ integerLimitIntersection(vibrators, VibratorInfo::getPrimitiveDelayMax),
+ integerLimitIntersection(vibrators, VibratorInfo::getCompositionSizeMax),
+ integerLimitIntersection(vibrators, VibratorInfo::getPwlePrimitiveDurationMax),
+ integerLimitIntersection(vibrators, VibratorInfo::getPwleSizeMax),
+ floatPropertyIntersection(vibrators, VibratorInfo::getQFactor),
+ frequencyProfileIntersection(vibrators));
}
private static int capabilitiesIntersection(VibratorInfo[] infos) {
- if (infos.length == 0) {
- return 0;
- }
int intersection = ~0;
for (VibratorInfo info : infos) {
intersection &= info.getCapabilities();
}
return intersection;
}
+
+ @Nullable
+ private static SparseBooleanArray supportedBrakingIntersection(VibratorInfo[] infos) {
+ for (VibratorInfo info : infos) {
+ if (!info.isBrakingSupportKnown()) {
+ // If one vibrator support is unknown, then the intersection is also unknown.
+ return null;
+ }
+ }
+
+ SparseBooleanArray intersection = new SparseBooleanArray();
+ SparseBooleanArray firstVibratorBraking = infos[0].getSupportedBraking();
+
+ brakingIdLoop:
+ for (int i = 0; i < firstVibratorBraking.size(); i++) {
+ int brakingId = firstVibratorBraking.keyAt(i);
+ if (!firstVibratorBraking.valueAt(i)) {
+ // The first vibrator already doesn't support this braking, so skip it.
+ continue brakingIdLoop;
+ }
+
+ for (int j = 1; j < infos.length; j++) {
+ if (!infos[j].hasBrakingSupport(brakingId)) {
+ // One vibrator doesn't support this braking, so the intersection doesn't.
+ continue brakingIdLoop;
+ }
+ }
+
+ intersection.put(brakingId, true);
+ }
+
+ return intersection;
+ }
+
+ @Nullable
+ private static SparseBooleanArray supportedEffectsIntersection(VibratorInfo[] infos) {
+ for (VibratorInfo info : infos) {
+ if (!info.isEffectSupportKnown()) {
+ // If one vibrator support is unknown, then the intersection is also unknown.
+ return null;
+ }
+ }
+
+ SparseBooleanArray intersection = new SparseBooleanArray();
+ SparseBooleanArray firstVibratorEffects = infos[0].getSupportedEffects();
+
+ effectIdLoop:
+ for (int i = 0; i < firstVibratorEffects.size(); i++) {
+ int effectId = firstVibratorEffects.keyAt(i);
+ if (!firstVibratorEffects.valueAt(i)) {
+ // The first vibrator already doesn't support this effect, so skip it.
+ continue effectIdLoop;
+ }
+
+ for (int j = 1; j < infos.length; j++) {
+ if (infos[j].isEffectSupported(effectId) != VIBRATION_EFFECT_SUPPORT_YES) {
+ // One vibrator doesn't support this effect, so the intersection doesn't.
+ continue effectIdLoop;
+ }
+ }
+
+ intersection.put(effectId, true);
+ }
+
+ return intersection;
+ }
+
+ @NonNull
+ private static SparseIntArray supportedPrimitivesAndDurationsIntersection(
+ VibratorInfo[] infos) {
+ SparseIntArray intersection = new SparseIntArray();
+ SparseIntArray firstVibratorPrimitives = infos[0].getSupportedPrimitives();
+
+ primitiveIdLoop:
+ for (int i = 0; i < firstVibratorPrimitives.size(); i++) {
+ int primitiveId = firstVibratorPrimitives.keyAt(i);
+ int primitiveDuration = firstVibratorPrimitives.valueAt(i);
+ if (primitiveDuration == 0) {
+ // The first vibrator already doesn't support this primitive, so skip it.
+ continue primitiveIdLoop;
+ }
+
+ for (int j = 1; j < infos.length; j++) {
+ int vibratorPrimitiveDuration = infos[j].getPrimitiveDuration(primitiveId);
+ if (vibratorPrimitiveDuration == 0) {
+ // One vibrator doesn't support this primitive, so the intersection doesn't.
+ continue primitiveIdLoop;
+ } else {
+ // The primitive vibration duration is the maximum among all vibrators.
+ primitiveDuration = Math.max(primitiveDuration, vibratorPrimitiveDuration);
+ }
+ }
+
+ intersection.put(primitiveId, primitiveDuration);
+ }
+ return intersection;
+ }
+
+ private static int integerLimitIntersection(VibratorInfo[] infos,
+ Function<VibratorInfo, Integer> propertyGetter) {
+ int limit = 0; // Limit 0 means unlimited
+ for (VibratorInfo info : infos) {
+ int vibratorLimit = propertyGetter.apply(info);
+ if ((limit == 0) || (vibratorLimit > 0 && vibratorLimit < limit)) {
+ // This vibrator is limited and intersection is unlimited or has a larger limit:
+ // use smaller limit here for the intersection.
+ limit = vibratorLimit;
+ }
+ }
+ return limit;
+ }
+
+ private static float floatPropertyIntersection(VibratorInfo[] infos,
+ Function<VibratorInfo, Float> propertyGetter) {
+ float property = propertyGetter.apply(infos[0]);
+ if (Float.isNaN(property)) {
+ // If one vibrator is undefined then the intersection is undefined.
+ return Float.NaN;
+ }
+ for (int i = 1; i < infos.length; i++) {
+ if (Float.compare(property, propertyGetter.apply(infos[i])) != 0) {
+ // If one vibrator has a different value then the intersection is undefined.
+ return Float.NaN;
+ }
+ }
+ return property;
+ }
+
+ @NonNull
+ private static FrequencyProfile frequencyProfileIntersection(VibratorInfo[] infos) {
+ float freqResolution = floatPropertyIntersection(infos,
+ info -> info.getFrequencyProfile().getFrequencyResolutionHz());
+ float resonantFreq = floatPropertyIntersection(infos,
+ VibratorInfo::getResonantFrequencyHz);
+ Range<Float> freqRange = frequencyRangeIntersection(infos, freqResolution);
+
+ if ((freqRange == null) || Float.isNaN(freqResolution)) {
+ return new FrequencyProfile(resonantFreq, Float.NaN, freqResolution, null);
+ }
+
+ int amplitudeCount =
+ Math.round(1 + (freqRange.getUpper() - freqRange.getLower()) / freqResolution);
+ float[] maxAmplitudes = new float[amplitudeCount];
+
+ // Use MAX_VALUE here to ensure that the FrequencyProfile constructor called with this
+ // will fail if the loop below is broken and do not replace filled values with actual
+ // vibrator measurements.
+ Arrays.fill(maxAmplitudes, Float.MAX_VALUE);
+
+ for (VibratorInfo info : infos) {
+ Range<Float> vibratorFreqRange = info.getFrequencyProfile().getFrequencyRangeHz();
+ float[] vibratorMaxAmplitudes = info.getFrequencyProfile().getMaxAmplitudes();
+ int vibratorStartIdx = Math.round(
+ (freqRange.getLower() - vibratorFreqRange.getLower()) / freqResolution);
+ int vibratorEndIdx = vibratorStartIdx + maxAmplitudes.length - 1;
+
+ if ((vibratorStartIdx < 0) || (vibratorEndIdx >= vibratorMaxAmplitudes.length)) {
+ Slog.w(TAG, "Error calculating the intersection of vibrator frequency"
+ + " profiles: attempted to fetch from vibrator "
+ + info.getId() + " max amplitude with bad index " + vibratorStartIdx);
+ return new FrequencyProfile(resonantFreq, Float.NaN, Float.NaN, null);
+ }
+
+ for (int i = 0; i < maxAmplitudes.length; i++) {
+ maxAmplitudes[i] = Math.min(maxAmplitudes[i],
+ vibratorMaxAmplitudes[vibratorStartIdx + i]);
+ }
+ }
+
+ return new FrequencyProfile(resonantFreq, freqRange.getLower(),
+ freqResolution, maxAmplitudes);
+ }
+
+ @Nullable
+ private static Range<Float> frequencyRangeIntersection(VibratorInfo[] infos,
+ float frequencyResolution) {
+ Range<Float> firstRange = infos[0].getFrequencyProfile().getFrequencyRangeHz();
+ if (firstRange == null) {
+ // If one vibrator is undefined then the intersection is undefined.
+ return null;
+ }
+ float intersectionLower = firstRange.getLower();
+ float intersectionUpper = firstRange.getUpper();
+
+ // Generate the intersection of all vibrator supported ranges, making sure that both
+ // min supported frequencies are aligned w.r.t. the frequency resolution.
+
+ for (int i = 1; i < infos.length; i++) {
+ Range<Float> vibratorRange = infos[i].getFrequencyProfile().getFrequencyRangeHz();
+ if (vibratorRange == null) {
+ // If one vibrator is undefined then the intersection is undefined.
+ return null;
+ }
+
+ if ((vibratorRange.getLower() >= intersectionUpper)
+ || (vibratorRange.getUpper() <= intersectionLower)) {
+ // If the range and intersection are disjoint then the intersection is undefined
+ return null;
+ }
+
+ float frequencyDelta = Math.abs(intersectionLower - vibratorRange.getLower());
+ if ((frequencyDelta % frequencyResolution) > EPSILON) {
+ // If the intersection is not aligned with one vibrator then it's undefined
+ return null;
+ }
+
+ intersectionLower = Math.max(intersectionLower, vibratorRange.getLower());
+ intersectionUpper = Math.min(intersectionUpper, vibratorRange.getUpper());
+ }
+
+ if ((intersectionUpper - intersectionLower) < frequencyResolution) {
+ // If the intersection is empty then it's undefined.
+ return null;
+ }
+
+ return Range.create(intersectionLower, intersectionUpper);
+ }
}
/** Listener for all vibrators state change. */
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index f18c9c9..bd65a41 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -4787,33 +4787,6 @@
}
/**
- * Immediately removes the user or, if the user cannot be removed, such as when the user is
- * the current user, then set the user as ephemeral so that it will be removed when it is
- * stopped.
- *
- * @param evenWhenDisallowed when {@code true}, user is removed even if the caller has the
- * {@link #DISALLOW_REMOVE_USER} or {@link #DISALLOW_REMOVE_MANAGED_PROFILE} restriction
- *
- * @return the {@link RemoveResult} code
- *
- * @deprecated TODO(b/199446770): remove this call after converting all calls to
- * removeUserWhenPossible(UserHandle, boolean)
- *
- * @hide
- */
- @Deprecated
- @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
- Manifest.permission.CREATE_USERS})
- public @RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId,
- boolean evenWhenDisallowed) {
- try {
- return mService.removeUserWhenPossible(userId, evenWhenDisallowed);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
-
- /**
* Updates the user's name.
*
* @param userId the user's integer id
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index eba9ff1..23baa5d 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -31,6 +31,7 @@
import android.hardware.vibrator.IVibrator;
import android.media.AudioAttributes;
import android.os.vibrator.VibrationConfig;
+import android.os.vibrator.VibratorFrequencyProfile;
import android.util.Log;
import java.lang.annotation.Retention;
@@ -208,8 +209,8 @@
/**
* Check whether the vibrator has independent frequency control.
*
- * @return True if the hardware can control the frequency of the vibrations, otherwise false.
- * @hide
+ * @return True if the hardware can control the frequency of the vibrations independently of
+ * the vibration amplitude, false otherwise.
*/
public boolean hasFrequencyControl() {
// We currently can only control frequency of the vibration using the compose PWLE method.
@@ -229,28 +230,48 @@
}
/**
- * Gets the resonant frequency of the vibrator.
+ * Gets the resonant frequency of the vibrator, if applicable.
*
- * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or
- * this vibrator is a composite of multiple physical devices.
- * @hide
+ * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown, not
+ * applicable, or if this vibrator is a composite of multiple physical devices with different
+ * frequencies.
*/
public float getResonantFrequency() {
- return getInfo().getResonantFrequency();
+ return getInfo().getResonantFrequencyHz();
}
/**
* Gets the <a href="https://en.wikipedia.org/wiki/Q_factor">Q factor</a> of the vibrator.
*
- * @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown or
- * this vibrator is a composite of multiple physical devices.
- * @hide
+ * @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown, not
+ * applicable, or if this vibrator is a composite of multiple physical devices with different
+ * Q factors.
*/
public float getQFactor() {
return getInfo().getQFactor();
}
/**
+ * Gets the profile that describes the vibrator output across the supported frequency range.
+ *
+ * <p>The profile describes the relative output acceleration that the device can reach when it
+ * vibrates at different frequencies.
+ *
+ * @return The frequency profile for this vibrator, or null if the vibrator does not have
+ * frequency control. If this vibrator is a composite of multiple physical devices then this
+ * will return a profile supported in all devices, or null if the intersection is empty or not
+ * available.
+ */
+ @Nullable
+ public VibratorFrequencyProfile getFrequencyProfile() {
+ VibratorInfo.FrequencyProfile frequencyProfile = getInfo().getFrequencyProfile();
+ if (frequencyProfile.isEmpty()) {
+ return null;
+ }
+ return new VibratorFrequencyProfile(frequencyProfile);
+ }
+
+ /**
* Return the maximum amplitude the vibrator can play using the audio haptic channels.
*
* <p>This is a positive value, or {@link Float#NaN NaN} if it's unknown. If this returns a
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 5271c4d..00ce14f 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -16,7 +16,6 @@
package android.os;
-import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.vibrator.Braking;
@@ -26,6 +25,8 @@
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import com.android.internal.util.Preconditions;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -56,7 +57,7 @@
private final int mPwlePrimitiveDurationMax;
private final int mPwleSizeMax;
private final float mQFactor;
- private final FrequencyMapping mFrequencyMapping;
+ private final FrequencyProfile mFrequencyProfile;
VibratorInfo(Parcel in) {
mId = in.readInt();
@@ -69,7 +70,15 @@
mPwlePrimitiveDurationMax = in.readInt();
mPwleSizeMax = in.readInt();
mQFactor = in.readFloat();
- mFrequencyMapping = in.readParcelable(VibratorInfo.class.getClassLoader(), android.os.VibratorInfo.FrequencyMapping.class);
+ mFrequencyProfile = FrequencyProfile.CREATOR.createFromParcel(in);
+ }
+
+ public VibratorInfo(int id, @NonNull VibratorInfo baseVibratorInfo) {
+ this(id, baseVibratorInfo.mCapabilities, baseVibratorInfo.mSupportedEffects,
+ baseVibratorInfo.mSupportedBraking, baseVibratorInfo.mSupportedPrimitives,
+ baseVibratorInfo.mPrimitiveDelayMax, baseVibratorInfo.mCompositionSizeMax,
+ baseVibratorInfo.mPwlePrimitiveDurationMax, baseVibratorInfo.mPwleSizeMax,
+ baseVibratorInfo.mQFactor, baseVibratorInfo.mFrequencyProfile);
}
/**
@@ -92,7 +101,7 @@
* @param pwleSizeMax The maximum number of primitives supported by a PWLE
* composition.
* @param qFactor The vibrator quality factor.
- * @param frequencyMapping The description of the vibrator supported frequencies and max
+ * @param frequencyProfile The description of the vibrator supported frequencies and max
* amplitude mappings.
* @hide
*/
@@ -100,7 +109,9 @@
@Nullable SparseBooleanArray supportedBraking,
@NonNull SparseIntArray supportedPrimitives, int primitiveDelayMax,
int compositionSizeMax, int pwlePrimitiveDurationMax, int pwleSizeMax,
- float qFactor, @NonNull FrequencyMapping frequencyMapping) {
+ float qFactor, @NonNull FrequencyProfile frequencyProfile) {
+ Preconditions.checkNotNull(supportedPrimitives);
+ Preconditions.checkNotNull(frequencyProfile);
mId = id;
mCapabilities = capabilities;
mSupportedEffects = supportedEffects == null ? null : supportedEffects.clone();
@@ -111,14 +122,7 @@
mPwlePrimitiveDurationMax = pwlePrimitiveDurationMax;
mPwleSizeMax = pwleSizeMax;
mQFactor = qFactor;
- mFrequencyMapping = frequencyMapping;
- }
-
- protected VibratorInfo(int id, int capabilities, VibratorInfo baseVibrator) {
- this(id, capabilities, baseVibrator.mSupportedEffects, baseVibrator.mSupportedBraking,
- baseVibrator.mSupportedPrimitives, baseVibrator.mPrimitiveDelayMax,
- baseVibrator.mCompositionSizeMax, baseVibrator.mPwlePrimitiveDurationMax,
- baseVibrator.mPwleSizeMax, baseVibrator.mQFactor, baseVibrator.mFrequencyMapping);
+ mFrequencyProfile = frequencyProfile;
}
@Override
@@ -133,7 +137,7 @@
dest.writeInt(mPwlePrimitiveDurationMax);
dest.writeInt(mPwleSizeMax);
dest.writeFloat(mQFactor);
- dest.writeParcelable(mFrequencyMapping, flags);
+ mFrequencyProfile.writeToParcel(dest, flags);
}
@Override
@@ -170,13 +174,13 @@
&& Objects.equals(mSupportedEffects, that.mSupportedEffects)
&& Objects.equals(mSupportedBraking, that.mSupportedBraking)
&& Objects.equals(mQFactor, that.mQFactor)
- && Objects.equals(mFrequencyMapping, that.mFrequencyMapping);
+ && Objects.equals(mFrequencyProfile, that.mFrequencyProfile);
}
@Override
public int hashCode() {
int hashCode = Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
- mQFactor, mFrequencyMapping);
+ mQFactor, mFrequencyProfile);
for (int i = 0; i < mSupportedPrimitives.size(); i++) {
hashCode = 31 * hashCode + mSupportedPrimitives.keyAt(i);
hashCode = 31 * hashCode + mSupportedPrimitives.valueAt(i);
@@ -198,7 +202,7 @@
+ ", mPwlePrimitiveDurationMax=" + mPwlePrimitiveDurationMax
+ ", mPwleSizeMax=" + mPwleSizeMax
+ ", mQFactor=" + mQFactor
- + ", mFrequencyMapping=" + mFrequencyMapping
+ + ", mFrequencyProfile=" + mFrequencyProfile
+ '}';
}
@@ -234,6 +238,30 @@
return Braking.NONE;
}
+ /** @hide */
+ @Nullable
+ public SparseBooleanArray getSupportedBraking() {
+ if (mSupportedBraking == null) {
+ return null;
+ }
+ return mSupportedBraking.clone();
+ }
+
+ /** @hide */
+ public boolean isBrakingSupportKnown() {
+ return mSupportedBraking != null;
+ }
+
+ /** @hide */
+ public boolean hasBrakingSupport(@Braking int braking) {
+ return (mSupportedBraking != null) && mSupportedBraking.get(braking);
+ }
+
+ /** @hide */
+ public boolean isEffectSupportKnown() {
+ return mSupportedEffects != null;
+ }
+
/**
* Query whether the vibrator supports the given effect.
*
@@ -252,6 +280,15 @@
: Vibrator.VIBRATION_EFFECT_SUPPORT_NO;
}
+ /** @hide */
+ @Nullable
+ public SparseBooleanArray getSupportedEffects() {
+ if (mSupportedEffects == null) {
+ return null;
+ }
+ return mSupportedEffects.clone();
+ }
+
/**
* Query whether the vibrator supports the given primitive.
*
@@ -276,6 +313,11 @@
return mSupportedPrimitives.get(primitiveId);
}
+ /** @hide */
+ public SparseIntArray getSupportedPrimitives() {
+ return mSupportedPrimitives.clone();
+ }
+
/**
* Query the maximum delay supported for a primitive in a composed effect.
*
@@ -329,8 +371,8 @@
* @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or
* this vibrator is a composite of multiple physical devices.
*/
- public float getResonantFrequency() {
- return mFrequencyMapping.mResonantFrequencyHz;
+ public float getResonantFrequencyHz() {
+ return mFrequencyProfile.mResonantFrequencyHz;
}
/**
@@ -344,31 +386,14 @@
}
/**
- * Return a range of frequency values supported by the vibrator.
+ * Gets the profile of supported frequencies, including the measurements of maximum relative
+ * output acceleration for supported vibration frequencies.
*
- * @return A range of frequency values supported, in hertz. The range will always contain the
- * device resonant frequency. Devices without frequency control will return null.
- * @hide
+ * <p>If the devices does not have frequency control then the profile should be empty.
*/
- @Nullable
- public Range<Float> getFrequencyRangeHz() {
- return mFrequencyMapping.mFrequencyRangeHz;
- }
-
- /**
- * Return the maximum amplitude the vibrator can play at given frequency.
- *
- * @param frequencyHz The frequency, in hertz, for query.
-
- * @return a value in [0,1] representing the maximum amplitude the device can play at given
- * frequency. Devices without frequency control will return 0 to any input. Devices with
- * frequency control will return the supported value, for input in
- * {@link #getFrequencyRangeHz()}, and 0 for any other input.
- * @hide
- */
- @FloatRange(from = 0, to = 1)
- public float getMaxAmplitude(float frequencyHz) {
- return mFrequencyMapping.getMaxAmplitude(frequencyHz);
+ @NonNull
+ public FrequencyProfile getFrequencyProfile() {
+ return mFrequencyProfile;
}
protected long getCapabilities() {
@@ -452,7 +477,7 @@
* Describes the maximum relative output acceleration that can be achieved for each supported
* frequency in a specific vibrator.
*
- * <p>This mapping is defined by the following parameters:
+ * <p>This profile is defined by the following parameters:
*
* <ol>
* <li>{@code minFrequencyHz}, {@code resonantFrequencyHz} and {@code frequencyResolutionHz}
@@ -466,7 +491,7 @@
*
* @hide
*/
- public static final class FrequencyMapping implements Parcelable {
+ public static final class FrequencyProfile implements Parcelable {
@Nullable
private final Range<Float> mFrequencyRangeHz;
private final float mMinFrequencyHz;
@@ -474,7 +499,7 @@
private final float mFrequencyResolutionHz;
private final float[] mMaxAmplitudes;
- FrequencyMapping(Parcel in) {
+ FrequencyProfile(Parcel in) {
this(in.readFloat(), in.readFloat(), in.readFloat(), in.createFloatArray());
}
@@ -484,13 +509,13 @@
* @param resonantFrequencyHz The vibrator resonant frequency, in hertz.
* @param minFrequencyHz Minimum supported frequency, in hertz.
* @param frequencyResolutionHz The frequency resolution, in hertz, used by the max
- * amplitudes mapping.
+ * amplitude measurements.
* @param maxAmplitudes The max amplitude supported by each supported frequency,
* starting at minimum frequency with jumps of frequency
* resolution.
* @hide
*/
- public FrequencyMapping(float resonantFrequencyHz, float minFrequencyHz,
+ public FrequencyProfile(float resonantFrequencyHz, float minFrequencyHz,
float frequencyResolutionHz, float[] maxAmplitudes) {
mMinFrequencyHz = minFrequencyHz;
mResonantFrequencyHz = resonantFrequencyHz;
@@ -500,18 +525,25 @@
System.arraycopy(maxAmplitudes, 0, mMaxAmplitudes, 0, maxAmplitudes.length);
}
- // If any required field is undefined then leave this mapping empty.
+ // If any required field is undefined or has a bad value then this profile is invalid.
boolean isValid = !Float.isNaN(resonantFrequencyHz)
+ && (resonantFrequencyHz > 0)
&& !Float.isNaN(minFrequencyHz)
+ && (minFrequencyHz > 0)
&& !Float.isNaN(frequencyResolutionHz)
+ && (frequencyResolutionHz > 0)
&& (mMaxAmplitudes.length > 0);
+ // If any max amplitude is outside the allowed range then this profile is invalid.
+ for (int i = 0; i < mMaxAmplitudes.length; i++) {
+ isValid &= (mMaxAmplitudes[i] >= 0) && (mMaxAmplitudes[i] <= 1);
+ }
+
float maxFrequencyHz = isValid
? minFrequencyHz + frequencyResolutionHz * (mMaxAmplitudes.length - 1)
: Float.NaN;
- // If the non-empty mapping does not have min < resonant < max frequency respected
- // then leave this mapping empty.
+ // If the constraint min < resonant < max is not met then it is invalid.
isValid &= !Float.isNaN(maxFrequencyHz)
&& (resonantFrequencyHz >= minFrequencyHz)
&& (resonantFrequencyHz <= maxFrequencyHz)
@@ -520,14 +552,17 @@
mFrequencyRangeHz = isValid ? Range.create(minFrequencyHz, maxFrequencyHz) : null;
}
- /**
- * Returns true if this frequency mapping is empty, i.e. the only supported is the resonant
- * frequency.
- */
+ /** Returns true if the supported frequency range is empty. */
public boolean isEmpty() {
return mFrequencyRangeHz == null;
}
+ /** Returns the supported frequency range, in hertz. */
+ @Nullable
+ public Range<Float> getFrequencyRangeHz() {
+ return mFrequencyRangeHz;
+ }
+
/**
* Returns the maximum relative amplitude the vibrator can reach while playing at the
* given frequency.
@@ -535,24 +570,43 @@
* @param frequencyHz frequency, in hertz, for query.
* @return A value in [0,1] representing the max relative amplitude supported at the given
* frequency. This will return 0 if the frequency is outside the supported range, or if the
- * mapping is empty.
+ * supported frequency range is empty.
*/
public float getMaxAmplitude(float frequencyHz) {
- if (isEmpty() || Float.isNaN(frequencyHz)) {
+ if (isEmpty() || Float.isNaN(frequencyHz) || !mFrequencyRangeHz.contains(frequencyHz)) {
// Unsupported frequency requested, vibrator cannot play at this frequency.
return 0;
}
- float position = (frequencyHz - mMinFrequencyHz) / mFrequencyResolutionHz;
- int floorIndex = (int) Math.floor(position);
- int ceilIndex = (int) Math.ceil(position);
- if (floorIndex < 0 || floorIndex >= mMaxAmplitudes.length) {
- return 0;
- }
- if (floorIndex != ceilIndex && ceilIndex < mMaxAmplitudes.length) {
- // Value in between two mapped frequency values, use the lowest supported one.
- return MathUtils.min(mMaxAmplitudes[floorIndex], mMaxAmplitudes[ceilIndex]);
- }
- return mMaxAmplitudes[floorIndex];
+
+ // Subtract minFrequencyHz to simplify offset calculations.
+ float mappingFreq = frequencyHz - mMinFrequencyHz;
+
+ // Find the bucket to interpolate within.
+ // Any calculated index should be safe, except exactly equal to max amplitude can be
+ // one step too high, so constrain it to guarantee safety.
+ int startIdx = MathUtils.constrain(
+ /* amount= */ (int) Math.floor(mappingFreq / mFrequencyResolutionHz),
+ /* low= */ 0, /* high= */ mMaxAmplitudes.length - 1);
+ int nextIdx = MathUtils.constrain(
+ /* amount= */ startIdx + 1,
+ /* low= */ 0, /* high= */ mMaxAmplitudes.length - 1);
+
+ // Linearly interpolate the amplitudes based on the frequency range of the bucket.
+ return MathUtils.constrainedMap(
+ mMaxAmplitudes[startIdx], mMaxAmplitudes[nextIdx],
+ startIdx * mFrequencyResolutionHz, nextIdx * mFrequencyResolutionHz,
+ mappingFreq);
+ }
+
+ /** Returns the raw list of maximum relative output accelerations from the vibrator. */
+ @NonNull
+ public float[] getMaxAmplitudes() {
+ return Arrays.copyOf(mMaxAmplitudes, mMaxAmplitudes.length);
+ }
+
+ /** Returns the raw frequency resolution used for max amplitude measurements, in hertz. */
+ public float getFrequencyResolutionHz() {
+ return mFrequencyResolutionHz;
}
@Override
@@ -573,10 +627,10 @@
if (this == o) {
return true;
}
- if (!(o instanceof FrequencyMapping)) {
+ if (!(o instanceof FrequencyProfile)) {
return false;
}
- FrequencyMapping that = (FrequencyMapping) o;
+ FrequencyProfile that = (FrequencyProfile) o;
return Float.compare(mMinFrequencyHz, that.mMinFrequencyHz) == 0
&& Float.compare(mResonantFrequencyHz, that.mResonantFrequencyHz) == 0
&& Float.compare(mFrequencyResolutionHz, that.mFrequencyResolutionHz) == 0
@@ -593,7 +647,7 @@
@Override
public String toString() {
- return "FrequencyMapping{"
+ return "FrequencyProfile{"
+ "mFrequencyRange=" + mFrequencyRangeHz
+ ", mMinFrequency=" + mMinFrequencyHz
+ ", mResonantFrequency=" + mResonantFrequencyHz
@@ -603,16 +657,16 @@
}
@NonNull
- public static final Creator<FrequencyMapping> CREATOR =
- new Creator<FrequencyMapping>() {
+ public static final Creator<FrequencyProfile> CREATOR =
+ new Creator<FrequencyProfile>() {
@Override
- public FrequencyMapping createFromParcel(Parcel in) {
- return new FrequencyMapping(in);
+ public FrequencyProfile createFromParcel(Parcel in) {
+ return new FrequencyProfile(in);
}
@Override
- public FrequencyMapping[] newArray(int size) {
- return new FrequencyMapping[size];
+ public FrequencyProfile[] newArray(int size) {
+ return new FrequencyProfile[size];
}
};
}
@@ -629,8 +683,8 @@
private int mPwlePrimitiveDurationMax;
private int mPwleSizeMax;
private float mQFactor = Float.NaN;
- private FrequencyMapping mFrequencyMapping =
- new FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, null);
+ private FrequencyProfile mFrequencyProfile =
+ new FrequencyProfile(Float.NaN, Float.NaN, Float.NaN, null);
/** A builder class for a {@link VibratorInfo}. */
public Builder(int id) {
@@ -702,8 +756,8 @@
/** Configure the vibrator frequency information like resonant frequency and bandwidth. */
@NonNull
- public Builder setFrequencyMapping(FrequencyMapping frequencyMapping) {
- mFrequencyMapping = frequencyMapping;
+ public Builder setFrequencyProfile(@NonNull FrequencyProfile frequencyProfile) {
+ mFrequencyProfile = frequencyProfile;
return this;
}
@@ -712,7 +766,7 @@
public VibratorInfo build() {
return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
mSupportedPrimitives, mPrimitiveDelayMax, mCompositionSizeMax,
- mPwlePrimitiveDurationMax, mPwleSizeMax, mQFactor, mFrequencyMapping);
+ mPwlePrimitiveDurationMax, mPwleSizeMax, mQFactor, mFrequencyProfile);
}
/**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl b/core/java/android/os/logcat/ILogcatManagerService.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
copy to core/java/android/os/logcat/ILogcatManagerService.aidl
index 2b3e961..68b5679 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
+++ b/core/java/android/os/logcat/ILogcatManagerService.aidl
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.shared.system.smartspace;
+package android.os.logcat;
-import com.android.systemui.shared.system.smartspace.ISmartspaceCallback;
+/**
+ * @hide
+ */
+interface ILogcatManagerService {
+ void startThread(in int uid, in int gid, in int pid, in int fd);
+ void finishThread(in int uid, in int gid, in int pid, in int fd);
+}
-// Controller that keeps track of SmartSpace instances in remote processes (such as Launcher).
-interface ISmartspaceTransitionController {
- oneway void setSmartspace(ISmartspaceCallback callback);
-}
\ No newline at end of file
diff --git a/core/java/android/os/logcat/OWNERS b/core/java/android/os/logcat/OWNERS
new file mode 100644
index 0000000..cb21a6f
--- /dev/null
+++ b/core/java/android/os/logcat/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/services/core/java/com/android/server/logcat/OWNERS
diff --git a/core/java/android/os/vibrator/VibratorFrequencyProfile.java b/core/java/android/os/vibrator/VibratorFrequencyProfile.java
new file mode 100644
index 0000000..23b45ae
--- /dev/null
+++ b/core/java/android/os/vibrator/VibratorFrequencyProfile.java
@@ -0,0 +1,100 @@
+/*
+ * 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.os.vibrator;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.os.VibratorInfo;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Describes the output of a {@link android.os.Vibrator} for different vibration frequencies.
+ *
+ * <p>The profile contains the minimum and maximum supported vibration frequencies, if the device
+ * supports independent frequency control.
+ *
+ * <p>It also describes the relative output acceleration of a vibration at different supported
+ * frequencies. The acceleration is defined by a relative amplitude value between 0 and 1,
+ * inclusive, where 0 represents the vibrator off state and 1 represents the maximum output
+ * acceleration that the vibrator can reach across all supported frequencies.
+ *
+ * <p>The measurements are returned as an array of uniformly distributed amplitude values for
+ * frequencies between the minimum and maximum supported ones. The measurement interval is the
+ * frequency increment between each pair of amplitude values.
+ *
+ * <p>Vibrators without independent frequency control do not have a frequency profile.
+ */
+public final class VibratorFrequencyProfile {
+
+ private final VibratorInfo.FrequencyProfile mFrequencyProfile;
+
+ /** @hide */
+ public VibratorFrequencyProfile(@NonNull VibratorInfo.FrequencyProfile frequencyProfile) {
+ Preconditions.checkArgument(!frequencyProfile.isEmpty(),
+ "Frequency profile must have a non-empty frequency range");
+ mFrequencyProfile = frequencyProfile;
+ }
+
+ /**
+ * Measurements of the maximum relative amplitude the vibrator can achieve for each supported
+ * frequency.
+ *
+ * <p>The frequency of a measurement is determined as:
+ *
+ * {@code getMinFrequency() + measurementIndex * getMaxAmplitudeMeasurementInterval()}
+ *
+ * <p>The returned list will not be empty, and will have entries representing frequencies from
+ * {@link #getMinFrequency()} to {@link #getMaxFrequency()}, inclusive.
+ *
+ * @return Array of maximum relative amplitude measurements, each value is between 0 and 1,
+ * inclusive.
+ */
+ @NonNull
+ @FloatRange(from = 0, to = 1)
+ public float[] getMaxAmplitudeMeasurements() {
+ // VibratorInfo getters always return a copy or clone of the data objects.
+ return mFrequencyProfile.getMaxAmplitudes();
+ }
+
+ /**
+ * Gets the frequency interval used to measure the maximum relative amplitudes.
+ *
+ * @return the frequency interval used for the measurement, in hertz.
+ */
+ public float getMaxAmplitudeMeasurementInterval() {
+ return mFrequencyProfile.getFrequencyResolutionHz();
+ }
+
+ /**
+ * Gets the minimum frequency supported by the vibrator.
+ *
+ * @return the minimum frequency supported by the vibrator, in hertz.
+ */
+ public float getMinFrequency() {
+ return mFrequencyProfile.getFrequencyRangeHz().getLower();
+ }
+
+ /**
+ * Gets the maximum frequency supported by the vibrator.
+ *
+ * @return the maximum frequency supported by the vibrator, in hertz.
+ */
+ public float getMaxFrequency() {
+ return mFrequencyProfile.getFrequencyRangeHz().getUpper();
+ }
+}
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index 0894e37..c9dd06c 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -56,6 +56,9 @@
in AndroidFuture<String> callback);
void getUnusedAppCount(
in AndroidFuture callback);
+ void getHibernationEligibility(
+ in String packageName,
+ in AndroidFuture callback);
void revokeOwnPermissionsOnKill(in String packageName, in List<String> permissions,
in AndroidFuture callback);
}
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index a0115a9..0cf06aa 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -128,6 +128,51 @@
/** Count and app even if it is a system app. */
public static final int COUNT_WHEN_SYSTEM = 2;
+ /** @hide */
+ @IntDef(prefix = { "HIBERNATION_ELIGIBILITY_"}, value = {
+ HIBERNATION_ELIGIBILITY_UNKNOWN,
+ HIBERNATION_ELIGIBILITY_ELIGIBLE,
+ HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM,
+ HIBERNATION_ELIGIBILITY_EXEMPT_BY_USER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface HibernationEligibilityFlag {}
+
+ /**
+ * Unknown whether package is eligible for hibernation.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int HIBERNATION_ELIGIBILITY_UNKNOWN = -1;
+
+ /**
+ * Package is eligible for app hibernation and may be hibernated when the job runs.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int HIBERNATION_ELIGIBILITY_ELIGIBLE = 0;
+
+ /**
+ * Package is not eligible for app hibernation because it is categorically exempt via the
+ * system.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM = 1;
+
+ /**
+ * Package is not eligible for app hibernation because it has been exempt by the user's
+ * preferences. Note that this should not be set if the package is exempt from hibernation by
+ * the system as the user preference would have no effect.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int HIBERNATION_ELIGIBILITY_EXEMPT_BY_USER = 2;
+
/**
* Callback for delivering the result of {@link #revokeRuntimePermissions}.
*/
@@ -819,6 +864,39 @@
}
/**
+ * Get the hibernation eligibility of a package. See {@link HibernationEligibilityFlag}.
+ *
+ * @param packageName package name to check eligibility
+ * @param executor executor to run callback on
+ * @param callback callback for when result is generated
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_APP_HIBERNATION)
+ public void getHibernationEligibility(@NonNull String packageName,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull IntConsumer callback) {
+ checkNotNull(executor);
+ checkNotNull(callback);
+
+ mRemoteService.postAsync(service -> {
+ AndroidFuture<Integer> eligibilityResult = new AndroidFuture<>();
+ service.getHibernationEligibility(packageName, eligibilityResult);
+ return eligibilityResult;
+ }).whenCompleteAsync((eligibility, err) -> {
+ if (err != null) {
+ Log.e(TAG, "Error getting hibernation eligibility", err);
+ callback.accept(HIBERNATION_ELIGIBILITY_UNKNOWN);
+ } else {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ callback.accept(eligibility);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }, executor);
+ }
+
+ /**
* Triggers the revocation of one or more permissions for a package, under the following
* conditions:
* <ul>
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index b1e3cfc..8d9f82b 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -375,6 +375,22 @@
throw new AbstractMethodError("Must be overridden in implementing class");
}
+ /**
+ * Get the hibernation eligibility of the app. See
+ * {@link android.permission.PermissionControllerManager.HibernationEligibilityFlag}.
+ *
+ * @param packageName package to check eligibility
+ * @param callback callback after eligibility is returned
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MANAGE_APP_HIBERNATION)
+ public void onGetHibernationEligibility(@NonNull String packageName,
+ @NonNull IntConsumer callback) {
+ throw new AbstractMethodError("Must be overridden in implementing class");
+ }
+
@Override
public final @NonNull IBinder onBind(Intent intent) {
return new IPermissionController.Stub() {
@@ -669,6 +685,22 @@
}
@Override
+ public void getHibernationEligibility(@NonNull String packageName,
+ @NonNull AndroidFuture callback) {
+ try {
+ Objects.requireNonNull(callback);
+
+ enforceSomePermissionsGrantedToCaller(
+ Manifest.permission.MANAGE_APP_HIBERNATION);
+
+ PermissionControllerService.this.onGetHibernationEligibility(packageName,
+ callback::complete);
+ } catch (Throwable t) {
+ callback.completeExceptionally(t);
+ }
+ }
+
+ @Override
public void revokeOwnPermissionsOnKill(@NonNull String packageName,
@NonNull List<String> permissions, @NonNull AndroidFuture callback) {
try {
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index e4aee76..15f13eb 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -33,6 +33,7 @@
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.AppGlobals;
+import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.PropertyInvalidatedCache;
import android.compat.annotation.ChangeId;
@@ -239,6 +240,41 @@
}
/**
+ *
+ * Similar to checkPermissionForDataDelivery, except it results in an app op start, rather than
+ * a note. If this method is used, then {@link #finishDataDelivery(String, AttributionSource)}
+ * must be used when access is finished.
+ *
+ * @param permission The permission to check.
+ * @param attributionSource the permission identity
+ * @param message A message describing the reason the permission was checked
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ *
+ * @see #checkPermissionForDataDelivery(String, AttributionSource, String)
+ */
+ @PermissionCheckerManager.PermissionResult
+ public int checkPermissionForStartDataDelivery(@NonNull String permission,
+ @NonNull AttributionSource attributionSource, @Nullable String message) {
+ return PermissionChecker.checkPermissionForDataDelivery(mContext, permission,
+ // FIXME(b/199526514): PID should be passed inside AttributionSource.
+ PermissionChecker.PID_UNKNOWN, attributionSource, message, true);
+ }
+
+ /**
+ * Indicate that usage has finished for an {@link AttributionSource} started with
+ * {@link #checkPermissionForStartDataDelivery(String, AttributionSource, String)}
+ *
+ * @param permission The permission to check.
+ * @param attributionSource the permission identity to finish
+ */
+ public void finishDataDelivery(@NonNull String permission,
+ @NonNull AttributionSource attributionSource) {
+ PermissionChecker.finishDataDelivery(mContext, AppOpsManager.permissionToOp(permission),
+ attributionSource);
+ }
+
+ /**
* Checks whether a given data access chain described by the given {@link AttributionSource}
* has a given permission. Call this method if you are the datasource which would not blame you
* for access to the data since you are the data. Use this API if you are the datasource of the
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 133e384..bb1f393 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package android.service.dreams;
import android.annotation.IdRes;
@@ -57,7 +58,6 @@
import android.view.accessibility.AccessibilityEvent;
import com.android.internal.util.DumpUtils;
-import com.android.internal.util.DumpUtils.Dump;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -111,7 +111,7 @@
* <p>If specified with the {@code <meta-data>} element,
* additional information for the dream is defined using the
* {@link android.R.styleable#Dream <dream>} element in a separate XML file.
- * Currently, the only addtional
+ * Currently, the only additional
* information you can provide is for a settings activity that allows the user to configure
* the dream behavior. For example:</p>
* <p class="code-caption">res/xml/my_dream.xml</p>
@@ -159,7 +159,8 @@
* </pre>
*/
public class DreamService extends Service implements Window.Callback {
- private final String TAG = DreamService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]";
+ private final String mTag =
+ DreamService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]";
/**
* The name of the dream manager service.
@@ -224,13 +225,13 @@
private DreamServiceWrapper mDreamServiceWrapper;
private Runnable mDispatchAfterOnAttachedToWindow;
- private OverlayConnection mOverlayConnection;
+ private final OverlayConnection mOverlayConnection;
private static class OverlayConnection implements ServiceConnection {
// Overlay set during onBind.
private IDreamOverlay mOverlay;
// A Queue of pending requests to execute on the overlay.
- private ArrayDeque<Consumer<IDreamOverlay>> mRequests;
+ private final ArrayDeque<Consumer<IDreamOverlay>> mRequests;
private boolean mBound;
@@ -292,7 +293,7 @@
}
}
- private IDreamOverlayCallback mOverlayCallback = new IDreamOverlayCallback.Stub() {
+ private final IDreamOverlayCallback mOverlayCallback = new IDreamOverlayCallback.Stub() {
@Override
public void onExitRequested() {
// Simply finish dream when exit is requested.
@@ -319,11 +320,11 @@
public boolean dispatchKeyEvent(KeyEvent event) {
// TODO: create more flexible version of mInteractive that allows use of KEYCODE_BACK
if (!mInteractive) {
- if (mDebug) Slog.v(TAG, "Waking up on keyEvent");
+ if (mDebug) Slog.v(mTag, "Waking up on keyEvent");
wakeUp();
return true;
} else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
- if (mDebug) Slog.v(TAG, "Waking up on back key");
+ if (mDebug) Slog.v(mTag, "Waking up on back key");
wakeUp();
return true;
}
@@ -334,7 +335,7 @@
@Override
public boolean dispatchKeyShortcutEvent(KeyEvent event) {
if (!mInteractive) {
- if (mDebug) Slog.v(TAG, "Waking up on keyShortcutEvent");
+ if (mDebug) Slog.v(mTag, "Waking up on keyShortcutEvent");
wakeUp();
return true;
}
@@ -347,7 +348,7 @@
// TODO: create more flexible version of mInteractive that allows clicks
// but finish()es on any other kind of activity
if (!mInteractive && event.getActionMasked() == MotionEvent.ACTION_UP) {
- if (mDebug) Slog.v(TAG, "Waking up on touchEvent");
+ if (mDebug) Slog.v(mTag, "Waking up on touchEvent");
wakeUp();
return true;
}
@@ -358,7 +359,7 @@
@Override
public boolean dispatchTrackballEvent(MotionEvent event) {
if (!mInteractive) {
- if (mDebug) Slog.v(TAG, "Waking up on trackballEvent");
+ if (mDebug) Slog.v(mTag, "Waking up on trackballEvent");
wakeUp();
return true;
}
@@ -369,7 +370,7 @@
@Override
public boolean dispatchGenericMotionEvent(MotionEvent event) {
if (!mInteractive) {
- if (mDebug) Slog.v(TAG, "Waking up on genericMotionEvent");
+ if (mDebug) Slog.v(mTag, "Waking up on genericMotionEvent");
wakeUp();
return true;
}
@@ -624,7 +625,7 @@
}
/**
- * Returns whether or not this dream is interactive. Defaults to false.
+ * Returns whether this dream is interactive. Defaults to false.
*
* @see #setInteractive(boolean)
*/
@@ -648,7 +649,7 @@
}
/**
- * Returns whether or not this dream is in fullscreen mode. Defaults to false.
+ * Returns whether this dream is in fullscreen mode. Defaults to false.
*
* @see #setFullscreen(boolean)
*/
@@ -670,7 +671,7 @@
}
/**
- * Returns whether or not this dream keeps the screen bright while dreaming.
+ * Returns whether this dream keeps the screen bright while dreaming.
* Defaults to false, allowing the screen to dim if necessary.
*
* @see #setScreenBright(boolean)
@@ -680,7 +681,7 @@
}
/**
- * Marks this dream as windowless. Only available to doze dreams.
+ * Marks this dream as windowless. Only available to doze dreams.
*
* @hide
*
@@ -690,7 +691,7 @@
}
/**
- * Returns whether or not this dream is windowless. Only available to doze dreams.
+ * Returns whether this dream is windowless. Only available to doze dreams.
*
* @hide
*/
@@ -717,12 +718,12 @@
* Starts dozing, entering a deep dreamy sleep.
* <p>
* Dozing enables the system to conserve power while the user is not actively interacting
- * with the device. While dozing, the display will remain on in a low-power state
+ * with the device. While dozing, the display will remain on in a low-power state
* and will continue to show its previous contents but the application processor and
* other system components will be allowed to suspend when possible.
* </p><p>
* While the application processor is suspended, the dream may stop executing code
- * for long periods of time. Prior to being suspended, the dream may schedule periodic
+ * for long periods of time. Prior to being suspended, the dream may schedule periodic
* wake-ups to render new content by scheduling an alarm with the {@link AlarmManager}.
* The dream may also keep the CPU awake by acquiring a
* {@link android.os.PowerManager#PARTIAL_WAKE_LOCK partial wake lock} when necessary.
@@ -731,7 +732,7 @@
* awake for very long.
* </p><p>
* It is a good idea to call this method some time after the dream's entry animation
- * has completed and the dream is ready to doze. It is important to completely
+ * has completed and the dream is ready to doze. It is important to completely
* finish all of the work needed before dozing since the application processor may
* be suspended at any moment once this method is called unless other wake locks
* are being held.
@@ -752,7 +753,7 @@
private void updateDoze() {
if (mDreamToken == null) {
- Slog.w(TAG, "Updating doze without a dream token.");
+ Slog.w(mTag, "Updating doze without a dream token.");
return;
}
@@ -768,7 +769,7 @@
/**
* Stops dozing, returns to active dreaming.
* <p>
- * This method reverses the effect of {@link #startDozing}. From this moment onward,
+ * This method reverses the effect of {@link #startDozing}. From this moment onward,
* the application processor will be kept awake as long as the dream is running
* or until the dream starts dozing again.
* </p>
@@ -790,12 +791,11 @@
/**
* Returns true if the dream will allow the system to enter a low-power state while
- * it is running without actually turning off the screen. Defaults to false,
+ * it is running without actually turning off the screen. Defaults to false,
* keeping the application processor awake while the dream is running.
*
* @return True if the dream is dozing.
*
- * @see #setDozing(boolean)
* @hide For use by system UI components only.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -822,7 +822,7 @@
* Sets the screen state to use while dozing.
* <p>
* The value of this property determines the power state of the primary display
- * once {@link #startDozing} has been called. The default value is
+ * once {@link #startDozing} has been called. The default value is
* {@link Display#STATE_UNKNOWN} which lets the system decide.
* The dream may set a different state before starting to doze and may
* perform transitions between states while dozing to conserve power and
@@ -836,7 +836,7 @@
* If not using Sidekick, it is recommended that the state be set to
* {@link Display#STATE_DOZE_SUSPEND} once the dream has completely
* finished drawing and before it releases its wakelock
- * to allow the display hardware to be fully suspended. While suspended,
+ * to allow the display hardware to be fully suspended. While suspended,
* the display will preserve its on-screen contents.
* </p><p>
* If the doze suspend state is used, the dream must make sure to set the mode back
@@ -844,7 +844,7 @@
* since the display updates may be ignored and not seen by the user otherwise.
* </p><p>
* The set of available display power states and their behavior while dozing is
- * hardware dependent and may vary across devices. The dream may therefore
+ * hardware dependent and may vary across devices. The dream may therefore
* need to be modified or configured to correctly support the hardware.
* </p>
*
@@ -883,19 +883,19 @@
* Sets the screen brightness to use while dozing.
* <p>
* The value of this property determines the power state of the primary display
- * once {@link #startDozing} has been called. The default value is
+ * once {@link #startDozing} has been called. The default value is
* {@link PowerManager#BRIGHTNESS_DEFAULT} which lets the system decide.
* The dream may set a different brightness before starting to doze and may adjust
* the brightness while dozing to conserve power and achieve various effects.
* </p><p>
* Note that dream may specify any brightness in the full 0-255 range, including
* values that are less than the minimum value for manual screen brightness
- * adjustments by the user. In particular, the value may be set to 0 which may
+ * adjustments by the user. In particular, the value may be set to 0 which may
* turn off the backlight entirely while still leaving the screen on although
* this behavior is device dependent and not guaranteed.
* </p><p>
* The available range of display brightness values and their behavior while dozing is
- * hardware dependent and may vary across devices. The dream may therefore
+ * hardware dependent and may vary across devices. The dream may therefore
* need to be modified or configured to correctly support the hardware.
* </p>
*
@@ -922,7 +922,7 @@
*/
@Override
public void onCreate() {
- if (mDebug) Slog.v(TAG, "onCreate()");
+ if (mDebug) Slog.v(mTag, "onCreate()");
super.onCreate();
}
@@ -930,7 +930,7 @@
* Called when the dream's window has been created and is visible and animation may now begin.
*/
public void onDreamingStarted() {
- if (mDebug) Slog.v(TAG, "onDreamingStarted()");
+ if (mDebug) Slog.v(mTag, "onDreamingStarted()");
// hook for subclasses
}
@@ -939,7 +939,7 @@
* before the window has been removed.
*/
public void onDreamingStopped() {
- if (mDebug) Slog.v(TAG, "onDreamingStopped()");
+ if (mDebug) Slog.v(mTag, "onDreamingStopped()");
// hook for subclasses
}
@@ -947,11 +947,11 @@
* Called when the dream is being asked to stop itself and wake.
* <p>
* The default implementation simply calls {@link #finish} which ends the dream
- * immediately. Subclasses may override this function to perform a smooth exit
+ * immediately. Subclasses may override this function to perform a smooth exit
* transition then call {@link #finish} afterwards.
* </p><p>
* Note that the dream will only be given a short period of time (currently about
- * five seconds) to wake up. If the dream does not finish itself in a timely manner
+ * five seconds) to wake up. If the dream does not finish itself in a timely manner
* then the system will forcibly finish it once the time allowance is up.
* </p>
*/
@@ -962,7 +962,7 @@
/** {@inheritDoc} */
@Override
public final IBinder onBind(Intent intent) {
- if (mDebug) Slog.v(TAG, "onBind() intent = " + intent);
+ if (mDebug) Slog.v(mTag, "onBind() intent = " + intent);
mDreamServiceWrapper = new DreamServiceWrapper();
// Connect to the overlay service if present.
@@ -981,7 +981,7 @@
* </p>
*/
public final void finish() {
- if (mDebug) Slog.v(TAG, "finish(): mFinished=" + mFinished);
+ if (mDebug) Slog.v(mTag, "finish(): mFinished=" + mFinished);
Activity activity = mActivity;
if (activity != null) {
@@ -998,7 +998,7 @@
mFinished = true;
if (mDreamToken == null) {
- Slog.w(TAG, "Finish was called before the dream was attached.");
+ Slog.w(mTag, "Finish was called before the dream was attached.");
stopSelf();
return;
}
@@ -1026,8 +1026,10 @@
}
private void wakeUp(boolean fromSystem) {
- if (mDebug) Slog.v(TAG, "wakeUp(): fromSystem=" + fromSystem
- + ", mWaking=" + mWaking + ", mFinished=" + mFinished);
+ if (mDebug) {
+ Slog.v(mTag, "wakeUp(): fromSystem=" + fromSystem + ", mWaking=" + mWaking
+ + ", mFinished=" + mFinished);
+ }
if (!mWaking && !mFinished) {
mWaking = true;
@@ -1052,7 +1054,7 @@
// it we were finishing immediately.
if (!fromSystem && !mFinished) {
if (mActivity == null) {
- Slog.w(TAG, "WakeUp was called before the dream was attached.");
+ Slog.w(mTag, "WakeUp was called before the dream was attached.");
} else {
try {
mDreamManager.finishSelf(mDreamToken, false /*immediate*/);
@@ -1067,7 +1069,7 @@
/** {@inheritDoc} */
@Override
public void onDestroy() {
- if (mDebug) Slog.v(TAG, "onDestroy()");
+ if (mDebug) Slog.v(mTag, "onDestroy()");
// hook for subclasses
// Just in case destroy came in before detach, let's take care of that now
@@ -1083,9 +1085,9 @@
*
* Must run on mHandler.
*/
- private final void detach() {
+ private void detach() {
if (mStarted) {
- if (mDebug) Slog.v(TAG, "detach(): Calling onDreamingStopped()");
+ if (mDebug) Slog.v(mTag, "detach(): Calling onDreamingStopped()");
mStarted = false;
onDreamingStopped();
}
@@ -1110,12 +1112,12 @@
*/
private void attach(IBinder dreamToken, boolean canDoze, IRemoteCallback started) {
if (mDreamToken != null) {
- Slog.e(TAG, "attach() called when dream with token=" + mDreamToken
+ Slog.e(mTag, "attach() called when dream with token=" + mDreamToken
+ " already attached");
return;
}
if (mFinished || mWaking) {
- Slog.w(TAG, "attach() called after dream already finished");
+ Slog.w(mTag, "attach() called after dream already finished");
try {
mDreamManager.finishSelf(dreamToken, true /*immediate*/);
} catch (RemoteException ex) {
@@ -1158,10 +1160,9 @@
try {
if (!ActivityTaskManager.getService().startDreamActivity(i)) {
detach();
- return;
}
} catch (RemoteException e) {
- Log.w(TAG, "Could not connect to activity task manager to start dream activity");
+ Log.w(mTag, "Could not connect to activity task manager to start dream activity");
e.rethrowFromSystemServer();
}
} else {
@@ -1191,7 +1192,7 @@
// along well. Dreams usually don't need such bars anyways, so disable them by default.
mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
- // Hide all insets when the dream is showing
+ // Hide all insets when the dream is showing
mWindow.getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
mWindow.setDecorFitsSystemWindows(false);
@@ -1207,7 +1208,7 @@
try {
overlay.startDream(mWindow.getAttributes(), mOverlayCallback);
} catch (RemoteException e) {
- Log.e(TAG, "could not send window attributes:" + e);
+ Log.e(mTag, "could not send window attributes:" + e);
}
});
}
@@ -1243,17 +1244,12 @@
@Override
protected void dump(final FileDescriptor fd, PrintWriter pw, final String[] args) {
- DumpUtils.dumpAsync(mHandler, new Dump() {
- @Override
- public void dump(PrintWriter pw, String prefix) {
- dumpOnHandler(fd, pw, args);
- }
- }, pw, "", 1000);
+ DumpUtils.dumpAsync(mHandler, (pw1, prefix) -> dumpOnHandler(fd, pw1, args), pw, "", 1000);
}
/** @hide */
protected void dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.print(TAG + ": ");
+ pw.print(mTag + ": ");
if (mFinished) {
pw.println("stopped");
} else {
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/DefaultSelectionToolbarRenderService.java b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
similarity index 91%
rename from services/selectiontoolbar/java/com/android/server/selectiontoolbar/DefaultSelectionToolbarRenderService.java
rename to core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
index c26965d..c452e06 100644
--- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/DefaultSelectionToolbarRenderService.java
+++ b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
@@ -14,15 +14,17 @@
* limitations under the License.
*/
-package com.android.server.selectiontoolbar;
+package android.service.selectiontoolbar;
-import android.service.selectiontoolbar.SelectionToolbarRenderService;
import android.util.Log;
import android.view.selectiontoolbar.ShowInfo;
/**
* The default implementation of {@link SelectionToolbarRenderService}.
+ *
+ * @hide
*/
+// TODO(b/214122495): fix class not found then move to system service folder
public final class DefaultSelectionToolbarRenderService extends SelectionToolbarRenderService {
private static final String TAG = "DefaultSelectionToolbarRenderService";
diff --git a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
new file mode 100644
index 0000000..95ecc4e
--- /dev/null
+++ b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
@@ -0,0 +1,1619 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.selectiontoolbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.Size;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.Transformation;
+import android.widget.ArrayAdapter;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.internal.widget.floatingtoolbar.FloatingToolbarPopup;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * A popup window used by the floating toolbar to render menu items in the local app process.
+ *
+ * This class is responsible for the rendering/animation of the floating toolbar.
+ * It holds 2 panels (i.e. main panel and overflow panel) and an overflow button
+ * to transition between panels.
+ */
+
+final class RemoteSelectionToolbar implements FloatingToolbarPopup {
+
+ /* Minimum and maximum number of items allowed in the overflow. */
+ private static final int MIN_OVERFLOW_SIZE = 2;
+ private static final int MAX_OVERFLOW_SIZE = 4;
+
+ private final Context mContext;
+ private final View mParent; // Parent for the popup window.
+ private final PopupWindow mPopupWindow;
+
+ /* Margins between the popup window and its content. */
+ private final int mMarginHorizontal;
+ private final int mMarginVertical;
+
+ /* View components */
+ private final ViewGroup mContentContainer; // holds all contents.
+ private final ViewGroup mMainPanel; // holds menu items that are initially displayed.
+ // holds menu items hidden in the overflow.
+ private final OverflowPanel mOverflowPanel;
+ private final ImageButton mOverflowButton; // opens/closes the overflow.
+ /* overflow button drawables. */
+ private final Drawable mArrow;
+ private final Drawable mOverflow;
+ private final AnimatedVectorDrawable mToArrow;
+ private final AnimatedVectorDrawable mToOverflow;
+
+ private final OverflowPanelViewHelper mOverflowPanelViewHelper;
+
+ /* Animation interpolators. */
+ private final Interpolator mLogAccelerateInterpolator;
+ private final Interpolator mFastOutSlowInInterpolator;
+ private final Interpolator mLinearOutSlowInInterpolator;
+ private final Interpolator mFastOutLinearInInterpolator;
+
+ /* Animations. */
+ private final AnimatorSet mShowAnimation;
+ private final AnimatorSet mDismissAnimation;
+ private final AnimatorSet mHideAnimation;
+ private final AnimationSet mOpenOverflowAnimation;
+ private final AnimationSet mCloseOverflowAnimation;
+ private final Animation.AnimationListener mOverflowAnimationListener;
+
+ private final Rect mViewPortOnScreen = new Rect(); // portion of screen we can draw in.
+ private final Point mCoordsOnWindow = new Point(); // popup window coordinates.
+ /* Temporary data holders. Reset values before using. */
+ private final int[] mTmpCoords = new int[2];
+
+ private final Region mTouchableRegion = new Region();
+ private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer =
+ info -> {
+ info.contentInsets.setEmpty();
+ info.visibleInsets.setEmpty();
+ info.touchableRegion.set(mTouchableRegion);
+ info.setTouchableInsets(
+ ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+ };
+
+ private final int mLineHeight;
+ private final int mIconTextSpacing;
+
+ /**
+ * @see OverflowPanelViewHelper#preparePopupContent().
+ */
+ private final Runnable mPreparePopupContentRTLHelper = new Runnable() {
+ @Override
+ public void run() {
+ setPanelsStatesAtRestingPosition();
+ setContentAreaAsTouchableSurface();
+ mContentContainer.setAlpha(1);
+ }
+ };
+
+ private boolean mDismissed = true; // tracks whether this popup is dismissed or dismissing.
+ private boolean mHidden; // tracks whether this popup is hidden or hiding.
+
+ /* Calculated sizes for panels and overflow button. */
+ private final Size mOverflowButtonSize;
+ private Size mOverflowPanelSize; // Should be null when there is no overflow.
+ private Size mMainPanelSize;
+
+ /* Menu items and click listeners */
+ private final Map<MenuItemRepr, MenuItem> mMenuItems = new LinkedHashMap<>();
+ private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
+ private final View.OnClickListener mMenuItemButtonOnClickListener =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mOnMenuItemClickListener == null) {
+ return;
+ }
+ final Object tag = v.getTag();
+ if (!(tag instanceof MenuItemRepr)) {
+ return;
+ }
+ final MenuItem menuItem = mMenuItems.get((MenuItemRepr) tag);
+ if (menuItem == null) {
+ return;
+ }
+ mOnMenuItemClickListener.onMenuItemClick(menuItem);
+ }
+ };
+
+ private boolean mOpenOverflowUpwards; // Whether the overflow opens upwards or downwards.
+ private boolean mIsOverflowOpen;
+
+ private int mTransitionDurationScale; // Used to scale the toolbar transition duration.
+
+ private final Rect mPreviousContentRect = new Rect();
+ private int mSuggestedWidth;
+ private boolean mWidthChanged = true;
+
+ /**
+ * Initializes a new floating toolbar popup.
+ *
+ * @param parent A parent view to get the {@link android.view.View#getWindowToken()} token
+ * from.
+ */
+ RemoteSelectionToolbar(Context context, View parent) {
+ mParent = Objects.requireNonNull(parent);
+ mContext = applyDefaultTheme(context);
+ mContentContainer = createContentContainer(mContext);
+ mPopupWindow = createPopupWindow(mContentContainer);
+ mMarginHorizontal = parent.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
+ mMarginVertical = parent.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_vertical_margin);
+ mLineHeight = context.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_height);
+ mIconTextSpacing = context.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_icon_text_spacing);
+
+ // Interpolators
+ mLogAccelerateInterpolator = new LogAccelerateInterpolator();
+ mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.fast_out_slow_in);
+ mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.linear_out_slow_in);
+ mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.fast_out_linear_in);
+
+ // Drawables. Needed for views.
+ mArrow = mContext.getResources()
+ .getDrawable(R.drawable.ft_avd_tooverflow, mContext.getTheme());
+ mArrow.setAutoMirrored(true);
+ mOverflow = mContext.getResources()
+ .getDrawable(R.drawable.ft_avd_toarrow, mContext.getTheme());
+ mOverflow.setAutoMirrored(true);
+ mToArrow = (AnimatedVectorDrawable) mContext.getResources()
+ .getDrawable(R.drawable.ft_avd_toarrow_animation, mContext.getTheme());
+ mToArrow.setAutoMirrored(true);
+ mToOverflow = (AnimatedVectorDrawable) mContext.getResources()
+ .getDrawable(R.drawable.ft_avd_tooverflow_animation, mContext.getTheme());
+ mToOverflow.setAutoMirrored(true);
+
+ // Views
+ mOverflowButton = createOverflowButton();
+ mOverflowButtonSize = measure(mOverflowButton);
+ mMainPanel = createMainPanel();
+ mOverflowPanelViewHelper = new OverflowPanelViewHelper(mContext, mIconTextSpacing);
+ mOverflowPanel = createOverflowPanel();
+
+ // Animation. Need views.
+ mOverflowAnimationListener = createOverflowAnimationListener();
+ mOpenOverflowAnimation = new AnimationSet(true);
+ mOpenOverflowAnimation.setAnimationListener(mOverflowAnimationListener);
+ mCloseOverflowAnimation = new AnimationSet(true);
+ mCloseOverflowAnimation.setAnimationListener(mOverflowAnimationListener);
+ mShowAnimation = createEnterAnimation(mContentContainer);
+ mDismissAnimation = createExitAnimation(
+ mContentContainer,
+ 150, // startDelay
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPopupWindow.dismiss();
+ mContentContainer.removeAllViews();
+ }
+ });
+ mHideAnimation = createExitAnimation(
+ mContentContainer,
+ 0, // startDelay
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPopupWindow.dismiss();
+ }
+ });
+ }
+
+ @Override
+ public boolean setOutsideTouchable(
+ boolean outsideTouchable, @Nullable PopupWindow.OnDismissListener onDismiss) {
+ boolean ret = false;
+ if (mPopupWindow.isOutsideTouchable() ^ outsideTouchable) {
+ mPopupWindow.setOutsideTouchable(outsideTouchable);
+ mPopupWindow.setFocusable(!outsideTouchable);
+ mPopupWindow.update();
+ ret = true;
+ }
+ mPopupWindow.setOnDismissListener(onDismiss);
+ return ret;
+ }
+
+ /**
+ * Lays out buttons for the specified menu items.
+ * Requires a subsequent call to {@link FloatingToolbar#show()} to show the items.
+ */
+ private void layoutMenuItems(
+ List<MenuItem> menuItems,
+ MenuItem.OnMenuItemClickListener menuItemClickListener,
+ int suggestedWidth) {
+ cancelOverflowAnimations();
+ clearPanels();
+ updateMenuItems(menuItems, menuItemClickListener);
+ menuItems = layoutMainPanelItems(menuItems, getAdjustedToolbarWidth(suggestedWidth));
+ if (!menuItems.isEmpty()) {
+ // Add remaining items to the overflow.
+ layoutOverflowPanelItems(menuItems);
+ }
+ updatePopupSize();
+ }
+
+ /**
+ * Updates the popup's menu items without rebuilding the widget.
+ * Use in place of layoutMenuItems() when the popup's views need not be reconstructed.
+ *
+ * @see #isLayoutRequired(List<MenuItem>)
+ */
+ private void updateMenuItems(
+ List<MenuItem> menuItems, MenuItem.OnMenuItemClickListener menuItemClickListener) {
+ mMenuItems.clear();
+ for (MenuItem menuItem : menuItems) {
+ mMenuItems.put(MenuItemRepr.of(menuItem), menuItem);
+ }
+ mOnMenuItemClickListener = menuItemClickListener;
+ }
+
+ /**
+ * Returns true if this popup needs a relayout to properly render the specified menu items.
+ */
+ private boolean isLayoutRequired(List<MenuItem> menuItems) {
+ return !MenuItemRepr.reprEquals(menuItems, mMenuItems.values());
+ }
+
+ @Override
+ public void setWidthChanged(boolean widthChanged) {
+ mWidthChanged = widthChanged;
+ }
+
+ @Override
+ public void setSuggestedWidth(int suggestedWidth) {
+ // Check if there's been a substantial width spec change.
+ int difference = Math.abs(suggestedWidth - mSuggestedWidth);
+ mWidthChanged = difference > (mSuggestedWidth * 0.2);
+ mSuggestedWidth = suggestedWidth;
+ }
+
+ @Override
+ public void show(List<MenuItem> menuItems,
+ MenuItem.OnMenuItemClickListener menuItemClickListener, Rect contentRect) {
+ if (isLayoutRequired(menuItems) || mWidthChanged) {
+ dismiss();
+ layoutMenuItems(menuItems, menuItemClickListener, mSuggestedWidth);
+ } else {
+ updateMenuItems(menuItems, menuItemClickListener);
+ }
+ if (!isShowing()) {
+ show(contentRect);
+ } else if (!mPreviousContentRect.equals(contentRect)) {
+ updateCoordinates(contentRect);
+ }
+ mWidthChanged = false;
+ mPreviousContentRect.set(contentRect);
+ }
+
+ private void show(Rect contentRectOnScreen) {
+ Objects.requireNonNull(contentRectOnScreen);
+
+ if (isShowing()) {
+ return;
+ }
+
+ mHidden = false;
+ mDismissed = false;
+ cancelDismissAndHideAnimations();
+ cancelOverflowAnimations();
+
+ refreshCoordinatesAndOverflowDirection(contentRectOnScreen);
+ preparePopupContent();
+ // We need to specify the position in window coordinates.
+ // TODO: Consider to use PopupWindow.setIsLaidOutInScreen(true) so that we can
+ // specify the popup position in screen coordinates.
+ mPopupWindow.showAtLocation(
+ mParent, Gravity.NO_GRAVITY, mCoordsOnWindow.x, mCoordsOnWindow.y);
+ setTouchableSurfaceInsetsComputer();
+ runShowAnimation();
+ }
+
+ @Override
+ public void dismiss() {
+ if (mDismissed) {
+ return;
+ }
+
+ mHidden = false;
+ mDismissed = true;
+ mHideAnimation.cancel();
+
+ runDismissAnimation();
+ setZeroTouchableSurface();
+ }
+
+ @Override
+ public void hide() {
+ if (!isShowing()) {
+ return;
+ }
+
+ mHidden = true;
+ runHideAnimation();
+ setZeroTouchableSurface();
+ }
+
+ @Override
+ public boolean isShowing() {
+ return !mDismissed && !mHidden;
+ }
+
+ @Override
+ public boolean isHidden() {
+ return mHidden;
+ }
+
+ /**
+ * Updates the coordinates of this popup.
+ * The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
+ * This is a no-op if this popup is not showing.
+ */
+ private void updateCoordinates(Rect contentRectOnScreen) {
+ Objects.requireNonNull(contentRectOnScreen);
+
+ if (!isShowing() || !mPopupWindow.isShowing()) {
+ return;
+ }
+
+ cancelOverflowAnimations();
+ refreshCoordinatesAndOverflowDirection(contentRectOnScreen);
+ preparePopupContent();
+ // We need to specify the position in window coordinates.
+ // TODO: Consider to use PopupWindow.setIsLaidOutInScreen(true) so that we can
+ // specify the popup position in screen coordinates.
+ mPopupWindow.update(
+ mCoordsOnWindow.x, mCoordsOnWindow.y,
+ mPopupWindow.getWidth(), mPopupWindow.getHeight());
+ }
+
+ private void refreshCoordinatesAndOverflowDirection(Rect contentRectOnScreen) {
+ refreshViewPort();
+
+ // Initialize x ensuring that the toolbar isn't rendered behind the nav bar in
+ // landscape.
+ final int x = Math.min(
+ contentRectOnScreen.centerX() - mPopupWindow.getWidth() / 2,
+ mViewPortOnScreen.right - mPopupWindow.getWidth());
+
+ final int y;
+
+ final int availableHeightAboveContent =
+ contentRectOnScreen.top - mViewPortOnScreen.top;
+ final int availableHeightBelowContent =
+ mViewPortOnScreen.bottom - contentRectOnScreen.bottom;
+
+ final int margin = 2 * mMarginVertical;
+ final int toolbarHeightWithVerticalMargin = mLineHeight + margin;
+
+ if (!hasOverflow()) {
+ if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin) {
+ // There is enough space at the top of the content.
+ y = contentRectOnScreen.top - toolbarHeightWithVerticalMargin;
+ } else if (availableHeightBelowContent >= toolbarHeightWithVerticalMargin) {
+ // There is enough space at the bottom of the content.
+ y = contentRectOnScreen.bottom;
+ } else if (availableHeightBelowContent >= mLineHeight) {
+ // Just enough space to fit the toolbar with no vertical margins.
+ y = contentRectOnScreen.bottom - mMarginVertical;
+ } else {
+ // Not enough space. Prefer to position as high as possible.
+ y = Math.max(
+ mViewPortOnScreen.top,
+ contentRectOnScreen.top - toolbarHeightWithVerticalMargin);
+ }
+ } else {
+ // Has an overflow.
+ final int minimumOverflowHeightWithMargin =
+ calculateOverflowHeight(MIN_OVERFLOW_SIZE) + margin;
+ final int availableHeightThroughContentDown =
+ mViewPortOnScreen.bottom - contentRectOnScreen.top
+ + toolbarHeightWithVerticalMargin;
+ final int availableHeightThroughContentUp =
+ contentRectOnScreen.bottom - mViewPortOnScreen.top
+ + toolbarHeightWithVerticalMargin;
+
+ if (availableHeightAboveContent >= minimumOverflowHeightWithMargin) {
+ // There is enough space at the top of the content rect for the overflow.
+ // Position above and open upwards.
+ updateOverflowHeight(availableHeightAboveContent - margin);
+ y = contentRectOnScreen.top - mPopupWindow.getHeight();
+ mOpenOverflowUpwards = true;
+ } else if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin
+ && availableHeightThroughContentDown >= minimumOverflowHeightWithMargin) {
+ // There is enough space at the top of the content rect for the main panel
+ // but not the overflow.
+ // Position above but open downwards.
+ updateOverflowHeight(availableHeightThroughContentDown - margin);
+ y = contentRectOnScreen.top - toolbarHeightWithVerticalMargin;
+ mOpenOverflowUpwards = false;
+ } else if (availableHeightBelowContent >= minimumOverflowHeightWithMargin) {
+ // There is enough space at the bottom of the content rect for the overflow.
+ // Position below and open downwards.
+ updateOverflowHeight(availableHeightBelowContent - margin);
+ y = contentRectOnScreen.bottom;
+ mOpenOverflowUpwards = false;
+ } else if (availableHeightBelowContent >= toolbarHeightWithVerticalMargin
+ && mViewPortOnScreen.height() >= minimumOverflowHeightWithMargin) {
+ // There is enough space at the bottom of the content rect for the main panel
+ // but not the overflow.
+ // Position below but open upwards.
+ updateOverflowHeight(availableHeightThroughContentUp - margin);
+ y = contentRectOnScreen.bottom + toolbarHeightWithVerticalMargin
+ - mPopupWindow.getHeight();
+ mOpenOverflowUpwards = true;
+ } else {
+ // Not enough space.
+ // Position at the top of the view port and open downwards.
+ updateOverflowHeight(mViewPortOnScreen.height() - margin);
+ y = mViewPortOnScreen.top;
+ mOpenOverflowUpwards = false;
+ }
+ }
+
+ // We later specify the location of PopupWindow relative to the attached window.
+ // The idea here is that 1) we can get the location of a View in both window coordinates
+ // and screen coordinates, where the offset between them should be equal to the window
+ // origin, and 2) we can use an arbitrary for this calculation while calculating the
+ // location of the rootview is supposed to be least expensive.
+ // TODO: Consider to use PopupWindow.setIsLaidOutInScreen(true) so that we can avoid
+ // the following calculation.
+ mParent.getRootView().getLocationOnScreen(mTmpCoords);
+ int rootViewLeftOnScreen = mTmpCoords[0];
+ int rootViewTopOnScreen = mTmpCoords[1];
+ mParent.getRootView().getLocationInWindow(mTmpCoords);
+ int rootViewLeftOnWindow = mTmpCoords[0];
+ int rootViewTopOnWindow = mTmpCoords[1];
+ int windowLeftOnScreen = rootViewLeftOnScreen - rootViewLeftOnWindow;
+ int windowTopOnScreen = rootViewTopOnScreen - rootViewTopOnWindow;
+ mCoordsOnWindow.set(
+ Math.max(0, x - windowLeftOnScreen), Math.max(0, y - windowTopOnScreen));
+ }
+
+ /**
+ * Performs the "show" animation on the floating popup.
+ */
+ private void runShowAnimation() {
+ mShowAnimation.start();
+ }
+
+ /**
+ * Performs the "dismiss" animation on the floating popup.
+ */
+ private void runDismissAnimation() {
+ mDismissAnimation.start();
+ }
+
+ /**
+ * Performs the "hide" animation on the floating popup.
+ */
+ private void runHideAnimation() {
+ mHideAnimation.start();
+ }
+
+ private void cancelDismissAndHideAnimations() {
+ mDismissAnimation.cancel();
+ mHideAnimation.cancel();
+ }
+
+ private void cancelOverflowAnimations() {
+ mContentContainer.clearAnimation();
+ mMainPanel.animate().cancel();
+ mOverflowPanel.animate().cancel();
+ mToArrow.stop();
+ mToOverflow.stop();
+ }
+
+ private void openOverflow() {
+ final int targetWidth = mOverflowPanelSize.getWidth();
+ final int targetHeight = mOverflowPanelSize.getHeight();
+ final int startWidth = mContentContainer.getWidth();
+ final int startHeight = mContentContainer.getHeight();
+ final float startY = mContentContainer.getY();
+ final float left = mContentContainer.getX();
+ final float right = left + mContentContainer.getWidth();
+ Animation widthAnimation = new Animation() {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
+ setWidth(mContentContainer, startWidth + deltaWidth);
+ if (isInRTLMode()) {
+ mContentContainer.setX(left);
+
+ // Lock the panels in place.
+ mMainPanel.setX(0);
+ mOverflowPanel.setX(0);
+ } else {
+ mContentContainer.setX(right - mContentContainer.getWidth());
+
+ // Offset the panels' positions so they look like they're locked in place
+ // on the screen.
+ mMainPanel.setX(mContentContainer.getWidth() - startWidth);
+ mOverflowPanel.setX(mContentContainer.getWidth() - targetWidth);
+ }
+ }
+ };
+ Animation heightAnimation = new Animation() {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
+ setHeight(mContentContainer, startHeight + deltaHeight);
+ if (mOpenOverflowUpwards) {
+ mContentContainer.setY(
+ startY - (mContentContainer.getHeight() - startHeight));
+ positionContentYCoordinatesIfOpeningOverflowUpwards();
+ }
+ }
+ };
+ final float overflowButtonStartX = mOverflowButton.getX();
+ final float overflowButtonTargetX =
+ isInRTLMode() ? overflowButtonStartX + targetWidth - mOverflowButton.getWidth()
+ : overflowButtonStartX - targetWidth + mOverflowButton.getWidth();
+ Animation overflowButtonAnimation = new Animation() {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ float overflowButtonX = overflowButtonStartX
+ + interpolatedTime * (overflowButtonTargetX - overflowButtonStartX);
+ float deltaContainerWidth =
+ isInRTLMode() ? 0 : mContentContainer.getWidth() - startWidth;
+ float actualOverflowButtonX = overflowButtonX + deltaContainerWidth;
+ mOverflowButton.setX(actualOverflowButtonX);
+ }
+ };
+ widthAnimation.setInterpolator(mLogAccelerateInterpolator);
+ widthAnimation.setDuration(getAdjustedDuration(250));
+ heightAnimation.setInterpolator(mFastOutSlowInInterpolator);
+ heightAnimation.setDuration(getAdjustedDuration(250));
+ overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator);
+ overflowButtonAnimation.setDuration(getAdjustedDuration(250));
+ mOpenOverflowAnimation.getAnimations().clear();
+ mOpenOverflowAnimation.getAnimations().clear();
+ mOpenOverflowAnimation.addAnimation(widthAnimation);
+ mOpenOverflowAnimation.addAnimation(heightAnimation);
+ mOpenOverflowAnimation.addAnimation(overflowButtonAnimation);
+ mContentContainer.startAnimation(mOpenOverflowAnimation);
+ mIsOverflowOpen = true;
+ mMainPanel.animate()
+ .alpha(0).withLayer()
+ .setInterpolator(mLinearOutSlowInInterpolator)
+ .setDuration(250)
+ .start();
+ mOverflowPanel.setAlpha(1); // fadeIn in 0ms.
+ }
+
+ private void closeOverflow() {
+ final int targetWidth = mMainPanelSize.getWidth();
+ final int startWidth = mContentContainer.getWidth();
+ final float left = mContentContainer.getX();
+ final float right = left + mContentContainer.getWidth();
+ Animation widthAnimation = new Animation() {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
+ setWidth(mContentContainer, startWidth + deltaWidth);
+ if (isInRTLMode()) {
+ mContentContainer.setX(left);
+
+ // Lock the panels in place.
+ mMainPanel.setX(0);
+ mOverflowPanel.setX(0);
+ } else {
+ mContentContainer.setX(right - mContentContainer.getWidth());
+
+ // Offset the panels' positions so they look like they're locked in place
+ // on the screen.
+ mMainPanel.setX(mContentContainer.getWidth() - targetWidth);
+ mOverflowPanel.setX(mContentContainer.getWidth() - startWidth);
+ }
+ }
+ };
+ final int targetHeight = mMainPanelSize.getHeight();
+ final int startHeight = mContentContainer.getHeight();
+ final float bottom = mContentContainer.getY() + mContentContainer.getHeight();
+ Animation heightAnimation = new Animation() {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
+ setHeight(mContentContainer, startHeight + deltaHeight);
+ if (mOpenOverflowUpwards) {
+ mContentContainer.setY(bottom - mContentContainer.getHeight());
+ positionContentYCoordinatesIfOpeningOverflowUpwards();
+ }
+ }
+ };
+ final float overflowButtonStartX = mOverflowButton.getX();
+ final float overflowButtonTargetX =
+ isInRTLMode() ? overflowButtonStartX - startWidth + mOverflowButton.getWidth()
+ : overflowButtonStartX + startWidth - mOverflowButton.getWidth();
+ Animation overflowButtonAnimation = new Animation() {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ float overflowButtonX = overflowButtonStartX
+ + interpolatedTime * (overflowButtonTargetX - overflowButtonStartX);
+ float deltaContainerWidth =
+ isInRTLMode() ? 0 : mContentContainer.getWidth() - startWidth;
+ float actualOverflowButtonX = overflowButtonX + deltaContainerWidth;
+ mOverflowButton.setX(actualOverflowButtonX);
+ }
+ };
+ widthAnimation.setInterpolator(mFastOutSlowInInterpolator);
+ widthAnimation.setDuration(getAdjustedDuration(250));
+ heightAnimation.setInterpolator(mLogAccelerateInterpolator);
+ heightAnimation.setDuration(getAdjustedDuration(250));
+ overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator);
+ overflowButtonAnimation.setDuration(getAdjustedDuration(250));
+ mCloseOverflowAnimation.getAnimations().clear();
+ mCloseOverflowAnimation.addAnimation(widthAnimation);
+ mCloseOverflowAnimation.addAnimation(heightAnimation);
+ mCloseOverflowAnimation.addAnimation(overflowButtonAnimation);
+ mContentContainer.startAnimation(mCloseOverflowAnimation);
+ mIsOverflowOpen = false;
+ mMainPanel.animate()
+ .alpha(1).withLayer()
+ .setInterpolator(mFastOutLinearInInterpolator)
+ .setDuration(100)
+ .start();
+ mOverflowPanel.animate()
+ .alpha(0).withLayer()
+ .setInterpolator(mLinearOutSlowInInterpolator)
+ .setDuration(150)
+ .start();
+ }
+
+ /**
+ * Defines the position of the floating toolbar popup panels when transition animation has
+ * stopped.
+ */
+ private void setPanelsStatesAtRestingPosition() {
+ mOverflowButton.setEnabled(true);
+ mOverflowPanel.awakenScrollBars();
+
+ if (mIsOverflowOpen) {
+ // Set open state.
+ final Size containerSize = mOverflowPanelSize;
+ setSize(mContentContainer, containerSize);
+ mMainPanel.setAlpha(0);
+ mMainPanel.setVisibility(View.INVISIBLE);
+ mOverflowPanel.setAlpha(1);
+ mOverflowPanel.setVisibility(View.VISIBLE);
+ mOverflowButton.setImageDrawable(mArrow);
+ mOverflowButton.setContentDescription(mContext.getString(
+ R.string.floating_toolbar_close_overflow_description));
+
+ // Update x-coordinates depending on RTL state.
+ if (isInRTLMode()) {
+ mContentContainer.setX(mMarginHorizontal); // align left
+ mMainPanel.setX(0); // align left
+ mOverflowButton.setX(// align right
+ containerSize.getWidth() - mOverflowButtonSize.getWidth());
+ mOverflowPanel.setX(0); // align left
+ } else {
+ mContentContainer.setX(// align right
+ mPopupWindow.getWidth() - containerSize.getWidth() - mMarginHorizontal);
+ mMainPanel.setX(-mContentContainer.getX()); // align right
+ mOverflowButton.setX(0); // align left
+ mOverflowPanel.setX(0); // align left
+ }
+
+ // Update y-coordinates depending on overflow's open direction.
+ if (mOpenOverflowUpwards) {
+ mContentContainer.setY(mMarginVertical); // align top
+ mMainPanel.setY(// align bottom
+ containerSize.getHeight() - mContentContainer.getHeight());
+ mOverflowButton.setY(// align bottom
+ containerSize.getHeight() - mOverflowButtonSize.getHeight());
+ mOverflowPanel.setY(0); // align top
+ } else {
+ // opens downwards.
+ mContentContainer.setY(mMarginVertical); // align top
+ mMainPanel.setY(0); // align top
+ mOverflowButton.setY(0); // align top
+ mOverflowPanel.setY(mOverflowButtonSize.getHeight()); // align bottom
+ }
+ } else {
+ // Overflow not open. Set closed state.
+ final Size containerSize = mMainPanelSize;
+ setSize(mContentContainer, containerSize);
+ mMainPanel.setAlpha(1);
+ mMainPanel.setVisibility(View.VISIBLE);
+ mOverflowPanel.setAlpha(0);
+ mOverflowPanel.setVisibility(View.INVISIBLE);
+ mOverflowButton.setImageDrawable(mOverflow);
+ mOverflowButton.setContentDescription(mContext.getString(
+ R.string.floating_toolbar_open_overflow_description));
+
+ if (hasOverflow()) {
+ // Update x-coordinates depending on RTL state.
+ if (isInRTLMode()) {
+ mContentContainer.setX(mMarginHorizontal); // align left
+ mMainPanel.setX(0); // align left
+ mOverflowButton.setX(0); // align left
+ mOverflowPanel.setX(0); // align left
+ } else {
+ mContentContainer.setX(// align right
+ mPopupWindow.getWidth() - containerSize.getWidth() - mMarginHorizontal);
+ mMainPanel.setX(0); // align left
+ mOverflowButton.setX(// align right
+ containerSize.getWidth() - mOverflowButtonSize.getWidth());
+ mOverflowPanel.setX(// align right
+ containerSize.getWidth() - mOverflowPanelSize.getWidth());
+ }
+
+ // Update y-coordinates depending on overflow's open direction.
+ if (mOpenOverflowUpwards) {
+ mContentContainer.setY(// align bottom
+ mMarginVertical + mOverflowPanelSize.getHeight()
+ - containerSize.getHeight());
+ mMainPanel.setY(0); // align top
+ mOverflowButton.setY(0); // align top
+ mOverflowPanel.setY(// align bottom
+ containerSize.getHeight() - mOverflowPanelSize.getHeight());
+ } else {
+ // opens downwards.
+ mContentContainer.setY(mMarginVertical); // align top
+ mMainPanel.setY(0); // align top
+ mOverflowButton.setY(0); // align top
+ mOverflowPanel.setY(mOverflowButtonSize.getHeight()); // align bottom
+ }
+ } else {
+ // No overflow.
+ mContentContainer.setX(mMarginHorizontal); // align left
+ mContentContainer.setY(mMarginVertical); // align top
+ mMainPanel.setX(0); // align left
+ mMainPanel.setY(0); // align top
+ }
+ }
+ }
+
+ private void updateOverflowHeight(int suggestedHeight) {
+ if (hasOverflow()) {
+ final int maxItemSize =
+ (suggestedHeight - mOverflowButtonSize.getHeight()) / mLineHeight;
+ final int newHeight = calculateOverflowHeight(maxItemSize);
+ if (mOverflowPanelSize.getHeight() != newHeight) {
+ mOverflowPanelSize = new Size(mOverflowPanelSize.getWidth(), newHeight);
+ }
+ setSize(mOverflowPanel, mOverflowPanelSize);
+ if (mIsOverflowOpen) {
+ setSize(mContentContainer, mOverflowPanelSize);
+ if (mOpenOverflowUpwards) {
+ final int deltaHeight = mOverflowPanelSize.getHeight() - newHeight;
+ mContentContainer.setY(mContentContainer.getY() + deltaHeight);
+ mOverflowButton.setY(mOverflowButton.getY() - deltaHeight);
+ }
+ } else {
+ setSize(mContentContainer, mMainPanelSize);
+ }
+ updatePopupSize();
+ }
+ }
+
+ private void updatePopupSize() {
+ int width = 0;
+ int height = 0;
+ if (mMainPanelSize != null) {
+ width = Math.max(width, mMainPanelSize.getWidth());
+ height = Math.max(height, mMainPanelSize.getHeight());
+ }
+ if (mOverflowPanelSize != null) {
+ width = Math.max(width, mOverflowPanelSize.getWidth());
+ height = Math.max(height, mOverflowPanelSize.getHeight());
+ }
+ mPopupWindow.setWidth(width + mMarginHorizontal * 2);
+ mPopupWindow.setHeight(height + mMarginVertical * 2);
+ maybeComputeTransitionDurationScale();
+ }
+
+ private void refreshViewPort() {
+ mParent.getWindowVisibleDisplayFrame(mViewPortOnScreen);
+ }
+
+ private int getAdjustedToolbarWidth(int suggestedWidth) {
+ int width = suggestedWidth;
+ refreshViewPort();
+ int maximumWidth = mViewPortOnScreen.width() - 2 * mParent.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
+ if (width <= 0) {
+ width = mParent.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_preferred_width);
+ }
+ return Math.min(width, maximumWidth);
+ }
+
+ /**
+ * Sets the touchable region of this popup to be zero. This means that all touch events on
+ * this popup will go through to the surface behind it.
+ */
+ private void setZeroTouchableSurface() {
+ mTouchableRegion.setEmpty();
+ }
+
+ /**
+ * Sets the touchable region of this popup to be the area occupied by its content.
+ */
+ private void setContentAreaAsTouchableSurface() {
+ Objects.requireNonNull(mMainPanelSize);
+ final int width;
+ final int height;
+ if (mIsOverflowOpen) {
+ Objects.requireNonNull(mOverflowPanelSize);
+ width = mOverflowPanelSize.getWidth();
+ height = mOverflowPanelSize.getHeight();
+ } else {
+ width = mMainPanelSize.getWidth();
+ height = mMainPanelSize.getHeight();
+ }
+ mTouchableRegion.set(
+ (int) mContentContainer.getX(),
+ (int) mContentContainer.getY(),
+ (int) mContentContainer.getX() + width,
+ (int) mContentContainer.getY() + height);
+ }
+
+ /**
+ * Make the touchable area of this popup be the area specified by mTouchableRegion.
+ * This should be called after the popup window has been dismissed (dismiss/hide)
+ * and is probably being re-shown with a new content root view.
+ */
+ private void setTouchableSurfaceInsetsComputer() {
+ ViewTreeObserver viewTreeObserver = mPopupWindow.getContentView()
+ .getRootView()
+ .getViewTreeObserver();
+ viewTreeObserver.removeOnComputeInternalInsetsListener(mInsetsComputer);
+ viewTreeObserver.addOnComputeInternalInsetsListener(mInsetsComputer);
+ }
+
+ private boolean isInRTLMode() {
+ return mContext.getApplicationInfo().hasRtlSupport()
+ && mContext.getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_RTL;
+ }
+
+ private boolean hasOverflow() {
+ return mOverflowPanelSize != null;
+ }
+
+ /**
+ * Fits as many menu items in the main panel and returns a list of the menu items that
+ * were not fit in.
+ *
+ * @return The menu items that are not included in this main panel.
+ */
+ public List<MenuItem> layoutMainPanelItems(
+ List<MenuItem> menuItems, final int toolbarWidth) {
+ Objects.requireNonNull(menuItems);
+
+ int availableWidth = toolbarWidth;
+
+ final LinkedList<MenuItem> remainingMenuItems = new LinkedList<>();
+ // add the overflow menu items to the end of the remainingMenuItems list.
+ final LinkedList<MenuItem> overflowMenuItems = new LinkedList();
+ for (MenuItem menuItem : menuItems) {
+ if (menuItem.getItemId() != android.R.id.textAssist
+ && menuItem.requiresOverflow()) {
+ overflowMenuItems.add(menuItem);
+ } else {
+ remainingMenuItems.add(menuItem);
+ }
+ }
+ remainingMenuItems.addAll(overflowMenuItems);
+
+ mMainPanel.removeAllViews();
+ mMainPanel.setPaddingRelative(0, 0, 0, 0);
+
+ int lastGroupId = -1;
+ boolean isFirstItem = true;
+ while (!remainingMenuItems.isEmpty()) {
+ final MenuItem menuItem = remainingMenuItems.peek();
+
+ // if this is the first item, regardless of requiresOverflow(), it should be
+ // displayed on the main panel. Otherwise all items including this one will be
+ // overflow items, and should be displayed in overflow panel.
+ if (!isFirstItem && menuItem.requiresOverflow()) {
+ break;
+ }
+
+ final boolean showIcon = isFirstItem && menuItem.getItemId() == R.id.textAssist;
+ final View menuItemButton = createMenuItemButton(
+ mContext, menuItem, mIconTextSpacing, showIcon);
+ if (!showIcon && menuItemButton instanceof LinearLayout) {
+ ((LinearLayout) menuItemButton).setGravity(Gravity.CENTER);
+ }
+
+ // Adding additional start padding for the first button to even out button spacing.
+ if (isFirstItem) {
+ menuItemButton.setPaddingRelative(
+ (int) (1.5 * menuItemButton.getPaddingStart()),
+ menuItemButton.getPaddingTop(),
+ menuItemButton.getPaddingEnd(),
+ menuItemButton.getPaddingBottom());
+ }
+
+ // Adding additional end padding for the last button to even out button spacing.
+ boolean isLastItem = remainingMenuItems.size() == 1;
+ if (isLastItem) {
+ menuItemButton.setPaddingRelative(
+ menuItemButton.getPaddingStart(),
+ menuItemButton.getPaddingTop(),
+ (int) (1.5 * menuItemButton.getPaddingEnd()),
+ menuItemButton.getPaddingBottom());
+ }
+
+ menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ final int menuItemButtonWidth = Math.min(
+ menuItemButton.getMeasuredWidth(), toolbarWidth);
+
+ // Check if we can fit an item while reserving space for the overflowButton.
+ final boolean canFitWithOverflow =
+ menuItemButtonWidth <= availableWidth - mOverflowButtonSize.getWidth();
+ final boolean canFitNoOverflow =
+ isLastItem && menuItemButtonWidth <= availableWidth;
+ if (canFitWithOverflow || canFitNoOverflow) {
+ setButtonTagAndClickListener(menuItemButton, menuItem);
+ // Set tooltips for main panel items, but not overflow items (b/35726766).
+ menuItemButton.setTooltipText(menuItem.getTooltipText());
+ mMainPanel.addView(menuItemButton);
+ final ViewGroup.LayoutParams params = menuItemButton.getLayoutParams();
+ params.width = menuItemButtonWidth;
+ menuItemButton.setLayoutParams(params);
+ availableWidth -= menuItemButtonWidth;
+ remainingMenuItems.pop();
+ } else {
+ break;
+ }
+ lastGroupId = menuItem.getGroupId();
+ isFirstItem = false;
+ }
+
+ if (!remainingMenuItems.isEmpty()) {
+ // Reserve space for overflowButton.
+ mMainPanel.setPaddingRelative(0, 0, mOverflowButtonSize.getWidth(), 0);
+ }
+
+ mMainPanelSize = measure(mMainPanel);
+ return remainingMenuItems;
+ }
+
+ private void layoutOverflowPanelItems(List<MenuItem> menuItems) {
+ ArrayAdapter<MenuItem> overflowPanelAdapter =
+ (ArrayAdapter<MenuItem>) mOverflowPanel.getAdapter();
+ overflowPanelAdapter.clear();
+ final int size = menuItems.size();
+ for (int i = 0; i < size; i++) {
+ overflowPanelAdapter.add(menuItems.get(i));
+ }
+ mOverflowPanel.setAdapter(overflowPanelAdapter);
+ if (mOpenOverflowUpwards) {
+ mOverflowPanel.setY(0);
+ } else {
+ mOverflowPanel.setY(mOverflowButtonSize.getHeight());
+ }
+
+ int width = Math.max(getOverflowWidth(), mOverflowButtonSize.getWidth());
+ int height = calculateOverflowHeight(MAX_OVERFLOW_SIZE);
+ mOverflowPanelSize = new Size(width, height);
+ setSize(mOverflowPanel, mOverflowPanelSize);
+ }
+
+ /**
+ * Resets the content container and appropriately position it's panels.
+ */
+ private void preparePopupContent() {
+ mContentContainer.removeAllViews();
+
+ // Add views in the specified order so they stack up as expected.
+ // Order: overflowPanel, mainPanel, overflowButton.
+ if (hasOverflow()) {
+ mContentContainer.addView(mOverflowPanel);
+ }
+ mContentContainer.addView(mMainPanel);
+ if (hasOverflow()) {
+ mContentContainer.addView(mOverflowButton);
+ }
+ setPanelsStatesAtRestingPosition();
+ setContentAreaAsTouchableSurface();
+
+ // The positioning of contents in RTL is wrong when the view is first rendered.
+ // Hide the view and post a runnable to recalculate positions and render the view.
+ // TODO: Investigate why this happens and fix.
+ if (isInRTLMode()) {
+ mContentContainer.setAlpha(0);
+ mContentContainer.post(mPreparePopupContentRTLHelper);
+ }
+ }
+
+ /**
+ * Clears out the panels and their container. Resets their calculated sizes.
+ */
+ private void clearPanels() {
+ mOverflowPanelSize = null;
+ mMainPanelSize = null;
+ mIsOverflowOpen = false;
+ mMainPanel.removeAllViews();
+ ArrayAdapter<MenuItem> overflowPanelAdapter =
+ (ArrayAdapter<MenuItem>) mOverflowPanel.getAdapter();
+ overflowPanelAdapter.clear();
+ mOverflowPanel.setAdapter(overflowPanelAdapter);
+ mContentContainer.removeAllViews();
+ }
+
+ private void positionContentYCoordinatesIfOpeningOverflowUpwards() {
+ if (mOpenOverflowUpwards) {
+ mMainPanel.setY(mContentContainer.getHeight() - mMainPanelSize.getHeight());
+ mOverflowButton.setY(mContentContainer.getHeight() - mOverflowButton.getHeight());
+ mOverflowPanel.setY(mContentContainer.getHeight() - mOverflowPanelSize.getHeight());
+ }
+ }
+
+ private int getOverflowWidth() {
+ int overflowWidth = 0;
+ final int count = mOverflowPanel.getAdapter().getCount();
+ for (int i = 0; i < count; i++) {
+ MenuItem menuItem = (MenuItem) mOverflowPanel.getAdapter().getItem(i);
+ overflowWidth =
+ Math.max(mOverflowPanelViewHelper.calculateWidth(menuItem), overflowWidth);
+ }
+ return overflowWidth;
+ }
+
+ private int calculateOverflowHeight(int maxItemSize) {
+ // Maximum of 4 items, minimum of 2 if the overflow has to scroll.
+ int actualSize = Math.min(
+ MAX_OVERFLOW_SIZE,
+ Math.min(
+ Math.max(MIN_OVERFLOW_SIZE, maxItemSize),
+ mOverflowPanel.getCount()));
+ int extension = 0;
+ if (actualSize < mOverflowPanel.getCount()) {
+ // The overflow will require scrolling to get to all the items.
+ // Extend the height so that part of the hidden items is displayed.
+ extension = (int) (mLineHeight * 0.5f);
+ }
+ return actualSize * mLineHeight
+ + mOverflowButtonSize.getHeight()
+ + extension;
+ }
+
+ private void setButtonTagAndClickListener(View menuItemButton, MenuItem menuItem) {
+ menuItemButton.setTag(MenuItemRepr.of(menuItem));
+ menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener);
+ }
+
+ /**
+ * NOTE: Use only in android.view.animation.* animations. Do not use in android.animation.*
+ * animations. See comment about this in the code.
+ */
+ private int getAdjustedDuration(int originalDuration) {
+ if (mTransitionDurationScale < 150) {
+ // For smaller transition, decrease the time.
+ return Math.max(originalDuration - 50, 0);
+ } else if (mTransitionDurationScale > 300) {
+ // For bigger transition, increase the time.
+ return originalDuration + 50;
+ }
+
+ // Scale the animation duration with getDurationScale(). This allows
+ // android.view.animation.* animations to scale just like android.animation.* animations
+ // when animator duration scale is adjusted in "Developer Options".
+ // For this reason, do not use this method for android.animation.* animations.
+ return (int) (originalDuration * ValueAnimator.getDurationScale());
+ }
+
+ private void maybeComputeTransitionDurationScale() {
+ if (mMainPanelSize != null && mOverflowPanelSize != null) {
+ int w = mMainPanelSize.getWidth() - mOverflowPanelSize.getWidth();
+ int h = mOverflowPanelSize.getHeight() - mMainPanelSize.getHeight();
+ mTransitionDurationScale = (int) (Math.sqrt(w * w + h * h)
+ / mContentContainer.getContext().getResources().getDisplayMetrics().density);
+ }
+ }
+
+ private ViewGroup createMainPanel() {
+ ViewGroup mainPanel = new LinearLayout(mContext) {
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (isOverflowAnimating()) {
+ // Update widthMeasureSpec to make sure that this view is not clipped
+ // as we offset its coordinates with respect to its parent.
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(
+ mMainPanelSize.getWidth(),
+ MeasureSpec.EXACTLY);
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // Intercept the touch event while the overflow is animating.
+ return isOverflowAnimating();
+ }
+ };
+ return mainPanel;
+ }
+
+ private ImageButton createOverflowButton() {
+ final ImageButton overflowButton = (ImageButton) LayoutInflater.from(mContext)
+ .inflate(R.layout.floating_popup_overflow_button, null);
+ overflowButton.setImageDrawable(mOverflow);
+ overflowButton.setOnClickListener(v -> {
+ if (mIsOverflowOpen) {
+ overflowButton.setImageDrawable(mToOverflow);
+ mToOverflow.start();
+ closeOverflow();
+ } else {
+ overflowButton.setImageDrawable(mToArrow);
+ mToArrow.start();
+ openOverflow();
+ }
+ });
+ return overflowButton;
+ }
+
+ private OverflowPanel createOverflowPanel() {
+ final OverflowPanel overflowPanel = new OverflowPanel(this);
+ overflowPanel.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ overflowPanel.setDivider(null);
+ overflowPanel.setDividerHeight(0);
+
+ final ArrayAdapter adapter =
+ new ArrayAdapter<MenuItem>(mContext, 0) {
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ return mOverflowPanelViewHelper.getView(
+ getItem(position), mOverflowPanelSize.getWidth(), convertView);
+ }
+ };
+ overflowPanel.setAdapter(adapter);
+
+ overflowPanel.setOnItemClickListener((parent, view, position, id) -> {
+ MenuItem menuItem = (MenuItem) overflowPanel.getAdapter().getItem(position);
+ if (mOnMenuItemClickListener != null) {
+ mOnMenuItemClickListener.onMenuItemClick(menuItem);
+ }
+ });
+
+ return overflowPanel;
+ }
+
+ private boolean isOverflowAnimating() {
+ final boolean overflowOpening = mOpenOverflowAnimation.hasStarted()
+ && !mOpenOverflowAnimation.hasEnded();
+ final boolean overflowClosing = mCloseOverflowAnimation.hasStarted()
+ && !mCloseOverflowAnimation.hasEnded();
+ return overflowOpening || overflowClosing;
+ }
+
+ private Animation.AnimationListener createOverflowAnimationListener() {
+ Animation.AnimationListener listener = new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ // Disable the overflow button while it's animating.
+ // It will be re-enabled when the animation stops.
+ mOverflowButton.setEnabled(false);
+ // Ensure both panels have visibility turned on when the overflow animation
+ // starts.
+ mMainPanel.setVisibility(View.VISIBLE);
+ mOverflowPanel.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ // Posting this because it seems like this is called before the animation
+ // actually ends.
+ mContentContainer.post(() -> {
+ setPanelsStatesAtRestingPosition();
+ setContentAreaAsTouchableSurface();
+ });
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+ };
+ return listener;
+ }
+
+ private static Size measure(View view) {
+ Preconditions.checkState(view.getParent() == null);
+ view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ return new Size(view.getMeasuredWidth(), view.getMeasuredHeight());
+ }
+
+ private static void setSize(View view, int width, int height) {
+ view.setMinimumWidth(width);
+ view.setMinimumHeight(height);
+ ViewGroup.LayoutParams params = view.getLayoutParams();
+ params = (params == null) ? new ViewGroup.LayoutParams(0, 0) : params;
+ params.width = width;
+ params.height = height;
+ view.setLayoutParams(params);
+ }
+
+ private static void setSize(View view, Size size) {
+ setSize(view, size.getWidth(), size.getHeight());
+ }
+
+ private static void setWidth(View view, int width) {
+ ViewGroup.LayoutParams params = view.getLayoutParams();
+ setSize(view, width, params.height);
+ }
+
+ private static void setHeight(View view, int height) {
+ ViewGroup.LayoutParams params = view.getLayoutParams();
+ setSize(view, params.width, height);
+ }
+
+ /**
+ * A custom ListView for the overflow panel.
+ */
+ private static final class OverflowPanel extends ListView {
+
+ private final RemoteSelectionToolbar mPopup;
+
+ OverflowPanel(RemoteSelectionToolbar popup) {
+ super(Objects.requireNonNull(popup).mContext);
+ this.mPopup = popup;
+ setScrollBarDefaultDelayBeforeFade(ViewConfiguration.getScrollDefaultDelay() * 3);
+ setScrollIndicators(View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Update heightMeasureSpec to make sure that this view is not clipped
+ // as we offset it's coordinates with respect to its parent.
+ int height = mPopup.mOverflowPanelSize.getHeight()
+ - mPopup.mOverflowButtonSize.getHeight();
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (mPopup.isOverflowAnimating()) {
+ // Eat the touch event.
+ return true;
+ }
+ return super.dispatchTouchEvent(ev);
+ }
+
+ @Override
+ protected boolean awakenScrollBars() {
+ return super.awakenScrollBars();
+ }
+ }
+
+ /**
+ * A custom interpolator used for various floating toolbar animations.
+ */
+ private static final class LogAccelerateInterpolator implements Interpolator {
+
+ private static final int BASE = 100;
+ private static final float LOGS_SCALE = 1f / computeLog(1, BASE);
+
+ private static float computeLog(float t, int base) {
+ return (float) (1 - Math.pow(base, -t));
+ }
+
+ @Override
+ public float getInterpolation(float t) {
+ return 1 - computeLog(1 - t, BASE) * LOGS_SCALE;
+ }
+ }
+
+ /**
+ * A helper for generating views for the overflow panel.
+ */
+ private static final class OverflowPanelViewHelper {
+
+ private final View mCalculator;
+ private final int mIconTextSpacing;
+ private final int mSidePadding;
+
+ private final Context mContext;
+
+ OverflowPanelViewHelper(Context context, int iconTextSpacing) {
+ mContext = Objects.requireNonNull(context);
+ mIconTextSpacing = iconTextSpacing;
+ mSidePadding = context.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_overflow_side_padding);
+ mCalculator = createMenuButton(null);
+ }
+
+ public View getView(MenuItem menuItem, int minimumWidth, View convertView) {
+ Objects.requireNonNull(menuItem);
+ if (convertView != null) {
+ updateMenuItemButton(
+ convertView, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
+ } else {
+ convertView = createMenuButton(menuItem);
+ }
+ convertView.setMinimumWidth(minimumWidth);
+ return convertView;
+ }
+
+ public int calculateWidth(MenuItem menuItem) {
+ updateMenuItemButton(
+ mCalculator, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
+ mCalculator.measure(
+ View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ return mCalculator.getMeasuredWidth();
+ }
+
+ private View createMenuButton(MenuItem menuItem) {
+ View button = createMenuItemButton(
+ mContext, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
+ button.setPadding(mSidePadding, 0, mSidePadding, 0);
+ return button;
+ }
+
+ private boolean shouldShowIcon(MenuItem menuItem) {
+ if (menuItem != null) {
+ return menuItem.getGroupId() == android.R.id.textAssist;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Creates and returns a menu button for the specified menu item.
+ */
+ private static View createMenuItemButton(
+ Context context, MenuItem menuItem, int iconTextSpacing, boolean showIcon) {
+ final View menuItemButton = LayoutInflater.from(context)
+ .inflate(R.layout.floating_popup_menu_button, null);
+ if (menuItem != null) {
+ updateMenuItemButton(menuItemButton, menuItem, iconTextSpacing, showIcon);
+ }
+ return menuItemButton;
+ }
+
+ /**
+ * Updates the specified menu item button with the specified menu item data.
+ */
+ private static void updateMenuItemButton(
+ View menuItemButton, MenuItem menuItem, int iconTextSpacing, boolean showIcon) {
+ final TextView buttonText = menuItemButton.findViewById(
+ R.id.floating_toolbar_menu_item_text);
+ buttonText.setEllipsize(null);
+ if (TextUtils.isEmpty(menuItem.getTitle())) {
+ buttonText.setVisibility(View.GONE);
+ } else {
+ buttonText.setVisibility(View.VISIBLE);
+ buttonText.setText(menuItem.getTitle());
+ }
+ final ImageView buttonIcon = menuItemButton.findViewById(
+ R.id.floating_toolbar_menu_item_image);
+ if (menuItem.getIcon() == null || !showIcon) {
+ buttonIcon.setVisibility(View.GONE);
+ if (buttonText != null) {
+ buttonText.setPaddingRelative(0, 0, 0, 0);
+ }
+ } else {
+ buttonIcon.setVisibility(View.VISIBLE);
+ buttonIcon.setImageDrawable(menuItem.getIcon());
+ if (buttonText != null) {
+ buttonText.setPaddingRelative(iconTextSpacing, 0, 0, 0);
+ }
+ }
+ final CharSequence contentDescription = menuItem.getContentDescription();
+ if (TextUtils.isEmpty(contentDescription)) {
+ menuItemButton.setContentDescription(menuItem.getTitle());
+ } else {
+ menuItemButton.setContentDescription(contentDescription);
+ }
+ }
+
+ private static ViewGroup createContentContainer(Context context) {
+ ViewGroup contentContainer = (ViewGroup) LayoutInflater.from(context)
+ .inflate(R.layout.floating_popup_container, null);
+ contentContainer.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+ contentContainer.setTag("floating_toolbar");
+ contentContainer.setClipToOutline(true);
+ return contentContainer;
+ }
+
+ private static PopupWindow createPopupWindow(ViewGroup content) {
+ ViewGroup popupContentHolder = new LinearLayout(content.getContext());
+ PopupWindow popupWindow = new PopupWindow(popupContentHolder);
+ // TODO: Use .setIsLaidOutInScreen(true) instead of .setClippingEnabled(false)
+ // unless FLAG_LAYOUT_IN_SCREEN has any unintentional side-effects.
+ popupWindow.setClippingEnabled(false);
+ popupWindow.setWindowLayoutType(
+ WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL);
+ popupWindow.setAnimationStyle(0);
+ popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ content.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+ popupContentHolder.addView(content);
+ return popupWindow;
+ }
+
+ /**
+ * Creates an "appear" animation for the specified view.
+ *
+ * @param view The view to animate
+ */
+ private static AnimatorSet createEnterAnimation(View view) {
+ AnimatorSet animation = new AnimatorSet();
+ animation.playTogether(
+ ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(150));
+ return animation;
+ }
+
+ /**
+ * Creates a "disappear" animation for the specified view.
+ *
+ * @param view The view to animate
+ * @param startDelay The start delay of the animation
+ * @param listener The animation listener
+ */
+ private static AnimatorSet createExitAnimation(
+ View view, int startDelay, Animator.AnimatorListener listener) {
+ AnimatorSet animation = new AnimatorSet();
+ animation.playTogether(
+ ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(100));
+ animation.setStartDelay(startDelay);
+ animation.addListener(listener);
+ return animation;
+ }
+
+ /**
+ * Returns a re-themed context with controlled look and feel for views.
+ */
+ private static Context applyDefaultTheme(Context originalContext) {
+ TypedArray a = originalContext.obtainStyledAttributes(new int[]{R.attr.isLightTheme});
+ boolean isLightTheme = a.getBoolean(0, true);
+ int themeId =
+ isLightTheme ? R.style.Theme_DeviceDefault_Light : R.style.Theme_DeviceDefault;
+ a.recycle();
+ return new ContextThemeWrapper(originalContext, themeId);
+ }
+
+ /**
+ * Represents the identity of a MenuItem that is rendered in a FloatingToolbarPopup.
+ */
+ @VisibleForTesting
+ public static final class MenuItemRepr {
+
+ public final int itemId;
+ public final int groupId;
+ @Nullable public final String title;
+ @Nullable private final Drawable mIcon;
+
+ private MenuItemRepr(
+ int itemId, int groupId, @Nullable CharSequence title, @Nullable Drawable icon) {
+ this.itemId = itemId;
+ this.groupId = groupId;
+ this.title = (title == null) ? null : title.toString();
+ mIcon = icon;
+ }
+
+ /**
+ * Creates an instance of MenuItemRepr for the specified menu item.
+ */
+ public static MenuItemRepr of(MenuItem menuItem) {
+ return new MenuItemRepr(
+ menuItem.getItemId(),
+ menuItem.getGroupId(),
+ menuItem.getTitle(),
+ menuItem.getIcon());
+ }
+
+ /**
+ * Returns this object's hashcode.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(itemId, groupId, title, mIcon);
+ }
+
+ /**
+ * Returns true if this object is the same as the specified object.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof MenuItemRepr)) {
+ return false;
+ }
+ final MenuItemRepr other = (MenuItemRepr) o;
+ return itemId == other.itemId
+ && groupId == other.groupId
+ && TextUtils.equals(title, other.title)
+ // Many Drawables (icons) do not implement equals(). Using equals() here instead
+ // of reference comparisons in case a Drawable subclass implements equals().
+ && Objects.equals(mIcon, other.mIcon);
+ }
+
+ /**
+ * Returns true if the two menu item collections are the same based on MenuItemRepr.
+ */
+ public static boolean reprEquals(
+ Collection<MenuItem> menuItems1, Collection<MenuItem> menuItems2) {
+ if (menuItems1.size() != menuItems2.size()) {
+ return false;
+ }
+
+ final Iterator<MenuItem> menuItems2Iter = menuItems2.iterator();
+ for (MenuItem menuItem1 : menuItems1) {
+ final MenuItem menuItem2 = menuItems2Iter.next();
+ if (!MenuItemRepr.of(menuItem1).equals(MenuItemRepr.of(menuItem2))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index dd4355d..f2a0355 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -87,6 +87,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
+import android.view.WindowLayout;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.window.ClientWindowFrames;
@@ -389,8 +390,9 @@
public void resized(ClientWindowFrames frames, boolean reportDraw,
MergedConfiguration mergedConfiguration, boolean forceLayout,
boolean alwaysConsumeSystemBars, int displayId) {
- Message msg = mCaller.obtainMessageI(MSG_WINDOW_RESIZED,
- reportDraw ? 1 : 0);
+ Message msg = mCaller.obtainMessageIO(MSG_WINDOW_RESIZED,
+ reportDraw ? 1 : 0,
+ mergedConfiguration);
mCaller.sendMessage(msg);
}
@@ -1028,6 +1030,10 @@
}
}
+ private void updateConfiguration(MergedConfiguration mergedConfiguration) {
+ mMergedConfiguration.setTo(mergedConfiguration);
+ }
+
void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {
if (mDestroyed) {
Log.w(TAG, "Ignoring updateSurface due to destroyed");
@@ -1066,8 +1072,6 @@
mLayout.x = 0;
mLayout.y = 0;
- mLayout.width = myWidth;
- mLayout.height = myHeight;
mLayout.format = mFormat;
mCurWindowFlags = mWindowFlags;
@@ -1076,6 +1080,23 @@
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+
+ final Configuration config = mMergedConfiguration.getMergedConfiguration();
+ final Rect maxBounds = config.windowConfiguration.getMaxBounds();
+ if (myWidth == ViewGroup.LayoutParams.MATCH_PARENT
+ && myHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
+ mLayout.width = myWidth;
+ mLayout.height = myHeight;
+ mLayout.flags &= ~WindowManager.LayoutParams.FLAG_SCALED;
+ } else {
+ final float layoutScale = Math.max(
+ maxBounds.width() / (float) myWidth,
+ maxBounds.height() / (float) myHeight);
+ mLayout.width = (int) (myWidth * layoutScale + .5f);
+ mLayout.height = (int) (myHeight * layoutScale + .5f);
+ mLayout.flags |= WindowManager.LayoutParams.FLAG_SCALED;
+ }
+
mCurWindowPrivateFlags = mWindowPrivateFlags;
mLayout.privateFlags = mWindowPrivateFlags;
@@ -1121,12 +1142,14 @@
final int relayoutResult = mSession.relayout(
mWindow, mLayout, mWidth, mHeight,
- View.VISIBLE, 0, -1, mWinFrames, mMergedConfiguration, mSurfaceControl,
- mInsetsState, mTempControls, mSurfaceSize);
+ View.VISIBLE, 0, mWinFrames, mMergedConfiguration, mSurfaceControl,
+ mInsetsState, mTempControls);
final int transformHint = SurfaceControl.rotationToBufferTransform(
(mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
mSurfaceControl.setTransformHint(transformHint);
+ WindowLayout.computeSurfaceSize(mLayout, maxBounds, mWidth, mHeight,
+ mWinFrames.frame, false /* dragResizing */, mSurfaceSize);
if (mSurfaceControl.isValid()) {
if (mBbqSurfaceControl == null) {
@@ -1164,7 +1187,6 @@
int h = mWinFrames.frame.height();
final DisplayCutout rawCutout = mInsetsState.getDisplayCutout();
- final Configuration config = getResources().getConfiguration();
final Rect visibleFrame = new Rect(mWinFrames.frame);
visibleFrame.intersect(mInsetsState.getDisplayFrame());
WindowInsets windowInsets = mInsetsState.calculateInsets(visibleFrame,
@@ -2321,6 +2343,7 @@
} break;
case MSG_WINDOW_RESIZED: {
final boolean reportDraw = message.arg1 != 0;
+ mEngine.updateConfiguration(((MergedConfiguration) message.obj));
mEngine.updateSurface(true, false, reportDraw);
mEngine.doOffsetsChanged(true);
mEngine.scaleAndCropScreenshot();
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index ce63376..be66db2 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -363,6 +363,9 @@
public String toString() {
int lineBreakStyle = (mLineBreakConfig != null)
? mLineBreakConfig.getLineBreakStyle() : LineBreakConfig.LINE_BREAK_STYLE_NONE;
+ int lineBreakWordStyle = (mLineBreakConfig != null)
+ ? mLineBreakConfig.getLineBreakWordStyle()
+ : LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
return "{"
+ "textSize=" + mPaint.getTextSize()
+ ", textScaleX=" + mPaint.getTextScaleX()
@@ -376,6 +379,7 @@
+ ", breakStrategy=" + mBreakStrategy
+ ", hyphenationFrequency=" + mHyphenationFrequency
+ ", lineBreakStyle=" + lineBreakStyle
+ + ", lineBreakWordStyle=" + lineBreakWordStyle
+ "}";
}
};
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index 69af2a5..bd468d9 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -17,7 +17,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.annotation.UiThread;
+import android.graphics.Region;
import android.hardware.HardwareBuffer;
/**
@@ -124,4 +126,16 @@
default void removeOnBufferTransformHintChangedListener(
@NonNull OnBufferTransformHintChangedListener listener) {
}
+
+ /**
+ * Sets the touchable region for this SurfaceControl, expressed in surface local
+ * coordinates. By default the touchable region is the entire Layer, indicating
+ * that if the layer is otherwise eligible to receive touch it receives touch
+ * on the entire surface. Setting the touchable region allows the SurfaceControl
+ * to receive touch in some regions, while allowing for pass-through in others.
+ *
+ * @param r The region to use or null to use the entire Layer bounds
+ */
+ default void setTouchableRegion(@Nullable Region r) {
+ }
}
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index 9889eaa..9b36b9b 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -53,6 +53,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
/**
@@ -386,6 +387,33 @@
* @param boundBottom the bottom bounding rect of the display cutout in pixels. If null is
* passed, it's treated as an empty rectangle (0,0)-(0,0).
* @param waterfallInsets the insets for the curved areas in waterfall display.
+ * @param info the cutout path parser info.
+ * @hide
+ */
+ public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft,
+ @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom,
+ @NonNull Insets waterfallInsets, @Nullable CutoutPathParserInfo info) {
+ this(safeInsets.toRect(), waterfallInsets, boundLeft, boundTop, boundRight, boundBottom,
+ info, true);
+ }
+
+ /**
+ * Creates a DisplayCutout instance.
+ *
+ * <p>Note that this is only useful for tests. For production code, developers should always
+ * use a {@link DisplayCutout} obtained from the system.</p>
+ *
+ * @param safeInsets the insets from each edge which avoid the display cutout as returned by
+ * {@link #getSafeInsetTop()} etc.
+ * @param boundLeft the left bounding rect of the display cutout in pixels. If null is passed,
+ * it's treated as an empty rectangle (0,0)-(0,0).
+ * @param boundTop the top bounding rect of the display cutout in pixels. If null is passed,
+ * it's treated as an empty rectangle (0,0)-(0,0).
+ * @param boundRight the right bounding rect of the display cutout in pixels. If null is
+ * passed, it's treated as an empty rectangle (0,0)-(0,0).
+ * @param boundBottom the bottom bounding rect of the display cutout in pixels. If null is
+ * passed, it's treated as an empty rectangle (0,0)-(0,0).
+ * @param waterfallInsets the insets for the curved areas in waterfall display.
*/
public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft,
@Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom,
@@ -1098,6 +1126,85 @@
}
/**
+ * @return a copy of this cutout that has been rotated for a display in toRotation.
+ * @hide
+ */
+ public DisplayCutout getRotated(int startWidth, int startHeight,
+ int fromRotation, int toRotation) {
+ if (this == DisplayCutout.NO_CUTOUT) {
+ return DisplayCutout.NO_CUTOUT;
+ }
+ final int rotation = RotationUtils.deltaRotation(fromRotation, toRotation);
+ if (rotation == ROTATION_0) {
+ return this;
+ }
+ final Insets waterfallInsets = RotationUtils.rotateInsets(getWaterfallInsets(), rotation);
+ // returns a copy
+ final Rect[] newBounds = getBoundingRectsAll();
+ final Rect displayBounds = new Rect(0, 0, startWidth, startHeight);
+ for (int i = 0; i < newBounds.length; ++i) {
+ if (newBounds[i].isEmpty()) continue;
+ RotationUtils.rotateBounds(newBounds[i], displayBounds, rotation);
+ }
+ Collections.rotate(Arrays.asList(newBounds), -rotation);
+ final CutoutPathParserInfo info = getCutoutPathParserInfo();
+ final CutoutPathParserInfo newInfo = new CutoutPathParserInfo(
+ info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(),
+ info.getCutoutSpec(), toRotation, info.getScale());
+ final boolean swapAspect = (rotation % 2) != 0;
+ final int endWidth = swapAspect ? startHeight : startWidth;
+ final int endHeight = swapAspect ? startWidth : startHeight;
+ final DisplayCutout tmp =
+ DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo);
+ final Rect safeInsets = DisplayCutout.computeSafeInsets(endWidth, endHeight, tmp);
+ return tmp.replaceSafeInsets(safeInsets);
+ }
+
+ /**
+ * Compute the insets derived from a cutout. This is usually used to populate the safe-insets
+ * of the cutout via {@link #replaceSafeInsets}.
+ * @hide
+ */
+ public static Rect computeSafeInsets(int displayW, int displayH, DisplayCutout cutout) {
+ if (displayW == displayH) {
+ throw new UnsupportedOperationException("not implemented: display=" + displayW + "x"
+ + displayH + " cutout=" + cutout);
+ }
+
+ int leftInset = Math.max(cutout.getWaterfallInsets().left, findCutoutInsetForSide(
+ displayW, displayH, cutout.getBoundingRectLeft(), Gravity.LEFT));
+ int topInset = Math.max(cutout.getWaterfallInsets().top, findCutoutInsetForSide(
+ displayW, displayH, cutout.getBoundingRectTop(), Gravity.TOP));
+ int rightInset = Math.max(cutout.getWaterfallInsets().right, findCutoutInsetForSide(
+ displayW, displayH, cutout.getBoundingRectRight(), Gravity.RIGHT));
+ int bottomInset = Math.max(cutout.getWaterfallInsets().bottom, findCutoutInsetForSide(
+ displayW, displayH, cutout.getBoundingRectBottom(), Gravity.BOTTOM));
+
+ return new Rect(leftInset, topInset, rightInset, bottomInset);
+ }
+
+ private static int findCutoutInsetForSide(int displayW, int displayH, Rect boundingRect,
+ int gravity) {
+ if (boundingRect.isEmpty()) {
+ return 0;
+ }
+
+ int inset = 0;
+ switch (gravity) {
+ case Gravity.TOP:
+ return Math.max(inset, boundingRect.bottom);
+ case Gravity.BOTTOM:
+ return Math.max(inset, displayH - boundingRect.top);
+ case Gravity.LEFT:
+ return Math.max(inset, boundingRect.right);
+ case Gravity.RIGHT:
+ return Math.max(inset, displayW - boundingRect.left);
+ default:
+ throw new IllegalArgumentException("unknown gravity: " + gravity);
+ }
+ }
+
+ /**
* Helper class for passing {@link DisplayCutout} through binder.
*
* Needed, because {@code readFromParcel} cannot be used with immutable classes.
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index fb848ad..c5ccc18 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -541,6 +541,11 @@
void stopWindowTrace();
/**
+ * If window tracing is active, saves the window trace to file, otherwise does nothing
+ */
+ void saveWindowTraceToFile();
+
+ /**
* Returns true if window trace is enabled.
*/
boolean isWindowTraceEnabled();
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 6226566..ccf1e44 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -74,7 +74,6 @@
* @param viewVisibility Window root view's visibility.
* @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING},
* {@link WindowManagerGlobal#RELAYOUT_DEFER_SURFACE_DESTROY}.
- * @param frameNumber A frame number in which changes requested in this layout will be rendered.
* @param outFrame Rect in which is placed the new position/size on
* screen.
* @param outContentInsets Rect in which is placed the offsets from
@@ -97,17 +96,15 @@
* since it was last displayed.
* @param outSurface Object in which is placed the new display surface.
* @param insetsState The current insets state in the system.
- * @param outSurfaceSize The width and height of the surface control
*
* @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_SHOW_FOCUS},
* {@link WindowManagerGlobal#RELAYOUT_FIRST_TIME}.
*/
int relayout(IWindow window, in WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility,
- int flags, long frameNumber, out ClientWindowFrames outFrames,
+ int flags, out ClientWindowFrames outFrames,
out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl,
- out InsetsState insetsState, out InsetsSourceControl[] activeControls,
- out Point outSurfaceSize);
+ out InsetsState insetsState, out InsetsSourceControl[] activeControls);
/*
* Notify the window manager that an application is relaunching and
@@ -342,4 +339,9 @@
* @param callback The {@link IOnBackInvokedCallback} to set.
*/
oneway void setOnBackInvokedCallback(IWindow window, IOnBackInvokedCallback callback);
+
+ /**
+ * Clears a touchable region set by {@link #setInsets}.
+ */
+ void clearTouchableRegion(IWindow window);
}
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 4f1354d..188d745 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -572,6 +572,8 @@
* @return The identifier object for this device
* @hide
*/
+ @TestApi
+ @NonNull
public InputDeviceIdentifier getIdentifier() {
return mIdentifier;
}
@@ -735,6 +737,21 @@
}
/**
+ * Gets the key code produced by the specified location on a US keyboard layout.
+ * Key code as defined in {@link android.view.KeyEvent}.
+ * This API is only functional for devices with {@link InputDevice#SOURCE_KEYBOARD} available
+ * which can alter their key mapping using country specific keyboard layouts.
+ *
+ * @param locationKeyCode The location of a key on a US keyboard layout.
+ * @return The key code produced when pressing the key at the specified location, given the
+ * active keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the requested
+ * mapping could not be determined, or if an error occurred.
+ */
+ public int getKeyCodeForKeyLocation(int locationKeyCode) {
+ return InputManager.getInstance().getKeyCodeForKeyLocation(mId, locationKeyCode);
+ }
+
+ /**
* Gets information about the range of values for a particular {@link MotionEvent} axis.
* If the device supports multiple sources, the same axis may have different meanings
* for each source. Returns information about the first axis found for any source.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 97b5a31..1496a4a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -52,6 +52,7 @@
import static android.view.ViewRootImplProto.WIDTH;
import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES;
import static android.view.ViewRootImplProto.WIN_FRAME;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
@@ -83,6 +84,8 @@
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
@@ -471,6 +474,9 @@
final Region mTransparentRegion;
final Region mPreviousTransparentRegion;
+ Region mTouchableRegion;
+ Region mPreviousTouchableRegion;
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
int mWidth;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -2868,9 +2874,9 @@
}
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
final boolean freeformResizing = (relayoutResult
- & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM) != 0;
+ & RELAYOUT_RES_DRAG_RESIZING_FREEFORM) != 0;
final boolean dockedResizing = (relayoutResult
- & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED) != 0;
+ & RELAYOUT_RES_DRAG_RESIZING_DOCKED) != 0;
final boolean dragResizing = freeformResizing || dockedResizing;
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_BLAST_SYNC) != 0) {
if (DEBUG_BLAST) {
@@ -3249,9 +3255,15 @@
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
+ Rect contentInsets = null;
+ Rect visibleInsets = null;
+ Region touchableRegion = null;
+ int touchableInsetMode = TOUCHABLE_INSETS_REGION;
+ boolean computedInternalInsets = false;
if (computesInternalInsets) {
- // Clear the original insets.
final ViewTreeObserver.InternalInsetsInfo insets = mAttachInfo.mGivenInternalInsets;
+
+ // Clear the original insets.
insets.reset();
// Compute new insets in place.
@@ -3263,9 +3275,6 @@
mLastGivenInsets.set(insets);
// Translate insets to screen coordinates if needed.
- final Rect contentInsets;
- final Rect visibleInsets;
- final Region touchableRegion;
if (mTranslator != null) {
contentInsets = mTranslator.getTranslatedContentInsets(insets.contentInsets);
visibleInsets = mTranslator.getTranslatedVisibleInsets(insets.visibleInsets);
@@ -3275,12 +3284,46 @@
visibleInsets = insets.visibleInsets;
touchableRegion = insets.touchableRegion;
}
-
- try {
- mWindowSession.setInsets(mWindow, insets.mTouchableInsets,
- contentInsets, visibleInsets, touchableRegion);
- } catch (RemoteException e) {
+ computedInternalInsets = true;
+ }
+ touchableInsetMode = insets.mTouchableInsets;
+ }
+ boolean needsSetInsets = computedInternalInsets;
+ needsSetInsets |= !Objects.equals(mPreviousTouchableRegion, mTouchableRegion) &&
+ (mTouchableRegion != null);
+ if (needsSetInsets) {
+ if (mTouchableRegion != null) {
+ if (mPreviousTouchableRegion == null) {
+ mPreviousTouchableRegion = new Region();
}
+ mPreviousTouchableRegion.set(mTouchableRegion);
+ if (touchableInsetMode != TOUCHABLE_INSETS_REGION) {
+ Log.e(mTag, "Setting touchableInsetMode to non TOUCHABLE_INSETS_REGION" +
+ " from OnComputeInternalInsets, while also using setTouchableRegion" +
+ " causes setTouchableRegion to be ignored");
+ }
+ } else {
+ mPreviousTouchableRegion = null;
+ }
+ if (contentInsets == null) contentInsets = new Rect(0,0,0,0);
+ if (visibleInsets == null) visibleInsets = new Rect(0,0,0,0);
+ if (touchableRegion == null) {
+ touchableRegion = mTouchableRegion;
+ } else if (touchableRegion != null && mTouchableRegion != null) {
+ touchableRegion.op(touchableRegion, mTouchableRegion, Region.Op.UNION);
+ }
+ try {
+ mWindowSession.setInsets(mWindow, touchableInsetMode,
+ contentInsets, visibleInsets, touchableRegion);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else if (mTouchableRegion == null && mPreviousTouchableRegion != null) {
+ mPreviousTouchableRegion = null;
+ try {
+ mWindowSession.clearTouchableRegion(mWindow);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
@@ -7928,22 +7971,25 @@
}
}
- long frameNumber = -1;
- if (mSurface.isValid()) {
- frameNumber = mSurface.getNextFrameNumber();
- }
+ final int requestedWidth = (int) (mView.getMeasuredWidth() * appScale + 0.5f);
+ final int requestedHeight = (int) (mView.getMeasuredHeight() * appScale + 0.5f);
int relayoutResult = mWindowSession.relayout(mWindow, params,
- (int) (mView.getMeasuredWidth() * appScale + 0.5f),
- (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
- insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
+ requestedWidth, requestedHeight, viewVisibility,
+ insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
- mTempControls, mSurfaceSize);
+ mTempControls);
final int transformHint = SurfaceControl.rotationToBufferTransform(
(mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
mSurfaceControl.setTransformHint(transformHint);
+ final WindowConfiguration winConfig = getConfiguration().windowConfiguration;
+ final boolean dragResizing = (relayoutResult
+ & (RELAYOUT_RES_DRAG_RESIZING_DOCKED | RELAYOUT_RES_DRAG_RESIZING_FREEFORM)) != 0;
+ WindowLayout.computeSurfaceSize(mWindowAttributes, winConfig.getMaxBounds(), requestedWidth,
+ requestedHeight, mTmpFrames.frame, dragResizing, mSurfaceSize);
+
if (mAttachInfo.mContentCaptureManager != null) {
MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager
.getMainContentCaptureSession();
@@ -10701,4 +10747,15 @@
}
return mFallbackOnBackInvokedDispatcher;
}
+
+ @Override
+ public void setTouchableRegion(Region r) {
+ if (r != null) {
+ mTouchableRegion = new Region(r);
+ } else {
+ mTouchableRegion = null;
+ }
+ mLastGivenInsets.reset();
+ requestLayout();
+ }
}
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index e5c7d6d..27f89ec 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -36,6 +36,7 @@
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
import android.graphics.Insets;
+import android.graphics.Point;
import android.graphics.Rect;
import android.util.Log;
@@ -258,6 +259,7 @@
+ " outFrame=" + outFrame.toShortString()
+ " outParentFrame=" + outParentFrame.toShortString()
+ " outDisplayFrame=" + outDisplayFrame.toShortString()
+ + " windowBounds=" + windowBounds.toShortString()
+ " attachedWindowFrame=" + (attachedWindowFrame != null
? attachedWindowFrame.toShortString()
: "null")
@@ -272,4 +274,45 @@
return clippedByDisplayCutout;
}
+
+ public static void computeSurfaceSize(WindowManager.LayoutParams attrs, Rect maxBounds,
+ int requestedWidth, int requestedHeight, Rect winFrame, boolean dragResizing,
+ Point outSurfaceSize) {
+ int width;
+ int height;
+ if ((attrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0) {
+ // For a scaled surface, we always want the requested size.
+ width = requestedWidth;
+ height = requestedHeight;
+ } else {
+ // When we're doing a drag-resizing, request a surface that's fullscreen size,
+ // so that we don't need to reallocate during the process. This also prevents
+ // buffer drops due to size mismatch.
+ if (dragResizing) {
+ // The maxBounds should match the display size which applies fixed-rotation
+ // transformation if there is any.
+ width = maxBounds.width();
+ height = maxBounds.height();
+ } else {
+ width = winFrame.width();
+ height = winFrame.height();
+ }
+ }
+
+ // This doesn't necessarily mean that there is an error in the system. The sizes might be
+ // incorrect, because it is before the first layout or draw.
+ if (width < 1) {
+ width = 1;
+ }
+ if (height < 1) {
+ height = 1;
+ }
+
+ // Adjust for surface insets.
+ final Rect surfaceInsets = attrs.surfaceInsets;
+ width += surfaceInsets.left + surfaceInsets.right;
+ height += surfaceInsets.top + surfaceInsets.bottom;
+
+ outSurfaceSize.set(width, height);
+ }
}
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 998498b..3392edc 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -19,7 +19,6 @@
import android.annotation.Nullable;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
-import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.IBinder;
@@ -264,10 +263,10 @@
@Override
public int relayout(IWindow window, WindowManager.LayoutParams inAttrs,
- int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber,
+ int requestedWidth, int requestedHeight, int viewFlags, int flags,
ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
SurfaceControl outSurfaceControl, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls, Point outSurfaceSize) {
+ InsetsSourceControl[] outActiveControls) {
final State state;
synchronized (this) {
state = mStateForWindow.get(window.asBinder());
@@ -286,7 +285,6 @@
WindowManager.LayoutParams attrs = state.mParams;
if (viewFlags == View.VISIBLE) {
- outSurfaceSize.set(getSurfaceWidth(attrs), getSurfaceHeight(attrs));
t.setOpaque(sc, isOpaque(attrs)).show(sc).apply();
outSurfaceControl.copyFrom(sc, "WindowlessWindowManager.relayout");
} else {
@@ -335,6 +333,11 @@
}
@Override
+ public void clearTouchableRegion(android.view.IWindow window) {
+ setTouchRegion(window.asBinder(), null);
+ }
+
+ @Override
public void finishDrawing(android.view.IWindow window,
android.view.SurfaceControl.Transaction postDrawTransaction) {
synchronized (this) {
@@ -480,17 +483,6 @@
return null;
}
- private int getSurfaceWidth(WindowManager.LayoutParams attrs) {
- final Rect surfaceInsets = attrs.surfaceInsets;
- return surfaceInsets != null
- ? attrs.width + surfaceInsets.left + surfaceInsets.right : attrs.width;
- }
- private int getSurfaceHeight(WindowManager.LayoutParams attrs) {
- final Rect surfaceInsets = attrs.surfaceInsets;
- return surfaceInsets != null
- ? attrs.height + surfaceInsets.top + surfaceInsets.bottom : attrs.height;
- }
-
@Override
public void grantEmbeddedWindowFocus(IWindow callingWindow, IBinder targetInputToken,
boolean grantFocus) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 6a22023..849f3c9 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -18,6 +18,8 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_IMMEDIATE;
+import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_MONITOR;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.EDITOR_INFO;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_INSETS_SOURCE_CONSUMER;
@@ -1793,6 +1795,14 @@
Log.w(TAG, "Ignoring startStylusHandwriting: View's window does not have focus.");
return;
}
+ if (mServedInputConnection != null && getDelegate().hasActiveConnection(view)) {
+ // TODO (b/210039666): optimize CURSOR_UPDATE_IMMEDIATE.
+ // TODO (b/215533103): Introduce new modes in requestCursorUpdates().
+ // TODO (b/210039666): Pipe IME displayId from InputBindResult and use it here.
+ // instead of mDisplayId.
+ mServedInputConnection.requestCursorUpdatesFromImm(
+ CURSOR_UPDATE_IMMEDIATE | CURSOR_UPDATE_MONITOR, mDisplayId);
+ }
try {
mService.startStylusHandwriting(mClient);
@@ -2419,9 +2429,9 @@
public boolean isCursorAnchorInfoEnabled() {
synchronized (mH) {
final boolean isImmediate = (mRequestUpdateCursorAnchorInfoMonitorMode &
- InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0;
+ CURSOR_UPDATE_IMMEDIATE) != 0;
final boolean isMonitoring = (mRequestUpdateCursorAnchorInfoMonitorMode &
- InputConnection.CURSOR_UPDATE_MONITOR) != 0;
+ CURSOR_UPDATE_MONITOR) != 0;
return isImmediate || isMonitoring;
}
}
@@ -2493,7 +2503,7 @@
// If immediate bit is set, we will call updateCursorAnchorInfo() even when the data has
// not been changed from the previous call.
final boolean isImmediate = (mRequestUpdateCursorAnchorInfoMonitorMode &
- InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0;
+ CURSOR_UPDATE_IMMEDIATE) != 0;
if (!isImmediate && Objects.equals(mCursorAnchorInfo, cursorAnchorInfo)) {
// TODO: Consider always emitting this message once we have addressed redundant
// calls of this method from android.widget.Editor.
@@ -2507,7 +2517,7 @@
mCurrentInputMethodSession.updateCursorAnchorInfo(cursorAnchorInfo);
mCursorAnchorInfo = cursorAnchorInfo;
// Clear immediate bit (if any).
- mRequestUpdateCursorAnchorInfoMonitorMode &= ~InputConnection.CURSOR_UPDATE_IMMEDIATE;
+ mRequestUpdateCursorAnchorInfoMonitorMode &= ~CURSOR_UPDATE_IMMEDIATE;
}
}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 364ba50..f14c251 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -19,6 +19,8 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -135,6 +137,20 @@
public @interface CacheMode {}
/**
+ * Enable web content to apply light or dark style according to the app's theme
+ * and WebView to attempt to darken web content by algorithmic darkening when
+ * appropriate.
+ *
+ * Refer to {@link #setAllowAlgorithmicDarkening} for detail.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
+ @SystemApi
+ public static final long ENABLE_SIMPLIFIED_DARK_MODE = 214741472L;
+
+ /**
* Default cache usage mode. If the navigation type doesn't impose any
* specific behavior, use cached resources when they are available
* and not expired, otherwise load resources from the network.
@@ -239,6 +255,7 @@
* automatically darkened.
*
* @see #setForceDark
+ * @deprecated refer to {@link #setForceDark}
*/
public static final int FORCE_DARK_OFF = 0;
@@ -250,6 +267,7 @@
* be inverted.
*
* @see #setForceDark
+ * @deprecated refer to {@link #setForceDark}
*/
public static final int FORCE_DARK_AUTO = 1;
@@ -258,6 +276,7 @@
* as to emulate a dark theme.
*
* @see #setForceDark
+ * @deprecated refer to {@link #setForceDark}
*/
public static final int FORCE_DARK_ON = 2;
@@ -1533,6 +1552,13 @@
*
* @param forceDark the force dark mode to set.
* @see #getForceDark
+ * @deprecated The "force dark" model previously implemented by WebView was complex
+ * and didn't interoperate well with current Web standards for
+ * prefers-color-scheme and color-scheme. In apps with
+ * {@code targetSdkVersion} ≥ {@link android.os.Build.VERSION_CODES#TIRAMISU}
+ * this API is a no-op and WebView will always use the dark style defined by web content
+ * authors if the app's theme is dark. To customize the behavior, refer to
+ * {@link #setAllowAlgorithmicDarkening}.
*/
public void setForceDark(@ForceDark int forceDark) {
// Stub implementation to satisfy Roboelectrc shadows that don't override this yet.
@@ -1544,6 +1570,7 @@
*
* @return the currently set force dark mode.
* @see #setForceDark
+ * @deprecated refer to {@link #setForceDark}.
*/
public @ForceDark int getForceDark() {
// Stub implementation to satisfy Roboelectrc shadows that don't override this yet.
@@ -1551,6 +1578,49 @@
}
/**
+ * Control whether algorithmic darkening is allowed.
+ *
+ * <p class="note">
+ * <b>Note:</b> This API and the behaviour described only apply to apps with
+ * {@code targetSdkVersion} ≥ {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+ *
+ * <p>
+ * WebView always sets the media query {@code prefers-color-scheme} according to the app's
+ * theme attribute {@link android.R.styleable#Theme_isLightTheme isLightTheme}, i.e.
+ * {@code prefers-color-scheme} is {@code light} if isLightTheme is true or not specified,
+ * otherwise it is {@code dark}. This means that the web content's light or dark style will
+ * be applied automatically to match the app's theme if the content supports it.
+ *
+ * <p>
+ * Algorithmic darkening is disallowed by default.
+ * <p>
+ * If the app's theme is dark and it allows algorithmic darkening, WebView will attempt to
+ * darken web content using an algorithm, if the content doesn't define its own dark styles
+ * and doesn't explicitly disable darkening.
+ *
+ * <p>
+ * If Android is applying Force Dark to WebView then WebView will ignore the value of
+ * this setting and behave as if it were set to true.
+ *
+ * @param allow allow algorithmic darkening or not.
+ */
+ public void setAllowAlgorithmicDarkening(boolean allow) {
+ // Stub implementation to satisfy Roboelectrc shadows that don't override this yet.
+ }
+
+ /**
+ * Get if algorithmic darkening is allowed or not for this WebView.
+ * The default is false.
+ *
+ * @return if the algorithmic darkening is allowed or not.
+ * @see #setAllowAlgorithmicDarkening
+ */
+ public boolean getAllowAlgorithmicDarkening() {
+ // Stub implementation to satisfy Roboelectrc shadows that don't override this yet.
+ return false;
+ }
+
+ /**
* @hide
*/
@IntDef(flag = true, prefix = { "MENU_ITEM_" }, value = {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index b21d08c..c6f64f4 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1517,6 +1517,12 @@
}
}
+ /**
+ * @deprecated As RemoteViews may be reapplied frequently, it is preferable to call
+ * {@link #setDisplayedChild(int, int)} to ensure that the adapter index does not change
+ * unexpectedly.
+ */
+ @Deprecated
private final class ViewContentNavigation extends Action {
final boolean mNext;
@@ -4121,7 +4127,11 @@
* Equivalent to calling {@link AdapterViewAnimator#showNext()}
*
* @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()}
+ * @deprecated As RemoteViews may be reapplied frequently, it is preferable to call
+ * {@link #setDisplayedChild(int, int)} to ensure that the adapter index does not change
+ * unexpectedly.
*/
+ @Deprecated
public void showNext(@IdRes int viewId) {
addAction(new ViewContentNavigation(viewId, true /* next */));
}
@@ -4130,7 +4140,11 @@
* Equivalent to calling {@link AdapterViewAnimator#showPrevious()}
*
* @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()}
+ * @deprecated As RemoteViews may be reapplied frequently, it is preferable to call
+ * {@link #setDisplayedChild(int, int)} to ensure that the adapter index does not change
+ * unexpectedly.
*/
+ @Deprecated
public void showPrevious(@IdRes int viewId) {
addAction(new ViewContentNavigation(viewId, false /* next */));
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 7161730..41c5401 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -350,6 +350,7 @@
* @attr ref android.R.styleable#TextView_breakStrategy
* @attr ref android.R.styleable#TextView_hyphenationFrequency
* @attr ref android.R.styleable#TextView_lineBreakStyle
+ * @attr ref android.R.styleable#TextView_lineBreakWordStyle
* @attr ref android.R.styleable#TextView_autoSizeTextType
* @attr ref android.R.styleable#TextView_autoSizeMinTextSize
* @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
@@ -461,6 +462,13 @@
private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500;
+ // The default value of the line break style.
+ private static final int DEFAULT_LINE_BREAK_STYLE = LineBreakConfig.LINE_BREAK_STYLE_NONE;
+
+ // The default value of the line break word style.
+ private static final int DEFAULT_LINE_BREAK_WORD_STYLE =
+ LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
+
/**
* This change ID enables the fallback text line spacing (line height) for BoringLayout.
* @hide
@@ -1453,6 +1461,11 @@
a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE));
break;
+ case com.android.internal.R.styleable.TextView_lineBreakWordStyle:
+ mLineBreakConfig.setLineBreakWordStyle(
+ a.getInt(attr, LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE));
+ break;
+
case com.android.internal.R.styleable.TextView_autoSizeTextType:
mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
break;
@@ -3985,6 +3998,10 @@
float mLetterSpacing = 0;
String mFontFeatureSettings = null;
String mFontVariationSettings = null;
+ boolean mHasLineBreakStyle = false;
+ boolean mHasLineBreakWordStyle = false;
+ int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE;
+ int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE;
@Override
public String toString() {
@@ -4015,6 +4032,10 @@
+ " mLetterSpacing:" + mLetterSpacing + "\n"
+ " mFontFeatureSettings:" + mFontFeatureSettings + "\n"
+ " mFontVariationSettings:" + mFontVariationSettings + "\n"
+ + " mHasLineBreakStyle:" + mHasLineBreakStyle + "\n"
+ + " mHasLineBreakWordStyle:" + mHasLineBreakWordStyle + "\n"
+ + " mLineBreakStyle:" + mLineBreakStyle + "\n"
+ + " mLineBreakWordStyle:" + mLineBreakWordStyle + "\n"
+ "}";
}
}
@@ -4062,6 +4083,10 @@
com.android.internal.R.styleable.TextAppearance_fontFeatureSettings);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings,
com.android.internal.R.styleable.TextAppearance_fontVariationSettings);
+ sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle,
+ com.android.internal.R.styleable.TextAppearance_lineBreakStyle);
+ sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle,
+ com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle);
}
/**
@@ -4177,6 +4202,16 @@
case com.android.internal.R.styleable.TextAppearance_fontVariationSettings:
attributes.mFontVariationSettings = appearance.getString(attr);
break;
+ case com.android.internal.R.styleable.TextAppearance_lineBreakStyle:
+ attributes.mHasLineBreakStyle = true;
+ attributes.mLineBreakStyle =
+ appearance.getInt(attr, attributes.mLineBreakStyle);
+ break;
+ case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle:
+ attributes.mHasLineBreakWordStyle = true;
+ attributes.mLineBreakWordStyle =
+ appearance.getInt(attr, attributes.mLineBreakWordStyle);
+ break;
default:
}
}
@@ -4242,9 +4277,46 @@
if (attributes.mFontVariationSettings != null) {
setFontVariationSettings(attributes.mFontVariationSettings);
}
+
+ if (attributes.mHasLineBreakStyle || attributes.mHasLineBreakWordStyle) {
+ updateLineBreakConfigFromTextAppearance(attributes.mHasLineBreakStyle,
+ attributes.mHasLineBreakWordStyle, attributes.mLineBreakStyle,
+ attributes.mLineBreakWordStyle);
+ }
}
/**
+ * Updates the LineBreakConfig from the TextAppearance.
+ *
+ * This method updates the given line configuration from the TextAppearance. This method will
+ * request new layout if line break config has been changed.
+ *
+ * @param isLineBreakStyleSpecified true if the line break style is specified.
+ * @param isLineBreakWordStyleSpecified true if the line break word style is specified.
+ * @param lineBreakStyle the value of the line break style in the TextAppearance.
+ * @param lineBreakWordStyle the value of the line break word style in the TextAppearance.
+ */
+ private void updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified,
+ boolean isLineBreakWordStyleSpecified,
+ @LineBreakConfig.LineBreakStyle int lineBreakStyle,
+ @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
+ boolean updated = false;
+ if (isLineBreakStyleSpecified && mLineBreakConfig.getLineBreakStyle() != lineBreakStyle) {
+ mLineBreakConfig.setLineBreakStyle(lineBreakStyle);
+ updated = true;
+ }
+ if (isLineBreakWordStyleSpecified
+ && mLineBreakConfig.getLineBreakWordStyle() != lineBreakWordStyle) {
+ mLineBreakConfig.setLineBreakWordStyle(lineBreakWordStyle);
+ updated = true;
+ }
+ if (updated && mLayout != null) {
+ nullLayouts();
+ requestLayout();
+ invalidate();
+ }
+ }
+ /**
* Get the default primary {@link Locale} of the text in this TextView. This will always be
* the first member of {@link #getTextLocales()}.
* @return the default primary {@link Locale} of the text in this TextView.
@@ -4800,18 +4872,29 @@
/**
* Sets line break configuration indicates which strategy needs to be used when calculating the
- * text wrapping. There are thee strategies for the line break style(lb):
+ * text wrapping.
+ * <P>
+ * There are two types of line break rules that can be configured at the same time. One is
+ * line break style(lb) and the other is line break word style(lw). The line break style
+ * affects rule-based breaking. The line break word style affects dictionary-based breaking
+ * and provide phrase-based breaking opportunities. There are several types for the
+ * line break style:
* {@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE},
* {@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL} and
* {@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}.
- * The default value of the line break style is {@link LineBreakConfig#LINE_BREAK_STYLE_NONE},
- * which means no line break style is specified.
+ * The type for the line break word style is
+ * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE}.
+ * The default values of the line break style and the line break word style are
+ * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE} and
+ * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE} respectively, indicating that no line
+ * breaking rules are specified.
* See <a href="https://drafts.csswg.org/css-text/#line-break-property">
* the line-break property</a>
*
* @param lineBreakConfig the line break config for text wrapping.
*/
public void setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) {
+ Objects.requireNonNull(lineBreakConfig);
if (mLineBreakConfig.equals(lineBreakConfig)) {
return;
}
@@ -4858,7 +4941,13 @@
mTextDir = params.getTextDirection();
mBreakStrategy = params.getBreakStrategy();
mHyphenationFrequency = params.getHyphenationFrequency();
- mLineBreakConfig.set(params.getLineBreakConfig());
+ if (params.getLineBreakConfig() != null) {
+ mLineBreakConfig.set(params.getLineBreakConfig());
+ } else {
+ // Set default value if the line break config in the PrecomputedText.Params is null.
+ mLineBreakConfig.setLineBreakStyle(DEFAULT_LINE_BREAK_STYLE);
+ mLineBreakConfig.setLineBreakWordStyle(DEFAULT_LINE_BREAK_WORD_STYLE);
+ }
if (mLayout != null) {
nullLayouts();
requestLayout();
diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java
index cfccb71..fdaab66 100644
--- a/core/java/android/window/WindowContext.java
+++ b/core/java/android/window/WindowContext.java
@@ -17,6 +17,8 @@
import static android.view.WindowManagerImpl.createWindowContextWindowManager;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiContext;
@@ -135,7 +137,8 @@
}
/** Dispatch {@link Configuration} to each {@link ComponentCallbacks}. */
- void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
+ @VisibleForTesting(visibility = PACKAGE)
+ public void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
mCallbacksController.dispatchConfigurationChanged(newConfig);
}
diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
index 0470444..d1942ac 100644
--- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
@@ -932,6 +932,24 @@
});
}
+ /**
+ * Dispatches {@link InputConnection#requestCursorUpdates(int)}.
+ *
+ * <p>This method is intended to be called only from {@link InputMethodManager}.</p>
+ * @param cursorUpdateMode the mode for {@link InputConnection#requestCursorUpdates(int)}
+ * @param imeDisplayId displayId on which IME is displayed.
+ */
+ @Dispatching(cancellable = true)
+ public void requestCursorUpdatesFromImm(int cursorUpdateMode, int imeDisplayId) {
+ final int currentSessionId = mCurrentSessionId.get();
+ dispatchWithTracing("requestCursorUpdatesFromImm", () -> {
+ if (currentSessionId != mCurrentSessionId.get()) {
+ return; // cancelled
+ }
+ requestCursorUpdatesInternal(cursorUpdateMode, imeDisplayId);
+ });
+ }
+
@Dispatching(cancellable = true)
@Override
public void requestCursorUpdates(InputConnectionCommandHeader header, int cursorUpdateMode,
@@ -940,24 +958,28 @@
if (header.mSessionId != mCurrentSessionId.get()) {
return false; // cancelled
}
- final InputConnection ic = getInputConnection();
- if (ic == null || !isActive()) {
- Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
- return false;
- }
- if (mParentInputMethodManager.getDisplayId() != imeDisplayId) {
- // requestCursorUpdates() is not currently supported across displays.
- return false;
- }
- try {
- return ic.requestCursorUpdates(cursorUpdateMode);
- } catch (AbstractMethodError ignored) {
- // TODO(b/199934664): See if we can remove this by providing a default impl.
- return false;
- }
+ return requestCursorUpdatesInternal(cursorUpdateMode, imeDisplayId);
});
}
+ private boolean requestCursorUpdatesInternal(int cursorUpdateMode, int imeDisplayId) {
+ final InputConnection ic = getInputConnection();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
+ return false;
+ }
+ if (mParentInputMethodManager.getDisplayId() != imeDisplayId) {
+ // requestCursorUpdates() is not currently supported across displays.
+ return false;
+ }
+ try {
+ return ic.requestCursorUpdates(cursorUpdateMode);
+ } catch (AbstractMethodError ignored) {
+ // TODO(b/199934664): See if we can remove this by providing a default impl.
+ return false;
+ }
+ }
+
@Dispatching(cancellable = true)
@Override
public void commitContent(InputConnectionCommandHeader header,
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index 7f8accc..54e65e0 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -274,16 +274,15 @@
* [31:0] - per Subsystem fields, see {@link ModemPowerProfile}.
*
*/
- private static final int SUBSYSTEM_SHIFT = 32;
- private static final long SUBSYSTEM_MASK = 0xF << SUBSYSTEM_SHIFT;
+ private static final long SUBSYSTEM_MASK = 0xF_0000_0000L;
/**
* Power constant not associated with a subsystem.
*/
- public static final long SUBSYSTEM_NONE = 0 << SUBSYSTEM_SHIFT;
+ public static final long SUBSYSTEM_NONE = 0x0_0000_0000L;
/**
* Modem power constant.
*/
- public static final long SUBSYSTEM_MODEM = 1 << SUBSYSTEM_SHIFT;
+ public static final long SUBSYSTEM_MODEM = 0x1_0000_0000L;
@LongDef(prefix = { "SUBSYSTEM_" }, value = {
SUBSYSTEM_NONE,
@@ -292,7 +291,7 @@
@Retention(RetentionPolicy.SOURCE)
public @interface Subsystem {}
- private static final long SUBSYSTEM_FIELDS_MASK = 0xFFFFFFFF;
+ private static final long SUBSYSTEM_FIELDS_MASK = 0xFFFF_FFFF;
/**
* A map from Power Use Item to its power consumption.
@@ -582,7 +581,7 @@
handleDeprecatedModemConstant(ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP,
POWER_MODEM_CONTROLLER_SLEEP, 0);
handleDeprecatedModemConstant(ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE,
- POWER_MODEM_CONTROLLER_SLEEP, 0);
+ POWER_MODEM_CONTROLLER_IDLE, 0);
handleDeprecatedModemConstant(
ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_RX,
POWER_MODEM_CONTROLLER_RX, 0);
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 8d1f16b..44c7f54 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -22,6 +22,7 @@
import android.app.IActivityManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.type.DefaultMimeMapFactory;
+import android.net.TrafficStats;
import android.os.Build;
import android.os.DeadObjectException;
import android.os.IBinder;
@@ -32,7 +33,6 @@
import android.util.Slog;
import com.android.internal.logging.AndroidConfig;
-import com.android.server.NetworkManagementSocketTagger;
import dalvik.system.RuntimeHooks;
import dalvik.system.VMRuntime;
@@ -254,7 +254,7 @@
/*
* Wire socket tagging to traffic stats.
*/
- NetworkManagementSocketTagger.install();
+ TrafficStats.attachSocketTagger();
initialized = true;
}
diff --git a/core/java/com/android/internal/power/ModemPowerProfile.java b/core/java/com/android/internal/power/ModemPowerProfile.java
index 456ff4b..afea69a 100644
--- a/core/java/com/android/internal/power/ModemPowerProfile.java
+++ b/core/java/com/android/internal/power/ModemPowerProfile.java
@@ -22,9 +22,9 @@
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseDoubleArray;
-import com.android.internal.telephony.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -64,34 +64,27 @@
*/
private final SparseDoubleArray mPowerConstants = new SparseDoubleArray();
- private static final int MODEM_DRAIN_TYPE_SHIFT = 28;
- private static final int MODEM_DRAIN_TYPE_MASK = 0xF << MODEM_DRAIN_TYPE_SHIFT;
-
- private static final int MODEM_TX_LEVEL_SHIFT = 24;
- private static final int MODEM_TX_LEVEL_MASK = 0xF << MODEM_TX_LEVEL_SHIFT;
-
- private static final int MODEM_RAT_TYPE_SHIFT = 20;
- private static final int MODEM_RAT_TYPE_MASK = 0xF << MODEM_RAT_TYPE_SHIFT;
-
- private static final int MODEM_NR_FREQUENCY_RANGE_SHIFT = 16;
- private static final int MODEM_NR_FREQUENCY_RANGE_MASK = 0xF << MODEM_NR_FREQUENCY_RANGE_SHIFT;
+ private static final int MODEM_DRAIN_TYPE_MASK = 0xF000_0000;
+ private static final int MODEM_TX_LEVEL_MASK = 0x0F00_0000;
+ private static final int MODEM_RAT_TYPE_MASK = 0x00F0_0000;
+ private static final int MODEM_NR_FREQUENCY_RANGE_MASK = 0x000F_0000;
/**
* Corresponds to the overall modem battery drain while asleep.
*/
- public static final int MODEM_DRAIN_TYPE_SLEEP = 0 << MODEM_DRAIN_TYPE_SHIFT;
+ public static final int MODEM_DRAIN_TYPE_SLEEP = 0x0000_0000;
/**
* Corresponds to the overall modem battery drain while idle.
*/
- public static final int MODEM_DRAIN_TYPE_IDLE = 1 << MODEM_DRAIN_TYPE_SHIFT;
+ public static final int MODEM_DRAIN_TYPE_IDLE = 0x1000_0000;
/**
* Corresponds to the modem battery drain while receiving data. A specific Rx battery drain
* power constant can be selected using a bitwise OR (|) with {@link ModemRatType} and
* {@link ModemNrFrequencyRange} (when applicable).
*/
- public static final int MODEM_DRAIN_TYPE_RX = 2 << MODEM_DRAIN_TYPE_SHIFT;
+ public static final int MODEM_DRAIN_TYPE_RX = 0x2000_0000;
/**
* Corresponds to the modem battery drain while receiving data.
@@ -99,7 +92,7 @@
* Specific Tx battery drain power constanta can be selected using a bitwise OR (|) with
* {@link ModemRatType} and {@link ModemNrFrequencyRange} (when applicable).
*/
- public static final int MODEM_DRAIN_TYPE_TX = 3 << MODEM_DRAIN_TYPE_SHIFT;
+ public static final int MODEM_DRAIN_TYPE_TX = 0x3000_0000;
@IntDef(prefix = {"MODEM_DRAIN_TYPE_"}, value = {
MODEM_DRAIN_TYPE_SLEEP,
@@ -111,33 +104,44 @@
public @interface ModemDrainType {
}
- private static final String[] MODEM_DRAIN_TYPE_NAMES =
- new String[]{"SLEEP", "IDLE", "RX", "TX"};
+
+ private static final SparseArray<String> MODEM_DRAIN_TYPE_NAMES = new SparseArray<>(4);
+ static {
+ MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_SLEEP, "SLEEP");
+ MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_IDLE, "IDLE");
+ MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_RX, "RX");
+ MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_TX, "TX");
+ }
/**
* Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_0}.
*/
- public static final int MODEM_TX_LEVEL_0 = 0 << MODEM_TX_LEVEL_SHIFT;
+
+ public static final int MODEM_TX_LEVEL_0 = 0x0000_0000;
/**
* Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_1}.
*/
- public static final int MODEM_TX_LEVEL_1 = 1 << MODEM_TX_LEVEL_SHIFT;
+
+ public static final int MODEM_TX_LEVEL_1 = 0x0100_0000;
/**
* Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_2}.
*/
- public static final int MODEM_TX_LEVEL_2 = 2 << MODEM_TX_LEVEL_SHIFT;
+
+ public static final int MODEM_TX_LEVEL_2 = 0x0200_0000;
/**
* Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_3}.
*/
- public static final int MODEM_TX_LEVEL_3 = 3 << MODEM_TX_LEVEL_SHIFT;
+
+ public static final int MODEM_TX_LEVEL_3 = 0x0300_0000;
/**
* Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_4}.
*/
- public static final int MODEM_TX_LEVEL_4 = 4 << MODEM_TX_LEVEL_SHIFT;
+
+ public static final int MODEM_TX_LEVEL_4 = 0x0400_0000;
private static final int MODEM_TX_LEVEL_COUNT = 5;
@@ -152,21 +156,37 @@
public @interface ModemTxLevel {
}
+ private static final SparseArray<String> MODEM_TX_LEVEL_NAMES = new SparseArray<>(5);
+ static {
+ MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_0, "0");
+ MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_1, "1");
+ MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_2, "2");
+ MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_3, "3");
+ MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_4, "4");
+ }
+
+ private static final int[] MODEM_TX_LEVEL_MAP = new int[]{
+ MODEM_TX_LEVEL_0,
+ MODEM_TX_LEVEL_1,
+ MODEM_TX_LEVEL_2,
+ MODEM_TX_LEVEL_3,
+ MODEM_TX_LEVEL_4};
+
/**
* Fallback for any active modem usage that does not match specified Radio Access Technology
* (RAT) power constants.
*/
- public static final int MODEM_RAT_TYPE_DEFAULT = 0 << MODEM_RAT_TYPE_SHIFT;
+ public static final int MODEM_RAT_TYPE_DEFAULT = 0x0000_0000;
/**
* Corresponds to active modem usage on 4G {@link TelephonyManager#NETWORK_TYPE_LTE} RAT.
*/
- public static final int MODEM_RAT_TYPE_LTE = 1 << MODEM_RAT_TYPE_SHIFT;
+ public static final int MODEM_RAT_TYPE_LTE = 0x0010_0000;
/**
* Corresponds to active modem usage on 5G {@link TelephonyManager#NETWORK_TYPE_NR} RAT.
*/
- public static final int MODEM_RAT_TYPE_NR = 2 << MODEM_RAT_TYPE_SHIFT;
+ public static final int MODEM_RAT_TYPE_NR = 0x0020_0000;
@IntDef(prefix = {"MODEM_RAT_TYPE_"}, value = {
MODEM_RAT_TYPE_DEFAULT,
@@ -177,33 +197,38 @@
public @interface ModemRatType {
}
- private static final String[] MODEM_RAT_TYPE_NAMES = new String[]{"DEFAULT", "LTE", "NR"};
+ private static final SparseArray<String> MODEM_RAT_TYPE_NAMES = new SparseArray<>(3);
+ static {
+ MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_DEFAULT, "DEFAULT");
+ MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_LTE, "LTE");
+ MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_NR, "NR");
+ }
/**
* Fallback for any active 5G modem usage that does not match specified NR frequency power
* constants.
*/
- public static final int MODEM_NR_FREQUENCY_RANGE_DEFAULT = 0 << MODEM_NR_FREQUENCY_RANGE_SHIFT;
+ public static final int MODEM_NR_FREQUENCY_RANGE_DEFAULT = 0x0000_0000;
/**
* Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_LOW}.
*/
- public static final int MODEM_NR_FREQUENCY_RANGE_LOW = 1 << MODEM_NR_FREQUENCY_RANGE_SHIFT;
+ public static final int MODEM_NR_FREQUENCY_RANGE_LOW = 0x0001_0000;
/**
* Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_MID}.
*/
- public static final int MODEM_NR_FREQUENCY_RANGE_MID = 2 << MODEM_NR_FREQUENCY_RANGE_SHIFT;
+ public static final int MODEM_NR_FREQUENCY_RANGE_MID = 0x0002_0000;
/**
* Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_HIGH}.
*/
- public static final int MODEM_NR_FREQUENCY_RANGE_HIGH = 3 << MODEM_NR_FREQUENCY_RANGE_SHIFT;
+ public static final int MODEM_NR_FREQUENCY_RANGE_HIGH = 0x0003_0000;
/**
* Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_MMWAVE}.
*/
- public static final int MODEM_NR_FREQUENCY_RANGE_MMWAVE = 4 << MODEM_NR_FREQUENCY_RANGE_SHIFT;
+ public static final int MODEM_NR_FREQUENCY_RANGE_MMWAVE = 0x0004_0000;
@IntDef(prefix = {"MODEM_NR_FREQUENCY_RANGE_"}, value = {
MODEM_RAT_TYPE_DEFAULT,
@@ -215,9 +240,14 @@
@Retention(RetentionPolicy.SOURCE)
public @interface ModemNrFrequencyRange {
}
-
- private static final String[] MODEM_NR_FREQUENCY_RANGE_NAMES =
- new String[]{"DEFAULT", "LOW", "MID", "HIGH", "MMWAVE"};
+ private static final SparseArray<String> MODEM_NR_FREQUENCY_RANGE_NAMES = new SparseArray<>(5);
+ static {
+ MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_DEFAULT, "DEFAULT");
+ MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_LOW, "LOW");
+ MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_MID, "MID");
+ MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_HIGH, "HIGH");
+ MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_MMWAVE, "MMWAVE");
+ }
public ModemPowerProfile() {
}
@@ -261,11 +291,10 @@
final int ratType;
final int nrfType;
try {
- ratType = getTypeFromAttribute(parser, ATTR_RAT, MODEM_RAT_TYPE_SHIFT,
- MODEM_RAT_TYPE_NAMES);
+ ratType = getTypeFromAttribute(parser, ATTR_RAT, MODEM_RAT_TYPE_NAMES);
if (ratType == MODEM_RAT_TYPE_NR) {
nrfType = getTypeFromAttribute(parser, ATTR_NR_FREQUENCY,
- MODEM_NR_FREQUENCY_RANGE_SHIFT, MODEM_NR_FREQUENCY_RANGE_NAMES);
+ MODEM_NR_FREQUENCY_RANGE_NAMES);
} else {
nrfType = 0;
}
@@ -299,10 +328,7 @@
MODEM_TX_LEVEL_COUNT - 1));
continue;
}
- final int modemTxLevel = level << MODEM_TX_LEVEL_SHIFT;
- Slog.d("MWACHENS",
- "parsing tx at level:" + level + ", aka 0x" + Integer.toHexString(
- modemTxLevel));
+ final int modemTxLevel = MODEM_TX_LEVEL_MAP[level];
final int txKey = MODEM_DRAIN_TYPE_TX | modemTxLevel | ratType | nrfType;
setPowerConstant(txKey, txDrain);
break;
@@ -312,20 +338,31 @@
}
}
- private static int getTypeFromAttribute(XmlResourceParser parser, String attr, int shift,
- String[] names) {
+ private static int getTypeFromAttribute(XmlResourceParser parser, String attr,
+ SparseArray<String> names) {
final String value = XmlUtils.readStringAttribute(parser, attr);
- final int index = ArrayUtils.indexOf(names, value);
if (value == null) {
// Attribute was not specified, just use the default.
return 0;
}
+ int index = -1;
+ final int size = names.size();
+ // Manual linear search for string. (SparseArray uses == not equals.)
+ for (int i = 0; i < size; i++) {
+ if (value.equals(names.valueAt(i))) {
+ index = i;
+ }
+ }
if (index < 0) {
+ final String[] stringNames = new String[size];
+ for (int i = 0; i < size; i++) {
+ stringNames[i] = names.valueAt(i);
+ }
throw new IllegalArgumentException(
"Unexpected " + attr + " value : " + value + ". Acceptable values are "
- + Arrays.toString(names));
+ + Arrays.toString(stringNames));
}
- return index << shift;
+ return names.keyAt(index);
}
/**
@@ -384,39 +421,35 @@
private static String keyToString(int key) {
StringBuilder sb = new StringBuilder();
final int drainType = key & MODEM_DRAIN_TYPE_MASK;
- appendFieldToString(sb, "drain", MODEM_DRAIN_TYPE_NAMES,
- drainType >> MODEM_DRAIN_TYPE_SHIFT);
+ appendFieldToString(sb, "drain", MODEM_DRAIN_TYPE_NAMES, drainType);
sb.append(",");
if (drainType == MODEM_DRAIN_TYPE_TX) {
- final int txLevel = (key & MODEM_TX_LEVEL_MASK) >> MODEM_TX_LEVEL_SHIFT;
- sb.append("level:");
- sb.append(txLevel);
- sb.append(",");
+ final int txLevel = key & MODEM_TX_LEVEL_MASK;
+ appendFieldToString(sb, "level", MODEM_TX_LEVEL_NAMES, txLevel);
}
final int ratType = key & MODEM_RAT_TYPE_MASK;
- appendFieldToString(sb, "RAT", MODEM_RAT_TYPE_NAMES, ratType >> MODEM_RAT_TYPE_SHIFT);
+ appendFieldToString(sb, "RAT", MODEM_RAT_TYPE_NAMES, ratType);
if (ratType == MODEM_RAT_TYPE_NR) {
sb.append(",");
final int nrFreq = key & MODEM_NR_FREQUENCY_RANGE_MASK;
- appendFieldToString(sb, "nrFreq", MODEM_NR_FREQUENCY_RANGE_NAMES,
- nrFreq >> MODEM_NR_FREQUENCY_RANGE_SHIFT);
+ appendFieldToString(sb, "nrFreq", MODEM_NR_FREQUENCY_RANGE_NAMES, nrFreq);
}
return sb.toString();
}
-
- private static void appendFieldToString(StringBuilder sb, String fieldName, String[] names,
- int index) {
+ private static void appendFieldToString(StringBuilder sb, String fieldName,
+ SparseArray<String> names, int key) {
sb.append(fieldName);
sb.append(":");
- if (index < 0 || index >= names.length) {
- sb.append("UNKNOWN(");
- sb.append(index);
+ final String name = names.get(key, null);
+ if (name == null) {
+ sb.append("UNKNOWN(0x");
+ sb.append(Integer.toHexString(key));
sb.append(")");
} else {
- sb.append(names[index]);
+ sb.append(name);
}
}
diff --git a/core/java/com/android/internal/usb/DumpUtils.java b/core/java/com/android/internal/usb/DumpUtils.java
index b06a7f4..b32a6b0 100644
--- a/core/java/com/android/internal/usb/DumpUtils.java
+++ b/core/java/com/android/internal/usb/DumpUtils.java
@@ -244,10 +244,12 @@
writeContaminantPresenceStatus(dump, "contaminant_presence_status",
UsbPortStatusProto.CONTAMINANT_PRESENCE_STATUS,
status.getContaminantDetectionStatus());
- dump.write("usb_data_enabled", UsbPortStatusProto.USB_DATA_ENABLED,
- status.getUsbDataStatus());
+ dump.write("usb_data_status", UsbPortStatusProto.USB_DATA_STATUS,
+ UsbPort.usbDataStatusToString(status.getUsbDataStatus()));
dump.write("is_power_transfer_limited", UsbPortStatusProto.IS_POWER_TRANSFER_LIMITED,
status.isPowerTransferLimited());
+ dump.write("usb_power_brick_status", UsbPortStatusProto.USB_POWER_BRICK_STATUS,
+ UsbPort.powerBrickStatusToString(status.getPowerBrickStatus()));
dump.end(token);
}
}
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 998ec96..51e150e 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -249,7 +249,8 @@
optional android.service.NetworkStatsServiceDumpProto netstats = 3001 [
(section).type = SECTION_DUMPSYS,
- (section).args = "netstats --proto"
+ (section).args = "netstats --proto",
+ (section).userdebug_and_eng_only = true
];
optional android.providers.settings.SettingsServiceDumpProto settings = 3002 [
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 11560a5c..c33b7c9 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -69,7 +69,7 @@
// know what activity types to check for when invoking splitscreen multi-window.
optional bool is_home_recents_component = 6;
repeated IdentifierProto pending_activities = 7 [deprecated=true];
- optional int32 default_min_size_resizable_task = 8;
+ optional int32 default_min_size_resizable_task = 8 [deprecated=true];
}
message BarControllerProto {
@@ -226,7 +226,7 @@
optional bool is_sleeping = 36;
repeated string sleep_tokens = 37;
repeated .android.graphics.RectProto keep_clear_areas = 38;
-
+ optional int32 min_size_of_resizeable_task_dp = 39;
}
/* represents DisplayArea object */
@@ -439,7 +439,7 @@
optional bool is_on_screen = 37;
optional bool is_visible = 38;
optional bool pending_seamless_rotation = 39;
- optional int64 finished_seamless_rotation_frame = 40;
+ optional int64 finished_seamless_rotation_frame = 40 [deprecated=true];
optional WindowFramesProto window_frames = 41;
optional bool force_seamless_rotation = 42;
optional bool has_compat_scale = 43;
diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto
index b3f54f9..c5eaf42 100644
--- a/core/proto/android/service/usb.proto
+++ b/core/proto/android/service/usb.proto
@@ -264,8 +264,9 @@
optional DataRole data_role = 4;
repeated UsbPortStatusRoleCombinationProto role_combinations = 5;
optional android.service.ContaminantPresenceStatus contaminant_presence_status = 6;
- optional bool usb_data_enabled = 7;
+ optional string usb_data_status = 7;
optional bool is_power_transfer_limited = 8;
+ optional string usb_power_brick_status = 9;
}
message UsbPortStatusRoleCombinationProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a4d4069..f106872 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -974,6 +974,61 @@
android:permissionFlags="softRestricted|immutablyRestricted"
android:protectionLevel="dangerous" />
+ <!-- Required to be able to read audio files from shared storage.
+ <p>Protection level: dangerous -->
+ <permission-group android:name="android.permission-group.READ_MEDIA_AURAL"
+ android:icon="@drawable/perm_group_read_media_aural"
+ android:label="@string/permgrouplab_readMediaAural"
+ android:description="@string/permgroupdesc_readMediaAural"
+ android:priority="950" />
+
+ <!-- Allows an application to read audio files from external storage.
+ <p>This permission is enforced starting in API level
+ {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+ For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+ targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
+ must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.READ_MEDIA_AUDIO"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_readMediaAudio"
+ android:description="@string/permdesc_readMediaAudio"
+ android:protectionLevel="dangerous" />
+
+ <!-- Required to be able to read image and video files from shared storage.
+ <p>Protection level: dangerous -->
+ <permission-group android:name="android.permission-group.READ_MEDIA_VISUAL"
+ android:icon="@drawable/perm_group_read_media_visual"
+ android:label="@string/permgrouplab_readMediaVisual"
+ android:description="@string/permgroupdesc_readMediaVisual"
+ android:priority="1000" />
+
+ <!-- Allows an application to read audio files from external storage.
+ <p>This permission is enforced starting in API level
+ {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+ For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+ targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
+ must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.READ_MEDIA_VIDEO"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_readMediaVideo"
+ android:description="@string/permdesc_readMediaVideo"
+ android:protectionLevel="dangerous" />
+
+ <!-- Allows an application to read image files from external storage.
+ <p>This permission is enforced starting in API level
+ {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+ For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+ targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
+ must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.READ_MEDIA_IMAGE"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_readMediaImage"
+ android:description="@string/permdesc_readMediaImage"
+ android:protectionLevel="dangerous" />
+
<!-- Allows an application to write to external storage.
<p class="note"><strong>Note:</strong> If <em>both</em> your <a
href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
@@ -2306,6 +2361,26 @@
<permission android:name="android.permission.MANAGE_FACTORY_RESET_PROTECTION"
android:protectionLevel="signature|privileged"/>
+ <!-- ======================================== -->
+ <!-- Permissions for lost mode -->
+ <!-- ======================================== -->
+ <eat-comment />
+
+ <!-- @SystemApi Allows an application to trigger lost mode on an organization-owned device.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.TRIGGER_LOST_MODE"
+ android:protectionLevel="signature|role"/>
+
+ <!-- @SystemApi Allows an application to instruct the framework to send location to the device
+ admin when an organization-owned device is in lost mode.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.SEND_LOST_MODE_LOCATION_UPDATES"
+ android:protectionLevel="signature|privileged"/>
+
<!-- ================================== -->
<!-- Permissions for accessing hardware -->
<!-- ================================== -->
@@ -4251,7 +4326,8 @@
<!-- Allows low-level access to setting the keyboard layout.
<p>Not for use by third-party applications.
- @hide -->
+ @hide
+ @TestApi -->
<permission android:name="android.permission.SET_KEYBOARD_LAYOUT"
android:protectionLevel="signature" />
@@ -6199,6 +6275,9 @@
<p>Not for use by third-party applications.</p> -->
<attribution android:tag="MusicRecognitionManagerService"
android:label="@string/music_recognition_manager_service"/>
+ <!-- Attribution for Device Policy Manager service. -->
+ <attribution android:tag="DevicePolicyManagerService"
+ android:label="@string/device_policy_manager_service"/>
<application android:process="system"
android:persistent="true"
@@ -6690,7 +6769,7 @@
</service>
<!-- TODO: Move to ExtServices or relevant component. -->
- <service android:name="com.android.server.selectiontoolbar.DefaultSelectionToolbarRenderService"
+ <service android:name="android.service.selectiontoolbar.DefaultSelectionToolbarRenderService"
android:permission="android.permission.BIND_SELECTION_TOOLBAR_RENDER_SERVICE"
android:process=":ui"
android:exported="false">
diff --git a/core/res/res/drawable/ic_ime_nav_back.xml b/core/res/res/drawable/ic_ime_nav_back.xml
new file mode 100644
index 0000000..ca329aa
--- /dev/null
+++ b/core/res/res/drawable/ic_ime_nav_back.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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="28dp"
+ android:height="28dp"
+ android:autoMirrored="true"
+ android:viewportWidth="28"
+ android:viewportHeight="28">
+ <path
+ android:pathData="M16.78,10.03l-3.97,3.97l3.97,3.97l-1.06,1.06l-5.03,-5.03l5.03,-5.03z"
+ android:fillColor="#FFFFFFFF" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_ime_switcher.xml b/core/res/res/drawable/ic_ime_switcher.xml
new file mode 100644
index 0000000..6c3b766
--- /dev/null
+++ b/core/res/res/drawable/ic_ime_switcher.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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M19,7h2v2h-2V7zM15,7h2v2h-2V7zM3,7h2v2H3V7zM7,7h2v2H7V7zM11,7h2v2h-2V7zM19,11h2v2h-2V11zM15,11h2v2h-2V11zM3,11h2v2H3V11zM7,11h2v2H7V11zM11,11h2v2h-2V11zM7,15h10v2H7V15z"
+ android:fillColor="#FFFFFFFF"/>
+</vector>
diff --git a/core/res/res/drawable/perm_group_read_media_aural.xml b/core/res/res/drawable/perm_group_read_media_aural.xml
new file mode 100644
index 0000000..6fc9c69
--- /dev/null
+++ b/core/res/res/drawable/perm_group_read_media_aural.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M10,21q-1.65,0 -2.825,-1.175Q6,18.65 6,17q0,-1.65 1.175,-2.825Q8.35,13 10,13q0.575,0 1.063,0.137 0.487,0.138 0.937,0.413V3h6v4h-4v10q0,1.65 -1.175,2.825Q11.65,21 10,21z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/perm_group_read_media_visual.xml b/core/res/res/drawable/perm_group_read_media_visual.xml
new file mode 100644
index 0000000..a5db271
--- /dev/null
+++ b/core/res/res/drawable/perm_group_read_media_visual.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M9,14h10l-3.45,-4.5 -2.3,3 -1.55,-2zM8,18q-0.825,0 -1.412,-0.587Q6,16.825 6,16L6,4q0,-0.825 0.588,-1.413Q7.175,2 8,2h12q0.825,0 1.413,0.587Q22,3.175 22,4v12q0,0.825 -0.587,1.413Q20.825,18 20,18zM8,16h12L20,4L8,4v12zM4,22q-0.825,0 -1.413,-0.587Q2,20.825 2,20L2,6h2v14h14v2zM8,4v12L8,4z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/layout/input_method_nav_back.xml b/core/res/res/layout/input_method_nav_back.xml
new file mode 100644
index 0000000..671766a
--- /dev/null
+++ b/core/res/res/layout/input_method_nav_back.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<android.inputmethodservice.navigationbar.KeyButtonView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/input_method_nav_back"
+ android:layout_width="@dimen/input_method_navigation_key_width"
+ android:layout_height="match_parent"
+ android:layout_weight="0"
+ android:scaleType="center"
+ android:contentDescription="@string/input_method_nav_back_button_desc"
+ android:paddingStart="@dimen/input_method_navigation_key_padding"
+ android:paddingEnd="@dimen/input_method_navigation_key_padding"
+ />
diff --git a/core/res/res/layout/input_method_nav_home_handle.xml b/core/res/res/layout/input_method_nav_home_handle.xml
new file mode 100644
index 0000000..501f512
--- /dev/null
+++ b/core/res/res/layout/input_method_nav_home_handle.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.
+-->
+<android.inputmethodservice.navigationbar.NavigationHandle
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/input_method_nav_home_handle"
+ android:layout_width="72dp"
+ android:layout_height="match_parent"
+ android:layout_weight="0"
+ android:paddingStart="@dimen/input_method_navigation_key_padding"
+ android:paddingEnd="@dimen/input_method_navigation_key_padding"
+ />
diff --git a/core/res/res/layout/input_method_nav_ime_switcher.xml b/core/res/res/layout/input_method_nav_ime_switcher.xml
new file mode 100644
index 0000000..b571ba9
--- /dev/null
+++ b/core/res/res/layout/input_method_nav_ime_switcher.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<android.inputmethodservice.navigationbar.KeyButtonView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/input_method_nav_ime_switcher"
+ android:layout_width="@dimen/input_method_navigation_key_width"
+ android:layout_height="match_parent"
+ android:layout_weight="0"
+ android:contentDescription="@string/input_method_ime_switch_button_desc"
+ android:scaleType="center"
+ android:paddingStart="@dimen/input_method_navigation_key_padding"
+ android:paddingEnd="@dimen/input_method_navigation_key_padding"
+ />
diff --git a/core/res/res/layout/input_method_navigation_bar.xml b/core/res/res/layout/input_method_navigation_bar.xml
new file mode 100644
index 0000000..ce402fb
--- /dev/null
+++ b/core/res/res/layout/input_method_navigation_bar.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+<android.inputmethodservice.navigationbar.NavigationBarView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/input_method_navigation_bar_view"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false">
+
+ <android.inputmethodservice.navigationbar.NavigationBarInflaterView
+ android:id="@+id/input_method_nav_inflater"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false" />
+
+</android.inputmethodservice.navigationbar.NavigationBarView>
diff --git a/core/res/res/layout/input_method_navigation_layout.xml b/core/res/res/layout/input_method_navigation_layout.xml
new file mode 100644
index 0000000..05e750a
--- /dev/null
+++ b/core/res/res/layout/input_method_navigation_layout.xml
@@ -0,0 +1,57 @@
+<?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.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/input_method_rounded_corner_content_padding"
+ android:layout_marginEnd="@dimen/input_method_rounded_corner_content_padding"
+ android:paddingStart="@dimen/input_method_nav_content_padding"
+ android:paddingEnd="@dimen/input_method_nav_content_padding"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:id="@+id/input_method_nav_horizontal">
+
+ <FrameLayout
+ android:id="@+id/input_method_nav_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:id="@+id/input_method_nav_ends_group"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:clipToPadding="false"
+ android:clipChildren="false" />
+
+ <LinearLayout
+ android:id="@+id/input_method_nav_center_group"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:orientation="horizontal"
+ android:clipToPadding="false"
+ android:clipChildren="false" />
+
+ </FrameLayout>
+
+</FrameLayout>
diff --git a/core/res/res/values-sw360dp/dimens.xml b/core/res/res/values-sw360dp/dimens.xml
index 4c74264..00de60e 100644
--- a/core/res/res/values-sw360dp/dimens.xml
+++ b/core/res/res/values-sw360dp/dimens.xml
@@ -18,4 +18,8 @@
-->
<resources>
<dimen name="chooser_grid_padding">16dp</dimen>
+
+ <!-- Copied from SysUI's @dimen/navigation_key_width for the embedded nav bar in the IME -->
+ <dimen name="input_method_navigation_key_width">80dip</dimen>
+
</resources>
diff --git a/core/res/res/values-sw372dp/dimens.xml b/core/res/res/values-sw372dp/dimens.xml
new file mode 100644
index 0000000..cb29a19
--- /dev/null
+++ b/core/res/res/values-sw372dp/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>
+ <!-- Copied from SysUI's @dimen/nav_content_padding for the embedded nav bar in the IME -->
+ <dimen name="input_method_nav_content_padding">8dp</dimen>
+</resources>
diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml
index e8f15fd..4c70ea3 100644
--- a/core/res/res/values-sw600dp/dimens.xml
+++ b/core/res/res/values-sw600dp/dimens.xml
@@ -41,6 +41,11 @@
<!-- Size of lockscreen outerring on unsecure unlock LockScreen -->
<dimen name="keyguard_lockscreen_outerring_diameter">364dp</dimen>
+ <!-- Copied from SysUI's @dimen/navigation_key_width for the embedded nav bar in the IME -->
+ <dimen name="input_method_navigation_key_width">128dp</dimen>
+ <!-- Copied from SysUI's @dimen/navigation_key_padding for the embedded nav bar in the IME -->
+ <dimen name="input_method_navigation_key_padding">25dp</dimen>
+
<!-- Height of FaceUnlock view in keyguard -->
<dimen name="face_unlock_height">430dip</dimen>
diff --git a/core/res/res/values-sw900dp/dimens.xml b/core/res/res/values-sw900dp/dimens.xml
index 11092b2..9ec4204 100644
--- a/core/res/res/values-sw900dp/dimens.xml
+++ b/core/res/res/values-sw900dp/dimens.xml
@@ -24,4 +24,12 @@
the same as @dimen/navigation_bar_height -->
<dimen name="navigation_bar_height_landscape">56dp</dimen>
+ <!-- Copied from SysUI's @dimen/navigation_key_width for the embedded nav bar in the IME. -->
+ <dimen name="input_method_navigation_key_width">80dp</dimen>
+ <!-- Copied from SysUI's @dimen/navigation_key_padding for the embedded nav bar in the IME. -->
+ <dimen name="input_method_navigation_key_padding">0dp</dimen>
+ <!-- Copied from SysUI's @dimen/key_button_ripple_max_width for the embedded nav bar in the
+ IME. -->
+ <dimen name="input_method_nav_key_button_ripple_max_width">76dp</dimen>
+
</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index a5da1ba..8696f5a 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5038,6 +5038,10 @@
<attr name="fontFeatureSettings" format="string" />
<!-- Font variation settings. -->
<attr name="fontVariationSettings" format="string"/>
+ <!-- Specifies the strictness of line-breaking rules applied within an element. -->
+ <attr name="lineBreakStyle" />
+ <!-- Specifies the phrase-based breaking opportunities. -->
+ <attr name="lineBreakWordStyle" />
</declare-styleable>
<declare-styleable name="TextClock">
<!-- Specifies the formatting pattern used to show the time and/or date
@@ -5436,6 +5440,13 @@
<!-- ndicates breaking text with the most strictest line-breaking rules. -->
<enum name="strict" value="3" />
</attr>
+ <!-- Specify the phrase-based line break can be used when calculating the text wrapping.-->
+ <attr name="lineBreakWordStyle">
+ <!-- No line break word style specific. -->
+ <enum name="none" value="0" />
+ <!-- Specify the phrase based breaking. -->
+ <enum name="phrase" value="1" />
+ </attr>
<!-- Specify the type of auto-size. Note that this feature is not supported by EditText,
works only for TextView. -->
<attr name="autoSizeTextType" format="enum">
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c0c8618..02d6293d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -134,9 +134,6 @@
be sent during a change to the audio output device. -->
<bool name="config_sendAudioBecomingNoisy">true</bool>
- <!-- Whether Hearing Aid profile is supported -->
- <bool name="config_hearing_aid_profile_supported">false</bool>
-
<!-- Flag to disable all transition animations -->
<bool name="config_disableTransitionAnimation">false</bool>
@@ -404,10 +401,6 @@
<string-array translatable="false" name="config_tether_bluetooth_regexs">
</string-array>
- <!-- Max number of Bluetooth tethering connections allowed. If this is
- updated config_tether_dhcp_range has to be updated appropriately. -->
- <integer translatable="false" name="config_max_pan_devices">5</integer>
-
<!-- This setting is deprecated, please use
com.android.networkstack.tethering.R.array.config_dhcp_range instead. -->
<string-array translatable="false" name="config_tether_dhcp_range">
@@ -1944,53 +1937,38 @@
<!-- Integer to set a max latency the accelerometer will batch sensor requests with. -->
<integer name="config_flipToScreenOffMaxLatencyMicros">2000000</integer>
- <!-- Boolean indicating if current platform supports bluetooth SCO for off call
- use cases -->
+ <!-- Note: This config is deprecated
+ Boolean indicating if current platform supports bluetooth SCO for off call
+ use cases
+ -->
<bool name="config_bluetooth_sco_off_call">true</bool>
- <!-- Boolean indicating if current platform supports bluetooth wide band
- speech -->
- <bool name="config_bluetooth_wide_band_speech">true</bool>
-
- <!-- Boolean indicating if current platform need do one-time bluetooth address
- re-validation -->
+ <!-- Note: This config is deprecated
+ Boolean indicating if current platform need do one-time bluetooth address
+ re-validation
+ -->
<bool name="config_bluetooth_address_validation">false</bool>
- <!-- Boolean indicating if current platform supports BLE peripheral mode -->
- <bool name="config_bluetooth_le_peripheral_mode_supported">false</bool>
-
- <!-- Boolean indicating if current platform supports HFP inband ringing -->
- <bool name="config_bluetooth_hfp_inband_ringing_support">false</bool>
-
- <!-- Max number of scan filters supported by blutooth controller. 0 if the
- device does not support hardware scan filters-->
- <integer translatable="false" name="config_bluetooth_max_scan_filters">0</integer>
-
- <!-- Max number of advertisers supported by bluetooth controller. 0 if the
- device does not support multiple advertisement-->
- <integer translatable="false" name="config_bluetooth_max_advertisers">0</integer>
-
- <!-- Idle current for bluetooth controller. 0 by default-->
+ <!-- Note: This config is deprecated, use BluetoothProperties instead.
+ Idle current for bluetooth controller. 0 by default
+ -->
<integer translatable="false" name="config_bluetooth_idle_cur_ma">0</integer>
- <!-- Rx current for bluetooth controller. 0 by default-->
+ <!-- Note: This config is deprecated, use BluetoothProperties instead.
+ Rx current for bluetooth controller. 0 by default
+ -->
<integer translatable="false" name="config_bluetooth_rx_cur_ma">0</integer>
- <!-- Tx current for bluetooth controller. 0 by default-->
+ <!-- Note: This config is deprecated, use BluetoothProperties instead.
+ Tx current for bluetooth controller. 0 by default
+ -->
<integer translatable="false" name="config_bluetooth_tx_cur_ma">0</integer>
- <!-- Operating volatage for bluetooth controller. 0 by default-->
+ <!-- Note: This config is deprecated, use BluetoothProperties instead.
+ Operating volatage for bluetooth controller. 0 by default
+ -->
<integer translatable="false" name="config_bluetooth_operating_voltage_mv">0</integer>
- <!-- Max number of connected audio devices supported by Bluetooth stack -->
- <integer name="config_bluetooth_max_connected_audio_devices">5</integer>
-
- <!-- Whether supported profiles should be reloaded upon enabling bluetooth -->
- <bool name="config_bluetooth_reload_supported_profiles_when_enabled">false</bool>
-
- <!-- Enabling autoconnect over pan -->
- <bool name="config_bluetooth_pan_enable_autoconnect">false</bool>
-
<!-- The default data-use polling period. -->
<integer name="config_datause_polling_period_sec">600</integer>
@@ -2144,10 +2122,6 @@
<!-- The name of the package that will be allowed to change its components' label/icon. -->
<string name="config_overrideComponentUiPackage" translatable="false">com.android.stk</string>
- <!-- Enable/disable default bluetooth profiles:
- HSP_AG, ObexObjectPush, Audio, NAP -->
- <bool name="config_bluetooth_default_profiles">true</bool>
-
<!-- IP address of the dns server to use if nobody else suggests one -->
<string name="config_default_dns_server" translatable="false">8.8.8.8</string>
@@ -4325,6 +4299,10 @@
or empty if the default should be used. -->
<string translatable="false" name="config_deviceSpecificDisplayAreaPolicyProvider"></string>
+ <!-- Class name of the device specific implementation of DeviceStatePolicy.Provider
+ or empty if the default should be used. -->
+ <string translatable="false" name="config_deviceSpecificDeviceStatePolicyProvider"></string>
+
<!-- Component name of media projection permission dialog -->
<string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string>
@@ -4353,8 +4331,6 @@
<!-- Component name that should be granted Notification Assistant access -->
<string name="config_defaultAssistantAccessComponent" translatable="false">android.ext.services/android.ext.services.notification.Assistant</string>
- <bool name="config_supportBluetoothPersistedState">true</bool>
-
<bool name="config_keepRestrictedProfilesInBackground">true</bool>
<!-- Cellular network service package name to bind to by default. -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index a877bd3..3f08e4b 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -131,6 +131,19 @@
corners. -->
<dimen name="rounded_corner_radius_bottom_adjustment">0px</dimen>
+ <!-- Copied from SysUI's @dimen/navigation_key_width for the embedded nav bar in the IME. -->
+ <dimen name="input_method_navigation_key_width">70dp</dimen>
+ <!-- Copied from SysUI's @dimen/navigation_key_padding for the embedded nav bar in the IME. -->
+ <dimen name="input_method_navigation_key_padding">0dp</dimen>
+ <!-- Copied from SysUI's @dimen/nav_content_padding for the embedded nav bar in the IME. -->
+ <dimen name="input_method_nav_content_padding">0px</dimen>
+ <!-- Copied from SysUI's @dimen/rounded_corner_content_padding for the embedded nav bar in the
+ IME. -->
+ <dimen name="input_method_rounded_corner_content_padding">0px</dimen>
+ <!-- Copied from SysUI's @dimen/key_button_ripple_max_width for the embedded nav bar in the
+ IME. -->
+ <dimen name="input_method_nav_key_button_ripple_max_width">95dp</dimen>
+
<!-- Width of the window of the divider bar used to resize docked stacks. -->
<dimen name="docked_stack_divider_thickness">48dp</dimen>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 42386fc..505fe59 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3212,6 +3212,7 @@
<public type="attr" name="shouldUseDefaultUnfoldTransition" id="0x0101064c" />
<public type="attr" name="lineBreakStyle" id="0x0101064d" />
+ <public type="attr" name="lineBreakWordStyle" id="0x0101064e" />
<staging-public-group-final type="id" first-id="0x01fe0000">
<public name="accessibilityActionDragStart" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 48b5a4f..59ad302 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -521,6 +521,8 @@
<string name="twilight_service">Twilight Service</string>
<!-- Attribution for Gnss Time Update service. [CHAR LIMIT=NONE]-->
<string name="gnss_time_update_service">GNSS Time Update Service</string>
+ <!-- Attribution for Device Policy Manager service. [CHAR LIMIT=NONE]-->
+ <string name="device_policy_manager_service">Device Policy Manager Service</string>
<!-- Attribution for MusicRecognitionManagerService. [CHAR LIMIT=NONE]-->
<string name="music_recognition_manager_service">Music Recognition Manager Service</string>
@@ -876,6 +878,16 @@
<!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgroupdesc_storage">access photos, media, and files on your device</string>
+ <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=40]-->
+ <string name="permgrouplab_readMediaAural">Music & other audio</string>
+ <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]-->
+ <string name="permgroupdesc_readMediaAural">access audio files on your device</string>
+
+ <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=40]-->
+ <string name="permgrouplab_readMediaVisual">Photos & videos</string>
+ <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]-->
+ <string name="permgroupdesc_readMediaVisual">access images and video files on your device</string>
+
<!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgrouplab_microphone">Microphone</string>
<!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
@@ -1889,6 +1901,21 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
<string name="permdesc_sdcardRead">Allows the app to read the contents of your shared storage.</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+ <string name="permlab_readMediaAudio">read audio files from shared storage</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+ <string name="permdesc_readMediaAudio">Allows the app to read audio files from your shared storage.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+ <string name="permlab_readMediaVideo">read video files from shared storage</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+ <string name="permdesc_readMediaVideo">Allows the app to read video files from your shared storage.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+ <string name="permlab_readMediaImage">read image files from shared storage</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+ <string name="permdesc_readMediaImage">Allows the app to read image files from your shared storage.</string>
+
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] -->
<string name="permlab_sdcardWrite">modify or delete the contents of your shared storage</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] -->
@@ -3277,6 +3304,11 @@
<!-- Title for EditText context menu [CHAR LIMIT=20] -->
<string name="editTextMenuTitle">Text actions</string>
+ <!-- Content description of the back button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="input_method_nav_back_button_desc">Back</string>
+ <!-- Content description of the switch input method button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="input_method_ime_switch_button_desc">Switch input method</string>
+
<!-- If the device is getting low on internal storage, a notification is shown to the user. This is the title of that notification. -->
<string name="low_internal_storage_view_title">Storage space running out</string>
<!-- If the device is getting low on internal storage, a notification is shown to the user. This is the message of that notification. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f639350..4d8f9a2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -279,8 +279,6 @@
<java-symbol type="bool" name="config_flipToScreenOffEnabled" />
<java-symbol type="integer" name="config_flipToScreenOffMaxLatencyMicros" />
<java-symbol type="bool" name="config_bluetooth_sco_off_call" />
- <java-symbol type="bool" name="config_bluetooth_le_peripheral_mode_supported" />
- <java-symbol type="bool" name="config_bluetooth_hfp_inband_ringing_support" />
<java-symbol type="bool" name="config_cellBroadcastAppLinks" />
<java-symbol type="bool" name="config_duplicate_port_omadm_wappush" />
<java-symbol type="bool" name="config_disableTransitionAnimation" />
@@ -344,7 +342,6 @@
<java-symbol type="integer" name="config_timeZoneRulesCheckRetryCount" />
<java-symbol type="bool" name="config_sendAudioBecomingNoisy" />
<java-symbol type="bool" name="config_enableScreenshotChord" />
- <java-symbol type="bool" name="config_bluetooth_default_profiles" />
<java-symbol type="bool" name="config_enableWifiDisplay" />
<java-symbol type="bool" name="config_allowAnimationsInLowPowerMode" />
<java-symbol type="bool" name="config_useDevInputEventForAudioJack" />
@@ -418,9 +415,6 @@
<java-symbol type="dimen" name="config_pictureInPictureMaxAspectRatio" />
<java-symbol type="integer" name="config_pictureInPictureMaxNumberOfActions" />
<java-symbol type="dimen" name="config_closeToSquareDisplayMaxAspectRatio" />
- <java-symbol type="integer" name="config_bluetooth_max_advertisers" />
- <java-symbol type="integer" name="config_bluetooth_max_scan_filters" />
- <java-symbol type="integer" name="config_bluetooth_max_connected_audio_devices" />
<java-symbol type="integer" name="config_burnInProtectionMinHorizontalOffset" />
<java-symbol type="integer" name="config_burnInProtectionMaxHorizontalOffset" />
<java-symbol type="integer" name="config_burnInProtectionMinVerticalOffset" />
@@ -430,9 +424,6 @@
<java-symbol type="integer" name="config_bluetooth_rx_cur_ma" />
<java-symbol type="integer" name="config_bluetooth_tx_cur_ma" />
<java-symbol type="integer" name="config_bluetooth_operating_voltage_mv" />
- <java-symbol type="bool" name="config_bluetooth_pan_enable_autoconnect" />
- <java-symbol type="bool" name="config_bluetooth_reload_supported_profiles_when_enabled" />
- <java-symbol type="bool" name="config_hearing_aid_profile_supported" />
<java-symbol type="integer" name="config_cursorWindowSize" />
<java-symbol type="integer" name="config_drawLockTimeoutMillis" />
<java-symbol type="integer" name="config_doublePressOnPowerBehavior" />
@@ -451,7 +442,6 @@
<java-symbol type="integer" name="config_wakeUpToLastStateTimeoutMillis" />
<java-symbol type="integer" name="config_lowMemoryKillerMinFreeKbytesAdjust" />
<java-symbol type="integer" name="config_lowMemoryKillerMinFreeKbytesAbsolute" />
- <java-symbol type="integer" name="config_max_pan_devices" />
<java-symbol type="integer" name="config_ntpPollingInterval" />
<java-symbol type="integer" name="config_ntpPollingIntervalShorter" />
<java-symbol type="integer" name="config_ntpRetry" />
@@ -2439,6 +2429,24 @@
<!-- From PinyinIME(!!!) -->
<java-symbol type="string" name="inputMethod" />
+ <!-- Gestural Nav buttons within InputMethodService -->
+ <java-symbol type="dimen" name="input_method_nav_key_button_ripple_max_width" />
+ <java-symbol type="drawable" name="ic_ime_nav_back" />
+ <java-symbol type="drawable" name="ic_ime_switcher" />
+ <java-symbol type="id" name="input_method_nav_back" />
+ <java-symbol type="id" name="input_method_nav_buttons" />
+ <java-symbol type="id" name="input_method_nav_center_group" />
+ <java-symbol type="id" name="input_method_nav_ends_group" />
+ <java-symbol type="id" name="input_method_nav_home_handle" />
+ <java-symbol type="id" name="input_method_nav_horizontal" />
+ <java-symbol type="id" name="input_method_nav_ime_switcher" />
+ <java-symbol type="id" name="input_method_nav_inflater" />
+ <java-symbol type="layout" name="input_method_navigation_bar" />
+ <java-symbol type="layout" name="input_method_navigation_layout" />
+ <java-symbol type="layout" name="input_method_nav_back" />
+ <java-symbol type="layout" name="input_method_nav_home_handle" />
+ <java-symbol type="layout" name="input_method_nav_ime_switcher" />
+
<!-- From Chromium-WebView -->
<java-symbol type="attr" name="actionModeWebSearchDrawable" />
<java-symbol type="string" name="websearch" />
@@ -3824,8 +3832,6 @@
<java-symbol type="string" name="config_defaultAssistantAccessComponent" />
- <java-symbol type="bool" name="config_supportBluetoothPersistedState" />
-
<java-symbol type="string" name="slices_permission_request" />
<java-symbol type="string" name="screenshot_edit" />
@@ -4649,4 +4655,6 @@
<java-symbol type="bool" name="config_enableSafetyCenter" />
<java-symbol type="string" name="config_deviceManagerUpdater" />
+
+ <java-symbol type="string" name="config_deviceSpecificDeviceStatePolicyProvider" />
</resources>
diff --git a/core/tests/coretests/res/xml/power_profile_test.xml b/core/tests/coretests/res/xml/power_profile_test.xml
new file mode 100644
index 0000000..2257114
--- /dev/null
+++ b/core/tests/coretests/res/xml/power_profile_test.xml
@@ -0,0 +1,123 @@
+<?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.
+ -->
+
+<device name="Android">
+ <!-- This is the battery capacity in mAh -->
+ <item name="battery.capacity">3000</item>
+
+ <!-- Number of cores each CPU cluster contains -->
+ <array name="cpu.clusters.cores">
+ <value>4</value> <!-- Cluster 0 has 4 cores (cpu0, cpu1, cpu2, cpu3) -->
+ <value>4</value> <!-- Cluster 1 has 4 cores (cpu4, cpu5, cpu5, cpu7) -->
+ </array>
+
+ <!-- Power consumption when CPU is suspended -->
+ <item name="cpu.suspend">5</item>
+ <!-- Additional power consumption when CPU is in a kernel idle loop -->
+ <item name="cpu.idle">1.11</item>
+ <!-- Additional power consumption by CPU excluding cluster and core when running -->
+ <item name="cpu.active">2.55</item>
+
+ <!-- Additional power consumption by CPU cluster0 itself when running excluding cores in it -->
+ <item name="cpu.cluster_power.cluster0">2.11</item>
+ <!-- Additional power consumption by CPU cluster1 itself when running excluding cores in it -->
+ <item name="cpu.cluster_power.cluster1">2.22</item>
+
+ <!-- Different CPU speeds as reported in
+ /sys/devices/system/cpu/cpu0/cpufreq/stats/scaling_available_frequencies -->
+ <array name="cpu.core_speeds.cluster0">
+ <value>300000</value> <!-- 300 MHz CPU speed -->
+ <value>1000000</value> <!-- 1000 MHz CPU speed -->
+ <value>2000000</value> <!-- 2000 MHz CPU speed -->
+ </array>
+ <!-- Different CPU speeds as reported in
+ /sys/devices/system/cpu/cpu4/cpufreq/stats/scaling_available_frequencies -->
+ <array name="cpu.core_speeds.cluster1">
+ <value>300000</value> <!-- 300 MHz CPU speed -->
+ <value>1000000</value> <!-- 1000 MHz CPU speed -->
+ <value>2500000</value> <!-- 2500 MHz CPU speed -->
+ <value>3000000</value> <!-- 3000 MHz CPU speed -->
+ </array>
+
+ <!-- Additional power used by a CPU from cluster 0 when running at different
+ speeds. Currently this measurement also includes cluster cost. -->
+ <array name="cpu.core_power.cluster0">
+ <value>10</value> <!-- 300 MHz CPU speed -->
+ <value>20</value> <!-- 1000 MHz CPU speed -->
+ <value>30</value> <!-- 1900 MHz CPU speed -->
+ </array>
+ <!-- Additional power used by a CPU from cluster 1 when running at different
+ speeds. Currently this measurement also includes cluster cost. -->
+ <array name="cpu.core_power.cluster1">
+ <value>25</value> <!-- 300 MHz CPU speed -->
+ <value>35</value> <!-- 1000 MHz CPU speed -->
+ <value>50</value> <!-- 2500 MHz CPU speed -->
+ <value>60</value> <!-- 3000 MHz CPU speed -->
+ </array>
+
+ <!-- Power used by display unit in ambient display mode, including back lighting-->
+ <item name="ambient.on">0.5</item>
+ <!-- Additional power used when screen is turned on at minimum brightness -->
+ <item name="screen.on">100</item>
+ <!-- Additional power used when screen is at maximum brightness, compared to
+ screen at minimum brightness -->
+ <item name="screen.full">800</item>
+
+ <!-- Average power used by the camera flash module when on -->
+ <item name="camera.flashlight">500</item>
+ <!-- Average power use by the camera subsystem for a typical camera
+ application. Intended as a rough estimate for an application running a
+ preview and capturing approximately 10 full-resolution pictures per
+ minute. -->
+ <item name="camera.avg">600</item>
+
+ <!-- Additional power used by the audio hardware, probably due to DSP -->
+ <item name="audio">100.0</item>
+
+ <!-- Additional power used by the video hardware, probably due to DSP -->
+ <item name="video">150.0</item> <!-- ~50mA -->
+
+ <!-- Additional power used when GPS is acquiring a signal -->
+ <item name="gps.on">10</item>
+
+ <!-- Additional power used when cellular radio is transmitting/receiving -->
+ <item name="radio.active">60</item>
+ <!-- Additional power used when cellular radio is paging the tower -->
+ <item name="radio.scanning">3</item>
+ <!-- Additional power used when the cellular radio is on. Multi-value entry,
+ one per signal strength (no signal, weak, moderate, strong) -->
+ <array name="radio.on"> <!-- Strength 0 to BINS-1 -->
+ <value>6</value> <!-- none -->
+ <value>5</value> <!-- poor -->
+ <value>4</value> <!-- moderate -->
+ <value>3</value> <!-- good -->
+ <value>3</value> <!-- great -->
+ </array>
+
+ <!-- Cellular modem related values. These constants are deprecated, but still supported and
+ need to be tested -->
+ <item name="modem.controller.sleep">123</item>
+ <item name="modem.controller.idle">456</item>
+ <item name="modem.controller.rx">789</item>
+ <array name="modem.controller.tx"> <!-- Strength 0 to 4 -->
+ <value>10</value>
+ <value>20</value>
+ <value>30</value>
+ <value>40</value>
+ <value>50</value>
+ </array>
+</device>
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java b/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java
index 09985a8..aa188f2 100644
--- a/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java
+++ b/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java
@@ -116,25 +116,4 @@
@Override
public void onLowMemory() {}
}
-
- private static class TestComponentCallbacks2 implements ComponentCallbacks2 {
- private Configuration mConfiguration;
- private boolean mLowMemoryCalled;
- private int mLevel;
-
- @Override
- public void onConfigurationChanged(@NonNull Configuration newConfig) {
- mConfiguration = newConfig;
- }
-
- @Override
- public void onLowMemory() {
- mLowMemoryCalled = true;
- }
-
- @Override
- public void onTrimMemory(int level) {
- mLevel = level;
- }
- }
}
diff --git a/core/tests/coretests/src/android/content/ContextWrapperTest.java b/core/tests/coretests/src/android/content/ContextWrapperTest.java
new file mode 100644
index 0000000..ecaf1f4
--- /dev/null
+++ b/core/tests/coretests/src/android/content/ContextWrapperTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.content;
+
+import static android.content.ContextWrapper.COMPONENT_CALLBACK_ON_WRAPPER;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.app.WindowConfiguration;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.platform.test.annotations.Presubmit;
+import android.view.Display;
+import android.window.WindowContext;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+
+/**
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:ContextWrapperTest
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ContextWrapperTest {
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+ /**
+ * Before {@link android.os.Build.VERSION_CODES#TIRAMISU}, {@link ContextWrapper} must
+ * register {@link ComponentCallbacks} to {@link ContextWrapper#getApplicationContext} before
+ * {@link ContextWrapper#attachBaseContext(Context)}.
+ */
+ @DisableCompatChanges(COMPONENT_CALLBACK_ON_WRAPPER)
+ @Test
+ public void testRegisterComponentCallbacksWithoutBaseContextBeforeT() {
+ final ContextWrapper wrapper = new TestContextWrapper(null /* base */);
+ final ComponentCallbacks callbacks = new TestComponentCallbacks2();
+
+ // It should be no-op if unregister a ComponentCallbacks without registration.
+ wrapper.unregisterComponentCallbacks(callbacks);
+
+ wrapper.registerComponentCallbacks(callbacks);
+
+ assertThat(wrapper.mCallbacksRegisteredToSuper.size()).isEqualTo(1);
+ assertThat(wrapper.mCallbacksRegisteredToSuper.get(0)).isEqualTo(callbacks);
+
+ wrapper.unregisterComponentCallbacks(callbacks);
+
+ assertThat(wrapper.mCallbacksRegisteredToSuper.isEmpty()).isTrue();
+ }
+
+ /**
+ * After {@link android.os.Build.VERSION_CODES#TIRAMISU}, {@link ContextWrapper} must
+ * throw {@link IllegalStateException} before {@link ContextWrapper#attachBaseContext(Context)}.
+ */
+ @Test
+ public void testRegisterComponentCallbacksWithoutBaseContextAfterT() {
+ final ContextWrapper wrapper = new TestContextWrapper(null /* base */);
+ final ComponentCallbacks callbacks = new TestComponentCallbacks2();
+
+ try {
+ wrapper.unregisterComponentCallbacks(callbacks);
+ fail("ContextWrapper#unregisterComponentCallbacks must throw Exception before"
+ + " ContextWrapper#attachToBaseContext.");
+ } catch (IllegalStateException ignored) {
+ // It is expected to throw IllegalStateException.
+ }
+
+ try {
+ wrapper.registerComponentCallbacks(callbacks);
+ fail("ContextWrapper#registerComponentCallbacks must throw Exception before"
+ + " ContextWrapper#attachToBaseContext.");
+ } catch (IllegalStateException ignored) {
+ // It is expected to throw IllegalStateException.
+ }
+ }
+
+ /**
+ * {@link ContextWrapper#registerComponentCallbacks(ComponentCallbacks)} must delegate to its
+ * {@link ContextWrapper#getBaseContext()}, so does
+ * {@link ContextWrapper#unregisterComponentCallbacks(ComponentCallbacks)}.
+ */
+ @Test
+ public void testRegisterComponentCallbacks() {
+ final Context appContext = ApplicationProvider.getApplicationContext();
+ final Display display = appContext.getSystemService(DisplayManager.class)
+ .getDisplay(DEFAULT_DISPLAY);
+ final WindowContext windowContext = (WindowContext) appContext.createWindowContext(display,
+ TYPE_APPLICATION_OVERLAY, null /* options */);
+ final ContextWrapper wrapper = new ContextWrapper(windowContext);
+ final TestComponentCallbacks2 callbacks = new TestComponentCallbacks2();
+
+ wrapper.registerComponentCallbacks(callbacks);
+
+ assertThat(wrapper.mCallbacksRegisteredToSuper).isNull();
+
+ final Configuration dispatchedConfig = new Configuration();
+ dispatchedConfig.fontScale = 1.2f;
+ dispatchedConfig.windowConfiguration.setWindowingMode(
+ WindowConfiguration.WINDOWING_MODE_FREEFORM);
+ dispatchedConfig.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
+ windowContext.dispatchConfigurationChanged(dispatchedConfig);
+
+ assertThat(callbacks.mConfiguration).isEqualTo(dispatchedConfig);
+ }
+
+ private static class TestContextWrapper extends ContextWrapper {
+ TestContextWrapper(Context base) {
+ super(base);
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ // The default implementation of ContextWrapper#getApplicationContext is to delegate
+ // to the base Context, and it leads to NPE if #registerComponentCallbacks is called
+ // directly before attach to base Context.
+ // We call to ApplicationProvider#getApplicationContext to prevent NPE because
+ // developers may have its implementation to prevent NPE without attaching base Context.
+ final Context baseContext = getBaseContext();
+ if (baseContext == null) {
+ return ApplicationProvider.getApplicationContext();
+ } else {
+ return super.getApplicationContext();
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/content/TestComponentCallbacks2.java b/core/tests/coretests/src/android/content/TestComponentCallbacks2.java
new file mode 100644
index 0000000..6ae7fc4
--- /dev/null
+++ b/core/tests/coretests/src/android/content/TestComponentCallbacks2.java
@@ -0,0 +1,42 @@
+/*
+ * 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.content;
+
+import android.content.res.Configuration;
+
+import androidx.annotation.NonNull;
+
+class TestComponentCallbacks2 implements ComponentCallbacks2 {
+ android.content.res.Configuration mConfiguration;
+ boolean mLowMemoryCalled;
+ int mLevel;
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ mConfiguration = newConfig;
+ }
+
+ @Override
+ public void onLowMemory() {
+ mLowMemoryCalled = true;
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ mLevel = level;
+ }
+}
diff --git a/core/tests/coretests/src/android/os/VibratorInfoTest.java b/core/tests/coretests/src/android/os/VibratorInfoTest.java
index d0e03a2..88766e2 100644
--- a/core/tests/coretests/src/android/os/VibratorInfoTest.java
+++ b/core/tests/coretests/src/android/os/VibratorInfoTest.java
@@ -25,7 +25,6 @@
import android.hardware.vibrator.Braking;
import android.hardware.vibrator.IVibrator;
import android.platform.test.annotations.Presubmit;
-import android.util.Range;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,10 +42,10 @@
private static final float[] TEST_AMPLITUDE_MAP = new float[]{
/* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
- private static final VibratorInfo.FrequencyMapping EMPTY_FREQUENCY_MAPPING =
- new VibratorInfo.FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, null);
- private static final VibratorInfo.FrequencyMapping TEST_FREQUENCY_MAPPING =
- new VibratorInfo.FrequencyMapping(TEST_RESONANT_FREQUENCY, TEST_MIN_FREQUENCY,
+ private static final VibratorInfo.FrequencyProfile EMPTY_FREQUENCY_PROFILE =
+ new VibratorInfo.FrequencyProfile(Float.NaN, Float.NaN, Float.NaN, null);
+ private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
+ new VibratorInfo.FrequencyProfile(TEST_RESONANT_FREQUENCY, TEST_MIN_FREQUENCY,
TEST_FREQUENCY_RESOLUTION, TEST_AMPLITUDE_MAP);
@Test
@@ -142,95 +141,109 @@
}
@Test
- public void testGetFrequencyRangeHz_invalidFrequencyMappingReturnsNull() {
+ public void testGetFrequencyProfile_unsetProfileIsEmpty() {
+ assertTrue(
+ new VibratorInfo.Builder(TEST_VIBRATOR_ID).build().getFrequencyProfile().isEmpty());
+ }
+
+ @Test
+ public void testFrequencyProfile_invalidValuesCreatesEmptyProfile() {
// Invalid, contains NaN values or empty array.
- assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID).build().getFrequencyRangeHz());
- assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
- Float.NaN, 50, 25, TEST_AMPLITUDE_MAP))
- .build().getFrequencyRangeHz());
- assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
- 150, Float.NaN, 25, TEST_AMPLITUDE_MAP))
- .build().getFrequencyRangeHz());
- assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
- 150, 50, Float.NaN, TEST_AMPLITUDE_MAP))
- .build().getFrequencyRangeHz());
- assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setFrequencyMapping(new VibratorInfo.FrequencyMapping(150, 50, 25, null))
- .build().getFrequencyRangeHz());
+ assertTrue(new VibratorInfo.FrequencyProfile(
+ Float.NaN, 50, 25, TEST_AMPLITUDE_MAP).isEmpty());
+ assertTrue(new VibratorInfo.FrequencyProfile(
+ 150, Float.NaN, 25, TEST_AMPLITUDE_MAP).isEmpty());
+ assertTrue(new VibratorInfo.FrequencyProfile(
+ 150, 50, Float.NaN, TEST_AMPLITUDE_MAP).isEmpty());
+ assertTrue(new VibratorInfo.FrequencyProfile(150, 50, 25, null).isEmpty());
+ // Invalid, contains zero or negative frequency values.
+ assertTrue(new VibratorInfo.FrequencyProfile(-1, 50, 25, TEST_AMPLITUDE_MAP).isEmpty());
+ assertTrue(new VibratorInfo.FrequencyProfile(150, 0, 25, TEST_AMPLITUDE_MAP).isEmpty());
+ assertTrue(new VibratorInfo.FrequencyProfile(150, 50, -2, TEST_AMPLITUDE_MAP).isEmpty());
+ // Invalid max amplitude entries.
+ assertTrue(new VibratorInfo.FrequencyProfile(
+ 150, 50, 50, new float[] { -1, 0, 1, 1, 0 }).isEmpty());
+ assertTrue(new VibratorInfo.FrequencyProfile(
+ 150, 50, 50, new float[] { 0, 1, 2, 1, 0 }).isEmpty());
// Invalid, minFrequency > resonantFrequency
- assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
- /* resonantFrequencyHz= */ 150, /* minFrequencyHz= */ 250, 25, null))
- .build().getFrequencyRangeHz());
+ assertTrue(new VibratorInfo.FrequencyProfile(
+ /* resonantFrequencyHz= */ 150, /* minFrequencyHz= */ 250, 25, TEST_AMPLITUDE_MAP)
+ .isEmpty());
// Invalid, maxFrequency < resonantFrequency by changing resolution.
- assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
- 150, 50, /* frequencyResolutionHz= */ 10, null))
- .build().getFrequencyRangeHz());
+ assertTrue(new VibratorInfo.FrequencyProfile(
+ 150, 50, /* frequencyResolutionHz= */ 10, TEST_AMPLITUDE_MAP).isEmpty());
}
@Test
- public void testGetFrequencyRangeHz_resultRangeDerivedFromHalMapping() {
- VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
- /* resonantFrequencyHz= */ 150,
- /* minFrequencyHz= */ 50,
- /* frequencyResolutionHz= */ 25,
- new float[]{
- /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f,
- /* 200Hz= */ 0.8f}))
- .build();
-
- assertEquals(Range.create(50f, 200f), info.getFrequencyRangeHz());
+ public void testGetFrequencyRangeHz_emptyProfileReturnsNull() {
+ assertNull(new VibratorInfo.FrequencyProfile(
+ Float.NaN, 50, 25, TEST_AMPLITUDE_MAP).getFrequencyRangeHz());
+ assertNull(new VibratorInfo.FrequencyProfile(
+ 150, Float.NaN, 25, TEST_AMPLITUDE_MAP).getFrequencyRangeHz());
+ assertNull(new VibratorInfo.FrequencyProfile(
+ 150, 50, Float.NaN, TEST_AMPLITUDE_MAP).getFrequencyRangeHz());
+ assertNull(new VibratorInfo.FrequencyProfile(150, 50, 25, null).getFrequencyRangeHz());
}
@Test
- public void testGetMaxAmplitude_emptyMappingReturnsAlwaysZero() {
- VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
- assertEquals(0f, info.getMaxAmplitude(Float.NaN), TEST_TOLERANCE);
- assertEquals(0f, info.getMaxAmplitude(100f), TEST_TOLERANCE);
- assertEquals(0f, info.getMaxAmplitude(200f), TEST_TOLERANCE);
+ public void testGetFrequencyRangeHz_validProfileReturnsMappedValues() {
+ VibratorInfo.FrequencyProfile profile = new VibratorInfo.FrequencyProfile(
+ /* resonantFrequencyHz= */ 150,
+ /* minFrequencyHz= */ 50,
+ /* frequencyResolutionHz= */ 25,
+ /* maxAmplitudes= */ new float[]{
+ /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f,
+ /* 200Hz= */ 0.8f});
- info = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+ assertEquals(50f, profile.getFrequencyRangeHz().getLower(), TEST_TOLERANCE);
+ assertEquals(200f, profile.getFrequencyRangeHz().getUpper(), TEST_TOLERANCE);
+ }
+
+ @Test
+ public void testGetMaxAmplitude_emptyProfileReturnsAlwaysZero() {
+ VibratorInfo.FrequencyProfile profile = EMPTY_FREQUENCY_PROFILE;
+ assertEquals(0f, profile.getMaxAmplitude(Float.NaN), TEST_TOLERANCE);
+ assertEquals(0f, profile.getMaxAmplitude(100f), TEST_TOLERANCE);
+ assertEquals(0f, profile.getMaxAmplitude(200f), TEST_TOLERANCE);
+
+ profile = new VibratorInfo.FrequencyProfile(
/* resonantFrequencyHz= */ 150,
/* minFrequencyHz= */ Float.NaN,
/* frequencyResolutionHz= */ Float.NaN,
- null))
- .build();
+ /* maxAmplitudes= */ null);
- assertEquals(0f, info.getMaxAmplitude(Float.NaN), TEST_TOLERANCE);
- assertEquals(0f, info.getMaxAmplitude(100f), TEST_TOLERANCE);
- assertEquals(0f, info.getMaxAmplitude(150f), TEST_TOLERANCE);
+ assertEquals(0f, profile.getMaxAmplitude(Float.NaN), TEST_TOLERANCE);
+ assertEquals(0f, profile.getMaxAmplitude(100f), TEST_TOLERANCE);
+ assertEquals(0f, profile.getMaxAmplitude(150f), TEST_TOLERANCE);
}
@Test
- public void testGetMaxAmplitude_validMappingReturnsMappedValues() {
- VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+ public void testGetMaxAmplitude_validProfileReturnsMappedValues() {
+ VibratorInfo.FrequencyProfile profile = new VibratorInfo.FrequencyProfile(
/* resonantFrequencyHz= */ 150,
/* minFrequencyHz= */ 50,
/* frequencyResolutionHz= */ 25,
- new float[]{
+ /* maxAmplitudes= */ new float[]{
/* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f,
- /* 200Hz= */ 0.8f}))
- .build();
+ /* 200Hz= */ 0.8f});
- assertEquals(1f, info.getMaxAmplitude(150f), TEST_TOLERANCE);
- assertEquals(0.9f, info.getMaxAmplitude(175f), TEST_TOLERANCE);
- assertEquals(0.8f, info.getMaxAmplitude(125f), TEST_TOLERANCE);
- assertEquals(0.8f, info.getMaxAmplitude(info.getFrequencyRangeHz().getUpper()),
- TEST_TOLERANCE); // 200Hz
- assertEquals(0.1f, info.getMaxAmplitude(info.getFrequencyRangeHz().getLower()),
- TEST_TOLERANCE); // 50Hz
+ // Values in the max amplitudes array should return exact measurement.
+ assertEquals(1f, profile.getMaxAmplitude(150f), TEST_TOLERANCE);
+ assertEquals(0.9f, profile.getMaxAmplitude(175f), TEST_TOLERANCE);
+ assertEquals(0.8f, profile.getMaxAmplitude(125f), TEST_TOLERANCE);
- // 145Hz maps to the max amplitude for 125Hz, which is lower.
- assertEquals(0.8f, info.getMaxAmplitude(145f), TEST_TOLERANCE); // 145Hz
- // 185Hz maps to the max amplitude for 200Hz, which is lower.
- assertEquals(0.8f, info.getMaxAmplitude(185f), TEST_TOLERANCE); // 185Hz
+ // Min and max frequencies should return exact measurement from array.
+ assertEquals(0.8f, profile.getMaxAmplitude(200f), TEST_TOLERANCE);
+ assertEquals(0.1f, profile.getMaxAmplitude(50f), TEST_TOLERANCE);
+
+ // Values outside [50Hz, 200Hz] just return 0.
+ assertEquals(0f, profile.getMaxAmplitude(49f), TEST_TOLERANCE);
+ assertEquals(0f, profile.getMaxAmplitude(201f), TEST_TOLERANCE);
+
+ // 145Hz maps to linear value between 125Hz and 150Hz max amplitudes 0.8 and 1.
+ assertEquals(0.96f, profile.getMaxAmplitude(145f), TEST_TOLERANCE);
+ // 185Hz maps to linear value between 175Hz and 200Hz max amplitudes 0.9 and 0.8.
+ assertEquals(0.86f, profile.getMaxAmplitude(185f), TEST_TOLERANCE);
}
@Test
@@ -245,7 +258,7 @@
.setPwlePrimitiveDurationMax(50)
.setPwleSizeMax(20)
.setQFactor(2f)
- .setFrequencyMapping(TEST_FREQUENCY_MAPPING);
+ .setFrequencyProfile(TEST_FREQUENCY_PROFILE);
VibratorInfo complete = completeBuilder.build();
assertEquals(complete, complete);
@@ -272,23 +285,21 @@
.build();
assertNotEquals(complete, completeWithDifferentPrimitiveDuration);
- VibratorInfo completeWithDifferentFrequencyMapping = completeBuilder
- .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+ VibratorInfo completeWithDifferentFrequencyProfile = completeBuilder
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
TEST_RESONANT_FREQUENCY + 20,
TEST_MIN_FREQUENCY + 10,
TEST_FREQUENCY_RESOLUTION + 5,
TEST_AMPLITUDE_MAP))
.build();
- assertNotEquals(complete, completeWithDifferentFrequencyMapping);
+ assertNotEquals(complete, completeWithDifferentFrequencyProfile);
- VibratorInfo completeWithEmptyFrequencyMapping = completeBuilder
- .setFrequencyMapping(EMPTY_FREQUENCY_MAPPING)
+ VibratorInfo completeWithEmptyFrequencyProfile = completeBuilder
+ .setFrequencyProfile(EMPTY_FREQUENCY_PROFILE)
.build();
- assertNotEquals(complete, completeWithEmptyFrequencyMapping);
+ assertNotEquals(complete, completeWithEmptyFrequencyProfile);
- VibratorInfo completeWithUnknownQFactor = completeBuilder
- .setQFactor(Float.NaN)
- .build();
+ VibratorInfo completeWithUnknownQFactor = completeBuilder.setQFactor(Float.NaN).build();
assertNotEquals(complete, completeWithUnknownQFactor);
VibratorInfo completeWithDifferentQFactor = completeBuilder
@@ -316,7 +327,7 @@
.setSupportedEffects(VibrationEffect.EFFECT_CLICK)
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
.setQFactor(Float.NaN)
- .setFrequencyMapping(TEST_FREQUENCY_MAPPING)
+ .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
.build();
Parcel parcel = Parcel.obtain();
diff --git a/core/tests/coretests/src/android/os/VibratorTest.java b/core/tests/coretests/src/android/os/VibratorTest.java
index 981086d..7a66bef 100644
--- a/core/tests/coretests/src/android/os/VibratorTest.java
+++ b/core/tests/coretests/src/android/os/VibratorTest.java
@@ -61,6 +61,8 @@
@Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+ private static final float TEST_TOLERANCE = 1e-5f;
+
private Context mContextSpy;
private Vibrator mVibratorSpy;
@@ -76,6 +78,9 @@
@Test
public void getId_returnsDefaultId() {
assertEquals(-1, mVibratorSpy.getId());
+ assertEquals(-1, new SystemVibrator.NoVibratorInfo().getId());
+ assertEquals(-1, new SystemVibrator.MultiVibratorInfo(new VibratorInfo[] {
+ VibratorInfo.EMPTY_VIBRATOR_INFO, VibratorInfo.EMPTY_VIBRATOR_INFO }).getId());
}
@Test
@@ -90,8 +95,7 @@
@Test
public void areEffectsSupported_noVibrator_returnsAlwaysNo() {
- SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
- new VibratorInfo[0]);
+ VibratorInfo info = new SystemVibrator.NoVibratorInfo();
assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
}
@@ -104,7 +108,7 @@
VibratorInfo unsupportedVibrator = new VibratorInfo.Builder(/* id= */ 2)
.setSupportedEffects(new int[0])
.build();
- SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
@@ -116,7 +120,7 @@
.setSupportedEffects(VibrationEffect.EFFECT_CLICK)
.build();
VibratorInfo unknownSupportVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
- SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{supportedVibrator, unknownSupportVibrator});
assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN,
info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
@@ -130,7 +134,7 @@
VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
.setSupportedEffects(VibrationEffect.EFFECT_CLICK)
.build();
- SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, secondVibrator});
assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
@@ -148,8 +152,7 @@
@Test
public void arePrimitivesSupported_noVibrator_returnsAlwaysFalse() {
- SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
- new VibratorInfo[0]);
+ VibratorInfo info = new SystemVibrator.NoVibratorInfo();
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
}
@@ -160,7 +163,7 @@
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
.build();
VibratorInfo unsupportedVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
- SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
}
@@ -175,7 +178,7 @@
.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 15)
.build();
- SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, secondVibrator});
assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
}
@@ -192,8 +195,7 @@
@Test
public void getPrimitivesDurations_noVibrator_returnsAlwaysZero() {
- SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
- new VibratorInfo[0]);
+ VibratorInfo info = new SystemVibrator.NoVibratorInfo();
assertEquals(0, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
}
@@ -204,7 +206,7 @@
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
.build();
VibratorInfo unsupportedVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
- SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
assertEquals(0, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
}
@@ -219,12 +221,180 @@
.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
.build();
- SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, secondVibrator});
assertEquals(20, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
}
@Test
+ public void getQFactorAndResonantFrequency_noVibrator_returnsNaN() {
+ VibratorInfo info = new SystemVibrator.NoVibratorInfo();
+
+ assertTrue(Float.isNaN(info.getQFactor()));
+ assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+ }
+
+ @Test
+ public void getQFactorAndResonantFrequency_differentValues_returnsNaN() {
+ VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setQFactor(1f)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
+ .build();
+ VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setQFactor(2f)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 2, 2, null))
+ .build();
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, secondVibrator});
+
+ assertTrue(Float.isNaN(info.getQFactor()));
+ assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+
+ // One vibrator with values undefined.
+ VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3).build();
+ info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, thirdVibrator});
+
+ assertTrue(Float.isNaN(info.getQFactor()));
+ assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+ }
+
+ @Test
+ public void getQFactorAndResonantFrequency_sameValues_returnsValue() {
+ VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setQFactor(10f)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
+ /* resonantFrequencyHz= */ 11, 10, 0.5f, null))
+ .build();
+ VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setQFactor(10f)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
+ /* resonantFrequencyHz= */ 11, 5, 1, null))
+ .build();
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, secondVibrator});
+
+ assertEquals(10f, info.getQFactor(), TEST_TOLERANCE);
+ assertEquals(11f, info.getResonantFrequencyHz(), TEST_TOLERANCE);
+ }
+
+ @Test
+ public void getFrequencyProfile_noVibrator_returnsEmpty() {
+ VibratorInfo info = new SystemVibrator.NoVibratorInfo();
+
+ assertTrue(info.getFrequencyProfile().isEmpty());
+ }
+
+ @Test
+ public void getFrequencyProfile_differentResonantFrequencyOrResolutionValues_returnsEmpty() {
+ VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
+ new float[] { 0, 1 }))
+ .build();
+ VibratorInfo differentResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 1, 1,
+ new float[] { 0, 1 }))
+ .build();
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, differentResonantFrequency});
+
+ assertTrue(info.getFrequencyProfile().isEmpty());
+
+ VibratorInfo differentFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 2,
+ new float[] { 0, 1 }))
+ .build();
+ info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, differentFrequencyResolution});
+
+ assertTrue(info.getFrequencyProfile().isEmpty());
+ }
+
+ @Test
+ public void getFrequencyProfile_missingValues_returnsEmpty() {
+ VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
+ new float[] { 0, 1 }))
+ .build();
+ VibratorInfo missingResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(Float.NaN, 1, 1,
+ new float[] { 0, 1 }))
+ .build();
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, missingResonantFrequency});
+
+ assertTrue(info.getFrequencyProfile().isEmpty());
+
+ VibratorInfo missingMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, Float.NaN, 1,
+ new float[] { 0, 1 }))
+ .build();
+ info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, missingMinFrequency});
+
+ assertTrue(info.getFrequencyProfile().isEmpty());
+
+ VibratorInfo missingFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, Float.NaN,
+ new float[] { 0, 1 }))
+ .build();
+ info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, missingFrequencyResolution});
+
+ assertTrue(info.getFrequencyProfile().isEmpty());
+
+ VibratorInfo missingMaxAmplitudes = new VibratorInfo.Builder(/* id= */ 2)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
+ .build();
+ info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, missingMaxAmplitudes});
+
+ assertTrue(info.getFrequencyProfile().isEmpty());
+ }
+
+ @Test
+ public void getFrequencyProfile_unalignedMaxAmplitudes_returnsEmpty() {
+ VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
+ new float[] { 0, 1, 1, 0 }))
+ .build();
+ VibratorInfo unalignedMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.1f, 0.5f,
+ new float[] { 0, 1, 1, 0 }))
+ .build();
+ VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+ new float[] { 0, 1, 1, 0 }))
+ .build();
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, unalignedMinFrequency, thirdVibrator});
+
+ assertTrue(info.getFrequencyProfile().isEmpty());
+ }
+
+ @Test
+ public void getFrequencyProfile_alignedProfiles_returnsIntersection() {
+ VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
+ new float[] { 0.5f, 1, 1, 0.5f }))
+ .build();
+ VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+ new float[] { 1, 1, 1 }))
+ .build();
+ VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+ new float[] { 0.8f, 1, 0.8f, 0.5f }))
+ .build();
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, secondVibrator, thirdVibrator});
+
+ assertEquals(
+ new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }),
+ info.getFrequencyProfile());
+ }
+
+ @Test
public void vibrate_withVibrationAttributes_usesGivenAttributes() {
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
VibrationAttributes attributes = new VibrationAttributes.Builder().setUsage(
diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
index a5261ae..4319b97 100644
--- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java
+++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
@@ -19,6 +19,10 @@
import static android.view.DisplayCutout.NO_CUTOUT;
import static android.view.DisplayCutout.extractBoundsFromList;
import static android.view.DisplayCutout.fromSpec;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
@@ -497,6 +501,74 @@
new ParcelableWrapper(mCutoutNumbers));
}
+ @Test
+ public void testGetRotatedBounds_top_rot0() {
+ int displayW = 500, displayH = 1000;
+ DisplayCutout expected = new DisplayCutout(Insets.of(20, 100, 20, 0),
+ ZERO_RECT, new Rect(50, 0, 75, 100), ZERO_RECT, ZERO_RECT,
+ Insets.of(20, 0, 20, 0));
+ DisplayCutout cutout = new DisplayCutout(Insets.of(20, 100, 20, 0),
+ ZERO_RECT, new Rect(50, 0, 75, 100), ZERO_RECT, ZERO_RECT,
+ Insets.of(20, 0, 20, 0));
+ DisplayCutout rotated = cutout.getRotated(displayW, displayH, ROTATION_0, ROTATION_0);
+ assertEquals(expected, rotated);
+ }
+
+ @Test
+ public void testGetRotatedBounds_top_rot90() {
+ int displayW = 500, displayH = 1000;
+ DisplayCutout expected = new DisplayCutout(Insets.of(100, 20, 0, 20),
+ new Rect(0, displayW - 75, 100, displayW - 50), ZERO_RECT, ZERO_RECT, ZERO_RECT,
+ Insets.of(0, 20, 0, 20), createParserInfo(ROTATION_90));
+ DisplayCutout cutout = new DisplayCutout(Insets.of(20, 100, 20, 0),
+ ZERO_RECT, new Rect(50, 0, 75, 100), ZERO_RECT, ZERO_RECT,
+ Insets.of(20, 0, 20, 0));
+ DisplayCutout rotated = cutout.getRotated(displayW, displayH, ROTATION_0, ROTATION_90);
+ assertEquals(expected, rotated);
+ }
+
+ @Test
+ public void testGetRotatedBounds_top_rot180() {
+ int displayW = 500, displayH = 1000;
+ DisplayCutout expected = new DisplayCutout(Insets.of(20, 0, 20, 100),
+ ZERO_RECT, ZERO_RECT, ZERO_RECT,
+ new Rect(displayW - 75, displayH - 100, displayW - 50, displayH - 0),
+ Insets.of(20, 0, 20, 0), createParserInfo(ROTATION_180));
+ DisplayCutout cutout = new DisplayCutout(Insets.of(20, 100, 20, 0),
+ ZERO_RECT, new Rect(50, 0, 75, 100), ZERO_RECT, ZERO_RECT,
+ Insets.of(20, 0, 20, 0));
+ DisplayCutout rotated = cutout.getRotated(displayW, displayH, ROTATION_0, ROTATION_180);
+ assertEquals(expected, rotated);
+ }
+
+ @Test
+ public void testGetRotatedBounds_top_rot270() {
+ int displayW = 500, displayH = 1000;
+ DisplayCutout expected = new DisplayCutout(Insets.of(0, 20, 100, 20),
+ ZERO_RECT, ZERO_RECT, new Rect(displayH - 100, 50, displayH - 0, 75), ZERO_RECT,
+ Insets.of(0, 20, 0, 20), createParserInfo(ROTATION_270));
+ DisplayCutout cutout = new DisplayCutout(Insets.of(20, 100, 20, 0),
+ ZERO_RECT, new Rect(50, 0, 75, 100), ZERO_RECT, ZERO_RECT,
+ Insets.of(20, 0, 20, 0));
+ DisplayCutout rotated = cutout.getRotated(displayW, displayH, ROTATION_0, ROTATION_270);
+ assertEquals(expected, rotated);
+ }
+
+ @Test
+ public void testGetRotatedBounds_top_rot90to180() {
+ int displayW = 500, displayH = 1000;
+ DisplayCutout expected = new DisplayCutout(Insets.of(20, 0, 20, 100),
+ ZERO_RECT, ZERO_RECT, ZERO_RECT,
+ new Rect(displayW - 75, displayH - 100, displayW - 50, displayH - 0),
+ Insets.of(20, 0, 20, 0), createParserInfo(ROTATION_180));
+ DisplayCutout cutout = new DisplayCutout(Insets.of(100, 20, 0, 20),
+ new Rect(0, displayW - 75, 100, displayW - 50), ZERO_RECT, ZERO_RECT, ZERO_RECT,
+ Insets.of(0, 20, 0, 20));
+ // starting from 90, so the start displayW/H are swapped:
+ DisplayCutout rotated = cutout.getRotated(displayH, displayW, ROTATION_90, ROTATION_180);
+ assertEquals(expected, rotated);
+ }
+
private static DisplayCutout createCutoutTop() {
return createCutoutWithInsets(0, 100, 0, 0);
}
@@ -533,4 +605,11 @@
ZERO_RECT,
waterfallInsets);
}
+
+ private static DisplayCutout.CutoutPathParserInfo createParserInfo(
+ @Surface.Rotation int rotation) {
+ return new DisplayCutout.CutoutPathParserInfo(
+ 0 /* displayWidth */, 0 /* displayHeight */, 0f /* density */, "" /* cutoutSpec */,
+ rotation, 0f /* scale */);
+ }
}
diff --git a/core/tests/coretests/src/android/window/WindowContextTest.java b/core/tests/coretests/src/android/window/WindowContextTest.java
index 656e756..b2a4044 100644
--- a/core/tests/coretests/src/android/window/WindowContextTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextTest.java
@@ -21,16 +21,25 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import android.app.Activity;
import android.app.EmptyActivity;
import android.app.Instrumentation;
+import android.app.WindowConfiguration;
+import android.content.ComponentCallbacks;
import android.content.Context;
+import android.content.ContextWrapper;
import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.IBinder;
@@ -43,6 +52,7 @@
import android.view.WindowManagerGlobal;
import android.view.WindowManagerImpl;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
@@ -79,6 +89,8 @@
private final WindowContext mWindowContext = createWindowContext();
private final IWindowManager mWms = WindowManagerGlobal.getWindowManagerService();
+ private static final int TIMEOUT_IN_SECONDS = 4;
+
@Test
public void testCreateWindowContextWindowManagerAttachClientToken() {
final WindowManager windowContextWm = WindowManagerImpl
@@ -131,7 +143,7 @@
});
- assertTrue(latch.await(4, TimeUnit.SECONDS));
+ assertTrue(latch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS));
// Verify that the window token of the window context is created after first addView().
@@ -234,7 +246,7 @@
// Add the parent window
mInstrumentation.runOnMainSync(() -> wm.addView(parentWindow, params));
- assertTrue(listener.mLatch.await(4, TimeUnit.SECONDS));
+ assertTrue(listener.mLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS));
final WindowManager.LayoutParams subWindowAttrs =
new WindowManager.LayoutParams(TYPE_APPLICATION_ATTACHED_DIALOG);
@@ -251,6 +263,63 @@
null /* theme */));
}
+ @Test
+ public void testRegisterComponentCallbacks() {
+ final WindowContext windowContext = createWindowContext();
+ final ConfigurationListener listener = new ConfigurationListener();
+
+ windowContext.registerComponentCallbacks(listener);
+
+ try {
+ final Configuration config = new Configuration();
+ config.windowConfiguration.setWindowingMode(
+ WindowConfiguration.WINDOWING_MODE_FREEFORM);
+ config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
+
+ windowContext.dispatchConfigurationChanged(config);
+
+ try {
+ assertWithMessage("Waiting for onConfigurationChanged timeout.")
+ .that(listener.mLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)).isTrue();
+ } catch (InterruptedException e) {
+ fail("Waiting for configuration changed failed because of " + e);
+ }
+
+ assertThat(listener.mConfiguration).isEqualTo(config);
+ } finally {
+ windowContext.unregisterComponentCallbacks(listener);
+ }
+ }
+
+ @Test
+ public void testRegisterComponentCallbacksOnWindowContextWrapper() {
+ final WindowContext windowContext = createWindowContext();
+ final Context wrapper = new ContextWrapper(windowContext);
+ final ConfigurationListener listener = new ConfigurationListener();
+
+ wrapper.registerComponentCallbacks(listener);
+
+ try {
+ final Configuration config = new Configuration();
+ config.windowConfiguration.setWindowingMode(
+ WindowConfiguration.WINDOWING_MODE_FREEFORM);
+ config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
+
+ windowContext.dispatchConfigurationChanged(config);
+
+ try {
+ assertWithMessage("Waiting for onConfigurationChanged timeout.")
+ .that(listener.mLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)).isTrue();
+ } catch (InterruptedException e) {
+ fail("Waiting for configuration changed failed because of " + e);
+ }
+
+ assertThat(listener.mConfiguration).isEqualTo(config);
+ } finally {
+ wrapper.unregisterComponentCallbacks(listener);
+ }
+ }
+
private WindowContext createWindowContext() {
return createWindowContext(TYPE_APPLICATION_OVERLAY);
}
@@ -262,6 +331,20 @@
return (WindowContext) instContext.createWindowContext(display, type, null /* options */);
}
+ private static class ConfigurationListener implements ComponentCallbacks {
+ private Configuration mConfiguration;
+ private CountDownLatch mLatch = new CountDownLatch(1);
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ mConfiguration = newConfig;
+ mLatch.countDown();
+ }
+
+ @Override
+ public void onLowMemory() {}
+ }
+
private static class AttachStateListener implements View.OnAttachStateChangeListener {
final CountDownLatch mLatch = new CountDownLatch(1);
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
index 1efd78b..bc3b422 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
@@ -57,11 +57,13 @@
@Before
public void setUp() {
mContext = InstrumentationRegistry.getContext();
- mProfile = new PowerProfile(mContext, true);
+ mProfile = new PowerProfile(mContext);
}
@Test
public void testPowerProfile() {
+ mProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
+
assertEquals(2, mProfile.getNumCpuClusters());
assertEquals(4, mProfile.getNumCoresInCpuCluster(0));
assertEquals(4, mProfile.getNumCoresInCpuCluster(1));
@@ -83,6 +85,43 @@
mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 0));
assertEquals(100.0, mProfile.getAveragePower(PowerProfile.POWER_AUDIO));
assertEquals(150.0, mProfile.getAveragePower(PowerProfile.POWER_VIDEO));
+
+ assertEquals(123.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_SLEEP));
+ assertEquals(456.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE));
+ assertEquals(789.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX));
+ assertEquals(10.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, 0));
+ assertEquals(20.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, 1));
+ assertEquals(30.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, 2));
+ assertEquals(40.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, 3));
+ assertEquals(50.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, 4));
+
+ // Deprecated Modem constants should work with current format.
+ assertEquals(123.0, mProfile.getAverageBatteryDrainMa(
+ PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP));
+ assertEquals(456.0, mProfile.getAverageBatteryDrainMa(
+ PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE));
+ assertEquals(789.0, mProfile.getAverageBatteryDrainMa(
+ PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_RX));
+ assertEquals(10.0, mProfile.getAverageBatteryDrainMa(
+ PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT
+ | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+ | ModemPowerProfile.MODEM_TX_LEVEL_0));
+ assertEquals(20.0, mProfile.getAverageBatteryDrainMa(
+ PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT
+ | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+ | ModemPowerProfile.MODEM_TX_LEVEL_1));
+ assertEquals(30.0, mProfile.getAverageBatteryDrainMa(
+ PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT
+ | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+ | ModemPowerProfile.MODEM_TX_LEVEL_2));
+ assertEquals(40.0, mProfile.getAverageBatteryDrainMa(
+ PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT
+ | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+ | ModemPowerProfile.MODEM_TX_LEVEL_3));
+ assertEquals(50.0, mProfile.getAverageBatteryDrainMa(
+ PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT
+ | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+ | ModemPowerProfile.MODEM_TX_LEVEL_4));
}
@Test
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 92fca36..88920c8 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -231,6 +231,18 @@
targetSdk="29">
<new-permission name="android.permission.ACCESS_MEDIA_LOCATION" />
</split-permission>
+ <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
+ targetSdk="33">
+ <new-permission name="android.permission.READ_MEDIA_AUDIO" />
+ </split-permission>
+ <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
+ targetSdk="33">
+ <new-permission name="android.permission.READ_MEDIA_VIDEO" />
+ </split-permission>
+ <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
+ targetSdk="33">
+ <new-permission name="android.permission.READ_MEDIA_IMAGE" />
+ </split-permission>
<split-permission name="android.permission.BLUETOOTH"
targetSdk="31">
<new-permission name="android.permission.BLUETOOTH_SCAN" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index de086df..0e06fac 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -528,6 +528,7 @@
<!-- Permissions required for CTS test - CtsSafetyCenterTestCases -->
<permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
<permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
+ <permission name="android.permission.SEND_LOST_MODE_LOCATION_UPDATES" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 0752329..1567233 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -319,6 +319,12 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimationController.java"
},
+ "-1764792832": {
+ "message": "Start collecting in Transition: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
"-1750384749": {
"message": "Launch on display check: allow launch on public display",
"level": "DEBUG",
@@ -349,6 +355,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayRotation.java"
},
+ "-1728919185": {
+ "message": " unrelated invisible sibling %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-1715268616": {
"message": "Last window, removing starting window %s",
"level": "VERBOSE",
@@ -445,12 +457,6 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
- "-1587921395": {
- "message": " Top targets: %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_WINDOW_TRANSITIONS",
- "at": "com\/android\/server\/wm\/Transition.java"
- },
"-1585311008": {
"message": "Bring to front target: %s from %s",
"level": "DEBUG",
@@ -703,12 +709,6 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
- "-1375751630": {
- "message": " --- Start combine pass ---",
- "level": "VERBOSE",
- "group": "WM_DEBUG_WINDOW_TRANSITIONS",
- "at": "com\/android\/server\/wm\/Transition.java"
- },
"-1364754753": {
"message": "Task vanished taskId=%d",
"level": "VERBOSE",
@@ -1171,12 +1171,6 @@
"group": "WM_DEBUG_IME",
"at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java"
},
- "-855366859": {
- "message": " merging children in from %s: %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_WINDOW_TRANSITIONS",
- "at": "com\/android\/server\/wm\/Transition.java"
- },
"-853404763": {
"message": "\twallpaper=%s",
"level": "DEBUG",
@@ -1237,6 +1231,12 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
+ "-779095785": {
+ "message": " sibling is a participant with mode %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-775004869": {
"message": "Not a match: %s",
"level": "DEBUG",
@@ -1549,12 +1549,6 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
},
- "-446752714": {
- "message": " SKIP: sibling contains top target %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_WINDOW_TRANSITIONS",
- "at": "com\/android\/server\/wm\/Transition.java"
- },
"-445944810": {
"message": "finish(%b): mCanceled=%b",
"level": "DEBUG",
@@ -1723,12 +1717,6 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimation.java"
},
- "-302335479": {
- "message": " remove from topTargets %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_WINDOW_TRANSITIONS",
- "at": "com\/android\/server\/wm\/Transition.java"
- },
"-292790591": {
"message": "Attempted to set IME policy to a display that does not exist: %d",
"level": "WARN",
@@ -2071,6 +2059,12 @@
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "112145970": {
+ "message": " SKIP: its sibling was rejected",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"114070759": {
"message": "New wallpaper target: %s prevTarget: %s caller=%s",
"level": "VERBOSE",
@@ -2407,6 +2401,12 @@
"group": "WM_SHOW_SURFACE_ALLOC",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "405146734": {
+ "message": " Final targets: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"416924848": {
"message": "InsetsSource Control %s for target %s",
"level": "DEBUG",
@@ -2431,12 +2431,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "430260320": {
- "message": " sibling is a top target with mode %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_WINDOW_TRANSITIONS",
- "at": "com\/android\/server\/wm\/Transition.java"
- },
"431715812": {
"message": "Launch on display check: allow launch any on display",
"level": "DEBUG",
@@ -2755,12 +2749,6 @@
"group": "WM_SHOW_SURFACE_ALLOC",
"at": "com\/android\/server\/wm\/WindowStateAnimator.java"
},
- "751854538": {
- "message": "DisplayArea keep clear rects changed name =%s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_WINDOW_ORGANIZER",
- "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
- },
"765395228": {
"message": "onAnimationFinished(): controller=%s reorderMode=%d",
"level": "DEBUG",
@@ -2791,6 +2779,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
+ "800698875": {
+ "message": "SyncGroup %d: Started when there is other active SyncGroup",
+ "level": "WARN",
+ "group": "WM_DEBUG_SYNC_ENGINE",
+ "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
+ },
"806891543": {
"message": "Setting mOrientationChangeComplete=true because wtoken %s numInteresting=%d numDrawn=%d",
"level": "INFO",
@@ -3085,12 +3079,6 @@
"group": "WM_DEBUG_WALLPAPER",
"at": "com\/android\/server\/wm\/WallpaperController.java"
},
- "1186730970": {
- "message": " no common mode yet, so set it",
- "level": "VERBOSE",
- "group": "WM_DEBUG_WINDOW_TRANSITIONS",
- "at": "com\/android\/server\/wm\/Transition.java"
- },
"1191587912": {
"message": "Moved rootTask=%s behind rootTask=%s",
"level": "DEBUG",
@@ -3205,6 +3193,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1333520287": {
+ "message": "Creating PendingTransition: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
"1337596507": {
"message": "Sending to proc %s new compat %s",
"level": "VERBOSE",
@@ -3823,6 +3817,12 @@
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "2034988903": {
+ "message": "PendingStartTransaction found",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/WindowOrganizerController.java"
+ },
"2039056415": {
"message": "Found matching affinity candidate!",
"level": "DEBUG",
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index 4d81858..cffdf28 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -17,7 +17,7 @@
package android.graphics.text;
import android.annotation.IntDef;
-import android.annotation.Nullable;
+import android.annotation.NonNull;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -58,7 +58,28 @@
@Retention(RetentionPolicy.SOURCE)
public @interface LineBreakStyle {}
+ /**
+ * No line break word style specified.
+ */
+ public static final int LINE_BREAK_WORD_STYLE_NONE = 0;
+
+ /**
+ * Indicates the line breaking is based on the phrased. This makes text wrapping only on
+ * meaningful words. The support of the text wrapping word style varies depending on the
+ * locales. If the locale does not support the phrase based text wrapping,
+ * there will be no effect.
+ */
+ public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1;
+
+ /** @hide */
+ @IntDef(prefix = { "LINE_BREAK_WORD_STYLE_" }, value = {
+ LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface LineBreakWordStyle {}
+
private @LineBreakStyle int mLineBreakStyle = LINE_BREAK_STYLE_NONE;
+ private @LineBreakWordStyle int mLineBreakWordStyle = LINE_BREAK_WORD_STYLE_NONE;
public LineBreakConfig() {
}
@@ -66,14 +87,12 @@
/**
* Set the line break configuration.
*
- * @param config the new line break configuration.
+ * @param lineBreakConfig the new line break configuration.
*/
- public void set(@Nullable LineBreakConfig config) {
- if (config != null) {
- mLineBreakStyle = config.getLineBreakStyle();
- } else {
- mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_NONE;
- }
+ public void set(@NonNull LineBreakConfig lineBreakConfig) {
+ Objects.requireNonNull(lineBreakConfig);
+ mLineBreakStyle = lineBreakConfig.getLineBreakStyle();
+ mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle();
}
/**
@@ -94,17 +113,36 @@
mLineBreakStyle = lineBreakStyle;
}
+ /**
+ * Get the line break word style.
+ *
+ * @return The current line break word style to be used for the text wrapping.
+ */
+ public @LineBreakWordStyle int getLineBreakWordStyle() {
+ return mLineBreakWordStyle;
+ }
+
+ /**
+ * Set the line break word style.
+ *
+ * @param lineBreakWordStyle the new line break word style.
+ */
+ public void setLineBreakWordStyle(@LineBreakWordStyle int lineBreakWordStyle) {
+ mLineBreakWordStyle = lineBreakWordStyle;
+ }
+
@Override
public boolean equals(Object o) {
if (o == null) return false;
if (this == o) return true;
if (!(o instanceof LineBreakConfig)) return false;
LineBreakConfig that = (LineBreakConfig) o;
- return mLineBreakStyle == that.mLineBreakStyle;
+ return (mLineBreakStyle == that.mLineBreakStyle)
+ && (mLineBreakWordStyle == that.mLineBreakWordStyle);
}
@Override
public int hashCode() {
- return Objects.hash(mLineBreakStyle);
+ return Objects.hash(mLineBreakStyle, mLineBreakWordStyle);
}
}
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 5f4afb7..6d691c1 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -264,8 +264,10 @@
Preconditions.checkArgument(end <= mText.length, "Style exceeds the text length");
int lbStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakStyle() :
LineBreakConfig.LINE_BREAK_STYLE_NONE;
- nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, mCurrentOffset, end,
- isRtl);
+ int lbWordStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakWordStyle() :
+ LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
+ nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, lbWordStyle,
+ mCurrentOffset, end, isRtl);
mCurrentOffset = end;
return this;
}
@@ -445,7 +447,8 @@
*
* @param nativeBuilderPtr The native MeasuredParagraph builder pointer.
* @param paintPtr The native paint pointer to be applied.
- * @param lineBreakStyle The line break style of the text.
+ * @param lineBreakStyle The line break style(lb) of the text.
+ * @param lineBreakWordStyle The line break word style(lw) of the text.
* @param start The start offset in the copied buffer.
* @param end The end offset in the copied buffer.
* @param isRtl True if the text is RTL.
@@ -453,6 +456,7 @@
private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr,
/* Non Zero */ long paintPtr,
int lineBreakStyle,
+ int lineBreakWordStyle,
@IntRange(from = 0) int start,
@IntRange(from = 0) int end,
boolean isRtl);
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_background.xml
new file mode 100644
index 0000000..723963f
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_background.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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@android:color/system_accent1_100"/>
+ <corners android:radius="12dp"/>
+</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_background_ripple.xml
new file mode 100644
index 0000000..0a3a813
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_background_ripple.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.
+ -->
+<!-- DO NOT SUBMIT - find right color!! -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@android:color/system_accent1_10">
+ <item android:drawable="@drawable/letterbox_education_dismiss_background"/>
+</ripple>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml
new file mode 100644
index 0000000..ff57406
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@android:color/system_neutral2_400"
+ android:pathData="M16.59,8.59L12.0,13.17 7.41,8.59 6.0,10.0l6.0,6.0 6.0,-6.0z"/>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.xml
new file mode 100644
index 0000000..16dea48
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.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.
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@android:color/system_neutral2_200">
+ <item android:drawable="@drawable/letterbox_education_ic_expand_more"/>
+</ripple>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml
new file mode 100644
index 0000000..cbfcfd0
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml
@@ -0,0 +1,32 @@
+<?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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="@dimen/letterbox_education_dialog_icon_size"
+ android:height="@dimen/letterbox_education_dialog_icon_size"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+ <path
+ android:fillColor="@color/letterbox_education_text_secondary"
+ android:fillType="evenOdd"
+ android:pathData="M2 8C0.895431 8 0 8.89543 0 10V38C0 39.1046 0.895431 40 2 40H46C47.1046 40 48 39.1046 48 38V10C48 8.89543 47.1046 8 46 8H2ZM44 12H4V36H44V12Z" />
+ <path
+ android:fillColor="@color/letterbox_education_text_secondary"
+ android:pathData="M 14 22 H 30 V 26 H 14 V 22 Z" />
+ <path
+ android:fillColor="@color/letterbox_education_text_secondary"
+ android:pathData="M26 16L34 24L26 32V16Z" />
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_screen_rotation.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_screen_rotation.xml
new file mode 100644
index 0000000..469eb1e
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_screen_rotation.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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="@dimen/letterbox_education_dialog_icon_size"
+ android:height="@dimen/letterbox_education_dialog_icon_size"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+ <path
+ android:fillColor="@color/letterbox_education_text_secondary"
+ android:fillType="evenOdd"
+ android:pathData="M22.56 2H26C37.02 2 46 10.98 46 22H42C42 14.44 36.74 8.1 29.7 6.42L31.74 10L28.26 12L22.56 2ZM22 46H25.44L19.74 36L16.26 38L18.3 41.58C11.26 39.9 6 33.56 6 26H2C2 37.02 10.98 46 22 46ZM20.46 12L36 27.52L27.54 36L12 20.48L20.46 12ZM17.64 9.18C18.42 8.4 19.44 8 20.46 8C21.5 8 22.52 8.4 23.3 9.16L38.84 24.7C40.4 26.26 40.4 28.78 38.84 30.34L30.36 38.82C29.58 39.6 28.56 40 27.54 40C26.52 40 25.5 39.6 24.72 38.82L9.18 23.28C7.62 21.72 7.62 19.2 9.18 17.64L17.64 9.18Z" />
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml
new file mode 100644
index 0000000..dcb8aed
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml
@@ -0,0 +1,32 @@
+<?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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="@dimen/letterbox_education_dialog_icon_size"
+ android:height="@dimen/letterbox_education_dialog_icon_size"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+ <path
+ android:fillColor="@color/letterbox_education_text_secondary"
+ android:fillType="evenOdd"
+ android:pathData="M2 8C0.895431 8 0 8.89543 0 10V38C0 39.1046 0.895431 40 2 40H46C47.1046 40 48 39.1046 48 38V10C48 8.89543 47.1046 8 46 8H2ZM44 12H4V36H44V12Z" />
+ <path
+ android:fillColor="@color/letterbox_education_text_secondary"
+ android:pathData="M6 16C6 14.8954 6.89543 14 8 14H21C22.1046 14 23 14.8954 23 16V32C23 33.1046 22.1046 34 21 34H8C6.89543 34 6 33.1046 6 32V16Z" />
+ <path
+ android:fillColor="@color/letterbox_education_text_secondary"
+ android:pathData="M25 16C25 14.8954 25.8954 14 27 14H40C41.1046 14 42 14.8954 42 16V32C42 33.1046 41.1046 34 40 34H27C25.8954 34 25 33.1046 25 32V16Z" />
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml
new file mode 100644
index 0000000..cd1d99a
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml
@@ -0,0 +1,40 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/letterbox_education_dialog_action_width"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/letterbox_education_dialog_action_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginBottom="12dp"/>
+
+ <TextView
+ android:id="@+id/letterbox_education_dialog_action_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:lineSpacingExtra="4sp"
+ android:textAlignment="center"
+ android:textColor="@color/compat_controls_text"
+ android:textSize="14sp"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
new file mode 100644
index 0000000..edf737f
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
@@ -0,0 +1,98 @@
+<!--
+ ~ 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.
+ -->
+<com.android.wm.shell.compatui.LetterboxEduDialogLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@color/compat_controls_background"
+ android:gravity="center"
+ android:paddingTop="24dp"
+ android:paddingBottom="32dp"
+ android:paddingHorizontal="32dp">
+
+ <!-- Adding an extra layer to animate the alpha of the background and content separately. -->
+ <LinearLayout
+ android:id="@+id/letterbox_education_content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/letterbox_education_icon"
+ android:layout_width="@dimen/letterbox_education_dialog_icon_size"
+ android:layout_height="@dimen/letterbox_education_dialog_icon_size"
+ android:layout_marginBottom="20dp" />
+
+ <TextView
+ android:id="@+id/letterbox_education_dialog_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxWidth="@dimen/letterbox_education_dialog_title_max_width"
+ android:lineSpacingExtra="4sp"
+ android:textAlignment="center"
+ android:textColor="@color/compat_controls_text"
+ android:textSize="24sp"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="top"
+ android:orientation="horizontal"
+ android:paddingTop="43dp">
+
+ <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout
+ android:id="@+id/letterbox_education_dialog_screen_rotation_action"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:icon="@drawable/letterbox_education_ic_screen_rotation"/>
+
+ <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout
+ android:id="@+id/letterbox_education_dialog_split_screen_action"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/letterbox_education_dialog_space_between_actions"
+ app:icon="@drawable/letterbox_education_ic_split_screen"
+ app:text="@string/letterbox_education_split_screen_text"/>
+
+ <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout
+ android:id="@+id/letterbox_education_dialog_reposition_action"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:layout_marginStart="@dimen/letterbox_education_dialog_space_between_actions"
+ app:icon="@drawable/letterbox_education_ic_reposition"
+ app:text="@string/letterbox_education_reposition_text"/>
+
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/letterbox_education_dialog_dismiss"
+ android:layout_width="match_parent"
+ android:layout_height="56dp"
+ android:layout_marginTop="43dp"
+ android:layout_marginHorizontal="24dp"
+ android:background="@drawable/letterbox_education_dismiss_background_ripple"
+ android:gravity="center"
+ android:text="@string/letterbox_education_got_it"
+ android:textColor="@android:color/system_neutral1_900"
+ android:textAlignment="center"
+ android:contentDescription="@string/letterbox_education_got_it"/>
+
+ </LinearLayout>
+
+</com.android.wm.shell.compatui.LetterboxEduDialogLayout>
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_overlay_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_overlay_layout.xml
new file mode 100644
index 0000000..f4c6d65
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_overlay_layout.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.
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:alpha="0"
+ android:background="@android:color/system_neutral1_900"
+ android:clickable="true">
+</FrameLayout>
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_toast_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_toast_layout.xml
new file mode 100644
index 0000000..a309d48
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_toast_layout.xml
@@ -0,0 +1,61 @@
+<!--
+ ~ 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.
+ -->
+<com.android.wm.shell.compatui.LetterboxEduToastLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@color/compat_controls_background"
+ android:gravity="center"
+ android:paddingVertical="14dp"
+ android:paddingHorizontal="16dp">
+
+ <!-- Adding an extra layer to animate the alpha of the background and content separately. -->
+ <LinearLayout
+ android:id="@+id/letterbox_education_content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/letterbox_education_icon"
+ android:layout_width="@dimen/letterbox_education_toast_icon_size"
+ android:layout_height="@dimen/letterbox_education_toast_icon_size"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxWidth="@dimen/letterbox_education_toast_text_max_width"
+ android:paddingHorizontal="16dp"
+ android:lineSpacingExtra="5sp"
+ android:text="@string/letterbox_education_toast_title"
+ android:textAlignment="viewStart"
+ android:textColor="@color/compat_controls_text"
+ android:textSize="16sp"
+ android:maxLines="1"
+ android:ellipsize="end"/>
+
+ <ImageButton
+ android:id="@+id/letterbox_education_toast_expand"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/letterbox_education_ic_expand_more_ripple"
+ android:background="@android:color/transparent"
+ android:contentDescription="@string/letterbox_education_expand_button_description"/>
+
+ </LinearLayout>
+
+</com.android.wm.shell.compatui.LetterboxEduToastLayout>
diff --git a/libs/WindowManager/Shell/res/values/attrs.xml b/libs/WindowManager/Shell/res/values/attrs.xml
new file mode 100644
index 0000000..4aaeef8
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values/attrs.xml
@@ -0,0 +1,22 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <declare-styleable name="LetterboxEduDialogActionLayout">
+ <attr name="icon" format="reference" />
+ <attr name="text" format="string" />
+ </declare-styleable>
+</resources>
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index cf596f7..84aec64 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -34,6 +34,9 @@
<color name="compat_controls_background">@android:color/system_neutral1_800</color>
<color name="compat_controls_text">@android:color/system_neutral1_50</color>
+ <!-- Letterbox Education -->
+ <color name="letterbox_education_text_secondary">@android:color/system_neutral2_200</color>
+
<!-- GM2 colors -->
<color name="GM2_grey_200">#E8EAED</color>
<color name="GM2_grey_700">#5F6368</color>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 1c19a10..ab2c9b1 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -219,6 +219,37 @@
<!-- The width of the camera compat hint. -->
<dimen name="camera_compat_hint_width">143dp</dimen>
+ <!-- The corner radius of the letterbox education toast. -->
+ <dimen name="letterbox_education_toast_corner_radius">100dp</dimen>
+
+ <!-- The corner radius of the letterbox education dialog. -->
+ <dimen name="letterbox_education_dialog_corner_radius">28dp</dimen>
+
+ <!-- The margin between the letterbox education toast/dialog and the bottom of the task. -->
+ <dimen name="letterbox_education_margin_bottom">16dp</dimen>
+
+ <!-- The size of the icon in the letterbox education toast. -->
+ <dimen name="letterbox_education_toast_icon_size">24dp</dimen>
+
+ <!-- The size of an icon in the letterbox education dialog. -->
+ <dimen name="letterbox_education_dialog_icon_size">48dp</dimen>
+
+ <!-- The width of each action container in the letterbox education dialog -->
+ <dimen name="letterbox_education_dialog_action_width">136dp</dimen>
+
+ <!-- The space between two actions in the letterbox education dialog -->
+ <dimen name="letterbox_education_dialog_space_between_actions">18dp</dimen>
+
+ <!-- The maximum width of the title and subtitle in the letterbox education dialog. -->
+ <dimen name="letterbox_education_dialog_title_max_width">444dp</dimen>
+
+ <!-- The maximum width of the text in the letterbox education toast. -->
+ <dimen name="letterbox_education_toast_text_max_width">398dp</dimen>
+
+ <!-- The distance that the letterbox education dialog will move up during appear/dismiss
+ animation. -->
+ <dimen name="letterbox_education_dialog_animation_elevation">20dp</dimen>
+
<!-- The width of the brand image on staring surface. -->
<dimen name="starting_surface_brand_image_width">200dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index ab0013a..a8a9ed7 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -171,4 +171,28 @@
compatibility control. [CHAR LIMIT=NONE] -->
<string name="camera_compat_dismiss_button_description">No camera issues? Tap to dismiss.</string>
+ <!-- The title of the letterbox education dialog. [CHAR LIMIT=NONE] -->
+ <string name="letterbox_education_dialog_title">Get the most out of <xliff:g id="app_name" example="YouTube">%s</xliff:g></string>
+
+ <!-- The title of the letterbox education toast. [CHAR LIMIT=60] -->
+ <string name="letterbox_education_toast_title">Rotate your device for a full-screen view</string>
+
+ <!-- Description of the rotate screen into portrait action. [CHAR LIMIT=NONE] -->
+ <string name="letterbox_education_screen_rotation_portrait_text">Rotate your screen to portrait</string>
+
+ <!-- Description of the rotate screen into landscape action. [CHAR LIMIT=NONE] -->
+ <string name="letterbox_education_screen_rotation_landscape_text">Rotate your screen to landscape</string>
+
+ <!-- Description of the put app in split-screen action. [CHAR LIMIT=NONE] -->
+ <string name="letterbox_education_split_screen_text">Drag in another app to use split screen</string>
+
+ <!-- Description of the reposition app action. [CHAR LIMIT=NONE] -->
+ <string name="letterbox_education_reposition_text">Double tap to reposition</string>
+
+ <!-- Button text for dismissing the letterbox education dialog. [CHAR LIMIT=20] -->
+ <string name="letterbox_education_got_it">Got it</string>
+
+ <!-- Accessibility description of the letterbox education toast expand to dialog button. [CHAR LIMIT=NONE] -->
+ <string name="letterbox_education_expand_button_description">Expand for more information.</string>
+
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java
new file mode 100644
index 0000000..762a037
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java
@@ -0,0 +1,71 @@
+/*
+ * 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.wm.shell.compatui.letterboxedu;
+
+import android.annotation.StringRes;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Custom layout for Letterbox Education dialog action.
+ */
+// TODO(b/215316431): Add tests
+class LetterboxEduDialogActionLayout extends FrameLayout {
+ private final ImageView mIcon;
+ private final TextView mText;
+
+ LetterboxEduDialogActionLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ TypedArray styledAttributes =
+ context.getTheme().obtainStyledAttributes(
+ attrs,
+ R.styleable.LetterboxEduDialogActionLayout,
+ /* defStyleAttr= */ 0,
+ /* defStyleRes= */ 0);
+ int iconId = styledAttributes.getResourceId(
+ R.styleable.LetterboxEduDialogActionLayout_icon, 0);
+ String optionalText = styledAttributes.getString(
+ R.styleable.LetterboxEduDialogActionLayout_text);
+ styledAttributes.recycle();
+
+ View rootView = inflate(getContext(), R.layout.letterbox_education_dialog_action_layout,
+ this);
+
+ mIcon = rootView.findViewById(R.id.letterbox_education_dialog_action_icon);
+ mIcon.setImageResource(iconId);
+ mText = rootView.findViewById(R.id.letterbox_education_dialog_action_text);
+ if (optionalText != null) {
+ mText.setText(optionalText);
+ }
+ }
+
+ void setText(@StringRes int id) {
+ mText.setText(getResources().getString(id));
+ }
+
+ void setIconRotation(float rotation) {
+ mIcon.setRotation(rotation);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
new file mode 100644
index 0000000..662862a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
@@ -0,0 +1,94 @@
+/*
+ * 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.wm.shell.compatui.letterboxedu;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Configuration.Orientation;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Container for Letterbox Education Dialog.
+ */
+// TODO(b/215316431): Add tests
+public class LetterboxEduDialogLayout extends FrameLayout {
+
+ public LetterboxEduDialogLayout(Context context) {
+ this(context, null);
+ }
+
+ public LetterboxEduDialogLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public LetterboxEduDialogLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public LetterboxEduDialogLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ /**
+ * Register a callback for the dismiss button.
+ * @param callback The callback to register
+ */
+ void setDismissOnClickListener(Runnable callback) {
+ findViewById(R.id.letterbox_education_dialog_dismiss).setOnClickListener(
+ view -> callback.run());
+ }
+
+ /**
+ * Updates the layout with the given app info.
+ * @param appIcon The name of the app
+ * @param appIcon The icon of the app
+ */
+ void updateAppInfo(String appName, Drawable appIcon) {
+ ((ImageView) findViewById(R.id.letterbox_education_icon)).setImageDrawable(appIcon);
+ ((TextView) findViewById(R.id.letterbox_education_dialog_title)).setText(
+ getResources().getString(R.string.letterbox_education_dialog_title, appName));
+ }
+
+ /**
+ * Updates the layout according to the given orientation.
+ * @param orientation The orientation of the display
+ */
+ void updateDisplayOrientation(@Orientation int orientation) {
+ boolean isOrientationPortrait = orientation == Configuration.ORIENTATION_PORTRAIT;
+ ((LetterboxEduDialogActionLayout) findViewById(
+ R.id.letterbox_education_dialog_screen_rotation_action)).setText(
+ isOrientationPortrait
+ ? R.string.letterbox_education_screen_rotation_landscape_text
+ : R.string.letterbox_education_screen_rotation_portrait_text);
+
+ if (isOrientationPortrait) {
+ ((LetterboxEduDialogActionLayout) findViewById(
+ R.id.letterbox_education_dialog_split_screen_action)).setIconRotation(90f);
+ }
+
+ findViewById(R.id.letterbox_education_dialog_reposition_action).setVisibility(
+ isOrientationPortrait ? View.GONE : View.VISIBLE);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduToastLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduToastLayout.java
new file mode 100644
index 0000000..e7f592d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduToastLayout.java
@@ -0,0 +1,69 @@
+/*
+ * 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.wm.shell.compatui.letterboxedu;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Container for the Letterbox Education Toast.
+ */
+// TODO(b/215316431): Add tests
+public class LetterboxEduToastLayout extends FrameLayout {
+
+ public LetterboxEduToastLayout(Context context) {
+ this(context, null);
+ }
+
+ public LetterboxEduToastLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public LetterboxEduToastLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public LetterboxEduToastLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ /**
+ * Register a callback for the dismiss button.
+ * @param callback The callback to register
+ */
+ void setExpandOnClickListener(Runnable callback) {
+ findViewById(R.id.letterbox_education_toast_expand).setOnClickListener(
+ view -> callback.run());
+ }
+
+ /**
+ * Updates the layout with the given app info.
+ * @param appName The name of the app
+ * @param appIcon The icon of the app
+ */
+ void updateAppInfo(String appName, Drawable appIcon) {
+ ImageView icon = findViewById(R.id.letterbox_education_icon);
+ icon.setContentDescription(appName);
+ icon.setImageDrawable(appIcon);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 4ecc0b6..1bef552e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -126,9 +126,6 @@
*/
private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
- //tmp vars for unused relayout params
- private static final Point TMP_SURFACE_SIZE = new Point();
-
private final Window mWindow;
private final Runnable mClearWindowHandler;
private final ShellExecutor mSplashScreenExecutor;
@@ -244,9 +241,9 @@
window.setOuter(snapshotSurface);
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
- session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, -1,
+ session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0,
tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
- tmpControls, TMP_SURFACE_SIZE);
+ tmpControls);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} catch (RemoteException e) {
snapshotSurface.clearWindowSynced();
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 68b0b4e..cb478c8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -118,10 +118,10 @@
fun getPrimaryRegion(dividerRegion: Region, rotation: Int): Region {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
- Region(0, 0, displayBounds.bounds.right,
+ Region.from(0, 0, displayBounds.bounds.right,
dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset)
} else {
- Region(0, 0, dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
+ Region.from(0, 0, dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
displayBounds.bounds.bottom)
}
}
@@ -129,10 +129,10 @@
fun getSecondaryRegion(dividerRegion: Region, rotation: Int): Region {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
- Region(0, dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
+ Region.from(0, dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
displayBounds.bounds.right, displayBounds.bounds.bottom)
} else {
- Region(dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, 0,
+ Region.from(dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, 0,
displayBounds.bounds.right, displayBounds.bounds.bottom)
}
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
index ae92366..b7c80df 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
@@ -24,13 +24,11 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.wm.shell.flicker.appPairsDividerIsInvisibleAtEnd
import com.android.wm.shell.flicker.helpers.AppPairsHelper
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
import org.junit.After
-import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
@@ -84,11 +82,7 @@
@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()
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
index f1b0135..1ac664e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
@@ -24,12 +24,10 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.AppPairsHelper
import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -69,11 +67,7 @@
@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()
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
index 6998cd2..65eb9aa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
@@ -24,13 +24,11 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.AppPairsHelper
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
import org.junit.After
-import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
@@ -88,11 +86,7 @@
@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()
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
index 7a53224..12910dd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
@@ -25,12 +25,10 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appPairsDividerIsInvisibleAtEnd
import com.android.wm.shell.flicker.helpers.AppPairsHelper
import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -73,11 +71,7 @@
@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()
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
index efae207..cf4ea46 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
@@ -28,14 +28,14 @@
component: FlickerComponentName
) : BaseAppHelper(instrumentation, activityLabel, component) {
fun getPrimaryBounds(dividerBounds: Region): Region {
- val primaryAppBounds = Region(0, 0, dividerBounds.bounds.right,
+ val primaryAppBounds = Region.from(0, 0, dividerBounds.bounds.right,
dividerBounds.bounds.bottom + WindowUtils.dockedStackDividerInset)
return primaryAppBounds
}
fun getSecondaryBounds(dividerBounds: Region): Region {
val displayBounds = WindowUtils.displayBounds
- val secondaryAppBounds = Region(0,
+ val secondaryAppBounds = Region.from(0,
dividerBounds.bounds.bottom - WindowUtils.dockedStackDividerInset,
displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight)
return secondaryAppBounds
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 264d482..a510d69 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
@@ -166,9 +166,9 @@
val dividerBounds =
layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region.bounds
- val topAppBounds = Region(0, 0, dividerBounds.right,
+ val topAppBounds = Region.from(0, 0, dividerBounds.right,
dividerBounds.top + WindowUtils.dockedStackDividerInset)
- val bottomAppBounds = Region(0,
+ val bottomAppBounds = Region.from(0,
dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
displayBounds.right,
displayBounds.bottom - WindowUtils.navigationBarHeight)
@@ -187,9 +187,9 @@
val dividerBounds =
layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region.bounds
- val topAppBounds = Region(0, 0, dividerBounds.right,
+ val topAppBounds = Region.from(0, 0, dividerBounds.right,
dividerBounds.top + WindowUtils.dockedStackDividerInset)
- val bottomAppBounds = Region(0,
+ val bottomAppBounds = Region.from(0,
dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
displayBounds.right,
displayBounds.bottom - WindowUtils.navigationBarHeight)
diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp
index 09539ec..76ea2d5 100644
--- a/libs/hwui/jni/text/MeasuredText.cpp
+++ b/libs/hwui/jni/text/MeasuredText.cpp
@@ -65,11 +65,13 @@
// Regular JNI
static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr,
- jlong paintPtr, jint lbStyle, jint start, jint end, jboolean isRtl) {
+ jlong paintPtr, jint lbStyle, jint lbWordStyle, jint start, jint end,
+ jboolean isRtl) {
Paint* paint = toPaint(paintPtr);
const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface());
minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
- toBuilder(builderPtr)->addStyleRun(start, end, std::move(minikinPaint), lbStyle, isRtl);
+ toBuilder(builderPtr)
+ ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, isRtl);
}
// Regular JNI
@@ -144,7 +146,7 @@
static const JNINativeMethod gMTBuilderMethods[] = {
// MeasuredParagraphBuilder native functions.
{"nInitBuilder", "()J", (void*)nInitBuilder},
- {"nAddStyleRun", "(JJIIIZ)V", (void*)nAddStyleRun},
+ {"nAddStyleRun", "(JJIIIIZ)V", (void*)nAddStyleRun},
{"nAddReplacementRun", "(JJIIF)V", (void*)nAddReplacementRun},
{"nBuildMeasuredText", "(JJ[CZZZ)J", (void*)nBuildMeasuredText},
{"nFreeBuilder", "(J)V", (void*)nFreeBuilder},
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index 553b08f..7c57bd5 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -78,7 +78,9 @@
static sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> shader,
const shaders::LinearEffect& linearEffect,
- float maxDisplayLuminance, float maxLuminance) {
+ float maxDisplayLuminance,
+ float currentDisplayLuminanceNits,
+ float maxLuminance) {
auto shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect));
auto [runtimeEffect, error] = SkRuntimeEffect::MakeForShader(std::move(shaderString));
if (!runtimeEffect) {
@@ -89,8 +91,8 @@
effectBuilder.child("child") = std::move(shader);
- const auto uniforms = shaders::buildLinearEffectUniforms(linearEffect, mat4(),
- maxDisplayLuminance, maxLuminance);
+ const auto uniforms = shaders::buildLinearEffectUniforms(
+ linearEffect, mat4(), maxDisplayLuminance, currentDisplayLuminanceNits, maxLuminance);
for (const auto& uniform : uniforms) {
effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size());
@@ -201,7 +203,9 @@
auto shader = layerImage->makeShader(sampling,
SkMatrix::RectToRect(skiaSrcRect, skiaDestRect));
constexpr float kMaxDisplayBrightess = 1000.f;
+ constexpr float kCurrentDisplayBrightness = 500.f;
shader = createLinearEffectShader(std::move(shader), effect, kMaxDisplayBrightess,
+ kCurrentDisplayBrightness,
layer->getMaxLuminanceNits());
paint.setShader(shader);
canvas->drawRect(skiaDestRect, paint);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index d862377..f627a3c 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -117,7 +117,7 @@
RenderThread* rt = reinterpret_cast<RenderThread*>(data);
size_t preferredFrameTimelineIndex =
AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(cbData);
- int64_t vsyncId = AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
+ AVsyncId vsyncId = AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
cbData, preferredFrameTimelineIndex);
int64_t frameDeadline = AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos(
cbData, preferredFrameTimelineIndex);
diff --git a/libs/services/Android.bp b/libs/services/Android.bp
index bf2e764..f656ebf 100644
--- a/libs/services/Android.bp
+++ b/libs/services/Android.bp
@@ -27,6 +27,7 @@
name: "libservices",
srcs: [
":IDropBoxManagerService.aidl",
+ ":ILogcatManagerService_aidl",
"src/content/ComponentName.cpp",
"src/os/DropBoxManager.cpp",
],
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index d6e203c..587222a 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -29,7 +29,6 @@
import android.annotation.SystemApi;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
-import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -180,13 +179,9 @@
private static final long IMPLICIT_MIN_UPDATE_INTERVAL = -1;
private static final double IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR = 1D / 6D;
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "Use {@link "
- + "LocationManager} methods to provide the provider explicitly.")
- @Nullable private String mProvider;
+ private @Nullable String mProvider;
private @Quality int mQuality;
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "Use {@link "
- + "LocationRequest} instead.")
- private long mInterval;
+ private long mIntervalMillis;
private long mMinUpdateIntervalMillis;
private long mExpireAtRealtimeMillis;
private long mDurationMillis;
@@ -195,7 +190,7 @@
private final long mMaxUpdateDelayMillis;
private boolean mHideFromAppOps;
private final boolean mAdasGnssBypass;
- private boolean mLocationSettingsIgnored;
+ private boolean mBypass;
private boolean mLowPower;
private @Nullable WorkSource mWorkSource;
@@ -208,9 +203,7 @@
@NonNull
public static LocationRequest create() {
// 60 minutes is the default legacy interval
- return new LocationRequest.Builder(60 * 60 * 1000)
- .setQuality(QUALITY_LOW_POWER)
- .build();
+ return new LocationRequest.Builder(60 * 60 * 1000).build();
}
/**
@@ -239,7 +232,7 @@
} else if (LocationManager.GPS_PROVIDER.equals(provider)) {
quality = QUALITY_HIGH_ACCURACY;
} else {
- quality = POWER_LOW;
+ quality = QUALITY_BALANCED_POWER_ACCURACY;
}
return new LocationRequest.Builder(intervalMillis)
@@ -291,11 +284,11 @@
long maxUpdateDelayMillis,
boolean hiddenFromAppOps,
boolean adasGnssBypass,
- boolean locationSettingsIgnored,
+ boolean bypass,
boolean lowPower,
WorkSource workSource) {
mProvider = provider;
- mInterval = intervalMillis;
+ mIntervalMillis = intervalMillis;
mQuality = quality;
mMinUpdateIntervalMillis = minUpdateIntervalMillis;
mExpireAtRealtimeMillis = expireAtRealtimeMillis;
@@ -305,7 +298,7 @@
mMaxUpdateDelayMillis = maxUpdateDelayMillis;
mHideFromAppOps = hiddenFromAppOps;
mAdasGnssBypass = adasGnssBypass;
- mLocationSettingsIgnored = locationSettingsIgnored;
+ mBypass = bypass;
mLowPower = lowPower;
mWorkSource = Objects.requireNonNull(workSource);
}
@@ -354,7 +347,7 @@
mQuality = QUALITY_LOW_POWER;
break;
case POWER_NONE:
- mInterval = PASSIVE_INTERVAL;
+ mIntervalMillis = PASSIVE_INTERVAL;
break;
default:
throw new IllegalArgumentException("invalid quality: " + quality);
@@ -388,9 +381,9 @@
millis = Long.MAX_VALUE - 1;
}
- mInterval = millis;
- if (mMinUpdateIntervalMillis > mInterval) {
- mMinUpdateIntervalMillis = mInterval;
+ mIntervalMillis = millis;
+ if (mMinUpdateIntervalMillis > mIntervalMillis) {
+ mMinUpdateIntervalMillis = mIntervalMillis;
}
return this;
}
@@ -418,7 +411,7 @@
* @return the desired interval of location updates
*/
public @IntRange(from = 0) long getIntervalMillis() {
- return mInterval;
+ return mIntervalMillis;
}
/**
@@ -556,11 +549,11 @@
*/
public @IntRange(from = 0) long getMinUpdateIntervalMillis() {
if (mMinUpdateIntervalMillis == IMPLICIT_MIN_UPDATE_INTERVAL) {
- return (long) (mInterval * IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR);
+ return (long) (mIntervalMillis * IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR);
} else {
// the min is only necessary in case someone use a deprecated function to mess with the
// interval or min update interval
- return min(mMinUpdateIntervalMillis, mInterval);
+ return min(mMinUpdateIntervalMillis, mIntervalMillis);
}
}
@@ -673,7 +666,7 @@
@Deprecated
@RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
public @NonNull LocationRequest setLocationSettingsIgnored(boolean locationSettingsIgnored) {
- mLocationSettingsIgnored = locationSettingsIgnored;
+ mBypass = locationSettingsIgnored;
return this;
}
@@ -687,7 +680,7 @@
*/
@SystemApi
public boolean isLocationSettingsIgnored() {
- return mLocationSettingsIgnored;
+ return mBypass;
}
/**
@@ -696,7 +689,7 @@
* @hide
*/
public boolean isBypass() {
- return mAdasGnssBypass || mLocationSettingsIgnored;
+ return mAdasGnssBypass || mBypass;
}
/**
@@ -796,7 +789,7 @@
@Override
public void writeToParcel(@NonNull Parcel parcel, int flags) {
parcel.writeString(mProvider);
- parcel.writeLong(mInterval);
+ parcel.writeLong(mIntervalMillis);
parcel.writeInt(mQuality);
parcel.writeLong(mExpireAtRealtimeMillis);
parcel.writeLong(mDurationMillis);
@@ -806,7 +799,7 @@
parcel.writeLong(mMaxUpdateDelayMillis);
parcel.writeBoolean(mHideFromAppOps);
parcel.writeBoolean(mAdasGnssBypass);
- parcel.writeBoolean(mLocationSettingsIgnored);
+ parcel.writeBoolean(mBypass);
parcel.writeBoolean(mLowPower);
parcel.writeTypedObject(mWorkSource, 0);
}
@@ -821,7 +814,7 @@
}
LocationRequest that = (LocationRequest) o;
- return mInterval == that.mInterval
+ return mIntervalMillis == that.mIntervalMillis
&& mQuality == that.mQuality
&& mExpireAtRealtimeMillis == that.mExpireAtRealtimeMillis
&& mDurationMillis == that.mDurationMillis
@@ -831,7 +824,7 @@
&& mMaxUpdateDelayMillis == that.mMaxUpdateDelayMillis
&& mHideFromAppOps == that.mHideFromAppOps
&& mAdasGnssBypass == that.mAdasGnssBypass
- && mLocationSettingsIgnored == that.mLocationSettingsIgnored
+ && mBypass == that.mBypass
&& mLowPower == that.mLowPower
&& Objects.equals(mProvider, that.mProvider)
&& Objects.equals(mWorkSource, that.mWorkSource);
@@ -839,7 +832,7 @@
@Override
public int hashCode() {
- return Objects.hash(mProvider, mInterval, mWorkSource);
+ return Objects.hash(mProvider, mIntervalMillis, mWorkSource);
}
@NonNull
@@ -850,9 +843,9 @@
if (mProvider != null) {
s.append(mProvider).append(" ");
}
- if (mInterval != PASSIVE_INTERVAL) {
+ if (mIntervalMillis != PASSIVE_INTERVAL) {
s.append("@");
- TimeUtils.formatDuration(mInterval, s);
+ TimeUtils.formatDuration(mIntervalMillis, s);
switch (mQuality) {
case QUALITY_HIGH_ACCURACY:
@@ -879,14 +872,14 @@
s.append(", maxUpdates=").append(mMaxUpdates);
}
if (mMinUpdateIntervalMillis != IMPLICIT_MIN_UPDATE_INTERVAL
- && mMinUpdateIntervalMillis < mInterval) {
+ && mMinUpdateIntervalMillis < mIntervalMillis) {
s.append(", minUpdateInterval=");
TimeUtils.formatDuration(mMinUpdateIntervalMillis, s);
}
if (mMinUpdateDistanceMeters > 0.0) {
s.append(", minUpdateDistance=").append(mMinUpdateDistanceMeters);
}
- if (mMaxUpdateDelayMillis / 2 > mInterval) {
+ if (mMaxUpdateDelayMillis / 2 > mIntervalMillis) {
s.append(", maxUpdateDelay=");
TimeUtils.formatDuration(mMaxUpdateDelayMillis, s);
}
@@ -899,8 +892,8 @@
if (mAdasGnssBypass) {
s.append(", adasGnssBypass");
}
- if (mLocationSettingsIgnored) {
- s.append(", settingsBypass");
+ if (mBypass) {
+ s.append(", bypass");
}
if (mWorkSource != null && !mWorkSource.isEmpty()) {
s.append(", ").append(mWorkSource);
@@ -923,7 +916,7 @@
private long mMaxUpdateDelayMillis;
private boolean mHiddenFromAppOps;
private boolean mAdasGnssBypass;
- private boolean mLocationSettingsIgnored;
+ private boolean mBypass;
private boolean mLowPower;
@Nullable private WorkSource mWorkSource;
@@ -943,7 +936,7 @@
mMaxUpdateDelayMillis = 0;
mHiddenFromAppOps = false;
mAdasGnssBypass = false;
- mLocationSettingsIgnored = false;
+ mBypass = false;
mLowPower = false;
mWorkSource = null;
}
@@ -952,7 +945,7 @@
* Creates a new Builder with all parameters copied from the given location request.
*/
public Builder(@NonNull LocationRequest locationRequest) {
- mIntervalMillis = locationRequest.mInterval;
+ mIntervalMillis = locationRequest.mIntervalMillis;
mQuality = locationRequest.mQuality;
mDurationMillis = locationRequest.mDurationMillis;
mMaxUpdates = locationRequest.mMaxUpdates;
@@ -961,7 +954,7 @@
mMaxUpdateDelayMillis = locationRequest.mMaxUpdateDelayMillis;
mHiddenFromAppOps = locationRequest.mHideFromAppOps;
mAdasGnssBypass = locationRequest.mAdasGnssBypass;
- mLocationSettingsIgnored = locationRequest.mLocationSettingsIgnored;
+ mBypass = locationRequest.mBypass;
mLowPower = locationRequest.mLowPower;
mWorkSource = locationRequest.mWorkSource;
@@ -1160,14 +1153,15 @@
@SystemApi
@RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
public @NonNull Builder setLocationSettingsIgnored(boolean locationSettingsIgnored) {
- mLocationSettingsIgnored = locationSettingsIgnored;
+ mBypass = locationSettingsIgnored;
return this;
}
/**
* It set to true, indicates that extreme trade-offs should be made if possible to save
* power for this request. This usually involves specialized hardware modes which can
- * greatly affect the quality of locations. Defaults to false.
+ * greatly affect the quality of locations. Not all devices may support this. Defaults to
+ * false.
*
* <p>Permissions enforcement occurs when resulting location request is actually used, not
* when this method is invoked.
@@ -1227,7 +1221,7 @@
mMaxUpdateDelayMillis,
mHiddenFromAppOps,
mAdasGnssBypass,
- mLocationSettingsIgnored,
+ mBypass,
mLowPower,
new WorkSource(mWorkSource));
}
diff --git a/media/aidl/android/media/audio/common/AudioDeviceType.aidl b/media/aidl/android/media/audio/common/AudioDeviceType.aidl
index afe6d10..8e200de 100644
--- a/media/aidl/android/media/audio/common/AudioDeviceType.aidl
+++ b/media/aidl/android/media/audio/common/AudioDeviceType.aidl
@@ -168,4 +168,8 @@
* Output into a speaker of a phone / table dock.
*/
OUT_DOCK = 145,
+ /**
+ * Output to a broadcast group.
+ */
+ OUT_BROADCAST = 146,
}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceType.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceType.aidl
index 0b7b77c..6a7b686 100644
--- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceType.aidl
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceType.aidl
@@ -67,4 +67,5 @@
OUT_SUBMIX = 143,
OUT_TELEPHONY_TX = 144,
OUT_DOCK = 145,
+ OUT_BROADCAST = 146,
}
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 211a50e..dd17dc6 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -177,6 +177,11 @@
*/
public static final int TYPE_HDMI_EARC = 29;
+ /**
+ * A device type describing a Bluetooth Low Energy (BLE) broadcast group.
+ */
+ public static final int TYPE_BLE_BROADCAST = 30;
+
/** @hide */
@IntDef(flag = false, prefix = "TYPE", value = {
TYPE_BUILTIN_EARPIECE,
@@ -207,7 +212,8 @@
TYPE_REMOTE_SUBMIX,
TYPE_BLE_HEADSET,
TYPE_BLE_SPEAKER,
- TYPE_ECHO_REFERENCE}
+ TYPE_ECHO_REFERENCE,
+ TYPE_BLE_BROADCAST}
)
@Retention(RetentionPolicy.SOURCE)
public @interface AudioDeviceType {}
@@ -264,7 +270,8 @@
TYPE_HEARING_AID,
TYPE_BUILTIN_SPEAKER_SAFE,
TYPE_BLE_HEADSET,
- TYPE_BLE_SPEAKER}
+ TYPE_BLE_SPEAKER,
+ TYPE_BLE_BROADCAST}
)
@Retention(RetentionPolicy.SOURCE)
public @interface AudioDeviceTypeOut {}
@@ -296,6 +303,7 @@
case TYPE_BUILTIN_SPEAKER_SAFE:
case TYPE_BLE_HEADSET:
case TYPE_BLE_SPEAKER:
+ case TYPE_BLE_BROADCAST:
return true;
default:
return false;
@@ -636,6 +644,7 @@
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_HEADSET, TYPE_BLE_HEADSET);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_SPEAKER, TYPE_BLE_SPEAKER);
+ INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_BROADCAST, TYPE_BLE_BROADCAST);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, TYPE_BUILTIN_MIC);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, TYPE_BLUETOOTH_SCO);
@@ -690,6 +699,7 @@
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_REMOTE_SUBMIX, AudioSystem.DEVICE_OUT_REMOTE_SUBMIX);
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_HEADSET, AudioSystem.DEVICE_OUT_BLE_HEADSET);
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_SPEAKER, AudioSystem.DEVICE_OUT_BLE_SPEAKER);
+ EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_BROADCAST, AudioSystem.DEVICE_OUT_BLE_BROADCAST);
// privileges mapping to input device
EXT_TO_INT_INPUT_DEVICE_MAPPING = new SparseIntArray();
diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java
index ebe0882..9211c53 100644
--- a/media/java/android/media/AudioDevicePort.java
+++ b/media/java/android/media/AudioDevicePort.java
@@ -90,7 +90,8 @@
* {@link AudioManager#DEVICE_OUT_BLE_HEADSET}, {@link AudioManager#DEVICE_OUT_BLE_SPEAKER})
* use the MAC address of the bluetooth device in the form "00:11:22:AA:BB:CC" as reported by
* {@link BluetoothDevice#getAddress()}.
- * - Deivces that do not have an address will indicate an empty string "".
+ * - Bluetooth LE broadcast group ({@link AudioManager#DEVICE_OUT_BLE_BROADCAST} use the group number.
+ * - Devices that do not have an address will indicate an empty string "".
*/
public String address() {
return mAddress;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 68e5d94..c4cef4c 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -5440,6 +5440,10 @@
*/
public static final int DEVICE_OUT_BLE_SPEAKER = AudioSystem.DEVICE_OUT_BLE_SPEAKER;
/** @hide
+ * The audio output device code for a BLE audio brodcast group.
+ */
+ public static final int DEVICE_OUT_BLE_BROADCAST = AudioSystem.DEVICE_OUT_BLE_BROADCAST;
+ /** @hide
* This is not used as a returned value from {@link #getDevicesForStream}, but could be
* used in the future in a set method to select whatever default device is chosen by the
* platform-specific implementation.
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 50bf1e5..306479a 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -989,6 +989,8 @@
public static final int DEVICE_OUT_BLE_HEADSET = 0x20000000;
/** @hide */
public static final int DEVICE_OUT_BLE_SPEAKER = 0x20000001;
+ /** @hide */
+ public static final int DEVICE_OUT_BLE_BROADCAST = 0x20000002;
/** @hide */
public static final int DEVICE_OUT_DEFAULT = DEVICE_BIT_DEFAULT;
@@ -1049,6 +1051,7 @@
DEVICE_OUT_ALL_SET.add(DEVICE_OUT_ECHO_CANCELLER);
DEVICE_OUT_ALL_SET.add(DEVICE_OUT_BLE_HEADSET);
DEVICE_OUT_ALL_SET.add(DEVICE_OUT_BLE_SPEAKER);
+ DEVICE_OUT_ALL_SET.add(DEVICE_OUT_BLE_BROADCAST);
DEVICE_OUT_ALL_SET.add(DEVICE_OUT_DEFAULT);
DEVICE_OUT_ALL_A2DP_SET = new HashSet<>();
@@ -1079,6 +1082,7 @@
DEVICE_OUT_ALL_BLE_SET = new HashSet<>();
DEVICE_OUT_ALL_BLE_SET.add(DEVICE_OUT_BLE_HEADSET);
DEVICE_OUT_ALL_BLE_SET.add(DEVICE_OUT_BLE_SPEAKER);
+ DEVICE_OUT_ALL_BLE_SET.add(DEVICE_OUT_BLE_BROADCAST);
}
// input devices
@@ -1262,6 +1266,7 @@
/** @hide */ public static final String DEVICE_OUT_ECHO_CANCELLER_NAME = "echo_canceller";
/** @hide */ public static final String DEVICE_OUT_BLE_HEADSET_NAME = "ble_headset";
/** @hide */ public static final String DEVICE_OUT_BLE_SPEAKER_NAME = "ble_speaker";
+ /** @hide */ public static final String DEVICE_OUT_BLE_BROADCAST_NAME = "ble_broadcast";
/** @hide */ public static final String DEVICE_IN_COMMUNICATION_NAME = "communication";
/** @hide */ public static final String DEVICE_IN_AMBIENT_NAME = "ambient";
@@ -1361,6 +1366,8 @@
return DEVICE_OUT_BLE_HEADSET_NAME;
case DEVICE_OUT_BLE_SPEAKER:
return DEVICE_OUT_BLE_SPEAKER_NAME;
+ case DEVICE_OUT_BLE_BROADCAST:
+ return DEVICE_OUT_BLE_BROADCAST_NAME;
case DEVICE_OUT_DEFAULT:
default:
return "0x" + Integer.toHexString(device);
diff --git a/media/java/android/media/tv/AitInfo.java b/media/java/android/media/tv/AitInfo.java
index 71b1634..8e80a62 100644
--- a/media/java/android/media/tv/AitInfo.java
+++ b/media/java/android/media/tv/AitInfo.java
@@ -23,7 +23,6 @@
/**
* AIT (Application Information Table) info.
- * @hide
*/
public final class AitInfo implements Parcelable {
static final String TAG = "AitInfo";
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 9bc7367..1a9cab0 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -592,7 +592,11 @@
});
}
- /** @hide */
+ /**
+ * Informs the application that this session has been tuned to the given channel.
+ *
+ * @param channelUri The URI of the tuned channel.
+ */
public void notifyTuned(@NonNull Uri channelUri) {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@@ -947,12 +951,11 @@
/**
* Informs the app that the AIT (Application Information Table) is updated.
*
- * <p>This method should also be call when
+ * <p>This method should also be called when
* {@link #onSetInteractiveAppNotificationEnabled(boolean)} is called to send the first AIT
* info.
*
* @see #onSetInteractiveAppNotificationEnabled(boolean)
- * @hide
*/
public void notifyAitInfoUpdated(@NonNull final AitInfo aitInfo) {
executeOrPostRunnableOnMainThread(new Runnable() {
@@ -1214,7 +1217,6 @@
*
* @see TvView#setInteractiveAppNotificationEnabled(boolean)
* @see Session#notifyAitInfoUpdated(android.media.tv.AitInfo)
- * @hide
*/
public void onSetInteractiveAppNotificationEnabled(boolean enabled) {
}
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index d2086c5..6c25a70 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -493,7 +493,6 @@
*
* @see TvInputService.Session#notifyAitInfoUpdated(android.media.tv.AitInfo)
* @see android.media.tv.interactive.TvInteractiveAppView#setTvView(TvView)
- * @hide
*/
public void setInteractiveAppNotificationEnabled(boolean enabled) {
if (mSession != null) {
@@ -1074,7 +1073,6 @@
* This is called when the AIT (Application Information Table) info has been updated.
*
* @param aitInfo The current AIT info.
- * @hide
*/
public void onAitInfoUpdated(@NonNull String inputId, @NonNull AitInfo aitInfo) {
}
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 39be501..c5dfaa2 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -57,7 +57,7 @@
/**
* Central system API to the overall TV interactive application framework (TIAF) architecture, which
- * arbitrates interaction between applications and interactive apps.
+ * arbitrates interaction between Android applications and TV interactive apps.
*/
@SystemService(Context.TV_INTERACTIVE_APP_SERVICE)
public final class TvInteractiveAppManager {
@@ -75,22 +75,22 @@
/**
* Unrealized state of interactive app service.
- * @hide
*/
public static final int SERVICE_STATE_UNREALIZED = 1;
/**
* Preparing state of interactive app service.
- * @hide
*/
public static final int SERVICE_STATE_PREPARING = 2;
/**
* Ready state of interactive app service.
- * @hide
+ *
+ * <p>In this state, the interactive app service is ready, and interactive apps can be started.
+ *
+ * @see TvInteractiveAppView#startInteractiveApp()
*/
public static final int SERVICE_STATE_READY = 3;
/**
* Error state of interactive app service.
- * @hide
*/
public static final int SERVICE_STATE_ERROR = 4;
@@ -105,17 +105,14 @@
/**
* Stopped (or not started) state of interactive application.
- * @hide
*/
public static final int INTERACTIVE_APP_STATE_STOPPED = 1;
/**
* Running state of interactive application.
- * @hide
*/
public static final int INTERACTIVE_APP_STATE_RUNNING = 2;
/**
* Error state of interactive application.
- * @hide
*/
public static final int INTERACTIVE_APP_STATE_ERROR = 3;
@@ -136,46 +133,37 @@
/**
* No error.
- * @hide
*/
public static final int ERROR_NONE = 0;
/**
* Unknown error code.
- * @hide
*/
public static final int ERROR_UNKNOWN = 1;
/**
* Error code for an unsupported channel.
- * @hide
*/
public static final int ERROR_NOT_SUPPORTED = 2;
/**
* Error code for weak signal.
- * @hide
*/
public static final int ERROR_WEAK_SIGNAL = 3;
/**
* Error code when resource (e.g. tuner) is unavailable.
- * @hide
*/
public static final int ERROR_RESOURCE_UNAVAILABLE = 4;
/**
* Error code for blocked contents.
- * @hide
*/
public static final int ERROR_BLOCKED = 5;
/**
* Error code when the key or module is missing for the encrypted channel.
- * @hide
*/
public static final int ERROR_ENCRYPTED = 6;
/**
* Error code when the current channel is an unknown channel.
- * @hide
*/
public static final int ERROR_UNKNOWN_CHANNEL = 7;
-
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = false, prefix = "TELETEXT_APP_STATE_", value = {
@@ -561,7 +549,6 @@
/**
* Callback used to monitor status of the TV Interactive App.
- * @hide
*/
public abstract static class TvInteractiveAppCallback {
/**
@@ -796,7 +783,6 @@
*
* @param callback A callback used to monitor status of the TV Interactive App services.
* @param executor A {@link Executor} that the status change will be delivered to.
- * @hide
*/
public void registerCallback(
@NonNull TvInteractiveAppCallback callback,
@@ -812,7 +798,6 @@
* Unregisters the existing {@link TvInteractiveAppCallback}.
*
* @param callback The existing callback to remove.
- * @hide
*/
public void unregisterCallback(@NonNull final TvInteractiveAppCallback callback) {
Preconditions.checkNotNull(callback);
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index d599d0a..afa1ff7 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -34,6 +34,7 @@
import android.media.tv.TvContentRating;
import android.media.tv.TvInputManager;
import android.media.tv.TvTrackInfo;
+import android.media.tv.TvView;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -65,7 +66,8 @@
import java.util.List;
/**
- * The TvInteractiveAppService class represents a TV interactive applications RTE.
+ * A TV interactive application service is a service that provides runtime environment and runs TV
+ * interactive applications.
*/
public abstract class TvInteractiveAppService extends Service {
private static final boolean DEBUG = false;
@@ -201,10 +203,8 @@
/**
* Prepares TV Interactive App service for the given type.
- * @hide
*/
- public void onPrepare(@TvInteractiveAppInfo.InteractiveAppType int type) {
- }
+ public abstract void onPrepare(@TvInteractiveAppInfo.InteractiveAppType int type);
/**
* Registers App link info.
@@ -239,14 +239,11 @@
*
* @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
- public Session onCreateSession(
+ public abstract Session onCreateSession(
@NonNull String iAppServiceId,
- @TvInteractiveAppInfo.InteractiveAppType int type) {
- return null;
- }
+ @TvInteractiveAppInfo.InteractiveAppType int type);
/**
* Notifies the system when the state of the interactive app RTE has been changed.
@@ -256,7 +253,6 @@
* @param error the error code for error state. {@link TvInteractiveAppManager#ERROR_NONE} is
* used when the state is not
* {@link TvInteractiveAppManager#SERVICE_STATE_ERROR}.
- * @hide
*/
public final void notifyStateChanged(
@TvInteractiveAppInfo.InteractiveAppType int type,
@@ -272,7 +268,12 @@
/**
* Base class for derived classes to implement to provide a TV interactive app session.
- * @hide
+ *
+ * <p>A session is associated with a {@link TvInteractiveAppView} instance and handles
+ * corresponding communications. It also handles the communications with
+ * {@link android.media.tv.TvInputService.Session} if connected.
+ *
+ * @see TvInteractiveAppView#setTvView(TvView)
*/
public abstract static class Session implements KeyEvent.Callback {
private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
@@ -359,8 +360,11 @@
/**
* Creates broadcast-independent(BI) interactive application.
*
+ * <p>The implementation should call {@link #notifyBiInteractiveAppCreated(Uri, String)},
+ * no matter if it's created successfully or not.
+ *
+ * @see #notifyBiInteractiveAppCreated(Uri, String)
* @see #onDestroyBiInteractiveApp(String)
- * @hide
*/
public void onCreateBiInteractiveApp(@NonNull Uri biIAppUri, @Nullable Bundle params) {
}
@@ -370,10 +374,9 @@
* Destroys broadcast-independent(BI) interactive application.
*
* @param biIAppId the BI interactive app ID from
- * {@link #createBiInteractiveApp(Uri, Bundle)}
+ * {@link #onCreateBiInteractiveApp(Uri, Bundle)}}
*
* @see #onCreateBiInteractiveApp(Uri, Bundle)
- * @hide
*/
public void onDestroyBiInteractiveApp(@NonNull String biIAppId) {
}
@@ -482,7 +485,8 @@
/**
* Called when the corresponding TV input tuned to a channel.
- * @hide
+ *
+ * @param channelUri The tuned channel URI.
*/
public void onTuned(@NonNull Uri channelUri) {
}
@@ -1046,11 +1050,14 @@
/**
* Notifies the broadcast-independent(BI) interactive application has been created.
+ *
* @param biIAppId BI interactive app ID, which can be used to destroy the BI interactive
- * app.
- * @hide
+ * app. {@code null} if it's not created successfully.
+ *
+ * @see #onCreateBiInteractiveApp(Uri, Bundle)
*/
- public final void notifyBiInteractiveAppCreated(Uri biIAppUri, String biIAppId) {
+ public final void notifyBiInteractiveAppCreated(
+ @NonNull Uri biIAppUri, @Nullable String biIAppId) {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@Override
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 12e2199..2922bae 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -387,7 +387,6 @@
* {@link TvInteractiveAppInfo#getId()}.
*
* @see android.media.tv.interactive.TvInteractiveAppManager#getTvInteractiveAppServiceList()
- * @hide
*/
public void prepareInteractiveApp(
@NonNull String iAppServiceId,
@@ -416,7 +415,6 @@
/**
* Stops the interactive application.
- * @hide
*/
public void stopInteractiveApp() {
if (DEBUG) {
@@ -524,8 +522,10 @@
/**
* Creates broadcast-independent(BI) interactive application.
*
- * @see #destroyBiInteractiveApp(String)
- * @hide
+ * <p>{@link TvInteractiveAppCallback#onBiInteractiveAppCreated(String, Uri, String)} will be
+ * called for the result.
+ *
+ * @see TvInteractiveAppCallback#onBiInteractiveAppCreated(String, Uri, String)
*/
public void createBiInteractiveApp(@NonNull Uri biIAppUri, @Nullable Bundle params) {
if (DEBUG) {
@@ -542,7 +542,6 @@
* @param biIAppId the BI interactive app ID from {@link #createBiInteractiveApp(Uri, Bundle)}
*
* @see #createBiInteractiveApp(Uri, Bundle)
- * @hide
*/
public void destroyBiInteractiveApp(@NonNull String biIAppId) {
if (DEBUG) {
@@ -564,7 +563,6 @@
*
* @param tvView the TvView to be linked to this TvInteractiveAppView via linking of Sessions.
* @return The result of the operation.
- * @hide
*/
public int setTvView(@Nullable TvView tvView) {
if (tvView == null) {
@@ -623,14 +621,13 @@
}
/**
- * This is called when the session state is changed.
+ * This is called when the state of corresponding interactive app is changed.
*
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
* @param state the current state.
* @param err the error code for error state. {@link TvInteractiveAppManager#ERROR_NONE}
* is used when the state is not
* {@link TvInteractiveAppManager#INTERACTIVE_APP_STATE_ERROR}.
- * @hide
*/
public void onStateChanged(
@NonNull String iAppServiceId,
@@ -643,10 +640,12 @@
*
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
* @param biIAppUri URI associated this BI interactive app. This is the same URI in
- * {@link Session#createBiInteractiveApp(Uri, Bundle)}
+ * {@link #createBiInteractiveApp(Uri, Bundle)}
* @param biIAppId BI interactive app ID, which can be used to destroy the BI interactive
- * app.
- * @hide
+ * app. {@code null} if it's not created successfully.
+ *
+ * @see #createBiInteractiveApp(Uri, Bundle)
+ * @see #destroyBiInteractiveApp(String)
*/
public void onBiInteractiveAppCreated(@NonNull String iAppServiceId, @NonNull Uri biIAppUri,
@Nullable String biIAppId) {
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 3157375..be114d4 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -47,6 +47,7 @@
import android.media.tv.tuner.frontend.FrontendSettings;
import android.media.tv.tuner.frontend.FrontendStatus;
import android.media.tv.tuner.frontend.FrontendStatus.FrontendStatusType;
+import android.media.tv.tuner.frontend.FrontendStatusReadiness;
import android.media.tv.tuner.frontend.OnTuneEventListener;
import android.media.tv.tuner.frontend.ScanCallback;
import android.media.tv.tunerresourcemanager.ResourceClientProfile;
@@ -61,9 +62,7 @@
import android.os.Message;
import android.os.Process;
import android.util.Log;
-
import com.android.internal.util.FrameworkStatsLog;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -1005,6 +1004,7 @@
private native int nativeRemoveOutputPid(int pid);
private native Lnb nativeOpenLnbByHandle(int handle);
private native Lnb nativeOpenLnbByName(String name);
+ private native FrontendStatusReadiness[] nativeGetFrontendStatusReadiness(int[] statusTypes);
private native Descrambler nativeOpenDescramblerByHandle(int handle);
private native int nativeOpenDemuxByhandle(int handle);
@@ -1595,6 +1595,38 @@
}
/**
+ * Gets Frontend Status Readiness statuses for given status types.
+ *
+ * <p>This API is only supported by Tuner HAL 2.0 or higher. Unsupported versions would cause
+ * no-op. Use {@link TunerVersionChecker#getTunerVersion()} to check the version.
+ *
+ * @param statusTypes an array of status types.
+ *
+ * @return an array of current readiness states. {@code null} if the operation failed or
+ * unsupported versions.
+ * @throws IllegalStateException if there is no active frontend currently.
+ */
+ @Nullable
+ @SuppressLint("ArrayReturn")
+ @SuppressWarnings("NullableCollection")
+ public FrontendStatusReadiness[] getFrontendStatusReadiness(
+ @NonNull @FrontendStatusType int[] statusTypes) {
+ mFrontendLock.lock();
+ try {
+ if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_2_0, "Remove output PID")) {
+ return null;
+ }
+ if (mFrontend == null) {
+ throw new IllegalStateException("frontend is not initialized");
+ }
+ return nativeGetFrontendStatusReadiness(statusTypes);
+ } finally {
+ mFrontendLock.unlock();
+ }
+ }
+
+ /**
* Gets the currently initialized and activated frontend information. To get all the available
* frontend info on the device, use {@link getAvailableFrontendInfos()}.
*
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java b/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java
new file mode 100644
index 0000000..52527b3
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.media.tv.tuner.frontend.FrontendStatus.FrontendStatusType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A class contains the Frontend Status Readiness of a given type.
+ *
+ * @hide
+ */
+@SystemApi
+public class FrontendStatusReadiness {
+ /** @hide */
+ @IntDef({FRONTEND_STATUS_READINESS_UNDEFINED, FRONTEND_STATUS_READINESS_UNAVAILABLE,
+ FRONTEND_STATUS_READINESS_UNSTABLE, FRONTEND_STATUS_READINESS_STABLE,
+ FRONTEND_STATUS_READINESS_UNSUPPORTED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Readiness {}
+
+ /**
+ * The FrontendStatus readiness status for the given FrontendStatusType is undefined.
+ */
+ public static final int FRONTEND_STATUS_READINESS_UNDEFINED =
+ android.hardware.tv.tuner.FrontendStatusReadiness.UNDEFINED;
+
+ /**
+ * The FrontendStatus for the given FrontendStatusType is currently unavailable.
+ */
+ public static final int FRONTEND_STATUS_READINESS_UNAVAILABLE =
+ android.hardware.tv.tuner.FrontendStatusReadiness.UNAVAILABLE;
+
+ /**
+ * The FrontendStatus for the given FrontendStatusType is ready to read, but it’s unstable.
+ */
+ public static final int FRONTEND_STATUS_READINESS_UNSTABLE =
+ android.hardware.tv.tuner.FrontendStatusReadiness.UNSTABLE;
+
+ /**
+ * The FrontendStatus for the given FrontendStatusType is ready to read, and it’s stable.
+ */
+ public static final int FRONTEND_STATUS_READINESS_STABLE =
+ android.hardware.tv.tuner.FrontendStatusReadiness.STABLE;
+
+ /**
+ * The FrontendStatus for the given FrontendStatusType is not supported.
+ */
+ public static final int FRONTEND_STATUS_READINESS_UNSUPPORTED =
+ android.hardware.tv.tuner.FrontendStatusReadiness.UNSUPPORTED;
+
+ @FrontendStatusType private int mFrontendStatusType;
+ @Readiness private int mStatusReadiness;
+
+ private FrontendStatusReadiness(int type, int readiness) {
+ mFrontendStatusType = type;
+ mStatusReadiness = readiness;
+ }
+
+ /**
+ * Gets the frontend status type.
+ */
+ @FrontendStatusType
+ public int getStatusType() {
+ return mFrontendStatusType;
+ }
+ /**
+ * Gets the frontend status readiness.
+ */
+ @Readiness
+ public int getStatusReadiness() {
+ return mStatusReadiness;
+ }
+}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 41f3a67..68dd8d0 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -1631,6 +1631,36 @@
return (jint)mFeClient->removeOutputPid(pid);
}
+jobjectArray JTuner::getFrontendStatusReadiness(jintArray types) {
+ if (mFeClient == nullptr) {
+ ALOGE("frontend is not initialized");
+ return nullptr;
+ }
+
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jsize size = env->GetArrayLength(types);
+ jint intTypes[size];
+ env->GetIntArrayRegion(types, 0, size, intTypes);
+ std::vector<FrontendStatusType> v;
+ for (int i = 0; i < size; i++) {
+ v.push_back(static_cast<FrontendStatusType>(intTypes[i]));
+ }
+
+ vector<FrontendStatusReadiness> readiness = mFeClient->getStatusReadiness(v);
+ if (readiness.size() < size) {
+ return nullptr;
+ }
+
+ jclass clazz = env->FindClass("android/media/tv/tuner/frontend/FrontendStatusReadiness");
+ jmethodID init = env->GetMethodID(clazz, "<init>", "(II)V");
+ jobjectArray valObj = env->NewObjectArray(size, clazz, nullptr);
+ for (int i = 0; i < size; i++) {
+ jobject readinessObj = env->NewObject(clazz, init, intTypes[i], readiness[i]);
+ env->SetObjectArrayElement(valObj, i, readinessObj);
+ }
+ return valObj;
+}
+
jobject JTuner::openLnbByHandle(int handle) {
if (mTunerClient == nullptr) {
return nullptr;
@@ -4389,6 +4419,12 @@
return tuner->removeOutputPid(pid);
}
+static jobjectArray android_media_tv_Tuner_get_frontend_status_readiness(JNIEnv *env, jobject thiz,
+ jintArray types) {
+ sp<JTuner> tuner = getTuner(env, thiz);
+ return tuner->getFrontendStatusReadiness(types);
+}
+
static jint android_media_tv_Tuner_close_frontend(JNIEnv* env, jobject thiz, jint /* handle */) {
sp<JTuner> tuner = getTuner(env, thiz);
return tuner->closeFrontend();
@@ -4710,6 +4746,9 @@
(void *)android_media_tv_Tuner_get_maximum_frontends },
{ "nativeRemoveOutputPid", "(I)I",
(void *)android_media_tv_Tuner_remove_output_pid },
+ { "nativeGetFrontendStatusReadiness",
+ "([I)[Landroid/media/tv/tuner/frontend/FrontendStatusReadiness;",
+ (void *)android_media_tv_Tuner_get_frontend_status_readiness },
};
static const JNINativeMethod gFilterMethods[] = {
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index e9475dc..03e7fa9 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -206,6 +206,7 @@
jint setMaxNumberOfFrontends(int32_t frontendType, int32_t maxNumber);
int32_t getMaxNumberOfFrontends(int32_t frontendType);
jint removeOutputPid(int32_t pid);
+ jobjectArray getFrontendStatusReadiness(jintArray types);
jweak getObject();
diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp
index bea0342..c6337ec 100644
--- a/media/jni/tuner/FrontendClient.cpp
+++ b/media/jni/tuner/FrontendClient.cpp
@@ -152,6 +152,16 @@
return Result::INVALID_STATE;
}
+vector<FrontendStatusReadiness> FrontendClient::getStatusReadiness(
+ const std::vector<FrontendStatusType>& statusTypes) {
+ vector<FrontendStatusReadiness> readiness;
+ if (mTunerFrontend != nullptr) {
+ mTunerFrontend->getFrontendStatusReadiness(statusTypes, &readiness);
+ }
+
+ return readiness;
+}
+
shared_ptr<ITunerFrontend> FrontendClient::getAidlFrontend() {
return mTunerFrontend;
}
diff --git a/media/jni/tuner/FrontendClient.h b/media/jni/tuner/FrontendClient.h
index c6838c8..85f6d56 100644
--- a/media/jni/tuner/FrontendClient.h
+++ b/media/jni/tuner/FrontendClient.h
@@ -35,6 +35,7 @@
using ::aidl::android::hardware::tv::tuner::FrontendScanType;
using ::aidl::android::hardware::tv::tuner::FrontendSettings;
using ::aidl::android::hardware::tv::tuner::FrontendStatus;
+using ::aidl::android::hardware::tv::tuner::FrontendStatusReadiness;
using ::aidl::android::hardware::tv::tuner::FrontendStatusType;
using ::aidl::android::hardware::tv::tuner::FrontendType;
using ::aidl::android::hardware::tv::tuner::Result;
@@ -125,6 +126,12 @@
*/
Result removeOutputPid(int32_t pid);
+ /**
+ * Gets Frontend Status Readiness statuses for given status types.
+ */
+ vector<FrontendStatusReadiness> getStatusReadiness(
+ const std::vector<FrontendStatusType>& types);
+
int32_t getId();
shared_ptr<ITunerFrontend> getAidlFrontend();
diff --git a/native/android/choreographer.cpp b/native/android/choreographer.cpp
index fbd4b2e..e22580d 100644
--- a/native/android/choreographer.cpp
+++ b/native/android/choreographer.cpp
@@ -67,7 +67,7 @@
const AChoreographerFrameCallbackData* data) {
return AChoreographerFrameCallbackData_routeGetPreferredFrameTimelineIndex(data);
}
-int64_t AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
+AVsyncId AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
const AChoreographerFrameCallbackData* data, size_t index) {
return AChoreographerFrameCallbackData_routeGetFrameTimelineVsyncId(data, index);
}
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 5b19102..d01a30e 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -661,7 +661,7 @@
}
void ASurfaceTransaction_setFrameTimeline(ASurfaceTransaction* aSurfaceTransaction,
- int64_t vsyncId) {
+ AVsyncId vsyncId) {
CHECK_NOT_NULL(aSurfaceTransaction);
// TODO(b/210043506): Get start time from platform.
ASurfaceTransaction_to_Transaction(aSurfaceTransaction)
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 8813f98..28f930f 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -21,6 +21,7 @@
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -39,14 +40,11 @@
import android.net.NetworkStateSnapshot;
import android.net.NetworkTemplate;
import android.net.UnderlyingNetworkInfo;
+import android.net.netstats.IUsageCallback;
import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.net.netstats.provider.NetworkStatsProvider;
-import android.os.Binder;
import android.os.Build;
import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
import android.os.RemoteException;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -57,6 +55,7 @@
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.Executor;
/**
* Provides access to network usage history and statistics. Usage data is collected in
@@ -723,26 +722,36 @@
}
}
- /** @hide */
- public void registerUsageCallback(NetworkTemplate template, int networkType,
- long thresholdBytes, UsageCallback callback, @Nullable Handler handler) {
+ /**
+ * Registers to receive notifications about data usage on specified networks.
+ *
+ * <p>The callbacks will continue to be called as long as the process is alive or
+ * {@link #unregisterUsageCallback} is called.
+ *
+ * @param template Template used to match networks. See {@link NetworkTemplate}.
+ * @param thresholdBytes Threshold in bytes to be notified on. The provided value that lower
+ * than 2MiB will be clamped for non-privileged callers.
+ * @param executor The executor on which callback will be invoked. The provided {@link Executor}
+ * must run callback sequentially, otherwise the order of callbacks cannot be
+ * guaranteed.
+ * @param callback The {@link UsageCallback} that the system will call when data usage
+ * has exceeded the specified threshold.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public void registerUsageCallback(@NonNull NetworkTemplate template, long thresholdBytes,
+ @NonNull @CallbackExecutor Executor executor, @NonNull UsageCallback callback) {
+ Objects.requireNonNull(template, "NetworkTemplate cannot be null");
Objects.requireNonNull(callback, "UsageCallback cannot be null");
+ Objects.requireNonNull(executor, "Executor cannot be null");
- final Looper looper;
- if (handler == null) {
- looper = Looper.myLooper();
- } else {
- looper = handler.getLooper();
- }
-
- DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET,
+ final DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET,
template, thresholdBytes);
try {
- CallbackHandler callbackHandler = new CallbackHandler(looper, networkType,
- template.getSubscriberId(), callback);
+ final UsageCallbackWrapper callbackWrapper =
+ new UsageCallbackWrapper(executor, callback);
callback.request = mService.registerUsageCallback(
- mContext.getOpPackageName(), request, new Messenger(callbackHandler),
- new Binder());
+ mContext.getOpPackageName(), request, callbackWrapper);
if (DBG) Log.d(TAG, "registerUsageCallback returned " + callback.request);
if (callback.request == null) {
@@ -795,12 +804,15 @@
NetworkTemplate template = createTemplate(networkType, subscriberId);
if (DBG) {
Log.d(TAG, "registerUsageCallback called with: {"
- + " networkType=" + networkType
- + " subscriberId=" + subscriberId
- + " thresholdBytes=" + thresholdBytes
- + " }");
+ + " networkType=" + networkType
+ + " subscriberId=" + subscriberId
+ + " thresholdBytes=" + thresholdBytes
+ + " }");
}
- registerUsageCallback(template, networkType, thresholdBytes, callback, handler);
+
+ final Executor executor = handler == null ? r -> r.run() : r -> handler.post(r);
+
+ registerUsageCallback(template, thresholdBytes, executor, callback);
}
/**
@@ -825,6 +837,26 @@
* Base class for usage callbacks. Should be extended by applications wanting notifications.
*/
public static abstract class UsageCallback {
+ /**
+ * Called when data usage has reached the given threshold.
+ *
+ * Called by {@code NetworkStatsService} when the registered threshold is reached.
+ * If a caller implements {@link #onThresholdReached(NetworkTemplate)}, the system
+ * will not call {@link #onThresholdReached(int, String)}.
+ *
+ * @param template The {@link NetworkTemplate} that associated with this callback.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public void onThresholdReached(@NonNull NetworkTemplate template) {
+ // Backward compatibility for those who didn't override this function.
+ final int networkType = networkTypeForTemplate(template);
+ if (networkType != ConnectivityManager.TYPE_NONE) {
+ final String subscriberId = template.getSubscriberIds().isEmpty() ? null
+ : template.getSubscriberIds().iterator().next();
+ onThresholdReached(networkType, subscriberId);
+ }
+ }
/**
* Called when data usage has reached the given threshold.
@@ -835,6 +867,25 @@
* @hide used for internal bookkeeping
*/
private DataUsageRequest request;
+
+ /**
+ * Get network type from a template if feasible.
+ *
+ * @param template the target {@link NetworkTemplate}.
+ * @return legacy network type, only supports for the types which is already supported in
+ * {@link #registerUsageCallback(int, String, long, UsageCallback, Handler)}.
+ * {@link ConnectivityManager#TYPE_NONE} for other types.
+ */
+ private static int networkTypeForTemplate(@NonNull NetworkTemplate template) {
+ switch (template.getMatchRule()) {
+ case NetworkTemplate.MATCH_MOBILE:
+ return ConnectivityManager.TYPE_MOBILE;
+ case NetworkTemplate.MATCH_WIFI:
+ return ConnectivityManager.TYPE_WIFI;
+ default:
+ return ConnectivityManager.TYPE_NONE;
+ }
+ }
}
/**
@@ -953,43 +1004,32 @@
}
}
- private static class CallbackHandler extends Handler {
- private final int mNetworkType;
- private final String mSubscriberId;
- private UsageCallback mCallback;
+ private static class UsageCallbackWrapper extends IUsageCallback.Stub {
+ // Null if unregistered.
+ private volatile UsageCallback mCallback;
- CallbackHandler(Looper looper, int networkType, String subscriberId,
- UsageCallback callback) {
- super(looper);
- mNetworkType = networkType;
- mSubscriberId = subscriberId;
+ private final Executor mExecutor;
+
+ UsageCallbackWrapper(@NonNull Executor executor, @NonNull UsageCallback callback) {
mCallback = callback;
+ mExecutor = executor;
}
@Override
- public void handleMessage(Message message) {
- DataUsageRequest request =
- (DataUsageRequest) getObject(message, DataUsageRequest.PARCELABLE_KEY);
-
- switch (message.what) {
- case CALLBACK_LIMIT_REACHED: {
- if (mCallback != null) {
- mCallback.onThresholdReached(mNetworkType, mSubscriberId);
- } else {
- Log.e(TAG, "limit reached with released callback for " + request);
- }
- break;
- }
- case CALLBACK_RELEASED: {
- if (DBG) Log.d(TAG, "callback released for " + request);
- mCallback = null;
- break;
- }
+ public void onThresholdReached(DataUsageRequest request) {
+ // Copy it to a local variable in case mCallback changed inside the if condition.
+ final UsageCallback callback = mCallback;
+ if (callback != null) {
+ mExecutor.execute(() -> callback.onThresholdReached(request.template));
+ } else {
+ Log.e(TAG, "onThresholdReached with released callback for " + request);
}
}
- private static Object getObject(Message msg, String key) {
- return msg.getData().getParcelable(key);
+ @Override
+ public void onCallbackReleased(DataUsageRequest request) {
+ if (DBG) Log.d(TAG, "callback released for " + request);
+ mCallback = null;
}
}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl
index da0aa99..efe626d 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl
@@ -24,6 +24,7 @@
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.net.UnderlyingNetworkInfo;
+import android.net.netstats.IUsageCallback;
import android.net.netstats.provider.INetworkStatsProvider;
import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.os.IBinder;
@@ -71,7 +72,7 @@
/** Registers a callback on data usage. */
DataUsageRequest registerUsageCallback(String callingPackage,
- in DataUsageRequest request, in Messenger messenger, in IBinder binder);
+ in DataUsageRequest request, in IUsageCallback callback);
/** Unregisters a callback on data usage. */
void unregisterUsageRequest(in DataUsageRequest request);
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
index 58ca21f..735c44d 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
@@ -16,6 +16,7 @@
package android.net;
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
import static android.net.NetworkStats.IFACE_ALL;
@@ -34,6 +35,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.net.NetworkStatsHistory.Entry;
import android.os.Binder;
import android.service.NetworkStatsCollectionKeyProto;
import android.service.NetworkStatsCollectionProto;
@@ -71,6 +74,8 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -80,7 +85,7 @@
*
* @hide
*/
-// @SystemApi(client = MODULE_LIBRARIES)
+@SystemApi(client = MODULE_LIBRARIES)
public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.Writer {
private static final String TAG = NetworkStatsCollection.class.getSimpleName();
/** File header magic number: "ANET" */
@@ -810,6 +815,71 @@
}
/**
+ * Get the all historical stats of the collection {@link NetworkStatsCollection}.
+ *
+ * @return All {@link NetworkStatsHistory} in this collection.
+ */
+ @NonNull
+ public Map<Key, NetworkStatsHistory> getEntries() {
+ return new ArrayMap(mStats);
+ }
+
+ /**
+ * Builder class for {@link NetworkStatsCollection}.
+ */
+ public static final class Builder {
+ private final long mBucketDuration;
+ private final ArrayMap<Key, NetworkStatsHistory> mEntries = new ArrayMap<>();
+
+ /**
+ * Creates a new Builder with given bucket duration.
+ *
+ * @param bucketDuration Duration of the buckets of the object, in milliseconds.
+ */
+ public Builder(long bucketDuration) {
+ mBucketDuration = bucketDuration;
+ }
+
+ /**
+ * Add association of the history with the specified key in this map.
+ *
+ * @param key The object used to identify a network, see {@link Key}.
+ * @param history {@link NetworkStatsHistory} instance associated to the given {@link Key}.
+ * @return The builder object.
+ */
+ @NonNull
+ public NetworkStatsCollection.Builder addEntry(@NonNull Key key,
+ @NonNull NetworkStatsHistory history) {
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(history);
+ final List<Entry> historyEntries = history.getEntries();
+
+ final NetworkStatsHistory.Builder historyBuilder =
+ new NetworkStatsHistory.Builder(mBucketDuration, historyEntries.size());
+ for (Entry entry : historyEntries) {
+ historyBuilder.addEntry(entry);
+ }
+
+ mEntries.put(key, historyBuilder.build());
+ return this;
+ }
+
+ /**
+ * Builds the instance of the {@link NetworkStatsCollection}.
+ *
+ * @return the built instance of {@link NetworkStatsCollection}.
+ */
+ @NonNull
+ public NetworkStatsCollection build() {
+ final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
+ for (int i = 0; i < mEntries.size(); i++) {
+ collection.recordHistory(mEntries.keyAt(i), mEntries.valueAt(i));
+ }
+ return collection;
+ }
+ }
+
+ /**
* the identifier that associate with the {@link NetworkStatsHistory} object to identify
* a certain record in the {@link NetworkStatsCollection} object.
*/
diff --git a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
index c803a72..c2f0cdf 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
@@ -16,6 +16,8 @@
package android.net;
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
@@ -29,6 +31,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.RemoteException;
+import android.util.Log;
import com.android.server.NetworkManagementSocketTagger;
@@ -210,10 +213,29 @@
}
final NetworkStatsManager statsManager =
context.getSystemService(NetworkStatsManager.class);
+ if (statsManager == null) {
+ // TODO: Currently Process.isSupplemental is not working yet, because it depends on
+ // process to run in a certain UID range, which is not true for now. Change this
+ // to Log.wtf once Process.isSupplemental is ready.
+ Log.e(TAG, "TrafficStats not initialized, uid=" + Binder.getCallingUid());
+ return;
+ }
sStatsService = statsManager.getBinder();
}
/**
+ * Attach the socket tagger implementation to the current process, to
+ * get notified when a socket's {@link FileDescriptor} is assigned to
+ * a thread. See {@link SocketTagger#set(SocketTagger)}.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static void attachSocketTagger() {
+ NetworkManagementSocketTagger.install();
+ }
+
+ /**
* Set active tag to use when accounting {@link Socket} traffic originating
* from the current thread. Only one active tag per thread is supported.
* <p>
diff --git a/packages/ConnectivityT/framework-t/src/android/net/netstats/IUsageCallback.aidl b/packages/ConnectivityT/framework-t/src/android/net/netstats/IUsageCallback.aidl
new file mode 100644
index 0000000..4e8a5b2
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/netstats/IUsageCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.net.netstats;
+
+import android.net.DataUsageRequest;
+
+/**
+ * Interface for NetworkStatsService to notify events to the callers of registerUsageCallback.
+ *
+ * @hide
+ */
+oneway interface IUsageCallback {
+ void onThresholdReached(in DataUsageRequest request);
+ void onCallbackReleased(in DataUsageRequest request);
+}
diff --git a/packages/ConnectivityT/service/Android.bp b/packages/ConnectivityT/service/Android.bp
index 36dd200..24bc91d 100644
--- a/packages/ConnectivityT/service/Android.bp
+++ b/packages/ConnectivityT/service/Android.bp
@@ -26,6 +26,8 @@
srcs: [
"src/com/android/server/net/NetworkIdentity*.java",
"src/com/android/server/net/NetworkStats*.java",
+ "src/com/android/server/net/BpfInterfaceMapUpdater.java",
+ "src/com/android/server/net/InterfaceMapValue.java",
],
path: "src",
visibility: [
@@ -98,3 +100,28 @@
"//packages/modules/Connectivity:__subpackages__",
],
}
+
+cc_library_shared {
+ name: "libcom_android_net_module_util_jni",
+ min_sdk_version: "30",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ "-Wthread-safety",
+ ],
+ srcs: [
+ "jni/onload.cpp",
+ ],
+ stl: "libc++_static",
+ static_libs: [
+ "libnet_utils_device_common_bpfjni",
+ ],
+ shared_libs: [
+ "liblog",
+ "libnativehelper",
+ ],
+ apex_available: [
+ "//apex_available:platform",
+ ],
+}
diff --git a/packages/ConnectivityT/service/jni/onload.cpp b/packages/ConnectivityT/service/jni/onload.cpp
new file mode 100644
index 0000000..bca4697
--- /dev/null
+++ b/packages/ConnectivityT/service/jni/onload.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#include <nativehelper/JNIHelp.h>
+#include <log/log.h>
+
+namespace android {
+
+int register_com_android_net_module_util_BpfMap(JNIEnv* env, char const* class_name);
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
+ JNIEnv *env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ ALOGE("GetEnv failed");
+ return JNI_ERR;
+ }
+
+ if (register_com_android_net_module_util_BpfMap(env,
+ "com/android/net/module/util/BpfMap") < 0) return JNI_ERR;
+
+ return JNI_VERSION_1_6;
+}
+
+};
+
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/BpfInterfaceMapUpdater.java b/packages/ConnectivityT/service/src/com/android/server/net/BpfInterfaceMapUpdater.java
new file mode 100644
index 0000000..25c88eb
--- /dev/null
+++ b/packages/ConnectivityT/service/src/com/android/server/net/BpfInterfaceMapUpdater.java
@@ -0,0 +1,139 @@
+/*
+ * 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.net;
+
+import android.content.Context;
+import android.net.INetd;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.system.ErrnoException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
+import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.Struct.U32;
+
+/**
+ * Monitor interface added (without removed) and right interface name and its index to bpf map.
+ */
+public class BpfInterfaceMapUpdater {
+ private static final String TAG = BpfInterfaceMapUpdater.class.getSimpleName();
+ // This is current path but may be changed soon.
+ private static final String IFACE_INDEX_NAME_MAP_PATH =
+ "/sys/fs/bpf/map_netd_iface_index_name_map";
+ private final IBpfMap<U32, InterfaceMapValue> mBpfMap;
+ private final INetd mNetd;
+ private final Handler mHandler;
+ private final Dependencies mDeps;
+
+ public BpfInterfaceMapUpdater(Context ctx, Handler handler) {
+ this(ctx, handler, new Dependencies());
+ }
+
+ @VisibleForTesting
+ public BpfInterfaceMapUpdater(Context ctx, Handler handler, Dependencies deps) {
+ mDeps = deps;
+ mBpfMap = deps.getInterfaceMap();
+ mNetd = deps.getINetd(ctx);
+ mHandler = handler;
+ }
+
+ /**
+ * Dependencies of BpfInerfaceMapUpdater, for injection in tests.
+ */
+ @VisibleForTesting
+ public static class Dependencies {
+ /** Create BpfMap for updating interface and index mapping. */
+ public IBpfMap<U32, InterfaceMapValue> getInterfaceMap() {
+ try {
+ return new BpfMap<>(IFACE_INDEX_NAME_MAP_PATH, BpfMap.BPF_F_RDWR,
+ U32.class, InterfaceMapValue.class);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot create interface map: " + e);
+ return null;
+ }
+ }
+
+ /** Get InterfaceParams for giving interface name. */
+ public InterfaceParams getInterfaceParams(String ifaceName) {
+ return InterfaceParams.getByName(ifaceName);
+ }
+
+ /** Get INetd binder object. */
+ public INetd getINetd(Context ctx) {
+ return INetd.Stub.asInterface((IBinder) ctx.getSystemService(Context.NETD_SERVICE));
+ }
+ }
+
+ /**
+ * Start listening interface update event.
+ * Query current interface names before listening.
+ */
+ public void start() {
+ mHandler.post(() -> {
+ if (mBpfMap == null) {
+ Log.wtf(TAG, "Fail to start: Null bpf map");
+ return;
+ }
+
+ try {
+ // TODO: use a NetlinkMonitor and listen for RTM_NEWLINK messages instead.
+ mNetd.registerUnsolicitedEventListener(new InterfaceChangeObserver());
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Unable to register netd UnsolicitedEventListener, " + e);
+ }
+
+ final String[] ifaces;
+ try {
+ // TODO: use a netlink dump to get the current interface list.
+ ifaces = mNetd.interfaceGetList();
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.wtf(TAG, "Unable to query interface names by netd, " + e);
+ return;
+ }
+
+ for (String ifaceName : ifaces) {
+ addInterface(ifaceName);
+ }
+ });
+ }
+
+ private void addInterface(String ifaceName) {
+ final InterfaceParams iface = mDeps.getInterfaceParams(ifaceName);
+ if (iface == null) {
+ Log.e(TAG, "Unable to get InterfaceParams for " + ifaceName);
+ return;
+ }
+
+ try {
+ mBpfMap.updateEntry(new U32(iface.index), new InterfaceMapValue(ifaceName));
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Unable to update entry for " + ifaceName + ", " + e);
+ }
+ }
+
+ private class InterfaceChangeObserver extends BaseNetdUnsolicitedEventListener {
+ @Override
+ public void onInterfaceAdded(String ifName) {
+ mHandler.post(() -> addInterface(ifName));
+ }
+ }
+}
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/InterfaceMapValue.java b/packages/ConnectivityT/service/src/com/android/server/net/InterfaceMapValue.java
new file mode 100644
index 0000000..061f323
--- /dev/null
+++ b/packages/ConnectivityT/service/src/com/android/server/net/InterfaceMapValue.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.net;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * The value of bpf interface index map which is used for NetworkStatsService.
+ */
+public class InterfaceMapValue extends Struct {
+ @Field(order = 0, type = Type.ByteArray, arraysize = 16)
+ public final byte[] interfaceName;
+
+ public InterfaceMapValue(String iface) {
+ final byte[] ifaceArray = iface.getBytes();
+ interfaceName = new byte[16];
+ // All array bytes after the interface name, if any, must be 0.
+ System.arraycopy(ifaceArray, 0, interfaceName, 0, ifaceArray.length);
+ }
+}
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
index bb123a3..17f3455 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
@@ -26,10 +26,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.net.INetd;
+import android.content.Context;
+import android.net.ConnectivityManager;
import android.net.NetworkStats;
import android.net.UnderlyingNetworkInfo;
-import android.os.RemoteException;
import android.os.StrictMode;
import android.os.SystemClock;
@@ -70,7 +70,7 @@
private final boolean mUseBpfStats;
- private final INetd mNetd;
+ private final Context mContext;
/**
* Guards persistent data access in this class
@@ -158,12 +158,12 @@
NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, mStackedIfaces);
}
- public NetworkStatsFactory(@NonNull INetd netd) {
- this(new File("/proc/"), true, netd);
+ public NetworkStatsFactory(@NonNull Context ctx) {
+ this(ctx, new File("/proc/"), true);
}
@VisibleForTesting
- public NetworkStatsFactory(File procRoot, boolean useBpfStats, @NonNull INetd netd) {
+ public NetworkStatsFactory(@NonNull Context ctx, File procRoot, boolean useBpfStats) {
mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all");
mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt");
mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
@@ -172,7 +172,7 @@
mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1);
mTunAnd464xlatAdjustedStats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
}
- mNetd = netd;
+ mContext = ctx;
}
public NetworkStats readBpfNetworkStatsDev() throws IOException {
@@ -295,11 +295,12 @@
}
@GuardedBy("mPersistentDataLock")
- private void requestSwapActiveStatsMapLocked() throws RemoteException {
- // Ask netd to do a active map stats swap. When the binder call successfully returns,
+ private void requestSwapActiveStatsMapLocked() {
+ // Do a active map stats swap. When the binder call successfully returns,
// the system server should be able to safely read and clean the inactive map
// without race problem.
- mNetd.trafficSwapActiveStatsMap();
+ final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+ cm.swapActiveStatsMap();
}
/**
@@ -327,7 +328,7 @@
if (mUseBpfStats) {
try {
requestSwapActiveStatsMapLocked();
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
throw new IOException(e);
}
// Stats are always read from the inactive map, so they must be read after the
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
index b57a4f9..1953624 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
@@ -26,13 +26,12 @@
import android.net.NetworkStatsCollection;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
-import android.os.Bundle;
+import android.net.netstats.IUsageCallback;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-import android.os.Messenger;
import android.os.Process;
import android.os.RemoteException;
import android.util.ArrayMap;
@@ -75,10 +74,10 @@
*
* @return the normalized request wrapped within {@link RequestInfo}.
*/
- public DataUsageRequest register(DataUsageRequest inputRequest, Messenger messenger,
- IBinder binder, int callingUid, @NetworkStatsAccess.Level int accessLevel) {
- DataUsageRequest request = buildRequest(inputRequest);
- RequestInfo requestInfo = buildRequestInfo(request, messenger, binder, callingUid,
+ public DataUsageRequest register(DataUsageRequest inputRequest, IUsageCallback callback,
+ int callingUid, @NetworkStatsAccess.Level int accessLevel) {
+ DataUsageRequest request = buildRequest(inputRequest, callingUid);
+ RequestInfo requestInfo = buildRequestInfo(request, callback, callingUid,
accessLevel);
if (LOGV) Log.v(TAG, "Registering observer for " + request);
@@ -195,10 +194,12 @@
}
}
- private DataUsageRequest buildRequest(DataUsageRequest request) {
- // Cap the minimum threshold to a safe default to avoid too many callbacks
- long thresholdInBytes = Math.max(MIN_THRESHOLD_BYTES, request.thresholdInBytes);
- if (thresholdInBytes < request.thresholdInBytes) {
+ private DataUsageRequest buildRequest(DataUsageRequest request, int callingUid) {
+ // For non-system uid, cap the minimum threshold to a safe default to avoid too
+ // many callbacks.
+ long thresholdInBytes = (callingUid == Process.SYSTEM_UID ? request.thresholdInBytes
+ : Math.max(MIN_THRESHOLD_BYTES, request.thresholdInBytes));
+ if (thresholdInBytes > request.thresholdInBytes) {
Log.w(TAG, "Threshold was too low for " + request
+ ". Overriding to a safer default of " + thresholdInBytes + " bytes");
}
@@ -206,11 +207,10 @@
request.template, thresholdInBytes);
}
- private RequestInfo buildRequestInfo(DataUsageRequest request,
- Messenger messenger, IBinder binder, int callingUid,
- @NetworkStatsAccess.Level int accessLevel) {
+ private RequestInfo buildRequestInfo(DataUsageRequest request, IUsageCallback callback,
+ int callingUid, @NetworkStatsAccess.Level int accessLevel) {
if (accessLevel <= NetworkStatsAccess.Level.USER) {
- return new UserUsageRequestInfo(this, request, messenger, binder, callingUid,
+ return new UserUsageRequestInfo(this, request, callback, callingUid,
accessLevel);
} else {
// Safety check in case a new access level is added and we forgot to update this
@@ -218,7 +218,7 @@
throw new IllegalArgumentException(
"accessLevel " + accessLevel + " is less than DEVICESUMMARY.");
}
- return new NetworkUsageRequestInfo(this, request, messenger, binder, callingUid,
+ return new NetworkUsageRequestInfo(this, request, callback, callingUid,
accessLevel);
}
}
@@ -230,25 +230,23 @@
private abstract static class RequestInfo implements IBinder.DeathRecipient {
private final NetworkStatsObservers mStatsObserver;
protected final DataUsageRequest mRequest;
- private final Messenger mMessenger;
- private final IBinder mBinder;
+ private final IUsageCallback mCallback;
protected final int mCallingUid;
protected final @NetworkStatsAccess.Level int mAccessLevel;
protected NetworkStatsRecorder mRecorder;
protected NetworkStatsCollection mCollection;
RequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
- Messenger messenger, IBinder binder, int callingUid,
+ IUsageCallback callback, int callingUid,
@NetworkStatsAccess.Level int accessLevel) {
mStatsObserver = statsObserver;
mRequest = request;
- mMessenger = messenger;
- mBinder = binder;
+ mCallback = callback;
mCallingUid = callingUid;
mAccessLevel = accessLevel;
try {
- mBinder.linkToDeath(this, 0);
+ mCallback.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
binderDied();
}
@@ -257,7 +255,7 @@
@Override
public void binderDied() {
if (LOGV) {
- Log.v(TAG, "RequestInfo binderDied(" + mRequest + ", " + mBinder + ")");
+ Log.v(TAG, "RequestInfo binderDied(" + mRequest + ", " + mCallback + ")");
}
mStatsObserver.unregister(mRequest, Process.SYSTEM_UID);
callCallback(NetworkStatsManager.CALLBACK_RELEASED);
@@ -270,9 +268,7 @@
}
private void unlinkDeathRecipient() {
- if (mBinder != null) {
- mBinder.unlinkToDeath(this, 0);
- }
+ mCallback.asBinder().unlinkToDeath(this, 0);
}
/**
@@ -294,17 +290,19 @@
}
private void callCallback(int callbackType) {
- Bundle bundle = new Bundle();
- bundle.putParcelable(DataUsageRequest.PARCELABLE_KEY, mRequest);
- Message msg = Message.obtain();
- msg.what = callbackType;
- msg.setData(bundle);
try {
if (LOGV) {
Log.v(TAG, "sending notification " + callbackTypeToName(callbackType)
+ " for " + mRequest);
}
- mMessenger.send(msg);
+ switch (callbackType) {
+ case NetworkStatsManager.CALLBACK_LIMIT_REACHED:
+ mCallback.onThresholdReached(mRequest);
+ break;
+ case NetworkStatsManager.CALLBACK_RELEASED:
+ mCallback.onCallbackReleased(mRequest);
+ break;
+ }
} catch (RemoteException e) {
// May occur naturally in the race of binder death.
Log.w(TAG, "RemoteException caught trying to send a callback msg for " + mRequest);
@@ -334,9 +332,9 @@
private static class NetworkUsageRequestInfo extends RequestInfo {
NetworkUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
- Messenger messenger, IBinder binder, int callingUid,
+ IUsageCallback callback, int callingUid,
@NetworkStatsAccess.Level int accessLevel) {
- super(statsObserver, request, messenger, binder, callingUid, accessLevel);
+ super(statsObserver, request, callback, callingUid, accessLevel);
}
@Override
@@ -376,9 +374,9 @@
private static class UserUsageRequestInfo extends RequestInfo {
UserUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
- Messenger messenger, IBinder binder, int callingUid,
+ IUsageCallback callback, int callingUid,
@NetworkStatsAccess.Level int accessLevel) {
- super(statsObserver, request, messenger, binder, callingUid, accessLevel);
+ super(statsObserver, request, callback, callingUid, accessLevel);
}
@Override
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 1105de3..243d621 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -99,6 +99,7 @@
import android.net.TrafficStats;
import android.net.UnderlyingNetworkInfo;
import android.net.Uri;
+import android.net.netstats.IUsageCallback;
import android.net.netstats.provider.INetworkStatsProvider;
import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.net.netstats.provider.NetworkStatsProvider;
@@ -110,7 +111,6 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-import android.os.Messenger;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
@@ -357,6 +357,9 @@
@NonNull
private final LocationPermissionChecker mLocationPermissionChecker;
+ @NonNull
+ private final BpfInterfaceMapUpdater mInterfaceMapUpdater;
+
private static @NonNull File getDefaultSystemDir() {
return new File(Environment.getDataDirectory(), "system");
}
@@ -419,7 +422,7 @@
final NetworkStatsService service = new NetworkStatsService(context,
INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
alarmManager, wakeLock, getDefaultClock(),
- new DefaultNetworkStatsSettings(), new NetworkStatsFactory(netd),
+ new DefaultNetworkStatsSettings(), new NetworkStatsFactory(context),
new NetworkStatsObservers(), getDefaultSystemDir(), getDefaultBaseDir(),
new Dependencies());
@@ -454,6 +457,8 @@
mContentObserver = mDeps.makeContentObserver(mHandler, mSettings,
mNetworkStatsSubscriptionsMonitor);
mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
+ mInterfaceMapUpdater = mDeps.makeBpfInterfaceMapUpdater(mContext, mHandler);
+ mInterfaceMapUpdater.start();
}
/**
@@ -508,6 +513,13 @@
public LocationPermissionChecker makeLocationPermissionChecker(final Context context) {
return new LocationPermissionChecker(context);
}
+
+ /** Create BpfInterfaceMapUpdater to update bpf interface map. */
+ @NonNull
+ public BpfInterfaceMapUpdater makeBpfInterfaceMapUpdater(
+ @NonNull Context ctx, @NonNull Handler handler) {
+ return new BpfInterfaceMapUpdater(ctx, handler);
+ }
}
/**
@@ -988,8 +1000,17 @@
}
// TODO: switch to data layer stats once kernel exports
- // for now, read network layer stats and flatten across all ifaces
- final NetworkStats networkLayer = readNetworkStatsUidDetail(uid, INTERFACES_ALL, TAG_ALL);
+ // for now, read network layer stats and flatten across all ifaces.
+ // This function is used to query NeworkStats for calle's uid. The only caller method
+ // TrafficStats#getDataLayerSnapshotForUid alrady claim no special permission to query
+ // its own NetworkStats.
+ final long ident = Binder.clearCallingIdentity();
+ final NetworkStats networkLayer;
+ try {
+ networkLayer = readNetworkStatsUidDetail(uid, INTERFACES_ALL, TAG_ALL);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
// splice in operation counts
networkLayer.spliceOperationsFrom(mUidOperations);
@@ -1136,21 +1157,20 @@
}
@Override
- public DataUsageRequest registerUsageCallback(String callingPackage,
- DataUsageRequest request, Messenger messenger, IBinder binder) {
+ public DataUsageRequest registerUsageCallback(@NonNull String callingPackage,
+ @NonNull DataUsageRequest request, @NonNull IUsageCallback callback) {
Objects.requireNonNull(callingPackage, "calling package is null");
Objects.requireNonNull(request, "DataUsageRequest is null");
Objects.requireNonNull(request.template, "NetworkTemplate is null");
- Objects.requireNonNull(messenger, "messenger is null");
- Objects.requireNonNull(binder, "binder is null");
+ Objects.requireNonNull(callback, "callback is null");
int callingUid = Binder.getCallingUid();
@NetworkStatsAccess.Level int accessLevel = checkAccessLevel(callingPackage);
DataUsageRequest normalizedRequest;
final long token = Binder.clearCallingIdentity();
try {
- normalizedRequest = mStatsObservers.register(request, messenger, binder,
- callingUid, accessLevel);
+ normalizedRequest = mStatsObservers.register(
+ request, callback, callingUid, accessLevel);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index 3c444f2..7168f3c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -402,7 +402,10 @@
if (dreamInfo == null || dreamInfo.settingsComponentName == null) {
return;
}
- uiContext.startActivity(new Intent().setComponent(dreamInfo.settingsComponentName));
+ final Intent intent = new Intent()
+ .setComponent(dreamInfo.settingsComponentName)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ uiContext.startActivity(intent);
}
public void preview(DreamInfo dreamInfo) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/ChartData.java b/packages/SettingsLib/src/com/android/settingslib/net/ChartData.java
index e30aac5..a69c59c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/ChartData.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/ChartData.java
@@ -16,12 +16,18 @@
package com.android.settingslib.net;
-import android.net.NetworkStatsHistory;
+import android.app.usage.NetworkStats;
+
+import java.util.List;
public class ChartData {
- public NetworkStatsHistory network;
+ // Collect the data usage history of the network from the given {@link NetworkTemplate}.
+ public List<NetworkStats.Bucket> network;
- public NetworkStatsHistory detail;
- public NetworkStatsHistory detailDefault;
- public NetworkStatsHistory detailForeground;
+ // Collect the detail datausage history (foreground + Background).
+ public List<NetworkStats.Bucket> detail;
+ // Collect background datausage history.
+ public List<NetworkStats.Bucket> detailDefault;
+ // Collect foreground datausage history.
+ public List<NetworkStats.Bucket> detailForeground;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoader.java
index 60d22a0..573922f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoader.java
@@ -19,20 +19,21 @@
import static android.net.NetworkStats.SET_DEFAULT;
import static android.net.NetworkStats.SET_FOREGROUND;
import static android.net.NetworkStats.TAG_NONE;
-import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
-import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
-import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import android.annotation.NonNull;
+import android.app.usage.NetworkStats;
+import android.app.usage.NetworkStatsManager;
import android.content.AsyncTaskLoader;
import android.content.Context;
-import android.net.INetworkStatsSession;
-import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.os.Bundle;
import android.os.RemoteException;
import com.android.settingslib.AppItem;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Framework loader is deprecated, use the compat version instead.
*
@@ -42,26 +43,20 @@
public class ChartDataLoader extends AsyncTaskLoader<ChartData> {
private static final String KEY_TEMPLATE = "template";
private static final String KEY_APP = "app";
- private static final String KEY_FIELDS = "fields";
- private final INetworkStatsSession mSession;
+ private final NetworkStatsManager mNetworkStatsManager;
private final Bundle mArgs;
public static Bundle buildArgs(NetworkTemplate template, AppItem app) {
- return buildArgs(template, app, FIELD_RX_BYTES | FIELD_TX_BYTES);
- }
-
- public static Bundle buildArgs(NetworkTemplate template, AppItem app, int fields) {
final Bundle args = new Bundle();
args.putParcelable(KEY_TEMPLATE, template);
args.putParcelable(KEY_APP, app);
- args.putInt(KEY_FIELDS, fields);
return args;
}
- public ChartDataLoader(Context context, INetworkStatsSession session, Bundle args) {
+ public ChartDataLoader(Context context, NetworkStatsManager statsManager, Bundle args) {
super(context);
- mSession = session;
+ mNetworkStatsManager = statsManager;
mArgs = args;
}
@@ -75,10 +70,9 @@
public ChartData loadInBackground() {
final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE);
final AppItem app = mArgs.getParcelable(KEY_APP);
- final int fields = mArgs.getInt(KEY_FIELDS);
try {
- return loadInBackground(template, app, fields);
+ return loadInBackground(template, app);
} catch (RemoteException e) {
// since we can't do much without history, and we don't want to
// leave with half-baked UI, we bail hard.
@@ -86,10 +80,22 @@
}
}
- private ChartData loadInBackground(NetworkTemplate template, AppItem app, int fields)
+ @NonNull
+ private List<NetworkStats.Bucket> convertToBuckets(@NonNull NetworkStats stats) {
+ final List<NetworkStats.Bucket> ret = new ArrayList<>();
+ while (stats.hasNextBucket()) {
+ final NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+ stats.getNextBucket(bucket);
+ ret.add(bucket);
+ }
+ return ret;
+ }
+
+ private ChartData loadInBackground(NetworkTemplate template, AppItem app)
throws RemoteException {
final ChartData data = new ChartData();
- data.network = mSession.getHistoryForNetwork(template, fields);
+ data.network = convertToBuckets(mNetworkStatsManager.queryDetailsForDevice(
+ template, Long.MIN_VALUE, Long.MAX_VALUE));
if (app != null) {
// load stats for current uid and template
@@ -103,13 +109,13 @@
}
if (size > 0) {
- data.detail = new NetworkStatsHistory(data.detailForeground.getBucketDuration());
- data.detail.recordEntireHistory(data.detailDefault);
- data.detail.recordEntireHistory(data.detailForeground);
+ data.detail = new ArrayList<>();
+ data.detail.addAll(data.detailDefault);
+ data.detail.addAll(data.detailForeground);
} else {
- data.detailDefault = new NetworkStatsHistory(HOUR_IN_MILLIS);
- data.detailForeground = new NetworkStatsHistory(HOUR_IN_MILLIS);
- data.detail = new NetworkStatsHistory(HOUR_IN_MILLIS);
+ data.detailDefault = new ArrayList<>();
+ data.detailForeground = new ArrayList<>();
+ data.detail = new ArrayList<>();
}
}
@@ -129,17 +135,17 @@
}
/**
- * Collect {@link NetworkStatsHistory} for the requested UID, combining with
- * an existing {@link NetworkStatsHistory} if provided.
+ * Collect {@link List<NetworkStats.Bucket>} for the requested UID, combining with
+ * an existing {@link List<NetworkStats.Bucket>} if provided.
*/
- private NetworkStatsHistory collectHistoryForUid(
- NetworkTemplate template, int uid, int set, NetworkStatsHistory existing)
- throws RemoteException {
- final NetworkStatsHistory history = mSession.getHistoryForUid(
- template, uid, set, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES);
+ private List<NetworkStats.Bucket> collectHistoryForUid(
+ NetworkTemplate template, int uid, int set, List<NetworkStats.Bucket> existing) {
+ final List<NetworkStats.Bucket> history = convertToBuckets(
+ mNetworkStatsManager.queryDetailsForUidTagState(template,
+ Long.MIN_VALUE, Long.MAX_VALUE, uid, TAG_NONE, set));
if (existing != null) {
- existing.recordEntireHistory(history);
+ existing.addAll(history);
return existing;
} else {
return history;
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoader.java
index 649aeff..8da6032 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoader.java
@@ -16,13 +16,12 @@
package com.android.settingslib.net;
+import android.app.usage.NetworkStats;
+import android.app.usage.NetworkStatsManager;
import android.content.AsyncTaskLoader;
import android.content.Context;
-import android.net.INetworkStatsSession;
-import android.net.NetworkStats;
import android.net.NetworkTemplate;
import android.os.Bundle;
-import android.os.RemoteException;
/**
* Framework loader is deprecated, use the compat version instead.
@@ -35,7 +34,7 @@
private static final String KEY_START = "start";
private static final String KEY_END = "end";
- private final INetworkStatsSession mSession;
+ private final NetworkStatsManager mNetworkStatsManager;
private final Bundle mArgs;
public static Bundle buildArgs(NetworkTemplate template, long start, long end) {
@@ -46,9 +45,9 @@
return args;
}
- public SummaryForAllUidLoader(Context context, INetworkStatsSession session, Bundle args) {
+ public SummaryForAllUidLoader(Context context, Bundle args) {
super(context);
- mSession = session;
+ mNetworkStatsManager = context.getSystemService(NetworkStatsManager.class);
mArgs = args;
}
@@ -63,12 +62,7 @@
final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE);
final long start = mArgs.getLong(KEY_START);
final long end = mArgs.getLong(KEY_END);
-
- try {
- return mSession.getSummaryForAllUid(template, start, end, false);
- } catch (RemoteException e) {
- return null;
- }
+ return mNetworkStatsManager.querySummary(template, start, end);
}
@Override
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 46e24fa..c6fbfd8 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -110,6 +110,7 @@
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
<uses-permission android:name="android.permission.READ_INSTALL_SESSIONS" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
+ <uses-permission android:name="android.permission.SEND_LOST_MODE_LOCATION_UPDATES" />
<!-- ACCESS_BACKGROUND_LOCATION is needed for testing purposes only. -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- ACCESS_MTP is needed for testing purposes only. -->
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 3ae85e7..8323e32 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -71,6 +71,7 @@
yurilin@google.com
xuqiu@google.com
zakcohen@google.com
+jernej@google.com
#Android Auto
hseog@google.com
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index da9a92a..68c8c3e 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -122,6 +122,11 @@
* Set or clear device media playing
*/
void setMediaTarget(@Nullable SmartspaceTarget target);
+
+ /**
+ * Get the index of the currently selected page.
+ */
+ int getSelectedPage();
}
/** Interface for launching Intents, which can differ on the lockscreen */
diff --git a/packages/SystemUI/res/drawable/ic_warning.xml b/packages/SystemUI/res/drawable/ic_warning.xml
new file mode 100644
index 0000000..fbed779
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_warning.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
+ <path android:fillColor="@android:color/white" android:pathData="M12,12.5zM1,21L12,2l11,19zM11,15h2v-5h-2zM12,18q0.425,0 0.713,-0.288Q13,17.425 13,17t-0.287,-0.712Q12.425,16 12,16t-0.713,0.288Q11,16.575 11,17t0.287,0.712Q11.575,18 12,18zM4.45,19h15.1L12,6z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/screenshot_border.xml b/packages/SystemUI/res/drawable/overlay_border.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/screenshot_border.xml
rename to packages/SystemUI/res/drawable/overlay_border.xml
diff --git a/packages/SystemUI/res/drawable/screenshot_cancel.xml b/packages/SystemUI/res/drawable/overlay_cancel.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/screenshot_cancel.xml
rename to packages/SystemUI/res/drawable/overlay_cancel.xml
diff --git a/packages/SystemUI/res/drawable/screenshot_preview_background.xml b/packages/SystemUI/res/drawable/overlay_preview_background.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/screenshot_preview_background.xml
rename to packages/SystemUI/res/drawable/overlay_preview_background.xml
diff --git a/packages/SystemUI/res/layout/clipboard_content_preview.xml b/packages/SystemUI/res/layout/clipboard_content_preview.xml
deleted file mode 100644
index 7317a94..0000000
--- a/packages/SystemUI/res/layout/clipboard_content_preview.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/preview_border"
- android:elevation="9dp"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/screenshot_offset_x"
- android:layout_marginBottom="@dimen/screenshot_offset_y"
- android:layout_gravity="bottom|start"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- android:clipToPadding="false"
- android:clipChildren="false"
- android:padding="4dp"
- android:background="@drawable/screenshot_border"
- >
- <FrameLayout
- android:elevation="0dp"
- android:background="@drawable/screenshot_preview_background"
- android:clipChildren="true"
- android:clipToOutline="true"
- android:clipToPadding="true"
- android:layout_width="@dimen/screenshot_x_scale"
- android:layout_height="wrap_content">
- <TextView android:id="@+id/text_preview"
- android:textFontWeight="500"
- android:padding="8dp"
- android:gravity="center|start"
- android:ellipsize="end"
- android:autoSizeTextType="uniform"
- android:autoSizeMinTextSize="10sp"
- android:autoSizeMaxTextSize="200sp"
- android:textColor="?android:attr/textColorPrimary"
- android:layout_width="@dimen/screenshot_x_scale"
- android:layout_height="@dimen/screenshot_x_scale"/>
- <ImageView
- android:id="@+id/image_preview"
- android:scaleType="fitCenter"
- android:adjustViewBounds="true"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
- </FrameLayout>
-</FrameLayout>
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 76280d8..7ffb3b2 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -17,9 +17,8 @@
<com.android.systemui.clipboardoverlay.DraggableConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
- android:layout_gravity="bottom"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="match_parent">
<ImageView
android:id="@+id/actions_container_background"
android:visibility="gone"
@@ -57,5 +56,81 @@
android:id="@+id/edit_chip"/>
</LinearLayout>
</HorizontalScrollView>
- <include layout="@layout/clipboard_content_preview" />
+ <View
+ android:id="@+id/preview_border"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginStart="@dimen/overlay_offset_x"
+ android:layout_marginBottom="@dimen/overlay_offset_y"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ android:elevation="@dimen/overlay_preview_elevation"
+ app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end"
+ app:layout_constraintTop_toTopOf="@id/clipboard_preview_top"
+ android:background="@drawable/overlay_border"/>
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/clipboard_preview_end"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:barrierMargin="@dimen/overlay_border_width"
+ app:barrierDirection="end"
+ app:constraint_referenced_ids="clipboard_preview"/>
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/clipboard_preview_top"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:barrierDirection="top"
+ app:barrierMargin="@dimen/overlay_border_width_neg"
+ app:constraint_referenced_ids="clipboard_preview"/>
+ <FrameLayout
+ android:id="@+id/clipboard_preview"
+ android:elevation="@dimen/overlay_preview_elevation"
+ android:background="@drawable/overlay_preview_background"
+ android:clipChildren="true"
+ android:clipToOutline="true"
+ android:clipToPadding="true"
+ android:layout_width="@dimen/clipboard_preview_size"
+ android:layout_margin="@dimen/overlay_border_width"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ app:layout_constraintBottom_toBottomOf="@id/preview_border"
+ app:layout_constraintStart_toStartOf="@id/preview_border"
+ app:layout_constraintEnd_toEndOf="@id/preview_border"
+ app:layout_constraintTop_toTopOf="@id/preview_border">
+ <TextView android:id="@+id/text_preview"
+ android:textFontWeight="500"
+ android:padding="8dp"
+ android:gravity="center|start"
+ android:ellipsize="end"
+ android:autoSizeTextType="uniform"
+ android:autoSizeMinTextSize="10sp"
+ android:autoSizeMaxTextSize="200sp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:layout_width="@dimen/clipboard_preview_size"
+ android:layout_height="@dimen/clipboard_preview_size"/>
+ <ImageView
+ android:id="@+id/image_preview"
+ android:scaleType="fitCenter"
+ android:adjustViewBounds="true"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ </FrameLayout>
+ <FrameLayout
+ android:id="@+id/dismiss_button"
+ android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
+ android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
+ android:elevation="@dimen/overlay_dismiss_button_elevation"
+ android:visibility="gone"
+ app:layout_constraintStart_toEndOf="@id/clipboard_preview"
+ app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
+ app:layout_constraintTop_toTopOf="@id/clipboard_preview"
+ app:layout_constraintBottom_toTopOf="@id/clipboard_preview"
+ android:contentDescription="@string/clipboard_dismiss_description">
+ <ImageView
+ android:id="@+id/dismiss_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="@dimen/overlay_dismiss_button_margin"
+ android:src="@drawable/overlay_cancel"/>
+ </FrameLayout>
</com.android.systemui.clipboardoverlay.DraggableConstraintLayout>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml b/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
index f898ef6..5135947 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
@@ -18,7 +18,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/dream_overlay_complications_layer"
- android:padding="20dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextClock
diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml
index c6b502e..4929f50 100644
--- a/packages/SystemUI/res/layout/dream_overlay_container.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_container.xml
@@ -28,8 +28,6 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
- <include layout="@layout/dream_overlay_complications_layer" />
-
<com.android.systemui.dreams.DreamOverlayStatusBarView
android:id="@+id/dream_overlay_status_bar"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/media_ttt_chip.xml
index 2d082dc..a5fdcd9 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip.xml
@@ -28,8 +28,8 @@
<com.android.internal.widget.CachingIconView
android:id="@+id/app_icon"
- android:layout_width="@dimen/media_ttt_icon_size"
- android:layout_height="@dimen/media_ttt_icon_size"
+ android:layout_width="@dimen/media_ttt_app_icon_size"
+ android:layout_height="@dimen/media_ttt_app_icon_size"
android:layout_marginEnd="12dp"
/>
@@ -41,23 +41,34 @@
android:textColor="?android:attr/textColorPrimary"
/>
+ <!-- At most one of [loading, failure_icon, undo] will be visible at a time. -->
+
<ProgressBar
android:id="@+id/loading"
android:indeterminate="true"
- android:layout_width="@dimen/media_ttt_loading_size"
- android:layout_height="@dimen/media_ttt_loading_size"
- android:layout_marginStart="12dp"
+ android:layout_width="@dimen/media_ttt_status_icon_size"
+ android:layout_height="@dimen/media_ttt_status_icon_size"
+ android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
android:indeterminateTint="?androidprv:attr/colorAccentPrimaryVariant"
style="?android:attr/progressBarStyleSmall"
/>
+ <ImageView
+ android:id="@+id/failure_icon"
+ android:layout_width="@dimen/media_ttt_status_icon_size"
+ android:layout_height="@dimen/media_ttt_status_icon_size"
+ android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
+ android:src="@drawable/ic_warning"
+ android:tint="@color/GM2_red_500"
+ />
+
<TextView
android:id="@+id/undo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/media_transfer_undo"
android:textColor="?androidprv:attr/textColorOnAccent"
- android:layout_marginStart="12dp"
+ android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
android:textSize="@dimen/media_ttt_text_size"
android:paddingStart="@dimen/media_ttt_chip_outer_padding"
android:paddingEnd="@dimen/media_ttt_chip_outer_padding"
diff --git a/packages/SystemUI/res/layout/screenshot.xml b/packages/SystemUI/res/layout/screenshot.xml
index 7c0ee66..227212b 100644
--- a/packages/SystemUI/res/layout/screenshot.xml
+++ b/packages/SystemUI/res/layout/screenshot.xml
@@ -40,7 +40,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
- android:elevation="@dimen/screenshot_preview_elevation"
+ android:elevation="@dimen/overlay_preview_elevation"
android:src="@android:color/white"/>
<com.android.systemui.screenshot.ScreenshotSelectorView
android:id="@+id/screenshot_selector"
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index f6e3f1a..8f791c3 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -63,20 +63,20 @@
android:id="@+id/screenshot_preview_border"
android:layout_width="0dp"
android:layout_height="0dp"
- android:layout_marginStart="@dimen/screenshot_offset_x"
- android:layout_marginBottom="@dimen/screenshot_offset_y"
- android:elevation="@dimen/screenshot_preview_elevation"
+ android:layout_marginStart="@dimen/overlay_offset_x"
+ android:layout_marginBottom="@dimen/overlay_offset_y"
+ android:elevation="@dimen/overlay_preview_elevation"
android:alpha="0"
- android:background="@drawable/screenshot_border"
+ android:background="@drawable/overlay_border"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="@+id/screenshot_preview_end"
- app:layout_constraintTop_toTopOf="@+id/screenshot_preview_top"/>
+ app:layout_constraintEnd_toEndOf="@id/screenshot_preview_end"
+ app:layout_constraintTop_toTopOf="@id/screenshot_preview_top"/>
<androidx.constraintlayout.widget.Barrier
android:id="@+id/screenshot_preview_end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- app:barrierMargin="4dp"
+ app:barrierMargin="@dimen/overlay_border_width"
app:barrierDirection="end"
app:constraint_referenced_ids="screenshot_preview"/>
<androidx.constraintlayout.widget.Barrier
@@ -84,30 +84,30 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="top"
- app:barrierMargin="-4dp"
+ app:barrierMargin="@dimen/overlay_border_width_neg"
app:constraint_referenced_ids="screenshot_preview"/>
<ImageView
android:id="@+id/screenshot_preview"
android:visibility="invisible"
android:layout_width="@dimen/screenshot_x_scale"
- android:layout_margin="4dp"
+ android:layout_margin="@dimen/overlay_border_width"
android:layout_height="wrap_content"
android:layout_gravity="center"
- android:elevation="@dimen/screenshot_preview_elevation"
+ android:elevation="@dimen/overlay_preview_elevation"
android:contentDescription="@string/screenshot_edit_description"
android:scaleType="fitEnd"
- android:background="@drawable/screenshot_preview_background"
+ android:background="@drawable/overlay_preview_background"
android:adjustViewBounds="true"
- app:layout_constraintBottom_toBottomOf="@+id/screenshot_preview_border"
- app:layout_constraintStart_toStartOf="@+id/screenshot_preview_border"
- app:layout_constraintEnd_toEndOf="@+id/screenshot_preview_border"
- app:layout_constraintTop_toTopOf="@+id/screenshot_preview_border">
+ app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
+ app:layout_constraintStart_toStartOf="@id/screenshot_preview_border"
+ app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"
+ app:layout_constraintTop_toTopOf="@id/screenshot_preview_border">
</ImageView>
<FrameLayout
android:id="@+id/screenshot_dismiss_button"
- android:layout_width="@dimen/screenshot_dismiss_button_tappable_size"
- android:layout_height="@dimen/screenshot_dismiss_button_tappable_size"
- android:elevation="7dp"
+ android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
+ android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
+ android:elevation="@dimen/overlay_dismiss_button_elevation"
android:visibility="gone"
app:layout_constraintStart_toEndOf="@id/screenshot_preview"
app:layout_constraintEnd_toEndOf="@id/screenshot_preview"
@@ -118,8 +118,8 @@
android:id="@+id/screenshot_dismiss_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_margin="@dimen/screenshot_dismiss_button_margin"
- android:src="@drawable/screenshot_cancel"/>
+ android:layout_margin="@dimen/overlay_dismiss_button_margin"
+ android:src="@drawable/overlay_cancel"/>
</FrameLayout>
<ImageView
android:id="@+id/screenshot_scrollable_preview"
@@ -129,5 +129,5 @@
android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/screenshot_preview"
app:layout_constraintTop_toTopOf="@id/screenshot_preview"
- android:elevation="@dimen/screenshot_preview_elevation"/>
+ android:elevation="@dimen/overlay_preview_elevation"/>
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b12db5d..67d5b2f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -261,11 +261,6 @@
<!-- The padding on the global screenshot background image -->
<dimen name="screenshot_x_scale">80dp</dimen>
<dimen name="screenshot_bg_protection_height">242dp</dimen>
- <dimen name="screenshot_preview_elevation">4dp</dimen>
- <dimen name="screenshot_offset_y">8dp</dimen>
- <dimen name="screenshot_offset_x">16dp</dimen>
- <dimen name="screenshot_dismiss_button_tappable_size">48dp</dimen>
- <dimen name="screenshot_dismiss_button_margin">8dp</dimen>
<dimen name="screenshot_action_container_corner_radius">18dp</dimen>
<dimen name="screenshot_action_container_padding_vertical">4dp</dimen>
<dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
@@ -289,6 +284,19 @@
<dimen name="screenshot_crop_handle_thickness">3dp</dimen>
<dimen name="long_screenshot_action_bar_top_margin">8dp</dimen>
+ <!-- Dimensions shared between "overlays" (clipboard and screenshot preview UIs) -->
+ <dimen name="overlay_offset_y">8dp</dimen>
+ <dimen name="overlay_offset_x">16dp</dimen>
+ <dimen name="overlay_preview_elevation">4dp</dimen>
+ <dimen name="overlay_dismiss_button_elevation">7dp</dimen>
+ <dimen name="overlay_dismiss_button_tappable_size">48dp</dimen>
+ <dimen name="overlay_dismiss_button_margin">8dp</dimen>
+ <dimen name="overlay_border_width">4dp</dimen>
+ <!-- need a negative margin for some of the constraints. should be overlay_border_width * -1 -->
+ <dimen name="overlay_border_width_neg">-4dp</dimen>
+
+ <dimen name="clipboard_preview_size">@dimen/screenshot_x_scale</dimen>
+
<!-- The width of the view containing navigation buttons -->
<dimen name="navigation_key_width">70dp</dimen>
@@ -984,10 +992,11 @@
<!-- Media tap-to-transfer chip for sender device -->
<dimen name="media_ttt_chip_outer_padding">16dp</dimen>
<dimen name="media_ttt_text_size">16sp</dimen>
- <dimen name="media_ttt_icon_size">24dp</dimen>
- <dimen name="media_ttt_loading_size">20dp</dimen>
+ <dimen name="media_ttt_app_icon_size">24dp</dimen>
+ <dimen name="media_ttt_status_icon_size">20dp</dimen>
<dimen name="media_ttt_undo_button_vertical_padding">8dp</dimen>
<dimen name="media_ttt_undo_button_vertical_negative_margin">-8dp</dimen>
+ <dimen name="media_ttt_last_item_start_margin">12dp</dimen>
<!-- Media tap-to-transfer chip for receiver device -->
<dimen name="media_ttt_chip_size_receiver">100dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7441a81..7384ccc 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2158,8 +2158,14 @@
<string name="media_transfer_undo">Undo</string>
<!-- Text to ask the user to move their device closer to a different device (deviceName) in order to play media on the different device. [CHAR LIMIT=75] -->
<string name="media_move_closer_to_start_cast">Move closer to play on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
+ <!-- Text to ask the user to move their device closer to a different device (deviceName) in order to transfer media from the different device and back onto the current device. [CHAR LIMIT=75] -->
+ <string name="media_move_closer_to_end_cast">Move closer to <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g> to play here</string>
<!-- Text informing the user that their media is now playing on a different device (deviceName). [CHAR LIMIT=50] -->
- <string name="media_transfer_playing">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
+ <string name="media_transfer_playing_different_device">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
+ <!-- Text informing the user that their media is now playing on this device. [CHAR LIMIT=50] -->
+ <string name="media_transfer_playing_this_device">Playing on this phone</string>
+ <!-- Text informing the user that the media transfer has failed because something went wrong. [CHAR LIMIT=50] -->
+ <string name="media_transfer_failed">Something went wrong</string>
<!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] -->
<string name="controls_error_timeout">Inactive, check app</string>
@@ -2358,4 +2364,6 @@
<string name="clipboard_edit_text_copy">Copy</string>
<!-- Text informing user that content has been copied to the system clipboard [CHAR LIMIT=NONE] -->
<string name="clipboard_overlay_text_copied">Copied</string>
+ <!-- Label for button to dismiss clipboard overlay [CHAR LIMIT=NONE] -->
+ <string name="clipboard_dismiss_description">Dismiss copy UI</string>
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl
deleted file mode 100644
index 484791d..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shared.mediattt;
-
-import android.media.MediaRoute2Info;
-import com.android.systemui.shared.mediattt.DeviceInfo;
-
-/**
- * A callback interface that can be invoked to trigger media transfer events on System UI.
- *
- * This interface is for the *sender* device, which is the device currently playing media. This
- * sender device can transfer the media to a different device, called the receiver.
- *
- * System UI will implement this interface and other services will invoke it.
- */
-interface IDeviceSenderCallback {
- /**
- * Invoke to notify System UI that this device (the sender) is close to a receiver device, so
- * the user can potentially *start* a cast to the receiver device if the user moves their device
- * a bit closer.
- *
- * Important notes:
- * - When this callback triggers, the device is close enough to inform the user that
- * transferring is an option, but the device is *not* close enough to actually initiate a
- * transfer yet.
- * - This callback is for *starting* a cast. It should be used when this device is currently
- * playing media locally and the media should be transferred to be played on the receiver
- * device instead.
- */
- oneway void closeToReceiverToStartCast(
- in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl
new file mode 100644
index 0000000..eb1c9d0
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.mediattt;
+
+import android.media.MediaRoute2Info;
+import com.android.systemui.shared.mediattt.DeviceInfo;
+import com.android.systemui.shared.mediattt.IUndoTransferCallback;
+
+/**
+ * An interface that can be invoked to trigger media transfer events on System UI.
+ *
+ * This interface is for the *sender* device, which is the device currently playing media. This
+ * sender device can transfer the media to a different device, called the receiver.
+ *
+ * System UI will implement this interface and other services will invoke it.
+ */
+interface IDeviceSenderService {
+ /**
+ * Invoke to notify System UI that this device (the sender) is close to a receiver device, so
+ * the user can potentially *start* a cast to the receiver device if the user moves their device
+ * a bit closer.
+ *
+ * Important notes:
+ * - When this callback triggers, the device is close enough to inform the user that
+ * transferring is an option, but the device is *not* close enough to actually initiate a
+ * transfer yet.
+ * - This callback is for *starting* a cast. It should be used when this device is currently
+ * playing media locally and the media should be transferred to be played on the receiver
+ * device instead.
+ */
+ oneway void closeToReceiverToStartCast(
+ in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
+
+ /**
+ * Invoke to notify System UI that this device (the sender) is close to a receiver device, so
+ * the user can potentially *end* a cast on the receiver device if the user moves this device a
+ * bit closer.
+ *
+ * Important notes:
+ * - When this callback triggers, the device is close enough to inform the user that
+ * transferring is an option, but the device is *not* close enough to actually initiate a
+ * transfer yet.
+ * - This callback is for *ending* a cast. It should be used when media is currently being
+ * played on the receiver device and the media should be transferred to play locally
+ * instead.
+ */
+ oneway void closeToReceiverToEndCast(
+ in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
+
+ /**
+ * Invoke to notify System UI that a media transfer from this device (the sender) to a receiver
+ * device has been started.
+ *
+ * Important notes:
+ * - This callback is for *starting* a cast. It should be used when this device is currently
+ * playing media locally and the media has started being transferred to the receiver device
+ * instead.
+ */
+ oneway void transferToReceiverTriggered(
+ in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
+
+ /**
+ * Invoke to notify System UI that a media transfer from the receiver and back to this device
+ * (the sender) has been started.
+ *
+ * Important notes:
+ * - This callback is for *ending* a cast. It should be used when media is currently being
+ * played on the receiver device and the media has started being transferred to play locally
+ * instead.
+ */
+ oneway void transferToThisDeviceTriggered(
+ in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
+
+ /**
+ * Invoke to notify System UI that a media transfer from this device (the sender) to a receiver
+ * device has finished successfully.
+ *
+ * Important notes:
+ * - This callback is for *starting* a cast. It should be used when this device had previously
+ * been playing media locally and the media has successfully been transferred to the
+ * receiver device instead.
+ *
+ * @param undoCallback will be invoked if the user chooses to undo this transfer.
+ */
+ oneway void transferToReceiverSucceeded(
+ in MediaRoute2Info mediaInfo,
+ in DeviceInfo otherDeviceInfo,
+ in IUndoTransferCallback undoCallback);
+
+ /**
+ * Invoke to notify System UI that a media transfer from the receiver and back to this device
+ * (the sender) has finished successfully.
+ *
+ * Important notes:
+ * - This callback is for *ending* a cast. It should be used when media was previously being
+ * played on the receiver device and has been successfully transferred to play locally on
+ * this device instead.
+ *
+ * @param undoCallback will be invoked if the user chooses to undo this transfer.
+ */
+ oneway void transferToThisDeviceSucceeded(
+ in MediaRoute2Info mediaInfo,
+ in DeviceInfo otherDeviceInfo,
+ in IUndoTransferCallback undoCallback);
+
+ /**
+ * Invoke to notify System UI that the attempted transfer has failed.
+ *
+ * This callback will be used for both the transfer that should've *started* playing the media
+ * on the receiver and the transfer that should've *ended* the playing on the receiver.
+ */
+ oneway void transferFailed(in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
+
+ /**
+ * Invoke to notify System UI that this device is no longer close to the receiver device.
+ */
+ oneway void noLongerCloseToReceiver(
+ in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl
new file mode 100644
index 0000000..b47be87
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.mediattt;
+
+/**
+ * An interface that will be invoked by System UI if the user choose to undo a transfer.
+ *
+ * Other services will implement this interface and System UI will invoke it.
+ */
+interface IUndoTransferCallback {
+
+ /**
+ * Invoked by SystemUI when the user requests to undo the media transfer that just occurred.
+ *
+ * Implementors of this method are repsonsible for actually undoing the transfer.
+ */
+ oneway void onUndoTriggered();
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 54c798c..5d092d0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -55,8 +55,8 @@
// See IStartingWindow.aidl
public static final String KEY_EXTRA_SHELL_STARTING_WINDOW =
"extra_shell_starting_window";
- // See ISmartspaceTransitionController.aidl
- public static final String KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER = "smartspace_transition";
+ // See ISysuiUnlockAnimationController.aidl
+ public static final String KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER = "unlock_animation";
// See IRecentTasks.aidl
public static final String KEY_EXTRA_RECENT_TASKS = "recent_tasks";
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 f2f382d..ace7938 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
@@ -277,7 +277,6 @@
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();
@@ -332,13 +331,6 @@
}
} else {
wct = null;
- if (mOpeningLeashes != null) {
- // TODO: the launcher animation should handle this
- for (int i = 0; i < mOpeningLeashes.size(); ++i) {
- t.show(mOpeningLeashes.get(i));
- t.setAlpha(mOpeningLeashes.get(i), 1.f);
- }
- }
if (mPipTask != null && mPipTransaction != null) {
t.show(mInfo.getChange(mPipTask).getLeash());
PictureInPictureSurfaceTransaction.apply(mPipTransaction,
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ILauncherUnlockAnimationController.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ILauncherUnlockAnimationController.aidl
new file mode 100644
index 0000000..366193c
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ILauncherUnlockAnimationController.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.system.smartspace;
+
+import com.android.systemui.shared.system.smartspace.SmartspaceState;
+
+// Methods for System UI to interface with Launcher to perform the unlock animation.
+interface ILauncherUnlockAnimationController {
+ // Prepares Launcher for the unlock animation by setting scale/alpha/etc. to their starting
+ // values.
+ void prepareForUnlock(boolean willAnimateSmartspace, int selectedPage);
+
+ // Set the unlock percentage. This is used when System UI is controlling each frame of the
+ // unlock animation, such as during a swipe to unlock touch gesture.
+ oneway void setUnlockAmount(float amount);
+
+ // Play a full unlock animation from 0f to 1f. This is used when System UI is unlocking from a
+ // single action, such as biometric auth, and doesn't need to control individual frames.
+ oneway void playUnlockAnimation(boolean unlocked, long duration);
+
+ // Set the selected page on Launcher's smartspace.
+ oneway void setSmartspaceSelectedPage(int selectedPage);
+
+ // Set the visibility of Launcher's smartspace.
+ void setSmartspaceVisibility(int visibility);
+
+ // Tell SystemUI the smartspace's current state. Launcher code should call this whenever the
+ // smartspace state may have changed.
+ oneway void dispatchSmartspaceStateToSysui();
+}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceCallback.aidl
deleted file mode 100644
index 511df4c..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceCallback.aidl
+++ /dev/null
@@ -1,34 +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.systemui.shared.system.smartspace;
-
-import com.android.systemui.shared.system.smartspace.SmartspaceState;
-
-// Methods for getting and setting the state of a SmartSpace. This is used to allow a remote process
-// (such as System UI) to sync with and control a SmartSpace view hosted in another process (such as
-// Launcher).
-interface ISmartspaceCallback {
-
- // Return information about the state of the SmartSpace, including location on-screen and
- // currently selected page.
- SmartspaceState getSmartspaceState();
-
- // Set the currently selected page of this SmartSpace.
- oneway void setSelectedPage(int selectedPage);
-
- oneway void setVisibility(int visibility);
-}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl
new file mode 100644
index 0000000..cf83f62
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.system.smartspace;
+
+import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController;
+import com.android.systemui.shared.system.smartspace.SmartspaceState;
+
+// System UI unlock controller. Launcher will provide a LauncherUnlockAnimationController to this
+// controller, which System UI will use to control the unlock animation within the Launcher window.
+interface ISysuiUnlockAnimationController {
+ // Provides an implementation of the LauncherUnlockAnimationController to System UI, so that
+ // SysUI can use it to control the unlock animation in the launcher window.
+ oneway void setLauncherUnlockController(ILauncherUnlockAnimationController callback);
+
+ // Called by Launcher whenever anything happens to change the state of its smartspace. System UI
+ // proactively saves this and uses it to perform the unlock animation without needing to make a
+ // blocking query to Launcher asking about the smartspace state.
+ oneway void onLauncherSmartspaceStateUpdated(in SmartspaceState state);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
index 2d51c4d..d7e61d6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
@@ -28,15 +28,18 @@
class SmartspaceState() : Parcelable {
var boundsOnScreen: Rect = Rect()
var selectedPage = 0
+ var visibleOnScreen = false
constructor(parcel: Parcel) : this() {
this.boundsOnScreen = parcel.readParcelable(Rect::javaClass.javaClass.classLoader)
this.selectedPage = parcel.readInt()
+ this.visibleOnScreen = parcel.readBoolean()
}
override fun writeToParcel(dest: Parcel?, flags: Int) {
dest?.writeParcelable(boundsOnScreen, 0)
dest?.writeInt(selectedPage)
+ dest?.writeBoolean(visibleOnScreen)
}
override fun describeContents(): Int {
@@ -44,7 +47,9 @@
}
override fun toString(): String {
- return "boundsOnScreen: $boundsOnScreen, selectedPage: $selectedPage"
+ return "boundsOnScreen: $boundsOnScreen, " +
+ "selectedPage: $selectedPage, " +
+ "visibleOnScreen: $visibleOnScreen"
}
companion object CREATOR : Parcelable.Creator<SmartspaceState> {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 25b5511..8eb5289 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -20,6 +20,7 @@
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
+import static com.android.keyguard.KeyguardClockSwitch.SMALL;
import android.app.WallpaperManager;
import android.content.res.Resources;
@@ -41,7 +42,6 @@
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -86,6 +86,9 @@
private AnimatableClockController mLargeClockViewController;
private FrameLayout mLargeClockFrame; // centered clock
+ @KeyguardClockSwitch.ClockSize
+ private int mCurrentClockSize = SMALL;
+
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final KeyguardBypassController mBypassController;
@@ -110,7 +113,6 @@
private View mSmartspaceView;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
- private SmartspaceTransitionController mSmartspaceTransitionController;
private boolean mOnlyClock = false;
private Executor mUiExecutor;
@@ -136,7 +138,6 @@
KeyguardBypassController bypassController,
LockscreenSmartspaceController smartspaceController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
- SmartspaceTransitionController smartspaceTransitionController,
SecureSettings secureSettings,
@Main Executor uiExecutor,
@Main Resources resources) {
@@ -155,7 +156,22 @@
mSecureSettings = secureSettings;
mUiExecutor = uiExecutor;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
- mSmartspaceTransitionController = smartspaceTransitionController;
+ mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
+ new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
+ @Override
+ public void onSmartspaceSharedElementTransitionStarted() {
+ // The smartspace needs to be able to translate out of bounds in order to
+ // end up where the launcher's smartspace is, while its container is being
+ // swiped off the top of the screen.
+ setClipChildrenForUnlock(false);
+ }
+
+ @Override
+ public void onUnlockAnimationFinished() {
+ // For performance reasons, reset this once the unlock animation ends.
+ setClipChildrenForUnlock(true);
+ }
+ });
}
/**
@@ -236,7 +252,7 @@
mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0);
updateClockLayout();
- mSmartspaceTransitionController.setLockscreenSmartspace(mSmartspaceView);
+ mKeyguardUnlockAnimationController.setLockscreenSmartspace(mSmartspaceView);
}
mSecureSettings.registerContentObserver(
@@ -293,6 +309,8 @@
return;
}
+ mCurrentClockSize = clockSize;
+
boolean appeared = mView.switchToClock(clockSize, animate);
if (animate && appeared && clockSize == LARGE) {
mLargeClockViewController.animateAppear();
@@ -368,7 +386,14 @@
final Set<View> excludedViews = new HashSet<>();
if (mSmartspaceView != null) {
- excludedViews.add(mSmartspaceView);
+ excludedViews.add(mStatusArea);
+ }
+
+ // Don't change the alpha of the invisible clock.
+ if (mCurrentClockSize == LARGE) {
+ excludedViews.add(mClockFrame);
+ } else {
+ excludedViews.add(mLargeClockFrame);
}
setChildrenAlphaExcluding(alpha, excludedViews);
@@ -449,4 +474,16 @@
mUiExecutor.execute(() -> displayClock(KeyguardClockSwitch.SMALL, /* animate */ true));
}
}
+
+ /**
+ * Sets the clipChildren property on relevant views, to allow the smartspace to draw out of
+ * bounds during the unlock transition.
+ */
+ private void setClipChildrenForUnlock(boolean clip) {
+ mView.setClipChildren(clip);
+
+ if (mStatusArea != null) {
+ mStatusArea.setClipChildren(clip);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 8bf890d..a5fe0ef 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -22,7 +22,6 @@
import com.android.keyguard.KeyguardClockSwitch.ClockSize;
import com.android.systemui.communal.CommunalStateController;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -55,7 +54,6 @@
private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final KeyguardStateController mKeyguardStateController;
- private SmartspaceTransitionController mSmartspaceTransitionController;
private final Rect mClipBounds = new Rect();
@Inject
@@ -69,7 +67,6 @@
ConfigurationController configurationController,
DozeParameters dozeParameters,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
- SmartspaceTransitionController smartspaceTransitionController,
ScreenOffAnimationController screenOffAnimationController) {
super(keyguardStatusView);
mKeyguardSliceViewController = keyguardSliceViewController;
@@ -82,7 +79,6 @@
keyguardStateController, dozeParameters, screenOffAnimationController,
/* animateYPos= */ true, /* visibleOnCommunal= */ false);
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
- mSmartspaceTransitionController = smartspaceTransitionController;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index ae0702c..caf0307 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -59,6 +59,7 @@
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
@@ -91,6 +92,7 @@
private final WindowManager.LayoutParams mWindowLayoutParams;
private final PhoneWindow mWindow;
private final TimeoutHandler mTimeoutHandler;
+ private final AccessibilityManager mAccessibilityManager;
private final DraggableConstraintLayout mView;
private final ImageView mImagePreview;
@@ -98,6 +100,7 @@
private final ScreenshotActionChip mEditChip;
private final ScreenshotActionChip mRemoteCopyChip;
private final View mActionContainerBackground;
+ private final View mDismissButton;
private Runnable mOnSessionCompleteListener;
@@ -113,6 +116,8 @@
final Context displayContext = context.createDisplayContext(getDefaultDisplay());
mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
+ mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+
mWindowManager = mContext.getSystemService(WindowManager.class);
mTimeoutHandler = timeoutHandler;
@@ -135,10 +140,13 @@
mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
+ mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button));
mView.setOnDismissCallback(this::hideImmediate);
mView.setOnInteractionCallback(() -> mTimeoutHandler.resetTimeout());
+ mDismissButton.setOnClickListener(view -> animateOut());
+
mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
mRemoteCopyChip.setIcon(
Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
@@ -158,10 +166,10 @@
withWindowAttached(() -> {
mWindow.setContentView(mView);
updateInsets(mWindowManager.getCurrentWindowMetrics().getWindowInsets());
- getEnterAnimation().start();
+ mView.post(() -> getEnterAnimation().start());
});
- mTimeoutHandler.setOnTimeoutRunnable(() -> animateOut());
+ mTimeoutHandler.setOnTimeoutRunnable(this::animateOut);
mCloseDialogsReceiver = new BroadcastReceiver() {
@Override
@@ -226,8 +234,8 @@
mView.getLocationOnScreen(pt);
Rect rect = new Rect(pt[0], pt[1], pt[0] + mView.getWidth(),
pt[1] + mView.getHeight());
- if (!rect.contains((int) motionEvent.getRawX(),
- (int) motionEvent.getRawY())) {
+ if (!rect.contains(
+ (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
animateOut();
}
}
@@ -311,11 +319,15 @@
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
mView.setAlpha(0);
+ mDismissButton.setVisibility(View.GONE);
final View previewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
final View actionBackground = requireNonNull(
mView.findViewById(R.id.actions_container_background));
mImagePreview.setVisibility(View.VISIBLE);
mActionContainerBackground.setVisibility(View.VISIBLE);
+ if (mAccessibilityManager.isEnabled()) {
+ mDismissButton.setVisibility(View.VISIBLE);
+ }
anim.addUpdateListener(animation -> {
mView.setAlpha(animation.getAnimatedFraction());
@@ -385,7 +397,7 @@
private void reset() {
mView.setTranslationX(0);
- mView.setAlpha(1);
+ mView.setAlpha(0);
mTimeoutHandler.cancelTimeout();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 9bc3f17..b96c5ae 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -48,7 +48,6 @@
import com.android.systemui.recents.Recents;
import com.android.systemui.screenshot.dagger.ScreenshotModule;
import com.android.systemui.settings.dagger.SettingsModule;
-import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -188,12 +187,6 @@
return SystemUIFactory.getInstance();
}
- @SysUISingleton
- @Provides
- static SmartspaceTransitionController provideSmartspaceTransitionController() {
- return new SmartspaceTransitionController();
- }
-
// TODO: This should provided by the WM component
/** Provides Optional of BubbleManager */
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationHost.java b/packages/SystemUI/src/com/android/systemui/dreams/ComplicationHost.java
deleted file mode 100644
index 7c3152f..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationHost.java
+++ /dev/null
@@ -1,48 +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.systemui.dreams;
-
-import android.view.View;
-
-/**
- * A collection of interfaces related to hosting a complication.
- */
-public abstract class ComplicationHost {
- /**
- * An interface for the callback from the complication provider to indicate when the
- * complication is ready.
- */
- public interface CreationCallback {
- /**
- * Called to inform the complication view is ready to be placed within the visual space.
- * @param view The view representing the complication.
- * @param layoutParams The parameters to create the view with.
- */
- void onCreated(View view, ComplicationHostView.LayoutParams layoutParams);
- }
-
- /**
- * An interface for the callback from the complication provider to signal interactions in the
- * complication.
- */
- public interface InteractionCallback {
- /**
- * Called to signal the calling complication would like to exit the dream.
- */
- void onExit();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationHostView.java b/packages/SystemUI/src/com/android/systemui/dreams/ComplicationHostView.java
deleted file mode 100644
index a67dd5c..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationHostView.java
+++ /dev/null
@@ -1,44 +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.systemui.dreams;
-
-import android.content.Context;
-import android.util.AttributeSet;
-
-import androidx.constraintlayout.widget.ConstraintLayout;
-
-/**
- * {@link ComplicationHostView} is the container view for housing complications above of a dream.
- */
-public class ComplicationHostView extends ConstraintLayout {
- public ComplicationHostView(Context context) {
- super(context, null);
- }
-
- public ComplicationHostView(Context context, AttributeSet attrs) {
- super(context, attrs, 0);
- }
-
- public ComplicationHostView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr, 0);
- }
-
- public ComplicationHostView(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/ComplicationProvider.java
deleted file mode 100644
index 099e379..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationProvider.java
+++ /dev/null
@@ -1,36 +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.systemui.dreams;
-
-import android.content.Context;
-
-/**
- * {@link ComplicationProvider} is an interface for defining entities that can supply complications
- * to show over a dream. Presentation components such as the {@link DreamOverlayService} supply
- * implementations with the necessary context for constructing such overlays.
- */
-public interface ComplicationProvider {
- /**
- * Called when the {@link ComplicationHost} requests the associated complication be produced.
- *
- * @param context The {@link Context} used to construct the view.
- * @param creationCallback The callback to inform the complication has been created.
- * @param interactionCallback The callback to inform the complication has been interacted with.
- */
- void onCreateComplication(Context context, ComplicationHost.CreationCallback creationCallback,
- ComplicationHost.InteractionCallback interactionCallback);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 5b46079..be76e8f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -25,11 +25,11 @@
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
-import androidx.constraintlayout.widget.ConstraintLayout;
-
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.complication.ComplicationHostViewController;
+import com.android.systemui.dreams.complication.dagger.ComplicationHostViewComponent;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.dreams.dagger.DreamOverlayModule;
import com.android.systemui.util.ViewController;
@@ -47,6 +47,8 @@
private final int mDreamOverlayNotificationsDragAreaHeight;
private final DreamOverlayStatusBarViewController mStatusBarViewController;
+ private final ComplicationHostViewController mComplicationHostViewController;
+
// The dream overlay's content view, which is located below the status bar (in z-order) and is
// the space into which widgets are placed.
private final ViewGroup mDreamOverlayContentView;
@@ -74,6 +76,13 @@
final int childCount = mDreamOverlayContentView.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = mDreamOverlayContentView.getChildAt(i);
+
+ if (mComplicationHostViewController.getView() == child) {
+ region.op(mComplicationHostViewController.getTouchRegions(),
+ Region.Op.UNION);
+ continue;
+ }
+
if (child.getGlobalVisibleRect(rect)) {
region.op(rect, Region.Op.UNION);
}
@@ -93,6 +102,7 @@
@Inject
public DreamOverlayContainerViewController(
DreamOverlayContainerView containerView,
+ ComplicationHostViewComponent.Factory complicationHostViewFactory,
@Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
DreamOverlayStatusBarViewController statusBarViewController,
@Main Handler handler,
@@ -106,6 +116,13 @@
mView.getResources().getDimensionPixelSize(
R.dimen.dream_overlay_notifications_drag_area_height);
+ mComplicationHostViewController = complicationHostViewFactory.create().getController();
+ final View view = mComplicationHostViewController.getView();
+
+ mDreamOverlayContentView.addView(view,
+ new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+
mHandler = handler;
mMaxBurnInOffset = maxBurnInOffset;
mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval;
@@ -114,6 +131,7 @@
@Override
protected void onInit() {
mStatusBarViewController.init();
+ mComplicationHostViewController.init();
}
@Override
@@ -130,18 +148,10 @@
.removeOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
}
- void addOverlay(View overlayView, ConstraintLayout.LayoutParams layoutParams) {
- mDreamOverlayContentView.addView(overlayView, layoutParams);
- }
-
View getContainerView() {
return mView;
}
- void removeAllOverlays() {
- mDreamOverlayContentView.removeAllViews();
- }
-
@VisibleForTesting
int getDreamOverlayNotificationsDragAreaHeight() {
return mDreamOverlayNotificationsDragAreaHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index a53120f..16ed1fb 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -24,10 +24,13 @@
import android.view.WindowManager;
import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.ViewModelStore;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.PhoneWindow;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import java.util.concurrent.Executor;
@@ -47,8 +50,6 @@
private final Context mContext;
// The Executor ensures actions and ui updates happen on the same thread.
private final Executor mExecutor;
- // The state controller informs the service of updates to the complications present.
- private final DreamOverlayStateController mStateController;
// A controller for the dream overlay container view (which contains both the status bar and the
// content area).
private final DreamOverlayContainerViewController mDreamOverlayContainerViewController;
@@ -56,47 +57,51 @@
// A reference to the {@link Window} used to hold the dream overlay.
private Window mWindow;
- private final DreamOverlayStateController.Callback mOverlayStateCallback =
- new DreamOverlayStateController.Callback() {
- @Override
- public void onComplicationsChanged() {
- mExecutor.execute(() -> reloadComplicationsLocked());
- }
- };
+ private final Complication.Host mHost = new Complication.Host() {
+ @Override
+ public void requestExitDream() {
+ mExecutor.execute(DreamOverlayService.this::requestExit);
+ }
+ };
+
+ private final LifecycleRegistry mLifecycleRegistry;
+
+ private ViewModelStore mViewModelStore = new ViewModelStore();
@Inject
public DreamOverlayService(
Context context,
@Main Executor executor,
- DreamOverlayStateController overlayStateController,
DreamOverlayComponent.Factory dreamOverlayComponentFactory) {
mContext = context;
mExecutor = executor;
- mStateController = overlayStateController;
- mDreamOverlayContainerViewController =
- dreamOverlayComponentFactory.create().getDreamOverlayContainerViewController();
- mStateController.addCallback(mOverlayStateCallback);
+ final DreamOverlayComponent component =
+ dreamOverlayComponentFactory.create(mViewModelStore, mHost);
+ mDreamOverlayContainerViewController = component.getDreamOverlayContainerViewController();
+ setCurrentState(Lifecycle.State.CREATED);
+ mLifecycleRegistry = component.getLifecycleRegistry();
+ }
+
+ private void setCurrentState(Lifecycle.State state) {
+ mExecutor.execute(() -> mLifecycleRegistry.setCurrentState(state));
}
@Override
public void onDestroy() {
+ setCurrentState(Lifecycle.State.DESTROYED);
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
windowManager.removeView(mWindow.getDecorView());
- mStateController.removeCallback(mOverlayStateCallback);
super.onDestroy();
}
@Override
public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
- mExecutor.execute(() -> addOverlayWindowLocked(layoutParams));
- }
-
- private void reloadComplicationsLocked() {
- mDreamOverlayContainerViewController.removeAllOverlays();
- for (ComplicationProvider overlayProvider : mStateController.getComplications()) {
- addComplication(overlayProvider);
- }
+ setCurrentState(Lifecycle.State.STARTED);
+ mExecutor.execute(() -> {
+ addOverlayWindowLocked(layoutParams);
+ setCurrentState(Lifecycle.State.RESUMED);
+ });
}
/**
@@ -129,20 +134,5 @@
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
windowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
- mExecutor.execute(this::reloadComplicationsLocked);
- }
-
- @VisibleForTesting
- protected void addComplication(ComplicationProvider provider) {
- provider.onCreateComplication(mContext,
- (view, layoutParams) -> {
- // Always move UI related work to the main thread.
- mExecutor.execute(() -> mDreamOverlayContainerViewController
- .addOverlay(view, layoutParams));
- },
- () -> {
- // The Callback is set on the main thread.
- mExecutor.execute(this::requestExit);
- });
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index 66679bb..e838848 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -17,18 +17,17 @@
package com.android.systemui.dreams;
import androidx.annotation.NonNull;
-import androidx.concurrent.futures.CallbackToFutureAdapter;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.statusbar.policy.CallbackController;
-import com.google.common.util.concurrent.ListenableFuture;
-
import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashMap;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -42,35 +41,6 @@
@SysUISingleton
public class DreamOverlayStateController implements
CallbackController<DreamOverlayStateController.Callback> {
- // A counter for guaranteeing unique complications tokens within the scope of this state
- // controller.
- private int mNextComplicationTokenId = 0;
-
- /**
- * {@link ComplicationToken} provides a unique key for identifying {@link ComplicationProvider}
- * instances registered with {@link DreamOverlayStateController}.
- */
- public static class ComplicationToken {
- private final int mId;
-
- private ComplicationToken(int id) {
- mId = id;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof ComplicationToken)) return false;
- ComplicationToken that = (ComplicationToken) o;
- return mId == that.mId;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mId);
- }
- }
-
/**
* Callback for dream overlay events.
*/
@@ -84,7 +54,8 @@
private final Executor mExecutor;
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
- private final HashMap<ComplicationToken, ComplicationProvider> mComplications = new HashMap<>();
+
+ private final Collection<Complication> mComplications = new HashSet();
@VisibleForTesting
@Inject
@@ -93,47 +64,32 @@
}
/**
- * Adds a complication to be presented on top of dreams.
- * @param provider The {@link ComplicationProvider} providing the dream.
- * @return The {@link ComplicationToken} tied to the supplied {@link ComplicationProvider}.
+ * Adds a complication to be included on the dream overlay.
*/
- public ListenableFuture<ComplicationToken> addComplication(ComplicationProvider provider) {
- return CallbackToFutureAdapter.getFuture(completer -> {
- mExecutor.execute(() -> {
- final ComplicationToken token = new ComplicationToken(mNextComplicationTokenId++);
- mComplications.put(token, provider);
- notifyCallbacks();
- completer.set(token);
- });
- return "DreamOverlayStateController::addComplication";
+ public void addComplication(Complication complication) {
+ mExecutor.execute(() -> {
+ if (mComplications.add(complication)) {
+ mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
+ }
});
}
/**
- * Removes a complication from being shown on dreams.
- * @param token The {@link ComplicationToken} associated with the {@link ComplicationProvider}
- * to be removed.
- * @return The removed {@link ComplicationProvider}, {@code null} if not found.
+ * Removes a complication from inclusion on the dream overlay.
*/
- public ListenableFuture<ComplicationProvider> removeComplication(ComplicationToken token) {
- return CallbackToFutureAdapter.getFuture(completer -> {
- mExecutor.execute(() -> {
- final ComplicationProvider removedComplication = mComplications.remove(token);
-
- if (removedComplication != null) {
- notifyCallbacks();
- }
- completer.set(removedComplication);
- });
-
- return "DreamOverlayStateController::removeComplication";
+ public void removeComplication(Complication complication) {
+ mExecutor.execute(() -> {
+ if (mComplications.remove(complication)) {
+ mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
+ }
});
}
- private void notifyCallbacks() {
- for (Callback callback : mCallbacks) {
- callback.onComplicationsChanged();
- }
+ /**
+ * Returns collection of present {@link Complication}.
+ */
+ public Collection<Complication> getComplications() {
+ return Collections.unmodifiableCollection(mComplications);
}
@Override
@@ -161,12 +117,4 @@
mCallbacks.remove(callback);
});
}
-
- /**
- * Returns all registered {@link ComplicationProvider} instances.
- * @return A collection of {@link ComplicationProvider}.
- */
- public Collection<ComplicationProvider> getComplications() {
- return mComplications.values();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
new file mode 100644
index 0000000..96cf50d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
@@ -0,0 +1,210 @@
+/*
+ * 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.dreams.complication;
+
+import android.annotation.IntDef;
+import android.view.View;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * {@link Complication} is an interface for defining a complication, a visual component rendered
+ * above a dream. {@link Complication} instances encapsulate the logic for generating the view to be
+ * shown, along with the supporting control/logic. The decision for including the
+ * {@link Complication} is not the responsibility of the {@link Complication}. This is instead
+ * handled by domain logic and invocations to add and remove the {@link Complication} through
+ * {@link com.android.systemui.dreams.DreamOverlayStateController#addComplication(Complication)} and
+ * {@link com.android.systemui.dreams.DreamOverlayStateController#removeComplication(Complication)}.
+ * A {@link Complication} also does not represent a specific instance of the view. Instead, it
+ * should be viewed as a provider, where view instances are requested from it. The associated
+ * {@link ViewHolder} interface is requested for each view request. This object is retained for the
+ * view's lifetime, providing a container for any associated logic. The complication rendering
+ * system will consult this {@link ViewHolder} for {@link View} to show and
+ * {@link ComplicationLayoutParams} to position the view. {@link ComplicationLayoutParams} allow for
+ * specifying the sizing and position of the {@link Complication}.
+ *
+ * The following code sample exhibits the entities and lifecycle involved with a
+ * {@link Complication}.
+ *
+ * <pre>{@code
+ * // This component allows for the complication to generate a new ViewHolder for every request.
+ * @Subcomponent
+ * interface ExampleViewHolderComponent {
+ * @Subcomponent.Factory
+ * interface Factory {
+ * ExampleViewHolderComponent create();
+ * }
+ *
+ * ExampleViewHolder getViewHolder();
+ * }
+ *
+ * // An example entity that controls whether or not a complication should be included on dreams.
+ * // Note how the complication is tracked by reference for removal.
+ * public class ExampleComplicationProvider {
+ * private final DreamOverlayStateController mDreamOverlayStateController;
+ * private final ExampleComplication mComplication;
+ * @Inject
+ * public ExampleComplicationProvider(
+ * ExampleComplication complication,
+ * DreamOverlayStateController stateController) {
+ * mDreamOverlayStateController = stateController;
+ * mComplication = complication;
+ * }
+ *
+ * public void onShowConditionsMet(boolean met) {
+ * if (met) {
+ * mDreamOverlayStateController.addComplication(mComplication);
+ * } else {
+ * mDreamOverlayStateController.removeComplication(mComplication);
+ * }
+ * }
+ * }
+ *
+ * // An example complication. Note how a factory is created to supply a unique ViewHolder for each
+ * // request. Also, there is no particular view instance members defined in the complication.
+ * class ExampleComplication implements Complication {
+ * private final ExampleViewHolderComponent.Factory mFactory;
+ * @Inject
+ * public ExampleComplication(ExampleViewHolderComponent.Factory viewHolderComponentFactory) {
+ * mFactory = viewHolderComponentFactory;
+ * }
+ *
+ * @Override
+ * public ViewHolder createView(ComplicationViewModel model) {
+ * return mFactory.create().getViewHolder();
+ * }
+ * }
+ *
+ * // Not every ViewHolder needs to include a view controller. It is included here as an example of
+ * // how such logic can be contained and associated with the ViewHolder lifecycle.
+ * class ExampleViewController extends ViewController<FrameLayout> {
+ * protected ExampleViewController(FrameLayout view) {
+ * super(view);
+ * }
+ *
+ * @Override
+ * protected void onViewAttached() { }
+ *
+ * @Override
+ * protected void onViewDetached() { }
+ * }
+ *
+ * // An example ViewHolder. This is the correct place to contain any value/logic associated with a
+ * // particular instance of the ComplicationView.
+ * class ExampleViewHolder implements Complication.ViewHolder {
+ * final FrameLayout mView;
+ * final ExampleViewController mController;
+ *
+ * @Inject
+ * public ExampleViewHolder(Context context) {
+ * mView = new FrameLayout(context);
+ * mController = new ExampleViewController(mView);
+ * }
+ * @Override
+ * public View getView() {
+ * return mView;
+ * }
+ *
+ * @Override
+ * public ComplicationLayoutParams getLayoutParams() {
+ * return new ComplicationLayoutParams(
+ * 200,
+ * 100,
+ * ComplicationLayoutParams.POSITION_TOP | ComplicationLayoutParams.DIRECTION_END,
+ * ComplicationLayoutParams.DIRECTION_DOWN,
+ * 4);
+ * }
+ * }
+ * }
+ * </pre>
+ */
+public interface Complication {
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "CATEGORY_" }, value = {
+ CATEGORY_STANDARD,
+ CATEGORY_SYSTEM,
+ })
+
+ @interface Category {}
+ /**
+ * {@code CATEGORY_STANDARD} indicates the complication is a normal component. Rules and
+ * settings, such as hiding all complications, will apply to this complication.
+ */
+ int CATEGORY_STANDARD = 1 << 0;
+ /**
+ * {@code CATEGORY_SYSTEM} indicates complications driven by SystemUI. Usually, these are
+ * core components that are not user controlled. These can potentially deviate from given
+ * rule sets that would normally apply to {@code CATEGORY_STANDARD}.
+ */
+ int CATEGORY_SYSTEM = 1 << 1;
+
+ /**
+ * The {@link Host} interface specifies a way a {@link Complication} to communicate with its
+ * parent entity for information and actions.
+ */
+ interface Host {
+ /**
+ * Called to signal a {@link Complication} has requested to exit the dream.
+ */
+ void requestExitDream();
+ }
+
+ /**
+ * Returned through {@link Complication#createView(ComplicationViewModel)}, {@link ViewHolder}
+ * is a container for a single {@link Complication} instance. The {@link Host} guarantees that
+ * the {@link ViewHolder} will be retained for the lifetime of the {@link Complication}
+ * instance's user. The view is responsible for providing the view that represents the
+ * {@link Complication}. This object is the proper place to store any related entities, such as
+ * a {@link com.android.systemui.util.ViewController} for the view.
+ */
+ interface ViewHolder {
+ /**
+ * Returns the {@link View} associated with the {@link ViewHolder}. This {@link View} should
+ * be stable and generated once.
+ * @return
+ */
+ View getView();
+
+ /**
+ * Returns the {@link Category} associated with the {@link Complication}. {@link Category}
+ * is a grouping which helps define the relationship of the {@link Complication} to
+ * System UI and the rest of the system. It is used for presentation and other decisions.
+ */
+ @Complication.Category
+ default int getCategory() {
+ return Complication.CATEGORY_STANDARD;
+ }
+
+ /**
+ * Returns the {@link ComplicationLayoutParams} associated with this complication. The
+ * values expressed here are treated as preference rather than requirement. The hosting
+ * entity is free to modify/interpret the parameters as deemed fit.
+ */
+ ComplicationLayoutParams getLayoutParams();
+ }
+
+ /**
+ * Generates a {@link ViewHolder} for the {@link Complication}. This captures both the view and
+ * control logic for a single instance of the complication. The {@link Complication} may be
+ * asked at any time to generate another view.
+ * @param model The {@link ComplicationViewModel} associated with this particular
+ * {@link Complication} instance.
+ * @return a {@link ViewHolder} for this {@link Complication} instance.
+ */
+ ViewHolder createView(ComplicationViewModel model);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java
new file mode 100644
index 0000000..76818fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication;
+
+import androidx.lifecycle.LiveData;
+
+import com.android.systemui.dreams.DreamOverlayStateController;
+
+import java.util.Collection;
+
+import javax.inject.Inject;
+
+/**
+ * {@link ComplicationCollectionLiveData} wraps
+ * {@link DreamOverlayStateController#getComplications()} to provide an observable
+ * {@link Complication} data set tied to a lifecycle. This should not be directly accessed. Instead,
+ * clients should access the data from {@link ComplicationCollectionViewModel}.
+ */
+public class ComplicationCollectionLiveData extends LiveData<Collection<Complication>> {
+ final DreamOverlayStateController mDreamOverlayStateController;
+
+ final DreamOverlayStateController.Callback mStateControllerCallback;
+
+ {
+ mStateControllerCallback = new DreamOverlayStateController.Callback() {
+ @Override
+ public void onComplicationsChanged() {
+ setValue(mDreamOverlayStateController.getComplications());
+ }
+
+ };
+ }
+
+ @Inject
+ public ComplicationCollectionLiveData(DreamOverlayStateController stateController) {
+ super();
+ mDreamOverlayStateController = stateController;
+ }
+
+ @Override
+ protected void onActive() {
+ super.onActive();
+ mDreamOverlayStateController.addCallback(mStateControllerCallback);
+ setValue(mDreamOverlayStateController.getComplications());
+ }
+
+ @Override
+ protected void onInactive() {
+ mDreamOverlayStateController.removeCallback(mStateControllerCallback);
+ super.onInactive();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionViewModel.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionViewModel.java
new file mode 100644
index 0000000..7190d7a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionViewModel.java
@@ -0,0 +1,63 @@
+/*
+ * 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.dreams.complication;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Transformations;
+import androidx.lifecycle.ViewModel;
+
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+/**
+ * {@link ComplicationCollectionViewModel} is an abstraction for observing and accessing
+ * {@link ComplicationViewModel} for registered {@link Complication}.
+ */
+public class ComplicationCollectionViewModel extends ViewModel {
+ private final LiveData<Collection<ComplicationViewModel>> mComplications;
+ private final ComplicationViewModelTransformer mTransformer;
+
+ /**
+ * Injectable constructor for {@link ComplicationCollectionViewModel}. Note that this cannot
+ * be implicitly injected. Clients must bind scoped instance values through the corresponding
+ * dagger subcomponent.
+ */
+ @Inject
+ public ComplicationCollectionViewModel(
+ ComplicationCollectionLiveData complications,
+ ComplicationViewModelTransformer transformer) {
+ mComplications = Transformations.map(complications, collection -> convert(collection));
+ mTransformer = transformer;
+ }
+
+ private Collection<ComplicationViewModel> convert(Collection<Complication> complications) {
+ return complications
+ .stream()
+ .map(complication -> mTransformer.getViewModel(complication))
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * Returns {@link LiveData} for the collection of {@link Complication} represented as
+ * {@link ComplicationViewModel}.
+ */
+ public LiveData<Collection<ComplicationViewModel>> getComplications() {
+ return mComplications;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
new file mode 100644
index 0000000..f627f15
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
@@ -0,0 +1,138 @@
+/*
+ * 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.dreams.complication;
+
+import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewComponent.SCOPED_COMPLICATIONS_LAYOUT;
+import static com.android.systemui.dreams.complication.dagger.ComplicationModule.SCOPED_COMPLICATIONS_MODEL;
+
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.View;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.lifecycle.LifecycleOwner;
+
+import com.android.systemui.util.ViewController;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * The {@link ComplicationHostViewController} is responsible for displaying complications within
+ * a given container. It monitors the available {@link Complication} instances from
+ * {@link com.android.systemui.dreams.DreamOverlayStateController} and inserts/removes them through
+ * a {@link ComplicationLayoutEngine}.
+ */
+public class ComplicationHostViewController extends ViewController<ConstraintLayout> {
+ private final ComplicationLayoutEngine mLayoutEngine;
+ private final LifecycleOwner mLifecycleOwner;
+ private final ComplicationCollectionViewModel mComplicationCollectionViewModel;
+ private final HashMap<ComplicationId, Complication.ViewHolder> mComplications = new HashMap<>();
+
+ @Inject
+ protected ComplicationHostViewController(
+ @Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout view,
+ ComplicationLayoutEngine layoutEngine,
+ LifecycleOwner lifecycleOwner,
+ @Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel) {
+ super(view);
+ mLayoutEngine = layoutEngine;
+ mLifecycleOwner = lifecycleOwner;
+ mComplicationCollectionViewModel = viewModel;
+ }
+
+ @Override
+ protected void onInit() {
+ super.onInit();
+ mComplicationCollectionViewModel.getComplications().observe(mLifecycleOwner,
+ complicationViewModels -> updateComplications(complicationViewModels));
+ }
+
+ /**
+ * Returns the region in display space occupied by complications. Touches in this region
+ * (composed of a collection of individual rectangular regions) should be directed to the
+ * complications rather than the region underneath.
+ */
+ public Region getTouchRegions() {
+ final Region region = new Region();
+ final Rect rect = new Rect();
+ final int childCount = mView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = mView.getChildAt(i);
+ if (child.getGlobalVisibleRect(rect)) {
+ region.op(rect, Region.Op.UNION);
+ }
+ }
+
+ return region;
+ }
+
+ private void updateComplications(Collection<ComplicationViewModel> complications) {
+ final Collection<ComplicationId> ids = complications.stream()
+ .map(complicationViewModel -> complicationViewModel.getId())
+ .collect(Collectors.toSet());
+
+ final Collection<ComplicationId> removedComplicationIds =
+ mComplications.keySet().stream()
+ .filter(complicationId -> !ids.contains(complicationId))
+ .collect(Collectors.toSet());
+
+ // Trim removed complications
+ removedComplicationIds.forEach(complicationId -> {
+ mLayoutEngine.removeComplication(complicationId);
+ mComplications.remove(complicationId);
+ });
+
+ // Add new complications
+ final Collection<ComplicationViewModel> newComplications = complications
+ .stream()
+ .filter(complication -> !mComplications.containsKey(complication.getId()))
+ .collect(Collectors.toSet());
+
+ newComplications
+ .forEach(complication -> {
+ final ComplicationId id = complication.getId();
+ final Complication.ViewHolder viewHolder = complication.getComplication()
+ .createView(complication);
+ mComplications.put(id, viewHolder);
+ mLayoutEngine.addComplication(id, viewHolder.getView(),
+ viewHolder.getLayoutParams(), viewHolder.getCategory());
+ });
+ }
+
+ @Override
+ protected void onViewAttached() {
+ }
+
+ @Override
+ protected void onViewDetached() {
+ }
+
+ /**
+ * Exposes the associated {@link View}. Since this {@link View} is instantiated through dagger
+ * in the {@link ComplicationHostViewController} constructor, the
+ * {@link ComplicationHostViewController} is responsible for surfacing it so that it can be
+ * included in the parent view hierarchy.
+ */
+ public View getView() {
+ return mView;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationId.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationId.java
new file mode 100644
index 0000000..420359c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationId.java
@@ -0,0 +1,45 @@
+/*
+ * 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.dreams.complication;
+
+/**
+ * A {@link ComplicationId} is a value to uniquely identify a complication during the current
+ * runtime and within a particular scope. Any guarantees beyond this will need to be enforced
+ * externally.
+ */
+public class ComplicationId {
+ /**
+ * An associated factory for minting ids that are unique in for the factory's scope.
+ */
+ public static class Factory {
+ private int mNextId;
+
+ ComplicationId getNextId() {
+ return new ComplicationId(mNextId++);
+ }
+ }
+
+ private int mId;
+
+ private ComplicationId(int id) {
+ mId = id;
+ }
+
+ @Override
+ public String toString() {
+ return "ComplicationId{" + "mId=" + mId + "}";
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
new file mode 100644
index 0000000..cb24ae6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -0,0 +1,429 @@
+/*
+ * 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.dreams.complication;
+
+import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewComponent.SCOPED_COMPLICATIONS_LAYOUT;
+
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.Constraints;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * {@link ComplicationLayoutEngine} arranges a collection of {@link ComplicationViewModel} based on
+ * their layout parameters and attributes. The management of this set is done by
+ * {@link ComplicationHostViewController}.
+ */
+public class ComplicationLayoutEngine {
+ public static final String TAG = "ComplicationLayoutEngine";
+
+ /**
+ * {@link ViewEntry} is an internal container, capturing information necessary for working with
+ * a particular {@link Complication} view.
+ */
+ private static class ViewEntry implements Comparable<ViewEntry> {
+ private final View mView;
+ private final ComplicationLayoutParams mLayoutParams;
+ private final Parent mParent;
+ @Complication.Category
+ private final int mCategory;
+
+ /**
+ * Default constructor. {@link Parent} allows for the {@link ViewEntry}'s surrounding
+ * view hierarchy to be accessed without traversing the entire view tree.
+ */
+ ViewEntry(View view, ComplicationLayoutParams layoutParams, int category, Parent parent) {
+ mView = view;
+ // Views that are generated programmatically do not have a unique id assigned to them
+ // at construction. A new id is assigned here to enable ConstraintLayout relative
+ // specifications. Existing ids for inflated views are not preserved.
+ // {@link Complication.ViewHolder} should not reference the root container by id.
+ mView.setId(View.generateViewId());
+ mLayoutParams = layoutParams;
+ mCategory = category;
+ mParent = parent;
+ }
+
+ /**
+ * Returns the {@link View} associated with the {@link Complication}. This is the instance
+ * passed in at construction. The reference to this {@link View} is captured when the
+ * {@link Complication} is added to the {@link ComplicationLayoutEngine}. The
+ * {@link Complication} cannot modify the {@link View} reference beyond this point.
+ */
+ private View getView() {
+ return mView;
+ }
+
+ /**
+ * Returns The {@link ComplicationLayoutParams} associated with the view.
+ */
+ public ComplicationLayoutParams getLayoutParams() {
+ return mLayoutParams;
+ }
+
+ /**
+ * Interprets the {@link #getLayoutParams()} into {@link ConstraintLayout.LayoutParams} and
+ * applies them to the view. The method accounts for the relationship of the {@link View} to
+ * the other {@link Complication} views around it. The organization of the {@link View}
+ * instances in {@link ComplicationLayoutEngine} can be seen as lists. A {@link View} is
+ * either the head of its list or a following node. This head is passed into this method,
+ * which can be a reference to the {@link View} to indicate it is the head.
+ */
+ public void applyLayoutParams(View head) {
+ // Only the basic dimension parameters from the base ViewGroup.LayoutParams are carried
+ // over verbatim from the complication specified LayoutParam. Other fields are
+ // interpreted.
+ final ConstraintLayout.LayoutParams params =
+ new Constraints.LayoutParams(mLayoutParams.width, mLayoutParams.height);
+
+ final int direction = getLayoutParams().getDirection();
+
+ // If no parent, view is the anchor. In this case, it is given the highest priority for
+ // alignment. All alignment preferences are done in relation to the parent container.
+ final boolean isRoot = head == mView;
+
+ // Each view can be seen as a vector, having a point (described here as position) and
+ // direction. When a view is the head of a position, then it is the first in a sequence
+ // of complications to appear from that position. For example, being the head for
+ // position POSITION_TOP | POSITION_END will cause the view to be shown as the first
+ // view in that corner. In this case, the positions specify which sides to align with
+ // the parent. If the view is not the head, the positions perpendicular to the direction
+ // of the view specify which side to align with the opposing side of the head view.
+ // Otherwise, the position aligns with the containing view. This means a
+ // POSITION_BOTTOM | POSITION_START with DIRECTION_UP non-head view's bottom to be
+ // aligned with the preceding view node's top and start to be aligned with the
+ // parent's start.
+ mLayoutParams.iteratePositions(position -> {
+ switch(position) {
+ case ComplicationLayoutParams.POSITION_START:
+ if (isRoot || direction != ComplicationLayoutParams.DIRECTION_END) {
+ params.startToStart = ConstraintLayout.LayoutParams.PARENT_ID;
+ } else {
+ params.startToEnd = head.getId();
+ }
+ break;
+ case ComplicationLayoutParams.POSITION_TOP:
+ if (isRoot || direction != ComplicationLayoutParams.DIRECTION_DOWN) {
+ params.topToTop = ConstraintLayout.LayoutParams.PARENT_ID;
+ } else {
+ params.topToBottom = head.getId();
+ }
+ break;
+ case ComplicationLayoutParams.POSITION_BOTTOM:
+ if (isRoot || direction != ComplicationLayoutParams.DIRECTION_UP) {
+ params.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID;
+ } else {
+ params.bottomToTop = head.getId();
+ }
+ break;
+ case ComplicationLayoutParams.POSITION_END:
+ if (isRoot || direction != ComplicationLayoutParams.DIRECTION_START) {
+ params.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
+ } else {
+ params.endToStart = head.getId();
+ }
+ break;
+ }
+ });
+
+ mView.setLayoutParams(params);
+ }
+
+ /**
+ * Informs the {@link ViewEntry}'s parent entity to remove the {@link ViewEntry} from
+ * being shown further.
+ */
+ public void remove() {
+ mParent.removeEntry(this);
+
+ ((ViewGroup) mView.getParent()).removeView(mView);
+ }
+
+ @Override
+ public int compareTo(ViewEntry viewEntry) {
+ // If the two entries have different categories, system complications take precedence.
+ if (viewEntry.mCategory != mCategory) {
+ // Note that this logic will need to be adjusted if more categories are introduced.
+ return mCategory == Complication.CATEGORY_SYSTEM ? 1 : -1;
+ }
+
+ // A higher weight indicates greater precedence if all else being equal.
+ if (viewEntry.mLayoutParams.getWeight() != mLayoutParams.getWeight()) {
+ return mLayoutParams.getWeight() > viewEntry.mLayoutParams.getWeight() ? 1 : -1;
+ }
+
+ return 0;
+ }
+
+ /**
+ * {@link Builder} allows for a multiple entities to contribute to the {@link ViewEntry}
+ * construction. This is necessary for setting an immutable parent, which might not be
+ * known until the view hierarchy is traversed.
+ */
+ private static class Builder {
+ private final View mView;
+ private final ComplicationLayoutParams mLayoutParams;
+ private final int mCategory;
+ private Parent mParent;
+
+ Builder(View view, ComplicationLayoutParams lp, @Complication.Category int category) {
+ mView = view;
+ mLayoutParams = lp;
+ mCategory = category;
+ }
+
+ /**
+ * Returns the set {@link ComplicationLayoutParams}
+ */
+ public ComplicationLayoutParams getLayoutParams() {
+ return mLayoutParams;
+ }
+
+ /**
+ * Returns the set {@link Complication.Category}.
+ */
+ @Complication.Category
+ public int getCategory() {
+ return mCategory;
+ }
+
+ /**
+ * Sets the parent. Note that this references to the entity for handling events, such as
+ * requesting the removal of the {@link View}. It is not the
+ * {@link android.view.ViewGroup} which contains the {@link View}.
+ */
+ Builder setParent(Parent parent) {
+ mParent = parent;
+ return this;
+ }
+
+ /**
+ * Builds and returns the resulting {@link ViewEntry}.
+ */
+ ViewEntry build() {
+ return new ViewEntry(mView, mLayoutParams, mCategory, mParent);
+ }
+ }
+
+ /**
+ * An interface allowing an {@link ViewEntry} to signal events.
+ */
+ interface Parent {
+ /**
+ * Indicates the {@link ViewEntry} requests removal.
+ */
+ void removeEntry(ViewEntry entry);
+ }
+ }
+
+ /**
+ * {@link PositionGroup} represents a collection of {@link Complication} at a given location.
+ * It further organizes the {@link Complication} by the direction in which they emanate from
+ * this position.
+ */
+ private static class PositionGroup implements DirectionGroup.Parent {
+ private final HashMap<Integer, DirectionGroup> mDirectionGroups = new HashMap<>();
+
+ /**
+ * Invoked by the {@link PositionGroup} holder to introduce a {@link Complication} view to
+ * this group. It is assumed that the caller has correctly identified this
+ * {@link PositionGroup} as the proper home for the {@link Complication} based on its
+ * declared position.
+ */
+ public ViewEntry add(ViewEntry.Builder entryBuilder) {
+ final int direction = entryBuilder.getLayoutParams().getDirection();
+ if (!mDirectionGroups.containsKey(direction)) {
+ mDirectionGroups.put(direction, new DirectionGroup(this));
+ }
+
+ return mDirectionGroups.get(direction).add(entryBuilder);
+ }
+
+ @Override
+ public void onEntriesChanged() {
+ // Whenever an entry is added/removed from a child {@link DirectionGroup}, it is vital
+ // that all {@link DirectionGroup} children are visited. It is possible the overall
+ // head has changed, requiring constraints to be adjusted.
+ updateViews();
+ }
+
+ private void updateViews() {
+ ViewEntry head = null;
+
+ // Identify which {@link Complication} head from the set of {@link DirectionGroup}
+ // should be treated as the {@link PositionGroup} head.
+ for (DirectionGroup directionGroup : mDirectionGroups.values()) {
+ final ViewEntry groupHead = directionGroup.getHead();
+ if (head == null || (groupHead != null && groupHead.compareTo(head) > 0)) {
+ head = groupHead;
+ }
+ }
+
+ // A headless position group indicates no complications.
+ if (head == null) {
+ return;
+ }
+
+ for (DirectionGroup directionGroup : mDirectionGroups.values()) {
+ // Tell each {@link DirectionGroup} to update its containing {@link ViewEntry} based
+ // on the identified head. This iteration will also capture any newly added views.
+ directionGroup.updateViews(head.getView());
+ }
+ }
+ }
+
+ /**
+ * A {@link DirectionGroup} organizes the {@link ViewEntry} of a parent group that point are
+ * laid out in the same direction.
+ */
+ private static class DirectionGroup implements ViewEntry.Parent {
+ /**
+ * An interface implemented by the {@link DirectionGroup} parent to receive updates.
+ */
+ interface Parent {
+ /**
+ * Invoked to indicate a change to the {@link ViewEntry} composition for this
+ * {@link DirectionGroup}.
+ */
+ void onEntriesChanged();
+ }
+ private final ArrayList<ViewEntry> mViews = new ArrayList<>();
+ private final Parent mParent;
+
+ /**
+ * Creates a new {@link DirectionGroup} with the specified parent. Note that the
+ * {@link DirectionGroup} does not store its own direction. It is the responsibility of the
+ * {@link DirectionGroup.Parent} to maintain this association.
+ */
+ DirectionGroup(Parent parent) {
+ mParent = parent;
+ }
+
+ /**
+ * Returns the head of the group. It is assumed that the order of the {@link ViewEntry} is
+ * proactively maintained.
+ */
+ public ViewEntry getHead() {
+ return mViews.isEmpty() ? null : mViews.get(0);
+ }
+
+ /**
+ * Adds a {@link ViewEntry} via {@link ViewEntry.Builder} to this group.
+ */
+ public ViewEntry add(ViewEntry.Builder entryBuilder) {
+ final ViewEntry entry = entryBuilder.setParent(this).build();
+ mViews.add(entry);
+
+ // After adding view, reverse sort collection.
+ Collections.sort(mViews);
+ Collections.reverse(mViews);
+
+ mParent.onEntriesChanged();
+
+ return entry;
+ }
+
+ @Override
+ public void removeEntry(ViewEntry entry) {
+ // Sort is handled when the view is added, so should still be correct after removal.
+ // However, the head may have been removed, which may affect the layout of views in
+ // other DirectionGroups of the same PositionGroup.
+ mViews.remove(entry);
+ mParent.onEntriesChanged();
+ }
+
+ /**
+ * Invoked by {@link Parent} to update the layout of all children {@link ViewEntry} with
+ * the specified head. Note that the head might not be in this group and instead part of a
+ * neighboring group.
+ */
+ public void updateViews(View groupHead) {
+ Iterator<ViewEntry> it = mViews.iterator();
+
+ while (it.hasNext()) {
+ final ViewEntry viewEntry = it.next();
+ viewEntry.applyLayoutParams(groupHead);
+ groupHead = viewEntry.getView();
+ }
+ }
+ }
+
+ private final ConstraintLayout mLayout;
+ private final HashMap<ComplicationId, ViewEntry> mEntries = new HashMap<>();
+ private final HashMap<Integer, PositionGroup> mPositions = new HashMap<>();
+
+ /** */
+ @Inject
+ public ComplicationLayoutEngine(@Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout layout) {
+ mLayout = layout;
+ }
+
+ /**
+ * Adds a complication to this {@link ComplicationLayoutEngine}.
+ * @param id A {@link ComplicationId} unique to this complication. If this matches a
+ * complication within this {@link ComplicationViewModel}, the existing complication
+ * will be removed.
+ * @param view The {@link View} to be shown.
+ * @param lp The {@link ComplicationLayoutParams} as expressed by the {@link Complication}.
+ * These will be interpreted into the final applied parameters.
+ * @param category The {@link Complication.Category} for the {@link Complication}.
+ */
+ public void addComplication(ComplicationId id, View view,
+ ComplicationLayoutParams lp, @Complication.Category int category) {
+ // If the complication is present, remove.
+ if (mEntries.containsKey(id)) {
+ removeComplication(id);
+ }
+
+ final ViewEntry.Builder entryBuilder = new ViewEntry.Builder(view, lp, category);
+
+ // Add position group if doesn't already exist
+ final int position = lp.getPosition();
+ if (!mPositions.containsKey(position)) {
+ mPositions.put(position, new PositionGroup());
+ }
+
+ // Insert entry into group
+ final ViewEntry entry = mPositions.get(position).add(entryBuilder);
+ mEntries.put(id, entry);
+
+ mLayout.addView(entry.getView());
+ }
+
+ /**
+ * Removes a complication by {@link ComplicationId}.
+ */
+ public void removeComplication(ComplicationId id) {
+ if (!mEntries.containsKey(id)) {
+ Log.e(TAG, "could not find id:" + id);
+ return;
+ }
+
+ final ViewEntry entry = mEntries.get(id);
+ entry.remove();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
new file mode 100644
index 0000000..f9a69fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.complication;
+
+import android.annotation.IntDef;
+import android.view.ViewGroup;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * {@link ComplicationLayoutParams} allows a {@link Complication} to express its preferred location
+ * and dimensions. Note that these parameters are not directly applied by any {@link ViewGroup}.
+ * They are instead consulted for the final parameters which best seem fit for usage.
+ */
+public class ComplicationLayoutParams extends ViewGroup.LayoutParams {
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "POSITION_" }, value = {
+ POSITION_TOP,
+ POSITION_END,
+ POSITION_BOTTOM,
+ POSITION_START,
+ })
+
+ @interface Position {}
+ /** Align view with the top of parent or bottom of preceding {@link Complication}. */
+ static final int POSITION_TOP = 1 << 0;
+ /** Align view with the bottom of parent or top of preceding {@link Complication}. */
+ static final int POSITION_BOTTOM = 1 << 1;
+ /** Align view with the start of parent or end of preceding {@link Complication}. */
+ static final int POSITION_START = 1 << 2;
+ /** Align view with the end of parent or start of preceding {@link Complication}. */
+ static final int POSITION_END = 1 << 3;
+
+ private static final int FIRST_POSITION = POSITION_TOP;
+ private static final int LAST_POSITION = POSITION_END;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "DIRECTION_" }, value = {
+ DIRECTION_UP,
+ DIRECTION_DOWN,
+ DIRECTION_START,
+ DIRECTION_END,
+ })
+
+ @interface Direction {}
+ /** Position view upward from position. */
+ static final int DIRECTION_UP = 1 << 0;
+ /** Position view downward from position. */
+ static final int DIRECTION_DOWN = 1 << 1;
+ /** Position view towards the start of the parent. */
+ static final int DIRECTION_START = 1 << 2;
+ /** Position view towards the end of parent. */
+ static final int DIRECTION_END = 1 << 3;
+
+ @Position
+ private final int mPosition;
+
+ @Direction
+ private final int mDirection;
+
+ private final int mWeight;
+
+ // Do not allow specifying opposite positions
+ private static final int[] INVALID_POSITIONS =
+ { POSITION_BOTTOM | POSITION_TOP, POSITION_END | POSITION_START };
+
+ // Do not allow for specifying a direction towards the outside of the container.
+ private static final Map<Integer, Integer> INVALID_DIRECTIONS;
+ static {
+ INVALID_DIRECTIONS = new HashMap<>();
+ INVALID_DIRECTIONS.put(POSITION_BOTTOM, DIRECTION_DOWN);
+ INVALID_DIRECTIONS.put(POSITION_TOP, DIRECTION_UP);
+ INVALID_DIRECTIONS.put(POSITION_START, DIRECTION_START);
+ INVALID_DIRECTIONS.put(POSITION_END, DIRECTION_END);
+ }
+
+ /**
+ * Constructs a {@link ComplicationLayoutParams}.
+ * @param width The width {@link android.view.View.MeasureSpec} for the view.
+ * @param height The height {@link android.view.View.MeasureSpec} for the view.
+ * @param position The place within the parent container where the view should be positioned.
+ * @param direction The direction the view should be laid out from either the parent container
+ * or preceding view.
+ * @param weight The weight that should be considered for this view when compared to other
+ * views. This has an impact on the placement of the view but not the rendering of
+ * the view.
+ */
+ public ComplicationLayoutParams(int width, int height, @Position int position,
+ @Direction int direction, int weight) {
+ super(width, height);
+
+ if (!validatePosition(position)) {
+ throw new IllegalArgumentException("invalid position:" + position);
+ }
+ mPosition = position;
+
+ if (!validateDirection(position, direction)) {
+ throw new IllegalArgumentException("invalid direction:" + direction);
+ }
+
+ mDirection = direction;
+
+ mWeight = weight;
+ }
+
+ /**
+ * Constructs {@link ComplicationLayoutParams} from an existing instance.
+ */
+ public ComplicationLayoutParams(ComplicationLayoutParams source) {
+ super(source);
+ mPosition = source.mPosition;
+ mDirection = source.mDirection;
+ mWeight = source.mWeight;
+ }
+
+ private static boolean validateDirection(@Position int position, @Direction int direction) {
+ for (int currentPosition = FIRST_POSITION; currentPosition <= LAST_POSITION;
+ currentPosition <<= 1) {
+ if ((position & currentPosition) == currentPosition
+ && INVALID_DIRECTIONS.containsKey(currentPosition)
+ && (direction & INVALID_DIRECTIONS.get(currentPosition)) != 0) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Iterates over the defined positions and invokes the specified {@link Consumer} for each
+ * position specified for this {@link ComplicationLayoutParams}.
+ */
+ public void iteratePositions(Consumer<Integer> consumer) {
+ for (int currentPosition = FIRST_POSITION; currentPosition <= LAST_POSITION;
+ currentPosition <<= 1) {
+ if ((mPosition & currentPosition) == currentPosition) {
+ consumer.accept(currentPosition);
+ }
+ }
+ }
+
+ private static boolean validatePosition(@Position int position) {
+ if (position == 0) {
+ return false;
+ }
+
+ for (int combination : INVALID_POSITIONS) {
+ if ((position & combination) == combination) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Direction
+ public int getDirection() {
+ return mDirection;
+ }
+
+ @Position
+ public int getPosition() {
+ return mPosition;
+ }
+
+ public int getWeight() {
+ return mWeight;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java
new file mode 100644
index 0000000..f023937
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java
@@ -0,0 +1,67 @@
+/*
+ * 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.dreams.complication;
+
+import androidx.lifecycle.ViewModel;
+
+import javax.inject.Inject;
+
+/**
+ * {@link ComplicationViewModel} is an abstraction over {@link Complication}, providing the model
+ * from which any view-related interpretation of the {@link Complication} should be derived from.
+ */
+public class ComplicationViewModel extends ViewModel {
+ private final Complication mComplication;
+ private final ComplicationId mId;
+ private final Complication.Host mHost;
+
+ /**
+ * Default constructor for generating a {@link ComplicationViewModel}.
+ * @param complication The {@link Complication} represented by the view model.
+ * @param id The {@link ComplicationId} tied to this {@link Complication}.
+ * @param host The environment {@link Complication.Host}.
+ */
+ @Inject
+ public ComplicationViewModel(Complication complication, ComplicationId id,
+ Complication.Host host) {
+ mComplication = complication;
+ mId = id;
+ mHost = host;
+ }
+
+ /**
+ * Returns the {@link ComplicationId} for this {@link Complication} for stable id association.
+ */
+ public ComplicationId getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the underlying {@link Complication}. Should only as a redirection - for example,
+ * using the {@link Complication} to generate view. Any property should be surfaced through
+ * this ViewModel.
+ */
+ public Complication getComplication() {
+ return mComplication;
+ }
+
+ /**
+ * Requests the dream exit on behalf of the {@link Complication}.
+ */
+ public void exitDream() {
+ mHost.requestExitDream();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModelProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModelProvider.java
new file mode 100644
index 0000000..cc17ea1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModelProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.complication;
+
+import androidx.lifecycle.ViewModelProvider;
+import androidx.lifecycle.ViewModelStore;
+
+import com.android.systemui.dreams.complication.dagger.DaggerViewModelProviderFactory;
+
+import javax.inject.Inject;
+
+/**
+ * An intermediary to generate {@link ComplicationViewModel} tracked with a {@link ViewModelStore}.
+ */
+public class ComplicationViewModelProvider extends ViewModelProvider {
+ @Inject
+ public ComplicationViewModelProvider(ViewModelStore store, ComplicationViewModel viewModel) {
+ super(store, new DaggerViewModelProviderFactory(() -> viewModel));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModelTransformer.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModelTransformer.java
new file mode 100644
index 0000000..5d113dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModelTransformer.java
@@ -0,0 +1,56 @@
+/*
+ * 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.dreams.complication;
+
+import com.android.systemui.dreams.complication.dagger.ComplicationViewModelComponent;
+
+import java.util.HashMap;
+
+import javax.inject.Inject;
+
+/**
+ * The {@link ComplicationViewModelTransformer} responsibility is provide a mapping from
+ * {@link Complication} to {@link ComplicationViewModel}.
+ */
+public class ComplicationViewModelTransformer {
+ private final ComplicationId.Factory mComplicationIdFactory = new ComplicationId.Factory();
+ private final HashMap<Complication, ComplicationId> mComplicationIdMapping = new HashMap<>();
+ private final ComplicationViewModelComponent.Factory mViewModelComponentFactory;
+
+ @Inject
+ public ComplicationViewModelTransformer(
+ ComplicationViewModelComponent.Factory viewModelComponentFactory) {
+ mViewModelComponentFactory = viewModelComponentFactory;
+ }
+
+ /**
+ * Generates {@link ComplicationViewModel} from a {@link Complication}.
+ */
+ public ComplicationViewModel getViewModel(Complication complication) {
+ final ComplicationId id = getComplicationId(complication);
+ return mViewModelComponentFactory.create(complication, id)
+ .getViewModelProvider().get(id.toString(), ComplicationViewModel.class);
+ }
+
+ private ComplicationId getComplicationId(Complication complication) {
+ if (!mComplicationIdMapping.containsKey(complication)) {
+ mComplicationIdMapping.put(complication, mComplicationIdFactory.getNextId());
+ }
+
+ return mComplicationIdMapping.get(complication);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewComponent.java
new file mode 100644
index 0000000..4cc905e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewComponent.java
@@ -0,0 +1,89 @@
+/*
+ * 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.dreams.complication.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.view.LayoutInflater;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.systemui.dreams.complication.ComplicationHostViewController;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * {@link ComplicationHostViewComponent} encapsulates the shared logic around the host view layer
+ * for complications. Anything that references the layout should be provided through this component
+ * and its child module. The factory should be used in order to best tie the lifetime of the view
+ * to components.
+ */
+@Subcomponent(modules = {
+ ComplicationHostViewComponent.ComplicationHostViewModule.class,
+})
+@ComplicationHostViewComponent.ComplicationHostViewScope
+public interface ComplicationHostViewComponent {
+ String SCOPED_COMPLICATIONS_LAYOUT = "scoped_complications_layout";
+
+ /** Scope annotation for singleton items within {@link ComplicationHostViewComponent}. */
+ @Documented
+ @Retention(RUNTIME)
+ @Scope
+ @interface ComplicationHostViewScope {}
+
+ /**
+ * Factory for generating a new scoped component.
+ */
+ @Subcomponent.Factory
+ interface Factory {
+ ComplicationHostViewComponent create();
+ }
+
+ /** */
+ ComplicationHostViewController getController();
+
+ /**
+ * Module for providing a scoped host view.
+ */
+ @Module
+ abstract class ComplicationHostViewModule {
+ /**
+ * Generates a {@link ConstraintLayout}, which can host
+ * {@link com.android.systemui.dreams.complication.Complication} instances.
+ */
+ @Provides
+ @Named(SCOPED_COMPLICATIONS_LAYOUT)
+ @ComplicationHostViewScope
+ static ConstraintLayout providesComplicationHostView(
+ LayoutInflater layoutInflater) {
+ return Preconditions.checkNotNull((ConstraintLayout)
+ layoutInflater.inflate(R.layout.dream_overlay_complications_layer,
+ null),
+ "R.layout.dream_overlay_complications_layer did not properly inflated");
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java
new file mode 100644
index 0000000..b29e8c9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java
@@ -0,0 +1,64 @@
+/*
+ * 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.dreams.complication.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import androidx.lifecycle.ViewModelProvider;
+import androidx.lifecycle.ViewModelStore;
+
+import com.android.systemui.dreams.complication.ComplicationCollectionViewModel;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Module for housing components related to rendering complications.
+ */
+@Module(subcomponents = {
+ ComplicationViewModelComponent.class,
+ ComplicationHostViewComponent.class,
+})
+public interface ComplicationModule {
+ String SCOPED_COMPLICATIONS_MODEL = "scoped_complications_model";
+
+ /** Scope annotation for singleton items within the {@link ComplicationModule}. */
+ @Documented
+ @Retention(RUNTIME)
+ @Scope
+ @interface ComplicationScope {}
+
+ /**
+ * The complication collection is provided through this way to ensure that the instances are
+ * tied to the {@link ViewModelStore}.
+ */
+ @Provides
+ @Named(SCOPED_COMPLICATIONS_MODEL)
+ static ComplicationCollectionViewModel providesComplicationCollectionViewModel(
+ ViewModelStore store, ComplicationCollectionViewModel viewModel) {
+ final ViewModelProvider provider = new ViewModelProvider(store,
+ new DaggerViewModelProviderFactory(() -> viewModel));
+
+ return provider.get(ComplicationCollectionViewModel.class);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationViewModelComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationViewModelComponent.java
new file mode 100644
index 0000000..703cd28
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationViewModelComponent.java
@@ -0,0 +1,44 @@
+/*
+ * 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.dreams.complication.dagger;
+
+import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.ComplicationId;
+import com.android.systemui.dreams.complication.ComplicationViewModelProvider;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * The {@link ComplicationViewModelComponent} allows for a
+ * {@link com.android.systemui.dreams.complication.ComplicationViewModel} for a particular
+ * {@link Complication}. This component binds these instance specific values to allow injection with
+ * values provided at the wider scope.
+ */
+@Subcomponent
+public interface ComplicationViewModelComponent {
+ /**
+ * Factory for generating {@link ComplicationViewModelComponent}.
+ */
+ @Subcomponent.Factory
+ interface Factory {
+ ComplicationViewModelComponent create(@BindsInstance Complication complication,
+ @BindsInstance ComplicationId id);
+ }
+
+ /** */
+ ComplicationViewModelProvider getViewModelProvider();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DaggerViewModelProviderFactory.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DaggerViewModelProviderFactory.java
new file mode 100644
index 0000000..8ffedec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DaggerViewModelProviderFactory.java
@@ -0,0 +1,50 @@
+/*
+ * 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.dreams.complication.dagger;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+
+/**
+ * {@link DaggerViewModelProviderFactory} is a wrapper around a lambda allowing for uses of Dagger
+ * component.
+ */
+public class DaggerViewModelProviderFactory implements ViewModelProvider.Factory {
+ /**
+ * An interface for providing a {@link ViewModel} through
+ * {@link DaggerViewModelProviderFactory}.
+ */
+ public interface ViewModelCreator {
+ /**
+ * Creates a {@link ViewModel} to be returned.
+ */
+ ViewModel create();
+ }
+
+ private final ViewModelCreator mCreator;
+
+ public DaggerViewModelProviderFactory(ViewModelCreator creator) {
+ mCreator = creator;
+ }
+
+ @NonNull
+ @Override
+ public <T extends ViewModel> T create(@NonNull Class<T> aClass) {
+ return (T) mCreator.create();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index 072f50d..d5053a0 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -22,6 +22,7 @@
* Dagger Module providing Communal-related functionality.
*/
@Module(subcomponents = {
- DreamOverlayComponent.class})
+ DreamOverlayComponent.class,
+})
public interface DreamModule {
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
index c90332b..f0ab696 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
@@ -18,25 +18,36 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.ViewModelStore;
+
import com.android.systemui.dreams.DreamOverlayContainerViewController;
+import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.dagger.ComplicationModule;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import javax.inject.Scope;
+import dagger.BindsInstance;
import dagger.Subcomponent;
/**
* Dagger subcomponent for {@link DreamOverlayModule}.
*/
-@Subcomponent(modules = {DreamOverlayModule.class})
+@Subcomponent(modules = {
+ DreamOverlayModule.class,
+ ComplicationModule.class,
+})
@DreamOverlayComponent.DreamOverlayScope
public interface DreamOverlayComponent {
/** Simple factory for {@link DreamOverlayComponent}. */
@Subcomponent.Factory
interface Factory {
- DreamOverlayComponent create();
+ DreamOverlayComponent create(@BindsInstance ViewModelStore store,
+ @BindsInstance Complication.Host host);
}
/** Scope annotation for singleton items within the {@link DreamOverlayComponent}. */
@@ -46,6 +57,11 @@
@interface DreamOverlayScope {}
/** Builds a {@link DreamOverlayContainerViewController}. */
- @DreamOverlayScope
DreamOverlayContainerViewController getDreamOverlayContainerViewController();
+
+ /** Builds a {@link androidx.lifecycle.LifecycleRegistry} */
+ LifecycleRegistry getLifecycleRegistry();
+
+ /** Builds a {@link androidx.lifecycle.LifecycleOwner} */
+ LifecycleOwner getLifecycleOwner();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index d291203..b56aa2c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -22,6 +22,9 @@
import android.view.LayoutInflater;
import android.view.ViewGroup;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+
import com.android.internal.util.Preconditions;
import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView;
@@ -36,6 +39,7 @@
import javax.inject.Named;
+import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
@@ -124,4 +128,16 @@
return resources.getInteger(
R.integer.config_dreamOverlayBurnInProtectionUpdateIntervalMillis);
}
+
+ @Provides
+ @DreamOverlayComponent.DreamOverlayScope
+ static LifecycleOwner providesLifecycleOwner(Lazy<LifecycleRegistry> lifecycleRegistryLazy) {
+ return () -> lifecycleRegistryLazy.get();
+ }
+
+ @Provides
+ @DreamOverlayComponent.DreamOverlayScope
+ static LifecycleRegistry providesLifecycleRegistry(LifecycleOwner lifecycleOwner) {
+ return new LifecycleRegistry(lifecycleOwner);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 4be819a..e24df30 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -85,7 +85,7 @@
new BooleanFlag(400, true);
public static final BooleanFlag SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
- new BooleanFlag(401, false);
+ new BooleanFlag(401, true);
public static final ResourceBooleanFlag SMARTSPACE =
new ResourceBooleanFlag(402, R.bool.flag_smartspace);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 69bcf2e..582965a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -21,6 +21,8 @@
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Matrix
+import android.graphics.Rect
+import android.os.Handler
import android.view.RemoteAnimationTarget
import android.view.SyncRtSurfaceTransactionApplier
import android.view.View
@@ -33,11 +35,17 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController
+import com.android.systemui.plugins.BcSmartspaceDataPlugin
+import com.android.systemui.shared.system.ActivityManagerWrapper
+import com.android.systemui.shared.system.QuickStepContract
+import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
+import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController
+import com.android.systemui.shared.system.smartspace.SmartspaceState
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
import javax.inject.Inject
+import kotlin.math.min
/**
* Starting scale factor for the app/launcher surface behind the keyguard, when it's animating
@@ -76,6 +84,25 @@
const val DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD = 0.4f
/**
+ * How long the canned unlock animation takes. This is used if we are unlocking from biometric auth,
+ * from a tap on the unlock icon, or from the bouncer. This is not relevant if the lockscreen is
+ * swiped away via a touch gesture, or when it's flinging expanded/collapsed after a swipe.
+ */
+const val UNLOCK_ANIMATION_DURATION_MS = 200L
+
+/**
+ * Duration for the alpha animation on the surface behind. This plays to fade in the surface during
+ * a swipe to unlock (and to fade it back out if the swipe is cancelled).
+ */
+const val SURFACE_BEHIND_SWIPE_FADE_DURATION_MS = 150L
+
+/**
+ * Start delay for the surface behind animation, used so that the lockscreen can get out of the way
+ * before the surface begins appearing.
+ */
+const val UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS = 75L
+
+/**
* Initiates, controls, and ends the keyguard unlock animation.
*
* The unlock animation transitions between the keyguard (lock screen) and the app/launcher surface
@@ -85,7 +112,7 @@
* this controller will play a canned animation on the surface as well.
*
* The surface behind the keyguard is manipulated via a RemoteAnimation passed to
- * [notifyStartKeyguardExitAnimation] by [KeyguardViewMediator].
+ * [notifyStartSurfaceBehindRemoteAnimation] by [KeyguardViewMediator].
*/
@SysUISingleton
class KeyguardUnlockAnimationController @Inject constructor(
@@ -94,10 +121,99 @@
private val
keyguardViewMediator: Lazy<KeyguardViewMediator>,
private val keyguardViewController: KeyguardViewController,
- private val smartspaceTransitionController: SmartspaceTransitionController,
private val featureFlags: FeatureFlags,
- private val biometricUnlockController: BiometricUnlockController
-) : KeyguardStateController.Callback {
+ private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>
+) : KeyguardStateController.Callback, ISysuiUnlockAnimationController.Stub() {
+
+ interface KeyguardUnlockAnimationListener {
+ /**
+ * Called when the remote unlock animation, controlled by
+ * [KeyguardUnlockAnimationController], first starts.
+ *
+ * [playingCannedAnimation] indicates whether we are playing a canned animation to show the
+ * app/launcher behind the keyguard, vs. this being a swipe to unlock where the dismiss
+ * amount drives the animation.
+ * [fromWakeAndUnlock] tells us whether we are unlocking directly from AOD - in this case,
+ * the lockscreen is dismissed instantly, so we shouldn't run any animations that rely on it
+ * being visible.
+ */
+ @JvmDefault
+ fun onUnlockAnimationStarted(playingCannedAnimation: Boolean, fromWakeAndUnlock: Boolean) {}
+
+ /**
+ * Called when the remote unlock animation ends, in all cases, canned or swipe-to-unlock.
+ * The keyguard is no longer visible in this state and the app/launcher behind the keyguard
+ * is now completely visible.
+ */
+ @JvmDefault
+ fun onUnlockAnimationFinished() {}
+
+ /**
+ * Called when we begin the smartspace shared element transition, either due to an unlock
+ * action (biometric, etc.) or a swipe to unlock.
+ *
+ * This transition can begin BEFORE [onUnlockAnimationStarted] is called, if we are swiping
+ * to unlock and the surface behind the keyguard has not yet been made visible. This is
+ * because the lockscreen smartspace immediately begins moving towards the launcher
+ * smartspace location when a swipe begins, even before we start the keyguard exit remote
+ * animation and show the launcher itself.
+ */
+ @JvmDefault
+ fun onSmartspaceSharedElementTransitionStarted() {}
+ }
+
+ /** The SmartSpace view on the lockscreen, provided by [KeyguardClockSwitchController]. */
+ var lockscreenSmartspace: View? = null
+
+ /**
+ * The state of the Launcher's smartspace, delivered via [onLauncherSmartspaceStateUpdated].
+ * This is pushed to us from Launcher whenever their smartspace moves or its visibility changes.
+ * We'll animate the lockscreen smartspace to this location during an unlock.
+ */
+ var launcherSmartspaceState: SmartspaceState? = null
+
+ /**
+ * Whether a canned unlock animation is playing, vs. currently unlocking in response to a swipe
+ * gesture or panel fling. If we're swiping/flinging, the unlock animation is driven by the
+ * dismiss amount, via [onKeyguardDismissAmountChanged]. If we're using a canned animation, it's
+ * being driven by ValueAnimators started in [playCannedUnlockAnimation].
+ */
+ var playingCannedUnlockAnimation = false
+
+ /**
+ * Remote callback provided by Launcher that allows us to control the Launcher's unlock
+ * animation and smartspace.
+ *
+ * If this is null, we will not be animating any Launchers today and should fall back to window
+ * animations.
+ */
+ private var launcherUnlockController: ILauncherUnlockAnimationController? = null
+
+ private val listeners = ArrayList<KeyguardUnlockAnimationListener>()
+
+ /**
+ * Called from SystemUiProxy to pass us the launcher's unlock animation controller. If this
+ * doesn't happen, we won't use in-window animations or the smartspace shared element
+ * transition, but that's okay!
+ */
+ override fun setLauncherUnlockController(callback: ILauncherUnlockAnimationController?) {
+ launcherUnlockController = callback
+
+ // If the provided callback dies, set it to null. We'll always check whether it's null
+ // to avoid DeadObjectExceptions.
+ callback?.asBinder()?.linkToDeath({
+ launcherUnlockController = null
+ launcherSmartspaceState = null
+ }, 0 /* flags */)
+ }
+
+ /**
+ * Called from SystemUiProxy to pass us the latest state of the Launcher's smartspace. This is
+ * only done when the state has changed in some way.
+ */
+ override fun onLauncherSmartspaceStateUpdated(state: SmartspaceState?) {
+ launcherSmartspaceState = state
+ }
/**
* Information used to start, run, and finish a RemoteAnimation on the app or launcher surface
@@ -105,15 +221,16 @@
*
* If we're swiping to unlock, the "animation" is controlled via the gesture, tied to the
* dismiss amounts received in [onKeyguardDismissAmountChanged]. It does not have a fixed
- * duration, and it ends when the gesture reaches a certain threshold or is cancelled.
+ * duration, and it ends when the gesture reaches a certain threshold or is cancell
*
* If we're unlocking via biometrics, PIN entry, or from clicking a notification, a canned
- * animation is started in [notifyStartKeyguardExitAnimation].
+ * animation is started in [playCannedUnlockAnimation].
*/
@VisibleForTesting
var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null
private var surfaceBehindRemoteAnimationTarget: RemoteAnimationTarget? = null
private var surfaceBehindRemoteAnimationStartTime: Long = 0
+ private var surfaceBehindParams: SyncRtSurfaceTransactionApplier.SurfaceParams? = null
/**
* Alpha value applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the
@@ -125,6 +242,7 @@
*/
private var surfaceBehindAlpha = 1f
private var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f)
+ private var smartspaceAnimator = ValueAnimator.ofFloat(0f, 1f)
/**
* Matrix applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the
@@ -144,57 +262,113 @@
/** Rounded corner radius to apply to the surface behind the keyguard. */
private var roundedCornerRadius = 0f
- /** The SmartSpace view on the lockscreen, provided by [KeyguardClockSwitchController]. */
- public var lockscreenSmartSpace: View? = null
-
- /**
- * Whether we are currently in the process of unlocking the keyguard, and we are performing the
- * shared element SmartSpace transition.
- */
- private var unlockingWithSmartSpaceTransition: Boolean = false
-
/**
* Whether we tried to start the SmartSpace shared element transition for this unlock swipe.
- * It's possible we're unable to do so (if the Launcher SmartSpace is not available).
+ * It's possible we were unable to do so (if the Launcher SmartSpace is not available), and we
+ * need to keep track of that so that we don't start doing it halfway through the swipe if
+ * Launcher becomes available suddenly.
*/
private var attemptedSmartSpaceTransitionForThisSwipe = false
- init {
- surfaceBehindAlphaAnimator.duration = 150
- surfaceBehindAlphaAnimator.interpolator = Interpolators.ALPHA_IN
- surfaceBehindAlphaAnimator.addUpdateListener { valueAnimator: ValueAnimator ->
- surfaceBehindAlpha = valueAnimator.animatedValue as Float
- updateSurfaceBehindAppearAmount()
- }
- surfaceBehindAlphaAnimator.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- // If the surface alpha is 0f, it's no longer visible so we can safely be done with
- // the animation.
- if (surfaceBehindAlpha == 0f) {
- keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
- }
- }
- })
+ /**
+ * The original location of the lockscreen smartspace on the screen.
+ */
+ private val smartspaceOriginBounds = Rect()
- surfaceBehindEntryAnimator.duration = 450
- surfaceBehindEntryAnimator.interpolator = Interpolators.DECELERATE_QUINT
- surfaceBehindEntryAnimator.addUpdateListener { valueAnimator: ValueAnimator ->
- surfaceBehindAlpha = valueAnimator.animatedValue as Float
- setSurfaceBehindAppearAmount(valueAnimator.animatedValue as Float)
- }
- surfaceBehindEntryAnimator.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(
- false /* cancelled */)
+ /**
+ * The bounds to which the lockscreen smartspace is moving. This is set to the bounds of the
+ * launcher's smartspace prior to the transition starting.
+ */
+ private val smartspaceDestBounds = Rect()
+
+ /**
+ * From 0f to 1f, the progress of the smartspace shared element animation. 0f means the
+ * smartspace is at its normal position within the lock screen hierarchy, and 1f means it has
+ * fully animated to the location of the Launcher's smartspace.
+ */
+ private var smartspaceUnlockProgress = 0f
+
+ /**
+ * Whether we're currently unlocking, and we're talking to Launcher to perform in-window
+ * animations rather than simply animating the Launcher window like any other app. This can be
+ * true while [unlockingWithSmartspaceTransition] is false, if the smartspace is not available
+ * or was not ready in time.
+ */
+ private var unlockingToLauncherWithInWindowAnimations: Boolean = false
+
+ /**
+ * Whether we are currently unlocking, and the smartspace shared element transition is in
+ * progress. If true, we're also [unlockingToLauncherWithInWindowAnimations].
+ */
+ private var unlockingWithSmartspaceTransition: Boolean = false
+
+ private val handler = Handler()
+
+ init {
+ with(surfaceBehindAlphaAnimator) {
+ duration = SURFACE_BEHIND_SWIPE_FADE_DURATION_MS
+ interpolator = Interpolators.TOUCH_RESPONSE
+ addUpdateListener { valueAnimator: ValueAnimator ->
+ surfaceBehindAlpha = valueAnimator.animatedValue as Float
+ updateSurfaceBehindAppearAmount()
}
- })
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ // If the surface alpha is 0f, it's no longer visible so we can safely be done
+ // with the animation even if other properties are still animating.
+ if (surfaceBehindAlpha == 0f) {
+ keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation(
+ false /* cancelled */)
+ }
+ }
+ })
+ }
+
+ with(surfaceBehindEntryAnimator) {
+ duration = UNLOCK_ANIMATION_DURATION_MS
+ startDelay = UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS
+ interpolator = Interpolators.TOUCH_RESPONSE
+ addUpdateListener { valueAnimator: ValueAnimator ->
+ surfaceBehindAlpha = valueAnimator.animatedValue as Float
+ setSurfaceBehindAppearAmount(valueAnimator.animatedValue as Float)
+ }
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ playingCannedUnlockAnimation = false
+ keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(
+ false /* cancelled */
+ )
+ }
+ })
+ }
+
+ with(smartspaceAnimator) {
+ duration = UNLOCK_ANIMATION_DURATION_MS
+ interpolator = Interpolators.TOUCH_RESPONSE
+ addUpdateListener {
+ smartspaceUnlockProgress = it.animatedValue as Float
+ }
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ launcherUnlockController?.setSmartspaceVisibility(View.VISIBLE)
+ keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(
+ false /* cancelled */)
+ }
+ })
+ }
// Listen for changes in the dismiss amount.
keyguardStateController.addCallback(this)
roundedCornerRadius =
- context.resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat()
+ context.resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat()
+ }
+
+ /**
+ * Add a listener to be notified of various stages of the unlock animation.
+ */
+ fun addKeyguardUnlockAnimationListener(listener: KeyguardUnlockAnimationListener) {
+ listeners.add(listener)
}
/**
@@ -203,78 +377,220 @@
* surface for the unlock gesture/animation.
*
* When we're done with it, we'll call [KeyguardViewMediator.finishSurfaceBehindRemoteAnimation]
- * to end the RemoteAnimation.
+ * to end the RemoteAnimation. The KeyguardViewMediator will then end the animation and let us
+ * know that it's over by calling [notifyFinishedKeyguardExitAnimation].
*
- * [requestedShowSurfaceBehindKeyguard] denotes whether the exit animation started because of a
+ * [requestedShowSurfaceBehindKeyguard] indicates whether the animation started because of a
* call to [KeyguardViewMediator.showSurfaceBehindKeyguard], as happens during a swipe gesture,
- * as opposed to the keyguard hiding.
+ * as opposed to being called because the device was unlocked and the keyguard is going away.
*/
- fun notifyStartKeyguardExitAnimation(
+ fun notifyStartSurfaceBehindRemoteAnimation(
target: RemoteAnimationTarget,
startTime: Long,
requestedShowSurfaceBehindKeyguard: Boolean
) {
-
if (surfaceTransactionApplier == null) {
surfaceTransactionApplier = SyncRtSurfaceTransactionApplier(
keyguardViewController.viewRootImpl.view)
}
+ // New animation, new params.
+ surfaceBehindParams = null
+
surfaceBehindRemoteAnimationTarget = target
surfaceBehindRemoteAnimationStartTime = startTime
- // If the surface behind wasn't made visible during a swipe, we'll do a canned animation
- // to animate it in. Otherwise, the swipe touch events will continue animating it.
+ // If we specifically requested that the surface behind be made visible, it means we are
+ // swiping to unlock. In that case, the surface visibility is tied to the dismiss amount,
+ // and we'll handle that in onKeyguardDismissAmountChanged(). If we didn't request that, the
+ // keyguard is being dismissed for a different reason (biometric auth, etc.) and we should
+ // play a canned animation to make the surface fully visible.
if (!requestedShowSurfaceBehindKeyguard) {
- keyguardViewController.hide(startTime, 350)
-
- // If we're wake and unlocking, we don't want to animate the surface since we're going
- // to do the light reveal scrim from the black AOD screen. Make it visible and end the
- // remote aimation.
- if (biometricUnlockController.isWakeAndUnlock) {
- setSurfaceBehindAppearAmount(1f)
- keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(
- false /* cancelled */)
- } else {
- // Otherwise, animate it in normally.
- surfaceBehindEntryAnimator.start()
- }
+ playCannedUnlockAnimation()
}
+ listeners.forEach {
+ it.onUnlockAnimationStarted(
+ playingCannedUnlockAnimation /* playingCannedAnimation */,
+ biometricUnlockControllerLazy.get().isWakeAndUnlock /* isWakeAndUnlock */) }
+
// Finish the keyguard remote animation if the dismiss amount has crossed the threshold.
// Check it here in case there is no more change to the dismiss amount after the last change
// that starts the keyguard animation. @see #updateKeyguardViewMediatorIfThresholdsReached()
finishKeyguardExitRemoteAnimationIfReachThreshold()
}
- fun notifyCancelKeyguardExitAnimation() {
- surfaceBehindRemoteAnimationTarget = null
- }
+ /**
+ * Called by [KeyguardViewMediator] to let us know that the remote animation has finished, and
+ * we should clean up all of our state.
+ */
+ fun notifyFinishedKeyguardExitAnimation(cancelled: Boolean) {
+ // Cancel any pending actions.
+ handler.removeCallbacksAndMessages(null)
- fun notifyFinishedKeyguardExitAnimation() {
- surfaceBehindRemoteAnimationTarget = null
- }
+ // Make sure we made the surface behind fully visible, just in case. It should already be
+ // fully visible.
+ setSurfaceBehindAppearAmount(1f)
+ launcherUnlockController?.setUnlockAmount(1f)
+ smartspaceDestBounds.setEmpty()
- fun hideKeyguardViewAfterRemoteAnimation() {
- keyguardViewController.hide(surfaceBehindRemoteAnimationStartTime, 350)
+ // That target is no longer valid since the animation finished, null it out.
+ surfaceBehindRemoteAnimationTarget = null
+ surfaceBehindParams = null
+
+ playingCannedUnlockAnimation = false
+ unlockingToLauncherWithInWindowAnimations = false
+ unlockingWithSmartspaceTransition = false
+ resetSmartspaceTransition()
+
+ listeners.forEach { it.onUnlockAnimationFinished() }
}
/**
- * Whether we are currently in the process of unlocking the keyguard, and we are performing the
- * shared element SmartSpace transition.
+ * Play a canned unlock animation to unlock the device. This is used when we were *not* swiping
+ * to unlock using a touch gesture. If we were swiping to unlock, the animation will be driven
+ * by the dismiss amount via [onKeyguardDismissAmountChanged].
*/
- fun isUnlockingWithSmartSpaceTransition(): Boolean {
- return unlockingWithSmartSpaceTransition
+ fun playCannedUnlockAnimation() {
+ playingCannedUnlockAnimation = true
+
+ if (canPerformInWindowLauncherAnimations()) {
+ // If possible, use the neat in-window animations to unlock to the launcher.
+ unlockToLauncherWithInWindowAnimations()
+ } else if (!biometricUnlockControllerLazy.get().isWakeAndUnlock) {
+ // If the launcher isn't behind the keyguard, or the launcher unlock controller is not
+ // available, animate in the entire window.
+ surfaceBehindEntryAnimator.start()
+ } else {
+ setSurfaceBehindAppearAmount(1f)
+ keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(false)
+ }
+
+ // If this is a wake and unlock, hide the lockscreen immediately. In the future, we should
+ // animate it out nicely instead, but to the current state of wake and unlock, not hiding it
+ // causes a lot of issues.
+ // TODO(b/210016643): Not this, it looks not-ideal!
+ if (biometricUnlockControllerLazy.get().isWakeAndUnlock) {
+ keyguardViewController.hide(surfaceBehindRemoteAnimationStartTime, 350)
+ }
+ }
+
+ /**
+ * Unlock to the launcher, using in-window animations, and the smartspace shared element
+ * transition if possible.
+ */
+ private fun unlockToLauncherWithInWindowAnimations() {
+ unlockingToLauncherWithInWindowAnimations = true
+
+ // See if we can do the smartspace transition, and if so, do it!
+ if (prepareForSmartspaceTransition()) {
+ animateSmartspaceToDestination()
+ listeners.forEach { it.onSmartspaceSharedElementTransitionStarted() }
+ }
+
+ // Tell the launcher to prepare for the animation by setting its views invisible and
+ // syncing the selected smartspace pages.
+ launcherUnlockController?.prepareForUnlock(
+ unlockingWithSmartspaceTransition /* willAnimateSmartspace */,
+ (lockscreenSmartspace as BcSmartspaceDataPlugin.SmartspaceView?)?.selectedPage ?: -1)
+
+ // Begin the animation.
+ launcherUnlockController?.playUnlockAnimation(
+ true /* unlocked */, UNLOCK_ANIMATION_DURATION_MS)
+ if (!unlockingWithSmartspaceTransition) {
+ // If we are not unlocking with the smartspace transition, wait for the unlock animation
+ // to end and then finish the remote animation. If we are using the smartspace
+ // transition, it will finish the remote animation once it ends.
+ handler.postDelayed({
+ keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(
+ false /* cancelled */)
+ }, UNLOCK_ANIMATION_DURATION_MS)
+ }
+
+ // Wait a moment, then show the launcher surface.
+ setSurfaceBehindAppearAmount(1f)
+ }
+
+ /**
+ * Animates the lockscreen smartspace all the way to the launcher's smartspace location, then
+ * makes the launcher smartspace visible and ends the remote animation.
+ */
+ private fun animateSmartspaceToDestination() {
+ smartspaceAnimator.start()
+ }
+
+ /**
+ * Reset the lockscreen smartspace's position, and reset all state involving the smartspace
+ * transition.
+ */
+ public fun resetSmartspaceTransition() {
+ unlockingWithSmartspaceTransition = false
+ smartspaceUnlockProgress = 0f
+
+ lockscreenSmartspace?.post {
+ lockscreenSmartspace!!.translationX = 0f
+ lockscreenSmartspace!!.translationY = 0f
+ }
+ }
+
+ /**
+ * Moves the lockscreen smartspace towards the launcher smartspace's position.
+ */
+ private fun setSmartspaceProgressToDestinationBounds(progress: Float) {
+ if (smartspaceDestBounds.isEmpty) {
+ return
+ }
+
+ val progressClamped = min(1f, progress)
+
+ // Calculate the distance (relative to the origin) that we need to be for the current
+ // progress value.
+ val progressX =
+ (smartspaceDestBounds.left - smartspaceOriginBounds.left) * progressClamped
+ val progressY =
+ (smartspaceDestBounds.top - smartspaceOriginBounds.top) * progressClamped
+
+ val lockscreenSmartspaceCurrentBounds = Rect().also {
+ lockscreenSmartspace!!.getBoundsOnScreen(it)
+ }
+
+ // Figure out how far that is from our present location on the screen. This approach
+ // compensates for the fact that our parent container is also translating to animate out.
+ val dx = smartspaceOriginBounds.left + progressX -
+ lockscreenSmartspaceCurrentBounds.left
+ val dy = smartspaceOriginBounds.top + progressY -
+ lockscreenSmartspaceCurrentBounds.top
+
+ with(lockscreenSmartspace!!) {
+ translationX += dx
+ translationY += dy
+ }
}
/**
* Update the lockscreen SmartSpace to be positioned according to the current dismiss amount. As
* the dismiss amount increases, we will increase our SmartSpace's progress to the destination
* bounds (the location of the Launcher SmartSpace).
+ *
+ * This is used by [KeyguardClockSwitchController] to keep the smartspace position updated as
+ * the clock is swiped away.
*/
fun updateLockscreenSmartSpacePosition() {
- smartspaceTransitionController.setProgressToDestinationBounds(
- keyguardStateController.dismissAmount / DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)
+ setSmartspaceProgressToDestinationBounds(smartspaceUnlockProgress)
+ }
+
+ /**
+ * Asks the keyguard view to hide, using the start time from the beginning of the remote
+ * animation.
+ */
+ fun hideKeyguardViewAfterRemoteAnimation() {
+ // Hide the keyguard, with no fade out since we animated it away during the unlock.
+ keyguardViewController.hide(surfaceBehindRemoteAnimationStartTime, 0 /* fadeOutDuration */)
+ }
+
+ private fun applyParamsToSurface(params: SyncRtSurfaceTransactionApplier.SurfaceParams) {
+ surfaceTransactionApplier!!.scheduleApply(params)
+ surfaceBehindParams = params
}
/**
@@ -287,34 +603,56 @@
return
}
- val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.height()
- val scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
- (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
- MathUtils.clamp(amount, 0f, 1f))
+ if (unlockingToLauncherWithInWindowAnimations) {
+ // If we're using the in-window launcher animations, and haven't yet applied alpha = 1f
+ // to the launcher surface, do that now so we can see the launcher animations.
+ if (surfaceBehindParams?.alpha?.let { it < 1f } != false) {
+ applyParamsToSurface(
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+ surfaceBehindRemoteAnimationTarget!!.leash)
+ .withAlpha(1f)
+ .build())
+ }
- // Scale up from a point at the center-bottom of the surface.
- surfaceBehindMatrix.setScale(
+ // If we aren't using the canned unlock animation (which would be setting the unlock
+ // amount in its update listener), do it here.
+ if (!isPlayingCannedUnlockAnimation()) {
+ launcherUnlockController?.setUnlockAmount(amount)
+ }
+ } else {
+ // Otherwise, animate in the surface's scale/transltion.
+ val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.height()
+ val scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
+ (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
+ MathUtils.clamp(amount, 0f, 1f))
+
+ // Scale up from a point at the center-bottom of the surface.
+ surfaceBehindMatrix.setScale(
scaleFactor,
scaleFactor,
surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.width() / 2f,
- surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y)
+ surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
+ )
- // Translate up from the bottom.
- surfaceBehindMatrix.postTranslate(0f,
- surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount))
+ // Translate up from the bottom.
+ surfaceBehindMatrix.postTranslate(
+ 0f,
+ surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
+ )
- // If we're snapping the keyguard back, immediately begin fading it out.
- val animationAlpha =
+ // If we're snapping the keyguard back, immediately begin fading it out.
+ val animationAlpha =
if (keyguardStateController.isSnappingKeyguardBackAfterSwipe) amount
else surfaceBehindAlpha
- val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
- surfaceBehindRemoteAnimationTarget!!.leash)
+ applyParamsToSurface(
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+ surfaceBehindRemoteAnimationTarget!!.leash)
.withMatrix(surfaceBehindMatrix)
.withCornerRadius(roundedCornerRadius)
.withAlpha(animationAlpha)
- .build()
- surfaceTransactionApplier!!.scheduleApply(params)
+ .build())
+ }
}
/**
@@ -326,6 +664,10 @@
return
}
+ if (playingCannedUnlockAnimation) {
+ return
+ }
+
// For fling animations, we want to animate the surface in over the full distance. If we're
// dismissing the keyguard via a swipe gesture (or cancelling the swipe gesture), we want to
// bring in the surface behind over a relatively short swipe distance (~15%), to keep the
@@ -344,17 +686,19 @@
}
override fun onKeyguardDismissAmountChanged() {
- if (!KeyguardService.sEnableRemoteKeyguardGoingAwayAnimation) {
+ if (!willHandleUnlockAnimation()) {
return
}
if (keyguardViewController.isShowing) {
- updateKeyguardViewMediatorIfThresholdsReached()
+ showOrHideSurfaceIfDismissAmountThresholdsReached()
// If the surface is visible or it's about to be, start updating its appearance to
// reflect the new dismiss amount.
- if (keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
- keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) {
+ if ((keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
+ keyguardViewMediator.get()
+ .isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) &&
+ !playingCannedUnlockAnimation) {
updateSurfaceBehindAppearAmount()
}
}
@@ -362,7 +706,7 @@
// The end of the SmartSpace transition can occur after the keyguard is hidden (when we tell
// Launcher's SmartSpace to become visible again), so update it even if the keyguard view is
// no longer showing.
- updateSmartSpaceTransition()
+ applyDismissAmountToSmartspaceTransition()
}
/**
@@ -370,16 +714,28 @@
* such as reaching the point in the dismiss swipe where we need to make the surface behind the
* keyguard visible.
*/
- private fun updateKeyguardViewMediatorIfThresholdsReached() {
+ private fun showOrHideSurfaceIfDismissAmountThresholdsReached() {
if (!featureFlags.isEnabled(Flags.NEW_UNLOCK_SWIPE_ANIMATION)) {
return
}
+ // If we are playing the canned unlock animation, we flung away the keyguard to hide it and
+ // started a canned animation to show the surface behind the keyguard. The fling will cause
+ // panel height/dismiss amount updates, but we should ignore those updates here since the
+ // surface behind is already visible and animating.
+ if (playingCannedUnlockAnimation) {
+ return
+ }
+
val dismissAmount = keyguardStateController.dismissAmount
if (dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
!keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
// We passed the threshold, and we're not yet showing the surface behind the
// keyguard. Animate it in.
+ if (canPerformInWindowLauncherAnimations()) {
+ launcherUnlockController?.setUnlockAmount(0f)
+ unlockingToLauncherWithInWindowAnimations = true
+ }
keyguardViewMediator.get().showSurfaceBehindKeyguard()
fadeInSurfaceBehind()
} else if (dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
@@ -388,9 +744,9 @@
// out.
keyguardViewMediator.get().hideSurfaceBehindKeyguard()
fadeOutSurfaceBehind()
- } else {
- finishKeyguardExitRemoteAnimationIfReachThreshold()
}
+
+ finishKeyguardExitRemoteAnimationIfReachThreshold()
}
/**
@@ -417,6 +773,7 @@
// animating it out. This will be called again after the fling ends.
!keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture &&
dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)) {
+ setSurfaceBehindAppearAmount(1f)
keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(false /* cancelled */)
}
}
@@ -426,31 +783,53 @@
* dismiss amount, and also updates the SmartSpaceTransitionController, which will let Launcher
* know if it needs to do something as a result.
*/
- private fun updateSmartSpaceTransition() {
+ private fun applyDismissAmountToSmartspaceTransition() {
if (!featureFlags.isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED)) {
return
}
+ // If we are playing the canned animation, the smartspace is being animated directly between
+ // its original location and the location of the launcher smartspace by smartspaceAnimator.
+ // We can ignore the dismiss amount, which is caused by panel height changes as the panel is
+ // flung away.
+ if (playingCannedUnlockAnimation) {
+ return
+ }
+
val dismissAmount = keyguardStateController.dismissAmount
- // If we've begun a swipe, and are capable of doing the SmartSpace transition, start it!
+ // If we've begun a swipe, and haven't yet tried doing the SmartSpace transition, do that
+ // now.
if (!attemptedSmartSpaceTransitionForThisSwipe &&
- dismissAmount > 0f &&
- dismissAmount < 1f &&
- keyguardViewController.isShowing) {
+ keyguardViewController.isShowing &&
+ dismissAmount > 0f &&
+ dismissAmount < 1f) {
attemptedSmartSpaceTransitionForThisSwipe = true
- smartspaceTransitionController.prepareForUnlockTransition()
- if (keyguardStateController.canPerformSmartSpaceTransition()) {
- unlockingWithSmartSpaceTransition = true
- smartspaceTransitionController.launcherSmartspace?.setVisibility(
- View.INVISIBLE)
+ if (prepareForSmartspaceTransition()) {
+ unlockingWithSmartspaceTransition = true
+
+ // Ensure that the smartspace is invisible if we're doing the transition, and
+ // visible if we aren't.
+ launcherUnlockController?.setSmartspaceVisibility(
+ if (unlockingWithSmartspaceTransition) View.INVISIBLE else View.VISIBLE)
+
+ if (unlockingWithSmartspaceTransition) {
+ listeners.forEach { it.onSmartspaceSharedElementTransitionStarted() }
+ }
}
} else if (attemptedSmartSpaceTransitionForThisSwipe &&
- (dismissAmount == 0f || dismissAmount == 1f)) {
+ (dismissAmount == 0f || dismissAmount == 1f)) {
attemptedSmartSpaceTransitionForThisSwipe = false
- unlockingWithSmartSpaceTransition = false
- smartspaceTransitionController.launcherSmartspace?.setVisibility(View.VISIBLE)
+ unlockingWithSmartspaceTransition = false
+ launcherUnlockController?.setSmartspaceVisibility(View.VISIBLE)
+ }
+
+ if (unlockingWithSmartspaceTransition) {
+ val swipedFraction: Float = keyguardStateController.dismissAmount
+ val progress = swipedFraction / DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD
+ smartspaceUnlockProgress = progress
+ setSmartspaceProgressToDestinationBounds(smartspaceUnlockProgress)
}
}
@@ -463,4 +842,121 @@
surfaceBehindAlphaAnimator.cancel()
surfaceBehindAlphaAnimator.reverse()
}
+
+ /**
+ * Prepare for the smartspace shared element transition, if possible, by figuring out where we
+ * are animating from/to.
+ *
+ * Return true if we'll be able to do the smartspace transition, or false if conditions are not
+ * right to do it right now.
+ */
+ private fun prepareForSmartspaceTransition(): Boolean {
+ // Feature is disabled, so we don't want to.
+ if (!featureFlags.isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED)) {
+ return false
+ }
+
+ // If our controllers are null, or we haven't received a smartspace state from Launcher yet,
+ // we will not be doing any smartspace transitions today.
+ if (launcherUnlockController == null ||
+ lockscreenSmartspace == null ||
+ launcherSmartspaceState == null) {
+ return false
+ }
+
+ // If the launcher does not have a visible smartspace (either because it's paged off-screen,
+ // or the smartspace just doesn't exist), we can't do the transition.
+ if ((launcherSmartspaceState?.visibleOnScreen) != true) {
+ return false
+ }
+
+ // If our launcher isn't underneath, then we're unlocking to an app or custom launcher,
+ // neither of which have a smartspace.
+ if (!isNexusLauncherUnderneath()) {
+ return false
+ }
+
+ // TODO(b/213910911): Unfortunately the keyguard is hidden instantly on wake and unlock, so
+ // we won't have a lockscreen smartspace to animate. This is sad, and we should fix that!
+ if (biometricUnlockControllerLazy.get().isWakeAndUnlock) {
+ return false
+ }
+
+ // If we can't dismiss the lock screen via a swipe, then the only way we can do the shared
+ // element transition is if we're doing a biometric unlock. Otherwise, it means the bouncer
+ // is showing, and you can't see the lockscreen smartspace, so a shared element transition
+ // would not make sense.
+ if (!keyguardStateController.canDismissLockScreen() &&
+ !biometricUnlockControllerLazy.get().isBiometricUnlock) {
+ return false
+ }
+
+ unlockingWithSmartspaceTransition = true
+ smartspaceDestBounds.setEmpty()
+
+ // Assuming we were able to retrieve the launcher's state, start the lockscreen
+ // smartspace at 0, 0, and save its starting bounds.
+ with(lockscreenSmartspace!!) {
+ translationX = 0f
+ translationY = 0f
+ getBoundsOnScreen(smartspaceOriginBounds)
+ }
+
+ // Set the destination bounds to the launcher smartspace's bounds, offset by any
+ // padding on our smartspace.
+ with(smartspaceDestBounds) {
+ set(launcherSmartspaceState!!.boundsOnScreen)
+ offset(-lockscreenSmartspace!!.paddingLeft, -lockscreenSmartspace!!.paddingTop)
+ }
+
+ return true
+ }
+
+ /**
+ * Whether we should be able to do the in-window launcher animations given the current state of
+ * the device.
+ */
+ fun canPerformInWindowLauncherAnimations(): Boolean {
+ return isNexusLauncherUnderneath() && launcherUnlockController != null
+ }
+
+ /**
+ * Whether we are currently in the process of unlocking the keyguard, and we are performing the
+ * shared element SmartSpace transition.
+ */
+ fun isUnlockingWithSmartSpaceTransition(): Boolean {
+ return unlockingWithSmartspaceTransition
+ }
+
+ /**
+ * Whether this animation controller will be handling the unlock. We require remote animations
+ * to be enabled to do this.
+ *
+ * If this is not true, nothing in this class is relevant, and the unlock will be handled in
+ * [KeyguardViewMediator].
+ */
+ fun willHandleUnlockAnimation(): Boolean {
+ return KeyguardService.sEnableRemoteKeyguardGoingAwayAnimation
+ }
+
+ /**
+ * Whether we are playing a canned unlock animation, vs. unlocking from a touch gesture such as
+ * a swipe.
+ */
+ fun isPlayingCannedUnlockAnimation(): Boolean {
+ return playingCannedUnlockAnimation
+ }
+
+ companion object {
+ /**
+ * Return whether the Google Nexus launcher is underneath the keyguard, vs. some other
+ * launcher or an app. If so, we can communicate with it to perform in-window/shared element
+ * transitions!
+ */
+ fun isNexusLauncherUnderneath(): Boolean {
+ return ActivityManagerWrapper.getInstance()
+ .runningTask?.topActivity?.className?.equals(
+ QuickStepContract.LAUNCHER_ACTIVITY_CLASS_NAME) ?: false
+ }
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 8376681..08e1654 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2235,8 +2235,9 @@
createInteractionJankMonitorConf("DismissPanel"));
// Pass the surface and metadata to the unlock animation controller.
- mKeyguardUnlockAnimationControllerLazy.get().notifyStartKeyguardExitAnimation(
- apps[0], startTime, mSurfaceBehindRemoteAnimationRequested);
+ mKeyguardUnlockAnimationControllerLazy.get()
+ .notifyStartSurfaceBehindRemoteAnimation(
+ apps[0], startTime, mSurfaceBehindRemoteAnimationRequested);
} else {
mInteractionJankMonitor.begin(
createInteractionJankMonitorConf("RemoteAnimationDisabled"));
@@ -2373,8 +2374,10 @@
finishSurfaceBehindRemoteAnimation(cancelled);
mSurfaceBehindRemoteAnimationRequested = false;
- mKeyguardUnlockAnimationControllerLazy.get().notifyFinishedKeyguardExitAnimation();
});
+
+ mKeyguardUnlockAnimationControllerLazy.get().notifyFinishedKeyguardExitAnimation(
+ cancelled);
}
/**
@@ -2412,8 +2415,16 @@
return mSurfaceBehindRemoteAnimationRequested;
}
+ public boolean isAnimatingBetweenKeyguardAndSurfaceBehind() {
+ return mSurfaceBehindRemoteAnimationRunning;
+ }
+
/** If it's running, finishes the RemoteAnimation on the surface behind the keyguard. */
public void finishSurfaceBehindRemoteAnimation(boolean cancelled) {
+ if (!mSurfaceBehindRemoteAnimationRunning) {
+ return;
+ }
+
mSurfaceBehindRemoteAnimationRunning = false;
if (mSurfaceBehindRemoteAnimationFinishedCallback != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index d1fe7d4..4baef3a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -21,8 +21,6 @@
import android.view.WindowManager;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
@@ -33,10 +31,8 @@
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService;
import com.android.systemui.statusbar.commandline.CommandRegistry;
-import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.Optional;
-import java.util.concurrent.Executor;
import javax.inject.Named;
@@ -89,14 +85,11 @@
static Optional<MediaTttChipControllerSender> providesMediaTttChipControllerSender(
MediaTttFlags mediaTttFlags,
Context context,
- WindowManager windowManager,
- @Main Executor mainExecutor,
- @Background Executor backgroundExecutor) {
+ WindowManager windowManager) {
if (!mediaTttFlags.isMediaTttEnabled()) {
return Optional.empty();
}
- return Optional.of(new MediaTttChipControllerSender(
- context, windowManager, mainExecutor, backgroundExecutor));
+ return Optional.of(new MediaTttChipControllerSender(context, windowManager));
}
/** */
@@ -119,9 +112,7 @@
MediaTttFlags mediaTttFlags,
CommandRegistry commandRegistry,
Context context,
- MediaTttChipControllerSender mediaTttChipControllerSender,
- MediaTttChipControllerReceiver mediaTttChipControllerReceiver,
- @Main DelayableExecutor mainExecutor) {
+ MediaTttChipControllerReceiver mediaTttChipControllerReceiver) {
if (!mediaTttFlags.isMediaTttEnabled()) {
return Optional.empty();
}
@@ -129,9 +120,7 @@
new MediaTttCommandLineHelper(
commandRegistry,
context,
- mediaTttChipControllerSender,
- mediaTttChipControllerReceiver,
- mainExecutor));
+ mediaTttChipControllerReceiver));
}
/** Inject into MediaTttSenderService. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index 460d38f..3720851 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -28,21 +28,22 @@
import androidx.annotation.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver
-import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService
+import com.android.systemui.media.taptotransfer.sender.MoveCloserToEndCast
import com.android.systemui.media.taptotransfer.sender.MoveCloserToStartCast
-import com.android.systemui.media.taptotransfer.sender.TransferInitiated
-import com.android.systemui.media.taptotransfer.sender.TransferSucceeded
+import com.android.systemui.media.taptotransfer.sender.TransferFailed
+import com.android.systemui.media.taptotransfer.sender.TransferToReceiverTriggered
+import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceSucceeded
+import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceTriggered
+import com.android.systemui.media.taptotransfer.sender.TransferToReceiverSucceeded
import com.android.systemui.shared.mediattt.DeviceInfo
-import com.android.systemui.shared.mediattt.IDeviceSenderCallback
+import com.android.systemui.shared.mediattt.IDeviceSenderService
+import com.android.systemui.shared.mediattt.IUndoTransferCallback
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
-import com.android.systemui.util.concurrency.DelayableExecutor
import java.io.PrintWriter
-import java.util.concurrent.FutureTask
import javax.inject.Inject
/**
@@ -53,11 +54,9 @@
class MediaTttCommandLineHelper @Inject constructor(
commandRegistry: CommandRegistry,
private val context: Context,
- private val mediaTttChipControllerSender: MediaTttChipControllerSender,
private val mediaTttChipControllerReceiver: MediaTttChipControllerReceiver,
- @Main private val mainExecutor: DelayableExecutor,
) {
- private var senderCallback: IDeviceSenderCallback? = null
+ private var senderService: IDeviceSenderService? = null
private val senderServiceConnection = SenderServiceConnection()
private val appIconDrawable =
@@ -66,17 +65,15 @@
}
init {
- commandRegistry.registerCommand(
- ADD_CHIP_COMMAND_SENDER_TAG) { AddChipCommandSender() }
- commandRegistry.registerCommand(
- REMOVE_CHIP_COMMAND_SENDER_TAG) { RemoveChipCommandSender() }
+ commandRegistry.registerCommand(SENDER_COMMAND) { SenderCommand() }
commandRegistry.registerCommand(
ADD_CHIP_COMMAND_RECEIVER_TAG) { AddChipCommandReceiver() }
commandRegistry.registerCommand(
REMOVE_CHIP_COMMAND_RECEIVER_TAG) { RemoveChipCommandReceiver() }
}
- inner class AddChipCommandSender : Command {
+ /** All commands for the sender device. */
+ inner class SenderCommand : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
val otherDeviceName = args[0]
val mediaInfo = MediaRoute2Info.Builder("id", "Test Name")
@@ -86,62 +83,99 @@
when (args[1]) {
MOVE_CLOSER_TO_START_CAST_COMMAND_NAME -> {
- runOnService { senderCallback ->
- senderCallback.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo)
+ runOnService { senderService ->
+ senderService.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo)
}
}
-
- // TODO(b/203800643): Migrate other commands to invoke the service instead of the
- // controller.
- TRANSFER_INITIATED_COMMAND_NAME -> {
- val futureTask = FutureTask { fakeUndoRunnable }
- mediaTttChipControllerSender.displayChip(
- TransferInitiated(
- appIconDrawable,
- APP_ICON_CONTENT_DESCRIPTION,
- otherDeviceName,
- futureTask
- )
- )
- mainExecutor.executeDelayed({ futureTask.run() }, FUTURE_WAIT_TIME)
-
+ MOVE_CLOSER_TO_END_CAST_COMMAND_NAME -> {
+ runOnService { senderService ->
+ senderService.closeToReceiverToEndCast(mediaInfo, otherDeviceInfo)
+ }
}
- TRANSFER_SUCCEEDED_COMMAND_NAME -> {
- mediaTttChipControllerSender.displayChip(
- TransferSucceeded(
- appIconDrawable,
- APP_ICON_CONTENT_DESCRIPTION,
- otherDeviceName,
- fakeUndoRunnable
- )
- )
+ TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME -> {
+ runOnService { senderService ->
+ senderService.transferToReceiverTriggered(mediaInfo, otherDeviceInfo)
+ }
+ }
+ TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME -> {
+ runOnService { senderService ->
+ senderService.transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo)
+ }
+ }
+ TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME -> {
+ val undoCallback = object : IUndoTransferCallback.Stub() {
+ override fun onUndoTriggered() {
+ Log.i(TAG, "Undo transfer to receiver callback triggered")
+ // The external services that implement this callback would kick off a
+ // transfer back to this device, so mimic that here.
+ runOnService { senderService ->
+ senderService
+ .transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo)
+ }
+ }
+ }
+ runOnService { senderService ->
+ senderService
+ .transferToReceiverSucceeded(mediaInfo, otherDeviceInfo, undoCallback)
+ }
+ }
+ TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME -> {
+ val undoCallback = object : IUndoTransferCallback.Stub() {
+ override fun onUndoTriggered() {
+ Log.i(TAG, "Undo transfer to this device callback triggered")
+ // The external services that implement this callback would kick off a
+ // transfer back to the receiver, so mimic that here.
+ runOnService { senderService ->
+ senderService
+ .transferToReceiverTriggered(mediaInfo, otherDeviceInfo)
+ }
+ }
+ }
+ runOnService { senderService ->
+ senderService
+ .transferToThisDeviceSucceeded(mediaInfo, otherDeviceInfo, undoCallback)
+ }
+ }
+ TRANSFER_FAILED_COMMAND_NAME -> {
+ runOnService { senderService ->
+ senderService.transferFailed(mediaInfo, otherDeviceInfo)
+ }
+ }
+ NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME -> {
+ runOnService { senderService ->
+ senderService.noLongerCloseToReceiver(mediaInfo, otherDeviceInfo)
+ context.unbindService(senderServiceConnection)
+ }
}
else -> {
- pw.println("Chip type must be one of " +
+ pw.println("Sender command must be one of " +
"$MOVE_CLOSER_TO_START_CAST_COMMAND_NAME, " +
- "$TRANSFER_INITIATED_COMMAND_NAME, " +
- TRANSFER_SUCCEEDED_COMMAND_NAME
+ "$MOVE_CLOSER_TO_END_CAST_COMMAND_NAME, " +
+ "$TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME, " +
+ "$TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME, " +
+ "$TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME, " +
+ "$TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME, " +
+ "$TRANSFER_FAILED_COMMAND_NAME, " +
+ NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME
)
}
}
}
override fun help(pw: PrintWriter) {
- pw.println("Usage: adb shell cmd statusbar " +
- "$ADD_CHIP_COMMAND_SENDER_TAG <deviceName> <chipStatus>"
- )
+ pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND <deviceName> <chipStatus>")
}
- private fun runOnService(command: SenderCallbackCommand) {
- val currentServiceCallback = senderCallback
- if (currentServiceCallback != null) {
- command.run(currentServiceCallback)
+ private fun runOnService(command: SenderServiceCommand) {
+ val currentService = senderService
+ if (currentService != null) {
+ command.run(currentService)
} else {
bindService(command)
}
}
- private fun bindService(command: SenderCallbackCommand) {
+ private fun bindService(command: SenderServiceCommand) {
senderServiceConnection.pendingCommand = command
val binding = context.bindService(
Intent(context, MediaTttSenderService::class.java),
@@ -152,19 +186,6 @@
}
}
- /** A command to REMOVE the media ttt chip on the SENDER device. */
- inner class RemoveChipCommandSender : Command {
- override fun execute(pw: PrintWriter, args: List<String>) {
- mediaTttChipControllerSender.removeChip()
- if (senderCallback != null) {
- context.unbindService(senderServiceConnection)
- }
- }
- override fun help(pw: PrintWriter) {
- pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_SENDER_TAG")
- }
- }
-
/** A command to DISPLAY the media ttt chip on the RECEIVER device. */
inner class AddChipCommandReceiver : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
@@ -187,36 +208,32 @@
}
}
- /** A service connection for [IDeviceSenderCallback]. */
+ /** A service connection for [IDeviceSenderService]. */
private inner class SenderServiceConnection : ServiceConnection {
// A command that should be run when the service gets connected.
- var pendingCommand: SenderCallbackCommand? = null
+ var pendingCommand: SenderServiceCommand? = null
override fun onServiceConnected(className: ComponentName, service: IBinder) {
- val newCallback = IDeviceSenderCallback.Stub.asInterface(service)
- senderCallback = newCallback
+ val newCallback = IDeviceSenderService.Stub.asInterface(service)
+ senderService = newCallback
pendingCommand?.run(newCallback)
pendingCommand = null
}
override fun onServiceDisconnected(className: ComponentName) {
- senderCallback = null
+ senderService = null
}
}
- /** An interface defining a command that should be run on the sender callback. */
- private fun interface SenderCallbackCommand {
- /** Runs the command on the provided [senderCallback]. */
- fun run(senderCallback: IDeviceSenderCallback)
- }
-
- private val fakeUndoRunnable = Runnable {
- Log.i(TAG, "Undo runnable triggered")
+ /** An interface defining a command that should be run on the sender service. */
+ private fun interface SenderServiceCommand {
+ /** Runs the command on the provided [senderService]. */
+ fun run(senderService: IDeviceSenderService)
}
}
@VisibleForTesting
-const val ADD_CHIP_COMMAND_SENDER_TAG = "media-ttt-chip-add-sender"
+const val SENDER_COMMAND = "media-ttt-chip-sender"
@VisibleForTesting
const val REMOVE_CHIP_COMMAND_SENDER_TAG = "media-ttt-chip-remove-sender"
@VisibleForTesting
@@ -226,10 +243,21 @@
@VisibleForTesting
val MOVE_CLOSER_TO_START_CAST_COMMAND_NAME = MoveCloserToStartCast::class.simpleName!!
@VisibleForTesting
-val TRANSFER_INITIATED_COMMAND_NAME = TransferInitiated::class.simpleName!!
+val MOVE_CLOSER_TO_END_CAST_COMMAND_NAME = MoveCloserToEndCast::class.simpleName!!
@VisibleForTesting
-val TRANSFER_SUCCEEDED_COMMAND_NAME = TransferSucceeded::class.simpleName!!
+val TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME = TransferToReceiverTriggered::class.simpleName!!
+@VisibleForTesting
+val TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME =
+ TransferToThisDeviceTriggered::class.simpleName!!
+@VisibleForTesting
+val TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME = TransferToReceiverSucceeded::class.simpleName!!
+@VisibleForTesting
+val TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME =
+ TransferToThisDeviceSucceeded::class.simpleName!!
+@VisibleForTesting
+val TRANSFER_FAILED_COMMAND_NAME = TransferFailed::class.simpleName!!
+@VisibleForTesting
+val NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME = "NoLongerCloseToReceiver"
-private const val FUTURE_WAIT_TIME = 2000L
private const val APP_ICON_CONTENT_DESCRIPTION = "Fake media app icon"
private const val TAG = "MediaTapToTransferCli"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index 67721a5..adae07b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -81,6 +81,9 @@
/** Hides the chip. */
fun removeChip() {
+ // TODO(b/203800347): We may not want to hide the chip if we're currently in a
+ // TransferTriggered state: Once the user has initiated the transfer, they should be able
+ // to move away from the receiver device but still see the status of the transfer.
if (chipView == null) { return }
windowManager.removeView(chipView)
chipView = null
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index dd434e7..c656df2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -16,11 +16,12 @@
package com.android.systemui.media.taptotransfer.sender
+import android.content.Context
import android.graphics.drawable.Drawable
-import androidx.annotation.StringRes
+import android.view.View
import com.android.systemui.R
import com.android.systemui.media.taptotransfer.common.MediaTttChipState
-import java.util.concurrent.Future
+import com.android.systemui.shared.mediattt.IUndoTransferCallback
/**
* A class that stores all the information necessary to display the media tap-to-transfer chip on
@@ -28,66 +29,181 @@
*
* This is a sealed class where each subclass represents a specific chip state. Each subclass can
* contain additional information that is necessary for only that state.
- *
- * @property chipText a string resource for the text that the chip should display.
- * @property otherDeviceName the name of the other device involved in the transfer.
*/
sealed class ChipStateSender(
appIconDrawable: Drawable,
- appIconContentDescription: String,
- @StringRes internal val chipText: Int,
- internal val otherDeviceName: String,
-) : MediaTttChipState(appIconDrawable, appIconContentDescription)
+ appIconContentDescription: String
+) : MediaTttChipState(appIconDrawable, appIconContentDescription) {
+ /** Returns a fully-formed string with the text that the chip should display. */
+ abstract fun getChipTextString(context: Context): String
+
+ /** Returns true if the loading icon should be displayed and false otherwise. */
+ open fun showLoading(): Boolean = false
+
+ /**
+ * Returns a click listener for the undo button on the chip. Returns null if this chip state
+ * doesn't have an undo button.
+ *
+ * @param controllerSender passed as a parameter in case we want to display a new chip state
+ * when undo is clicked.
+ */
+ open fun undoClickListener(
+ controllerSender: MediaTttChipControllerSender
+ ): View.OnClickListener? = null
+}
/**
* A state representing that the two devices are close but not close enough to *start* a cast to
* the receiver device. The chip will instruct the user to move closer in order to initiate the
* transfer to the receiver.
+ *
+ * @property otherDeviceName the name of the other device involved in the transfer.
*/
class MoveCloserToStartCast(
appIconDrawable: Drawable,
appIconContentDescription: String,
- otherDeviceName: String,
-) : ChipStateSender(
- appIconDrawable,
- appIconContentDescription,
- R.string.media_move_closer_to_start_cast,
- otherDeviceName
-)
+ private val otherDeviceName: String,
+) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+ override fun getChipTextString(context: Context): String {
+ return context.getString(R.string.media_move_closer_to_start_cast, otherDeviceName)
+ }
+}
/**
- * A state representing that a transfer has been initiated (but not completed).
+ * A state representing that the two devices are close but not close enough to *end* a cast that's
+ * currently occurring the receiver device. The chip will instruct the user to move closer in order
+ * to initiate the transfer from the receiver and back onto this device (the original sender).
*
- * @property future a future that will be resolved when the transfer has either succeeded or failed.
- * If the transfer succeeded, the future can optionally return an undo runnable (see
- * [TransferSucceeded.undoRunnable]). [MediaTttChipControllerSender] is responsible for transitioning
- * the chip to the [TransferSucceeded] state if the future resolves successfully.
+ * @property otherDeviceName the name of the other device involved in the transfer.
*/
-class TransferInitiated(
+class MoveCloserToEndCast(
appIconDrawable: Drawable,
appIconContentDescription: String,
- otherDeviceName: String,
- val future: Future<Runnable?>
-) : ChipStateSender(
- appIconDrawable,
- appIconContentDescription,
- R.string.media_transfer_playing,
- otherDeviceName
-)
+ private val otherDeviceName: String,
+) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+ override fun getChipTextString(context: Context): String {
+ return context.getString(R.string.media_move_closer_to_end_cast, otherDeviceName)
+ }
+}
/**
- * A state representing that a transfer has been successfully completed.
+ * A state representing that a transfer to the receiver device has been initiated (but not
+ * completed).
*
- * @property undoRunnable if present, the runnable that should be run to undo the transfer. We will
- * show an Undo button on the chip if this runnable is present.
+ * @property otherDeviceName the name of the other device involved in the transfer.
*/
-class TransferSucceeded(
+class TransferToReceiverTriggered(
appIconDrawable: Drawable,
appIconContentDescription: String,
- otherDeviceName: String,
- val undoRunnable: Runnable? = null
-) : ChipStateSender(appIconDrawable,
- appIconContentDescription,
- R.string.media_transfer_playing,
- otherDeviceName
-)
+ private val otherDeviceName: String
+) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+ override fun getChipTextString(context: Context): String {
+ return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName)
+ }
+
+ override fun showLoading() = true
+}
+
+/**
+ * A state representing that a transfer from the receiver device and back to this device (the
+ * sender) has been initiated (but not completed).
+ */
+class TransferToThisDeviceTriggered(
+ appIconDrawable: Drawable,
+ appIconContentDescription: String
+) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+ override fun getChipTextString(context: Context): String {
+ return context.getString(R.string.media_transfer_playing_this_device)
+ }
+
+ override fun showLoading() = true
+}
+
+/**
+ * A state representing that a transfer to the receiver device has been successfully completed.
+ *
+ * @property otherDeviceName the name of the other device involved in the transfer.
+ * @property undoCallback if present, the callback that should be called when the user clicks the
+ * undo button. The undo button will only be shown if this is non-null.
+ */
+class TransferToReceiverSucceeded(
+ appIconDrawable: Drawable,
+ appIconContentDescription: String,
+ private val otherDeviceName: String,
+ val undoCallback: IUndoTransferCallback? = null
+) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+ override fun getChipTextString(context: Context): String {
+ return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName)
+ }
+
+ override fun undoClickListener(
+ controllerSender: MediaTttChipControllerSender
+ ): View.OnClickListener? {
+ if (undoCallback == null) {
+ return null
+ }
+
+ return View.OnClickListener {
+ this.undoCallback.onUndoTriggered()
+ // The external service should eventually send us a TransferToThisDeviceTriggered state,
+ // but that may take too long to go through the binder and the user may be confused as
+ // to why the UI hasn't changed yet. So, we immediately change the UI here.
+ controllerSender.displayChip(
+ TransferToThisDeviceTriggered(
+ this.appIconDrawable,
+ this.appIconContentDescription
+ )
+ )
+ }
+ }
+}
+
+/**
+ * A state representing that a transfer back to this device has been successfully completed.
+ *
+ * @property otherDeviceName the name of the other device involved in the transfer.
+ * @property undoCallback if present, the callback that should be called when the user clicks the
+ * undo button. The undo button will only be shown if this is non-null.
+ */
+class TransferToThisDeviceSucceeded(
+ appIconDrawable: Drawable,
+ appIconContentDescription: String,
+ private val otherDeviceName: String,
+ val undoCallback: IUndoTransferCallback? = null
+) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+ override fun getChipTextString(context: Context): String {
+ return context.getString(R.string.media_transfer_playing_this_device)
+ }
+
+ override fun undoClickListener(
+ controllerSender: MediaTttChipControllerSender
+ ): View.OnClickListener? {
+ if (undoCallback == null) {
+ return null
+ }
+
+ return View.OnClickListener {
+ this.undoCallback.onUndoTriggered()
+ // The external service should eventually send us a TransferToReceiverTriggered state,
+ // but that may take too long to go through the binder and the user may be confused as
+ // to why the UI hasn't changed yet. So, we immediately change the UI here.
+ controllerSender.displayChip(
+ TransferToReceiverTriggered(
+ this.appIconDrawable,
+ this.appIconContentDescription,
+ this.otherDeviceName
+ )
+ )
+ }
+ }
+}
+
+/** A state representing that a transfer has failed. */
+class TransferFailed(
+ appIconDrawable: Drawable,
+ appIconContentDescription: String
+) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+ override fun getChipTextString(context: Context): String {
+ return context.getString(R.string.media_transfer_failed)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 77d3d70..453e3d6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -23,11 +23,7 @@
import android.widget.TextView
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon
-import java.util.concurrent.Executor
-import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
@@ -38,8 +34,6 @@
class MediaTttChipControllerSender @Inject constructor(
context: Context,
windowManager: WindowManager,
- @Main private val mainExecutor: Executor,
- @Background private val backgroundExecutor: Executor,
) : MediaTttChipControllerCommon<ChipStateSender>(
context, windowManager, R.layout.media_ttt_chip
) {
@@ -51,63 +45,22 @@
// Text
currentChipView.requireViewById<TextView>(R.id.text).apply {
- text = context.getString(chipState.chipText, chipState.otherDeviceName)
+ text = chipState.getChipTextString(context)
}
// Loading
- val showLoading = chipState is TransferInitiated
currentChipView.requireViewById<View>(R.id.loading).visibility =
- if (showLoading) { View.VISIBLE } else { View.GONE }
+ if (chipState.showLoading()) { View.VISIBLE } else { View.GONE }
// Undo
- val undoClickListener: View.OnClickListener? =
- if (chipState is TransferSucceeded && chipState.undoRunnable != null)
- View.OnClickListener { chipState.undoRunnable.run() }
- else
- null
val undoView = currentChipView.requireViewById<View>(R.id.undo)
- undoView.visibility = if (undoClickListener != null) {
- View.VISIBLE
- } else {
- View.GONE
- }
+ val undoClickListener = chipState.undoClickListener(this)
undoView.setOnClickListener(undoClickListener)
+ undoView.visibility = if (undoClickListener != null) { View.VISIBLE } else { View.GONE }
- // Future handling
- if (chipState is TransferInitiated) {
- addFutureCallback(chipState)
- }
- }
-
- /**
- * Adds the appropriate callbacks to [chipState.future] so that we update the chip correctly
- * when the future resolves.
- */
- private fun addFutureCallback(chipState: TransferInitiated) {
- // Listen to the future on a background thread so we don't occupy the main thread while we
- // wait for it to complete.
- backgroundExecutor.execute {
- try {
- val undoRunnable = chipState.future.get(TRANSFER_TIMEOUT_SECONDS, TimeUnit.SECONDS)
- // Make UI changes on the main thread
- mainExecutor.execute {
- displayChip(
- TransferSucceeded(
- chipState.appIconDrawable,
- chipState.appIconContentDescription,
- chipState.otherDeviceName,
- undoRunnable
- )
- )
- }
- } catch (ex: Exception) {
- // TODO(b/203800327): Maybe show a failure chip here if UX decides we need one.
- mainExecutor.execute {
- removeChip()
- }
- }
- }
+ // Failure
+ val showFailure = chipState is TransferFailed
+ currentChipView.requireViewById<View>(R.id.failure_icon).visibility =
+ if (showFailure) { View.VISIBLE } else { View.GONE }
}
}
-
-private const val TRANSFER_TIMEOUT_SECONDS = 10L
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt
index b56a699..717752e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt
@@ -25,7 +25,8 @@
import android.os.IBinder
import com.android.systemui.R
import com.android.systemui.shared.mediattt.DeviceInfo
-import com.android.systemui.shared.mediattt.IDeviceSenderCallback
+import com.android.systemui.shared.mediattt.IUndoTransferCallback
+import com.android.systemui.shared.mediattt.IDeviceSenderService
import javax.inject.Inject
/**
@@ -37,12 +38,63 @@
) : Service() {
// TODO(b/203800643): Add logging when callbacks trigger.
- private val binder: IBinder = object : IDeviceSenderCallback.Stub() {
+ private val binder: IBinder = object : IDeviceSenderService.Stub() {
override fun closeToReceiverToStartCast(
mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
) {
this@MediaTttSenderService.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo)
}
+
+ override fun closeToReceiverToEndCast(
+ mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
+ ) {
+ this@MediaTttSenderService.closeToReceiverToEndCast(mediaInfo, otherDeviceInfo)
+ }
+
+ override fun transferFailed(
+ mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
+ ) {
+ this@MediaTttSenderService.transferFailed(mediaInfo)
+ }
+
+ override fun transferToReceiverTriggered(
+ mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
+ ) {
+ this@MediaTttSenderService.transferToReceiverTriggered(mediaInfo, otherDeviceInfo)
+ }
+
+ override fun transferToThisDeviceTriggered(
+ mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
+ ) {
+ this@MediaTttSenderService.transferToThisDeviceTriggered(mediaInfo)
+ }
+
+ override fun transferToReceiverSucceeded(
+ mediaInfo: MediaRoute2Info,
+ otherDeviceInfo: DeviceInfo,
+ undoCallback: IUndoTransferCallback
+ ) {
+ this@MediaTttSenderService.transferToReceiverSucceeded(
+ mediaInfo, otherDeviceInfo, undoCallback
+ )
+ }
+
+ override fun transferToThisDeviceSucceeded(
+ mediaInfo: MediaRoute2Info,
+ otherDeviceInfo: DeviceInfo,
+ undoCallback: IUndoTransferCallback
+ ) {
+ this@MediaTttSenderService.transferToThisDeviceSucceeded(
+ mediaInfo, otherDeviceInfo, undoCallback
+ )
+ }
+
+ override fun noLongerCloseToReceiver(
+ mediaInfo: MediaRoute2Info,
+ otherDeviceInfo: DeviceInfo
+ ) {
+ this@MediaTttSenderService.noLongerCloseToReceiver()
+ }
}
// TODO(b/203800643): Use the app icon from the media info instead of a fake one.
@@ -63,4 +115,68 @@
)
controller.displayChip(chipState)
}
+
+ private fun closeToReceiverToEndCast(mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo) {
+ val chipState = MoveCloserToEndCast(
+ appIconDrawable = fakeAppIconDrawable,
+ appIconContentDescription = mediaInfo.name.toString(),
+ otherDeviceName = otherDeviceInfo.name
+ )
+ controller.displayChip(chipState)
+ }
+
+ private fun transferFailed(mediaInfo: MediaRoute2Info) {
+ val chipState = TransferFailed(
+ appIconDrawable = fakeAppIconDrawable,
+ appIconContentDescription = mediaInfo.name.toString()
+ )
+ controller.displayChip(chipState)
+ }
+
+ private fun transferToReceiverTriggered(
+ mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
+ ) {
+ val chipState = TransferToReceiverTriggered(
+ appIconDrawable = fakeAppIconDrawable,
+ appIconContentDescription = mediaInfo.name.toString(),
+ otherDeviceName = otherDeviceInfo.name
+ )
+ controller.displayChip(chipState)
+ }
+
+ private fun transferToThisDeviceTriggered(mediaInfo: MediaRoute2Info) {
+ val chipState = TransferToThisDeviceTriggered(
+ appIconDrawable = fakeAppIconDrawable,
+ appIconContentDescription = mediaInfo.name.toString()
+ )
+ controller.displayChip(chipState)
+ }
+
+ private fun transferToReceiverSucceeded(
+ mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo, undoCallback: IUndoTransferCallback
+ ) {
+ val chipState = TransferToReceiverSucceeded(
+ appIconDrawable = fakeAppIconDrawable,
+ appIconContentDescription = mediaInfo.name.toString(),
+ otherDeviceName = otherDeviceInfo.name,
+ undoCallback = undoCallback
+ )
+ controller.displayChip(chipState)
+ }
+
+ private fun transferToThisDeviceSucceeded(
+ mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo, undoCallback: IUndoTransferCallback
+ ) {
+ val chipState = TransferToThisDeviceSucceeded(
+ appIconDrawable = fakeAppIconDrawable,
+ appIconContentDescription = mediaInfo.name.toString(),
+ otherDeviceName = otherDeviceInfo.name,
+ undoCallback = undoCallback
+ )
+ controller.displayChip(chipState)
+ }
+
+ private fun noLongerCloseToReceiver() {
+ controller.removeChip()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 60060aa..00a3149 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -31,9 +31,9 @@
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_STARTING_WINDOW;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
@@ -83,6 +83,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBar;
@@ -97,7 +98,6 @@
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.NotificationPanelViewController;
@@ -161,7 +161,7 @@
private final CommandQueue mCommandQueue;
private final ShellTransitions mShellTransitions;
private final Optional<StartingSurface> mStartingSurface;
- private final SmartspaceTransitionController mSmartspaceTransitionController;
+ private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController;
private final Optional<RecentTasks> mRecentTasks;
private final UiEventLogger mUiEventLogger;
@@ -503,8 +503,8 @@
KEY_EXTRA_SHELL_STARTING_WINDOW,
startingwindow.createExternalInterface().asBinder()));
params.putBinder(
- KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER,
- mSmartspaceTransitionController.createExternalInterface().asBinder());
+ KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
+ mSysuiUnlockAnimationController.asBinder());
mRecentTasks.ifPresent(recentTasks -> params.putBinder(
KEY_EXTRA_RECENT_TASKS,
recentTasks.createExternalInterface().asBinder()));
@@ -570,8 +570,8 @@
BroadcastDispatcher broadcastDispatcher,
ShellTransitions shellTransitions,
ScreenLifecycle screenLifecycle,
- SmartspaceTransitionController smartspaceTransitionController,
UiEventLogger uiEventLogger,
+ KeyguardUnlockAnimationController sysuiUnlockAnimationController,
DumpManager dumpManager) {
super(broadcastDispatcher);
mContext = context;
@@ -644,7 +644,7 @@
updateEnabledState();
startConnectionToCurrentUser();
mStartingSurface = startingSurface;
- mSmartspaceTransitionController = smartspaceTransitionController;
+ mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/shared/system/smartspace/SmartspaceTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shared/system/smartspace/SmartspaceTransitionController.kt
deleted file mode 100644
index 89b3df0..0000000
--- a/packages/SystemUI/src/com/android/systemui/shared/system/smartspace/SmartspaceTransitionController.kt
+++ /dev/null
@@ -1,143 +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.systemui.shared.system.smartspace
-
-import android.graphics.Rect
-import android.view.View
-import com.android.systemui.shared.system.ActivityManagerWrapper
-import com.android.systemui.shared.system.QuickStepContract
-import kotlin.math.min
-
-/**
- * Controller that keeps track of SmartSpace instances in remote processes (such as Launcher),
- * allowing System UI to query or update their state during shared-element transitions.
- */
-class SmartspaceTransitionController {
-
- /**
- * Implementation of [ISmartspaceTransitionController] that we provide to Launcher, allowing it
- * to provide us with a callback to query and update the state of its Smartspace.
- */
- private val ISmartspaceTransitionController = object : ISmartspaceTransitionController.Stub() {
- override fun setSmartspace(callback: ISmartspaceCallback?) {
- this@SmartspaceTransitionController.launcherSmartspace = callback
- updateLauncherSmartSpaceState()
- }
- }
-
- /**
- * Callback provided by Launcher to allow us to query and update the state of its SmartSpace.
- */
- public var launcherSmartspace: ISmartspaceCallback? = null
-
- public var lockscreenSmartspace: View? = null
-
- /**
- * Cached state of the Launcher SmartSpace. Retrieving the state is an IPC, so we should avoid
- * unnecessary
- */
- public var mLauncherSmartspaceState: SmartspaceState? = null
-
- /**
- * The bounds of our SmartSpace when the shared element transition began. We'll interpolate
- * between this and [smartspaceDestinationBounds] as the dismiss amount changes.
- */
- private val smartspaceOriginBounds = Rect()
-
- /** The bounds of the Launcher's SmartSpace, which is where we are animating our SmartSpace. */
-
- private val smartspaceDestinationBounds = Rect()
-
- fun createExternalInterface(): ISmartspaceTransitionController {
- return ISmartspaceTransitionController
- }
-
- /**
- * Updates [mLauncherSmartspaceState] and returns it. This will trigger a binder call, so use the
- * cached [mLauncherSmartspaceState] if possible.
- */
- fun updateLauncherSmartSpaceState(): SmartspaceState? {
- return launcherSmartspace?.smartspaceState.also {
- mLauncherSmartspaceState = it
- }
- }
-
- fun prepareForUnlockTransition() {
- updateLauncherSmartSpaceState().also { state ->
- if (state?.boundsOnScreen != null && lockscreenSmartspace != null) {
- lockscreenSmartspace!!.getBoundsOnScreen(smartspaceOriginBounds)
- with(smartspaceDestinationBounds) {
- set(state.boundsOnScreen)
- offset(-lockscreenSmartspace!!.paddingLeft,
- -lockscreenSmartspace!!.paddingTop)
- }
- }
- }
- }
-
- fun setProgressToDestinationBounds(progress: Float) {
- if (!isSmartspaceTransitionPossible()) {
- return
- }
-
- val progressClamped = min(1f, progress)
-
- // Calculate the distance (relative to the origin) that we need to be for the current
- // progress value.
- val progressX =
- (smartspaceDestinationBounds.left - smartspaceOriginBounds.left) * progressClamped
- val progressY =
- (smartspaceDestinationBounds.top - smartspaceOriginBounds.top) * progressClamped
-
- val lockscreenSmartspaceCurrentBounds = Rect().also {
- lockscreenSmartspace!!.getBoundsOnScreen(it)
- }
-
- // Figure out how far that is from our present location on the screen. This approach
- // compensates for the fact that our parent container is also translating to animate out.
- val dx = smartspaceOriginBounds.left + progressX -
- lockscreenSmartspaceCurrentBounds.left
- var dy = smartspaceOriginBounds.top + progressY -
- lockscreenSmartspaceCurrentBounds.top
-
- with(lockscreenSmartspace!!) {
- translationX = translationX + dx
- translationY = translationY + dy
- }
- }
-
- /**
- * Whether we're capable of performing the Smartspace shared element transition when we unlock.
- * This is true if:
- *
- * - The Launcher registered a Smartspace with us, it's reporting non-empty bounds on screen.
- * - Launcher is behind the keyguard, and the Smartspace is visible on the currently selected
- * page.
- */
- public fun isSmartspaceTransitionPossible(): Boolean {
- val smartSpaceNullOrBoundsEmpty = mLauncherSmartspaceState?.boundsOnScreen?.isEmpty ?: true
- return isLauncherUnderneath() && !smartSpaceNullOrBoundsEmpty
- }
-
- companion object {
- fun isLauncherUnderneath(): Boolean {
- return ActivityManagerWrapper.getInstance()
- .runningTask?.topActivity?.className?.equals(
- QuickStepContract.LAUNCHER_ACTIVITY_CLASS_NAME) ?: false
- }
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index 2a21f42..6cfbb43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -320,7 +320,7 @@
* @param updatePostTime whether or not to refresh the post time
*/
public void updateEntry(boolean updatePostTime) {
- mLogger.logUpdateEntry(updatePostTime);
+ mLogger.logUpdateEntry(mEntry.getKey(), updatePostTime);
long currentTime = mClock.currentTimeMillis();
mEarliestRemovaltime = currentTime + mMinimumDisplayTime;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 46004db..267ee6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -154,6 +154,16 @@
}
/**
+ * We're unlocking, and should not blur as the panel expansion changes.
+ */
+ var blursDisabledForUnlock: Boolean = false
+ set(value) {
+ if (field == value) return
+ field = value
+ scheduleUpdate()
+ }
+
+ /**
* Force stop blur effect when necessary.
*/
private var scrimsVisible: Boolean = false
@@ -192,7 +202,7 @@
combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(transitionToFullShadeProgress))
var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius)
- if (blursDisabledForAppLaunch) {
+ if (blursDisabledForAppLaunch || blursDisabledForUnlock) {
shadeRadius = 0f
}
@@ -309,9 +319,7 @@
/**
* Update blurs when pulling down the shade
*/
- override fun onPanelExpansionChanged(
- rawFraction: Float, expanded: Boolean, tracking: Boolean
- ) {
+ override fun onPanelExpansionChanged(rawFraction: Float, expanded: Boolean, tracking: Boolean) {
val timestamp = SystemClock.elapsedRealtimeNanos()
val expansion = MathUtils.saturate(
(rawFraction - panelPullDownMinFraction) / (1f - panelPullDownMinFraction))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index f22acb7..0fd9272 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -638,22 +638,6 @@
if (row != null) row.setHeadsUpAnimatingAway(animatingAway);
}
- /**
- * Set that this notification was automatically heads upped. This happens for example when
- * the user bypasses the lockscreen and media is playing.
- */
- public void setAutoHeadsUp(boolean autoHeadsUp) {
- mAutoHeadsUp = autoHeadsUp;
- }
-
- /**
- * @return if this notification was automatically heads upped. This happens for example when
- * * the user bypasses the lockscreen and media is playing.
- */
- public boolean isAutoHeadsUp() {
- return mAutoHeadsUp;
- }
-
public boolean mustStayOnScreen() {
return row != null && row.mustStayOnScreen();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index b84b382..0bf21af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -100,7 +100,7 @@
if (wasHeadsUp) {
if (shouldHeadsUp) {
mHeadsUpManager.updateNotification(entry.key, hunAgain)
- } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.key)) {
+ } else {
// We don't want this to be interrupting anymore, let's remove it
mHeadsUpManager.removeNotification(
entry.key, false /* removeImmediately */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
index 289dacb..26ba12c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
@@ -41,17 +41,29 @@
fun getChildCount(): Int = 0
+ /** Called to add a child to this view */
fun addChildAt(child: NodeController, index: Int) {
throw RuntimeException("Not supported")
}
+ /** Called to move one of this view's current children to a new position */
fun moveChildTo(child: NodeController, index: Int) {
throw RuntimeException("Not supported")
}
+ /** Called to remove one of this view's current children */
fun removeChild(child: NodeController, isTransfer: Boolean) {
throw RuntimeException("Not supported")
}
+
+ /** Called when this view has been added */
+ fun onViewAdded() {}
+
+ /** Called when this view has been moved */
+ fun onViewMoved() {}
+
+ /** Called when this view has been removed */
+ fun onViewRemoved() {}
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
index 4e9017e..2c9508e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
@@ -94,6 +94,10 @@
_view?.setOnClearAllClickListener(listener)
}
+ override fun onViewAdded() {
+ headerView?.isContentVisible = true
+ }
+
override val view: View
get() = _view!!
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 6d4ae4b..28cd285 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -215,13 +215,16 @@
fun addChildAt(child: ShadeNode, index: Int) {
controller.addChildAt(child.controller, index)
+ child.controller.onViewAdded()
}
fun moveChildTo(child: ShadeNode, index: Int) {
controller.moveChildTo(child.controller, index)
+ child.controller.onViewMoved()
}
fun removeChild(child: ShadeNode, isTransfer: Boolean) {
controller.removeChild(child.controller, isTransfer)
+ child.controller.onViewRemoved()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
deleted file mode 100644
index b61a540..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification.interruption
-
-import android.content.Context
-import android.media.MediaMetadata
-import android.provider.Settings
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.NotificationLockscreenUserManager
-import com.android.systemui.statusbar.NotificationMediaManager
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
-import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.tuner.TunerService
-import javax.inject.Inject
-
-/**
- * A class that automatically creates heads up for important notification when bypassing the
- * lockscreen
- */
-@SysUISingleton
-class BypassHeadsUpNotifier @Inject constructor(
- private val context: Context,
- private val bypassController: KeyguardBypassController,
- private val statusBarStateController: StatusBarStateController,
- private val headsUpManager: HeadsUpManagerPhone,
- private val notificationLockscreenUserManager: NotificationLockscreenUserManager,
- private val mediaManager: NotificationMediaManager,
- private val commonNotifCollection: CommonNotifCollection,
- tunerService: TunerService
-) : StatusBarStateController.StateListener, NotificationMediaManager.MediaListener {
-
- private var currentMediaEntry: NotificationEntry? = null
- private var enabled = true
-
- var fullyAwake = false
- set(value) {
- field = value
- if (value) {
- updateAutoHeadsUp(currentMediaEntry)
- }
- }
-
- init {
- statusBarStateController.addCallback(this)
- tunerService.addTunable(
- TunerService.Tunable { _, _ ->
- enabled = Settings.Secure.getIntForUser(
- context.contentResolver,
- Settings.Secure.SHOW_MEDIA_WHEN_BYPASSING,
- 0 /* default */,
- KeyguardUpdateMonitor.getCurrentUser()) != 0
- }, Settings.Secure.SHOW_MEDIA_WHEN_BYPASSING)
- }
-
- fun setUp() {
- mediaManager.addCallback(this)
- }
-
- override fun onPrimaryMetadataOrStateChanged(metadata: MediaMetadata?, state: Int) {
- val previous = currentMediaEntry
- val mediaNotificationKey = mediaManager.mediaNotificationKey
- currentMediaEntry =
- if (mediaNotificationKey != null && NotificationMediaManager.isPlayingState(state))
- commonNotifCollection.getEntry(mediaNotificationKey)
- else null
- updateAutoHeadsUp(previous)
- updateAutoHeadsUp(currentMediaEntry)
- }
-
- private fun updateAutoHeadsUp(entry: NotificationEntry?) {
- entry?.let {
- val autoHeadsUp = it == currentMediaEntry && canAutoHeadsUp(it)
- it.isAutoHeadsUp = autoHeadsUp
- if (autoHeadsUp) {
- headsUpManager.showNotification(it)
- }
- }
- }
-
- /**
- * @return {@code true} if this entry be autoHeadsUpped right now.
- */
- private fun canAutoHeadsUp(entry: NotificationEntry): Boolean {
- if (!isAutoHeadsUpAllowed()) {
- return false
- }
- if (entry.isSensitive) {
- // filter sensitive notifications
- return false
- }
- if (!notificationLockscreenUserManager.shouldShowOnKeyguard(entry)) {
- // filter notifications invisible on Keyguard
- return false
- }
- if (commonNotifCollection.getEntry(entry.key) != null) {
- // filter notifications not the active list currently
- return false
- }
- return true
- }
-
- override fun onStatePostChange() {
- updateAutoHeadsUp(currentMediaEntry)
- }
-
- /**
- * @return {@code true} if autoHeadsUp is possible right now.
- */
- private fun isAutoHeadsUpAllowed(): Boolean {
- if (!enabled) {
- return false
- }
- if (!bypassController.bypassEnabled) {
- return false
- }
- if (statusBarStateController.state != StatusBarState.KEYGUARD) {
- return false
- }
- if (!fullyAwake) {
- return false
- }
- return true
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
index b1c69e4..74fb3f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
@@ -124,7 +124,7 @@
if (wasHeadsUp) {
if (shouldHeadsUp) {
mHeadsUpManager.updateNotification(entry.getKey(), hunAgain);
- } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.getKey())) {
+ } else {
// We don't want this to be interrupting anymore, let's remove it
mHeadsUpManager.removeNotification(entry.getKey(), false /* removeImmediately */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 63cb4ae..5d6d0f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -424,7 +424,8 @@
}
@Override
- public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
+ public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
+ Runnable onFinishRunnable) {
enableAppearDrawing(true);
mIsHeadsUpAnimation = isHeadsUpAppear;
if (mDrawingAppearAnimation) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index b28fb58..46efef6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -268,6 +268,18 @@
}
@Override
+ public void onViewAdded() {
+ }
+
+ @Override
+ public void onViewMoved() {
+ }
+
+ @Override
+ public void onViewRemoved() {
+ }
+
+ @Override
public int getChildCount() {
final List<ExpandableNotificationRow> mChildren = mView.getAttachedChildren();
return mChildren != null ? mChildren.size() : 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 6eff799..8ffdb69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -358,7 +358,12 @@
Runnable onFinishedRunnable,
AnimatorListenerAdapter animationListener);
- public abstract void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear);
+ public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
+ performAddAnimation(delay, duration, isHeadsUpAppear, null);
+ }
+
+ public abstract void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
+ Runnable onEndRunnable);
/**
* Set the notification appearance to be below the speed bump.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
index f26598d..ec406f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
@@ -18,7 +18,6 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
import com.android.systemui.log.dagger.NotificationLog
import javax.inject.Inject
@@ -50,7 +49,7 @@
}
fun logRequestPipelineRowNotSet(notifKey: String) {
- buffer.log(TAG, WARNING, {
+ buffer.log(TAG, INFO, {
str1 = notifKey
}, {
"Row is not set so pipeline will not run. notif = $str1"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index 9c755e9..aa3e027 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -105,6 +105,9 @@
runAfter.run();
};
setViewVisible(mContent, visible, animate, endRunnable);
+ } else if (runAfter != null) {
+ // Execute the runAfter runnable immediately if there's no animation to perform.
+ runAfter.run();
}
if (!mContentAnimating) {
@@ -228,7 +231,7 @@
Runnable onFinishedRunnable,
AnimatorListenerAdapter animationListener) {
// TODO: Use duration
- setContentVisible(false);
+ setContentVisible(false, true /* animate */, onFinishedRunnable);
return 0;
}
@@ -239,6 +242,13 @@
}
@Override
+ public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
+ Runnable endRunnable) {
+ // TODO: use delay and duration
+ setContentVisible(true);
+ }
+
+ @Override
public boolean needsClippingToShelf() {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java
index c9a0f6c..bd5b7d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java
@@ -39,7 +39,8 @@
}
@Override
- public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
+ public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
+ Runnable onEnd) {
// No animation, it doesn't need it, this would be local
}
}
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 90f5179..4283343 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
@@ -5658,6 +5658,10 @@
}
}
+ protected void setLogger(StackStateLogger logger) {
+ mStateAnimator.setLogger(logger);
+ }
+
/**
* A listener that is notified when the empty space below the notifications is clicked on
*/
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 5833ec2..51ce779 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
@@ -184,6 +184,7 @@
private final SectionHeaderController mSilentHeaderController;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private final InteractionJankMonitor mJankMonitor;
+ private final StackStateLogger mStackStateLogger;
private NotificationStackScrollLayout mView;
private boolean mFadeNotificationsOnDismiss;
@@ -660,7 +661,9 @@
NotificationRemoteInputManager remoteInputManager,
VisualStabilityManager visualStabilityManager,
ShadeController shadeController,
- InteractionJankMonitor jankMonitor) {
+ InteractionJankMonitor jankMonitor,
+ StackStateLogger stackLogger) {
+ mStackStateLogger = stackLogger;
mAllowLongPress = allowLongPress;
mNotificationGutsManager = notificationGutsManager;
mVisibilityProvider = visibilityProvider;
@@ -712,6 +715,7 @@
public void attach(NotificationStackScrollLayout view) {
mView = view;
+ mView.setLogger(mStackStateLogger);
mView.setController(this);
mView.setTouchHandler(new TouchHandler());
mView.setStatusBar(mStatusBar);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 1d0d374..0d2bddc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -86,6 +86,7 @@
private NotificationShelf mShelf;
private float mStatusBarIconLocation;
private int[] mTmpLocation = new int[2];
+ private StackStateLogger mLogger;
public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
mHostLayout = hostLayout;
@@ -113,6 +114,10 @@
};
}
+ protected void setLogger(StackStateLogger logger) {
+ mLogger = logger;
+ }
+
public boolean isRunning() {
return !mAnimatorSet.isEmpty();
}
@@ -337,6 +342,12 @@
ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents) {
for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
final ExpandableView changingView = (ExpandableView) event.mChangingView;
+ boolean loggable = false;
+ String key = null;
+ if (changingView instanceof ExpandableNotificationRow && mLogger != null) {
+ loggable = true;
+ key = ((ExpandableNotificationRow) changingView).getEntry().getKey();
+ }
if (event.animationType ==
NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
@@ -407,10 +418,22 @@
if (event.headsUpFromBottom) {
mTmpState.yTranslation = mHeadsUpAppearHeightBottom;
} else {
+ Runnable onAnimationEnd = null;
+ if (loggable) {
+ String finalKey = key;
+ onAnimationEnd = () -> mLogger.appearAnimationEnded(finalKey);
+ }
changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR,
- true /* isHeadsUpAppear */);
+ true /* isHeadsUpAppear */, onAnimationEnd);
}
mHeadsUpAppearChildren.add(changingView);
+ // this only captures HEADS_UP_APPEAR animations, but HUNs can appear with normal
+ // ADD animations, which would not be logged here.
+ if (loggable) {
+ mLogger.logHUNViewAppearing(
+ ((ExpandableNotificationRow) changingView).getEntry().getKey());
+ }
+
mTmpState.applyToView(changingView);
} else if (event.animationType == NotificationStackScrollLayout
.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR ||
@@ -453,10 +476,21 @@
// We need to add the global animation listener, since once no animations are
// running anymore, the panel will instantly hide itself. We need to wait until
// the animation is fully finished for this though.
+ Runnable postAnimation = endRunnable;
+ if (loggable) {
+ mLogger.logHUNViewDisappearing(key);
+
+ Runnable finalEndRunnable = endRunnable;
+ String finalKey1 = key;
+ postAnimation = () -> {
+ mLogger.disappearAnimationEnded(finalKey1);
+ if (finalEndRunnable != null) finalEndRunnable.run();
+ };
+ }
long removeAnimationDelay = changingView.performRemoveAnimation(
ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
0, 0.0f, true /* isHeadsUpAppear */, targetLocation,
- endRunnable, getGlobalAnimationFinishedListener());
+ postAnimation, getGlobalAnimationFinishedListener());
mAnimationProperties.delay += removeAnimationDelay;
} else if (endRunnable != null) {
endRunnable.run();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
new file mode 100644
index 0000000..4315265
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -0,0 +1,44 @@
+package com.android.systemui.statusbar.notification.stack
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import javax.inject.Inject
+
+class StackStateLogger @Inject constructor(
+ @NotificationHeadsUpLog private val buffer: LogBuffer
+) {
+ fun logHUNViewDisappearing(key: String) {
+ buffer.log(TAG, LogLevel.INFO, {
+ str1 = key
+ }, {
+ "Heads up view disappearing $str1 "
+ })
+ }
+
+ fun logHUNViewAppearing(key: String) {
+ buffer.log(TAG, LogLevel.INFO, {
+ str1 = key
+ }, {
+ "Heads up notification view appearing $str1 "
+ })
+ }
+
+ fun disappearAnimationEnded(key: String) {
+ buffer.log(TAG, LogLevel.INFO, {
+ str1 = key
+ }, {
+ "Heads up notification disappear animation ended $str1 "
+ })
+ }
+
+ fun appearAnimationEnded(key: String) {
+ buffer.log(TAG, LogLevel.INFO, {
+ str1 = key
+ }, {
+ "Heads up notification appear animation ended $str1 "
+ })
+ }
+}
+
+private const val TAG = "StackScroll"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index dee1b33..8d500fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -44,6 +44,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -254,6 +255,8 @@
);
}
+ private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+
@Inject
public BiometricUnlockController(Context context, DozeScrimController dozeScrimController,
KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
@@ -269,7 +272,8 @@
WakefulnessLifecycle wakefulnessLifecycle,
ScreenLifecycle screenLifecycle,
AuthController authController,
- StatusBarStateController statusBarStateController) {
+ StatusBarStateController statusBarStateController,
+ KeyguardUnlockAnimationController keyguardUnlockAnimationController) {
mContext = context;
mPowerManager = powerManager;
mShadeController = shadeController;
@@ -292,6 +296,7 @@
mMetricsLogger = metricsLogger;
mAuthController = authController;
mStatusBarStateController = statusBarStateController;
+ mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
dumpManager.registerDumpable(getClass().getName(), this);
}
@@ -438,11 +443,15 @@
if (!wasDeviceInteractive) {
mPendingShowBouncer = true;
} else {
- mShadeController.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_NONE,
- true /* force */,
- false /* delayed */,
- BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR);
+ // If the keyguard unlock controller is going to handle the unlock animation, it
+ // will fling the panel collapsed when it's ready.
+ if (!mKeyguardUnlockAnimationController.willHandleUnlockAnimation()) {
+ mShadeController.animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_NONE,
+ true /* force */,
+ false /* delayed */,
+ BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR);
+ }
mPendingShowBouncer = false;
mKeyguardViewController.notifyKeyguardAuthenticated(
false /* strongAuth */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 2824ab8..77cff34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -60,16 +60,14 @@
private final KeyguardBypassController mBypassController;
private final GroupMembershipManager mGroupMembershipManager;
private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
- private final int mAutoHeadsUpNotificationDecay;
// TODO (b/162832756): remove visual stability manager when migrating to new pipeline
private VisualStabilityManager mVisualStabilityManager;
private boolean mReleaseOnExpandFinish;
private boolean mTrackingHeadsUp;
- private HashSet<String> mSwipedOutKeys = new HashSet<>();
- private HashSet<NotificationEntry> mEntriesToRemoveAfterExpand = new HashSet<>();
- private HashSet<String> mKeysToRemoveWhenLeavingKeyguard = new HashSet<>();
- private ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
+ private final HashSet<String> mSwipedOutKeys = new HashSet<>();
+ private final HashSet<NotificationEntry> mEntriesToRemoveAfterExpand = new HashSet<>();
+ private final ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
= new ArraySet<>();
private boolean mIsExpanded;
private boolean mHeadsUpGoingAway;
@@ -110,8 +108,6 @@
super(context, logger);
Resources resources = mContext.getResources();
mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
- mAutoHeadsUpNotificationDecay = resources.getInteger(
- R.integer.auto_heads_up_notification_decay);
statusBarStateController.addCallback(mStatusBarStateListener);
mBypassController = bypassController;
mGroupMembershipManager = groupMembershipManager;
@@ -234,15 +230,6 @@
}
}
- @Override
- public boolean isEntryAutoHeadsUpped(String key) {
- HeadsUpEntryPhone headsUpEntryPhone = getHeadsUpEntryPhone(key);
- if (headsUpEntryPhone == null) {
- return false;
- }
- return headsUpEntryPhone.isAutoHeadsUp();
- }
-
/**
* Set that we are exiting the headsUp pinned mode, but some notifications might still be
* animating out. This is used to keep the touchable regions in a reasonable state.
@@ -375,7 +362,6 @@
@Override
protected void onAlertEntryRemoved(AlertEntry alertEntry) {
- mKeysToRemoveWhenLeavingKeyguard.remove(alertEntry.mEntry.getKey());
super.onAlertEntryRemoved(alertEntry);
mEntryPool.release((HeadsUpEntryPhone) alertEntry);
}
@@ -437,11 +423,6 @@
*/
private boolean extended;
- /**
- * Was this entry received while on keyguard
- */
- private boolean mIsAutoHeadsUp;
-
@Override
public boolean isSticky() {
@@ -459,8 +440,6 @@
false /* persistent */);
} else if (mTrackingHeadsUp) {
mEntriesToRemoveAfterExpand.add(entry);
- } else if (mIsAutoHeadsUp && mStatusBarState == StatusBarState.KEYGUARD) {
- mKeysToRemoveWhenLeavingKeyguard.add(entry.getKey());
} else {
removeAlertEntry(entry.getKey());
}
@@ -471,7 +450,6 @@
@Override
public void updateEntry(boolean updatePostTime) {
- mIsAutoHeadsUp = mEntry.isAutoHeadsUp();
super.updateEntry(updatePostTime);
if (mEntriesToRemoveAfterExpand.contains(mEntry)) {
@@ -480,7 +458,6 @@
if (mEntriesToRemoveWhenReorderingAllowed.contains(mEntry)) {
mEntriesToRemoveWhenReorderingAllowed.remove(mEntry);
}
- mKeysToRemoveWhenLeavingKeyguard.remove(mEntry.getKey());
}
@Override
@@ -515,7 +492,6 @@
super.reset();
mMenuShownPinned = false;
extended = false;
- mIsAutoHeadsUp = false;
}
private void extendPulse() {
@@ -526,33 +502,8 @@
}
@Override
- public int compareTo(AlertEntry alertEntry) {
- HeadsUpEntryPhone headsUpEntry = (HeadsUpEntryPhone) alertEntry;
- boolean autoShown = isAutoHeadsUp();
- boolean otherAutoShown = headsUpEntry.isAutoHeadsUp();
- if (autoShown && !otherAutoShown) {
- return 1;
- } else if (!autoShown && otherAutoShown) {
- return -1;
- }
- return super.compareTo(alertEntry);
- }
-
- @Override
protected long calculateFinishTime() {
- return mPostTime + getDecayDuration() + (extended ? mExtensionTime : 0);
- }
-
- private int getDecayDuration() {
- if (isAutoHeadsUp()) {
- return getRecommendedHeadsUpTimeoutMs(mAutoHeadsUpNotificationDecay);
- } else {
- return getRecommendedHeadsUpTimeoutMs(mAutoDismissNotificationDecay);
- }
- }
-
- private boolean isAutoHeadsUp() {
- return mIsAutoHeadsUp;
+ return super.calculateFinishTime() + (extended ? mExtensionTime : 0);
}
}
@@ -577,13 +528,6 @@
boolean wasKeyguard = mStatusBarState == StatusBarState.KEYGUARD;
boolean isKeyguard = newState == StatusBarState.KEYGUARD;
mStatusBarState = newState;
- if (wasKeyguard && !isKeyguard && mKeysToRemoveWhenLeavingKeyguard.size() != 0) {
- String[] keys = mKeysToRemoveWhenLeavingKeyguard.toArray(new String[0]);
- for (String key : keys) {
- removeAlertEntry(key);
- }
- mKeysToRemoveWhenLeavingKeyguard.clear();
- }
if (wasKeyguard && !isKeyguard && mBypassController.getBypassEnabled()) {
ArrayList<String> keysToRemove = new ArrayList<>();
for (AlertEntry entry : mAlertEntries.values()) {
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 016b953..769f689 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -138,6 +138,7 @@
import com.android.systemui.idle.IdleHostView;
import com.android.systemui.idle.IdleHostViewController;
import com.android.systemui.idle.dagger.IdleViewComponent;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
@@ -791,7 +792,8 @@
Optional<SysUIUnfoldComponent> unfoldComponent,
ControlsComponent controlsComponent,
InteractionJankMonitor interactionJankMonitor,
- QsFrameTranslateController qsFrameTranslateController) {
+ QsFrameTranslateController qsFrameTranslateController,
+ KeyguardUnlockAnimationController keyguardUnlockAnimationController) {
super(view,
featureFlags,
falsingManager,
@@ -806,7 +808,8 @@
lockscreenGestureLogger,
panelExpansionStateManager,
ambientState,
- interactionJankMonitor);
+ interactionJankMonitor,
+ keyguardUnlockAnimationController);
mView = view;
mVibratorHelper = vibratorHelper;
mKeyguardMediaController = keyguardMediaController;
@@ -925,8 +928,33 @@
mQsFrameTranslateController = qsFrameTranslateController;
updateUserSwitcherFlags();
onFinishInflate();
-
mUseCombinedQSHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS);
+ keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
+ new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
+ @Override
+ public void onUnlockAnimationFinished() {
+ // Make sure the clock is in the correct position after the unlock animation
+ // so that it's not in the wrong place when we show the keyguard again.
+ positionClockAndNotifications(true /* forceClockUpdate */);
+ }
+
+ @Override
+ public void onUnlockAnimationStarted(
+ boolean playingCannedAnimation, boolean isWakeAndUnlock) {
+ // Disable blurs while we're unlocking so that panel expansion does not
+ // cause blurring. This will eventually be re-enabled by the panel view on
+ // ACTION_UP, since the user's finger might still be down after a swipe to
+ // unlock gesture, and we don't want that to cause blurring either.
+ mDepthController.setBlursDisabledForUnlock(mTracking);
+
+ if (playingCannedAnimation && !isWakeAndUnlock) {
+ // Fling the panel away so it's not in the way or the surface behind the
+ // keyguard, which will be appearing. If we're wake and unlocking, the
+ // lock screen is hidden instantly so should not be flung away.
+ fling(0f, false, 0.7f, false);
+ }
+ }
+ });
}
private void onFinishInflate() {
@@ -3324,6 +3352,10 @@
mAffordanceHelper.reset(true);
}
}
+
+ // If we unlocked from a swipe, the user's finger might still be down after the
+ // unlock animation ends. We need to wait until ACTION_UP to enable blurs again.
+ mDepthController.setBlursDisabledForUnlock(false);
}
private void updateMaxHeadsUpTranslation() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 53bfd77..05ac2a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -55,6 +55,7 @@
import com.android.systemui.doze.DozeLog;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -212,6 +213,8 @@
return mAmbientState;
}
+ private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+
public PanelViewController(
PanelView view,
FeatureFlags featureFlags,
@@ -227,7 +230,15 @@
LockscreenGestureLogger lockscreenGestureLogger,
PanelExpansionStateManager panelExpansionStateManager,
AmbientState ambientState,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ KeyguardUnlockAnimationController keyguardUnlockAnimationController) {
+ mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
+ keyguardStateController.addCallback(new KeyguardStateController.Callback() {
+ @Override
+ public void onKeyguardFadingAwayChanged() {
+ requestPanelHeightUpdate();
+ }
+ });
mAmbientState = ambientState;
mView = view;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
@@ -437,7 +448,8 @@
mUpdateFlingVelocity = vel;
}
} else if (!mStatusBar.isBouncerShowing()
- && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+ && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()
+ && !mKeyguardStateController.isKeyguardGoingAway()) {
boolean expands = onEmptySpaceClick(mInitialTouchX);
onTrackingStopped(expands);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 5d83cc6..48048b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -809,6 +809,12 @@
: ScrimState.SHADE_LOCKED.getBehindTint();
behindTint = ColorUtils.blendARGB(behindTint, stateTint, mQsExpansion);
}
+
+ // If the keyguard is going away, we should not be opaque.
+ if (mKeyguardStateController.isKeyguardGoingAway()) {
+ behindAlpha = 0f;
+ }
+
return new Pair<>(behindTint, behindAlpha);
}
@@ -1333,9 +1339,6 @@
public void setExpansionAffectsAlpha(boolean expansionAffectsAlpha) {
mExpansionAffectsAlpha = expansionAffectsAlpha;
- if (expansionAffectsAlpha) {
- applyAndDispatchState();
- }
}
public void setKeyguardOccluded(boolean keyguardOccluded) {
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 07ae33c..455ffdc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -211,7 +211,6 @@
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.init.NotificationsController;
-import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -482,7 +481,6 @@
private final HeadsUpManagerPhone mHeadsUpManager;
private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
private final DynamicPrivacyController mDynamicPrivacyController;
- private final BypassHeadsUpNotifier mBypassHeadsUpNotifier;
private final FalsingCollector mFalsingCollector;
private final FalsingManager mFalsingManager;
private final BroadcastDispatcher mBroadcastDispatcher;
@@ -619,6 +617,8 @@
protected boolean mDozing;
private boolean mIsFullscreen;
+ boolean mCloseQsBeforeScreenOff;
+
private final NotificationMediaManager mMediaManager;
private final NotificationLockscreenUserManager mLockscreenUserManager;
private final NotificationRemoteInputManager mRemoteInputManager;
@@ -701,7 +701,6 @@
KeyguardStateController keyguardStateController,
HeadsUpManagerPhone headsUpManagerPhone,
DynamicPrivacyController dynamicPrivacyController,
- BypassHeadsUpNotifier bypassHeadsUpNotifier,
FalsingManager falsingManager,
FalsingCollector falsingCollector,
BroadcastDispatcher broadcastDispatcher,
@@ -798,7 +797,6 @@
mKeyguardIndicationController = keyguardIndicationController;
mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
mDynamicPrivacyController = dynamicPrivacyController;
- mBypassHeadsUpNotifier = bypassHeadsUpNotifier;
mFalsingCollector = falsingCollector;
mFalsingManager = falsingManager;
mBroadcastDispatcher = broadcastDispatcher;
@@ -915,7 +913,6 @@
mScreenLifecycle.addObserver(mScreenObserver);
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
mUiModeManager = mContext.getSystemService(UiModeManager.class);
- mBypassHeadsUpNotifier.setUp();
if (mBubblesOptional.isPresent()) {
mBubblesOptional.get().setExpandListener(mBubbleExpandListener);
}
@@ -1124,6 +1121,15 @@
}
if (leaveOpen) {
mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
+ if (mIsKeyguard) {
+ // When device state changes on keyguard we don't want to keep the state of
+ // the shade and instead we open clean state of keyguard with shade closed.
+ // Normally some parts of QS state (like expanded/collapsed) are persisted and
+ // that causes incorrect UI rendering, especially when changing state with QS
+ // expanded. To prevent that we can close QS which resets QS and some parts of
+ // the shade to its default state. Read more in b/201537421
+ mCloseQsBeforeScreenOff = true;
+ }
}
}
@@ -2923,10 +2929,10 @@
}
boolean updateIsKeyguard() {
- return updateIsKeyguard(false /* force */);
+ return updateIsKeyguard(false /* forceStateChange */);
}
- boolean updateIsKeyguard(boolean force) {
+ boolean updateIsKeyguard(boolean forceStateChange) {
boolean wakeAndUnlocking = mBiometricUnlockController.getMode()
== BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
@@ -2959,7 +2965,7 @@
// as the animation could prepare 'fake AOD' interface (without actually
// transitioning to keyguard state) and this might reset the view states
if (!mScreenOffAnimationController.isKeyguardHideDelayed()) {
- return hideKeyguardImpl(force);
+ return hideKeyguardImpl(forceStateChange);
}
}
return false;
@@ -2967,6 +2973,8 @@
public void showKeyguardImpl() {
mIsKeyguard = true;
+ // In case we're locking while a smartspace transition is in progress, reset it.
+ mKeyguardUnlockAnimationController.resetSmartspaceTransition();
if (mKeyguardStateController.isLaunchTransitionFadingAway()) {
mNotificationPanelViewController.cancelAnimation();
onLaunchTransitionFadingEnded();
@@ -3087,12 +3095,12 @@
/**
* @return true if we would like to stay in the shade, false if it should go away entirely
*/
- public boolean hideKeyguardImpl(boolean force) {
+ public boolean hideKeyguardImpl(boolean forceStateChange) {
mIsKeyguard = false;
Trace.beginSection("StatusBar#hideKeyguard");
boolean staying = mStatusBarStateController.leaveOpenOnKeyguardHide();
int previousState = mStatusBarStateController.getState();
- if (!(mStatusBarStateController.setState(StatusBarState.SHADE, force))) {
+ if (!(mStatusBarStateController.setState(StatusBarState.SHADE, forceStateChange))) {
//TODO: StatusBarStateController should probably know about hiding the keyguard and
// notify listeners.
@@ -3145,6 +3153,7 @@
// bar.
mKeyguardStateController.notifyKeyguardGoingAway(true);
mCommandQueue.appTransitionPending(mDisplayId, true /* forced */);
+ updateScrimController();
}
/**
@@ -3316,7 +3325,10 @@
}
private void showBouncerOrLockScreenIfKeyguard() {
- if (!mKeyguardViewMediator.isHiding()) {
+ // If the keyguard is animating away, we aren't really the keyguard anymore and should not
+ // show the bouncer/lockscreen.
+ if (!mKeyguardViewMediator.isHiding()
+ && !mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
if (mState == StatusBarState.SHADE_LOCKED
&& mKeyguardUpdateMonitor.isUdfpsEnrolled()) {
// shade is showing while locked on the keyguard, so go back to showing the
@@ -3564,7 +3576,6 @@
maybeEscalateHeadsUp();
dismissVolumeDialog();
mWakeUpCoordinator.setFullyAwake(false);
- mBypassHeadsUpNotifier.setFullyAwake(false);
mKeyguardBypassController.onStartedGoingToSleep();
// The unlocked screen off and fold to aod animations might use our LightRevealScrim -
@@ -3606,7 +3617,6 @@
@Override
public void onFinishedWakingUp() {
mWakeUpCoordinator.setFullyAwake(true);
- mBypassHeadsUpNotifier.setFullyAwake(true);
mWakeUpCoordinator.setWakingUp(false);
if (mLaunchCameraWhenFinishedWaking) {
mNotificationPanelViewController.launchCamera(
@@ -3654,6 +3664,10 @@
public void onScreenTurnedOff() {
mFalsingCollector.onScreenOff();
mScrimController.onScreenTurnedOff();
+ if (mCloseQsBeforeScreenOff) {
+ mNotificationPanelViewController.closeQs();
+ mCloseQsBeforeScreenOff = false;
+ }
updateIsKeyguard();
}
};
@@ -3746,17 +3760,14 @@
public void updateScrimController() {
Trace.beginSection("StatusBar#updateScrimController");
- // We don't want to end up in KEYGUARD state when we're unlocking with
- // fingerprint from doze. We should cross fade directly from black.
- boolean unlocking = mBiometricUnlockController.isWakeAndUnlock()
- || mKeyguardStateController.isKeyguardFadingAway();
+ boolean unlocking = mKeyguardStateController.isShowing() && (
+ mBiometricUnlockController.isWakeAndUnlock()
+ || mKeyguardStateController.isKeyguardFadingAway()
+ || mKeyguardStateController.isKeyguardGoingAway()
+ || mKeyguardViewMediator.requestedShowSurfaceBehindKeyguard()
+ || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind());
- // Do not animate the scrim expansion when triggered by the fingerprint sensor.
- boolean onKeyguardOrHidingIt = mKeyguardStateController.isShowing()
- || mKeyguardStateController.isKeyguardFadingAway()
- || mKeyguardStateController.isKeyguardGoingAway();
- mScrimController.setExpansionAffectsAlpha(!(mBiometricUnlockController.isBiometricUnlock()
- && onKeyguardOrHidingIt));
+ mScrimController.setExpansionAffectsAlpha(!unlocking);
boolean launchingAffordanceWithPreview =
mNotificationPanelViewController.isLaunchingAffordanceWithPreview();
@@ -3768,7 +3779,7 @@
} else {
mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED);
}
- } else if (mBouncerShowing) {
+ } else if (mBouncerShowing && !unlocking) {
// Bouncer needs the front scrim when it's on top of an activity,
// tapping on a notification, editing QS or being dismissed by
// FLAG_DISMISS_KEYGUARD_ACTIVITY.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 6746b3e..cc65ca02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -6,6 +6,7 @@
import android.content.Context
import android.database.ContentObserver
import android.os.Handler
+import android.os.PowerManager
import android.provider.Settings
import android.view.Surface
import android.view.View
@@ -54,9 +55,10 @@
private val keyguardStateController: KeyguardStateController,
private val dozeParameters: dagger.Lazy<DozeParameters>,
private val globalSettings: GlobalSettings,
- private val interactionJankMonitor: InteractionJankMonitor
+ private val interactionJankMonitor: InteractionJankMonitor,
+ private val powerManager: PowerManager,
+ private val handler: Handler = Handler()
) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
- private val handler = Handler()
private lateinit var statusBar: StatusBar
private lateinit var lightRevealScrim: LightRevealScrim
@@ -219,7 +221,7 @@
// even if we're going from SHADE to SHADE or KEYGUARD to KEYGUARD, since we might have
// changed parts of the UI (such as showing AOD in the shade) without actually changing
// the StatusBarState. This ensures that the UI definitely reflects the desired state.
- statusBar.updateIsKeyguard(true /* force */)
+ statusBar.updateIsKeyguard(true /* forceStateChange */)
}
}
@@ -231,10 +233,18 @@
lightRevealAnimationPlaying = true
lightRevealAnimator.start()
handler.postDelayed({
- aodUiAnimationPlaying = true
+ // Only run this callback if the device is sleeping (not interactive). This callback
+ // is removed in onStartedWakingUp, but since that event is asynchronously
+ // dispatched, a race condition could make it possible for this callback to be run
+ // as the device is waking up. That results in the AOD UI being shown while we wake
+ // up, with unpredictable consequences.
+ if (!powerManager.isInteractive) {
+ aodUiAnimationPlaying = true
- // Show AOD. That'll cause the KeyguardVisibilityHelper to call #animateInKeyguard.
- statusBar.notificationPanelViewController.showAodUi()
+ // Show AOD. That'll cause the KeyguardVisibilityHelper to call
+ // #animateInKeyguard.
+ statusBar.notificationPanelViewController.showAodUi()
+ }
}, (ANIMATE_IN_KEYGUARD_DELAY * animatorDurationScale).toLong())
return true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 977fe9c..f5364b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -71,7 +71,6 @@
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.init.NotificationsController;
-import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -152,7 +151,6 @@
KeyguardStateController keyguardStateController,
HeadsUpManagerPhone headsUpManagerPhone,
DynamicPrivacyController dynamicPrivacyController,
- BypassHeadsUpNotifier bypassHeadsUpNotifier,
FalsingManager falsingManager,
FalsingCollector falsingCollector,
BroadcastDispatcher broadcastDispatcher,
@@ -250,7 +248,6 @@
keyguardStateController,
headsUpManagerPhone,
dynamicPrivacyController,
- bypassHeadsUpNotifier,
falsingManager,
falsingCollector,
broadcastDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 9587261..3084a95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -184,6 +184,7 @@
entry.setHeadsUp(false);
setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */);
EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
+ mLogger.logNotificationActuallyRemoved(entry.getKey());
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpStateChanged(entry, false);
}
@@ -378,10 +379,6 @@
public void onDensityOrFontScaleChanged() {
}
- public boolean isEntryAutoHeadsUpped(String key) {
- return false;
- }
-
/**
* Determines if the notification is for a critical call that must display on top of an active
* input notification.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index 2bdf62bb..6a74ba9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -73,6 +73,14 @@
})
}
+ fun logNotificationActuallyRemoved(key: String) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ }, {
+ "notification removed $str1 "
+ })
+ }
+
fun logUpdateNotification(key: String, alert: Boolean, hasEntry: Boolean) {
buffer.log(TAG, INFO, {
str1 = key
@@ -83,11 +91,12 @@
})
}
- fun logUpdateEntry(updatePostTime: Boolean) {
+ fun logUpdateEntry(key: String, updatePostTime: Boolean) {
buffer.log(TAG, INFO, {
+ str1 = key
bool1 = updatePostTime
}, {
- "update entry updatePostTime: $bool1"
+ "update entry $key updatePostTime: $bool1"
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index 7bf1601..050b670 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -50,13 +50,6 @@
boolean canDismissLockScreen();
/**
- * Whether we can currently perform the shared element SmartSpace transition. This is true if
- * we're on the lockscreen, it can be dismissed with a swipe, and the Launcher is underneath the
- * keyguard and displaying a SmartSpace that it has registered with System UI.
- */
- boolean canPerformSmartSpaceTransition();
-
- /**
* Whether the keyguard is allowed to rotate, or needs to be locked to the default orientation.
*/
boolean isKeyguardScreenRotationAllowed();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 05a586b..978564f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -35,7 +35,7 @@
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -44,6 +44,8 @@
import javax.inject.Inject;
+import dagger.Lazy;
+
/**
*/
@SysUISingleton
@@ -58,7 +60,7 @@
private final LockPatternUtils mLockPatternUtils;
private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
new UpdateMonitorCallback();
- private final SmartspaceTransitionController mSmartspaceTransitionController;
+ private final Lazy<KeyguardUnlockAnimationController> mUnlockAnimationControllerLazy;
private boolean mCanDismissLockScreen;
private boolean mShowing;
@@ -105,13 +107,13 @@
Context context,
KeyguardUpdateMonitor keyguardUpdateMonitor,
LockPatternUtils lockPatternUtils,
- SmartspaceTransitionController smartspaceTransitionController,
+ Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController,
DumpManager dumpManager) {
mContext = context;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
- mSmartspaceTransitionController = smartspaceTransitionController;
+ mUnlockAnimationControllerLazy = keyguardUnlockAnimationController;
dumpManager.registerDumpable(getClass().getSimpleName(), this);
@@ -249,12 +251,6 @@
}
@Override
- public boolean canPerformSmartSpaceTransition() {
- return canDismissLockScreen()
- && mSmartspaceTransitionController.isSmartspaceTransitionPossible();
- }
-
- @Override
public boolean isKeyguardScreenRotationAllowed() {
return SystemProperties.getBoolean("lockscreen.rot_override", false)
|| mContext.getResources().getBoolean(R.bool.config_enableLockScreenRotation);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 74e0f40..c2439df 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -48,7 +48,6 @@
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -96,9 +95,8 @@
@Mock
Resources mResources;
- KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock
- SmartspaceTransitionController mSmartSpaceTransitionController;
+ KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock
private ClockPlugin mClockPlugin;
@Mock
@@ -154,7 +152,6 @@
mBypassController,
mSmartspaceController,
mKeyguardUnlockAnimationController,
- mSmartSpaceTransitionController,
mSecureSettings,
mExecutor,
mResources
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 80de248..217092e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -24,7 +24,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.communal.CommunalStateController;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -61,8 +60,6 @@
@Mock
KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock
- SmartspaceTransitionController mSmartSpaceTransitionController;
- @Mock
ScreenOffAnimationController mScreenOffAnimationController;
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
@@ -83,7 +80,6 @@
mConfigurationController,
mDozeParameters,
mKeyguardUnlockAnimationController,
- mSmartSpaceTransitionController,
mScreenOffAnimationController);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 7c5f57f..7af039b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -27,15 +27,15 @@
import android.content.res.Resources;
import android.os.Handler;
import android.testing.AndroidTestingRunner;
-import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
-import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.complication.ComplicationHostViewController;
+import com.android.systemui.dreams.complication.dagger.ComplicationHostViewComponent;
import org.junit.Before;
import org.junit.Test;
@@ -64,6 +64,15 @@
DreamOverlayContainerView mDreamOverlayContainerView;
@Mock
+ ComplicationHostViewController mComplicationHostViewController;
+
+ @Mock
+ ComplicationHostViewComponent.Factory mComplicationHostViewComponentFactory;
+
+ @Mock
+ ComplicationHostViewComponent mComplicationHostViewComponent;
+
+ @Mock
ViewGroup mDreamOverlayContentView;
@Mock
@@ -80,9 +89,14 @@
DREAM_OVERLAY_NOTIFICATIONS_DRAG_AREA_HEIGHT);
when(mDreamOverlayContainerView.getResources()).thenReturn(mResources);
when(mDreamOverlayContainerView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
+ when(mComplicationHostViewComponentFactory.create())
+ .thenReturn(mComplicationHostViewComponent);
+ when(mComplicationHostViewComponent.getController())
+ .thenReturn(mComplicationHostViewController);
mController = new DreamOverlayContainerViewController(
mDreamOverlayContainerView,
+ mComplicationHostViewComponentFactory,
mDreamOverlayContentView,
mDreamOverlayStatusBarViewController,
mHandler,
@@ -104,20 +118,6 @@
}
@Test
- public void testAddOverlayAddsOverlayToContentView() {
- View overlay = new View(getContext());
- ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(100, 100);
- mController.addOverlay(overlay, layoutParams);
- verify(mDreamOverlayContentView).addView(overlay, layoutParams);
- }
-
- @Test
- public void testRemoveAllOverlaysRemovesOverlaysFromContentView() {
- mController.removeAllOverlays();
- verify(mDreamOverlayContentView).removeAllViews();
- }
-
- @Test
public void testOnViewAttachedRegistersComputeInsetsListener() {
mController.onViewAttached();
verify(mViewTreeObserver).addOnComputeInternalInsetsListener(any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index c0b7271..6b156a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -28,12 +28,11 @@
import android.service.dreams.IDreamOverlay;
import android.service.dreams.IDreamOverlayCallback;
import android.testing.AndroidTestingRunner;
-import android.view.View;
-import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
-import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -48,18 +47,21 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Arrays;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class DreamOverlayServiceTest extends SysuiTestCase {
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
+ @Mock
+ LifecycleOwner mLifecycleOwner;
+
+ @Mock
+ LifecycleRegistry mLifecycleRegistry;
+
@Rule
public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
@@ -76,12 +78,6 @@
WindowManagerImpl mWindowManager;
@Mock
- ComplicationProvider mProvider;
-
- @Mock
- DreamOverlayStateController mDreamOverlayStateController;
-
- @Mock
DreamOverlayComponent.Factory mDreamOverlayComponentFactory;
@Mock
@@ -102,13 +98,18 @@
when(mDreamOverlayComponent.getDreamOverlayContainerViewController())
.thenReturn(mDreamOverlayContainerViewController);
- when(mDreamOverlayComponentFactory.create())
+ when(mDreamOverlayComponent.getLifecycleOwner())
+ .thenReturn(mLifecycleOwner);
+ when(mDreamOverlayComponent.getLifecycleRegistry())
+ .thenReturn(mLifecycleRegistry);
+ when(mDreamOverlayComponentFactory
+ .create(any(), any()))
.thenReturn(mDreamOverlayComponent);
when(mDreamOverlayContainerViewController.getContainerView())
.thenReturn(mDreamOverlayContainerView);
mService = new DreamOverlayService(mContext, mMainExecutor,
- mDreamOverlayStateController, mDreamOverlayComponentFactory);
+ mDreamOverlayComponentFactory);
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
@@ -128,78 +129,6 @@
}
@Test
- public void testAddingOverlayToDream() throws Exception {
- // Add overlay.
- mService.addComplication(mProvider);
- mMainExecutor.runAllReady();
-
- final ArgumentCaptor<ComplicationHost.CreationCallback> creationCallbackCapture =
- ArgumentCaptor.forClass(ComplicationHost.CreationCallback.class);
- final ArgumentCaptor<ComplicationHost.InteractionCallback> interactionCallbackCapture =
- ArgumentCaptor.forClass(ComplicationHost.InteractionCallback.class);
-
- // Ensure overlay provider is asked to create view.
- verify(mProvider).onCreateComplication(any(), creationCallbackCapture.capture(),
- interactionCallbackCapture.capture());
- mMainExecutor.runAllReady();
-
- // Inform service of overlay view creation.
- final View view = new View(mContext);
- final ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
- );
- creationCallbackCapture.getValue().onCreated(view, lp);
- mMainExecutor.runAllReady();
-
- // Verify that DreamOverlayContainerViewController is asked to add an overlay for the view.
- verify(mDreamOverlayContainerViewController).addOverlay(view, lp);
- }
-
- @Test
- public void testDreamOverlayExit() throws Exception {
- // Add overlay.
- mService.addComplication(mProvider);
- mMainExecutor.runAllReady();
-
- // Capture interaction callback from overlay creation.
- final ArgumentCaptor<ComplicationHost.InteractionCallback> interactionCallbackCapture =
- ArgumentCaptor.forClass(ComplicationHost.InteractionCallback.class);
- verify(mProvider).onCreateComplication(any(), any(), interactionCallbackCapture.capture());
-
- // Ask service to exit.
- interactionCallbackCapture.getValue().onExit();
- mMainExecutor.runAllReady();
-
- // Ensure service informs dream host of exit.
- verify(mDreamOverlayCallback).onExitRequested();
- }
-
- @Test
- public void testListenerRegisteredWithDreamOverlayStateController() {
- // Verify overlay service registered as listener with DreamOverlayStateController
- // and inform callback of addition.
- final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
- ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
-
- verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
- when(mDreamOverlayStateController.getComplications()).thenReturn(Arrays.asList(mProvider));
- callbackCapture.getValue().onComplicationsChanged();
- mMainExecutor.runAllReady();
-
- // Verify provider is asked to create overlay.
- verify(mProvider).onCreateComplication(any(), any(), any());
- }
-
- @Test
- public void testOnDestroyRemovesOverlayStateCallback() {
- final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
- ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
- verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
- mService.onDestroy();
- verify(mDreamOverlayStateController).removeCallback(callbackCapture.getValue());
- }
-
- @Test
public void testShouldShowComplicationsTrueByDefault() {
assertThat(mService.shouldShowComplications()).isTrue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index efc3c7c..7d0833d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -27,11 +27,10 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
-import com.google.common.util.concurrent.ListenableFuture;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -47,7 +46,7 @@
DreamOverlayStateController.Callback mCallback;
@Mock
- ComplicationProvider mProvider;
+ Complication mComplication;
final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@@ -63,24 +62,23 @@
stateController.addCallback(mCallback);
// Add complication and verify callback is notified.
- final ListenableFuture<DreamOverlayStateController.ComplicationToken> tokenFuture =
- stateController.addComplication(mProvider);
+ stateController.addComplication(mComplication);
mExecutor.runAllReady();
verify(mCallback, times(1)).onComplicationsChanged();
- final Collection<ComplicationProvider> providers = stateController.getComplications();
- assertEquals(providers.size(), 1);
- assertTrue(providers.contains(mProvider));
+ final Collection<Complication> complications = stateController.getComplications();
+ assertEquals(complications.size(), 1);
+ assertTrue(complications.contains(mComplication));
clearInvocations(mCallback);
// Remove complication and verify callback is notified.
- stateController.removeComplication(tokenFuture.get());
+ stateController.removeComplication(mComplication);
mExecutor.runAllReady();
verify(mCallback, times(1)).onComplicationsChanged();
- assertTrue(providers.isEmpty());
+ assertTrue(stateController.getComplications().isEmpty());
}
@Test
@@ -88,7 +86,7 @@
final DreamOverlayStateController stateController =
new DreamOverlayStateController(mExecutor);
- stateController.addComplication(mProvider);
+ stateController.addComplication(mComplication);
mExecutor.runAllReady();
// Verify callback occurs on add when an overlay is already present.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
new file mode 100644
index 0000000..afc0309d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.dreams.complication;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.lifecycle.Observer;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ComplicationCollectionLiveDataTest extends SysuiTestCase {
+ @Before
+ public void setUp() throws Exception {
+ allowTestableLooperAsMainThread();
+ }
+
+ @Test
+ /**
+ * Ensures registration and callback lifecycles are respected.
+ */
+ public void testLifecycle() {
+ getContext().getMainExecutor().execute(() -> {
+ final DreamOverlayStateController stateController =
+ Mockito.mock(DreamOverlayStateController.class);
+ final ComplicationCollectionLiveData liveData =
+ new ComplicationCollectionLiveData(stateController);
+ final HashSet<Complication> complications = new HashSet<>();
+ final Observer<Collection<Complication>> observer = Mockito.mock(Observer.class);
+ complications.add(Mockito.mock(Complication.class));
+
+ when(stateController.getComplications()).thenReturn(complications);
+
+ liveData.observeForever(observer);
+ ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+
+ verify(stateController).addCallback(callbackCaptor.capture());
+ verifyUpdate(observer, complications);
+
+ complications.add(Mockito.mock(Complication.class));
+ callbackCaptor.getValue().onComplicationsChanged();
+
+ verifyUpdate(observer, complications);
+ });
+ }
+
+ void verifyUpdate(Observer<Collection<Complication>> observer,
+ Collection<Complication> targetCollection) {
+ ArgumentCaptor<Collection<Complication>> collectionCaptor =
+ ArgumentCaptor.forClass(Collection.class);
+
+ verify(observer).onChanged(collectionCaptor.capture());
+
+ assertThat(collectionCaptor.getValue().equals(targetCollection)).isTrue();
+ Mockito.clearInvocations(observer);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
new file mode 100644
index 0000000..3b9e398
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.complication;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ComplicationHostViewControllerTest extends SysuiTestCase {
+ @Mock
+ ConstraintLayout mComplicationHostView;
+
+ @Mock
+ LifecycleOwner mLifecycleOwner;
+
+ @Mock
+ LiveData<Collection<ComplicationViewModel>> mComplicationViewModelLiveData;
+
+ @Mock
+ ComplicationCollectionViewModel mViewModel;
+
+ @Mock
+ ComplicationViewModel mComplicationViewModel;
+
+ @Mock
+ ComplicationLayoutEngine mLayoutEngine;
+
+ @Mock
+ ComplicationId mComplicationId;
+
+ @Mock
+ Complication mComplication;
+
+ @Mock
+ Complication.ViewHolder mViewHolder;
+
+ @Mock
+ View mComplicationView;
+
+ @Mock
+ ComplicationLayoutParams mComplicationLayoutParams;
+
+ @Complication.Category
+ static final int COMPLICATION_CATEGORY = Complication.CATEGORY_SYSTEM;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mViewModel.getComplications()).thenReturn(mComplicationViewModelLiveData);
+ when(mComplicationViewModel.getId()).thenReturn(mComplicationId);
+ when(mComplicationViewModel.getComplication()).thenReturn(mComplication);
+ when(mComplication.createView(eq(mComplicationViewModel))).thenReturn(mViewHolder);
+ when(mViewHolder.getView()).thenReturn(mComplicationView);
+ when(mViewHolder.getCategory()).thenReturn(COMPLICATION_CATEGORY);
+ when(mViewHolder.getLayoutParams()).thenReturn(mComplicationLayoutParams);
+ when(mComplicationView.getParent()).thenReturn(mComplicationHostView);
+ }
+
+ /**
+ * Ensures the lifecycle of complications is properly handled.
+ */
+ @Test
+ public void testViewModelObservation() {
+ final ArgumentCaptor<Observer<Collection<ComplicationViewModel>>> observerArgumentCaptor =
+ ArgumentCaptor.forClass(Observer.class);
+ final ComplicationHostViewController controller = new ComplicationHostViewController(
+ mComplicationHostView,
+ mLayoutEngine,
+ mLifecycleOwner,
+ mViewModel);
+
+ controller.init();
+
+ verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner),
+ observerArgumentCaptor.capture());
+
+ final Observer<Collection<ComplicationViewModel>> observer =
+ observerArgumentCaptor.getValue();
+
+ // Add complication and ensure it is added to the view.
+ final HashSet<ComplicationViewModel> complications = new HashSet<>(
+ Arrays.asList(mComplicationViewModel));
+ observer.onChanged(complications);
+
+ verify(mLayoutEngine).addComplication(eq(mComplicationId), eq(mComplicationView),
+ eq(mComplicationLayoutParams), eq(COMPLICATION_CATEGORY));
+
+ // Remove complication and ensure it is removed from the view by id.
+ observer.onChanged(new HashSet<>());
+
+ verify(mLayoutEngine).removeComplication(eq(mComplicationId));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
new file mode 100644
index 0000000..f227a9b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
@@ -0,0 +1,303 @@
+/*
+ * 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.dreams.complication;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.Consumer;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ComplicationLayoutEngineTest extends SysuiTestCase {
+ @Mock
+ ConstraintLayout mLayout;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ private static class ViewInfo {
+ private static int sNextId = 1;
+ public final ComplicationId id;
+ public final View view;
+ public final ComplicationLayoutParams lp;
+
+ @Complication.Category
+ public final int category;
+
+ private static ComplicationId.Factory sFactory = new ComplicationId.Factory();
+
+ ViewInfo(ComplicationLayoutParams params, @Complication.Category int category,
+ ConstraintLayout layout) {
+ this.lp = params;
+ this.category = category;
+ this.view = Mockito.mock(View.class);
+ this.id = sFactory.getNextId();
+ when(view.getId()).thenReturn(sNextId++);
+ when(view.getParent()).thenReturn(layout);
+ }
+
+ void clearInvocations() {
+ Mockito.clearInvocations(view);
+ }
+ }
+
+ private void verifyChange(ViewInfo viewInfo,
+ boolean verifyAdd,
+ Consumer<ConstraintLayout.LayoutParams> paramConsumer) {
+ ArgumentCaptor<ConstraintLayout.LayoutParams> lpCaptor =
+ ArgumentCaptor.forClass(ConstraintLayout.LayoutParams.class);
+ verify(viewInfo.view).setLayoutParams(lpCaptor.capture());
+
+ if (verifyAdd) {
+ verify(mLayout).addView(eq(viewInfo.view));
+ }
+
+ ConstraintLayout.LayoutParams capturedParams = lpCaptor.getValue();
+ paramConsumer.accept(capturedParams);
+ }
+
+ private void addComplication(ComplicationLayoutEngine engine, ViewInfo info) {
+ engine.addComplication(info.id, info.view, info.lp, info.category);
+ }
+
+ /**
+ * Makes sure the engine properly places a view within the {@link ConstraintLayout}.
+ */
+ @Test
+ public void testSingleLayout() {
+ final ViewInfo firstViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 0),
+ Complication.CATEGORY_STANDARD,
+ mLayout);
+
+ final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout);
+ addComplication(engine, firstViewInfo);
+
+ // Ensure the view is added to the top end corner
+ verifyChange(firstViewInfo, true, lp -> {
+ assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ });
+ }
+
+ /**
+ * Ensures layout in a particular direction updates.
+ */
+ @Test
+ public void testDirectionLayout() {
+ final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout);
+
+ final ViewInfo firstViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 0),
+ Complication.CATEGORY_STANDARD,
+ mLayout);
+
+ addComplication(engine, firstViewInfo);
+
+ firstViewInfo.clearInvocations();
+
+ final ViewInfo secondViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 0),
+ Complication.CATEGORY_SYSTEM,
+ mLayout);
+
+ addComplication(engine, secondViewInfo);
+
+ // The first added view should now be underneath the second view.
+ verifyChange(firstViewInfo, false, lp -> {
+ assertThat(lp.topToBottom == secondViewInfo.view.getId()).isTrue();
+ assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ });
+
+ // The second view should be in the top position.
+ verifyChange(secondViewInfo, true, lp -> {
+ assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ });
+ }
+
+ /**
+ * Ensures layout in a particular position updates.
+ */
+ @Test
+ public void testPositionLayout() {
+ final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout);
+
+ final ViewInfo firstViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 0),
+ Complication.CATEGORY_STANDARD,
+ mLayout);
+
+ addComplication(engine, firstViewInfo);
+
+ final ViewInfo secondViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 0),
+ Complication.CATEGORY_SYSTEM,
+ mLayout);
+
+ addComplication(engine, secondViewInfo);
+
+ firstViewInfo.clearInvocations();
+ secondViewInfo.clearInvocations();
+
+ final ViewInfo thirdViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_START,
+ 1),
+ Complication.CATEGORY_SYSTEM,
+ mLayout);
+
+ addComplication(engine, thirdViewInfo);
+
+ // The first added view should now be underneath the second view.
+ verifyChange(firstViewInfo, false, lp -> {
+ assertThat(lp.topToBottom == secondViewInfo.view.getId()).isTrue();
+ assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ });
+
+ // The second view should be in underneath the third view.
+ verifyChange(secondViewInfo, false, lp -> {
+ assertThat(lp.topToBottom == thirdViewInfo.view.getId()).isTrue();
+ assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ });
+
+ // The third view should be in at the top.
+ verifyChange(thirdViewInfo, true, lp -> {
+ assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ });
+
+ final ViewInfo fourthViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_START,
+ 1),
+ Complication.CATEGORY_STANDARD,
+ mLayout);
+
+ addComplication(engine, fourthViewInfo);
+
+ verifyChange(fourthViewInfo, true, lp -> {
+ assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ assertThat(lp.endToStart == thirdViewInfo.view.getId()).isTrue();
+ });
+ }
+
+ /**
+ * Ensures layout in a particular position updates.
+ */
+ @Test
+ public void testRemoval() {
+ final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout);
+
+ final ViewInfo firstViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 0),
+ Complication.CATEGORY_STANDARD,
+ mLayout);
+
+ engine.addComplication(firstViewInfo.id, firstViewInfo.view, firstViewInfo.lp,
+ firstViewInfo.category);
+
+ final ViewInfo secondViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 0),
+ Complication.CATEGORY_SYSTEM,
+ mLayout);
+
+ engine.addComplication(secondViewInfo.id, secondViewInfo.view, secondViewInfo.lp,
+ secondViewInfo.category);
+
+ firstViewInfo.clearInvocations();
+
+ engine.removeComplication(secondViewInfo.id);
+ verify(mLayout).removeView(eq(secondViewInfo.view));
+
+ verifyChange(firstViewInfo, true, lp -> {
+ assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ });
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
new file mode 100644
index 0000000..d080bbc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.dreams.complication;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ComplicationLayoutParamsTest extends SysuiTestCase {
+ /**
+ * Ensures ComplicationLayoutParams cannot be constructed with improper position or direction.
+ */
+ @Test
+ public void testPositionValidation() {
+ final HashSet<Integer> invalidCombinations = new HashSet(Arrays.asList(
+ ComplicationLayoutParams.POSITION_BOTTOM | ComplicationLayoutParams.POSITION_TOP,
+ ComplicationLayoutParams.POSITION_END | ComplicationLayoutParams.POSITION_START
+ ));
+
+ final int allPositions = ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_START
+ | ComplicationLayoutParams.POSITION_END
+ | ComplicationLayoutParams.POSITION_BOTTOM;
+
+ final HashSet<Integer> allDirections = new HashSet(Arrays.asList(
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ ComplicationLayoutParams.DIRECTION_UP,
+ ComplicationLayoutParams.DIRECTION_START,
+ ComplicationLayoutParams.DIRECTION_END
+ ));
+
+ final HashMap<Integer, Integer> invalidDirections = new HashMap<>();
+ invalidDirections.put(ComplicationLayoutParams.DIRECTION_DOWN,
+ ComplicationLayoutParams.POSITION_BOTTOM);
+ invalidDirections.put(ComplicationLayoutParams.DIRECTION_UP,
+ ComplicationLayoutParams.POSITION_TOP);
+ invalidDirections.put(ComplicationLayoutParams.DIRECTION_START,
+ ComplicationLayoutParams.POSITION_START);
+ invalidDirections.put(ComplicationLayoutParams.DIRECTION_END,
+ ComplicationLayoutParams.POSITION_END);
+
+
+ for (int position = 0; position <= allPositions; ++position) {
+ boolean properPosition = position != 0;
+ if (properPosition) {
+ for (Integer combination : invalidCombinations) {
+ if ((combination & position) == combination) {
+ properPosition = false;
+ }
+ }
+ }
+ boolean exceptionEncountered = false;
+ for (Integer direction : allDirections) {
+ final int invalidPosition = invalidDirections.get(direction);
+ final boolean properDirection = (invalidPosition & position) != invalidPosition;
+
+ try {
+ final ComplicationLayoutParams params = new ComplicationLayoutParams(
+ 100,
+ 100,
+ position,
+ direction,
+ 0);
+ } catch (Exception e) {
+ exceptionEncountered = true;
+ }
+
+ assertThat((properPosition && properDirection) || exceptionEncountered).isTrue();
+ }
+ }
+ }
+
+ /**
+ * Ensures ComplicationLayoutParams is properly duplicated on copy construction.
+ */
+ @Test
+ public void testCopyConstruction() {
+ final ComplicationLayoutParams params = new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 3);
+ final ComplicationLayoutParams copy = new ComplicationLayoutParams(params);
+
+ assertThat(copy.getDirection() == params.getDirection()).isTrue();
+ assertThat(copy.getPosition() == params.getPosition()).isTrue();
+ assertThat(copy.getWeight() == params.getWeight()).isTrue();
+ assertThat(copy.height == params.height).isTrue();
+ assertThat(copy.width == params.width).isTrue();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationViewModelTransformerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationViewModelTransformerTest.java
new file mode 100644
index 0000000..2bc427d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationViewModelTransformerTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.dreams.complication;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.complication.dagger.ComplicationViewModelComponent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ComplicationViewModelTransformerTest extends SysuiTestCase {
+ @Mock
+ ComplicationViewModelComponent.Factory mFactory;
+
+ @Mock
+ ComplicationViewModelComponent mComponent;
+
+ @Mock
+ ComplicationViewModelProvider mViewModelProvider;
+
+ @Mock
+ ComplicationViewModel mViewModel;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mFactory.create(Mockito.any(), Mockito.any())).thenReturn(mComponent);
+ when(mComponent.getViewModelProvider()).thenReturn(mViewModelProvider);
+ when(mViewModelProvider.get(Mockito.any(), Mockito.any())).thenReturn(mViewModel);
+ }
+
+ /**
+ * Ensure the same id is returned for the same complication across invocations.
+ */
+ @Test
+ public void testStableIds() {
+ final ComplicationViewModelTransformer transformer =
+ new ComplicationViewModelTransformer(mFactory);
+
+ final Complication complication = Mockito.mock(Complication.class);
+
+ ArgumentCaptor<ComplicationId> idCaptor = ArgumentCaptor.forClass(ComplicationId.class);
+
+ transformer.getViewModel(complication);
+ verify(mFactory).create(Mockito.any(), idCaptor.capture());
+ final ComplicationId firstId = idCaptor.getValue();
+
+ Mockito.clearInvocations(mFactory);
+
+ transformer.getViewModel(complication);
+ verify(mFactory).create(Mockito.any(), idCaptor.capture());
+ final ComplicationId secondId = idCaptor.getValue();
+
+ assertEquals(secondId, firstId);
+ }
+
+ /**
+ * Ensure unique ids are assigned to different complications.
+ */
+ @Test
+ public void testUniqueIds() {
+ final ComplicationViewModelTransformer transformer =
+ new ComplicationViewModelTransformer(mFactory);
+
+ final Complication firstComplication = Mockito.mock(Complication.class);
+ final Complication secondComplication = Mockito.mock(Complication.class);
+
+ ArgumentCaptor<ComplicationId> idCaptor = ArgumentCaptor.forClass(ComplicationId.class);
+
+ transformer.getViewModel(firstComplication);
+ verify(mFactory).create(Mockito.any(), idCaptor.capture());
+ final ComplicationId firstId = idCaptor.getValue();
+
+ Mockito.clearInvocations(mFactory);
+
+ transformer.getViewModel(secondComplication);
+ verify(mFactory).create(Mockito.any(), idCaptor.capture());
+ final ComplicationId secondId = idCaptor.getValue();
+
+ assertNotEquals(secondId, firstId);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index f3043e9..fb1a968 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -14,7 +14,6 @@
import com.android.keyguard.KeyguardViewController
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.policy.KeyguardStateController
import junit.framework.Assert.assertEquals
@@ -44,8 +43,6 @@
@Mock
private lateinit var keyguardViewController: KeyguardViewController
@Mock
- private lateinit var smartspaceTransitionController: SmartspaceTransitionController
- @Mock
private lateinit var featureFlags: FeatureFlags
@Mock
private lateinit var biometricUnlockController: BiometricUnlockController
@@ -59,7 +56,7 @@
MockitoAnnotations.initMocks(this)
keyguardUnlockAnimationController = KeyguardUnlockAnimationController(
context, keyguardStateController, { keyguardViewMediator }, keyguardViewController,
- smartspaceTransitionController, featureFlags, biometricUnlockController
+ featureFlags, { biometricUnlockController }
)
`when`(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java))
@@ -87,7 +84,7 @@
fun noSurfaceAnimation_ifWakeAndUnlocking() {
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
- keyguardUnlockAnimationController.notifyStartKeyguardExitAnimation(
+ keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
remoteAnimationTarget,
0 /* startTime */,
false /* requestedShowSurfaceBehindKeyguard */
@@ -118,15 +115,12 @@
fun surfaceAnimation_ifNotWakeAndUnlocking() {
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(false)
- keyguardUnlockAnimationController.notifyStartKeyguardExitAnimation(
+ keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
remoteAnimationTarget,
0 /* startTime */,
false /* requestedShowSurfaceBehindKeyguard */
)
- // Make sure the animator was started.
- assertTrue(keyguardUnlockAnimationController.surfaceBehindEntryAnimator.isRunning)
-
// Since the animation is running, we should not have finished the remote animation.
verify(keyguardViewMediator, times(0)).onKeyguardExitRemoteAnimationFinished(
false /* cancelled */)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
index 4839bde..81ae209 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
@@ -21,18 +21,17 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
-import com.android.systemui.media.taptotransfer.sender.*
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService
import com.android.systemui.shared.mediattt.DeviceInfo
-import com.android.systemui.shared.mediattt.IDeviceSenderCallback
+import com.android.systemui.shared.mediattt.IDeviceSenderService
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
-import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.anyString
@@ -44,6 +43,7 @@
import java.util.concurrent.Executor
@SmallTest
+@Ignore("b/216286227")
class MediaTttCommandLineHelperTest : SysuiTestCase() {
private val inlineExecutor = Executor { command -> command.run() }
@@ -53,11 +53,9 @@
private lateinit var mediaTttCommandLineHelper: MediaTttCommandLineHelper
@Mock
- private lateinit var mediaTttChipControllerSender: MediaTttChipControllerSender
- @Mock
private lateinit var mediaTttChipControllerReceiver: MediaTttChipControllerReceiver
@Mock
- private lateinit var mediaSenderService: IDeviceSenderCallback.Stub
+ private lateinit var mediaSenderService: IDeviceSenderService.Stub
private lateinit var mediaSenderServiceComponentName: ComponentName
@Before
@@ -73,28 +71,15 @@
MediaTttCommandLineHelper(
commandRegistry,
context,
- mediaTttChipControllerSender,
mediaTttChipControllerReceiver,
- FakeExecutor(FakeSystemClock())
)
}
@Test(expected = IllegalStateException::class)
- fun constructor_addSenderCommandAlreadyRegistered() {
- // Since creating the chip controller should automatically register the add command, it
+ fun constructor_senderCommandAlreadyRegistered() {
+ // Since creating the chip controller should automatically register the sender command, it
// should throw when registering it again.
- commandRegistry.registerCommand(
- ADD_CHIP_COMMAND_SENDER_TAG
- ) { EmptyCommand() }
- }
-
- @Test(expected = IllegalStateException::class)
- fun constructor_removeSenderCommandAlreadyRegistered() {
- // Since creating the chip controller should automatically register the remove command, it
- // should throw when registering it again.
- commandRegistry.registerCommand(
- REMOVE_CHIP_COMMAND_SENDER_TAG
- ) { EmptyCommand() }
+ commandRegistry.registerCommand(SENDER_COMMAND) { EmptyCommand() }
}
@Test(expected = IllegalStateException::class)
@@ -127,24 +112,74 @@
}
@Test
- fun sender_transferInitiated_chipDisplayWithCorrectState() {
- commandRegistry.onShellCommand(pw, getTransferInitiatedCommand())
+ fun sender_moveCloserToEndCast_serviceCallbackCalled() {
+ commandRegistry.onShellCommand(pw, getMoveCloserToEndCastCommand())
- verify(mediaTttChipControllerSender).displayChip(any(TransferInitiated::class.java))
+ assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
+
+ val deviceInfoCaptor = argumentCaptor<DeviceInfo>()
+ verify(mediaSenderService).closeToReceiverToEndCast(any(), capture(deviceInfoCaptor))
+ assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
}
@Test
- fun sender_transferSucceeded_chipDisplayWithCorrectState() {
- commandRegistry.onShellCommand(pw, getTransferSucceededCommand())
+ fun sender_transferToReceiverTriggered_chipDisplayWithCorrectState() {
+ commandRegistry.onShellCommand(pw, getTransferToReceiverTriggeredCommand())
- verify(mediaTttChipControllerSender).displayChip(any(TransferSucceeded::class.java))
+ assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
+
+ val deviceInfoCaptor = argumentCaptor<DeviceInfo>()
+ verify(mediaSenderService).transferToReceiverTriggered(any(), capture(deviceInfoCaptor))
+ assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
}
@Test
- fun sender_removeCommand_chipRemoved() {
- commandRegistry.onShellCommand(pw, arrayOf(REMOVE_CHIP_COMMAND_SENDER_TAG))
+ fun sender_transferToThisDeviceTriggered_chipDisplayWithCorrectState() {
+ commandRegistry.onShellCommand(pw, getTransferToThisDeviceTriggeredCommand())
- verify(mediaTttChipControllerSender).removeChip()
+ assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
+ verify(mediaSenderService).transferToThisDeviceTriggered(any(), any())
+ }
+
+ @Test
+ fun sender_transferToReceiverSucceeded_chipDisplayWithCorrectState() {
+ commandRegistry.onShellCommand(pw, getTransferToReceiverSucceededCommand())
+
+ assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
+
+ val deviceInfoCaptor = argumentCaptor<DeviceInfo>()
+ verify(mediaSenderService)
+ .transferToReceiverSucceeded(any(), capture(deviceInfoCaptor), any())
+ assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
+ }
+
+ @Test
+ fun sender_transferToThisDeviceSucceeded_chipDisplayWithCorrectState() {
+ commandRegistry.onShellCommand(pw, getTransferToThisDeviceSucceededCommand())
+
+ assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
+
+ val deviceInfoCaptor = argumentCaptor<DeviceInfo>()
+ verify(mediaSenderService)
+ .transferToThisDeviceSucceeded(any(), capture(deviceInfoCaptor), any())
+ assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
+ }
+
+ @Test
+ fun sender_transferFailed_serviceCallbackCalled() {
+ commandRegistry.onShellCommand(pw, getTransferFailedCommand())
+
+ assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
+ verify(mediaSenderService).transferFailed(any(), any())
+ }
+
+ @Test
+ fun sender_noLongerCloseToReceiver_serviceCallbackCalledAndServiceUnbound() {
+ commandRegistry.onShellCommand(pw, getNoLongerCloseToReceiverCommand())
+
+ // Once we're no longer close to the receiver, we should unbind the service.
+ assertThat(context.isBound(mediaSenderServiceComponentName)).isFalse()
+ verify(mediaSenderService).noLongerCloseToReceiver(any(), any())
}
@Test
@@ -163,23 +198,58 @@
private fun getMoveCloserToStartCastCommand(): Array<String> =
arrayOf(
- ADD_CHIP_COMMAND_SENDER_TAG,
+ SENDER_COMMAND,
DEVICE_NAME,
MOVE_CLOSER_TO_START_CAST_COMMAND_NAME
)
- private fun getTransferInitiatedCommand(): Array<String> =
+ private fun getMoveCloserToEndCastCommand(): Array<String> =
arrayOf(
- ADD_CHIP_COMMAND_SENDER_TAG,
+ SENDER_COMMAND,
DEVICE_NAME,
- TRANSFER_INITIATED_COMMAND_NAME
+ MOVE_CLOSER_TO_END_CAST_COMMAND_NAME
)
- private fun getTransferSucceededCommand(): Array<String> =
+ private fun getTransferToReceiverTriggeredCommand(): Array<String> =
arrayOf(
- ADD_CHIP_COMMAND_SENDER_TAG,
+ SENDER_COMMAND,
DEVICE_NAME,
- TRANSFER_SUCCEEDED_COMMAND_NAME
+ TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME
+ )
+
+ private fun getTransferToThisDeviceTriggeredCommand(): Array<String> =
+ arrayOf(
+ SENDER_COMMAND,
+ DEVICE_NAME,
+ TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME
+ )
+
+ private fun getTransferToReceiverSucceededCommand(): Array<String> =
+ arrayOf(
+ SENDER_COMMAND,
+ DEVICE_NAME,
+ TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME
+ )
+
+ private fun getTransferToThisDeviceSucceededCommand(): Array<String> =
+ arrayOf(
+ SENDER_COMMAND,
+ DEVICE_NAME,
+ TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME
+ )
+
+ private fun getTransferFailedCommand(): Array<String> =
+ arrayOf(
+ SENDER_COMMAND,
+ DEVICE_NAME,
+ TRANSFER_FAILED_COMMAND_NAME
+ )
+
+ private fun getNoLongerCloseToReceiverCommand(): Array<String> =
+ arrayOf(
+ SENDER_COMMAND,
+ DEVICE_NAME,
+ NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME
)
class EmptyCommand : Command {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index 927ca7a..242fd19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.Mock
@@ -38,6 +39,7 @@
import org.mockito.MockitoAnnotations
@SmallTest
+@Ignore("b/216286227")
class MediaTttChipControllerCommonTest : SysuiTestCase() {
private lateinit var controllerCommon: MediaTttChipControllerCommon<MediaTttChipState>
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index afaab80..1d1265b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.Mock
@@ -34,6 +35,7 @@
import org.mockito.MockitoAnnotations
@SmallTest
+@Ignore("b/216286227")
class MediaTttChipControllerReceiverTest : SysuiTestCase() {
private lateinit var controllerReceiver: MediaTttChipControllerReceiver
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index ecc4c46..6b4eebe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -26,26 +26,21 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.shared.mediattt.IUndoTransferCallback
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
-import com.google.common.util.concurrent.SettableFuture
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-import java.util.concurrent.Future
@SmallTest
+@Ignore("b/216286227")
class MediaTttChipControllerSenderTest : SysuiTestCase() {
private lateinit var appIconDrawable: Drawable
- private lateinit var fakeMainClock: FakeSystemClock
- private lateinit var fakeMainExecutor: FakeExecutor
- private lateinit var fakeBackgroundClock: FakeSystemClock
- private lateinit var fakeBackgroundExecutor: FakeExecutor
private lateinit var controllerSender: MediaTttChipControllerSender
@@ -56,124 +51,92 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
appIconDrawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
- fakeMainClock = FakeSystemClock()
- fakeMainExecutor = FakeExecutor(fakeMainClock)
- fakeBackgroundClock = FakeSystemClock()
- fakeBackgroundExecutor = FakeExecutor(fakeBackgroundClock)
- controllerSender = MediaTttChipControllerSender(
- context, windowManager, fakeMainExecutor, fakeBackgroundExecutor
- )
+ controllerSender = MediaTttChipControllerSender(context, windowManager)
}
@Test
- fun moveCloserToStartCast_appIcon_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
- controllerSender.displayChip(moveCloserToStartCast())
+ fun moveCloserToStartCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() {
+ val state = moveCloserToStartCast()
+ controllerSender.displayChip(state)
val chipView = getChipView()
assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
- assertThat(chipView.getChipText()).contains(DEVICE_NAME)
+ assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
}
@Test
- fun transferInitiated_futureNotResolvedYet_appIcon_loadingIcon_noUndo() {
- val future: SettableFuture<Runnable?> = SettableFuture.create()
- controllerSender.displayChip(transferInitiated(future))
+ fun moveCloserToEndCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() {
+ val state = moveCloserToEndCast()
+ controllerSender.displayChip(state)
- // Don't resolve the future in any way and don't run our executors
-
- // Assert we're still in the loading state
val chipView = getChipView()
assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
- assertThat(chipView.getChipText()).contains(DEVICE_NAME)
+ assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
+ assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+ assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun transferToReceiverTriggered_appIcon_loadingIcon_noUndo_noFailureIcon() {
+ val state = transferToReceiverTriggered()
+ controllerSender.displayChip(state)
+
+ val chipView = getChipView()
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
+ assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+ assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
}
@Test
- fun transferInitiated_futureResolvedSuccessfully_switchesToTransferSucceeded() {
- val future: SettableFuture<Runnable?> = SettableFuture.create()
- val undoRunnable = Runnable { }
-
- controllerSender.displayChip(transferInitiated(future))
-
- future.set(undoRunnable)
- fakeBackgroundExecutor.advanceClockToLast()
- fakeBackgroundExecutor.runAllReady()
- fakeMainExecutor.advanceClockToLast()
- val numRun = fakeMainExecutor.runAllReady()
-
- // Assert we ran the future callback
- assertThat(numRun).isEqualTo(1)
- // Assert that we've moved to the successful state
- val chipView = getChipView()
- assertThat(chipView.getChipText()).contains(DEVICE_NAME)
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
- }
-
- @Test
- fun transferInitiated_futureCancelled_chipRemoved() {
- val future: SettableFuture<Runnable?> = SettableFuture.create()
-
- controllerSender.displayChip(transferInitiated(future))
-
- future.cancel(true)
- fakeBackgroundExecutor.advanceClockToLast()
- fakeBackgroundExecutor.runAllReady()
- fakeMainExecutor.advanceClockToLast()
- val numRun = fakeMainExecutor.runAllReady()
-
- // Assert we ran the future callback
- assertThat(numRun).isEqualTo(1)
- // Assert that we've hidden the chip
- verify(windowManager).removeView(any())
- }
-
- @Test
- fun transferInitiated_futureNotResolvedAfterTimeout_chipRemoved() {
- val future: SettableFuture<Runnable?> = SettableFuture.create()
- controllerSender.displayChip(transferInitiated(future))
-
- // We won't set anything on the future, but we will still run the executors so that we're
- // waiting on the future resolving. If we have a bug in our code, then this test will time
- // out because we're waiting on the future indefinitely.
- fakeBackgroundExecutor.advanceClockToLast()
- fakeBackgroundExecutor.runAllReady()
- fakeMainExecutor.advanceClockToLast()
- val numRun = fakeMainExecutor.runAllReady()
-
- // Assert we eventually decide to not wait for the future anymore
- assertThat(numRun).isEqualTo(1)
- // Assert we've hidden the chip
- verify(windowManager).removeView(any())
- }
-
- @Test
- fun transferSucceeded_appIcon_chipTextContainsDeviceName_noLoadingIcon() {
- controllerSender.displayChip(transferSucceeded())
+ fun transferToThisDeviceTriggered_appIcon_loadingIcon_noUndo_noFailureIcon() {
+ val state = transferToThisDeviceTriggered()
+ controllerSender.displayChip(state)
val chipView = getChipView()
assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
- assertThat(chipView.getChipText()).contains(DEVICE_NAME)
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+ assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
+ assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
+ assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
}
@Test
- fun transferSucceededNullUndoRunnable_noUndo() {
- controllerSender.displayChip(transferSucceeded(undoRunnable = null))
+ fun transferToReceiverSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() {
+ val state = transferToReceiverSucceeded()
+ controllerSender.displayChip(state)
+
+ val chipView = getChipView()
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
+ assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+ assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
+ assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+ assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun transferToReceiverSucceeded_nullUndoRunnable_noUndo() {
+ controllerSender.displayChip(transferToReceiverSucceeded(undoCallback = null))
val chipView = getChipView()
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
}
@Test
- fun transferSucceededWithUndoRunnable_undoWithClick() {
- controllerSender.displayChip(transferSucceeded { })
+ fun transferToReceiverSucceeded_withUndoRunnable_undoWithClick() {
+ val undoCallback = object : IUndoTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ }
+ controllerSender.displayChip(transferToReceiverSucceeded(undoCallback))
val chipView = getChipView()
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
@@ -181,48 +144,154 @@
}
@Test
- fun transferSucceededWithUndoRunnable_undoButtonClickRunsRunnable() {
- var runnableRun = false
- val runnable = Runnable { runnableRun = true }
+ fun transferToReceiverSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() {
+ var undoCallbackCalled = false
+ val undoCallback = object : IUndoTransferCallback.Stub() {
+ override fun onUndoTriggered() {
+ undoCallbackCalled = true
+ }
+ }
- controllerSender.displayChip(transferSucceeded(undoRunnable = runnable))
+ controllerSender.displayChip(transferToReceiverSucceeded(undoCallback))
getChipView().getUndoButton().performClick()
- assertThat(runnableRun).isTrue()
+ assertThat(undoCallbackCalled).isTrue()
}
@Test
- fun changeFromCloserToStartToTransferInitiated_loadingIconAppears() {
+ fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
+ val undoCallback = object : IUndoTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ }
+ controllerSender.displayChip(transferToReceiverSucceeded(undoCallback))
+
+ getChipView().getUndoButton().performClick()
+
+ assertThat(getChipView().getChipText())
+ .isEqualTo(transferToThisDeviceTriggered().getChipTextString(context))
+ }
+
+ @Test
+ fun transferToThisDeviceSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() {
+ val state = transferToThisDeviceSucceeded()
+ controllerSender.displayChip(state)
+
+ val chipView = getChipView()
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
+ assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+ assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
+ assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+ assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun transferToThisDeviceSucceeded_nullUndoRunnable_noUndo() {
+ controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback = null))
+
+ val chipView = getChipView()
+ assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun transferToThisDeviceSucceeded_withUndoRunnable_undoWithClick() {
+ val undoCallback = object : IUndoTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ }
+ controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback))
+
+ val chipView = getChipView()
+ assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ fun transferToThisDeviceSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() {
+ var undoCallbackCalled = false
+ val undoCallback = object : IUndoTransferCallback.Stub() {
+ override fun onUndoTriggered() {
+ undoCallbackCalled = true
+ }
+ }
+
+ controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback))
+ getChipView().getUndoButton().performClick()
+
+ assertThat(undoCallbackCalled).isTrue()
+ }
+
+ @Test
+ fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToReceiverTriggered() {
+ val undoCallback = object : IUndoTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ }
+ controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback))
+
+ getChipView().getUndoButton().performClick()
+
+ assertThat(getChipView().getChipText())
+ .isEqualTo(transferToReceiverTriggered().getChipTextString(context))
+ }
+
+ @Test
+ fun transferFailed_appIcon_noDeviceName_noLoadingIcon_noUndo_failureIcon() {
+ val state = transferFailed()
+ controllerSender.displayChip(state)
+
+ val chipView = getChipView()
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
+ assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+ assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
+ assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+ assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun changeFromCloserToStartToTransferTriggered_loadingIconAppears() {
controllerSender.displayChip(moveCloserToStartCast())
- controllerSender.displayChip(transferInitiated())
+ controllerSender.displayChip(transferToReceiverTriggered())
assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
}
@Test
- fun changeFromTransferInitiatedToTransferSucceeded_loadingIconDisappears() {
- controllerSender.displayChip(transferInitiated())
- controllerSender.displayChip(transferSucceeded())
+ fun changeFromTransferTriggeredToTransferSucceeded_loadingIconDisappears() {
+ controllerSender.displayChip(transferToReceiverTriggered())
+ controllerSender.displayChip(transferToReceiverSucceeded())
assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.GONE)
}
@Test
- fun changeFromTransferInitiatedToTransferSucceeded_undoButtonAppears() {
- controllerSender.displayChip(transferInitiated())
- controllerSender.displayChip(transferSucceeded { })
+ fun changeFromTransferTriggeredToTransferSucceeded_undoButtonAppears() {
+ controllerSender.displayChip(transferToReceiverTriggered())
+ controllerSender.displayChip(
+ transferToReceiverSucceeded(
+ object : IUndoTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ }
+ )
+ )
assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.VISIBLE)
}
@Test
fun changeFromTransferSucceededToMoveCloserToStart_undoButtonDisappears() {
- controllerSender.displayChip(transferSucceeded())
+ controllerSender.displayChip(transferToReceiverSucceeded())
controllerSender.displayChip(moveCloserToStartCast())
assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE)
}
+ @Test
+ fun changeFromTransferTriggeredToTransferFailed_failureIconAppears() {
+ controllerSender.displayChip(transferToReceiverTriggered())
+ controllerSender.displayChip(transferFailed())
+
+ assertThat(getChipView().getFailureIcon().visibility).isEqualTo(View.VISIBLE)
+ }
+
private fun LinearLayout.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
private fun LinearLayout.getChipText(): String =
@@ -233,6 +302,8 @@
private fun LinearLayout.getUndoButton(): View = this.requireViewById(R.id.undo)
+ private fun LinearLayout.getFailureIcon(): View = this.requireViewById(R.id.failure_icon)
+
private fun getChipView(): LinearLayout {
val viewCaptor = ArgumentCaptor.forClass(View::class.java)
verify(windowManager).addView(viewCaptor.capture(), any())
@@ -244,18 +315,32 @@
MoveCloserToStartCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
/** Helper method providing default parameters to not clutter up the tests. */
- private fun transferInitiated(
- future: Future<Runnable?> = TEST_FUTURE
- ) = TransferInitiated(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, future)
+ private fun moveCloserToEndCast() =
+ MoveCloserToEndCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
/** Helper method providing default parameters to not clutter up the tests. */
- private fun transferSucceeded(
- undoRunnable: Runnable? = null
- ) = TransferSucceeded(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoRunnable)
+ private fun transferToReceiverTriggered() =
+ TransferToReceiverTriggered(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun transferToThisDeviceTriggered() =
+ TransferToThisDeviceTriggered(appIconDrawable, APP_ICON_CONTENT_DESC)
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun transferToReceiverSucceeded(undoCallback: IUndoTransferCallback? = null) =
+ TransferToReceiverSucceeded(
+ appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback
+ )
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun transferToThisDeviceSucceeded(undoCallback: IUndoTransferCallback? = null) =
+ TransferToThisDeviceSucceeded(
+ appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback
+ )
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun transferFailed() = TransferFailed(appIconDrawable, APP_ICON_CONTENT_DESC)
}
private const val DEVICE_NAME = "My Tablet"
private const val APP_ICON_CONTENT_DESC = "Content description"
-// Use a settable future that hasn't yet been set so that we don't immediately switch to the success
-// state.
-private val TEST_FUTURE: SettableFuture<Runnable?> = SettableFuture.create()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt
index 8f64698..64542cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt
@@ -4,21 +4,24 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.shared.mediattt.DeviceInfo
-import com.android.systemui.shared.mediattt.IDeviceSenderCallback
+import com.android.systemui.shared.mediattt.IDeviceSenderService
+import com.android.systemui.shared.mediattt.IUndoTransferCallback
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.google.common.truth.Truth.assertThat
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
+@Ignore("b/216286227")
class MediaTttSenderServiceTest : SysuiTestCase() {
- private lateinit var service: MediaTttSenderService
- private lateinit var callback: IDeviceSenderCallback
+ private lateinit var service: IDeviceSenderService
@Mock
private lateinit var controller: MediaTttChipControllerSender
@@ -30,19 +33,94 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- service = MediaTttSenderService(context, controller)
- callback = IDeviceSenderCallback.Stub.asInterface(service.onBind(null))
+ val mediaTttSenderService = MediaTttSenderService(context, controller)
+ service = IDeviceSenderService.Stub.asInterface(mediaTttSenderService.onBind(null))
}
@Test
- fun closeToReceiverToStartCast_controllerTriggeredWithMoveCloserToStartCastState() {
+ fun closeToReceiverToStartCast_controllerTriggeredWithCorrectState() {
val name = "Fake name"
- callback.closeToReceiverToStartCast(mediaInfo, DeviceInfo(name))
+ service.closeToReceiverToStartCast(mediaInfo, DeviceInfo(name))
val chipStateCaptor = argumentCaptor<MoveCloserToStartCast>()
verify(controller).displayChip(capture(chipStateCaptor))
val chipState = chipStateCaptor.value!!
- assertThat(chipState.otherDeviceName).isEqualTo(name)
+ assertThat(chipState.getChipTextString(context)).contains(name)
+ }
+
+ @Test
+ fun closeToReceiverToEndCast_controllerTriggeredWithCorrectState() {
+ val name = "Fake name"
+ service.closeToReceiverToEndCast(mediaInfo, DeviceInfo(name))
+
+ val chipStateCaptor = argumentCaptor<MoveCloserToEndCast>()
+ verify(controller).displayChip(capture(chipStateCaptor))
+
+ val chipState = chipStateCaptor.value!!
+ assertThat(chipState.getChipTextString(context)).contains(name)
+ }
+
+ @Test
+ fun transferToThisDeviceTriggered_controllerTriggeredWithCorrectState() {
+ service.transferToThisDeviceTriggered(mediaInfo, DeviceInfo("Fake name"))
+
+ verify(controller).displayChip(any<TransferToThisDeviceTriggered>())
+ }
+
+ @Test
+ fun transferToReceiverTriggered_controllerTriggeredWithCorrectState() {
+ val name = "Fake name"
+ service.transferToReceiverTriggered(mediaInfo, DeviceInfo(name))
+
+ val chipStateCaptor = argumentCaptor<TransferToReceiverTriggered>()
+ verify(controller).displayChip(capture(chipStateCaptor))
+
+ val chipState = chipStateCaptor.value!!
+ assertThat(chipState.getChipTextString(context)).contains(name)
+ }
+
+ @Test
+ fun transferToReceiverSucceeded_controllerTriggeredWithCorrectState() {
+ val name = "Fake name"
+ val undoCallback = object : IUndoTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ }
+ service.transferToReceiverSucceeded(mediaInfo, DeviceInfo(name), undoCallback)
+
+ val chipStateCaptor = argumentCaptor<TransferToReceiverSucceeded>()
+ verify(controller).displayChip(capture(chipStateCaptor))
+
+ val chipState = chipStateCaptor.value!!
+ assertThat(chipState.getChipTextString(context)).contains(name)
+ assertThat(chipState.undoCallback).isEqualTo(undoCallback)
+ }
+
+ @Test
+ fun transferToThisDeviceSucceeded_controllerTriggeredWithCorrectState() {
+ val undoCallback = object : IUndoTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ }
+ service.transferToThisDeviceSucceeded(mediaInfo, DeviceInfo("name"), undoCallback)
+
+ val chipStateCaptor = argumentCaptor<TransferToThisDeviceSucceeded>()
+ verify(controller).displayChip(capture(chipStateCaptor))
+
+ val chipState = chipStateCaptor.value!!
+ assertThat(chipState.undoCallback).isEqualTo(undoCallback)
+ }
+
+ @Test
+ fun transferFailed_controllerTriggeredWithTransferFailedState() {
+ service.transferFailed(mediaInfo, DeviceInfo("Fake name"))
+
+ verify(controller).displayChip(any<TransferFailed>())
+ }
+
+ @Test
+ fun noLongerCloseToReceiver_controllerRemoveChipTriggered() {
+ service.noLongerCloseToReceiver(mediaInfo, DeviceInfo("Fake name"))
+
+ verify(controller).removeChip()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 0bce621..91e5d33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -302,6 +302,40 @@
}
@Test
+ fun ignoreBlurForUnlock_ignores() {
+ notificationShadeDepthController.onPanelExpansionChanged(
+ rawFraction = 1f, expanded = true, tracking = false
+ )
+ `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
+
+ notificationShadeDepthController.blursDisabledForAppLaunch = false
+ notificationShadeDepthController.blursDisabledForUnlock = true
+
+ notificationShadeDepthController.updateBlurCallback.doFrame(0)
+
+ // Since we are ignoring blurs for unlock, we should be applying blur = 0 despite setting it
+ // to maxBlur above.
+ verify(blurUtils).applyBlur(any(), eq(0), eq(false))
+ }
+
+ @Test
+ fun ignoreBlurForUnlock_doesNotIgnore() {
+ notificationShadeDepthController.onPanelExpansionChanged(
+ rawFraction = 1f, expanded = true, tracking = false
+ )
+ `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
+
+ notificationShadeDepthController.blursDisabledForAppLaunch = false
+ notificationShadeDepthController.blursDisabledForUnlock = false
+
+ notificationShadeDepthController.updateBlurCallback.doFrame(0)
+
+ // Since we are not ignoring blurs for unlock (or app launch), we should apply the blur we
+ // returned above (maxBlur).
+ verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
+ }
+
+ @Test
fun brightnessMirrorVisible_whenVisible() {
notificationShadeDepthController.brightnessMirrorVisible = true
verify(brightnessSpring).animateTo(eq(maxBlur), any())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 1961ab2..188baaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -561,6 +561,10 @@
override fun setMediaTarget(target: SmartspaceTarget?) {
}
+
+ override fun getSelectedPage(): Int {
+ return -1
+ }
})
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
index bbe92f6..15ff555 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
@@ -274,6 +274,18 @@
public void removeChild(@NonNull NodeController child, boolean isTransfer) {
view.removeView(child.getView());
}
+
+ @Override
+ public void onViewAdded() {
+ }
+
+ @Override
+ public void onViewMoved() {
+ }
+
+ @Override
+ public void onViewRemoved() {
+ }
}
private static class SpecBuilder {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 04c6f6c..86a705f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -137,6 +137,7 @@
@Mock private VisualStabilityManager mVisualStabilityManager;
@Mock private ShadeController mShadeController;
@Mock private InteractionJankMonitor mJankMonitor;
+ @Mock private StackStateLogger mStackLogger;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -191,7 +192,8 @@
mRemoteInputManager,
mVisualStabilityManager,
mShadeController,
- mJankMonitor
+ mJankMonitor,
+ mStackLogger
);
when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index c3349f1..5ca1f21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -42,6 +42,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -104,6 +105,8 @@
private ScreenLifecycle mScreenLifecycle;
@Mock
private StatusBarStateController mStatusBarStateController;
+ @Mock
+ private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private BiometricUnlockController mBiometricUnlockController;
@Before
@@ -126,7 +129,7 @@
mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters,
mMetricsLogger, mDumpManager, mPowerManager,
mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
- mAuthController, mStatusBarStateController);
+ mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 35f671bf..1c8b35a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -104,6 +104,7 @@
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.idle.IdleHostViewController;
import com.android.systemui.idle.dagger.IdleViewComponent;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
@@ -362,6 +363,8 @@
private QsFrameTranslateController mQsFrameTranslateController;
@Mock
private StatusBarWindowStateController mStatusBarWindowStateController;
+ @Mock
+ private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
private SysuiStatusBarStateController mStatusBarStateController;
private NotificationPanelViewController mNotificationPanelViewController;
@@ -546,7 +549,8 @@
mSysUIUnfoldComponent,
mControlsComponent,
mInteractionJankMonitor,
- mQsFrameTranslateController);
+ mQsFrameTranslateController,
+ mKeyguardUnlockAnimationController);
mNotificationPanelViewController.initDependencies(
mStatusBar,
() -> {},
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index bb8bad3..f4f5bfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -129,7 +129,6 @@
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.init.NotificationsController;
-import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake;
@@ -226,7 +225,6 @@
@Mock private NotificationMediaManager mNotificationMediaManager;
@Mock private NavigationBarController mNavigationBarController;
@Mock private AccessibilityFloatingMenuController mAccessibilityFloatingMenuController;
- @Mock private BypassHeadsUpNotifier mBypassHeadsUpNotifier;
@Mock private SysuiColorExtractor mColorExtractor;
@Mock private ColorExtractor.GradientColors mGradientColors;
@Mock private PulseExpansionHandler mPulseExpansionHandler;
@@ -394,7 +392,6 @@
mKeyguardStateController,
mHeadsUpManager,
mDynamicPrivacyController,
- mBypassHeadsUpNotifier,
new FalsingManagerFake(),
new FalsingCollectorFake(),
mBroadcastDispatcher,
@@ -819,31 +816,27 @@
}
@Test
- public void testSetExpansionAffectsAlpha_onlyWhenHidingKeyguard() {
- mStatusBar.updateScrimController();
- verify(mScrimController).setExpansionAffectsAlpha(eq(true));
-
- clearInvocations(mScrimController);
- when(mBiometricUnlockController.isBiometricUnlock()).thenReturn(true);
+ public void testSetExpansionAffectsAlpha_whenKeyguardShowingButGoingAwayForAnyReason() {
mStatusBar.updateScrimController();
verify(mScrimController).setExpansionAffectsAlpha(eq(true));
clearInvocations(mScrimController);
when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
mStatusBar.updateScrimController();
- verify(mScrimController).setExpansionAffectsAlpha(eq(false));
+ verify(mScrimController).setExpansionAffectsAlpha(eq(true));
clearInvocations(mScrimController);
- reset(mKeyguardStateController);
- when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true);
- mStatusBar.updateScrimController();
- verify(mScrimController).setExpansionAffectsAlpha(eq(false));
-
- clearInvocations(mScrimController);
- reset(mKeyguardStateController);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
mStatusBar.updateScrimController();
verify(mScrimController).setExpansionAffectsAlpha(eq(false));
+
+ clearInvocations(mScrimController);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true);
+ mStatusBar.updateScrimController();
+ verify(mScrimController).setExpansionAffectsAlpha(eq(false));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 24a56bc..71b32c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.phone
import android.animation.Animator
+import android.os.Handler
+import android.os.PowerManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.View
@@ -29,13 +31,19 @@
import com.android.systemui.statusbar.StatusBarStateControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.settings.GlobalSettings
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.never
import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@@ -53,7 +61,9 @@
@Mock
private lateinit var globalSettings: GlobalSettings
@Mock
- private lateinit var statusbar: StatusBar
+ private lateinit var statusBar: StatusBar
+ @Mock
+ private lateinit var notificationPanelViewController: NotificationPanelViewController
@Mock
private lateinit var lightRevealScrim: LightRevealScrim
@Mock
@@ -62,6 +72,10 @@
private lateinit var statusBarStateController: StatusBarStateControllerImpl
@Mock
private lateinit var interactionJankMonitor: InteractionJankMonitor
+ @Mock
+ private lateinit var powerManager: PowerManager
+ @Mock
+ private lateinit var handler: Handler
@Before
fun setUp() {
@@ -75,9 +89,24 @@
keyguardStateController,
dagger.Lazy<DozeParameters> { dozeParameters },
globalSettings,
- interactionJankMonitor
+ interactionJankMonitor,
+ powerManager,
+ handler = handler
)
- controller.initialize(statusbar, lightRevealScrim)
+ controller.initialize(statusBar, lightRevealScrim)
+ `when`(statusBar.notificationPanelViewController).thenReturn(
+ notificationPanelViewController)
+
+ // Screen off does not run if the panel is expanded, so we should say it's collapsed to test
+ // screen off.
+ `when`(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+ }
+
+ @After
+ fun cleanUp() {
+ // Tell the screen off controller to cancel the animations and clean up its state, or
+ // subsequent tests will act unpredictably as the animator continues running.
+ controller.onStartedWakingUp()
}
@Test
@@ -93,4 +122,49 @@
listener.value.onAnimationEnd(null)
Mockito.verify(animator).setListener(null)
}
+
+ /**
+ * The AOD UI is shown during the screen off animation, after a delay to allow the light reveal
+ * animation to start. If the device is woken up during the screen off, we should *never* do
+ * this.
+ *
+ * This test confirms that we do show the AOD UI when the device is not woken up
+ * (PowerManager#isInteractive = false).
+ */
+ @Test
+ fun testAodUiShownIfNotInteractive() {
+ `when`(dozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true)
+ `when`(powerManager.isInteractive).thenReturn(false)
+
+ val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+ controller.startAnimation()
+
+ verify(handler).postDelayed(callbackCaptor.capture(), anyLong())
+
+ callbackCaptor.value.run()
+
+ verify(notificationPanelViewController, times(1)).showAodUi()
+ }
+
+ /**
+ * The AOD UI is shown during the screen off animation, after a delay to allow the light reveal
+ * animation to start. If the device is woken up during the screen off, we should *never* do
+ * this.
+ *
+ * This test confirms that we do not show the AOD UI when the device is woken up during screen
+ * off (PowerManager#isInteractive = true).
+ */
+ @Test
+ fun testAodUiNotShownIfInteractive() {
+ `when`(dozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true)
+ `when`(powerManager.isInteractive).thenReturn(true)
+
+ val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+ controller.startAnimation()
+
+ verify(handler).postDelayed(callbackCaptor.capture(), anyLong())
+ callbackCaptor.value.run()
+
+ verify(notificationPanelViewController, never()).showAodUi()
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index d15ba26..d325840 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -23,14 +23,20 @@
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Person;
import android.content.Context;
import android.content.Intent;
+import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -40,12 +46,14 @@
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AlertingNotificationManagerTest;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -58,10 +66,15 @@
private HeadsUpManager mHeadsUpManager;
private boolean mLivesPastNormalTime;
private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
+ @Mock private HeadsUpManager.HeadsUpEntry mAlertEntry;
+ @Mock private NotificationEntry mEntry;
+ @Mock private StatusBarNotification mSbn;
+ @Mock private Notification mNotification;
+ @Mock private HeadsUpManagerLogger mLogger;
private final class TestableHeadsUpManager extends HeadsUpManager {
- TestableHeadsUpManager(Context context) {
- super(context, mock(HeadsUpManagerLogger.class));
+ TestableHeadsUpManager(Context context, HeadsUpManagerLogger logger) {
+ super(context, logger);
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
}
@@ -73,10 +86,12 @@
@Before
public void setUp() {
+ initMocks(this);
mAccessibilityMgr = mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
mDependency.injectTestDependency(UiEventLogger.class, mUiEventLoggerFake);
-
- mHeadsUpManager = new TestableHeadsUpManager(mContext);
+ when(mEntry.getSbn()).thenReturn(mSbn);
+ when(mSbn.getNotification()).thenReturn(mNotification);
+ mHeadsUpManager = new TestableHeadsUpManager(mContext, mLogger);
super.setUp();
mHeadsUpManager.mHandler.removeCallbacksAndMessages(null);
mHeadsUpManager.mHandler = mTestHandler;
@@ -88,6 +103,13 @@
}
@Test
+ public void testHunRemovedLogging() {
+ mAlertEntry.mEntry = mEntry;
+ mHeadsUpManager.onAlertEntryRemoved(mAlertEntry);
+ verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(mEntry.getKey()));
+ }
+
+ @Test
public void testShowNotification_autoDismissesWithAccessibilityTimeout() {
doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr)
.getRecommendedTimeoutMillis(anyInt(), anyInt());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
index 8ccaf93..4a8170f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -33,7 +33,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import org.junit.Before;
import org.junit.Test;
@@ -41,6 +41,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import dagger.Lazy;
+
@SmallTest
@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner.class)
@@ -52,9 +54,9 @@
private LockPatternUtils mLockPatternUtils;
private KeyguardStateController mKeyguardStateController;
@Mock
- private SmartspaceTransitionController mSmartSpaceTransitionController;
- @Mock
private DumpManager mDumpManager;
+ @Mock
+ private Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
@Before
public void setup() {
@@ -63,7 +65,7 @@
mContext,
mKeyguardUpdateMonitor,
mLockPatternUtils,
- mSmartSpaceTransitionController,
+ mKeyguardUnlockAnimationControllerLazy,
mDumpManager);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
index e136d00..aaea4ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
@@ -129,11 +129,6 @@
}
@Override
- public boolean canPerformSmartSpaceTransition() {
- return false;
- }
-
- @Override
public boolean isKeyguardScreenRotationAllowed() {
return false;
}
diff --git a/services/Android.bp b/services/Android.bp
index f33c8c0..26760aa 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -179,6 +179,10 @@
name: "libandroid_servers",
defaults: ["libservices.core-libs"],
whole_static_libs: ["libservices.core"],
+ required: [
+ // TODO: remove after NetworkStatsService is moved to the mainline module.
+ "libcom_android_net_module_util_jni",
+ ],
}
platform_compat_config {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 91e9093..5b580d9 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3497,6 +3497,7 @@
pw.append("hasWindowMagnificationConnection=").append(
String.valueOf(getWindowMagnificationMgr().isConnected()));
pw.println();
+ mMagnificationProcessor.dump(pw, getValidDisplayList());
final int userCount = mUserStates.size();
for (int i = 0; i < userCount; i++) {
mUserStates.valueAt(i).dump(fd, pw, args);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 0f35456..51b49ed 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -516,6 +516,8 @@
.append(String.valueOf(mMagnificationCapabilities));
pw.append(", audioDescriptionByDefaultEnabled=")
.append(String.valueOf(mIsAudioDescriptionByDefaultRequested));
+ pw.append(", magnificationFollowTypingEnabled=")
+ .append(String.valueOf(mMagnificationFollowTypingEnabled));
pw.append("}");
pw.println();
pw.append(" shortcut key:{");
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index e39b979..fe97a46 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -769,6 +769,10 @@
mMagnificationFollowTypingEnabled = enabled;
}
+ boolean isMagnificationFollowTypingEnabled() {
+ return mMagnificationFollowTypingEnabled;
+ }
+
/**
* Remove the display magnification with given id.
*
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
index 8f15d5c..9eb77b4 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -26,6 +26,10 @@
import android.accessibilityservice.MagnificationConfig;
import android.annotation.NonNull;
import android.graphics.Region;
+import android.view.Display;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
/**
* Processor class for AccessibilityService connection to control magnification on the specified
@@ -360,4 +364,45 @@
private void unregister(int displayId) {
mController.getFullScreenMagnificationController().unregister(displayId);
}
+
+ /**
+ * Dumps magnification configuration {@link MagnificationConfig} and state for each
+ * {@link Display}
+ */
+ public void dump(final PrintWriter pw, ArrayList<Display> displaysList) {
+ for (int i = 0; i < displaysList.size(); i++) {
+ final int displayId = displaysList.get(i).getDisplayId();
+
+ final MagnificationConfig config = getMagnificationConfig(displayId);
+ pw.println("Magnifier on display#" + displayId);
+ pw.append(" " + config).println();
+
+ final Region region = new Region();
+ getCurrentMagnificationRegion(displayId, region, true);
+ if (!region.isEmpty()) {
+ pw.append(" Magnification region=").append(region.toString()).println();
+ }
+ pw.append(" IdOfLastServiceToMagnify="
+ + getIdOfLastServiceToMagnify(config.getMode(), displayId)).println();
+
+ dumpTrackingTypingFocusEnabledState(pw, displayId, config.getMode());
+ }
+ }
+
+ private int getIdOfLastServiceToMagnify(int mode, int displayId) {
+ return (mode == MAGNIFICATION_MODE_FULLSCREEN)
+ ? mController.getFullScreenMagnificationController()
+ .getIdOfLastServiceToMagnify(displayId)
+ : mController.getWindowMagnificationMgr().getIdOfLastServiceToMagnify(
+ displayId);
+ }
+
+ private void dumpTrackingTypingFocusEnabledState(final PrintWriter pw, int displayId,
+ int mode) {
+ if (mode == MAGNIFICATION_MODE_WINDOW) {
+ pw.append(" TrackingTypingFocusEnabled=" + mController
+ .getWindowMagnificationMgr().isTrackingTypingFocusEnabled(displayId))
+ .println();
+ }
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index 95c7d44..278f3f9 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -318,6 +318,26 @@
mMagnificationFollowTypingEnabled = enabled;
}
+ boolean isMagnificationFollowTypingEnabled() {
+ return mMagnificationFollowTypingEnabled;
+ }
+
+ /**
+ * Get the ID of the last service that changed the magnification config.
+ *
+ * @param displayId The logical display id.
+ * @return The id
+ */
+ public int getIdOfLastServiceToMagnify(int displayId) {
+ synchronized (mLock) {
+ final WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
+ if (magnifier != null) {
+ return magnifier.mIdOfLastServiceToControl;
+ }
+ }
+ return INVALID_SERVICE_ID;
+ }
+
/**
* Enable or disable tracking typing focus for the specific magnification window.
*
@@ -466,6 +486,7 @@
boolean previousEnabled;
synchronized (mLock) {
if (mConnectionWrapper == null) {
+ Slog.w(TAG, "enableWindowMagnification failed: connection null");
return false;
}
WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
@@ -508,6 +529,7 @@
synchronized (mLock) {
WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
if (magnifier == null || mConnectionWrapper == null) {
+ Slog.w(TAG, "disableWindowMagnification failed: connection " + mConnectionWrapper);
return false;
}
disabled = magnifier.disableWindowMagnificationInternal(animationCallback);
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 0fd2967..75acf81 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -23,14 +23,18 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.compat.CompatChanges;
+import android.companion.virtual.VirtualDeviceManager.ActivityListener;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
+import android.view.Display;
import android.window.DisplayWindowPolicyController;
import java.util.List;
@@ -60,15 +64,29 @@
@NonNull
final ArraySet<Integer> mRunningUids = new ArraySet<>();
+ @Nullable private final ActivityListener mActivityListener;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ /**
+ * Creates a window policy controller that is generic to the different use cases of virtual
+ * device.
+ *
+ * @param windowFlags The window flags that this controller is interested in.
+ * @param systemWindowFlags The system window flags that this controller is interested in.
+ * @param allowedUsers The set of users that are allowed to stream in this display.
+ * @param activityListener Activity listener to listen for activity changes. The display ID
+ * is not populated in this callback and is always {@link Display#INVALID_DISPLAY}.
+ */
GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
@NonNull ArraySet<UserHandle> allowedUsers,
@Nullable Set<ComponentName> allowedActivities,
- @Nullable Set<ComponentName> blockedActivities) {
+ @Nullable Set<ComponentName> blockedActivities,
+ @NonNull ActivityListener activityListener) {
mAllowedUsers = allowedUsers;
mAllowedActivities = allowedActivities == null ? null : new ArraySet<>(allowedActivities);
mBlockedActivities = blockedActivities == null ? null : new ArraySet<>(blockedActivities);
setInterestedWindowFlags(windowFlags, systemWindowFlags);
+ mActivityListener = activityListener;
}
@Override
@@ -92,13 +110,21 @@
@Override
public void onTopActivityChanged(ComponentName topActivity, int uid) {
-
+ if (mActivityListener != null) {
+ // Post callback on the main thread so it doesn't block activity launching
+ mHandler.post(() ->
+ mActivityListener.onTopActivityChanged(Display.INVALID_DISPLAY, topActivity));
+ }
}
@Override
public void onRunningAppsChanged(ArraySet<Integer> runningUids) {
mRunningUids.clear();
mRunningUids.addAll(runningUids);
+ if (mActivityListener != null && mRunningUids.isEmpty()) {
+ // Post callback on the main thread so it doesn't block activity launching
+ mHandler.post(() -> mActivityListener.onDisplayEmpty(Display.INVALID_DISPLAY));
+ }
}
/**
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index ae39d7e..6c56e2f 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -27,6 +27,7 @@
import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualTouchEvent;
import android.os.IBinder;
+import android.os.IInputConstants;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Slog;
@@ -126,6 +127,7 @@
final InputManagerInternal inputManagerInternal =
LocalServices.getService(InputManagerInternal.class);
inputManagerInternal.setVirtualMousePointerDisplayId(displayId);
+ inputManagerInternal.setPointerAcceleration(1);
mActivePointerDisplayId = displayId;
}
try {
@@ -210,6 +212,8 @@
final InputManagerInternal inputManagerInternal =
LocalServices.getService(InputManagerInternal.class);
inputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
+ inputManagerInternal.setPointerAcceleration(
+ IInputConstants.DEFAULT_POINTER_ACCELERATION);
mActivePointerDisplayId = Display.INVALID_DISPLAY;
}
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 6ab8e75..5c8fb2e 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -29,7 +29,10 @@
import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.IVirtualDeviceActivityListener;
+import android.companion.virtual.VirtualDeviceManager.ActivityListener;
import android.companion.virtual.VirtualDeviceParams;
+import android.content.ComponentName;
import android.content.Context;
import android.graphics.Point;
import android.graphics.PointF;
@@ -75,6 +78,30 @@
private final OnDeviceCloseListener mListener;
private final IBinder mAppToken;
private final VirtualDeviceParams mParams;
+ private final IVirtualDeviceActivityListener mActivityListener;
+
+ private ActivityListener createListenerAdapter(int displayId) {
+ return new ActivityListener() {
+
+ @Override
+ public void onTopActivityChanged(int unusedDisplayId, ComponentName topActivity) {
+ try {
+ mActivityListener.onTopActivityChanged(displayId, topActivity);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to call mActivityListener", e);
+ }
+ }
+
+ @Override
+ public void onDisplayEmpty(int unusedDisplayId) {
+ try {
+ mActivityListener.onDisplayEmpty(displayId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to call mActivityListener", e);
+ }
+ }
+ };
+ }
/**
* A mapping from the virtual display ID to its corresponding
@@ -85,18 +112,22 @@
VirtualDeviceImpl(Context context, AssociationInfo associationInfo,
IBinder token, int ownerUid, OnDeviceCloseListener listener,
- PendingTrampolineCallback pendingTrampolineCallback, VirtualDeviceParams params) {
+ PendingTrampolineCallback pendingTrampolineCallback,
+ IVirtualDeviceActivityListener activityListener,
+ VirtualDeviceParams params) {
this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener,
- pendingTrampolineCallback, params);
+ pendingTrampolineCallback, activityListener, params);
}
@VisibleForTesting
VirtualDeviceImpl(Context context, AssociationInfo associationInfo, IBinder token,
int ownerUid, InputController inputController, OnDeviceCloseListener listener,
- PendingTrampolineCallback pendingTrampolineCallback, VirtualDeviceParams params) {
+ PendingTrampolineCallback pendingTrampolineCallback,
+ IVirtualDeviceActivityListener activityListener, VirtualDeviceParams params) {
mContext = context;
mAssociationInfo = associationInfo;
mPendingTrampolineCallback = pendingTrampolineCallback;
+ mActivityListener = activityListener;
mOwnerUid = ownerUid;
mAppToken = token;
mParams = params;
@@ -361,9 +392,11 @@
displayId, false);
final GenericWindowPolicyController dwpc =
new GenericWindowPolicyController(FLAG_SECURE,
- SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, getAllowedUserHandles(),
+ SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+ getAllowedUserHandles(),
mParams.getAllowedActivities(),
- mParams.getBlockedActivities());
+ mParams.getBlockedActivities(),
+ createListenerAdapter(displayId));
mWindowPolicyControllers.put(displayId, dwpc);
return dwpc;
}
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 7e0c2fc..b507110 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -27,6 +27,7 @@
import android.companion.CompanionDeviceManager;
import android.companion.CompanionDeviceManager.OnAssociationsChangedListener;
import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.IVirtualDeviceManager;
import android.companion.virtual.VirtualDeviceParams;
import android.content.Context;
@@ -176,7 +177,8 @@
IBinder token,
String packageName,
int associationId,
- @NonNull VirtualDeviceParams params) {
+ @NonNull VirtualDeviceParams params,
+ @NonNull IVirtualDeviceActivityListener activityListener) {
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"createVirtualDevice");
@@ -206,7 +208,7 @@
}
}
},
- this, params);
+ this, activityListener, params);
mVirtualDevices.put(associationInfo.getId(), virtualDevice);
return virtualDevice;
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 094ed37..1106fe7 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -176,6 +176,9 @@
"overlayable_policy_aidl-java",
"SurfaceFlingerProperties",
"com.android.sysprop.watchdog",
+ // This is used for services.connectivity-tiramisu-sources.
+ // TODO: delete when NetworkStatsService is moved to the mainline module.
+ "net-utils-device-common-bpf",
],
javac_shard_size: 50,
}
diff --git a/services/core/java/android/app/usage/OWNERS b/services/core/java/android/app/usage/OWNERS
new file mode 100644
index 0000000..3a55514
--- /dev/null
+++ b/services/core/java/android/app/usage/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/core/java/android/app/usage/OWNERS
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index 21fc19e..435d294 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -17,6 +17,7 @@
package android.app.usage;
import android.annotation.CurrentTimeMillisLong;
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -25,6 +26,7 @@
import android.content.LocusId;
import android.content.res.Configuration;
import android.os.IBinder;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
@@ -362,4 +364,52 @@
/** Unregister a listener from being notified of every estimated launch time change. */
public abstract void unregisterLaunchTimeChangedListener(
@NonNull EstimatedLaunchTimeChangedListener listener);
+
+ /**
+ * Reports a broadcast dispatched event to the UsageStatsManager.
+ *
+ * @param sourceUid uid of the package that sent the broadcast.
+ * @param targetPackage name of the package that the broadcast is targeted to.
+ * @param targetUser user that {@code targetPackage} belongs to.
+ * @param idForResponseEvent ID to be used for recording any response events corresponding
+ * to this broadcast.
+ * @param timestampMs time (in millis) when the broadcast was dispatched, in
+ * {@link SystemClock#elapsedRealtime()} timebase.
+ */
+ public abstract void reportBroadcastDispatched(int sourceUid, @NonNull String targetPackage,
+ @NonNull UserHandle targetUser, long idForResponseEvent,
+ @ElapsedRealtimeLong long timestampMs);
+
+ /**
+ * Reports a notification posted event to the UsageStatsManager.
+ *
+ * @param packageName name of the package which posted the notification.
+ * @param user user that {@code packageName} belongs to.
+ * @param timestampMs time (in millis) when the notification was posted, in
+ * {@link SystemClock#elapsedRealtime()} timebase.
+ */
+ public abstract void reportNotificationPosted(@NonNull String packageName,
+ @NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs);
+
+ /**
+ * Reports a notification updated event to the UsageStatsManager.
+ *
+ * @param packageName name of the package which updated the notification.
+ * @param user user that {@code packageName} belongs to.
+ * @param timestampMs time (in millis) when the notification was updated, in
+ * {@link SystemClock#elapsedRealtime()} timebase.
+ */
+ public abstract void reportNotificationUpdated(@NonNull String packageName,
+ @NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs);
+
+ /**
+ * Reports a notification removed event to the UsageStatsManager.
+ *
+ * @param packageName name of the package which removed the notification.
+ * @param user user that {@code packageName} belongs to.
+ * @param timestampMs time (in millis) when the notification was removed, in
+ * {@link SystemClock#elapsedRealtime()} timebase.
+ */
+ public abstract void reportNotificationRemoved(@NonNull String packageName,
+ @NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs);
}
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 3951680..8551d88 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -20,12 +20,12 @@
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.OBSERVE_NETWORK_POLICY;
import static android.Manifest.permission.SHUTDOWN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
import static android.net.INetd.FIREWALL_ALLOWLIST;
-import static android.net.INetd.FIREWALL_CHAIN_DOZABLE;
import static android.net.INetd.FIREWALL_CHAIN_NONE;
-import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE;
-import static android.net.INetd.FIREWALL_CHAIN_RESTRICTED;
-import static android.net.INetd.FIREWALL_CHAIN_STANDBY;
import static android.net.INetd.FIREWALL_DENYLIST;
import static android.net.INetd.FIREWALL_RULE_ALLOW;
import static android.net.INetd.FIREWALL_RULE_DENY;
@@ -44,6 +44,7 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.Context;
+import android.net.ConnectivityManager;
import android.net.INetd;
import android.net.INetdUnsolicitedEventListener;
import android.net.INetworkManagementEventObserver;
@@ -1158,19 +1159,12 @@
}
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "inetd bandwidth");
+ final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
try {
if (allowlist) {
- if (enable) {
- mNetdService.bandwidthAddNiceApp(uid);
- } else {
- mNetdService.bandwidthRemoveNiceApp(uid);
- }
+ cm.updateMeteredNetworkAllowList(uid, enable);
} else {
- if (enable) {
- mNetdService.bandwidthAddNaughtyApp(uid);
- } else {
- mNetdService.bandwidthRemoveNaughtyApp(uid);
- }
+ cm.updateMeteredNetworkDenyList(uid, enable);
}
synchronized (mRulesLock) {
if (enable) {
@@ -1179,7 +1173,7 @@
quotaList.delete(uid);
}
}
- } catch (RemoteException | ServiceSpecificException e) {
+ } catch (RuntimeException e) {
throw new IllegalStateException(e);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
@@ -1464,9 +1458,10 @@
throw new IllegalArgumentException("Bad child chain: " + chainName);
}
+ final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
try {
- mNetdService.firewallEnableChildChain(chain, enable);
- } catch (RemoteException | ServiceSpecificException e) {
+ cm.setFirewallChainEnabled(chain, enable);
+ } catch (RuntimeException e) {
throw new IllegalStateException(e);
}
@@ -1538,25 +1533,10 @@
updateFirewallUidRuleLocked(chain, uid, FIREWALL_RULE_DEFAULT);
}
}
+ final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
try {
- switch (chain) {
- case FIREWALL_CHAIN_DOZABLE:
- mNetdService.firewallReplaceUidChain("fw_dozable", true, uids);
- break;
- case FIREWALL_CHAIN_STANDBY:
- mNetdService.firewallReplaceUidChain("fw_standby", false, uids);
- break;
- case FIREWALL_CHAIN_POWERSAVE:
- mNetdService.firewallReplaceUidChain("fw_powersave", true, uids);
- break;
- case FIREWALL_CHAIN_RESTRICTED:
- mNetdService.firewallReplaceUidChain("fw_restricted", true, uids);
- break;
- case FIREWALL_CHAIN_NONE:
- default:
- Slog.d(TAG, "setFirewallUidRules() called on invalid chain: " + chain);
- }
- } catch (RemoteException e) {
+ cm.replaceFirewallChain(chain, uids);
+ } catch (RuntimeException e) {
Slog.w(TAG, "Error flushing firewall chain " + chain, e);
}
}
@@ -1572,10 +1552,10 @@
private void setFirewallUidRuleLocked(int chain, int uid, int rule) {
if (updateFirewallUidRuleLocked(chain, uid, rule)) {
- final int ruleType = getFirewallRuleType(chain, rule);
+ final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
try {
- mNetdService.firewallSetUidRule(chain, uid, ruleType);
- } catch (RemoteException | ServiceSpecificException e) {
+ cm.updateFirewallRule(chain, uid, isFirewallRuleAllow(chain, rule));
+ } catch (RuntimeException e) {
throw new IllegalStateException(e);
}
}
@@ -1645,12 +1625,12 @@
}
}
- private int getFirewallRuleType(int chain, int rule) {
+ // There are only two type of firewall rule: FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY.
+ private boolean isFirewallRuleAllow(int chain, int rule) {
if (rule == NetworkPolicyManager.FIREWALL_RULE_DEFAULT) {
- return getFirewallType(chain) == FIREWALL_ALLOWLIST
- ? INetd.FIREWALL_RULE_DENY : INetd.FIREWALL_RULE_ALLOW;
+ return getFirewallType(chain) == FIREWALL_DENYLIST;
}
- return rule;
+ return rule == INetd.FIREWALL_RULE_ALLOW;
}
private void enforceSystemUid() {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index c062365..b6a0ec4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -98,6 +98,7 @@
import android.util.DisplayMetrics;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
+import android.window.SplashScreen;
import com.android.internal.compat.CompatibilityChangeConfig;
import com.android.internal.util.HexDump;
@@ -178,6 +179,7 @@
private boolean mIsLockTask;
private boolean mAsync;
private BroadcastOptions mBroadcastOptions;
+ private boolean mShowSplashScreen;
final boolean mDumping;
@@ -428,6 +430,8 @@
mBroadcastOptions.setBackgroundActivityStartsAllowed(true);
} else if (opt.equals("--async")) {
mAsync = true;
+ } else if (opt.equals("--splashscreen-show-icon")) {
+ mShowSplashScreen = true;
} else {
return false;
}
@@ -566,6 +570,12 @@
}
options.setLockTaskEnabled(true);
}
+ if (mShowSplashScreen) {
+ if (options == null) {
+ options = ActivityOptions.makeBasic();
+ }
+ options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
+ }
if (mWaitOption) {
result = mInternal.startActivityAndWait(null, SHELL_PACKAGE_NAME, null, intent,
mimeType, null, null, 0, mStartFlags, profilerInfo,
@@ -3301,6 +3311,7 @@
pw.println(" --windowingMode <WINDOWING_MODE>: The windowing mode to launch the activity into.");
pw.println(" --activityType <ACTIVITY_TYPE>: The activity type to launch the activity as.");
pw.println(" --display <DISPLAY_ID>: The display to launch the activity into.");
+ pw.println(" --splashscreen-icon: Show the splash screen icon on launch.");
pw.println(" start-service [--user <USER_ID> | current] <INTENT>");
pw.println(" Start a Service. Options are:");
pw.println(" --user <USER_ID> | current: Specify which user to run as; if not");
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index e36ea20..bb939b7 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -67,6 +67,7 @@
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import java.io.FileDescriptor;
@@ -1832,7 +1833,7 @@
private boolean noteOpForManifestReceiver(int appOp, BroadcastRecord r, ResolveInfo info,
ComponentName component) {
- if (info.activityInfo.attributionTags == null) {
+ if (ArrayUtils.isEmpty(info.activityInfo.attributionTags)) {
return noteOpForManifestReceiverInner(appOp, r, info, component, null);
} else {
// Attribution tags provided, noteOp each tag
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 3c5b872..df792ee2 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -1046,6 +1046,16 @@
userId = UserHandle.getCallingUserId();
}
+ if (isAuthorityRedirectedForCloneProfile(authority)) {
+ UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
+ UserInfo userInfo = umInternal.getUserInfo(userId);
+
+ if (userInfo != null && userInfo.isCloneProfile()) {
+ userId = umInternal.getProfileParentId(userId);
+ checkUser = false;
+ }
+ }
+
ProviderInfo cpi = null;
try {
cpi = AppGlobals.getPackageManager().resolveContentProvider(authority,
@@ -1055,17 +1065,6 @@
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
userId);
- if (cpi == null && isAuthorityRedirectedForCloneProfile(authority)) {
- // This might be a provider that's running only in the system user that's
- // also serving clone profiles
- cpi = AppGlobals.getPackageManager().resolveContentProvider(authority,
- ActivityManagerService.STOCK_PM_FLAGS
- | PackageManager.GET_URI_PERMISSION_PATTERNS
- | PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- UserHandle.USER_SYSTEM);
- }
} catch (RemoteException ignored) {
}
if (cpi == null) {
@@ -1073,16 +1072,6 @@
+ "; expected to find a valid ContentProvider for this authority";
}
- if (isAuthorityRedirectedForCloneProfile(authority)) {
- UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
- UserInfo userInfo = umInternal.getUserInfo(userId);
-
- if (userInfo != null && userInfo.isCloneProfile()) {
- userId = umInternal.getProfileParentId(userId);
- checkUser = false;
- }
- }
-
final int callingPid = Binder.getCallingPid();
ProcessRecord r;
final String appName;
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index f5f7bb3..417ebcd 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -938,6 +938,19 @@
}
}
+ /**
+ * Remove frame rate override due to mode switch
+ */
+ private void resetFps(String packageName, @UserIdInt int userId) {
+ try {
+ final float fps = 0.0f;
+ final int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
+ nativeSetOverrideFrameRate(uid, fps);
+ } catch (PackageManager.NameNotFoundException e) {
+ return;
+ }
+ }
+
private void enableCompatScale(String packageName, long scaleId) {
final long uid = Binder.clearCallingIdentity();
try {
@@ -1030,6 +1043,7 @@
if (gameMode == GameManager.GAME_MODE_STANDARD
|| gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
disableCompatScale(packageName);
+ resetFps(packageName, userId);
return;
}
GamePackageConfiguration packageConfig = null;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 565e295..65be5f0 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -59,6 +59,7 @@
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -1794,4 +1795,10 @@
}
return null;
}
+
+ UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
+ synchronized (mDeviceStateLock) {
+ return mDeviceInventory.getDeviceSensorUuid(device);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 2dd6bf5..f5a529b 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -48,7 +48,9 @@
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
+import java.util.UUID;
/**
* Class to manage the inventory of all connected devices.
@@ -185,12 +187,20 @@
final @NonNull String mDeviceName;
final @NonNull String mDeviceAddress;
int mDeviceCodecFormat;
+ final UUID mSensorUuid;
- DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) {
+ DeviceInfo(int deviceType, String deviceName, String deviceAddress,
+ int deviceCodecFormat, UUID sensorUuid) {
mDeviceType = deviceType;
mDeviceName = deviceName == null ? "" : deviceName;
mDeviceAddress = deviceAddress == null ? "" : deviceAddress;
mDeviceCodecFormat = deviceCodecFormat;
+ mSensorUuid = sensorUuid;
+ }
+
+ DeviceInfo(int deviceType, String deviceName, String deviceAddress,
+ int deviceCodecFormat) {
+ this(deviceType, deviceName, deviceAddress, deviceCodecFormat, null);
}
@Override
@@ -199,7 +209,8 @@
+ " (" + AudioSystem.getDeviceName(mDeviceType)
+ ") name:" + mDeviceName
+ " addr:" + mDeviceAddress
- + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]";
+ + " codec: " + Integer.toHexString(mDeviceCodecFormat)
+ + " sensorUuid: " + Objects.toString(mSensorUuid) + "]";
}
@NonNull String getKey() {
@@ -980,8 +991,13 @@
// Reset A2DP suspend state each time a new sink is connected
mAudioSystem.setParameters("A2dpSuspended=false");
+ // The convention for head tracking sensors associated with A2DP devices is to
+ // use a UUID derived from the MAC address as follows:
+ // time_low = 0, time_mid = 0, time_hi = 0, clock_seq = 0, node = MAC Address
+ UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(
+ new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
- address, a2dpCodec);
+ address, a2dpCodec, sensorUuid);
final String diKey = di.getKey();
mConnectedDevices.put(diKey, di);
// on a connection always overwrite the device seen by AudioPolicy, see comment above when
@@ -1456,6 +1472,17 @@
mDevRoleCapturePresetDispatchers.finishBroadcast();
}
+ UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
+ final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
+ device.getAddress());
+ synchronized (mDevicesLock) {
+ DeviceInfo di = mConnectedDevices.get(key);
+ if (di == null) {
+ return null;
+ }
+ return di.mSensorUuid;
+ }
+ }
//----------------------------------------------------------
// For tests only
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0ea936e..a853ac5 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -190,6 +190,7 @@
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -11017,6 +11018,10 @@
return delayMillis;
}
+ UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
+ return mDeviceBroker.getDeviceSensorUuid(device);
+ }
+
//======================
// misc
//======================
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index e6789d5..106cbba 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -43,6 +43,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
+import java.util.UUID;
/**
* A helper class to manage Spatializer related functionality
@@ -168,6 +169,7 @@
return;
}
mState = STATE_DISABLED_UNAVAILABLE;
+ mASA.getDevicesForAttributes(DEFAULT_ATTRIBUTES).toArray(ROUTING_DEVICES);
// note at this point mSpat is still not instantiated
}
@@ -204,6 +206,11 @@
logd("onRoutingUpdated: can spatialize media 5.1:" + able
+ " on device:" + ROUTING_DEVICES[0]);
setDispatchAvailableState(able);
+
+ if (mDesiredHeadTrackingMode != Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED
+ && mDesiredHeadTrackingMode != Spatializer.HEAD_TRACKING_MODE_DISABLED) {
+ postInitSensors();
+ }
}
//------------------------------------------------------
@@ -909,12 +916,21 @@
}
}
// initialize sensor handles
- // TODO-HT update to non-private sensor once head tracker sensor is defined
- for (Sensor sensor : mSensorManager.getDynamicSensorList(
- Sensor.TYPE_DEVICE_PRIVATE_BASE)) {
- if (sensor.getStringType().equals(HEADTRACKER_SENSOR)) {
- headHandle = sensor.getHandle();
- break;
+ UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(ROUTING_DEVICES[0]);
+ List<Sensor> sensors = new ArrayList<Sensor>(0);
+ sensors.addAll(mSensorManager.getDynamicSensorList(Sensor.TYPE_HEAD_TRACKER));
+ sensors.addAll(mSensorManager.getDynamicSensorList(Sensor.TYPE_DEVICE_PRIVATE_BASE));
+ for (Sensor sensor : sensors) {
+ if (sensor.getType() == Sensor.TYPE_HEAD_TRACKER
+ || sensor.getStringType().equals(HEADTRACKER_SENSOR)) {
+ UUID uuid = sensor.getUuid();
+ if (uuid.equals(routingDeviceUuid)) {
+ headHandle = sensor.getHandle();
+ break;
+ }
+ if (uuid.equals(UuidUtils.STANDALONE_UUID)) {
+ headHandle = sensor.getHandle();
+ }
}
}
Sensor screenSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
diff --git a/services/core/java/com/android/server/audio/UuidUtils.java b/services/core/java/com/android/server/audio/UuidUtils.java
new file mode 100644
index 0000000..2003619
--- /dev/null
+++ b/services/core/java/com/android/server/audio/UuidUtils.java
@@ -0,0 +1,70 @@
+/*
+ * 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.audio;
+
+import android.media.AudioDeviceAttributes;
+import android.media.AudioSystem;
+import android.util.Slog;
+
+import java.util.UUID;
+
+/**
+ * UuidUtils class implements helper functions to handle unique identifiers
+ * used to associate head tracking sensors to audio devices.
+ */
+class UuidUtils {
+ private static final String TAG = "AudioService.UuidUtils";
+
+ private static final long LSB_PREFIX_MASK = 0xFFFF000000000000L;
+ private static final long LSB_SUFFIX_MASK = 0x0000FFFFFFFFFFFFL;
+ // The sensor UUID for Bluetooth devices is defined as follows:
+ // - 8 most significant bytes: All 0s
+ // - 8 most significant bytes: Ascii B, Ascii T, Device MAC address on 6 bytes
+ private static final long LSB_PREFIX_BT = 0x4254000000000000L;
+
+ /**
+ * Special UUID for a head tracking sensor not associated with an audio device.
+ */
+ public static final UUID STANDALONE_UUID = new UUID(0, 0);
+
+ /**
+ * Generate a headtracking UUID from AudioDeviceAttributes
+ */
+ public static UUID uuidFromAudioDeviceAttributes(AudioDeviceAttributes device) {
+ switch (device.getInternalType()) {
+ case AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP:
+ String address = device.getAddress().replace(":", "");
+ if (address.length() != 12) {
+ return null;
+ }
+ address = "0x" + address;
+ long lsb = LSB_PREFIX_BT;
+ try {
+ lsb |= Long.decode(address).longValue();
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ if (AudioService.DEBUG_DEVICES) {
+ Slog.i(TAG, "uuidFromAudioDeviceAttributes lsb: " + Long.toHexString(lsb));
+ }
+ return new UUID(0, lsb);
+ default:
+ // Handle other device types here
+ return null;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index fce6737..a5024ff 100644
--- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -18,7 +18,6 @@
import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER;
import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY;
-import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
@@ -409,8 +408,8 @@
private void registerUsageCallback(long budget) {
maybeUnregisterUsageCallback();
- mStatsManager.registerUsageCallback(mNetworkTemplate, TYPE_MOBILE, budget,
- mUsageCallback, mHandler);
+ mStatsManager.registerUsageCallback(mNetworkTemplate, budget,
+ (command) -> mHandler.post(command), mUsageCallback);
mMultipathBudget = budget;
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 709af91..c2ca3a5 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -51,7 +51,6 @@
import com.android.server.DisplayThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
-import com.android.server.policy.DeviceStatePolicyImpl;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowProcessController;
@@ -142,7 +141,9 @@
private final SparseArray<ProcessRecord> mProcessRecords = new SparseArray<>();
public DeviceStateManagerService(@NonNull Context context) {
- this(context, new DeviceStatePolicyImpl(context));
+ this(context, DeviceStatePolicy.Provider
+ .fromResources(context.getResources())
+ .instantiate(context));
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java b/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
index 274b8e5..5c4e2f3 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
@@ -17,6 +17,11 @@
package com.android.server.devicestate;
import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Resources;
+import android.text.TextUtils;
+
+import com.android.server.policy.DeviceStatePolicyImpl;
/**
* Interface for the component responsible for supplying the current device state as well as
@@ -24,9 +29,15 @@
*
* @see DeviceStateManagerService
*/
-public interface DeviceStatePolicy {
+public abstract class DeviceStatePolicy {
+ protected final Context mContext;
+
+ protected DeviceStatePolicy(@NonNull Context context) {
+ mContext = context;
+ }
+
/** Returns the provider of device states. */
- DeviceStateProvider getDeviceStateProvider();
+ public abstract DeviceStateProvider getDeviceStateProvider();
/**
* Configures the system into the provided state. Guaranteed not to be called again until the
@@ -36,5 +47,58 @@
* @param onComplete a callback that must be triggered once the system has been properly
* configured to match the supplied state.
*/
- void configureDeviceForState(int state, @NonNull Runnable onComplete);
+ public abstract void configureDeviceForState(int state, @NonNull Runnable onComplete);
+
+ /** Provider for platform-default device state policy. */
+ static final class DefaultProvider implements DeviceStatePolicy.Provider {
+ @Override
+ public DeviceStatePolicy instantiate(@NonNull Context context) {
+ return new DeviceStatePolicyImpl(context);
+ }
+ }
+
+ /**
+ * Provider for {@link DeviceStatePolicy} instances.
+ *
+ * <p>By implementing this interface and overriding the
+ * {@code config_deviceSpecificDeviceStatePolicyProvider}, a device-specific implementations
+ * of {@link DeviceStatePolicy} can be supplied.
+ */
+ public interface Provider {
+ /**
+ * Instantiates a new {@link DeviceStatePolicy}.
+ *
+ * @see DeviceStatePolicy#DeviceStatePolicy
+ */
+ DeviceStatePolicy instantiate(@NonNull Context context);
+
+ /**
+ * Instantiates the device-specific {@link DeviceStatePolicy.Provider}.
+ *
+ * Checks the {@code config_deviceSpecificDeviceStatePolicyProvider} resource to see if
+ * a device specific policy provider has been supplied. If so, returns an instance of that
+ * provider. If there is no value provided then the method returns the
+ * {@link DeviceStatePolicy.DefaultProvider}.
+ *
+ * An {@link IllegalStateException} is thrown if there is a value provided for that
+ * resource, but it doesn't correspond to a class that is found.
+ */
+ static Provider fromResources(@NonNull Resources res) {
+ final String name = res.getString(
+ com.android.internal.R.string.config_deviceSpecificDeviceStatePolicyProvider);
+ if (TextUtils.isEmpty(name)) {
+ return new DeviceStatePolicy.DefaultProvider();
+ }
+
+ try {
+ return (DeviceStatePolicy.Provider) Class.forName(name).newInstance();
+ } catch (ReflectiveOperationException | ClassCastException e) {
+ throw new IllegalStateException("Couldn't instantiate class " + name
+ + " for config_deviceSpecificDeviceStatePolicyProvider:"
+ + " make sure it has a public zero-argument constructor"
+ + " and implements DeviceStatePolicy.Provider", e);
+ }
+ }
+ }
+
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 3df2422..f3969b1 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -32,6 +32,7 @@
import com.android.internal.R;
import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.config.BrightnessThresholds;
import com.android.server.display.config.Density;
import com.android.server.display.config.DisplayConfiguration;
import com.android.server.display.config.DisplayQuirks;
@@ -42,6 +43,7 @@
import com.android.server.display.config.RefreshRateRange;
import com.android.server.display.config.SensorDetails;
import com.android.server.display.config.ThermalStatus;
+import com.android.server.display.config.Thresholds;
import com.android.server.display.config.XmlParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -130,6 +132,10 @@
private float mBrightnessRampSlowIncrease = Float.NaN;
private int mAmbientHorizonLong = AMBIENT_LIGHT_LONG_HORIZON_MILLIS;
private int mAmbientHorizonShort = AMBIENT_LIGHT_SHORT_HORIZON_MILLIS;
+ private float mScreenBrighteningMinThreshold = 0.0f; // Retain behaviour as though there is
+ private float mScreenDarkeningMinThreshold = 0.0f; // no minimum threshold for change in
+ private float mAmbientLuxBrighteningMinThreshold = 0.0f; // screen brightness or ambient
+ private float mAmbientLuxDarkeningMinThreshold = 0.0f; // brightness.
private Spline mBrightnessToBacklightSpline;
private Spline mBacklightToBrightnessSpline;
private Spline mBacklightToNitsSpline;
@@ -364,6 +370,22 @@
return mAmbientHorizonShort;
}
+ public float getScreenBrighteningMinThreshold() {
+ return mScreenBrighteningMinThreshold;
+ }
+
+ public float getScreenDarkeningMinThreshold() {
+ return mScreenDarkeningMinThreshold;
+ }
+
+ public float getAmbientLuxBrighteningMinThreshold() {
+ return mAmbientLuxBrighteningMinThreshold;
+ }
+
+ public float getAmbientLuxDarkeningMinThreshold() {
+ return mAmbientLuxDarkeningMinThreshold;
+ }
+
SensorData getAmbientLightSensor() {
return mAmbientLightSensor;
}
@@ -425,6 +447,10 @@
+ ", mBrightnessRampSlowIncrease=" + mBrightnessRampSlowIncrease
+ ", mAmbientHorizonLong=" + mAmbientHorizonLong
+ ", mAmbientHorizonShort=" + mAmbientHorizonShort
+ + ", mScreenDarkeningMinThreshold=" + mScreenDarkeningMinThreshold
+ + ", mScreenBrighteningMinThreshold=" + mScreenBrighteningMinThreshold
+ + ", mAmbientLuxDarkeningMinThreshold=" + mAmbientLuxDarkeningMinThreshold
+ + ", mAmbientLuxBrighteningMinThreshold=" + mAmbientLuxBrighteningMinThreshold
+ ", mAmbientLightSensor=" + mAmbientLightSensor
+ ", mProximitySensor=" + mProximitySensor
+ ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray())
@@ -482,6 +508,7 @@
loadAmbientLightSensorFromDdc(config);
loadProxSensorFromDdc(config);
loadAmbientHorizonFromDdc(config);
+ loadBrightnessChangeThresholds(config);
} else {
Slog.w(TAG, "DisplayDeviceConfig file is null");
}
@@ -865,6 +892,45 @@
}
}
+ private void loadBrightnessChangeThresholds(DisplayConfiguration config) {
+ Thresholds displayBrightnessThresholds = config.getDisplayBrightnessChangeThresholds();
+ Thresholds ambientBrightnessThresholds = config.getAmbientBrightnessChangeThresholds();
+
+ if (displayBrightnessThresholds != null) {
+ BrightnessThresholds brighteningScreen =
+ displayBrightnessThresholds.getBrighteningThresholds();
+ BrightnessThresholds darkeningScreen =
+ displayBrightnessThresholds.getDarkeningThresholds();
+
+ final BigDecimal screenBrighteningThreshold = brighteningScreen.getMinimum();
+ final BigDecimal screenDarkeningThreshold = darkeningScreen.getMinimum();
+
+ if (screenBrighteningThreshold != null) {
+ mScreenBrighteningMinThreshold = screenBrighteningThreshold.floatValue();
+ }
+ if (screenDarkeningThreshold != null) {
+ mScreenDarkeningMinThreshold = screenDarkeningThreshold.floatValue();
+ }
+ }
+
+ if (ambientBrightnessThresholds != null) {
+ BrightnessThresholds brighteningAmbientLux =
+ ambientBrightnessThresholds.getBrighteningThresholds();
+ BrightnessThresholds darkeningAmbientLux =
+ ambientBrightnessThresholds.getDarkeningThresholds();
+
+ final BigDecimal ambientBrighteningThreshold = brighteningAmbientLux.getMinimum();
+ final BigDecimal ambientDarkeningThreshold = darkeningAmbientLux.getMinimum();
+
+ if (ambientBrighteningThreshold != null) {
+ mAmbientLuxBrighteningMinThreshold = ambientBrighteningThreshold.floatValue();
+ }
+ if (ambientDarkeningThreshold != null) {
+ mAmbientLuxDarkeningMinThreshold = ambientDarkeningThreshold.floatValue();
+ }
+ }
+ }
+
private @PowerManager.ThermalStatus int convertThermalStatus(ThermalStatus value) {
if (value == null) {
return PowerManager.THERMAL_STATUS_NONE;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index c6d3829..31c496ed 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -909,9 +909,14 @@
com.android.internal.R.array.config_ambientDarkeningThresholds);
int[] ambientThresholdLevels = resources.getIntArray(
com.android.internal.R.array.config_ambientThresholdLevels);
+ float ambientDarkeningMinThreshold =
+ mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
+ float ambientBrighteningMinThreshold =
+ mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
HysteresisLevels ambientBrightnessThresholds = new HysteresisLevels(
ambientBrighteningThresholds, ambientDarkeningThresholds,
- ambientThresholdLevels);
+ ambientThresholdLevels, ambientDarkeningMinThreshold,
+ ambientBrighteningMinThreshold);
int[] screenBrighteningThresholds = resources.getIntArray(
com.android.internal.R.array.config_screenBrighteningThresholds);
@@ -919,8 +924,13 @@
com.android.internal.R.array.config_screenDarkeningThresholds);
int[] screenThresholdLevels = resources.getIntArray(
com.android.internal.R.array.config_screenThresholdLevels);
+ float screenDarkeningMinThreshold =
+ mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
+ float screenBrighteningMinThreshold =
+ mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
HysteresisLevels screenBrightnessThresholds = new HysteresisLevels(
- screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels);
+ screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
+ screenDarkeningMinThreshold, screenBrighteningMinThreshold);
long brighteningLightDebounce = resources.getInteger(
com.android.internal.R.integer.config_autoBrightnessBrighteningLightDebounce);
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index 2b56569..7a932ce 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -30,17 +30,13 @@
public class HysteresisLevels {
private static final String TAG = "HysteresisLevels";
- // Default hysteresis constraints for brightening or darkening.
- // The recent value must have changed by at least this fraction relative to the
- // current value before a change will be considered.
- private static final float DEFAULT_BRIGHTENING_HYSTERESIS = 0.10f;
- private static final float DEFAULT_DARKENING_HYSTERESIS = 0.20f;
-
private static final boolean DEBUG = false;
private final float[] mBrighteningThresholds;
private final float[] mDarkeningThresholds;
private final float[] mThresholdLevels;
+ private final float mMinDarkening;
+ private final float mMinBrightening;
/**
* Creates a {@code HysteresisLevels} object with the given equal-length
@@ -48,9 +44,12 @@
* @param brighteningThresholds an array of brightening hysteresis constraint constants.
* @param darkeningThresholds an array of darkening hysteresis constraint constants.
* @param thresholdLevels a monotonically increasing array of threshold levels.
+ * @param minBrighteningThreshold the minimum value for which the brightening value needs to
+ * return.
+ * @param minDarkeningThreshold the minimum value for which the darkening value needs to return.
*/
HysteresisLevels(int[] brighteningThresholds, int[] darkeningThresholds,
- int[] thresholdLevels) {
+ int[] thresholdLevels, float minDarkeningThreshold, float minBrighteningThreshold) {
if (brighteningThresholds.length != darkeningThresholds.length
|| darkeningThresholds.length != thresholdLevels.length + 1) {
throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
@@ -58,6 +57,8 @@
mBrighteningThresholds = setArrayFormat(brighteningThresholds, 1000.0f);
mDarkeningThresholds = setArrayFormat(darkeningThresholds, 1000.0f);
mThresholdLevels = setArrayFormat(thresholdLevels, 1.0f);
+ mMinDarkening = minDarkeningThreshold;
+ mMinBrightening = minBrighteningThreshold;
}
/**
@@ -65,11 +66,13 @@
*/
public float getBrighteningThreshold(float value) {
final float brightConstant = getReferenceLevel(value, mBrighteningThresholds);
- final float brightThreshold = value * (1.0f + brightConstant);
+ float brightThreshold = value * (1.0f + brightConstant);
if (DEBUG) {
Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold="
+ brightThreshold + ", value=" + value);
}
+
+ brightThreshold = Math.max(brightThreshold, value + mMinBrightening);
return brightThreshold;
}
@@ -78,12 +81,13 @@
*/
public float getDarkeningThreshold(float value) {
final float darkConstant = getReferenceLevel(value, mDarkeningThresholds);
- final float darkThreshold = value * (1.0f - darkConstant);
+ float darkThreshold = value * (1.0f - darkConstant);
if (DEBUG) {
Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold="
+ darkThreshold + ", value=" + value);
}
- return darkThreshold;
+ darkThreshold = Math.min(darkThreshold, value - mMinDarkening);
+ return Math.max(darkThreshold, 0.0f);
}
/**
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 4de39bc..b9c7123 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -16,6 +16,8 @@
package com.android.server.input;
+import static android.view.KeyEvent.KEYCODE_UNKNOWN;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
@@ -289,6 +291,8 @@
int deviceId, int sourceMask, int sw);
private static native boolean nativeHasKeys(long ptr,
int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
+ private static native int nativeGetKeyCodeForKeyLocation(long ptr, int deviceId,
+ int locationKeyCode);
private static native InputChannel nativeCreateInputChannel(long ptr, String name);
private static native InputChannel nativeCreateInputMonitor(long ptr, int displayId,
boolean isGestureMonitor, String name, int pid);
@@ -313,6 +317,7 @@
IBinder fromChannelToken, IBinder toChannelToken, boolean isDragDrop);
private static native boolean nativeTransferTouch(long ptr, IBinder destChannelToken);
private static native void nativeSetPointerSpeed(long ptr, int speed);
+ private static native void nativeSetPointerAcceleration(long ptr, float acceleration);
private static native void nativeSetShowTouches(long ptr, boolean enabled);
private static native void nativeSetInteractive(long ptr, boolean interactive);
private static native void nativeReloadCalibration(long ptr);
@@ -666,6 +671,22 @@
}
/**
+ * Returns the keyCode generated by the specified location on a US keyboard layout.
+ * This takes into consideration the currently active keyboard layout.
+ *
+ * @param deviceId The input device id.
+ * @param locationKeyCode The location of a key on a US keyboard layout.
+ * @return The KeyCode this physical key location produces.
+ */
+ @Override // Binder call
+ public int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode) {
+ if (locationKeyCode <= KEYCODE_UNKNOWN || locationKeyCode > KeyEvent.getMaxKeyCode()) {
+ return KEYCODE_UNKNOWN;
+ }
+ return nativeGetKeyCodeForKeyLocation(mPtr, deviceId, locationKeyCode);
+ }
+
+ /**
* Transfer the current touch gesture to the provided window.
*
* @param destChannelToken The token of the window or input channel that should receive the
@@ -1777,6 +1798,10 @@
nativeSetPointerSpeed(mPtr, speed);
}
+ private void setPointerAcceleration(float acceleration) {
+ nativeSetPointerAcceleration(mPtr, acceleration);
+ }
+
private void registerPointerSpeedSettingObserver() {
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.POINTER_SPEED), true,
@@ -3467,6 +3492,11 @@
}
@Override
+ public void setPointerAcceleration(float acceleration) {
+ InputManagerService.this.setPointerAcceleration(acceleration);
+ }
+
+ @Override
public void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) {
InputManagerService.this.setDisplayEligibilityForPointerCapture(displayId, isEligible);
}
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
new file mode 100644
index 0000000..c86ebd2
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -0,0 +1,224 @@
+/*
+ * 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.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Binder;
+import android.os.DeadObjectException;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.util.Slog;
+import android.view.InputChannel;
+import android.view.MotionEvent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputBinding;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethod;
+import com.android.internal.view.IInputMethodSession;
+import com.android.internal.view.IInputSessionCallback;
+import com.android.internal.view.InlineSuggestionsRequestInfo;
+
+import java.util.List;
+
+/**
+ * A wrapper class to invoke IPCs defined in {@link IInputMethod}.
+ */
+final class IInputMethodInvoker {
+ private static final String TAG = InputMethodManagerService.TAG;
+ private static final boolean DEBUG = InputMethodManagerService.DEBUG;
+
+ @AnyThread
+ @Nullable
+ static IInputMethodInvoker create(@Nullable IInputMethod inputMethod) {
+ if (inputMethod == null) {
+ return null;
+ }
+ if (!Binder.isProxy(inputMethod)) {
+ // IInputMethodInvoker must be used only within the system_server and InputMethodService
+ // must not be running in the system_server. Therefore, "inputMethod" must be a Proxy.
+ throw new UnsupportedOperationException(inputMethod + " must have been a BinderProxy.");
+ }
+ return new IInputMethodInvoker(inputMethod);
+ }
+
+ /**
+ * A simplified version of {@link android.os.Debug#getCaller()}.
+ *
+ * @return method name of the caller.
+ */
+ @AnyThread
+ private static String getCallerMethodName() {
+ final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
+ if (callStack.length <= 4) {
+ return "<bottom of call stack>";
+ }
+ return callStack[4].getMethodName();
+ }
+
+ @AnyThread
+ private static void logRemoteException(@NonNull RemoteException e) {
+ if (DEBUG || !(e instanceof DeadObjectException)) {
+ Slog.w(TAG, "IPC failed at IInputMethodInvoker#" + getCallerMethodName(), e);
+ }
+ }
+
+ @AnyThread
+ static int getBinderIdentityHashCode(@Nullable IInputMethodInvoker obj) {
+ if (obj == null) {
+ return 0;
+ }
+
+ return System.identityHashCode(obj.mTarget);
+ }
+
+ @NonNull
+ private final IInputMethod mTarget;
+
+ private IInputMethodInvoker(@NonNull IInputMethod target) {
+ mTarget = target;
+ }
+
+ @AnyThread
+ @NonNull
+ IBinder asBinder() {
+ return mTarget.asBinder();
+ }
+
+ @AnyThread
+ void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
+ int configChanges, boolean stylusHwSupported) {
+ try {
+ mTarget.initializeInternal(token, privOps, configChanges, stylusHwSupported);
+ } catch (RemoteException e) {
+ logRemoteException(e);
+ }
+ }
+
+ @AnyThread
+ void onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo,
+ IInlineSuggestionsRequestCallback cb) {
+ try {
+ mTarget.onCreateInlineSuggestionsRequest(requestInfo, cb);
+ } catch (RemoteException e) {
+ logRemoteException(e);
+ }
+ }
+
+ @AnyThread
+ void bindInput(InputBinding binding) {
+ try {
+ mTarget.bindInput(binding);
+ } catch (RemoteException e) {
+ logRemoteException(e);
+ }
+ }
+
+ @AnyThread
+ void unbindInput() {
+ try {
+ mTarget.unbindInput();
+ } catch (RemoteException e) {
+ logRemoteException(e);
+ }
+ }
+
+ @AnyThread
+ void startInput(IBinder startInputToken, IInputContext inputContext, EditorInfo attribute,
+ boolean restarting) {
+ try {
+ mTarget.startInput(startInputToken, inputContext, attribute, restarting);
+ } catch (RemoteException e) {
+ logRemoteException(e);
+ }
+ }
+
+ @AnyThread
+ void createSession(InputChannel channel, IInputSessionCallback callback) {
+ try {
+ mTarget.createSession(channel, callback);
+ } catch (RemoteException e) {
+ logRemoteException(e);
+ }
+ }
+
+ @AnyThread
+ void setSessionEnabled(IInputMethodSession session, boolean enabled) {
+ try {
+ mTarget.setSessionEnabled(session, enabled);
+ } catch (RemoteException e) {
+ logRemoteException(e);
+ }
+ }
+
+ // TODO(b/192412909): Convert this back to void method
+ @AnyThread
+ boolean showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) {
+ try {
+ mTarget.showSoftInput(showInputToken, flags, resultReceiver);
+ } catch (RemoteException e) {
+ logRemoteException(e);
+ return false;
+ }
+ return true;
+ }
+
+ // TODO(b/192412909): Convert this back to void method
+ @AnyThread
+ boolean hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) {
+ try {
+ mTarget.hideSoftInput(hideInputToken, flags, resultReceiver);
+ } catch (RemoteException e) {
+ logRemoteException(e);
+ return false;
+ }
+ return true;
+ }
+
+ @AnyThread
+ void changeInputMethodSubtype(InputMethodSubtype subtype) {
+ try {
+ mTarget.changeInputMethodSubtype(subtype);
+ } catch (RemoteException e) {
+ logRemoteException(e);
+ }
+ }
+
+ @AnyThread
+ void canStartStylusHandwriting(int requestId) {
+ try {
+ mTarget.canStartStylusHandwriting(requestId);
+ } catch (RemoteException e) {
+ logRemoteException(e);
+ }
+ }
+
+ @AnyThread
+ void startStylusHandwriting(InputChannel channel, List<MotionEvent> events) {
+ try {
+ mTarget.startStylusHandwriting(channel, events);
+ } catch (RemoteException e) {
+ logRemoteException(e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 784d058..2230dcd 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -75,7 +75,7 @@
@GuardedBy("ImfLock.class") @Nullable private String mCurId;
@GuardedBy("ImfLock.class") @Nullable private String mSelectedMethodId;
@GuardedBy("ImfLock.class") @Nullable private Intent mCurIntent;
- @GuardedBy("ImfLock.class") @Nullable private IInputMethod mCurMethod;
+ @GuardedBy("ImfLock.class") @Nullable private IInputMethodInvoker mCurMethod;
@GuardedBy("ImfLock.class") private int mCurMethodUid = Process.INVALID_UID;
@GuardedBy("ImfLock.class") private IBinder mCurToken;
@GuardedBy("ImfLock.class") private int mCurSeq;
@@ -241,7 +241,7 @@
*/
@GuardedBy("ImfLock.class")
@Nullable
- IInputMethod getCurMethod() {
+ IInputMethodInvoker getCurMethod() {
return mCurMethod;
}
@@ -298,7 +298,7 @@
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onServiceConnected");
synchronized (ImfLock.class) {
if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
- mCurMethod = IInputMethod.Stub.asInterface(service);
+ mCurMethod = IInputMethodInvoker.create(IInputMethod.Stub.asInterface(service));
updateCurrentMethodUid();
if (mCurToken == null) {
Slog.w(TAG, "Service connected without a token!");
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodDialogWindowContext.java b/services/core/java/com/android/server/inputmethod/InputMethodDialogWindowContext.java
new file mode 100644
index 0000000..1779333
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodDialogWindowContext.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.inputmethod;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.view.ContextThemeWrapper;
+import android.view.WindowManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Provides the window context for the IME switcher dialog.
+ */
+@VisibleForTesting(visibility = PACKAGE)
+public final class InputMethodDialogWindowContext {
+ @Nullable
+ private Context mDialogWindowContext;
+
+ /**
+ * Returns the window context for IME switch dialogs to receive configuration changes.
+ *
+ * This method initializes the window context if it was not initialized, or moves the context to
+ * the targeted display if the current display of context is different from the display
+ * specified by {@code displayId}.
+ */
+ @NonNull
+ @VisibleForTesting(visibility = PACKAGE)
+ public Context get(int displayId) {
+ if (mDialogWindowContext == null || mDialogWindowContext.getDisplayId() != displayId) {
+ final Context systemUiContext = ActivityThread.currentActivityThread()
+ .getSystemUiContext(displayId);
+ final Context windowContext = systemUiContext.createWindowContext(
+ WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG, null /* options */);
+ mDialogWindowContext = new ContextThemeWrapper(
+ windowContext, com.android.internal.R.style.Theme_DeviceDefault_Settings);
+ }
+ return mDialogWindowContext;
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 2a24489..8a6aa0d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -160,14 +160,12 @@
import com.android.internal.inputmethod.UnbindReason;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
-import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.TransferPipe;
import com.android.internal.util.DumpUtils;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInlineSuggestionsResponseCallback;
import com.android.internal.view.IInputContext;
-import com.android.internal.view.IInputMethod;
import com.android.internal.view.IInputMethodClient;
import com.android.internal.view.IInputMethodManager;
import com.android.internal.view.IInputMethodSession;
@@ -271,7 +269,6 @@
final WindowManagerInternal mWindowManagerInternal;
final PackageManagerInternal mPackageManagerInternal;
final InputManagerInternal mInputManagerInternal;
- private final HandlerCaller mCaller;
final boolean mHasFeature;
private final ArrayMap<String, List<InputMethodSubtype>> mAdditionalSubtypeMap =
new ArrayMap<>();
@@ -329,7 +326,7 @@
static class SessionState {
final ClientState client;
- final IInputMethod method;
+ final IInputMethodInvoker method;
IInputMethodSession session;
InputChannel channel;
@@ -338,14 +335,14 @@
public String toString() {
return "SessionState{uid " + client.uid + " pid " + client.pid
+ " method " + Integer.toHexString(
- System.identityHashCode(method))
+ IInputMethodInvoker.getBinderIdentityHashCode(method))
+ " session " + Integer.toHexString(
System.identityHashCode(session))
+ " channel " + channel
+ "}";
}
- SessionState(ClientState _client, IInputMethod _method,
+ SessionState(ClientState _client, IInputMethodInvoker _method,
IInputMethodSession _session, InputChannel _channel) {
client = _client;
method = _method;
@@ -613,7 +610,7 @@
*/
@GuardedBy("ImfLock.class")
@Nullable
- private IInputMethod getCurMethodLocked() {
+ private IInputMethodInvoker getCurMethodLocked() {
return mBindingController.getCurMethod();
}
@@ -692,8 +689,8 @@
/**
* Internal state snapshot when
- * {@link IInputMethod#startInput(IBinder, IInputContext, EditorInfo, boolean)} is about to be
- * called.
+ * {@link com.android.internal.view.IInputMethod#startInput(IBinder, IInputContext, EditorInfo,
+ * boolean)} is about to be called.
*
* <p>Calling that IPC endpoint basically means that
* {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
@@ -1527,8 +1524,8 @@
@Override
public void onUserUnlocking(@NonNull TargetUser user) {
// Called on ActivityManager thread.
- mService.mHandler.sendMessage(mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER,
- /* arg1= */ user.getUserIdentifier(), /* arg2= */ 0));
+ mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER, user.getUserIdentifier(), 0)
+ .sendToTarget();
}
}
@@ -1588,8 +1585,6 @@
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
mImeDisplayValidator = mWindowManagerInternal::getDisplayImePolicy;
- mCaller = new HandlerCaller(context, thread.getLooper(), this::handleMessage,
- true /*asyncHandler*/);
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
mUserManager = mContext.getSystemService(UserManager.class);
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
@@ -1951,18 +1946,14 @@
InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback) {
final InputMethodInfo imi = mMethodMap.get(getSelectedMethodIdLocked());
try {
- IInputMethod curMethod = getCurMethodLocked();
+ IInputMethodInvoker curMethod = getCurMethodLocked();
if (userId == mSettings.getCurrentUserId() && imi != null
&& imi.isInlineSuggestionsEnabled() && curMethod != null) {
final IInlineSuggestionsRequestCallback callbackImpl =
new InlineSuggestionsRequestCallbackDecorator(callback,
imi.getPackageName(), mCurTokenDisplayId, getCurTokenLocked(),
this);
- try {
- curMethod.onCreateInlineSuggestionsRequest(requestInfo, callbackImpl);
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException calling onCreateInlineSuggestionsRequest()", e);
- }
+ curMethod.onCreateInlineSuggestionsRequest(requestInfo, callbackImpl);
} else {
callback.onInlineSuggestionsUnsupported();
}
@@ -2190,13 +2181,9 @@
mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
if (mBoundToMethod) {
mBoundToMethod = false;
- IInputMethod curMethod = getCurMethodLocked();
+ IInputMethodInvoker curMethod = getCurMethodLocked();
if (curMethod != null) {
- try {
- curMethod.unbindInput();
- } catch (RemoteException e) {
- // There is nothing interesting about the method dying.
- }
+ curMethod.unbindInput();
}
}
mCurClient = null;
@@ -2208,6 +2195,24 @@
}
}
+ @NonNull
+ private Message obtainMessageOO(int what, Object arg1, Object arg2) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg1;
+ args.arg2 = arg2;
+ return mHandler.obtainMessage(what, 0, 0, args);
+ }
+
+ @NonNull
+ private Message obtainMessageIIIO(int what, int argi1, int argi2, int argi3, Object arg1) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg1;
+ args.argi1 = argi1;
+ args.argi2 = argi2;
+ args.argi3 = argi3;
+ return mHandler.obtainMessage(what, 0, 0, args);
+ }
+
private void executeOrSendMessage(IInputMethodClient target, Message msg) {
if (target.asBinder() instanceof Binder) {
// This is supposed to be emulating the one-way semantics when the IME client is
@@ -2216,7 +2221,7 @@
// We probably should create a simple wrapper of IInputMethodClient as the first step
// to get rid of executeOrSendMessage() then should prohibit system_server to be the
// IME client for long term.
- mCaller.sendMessage(msg);
+ msg.sendToTarget();
} else {
handleMessage(msg);
msg.recycle();
@@ -2230,19 +2235,15 @@
+ mCurClient.client.asBinder());
if (mBoundToMethod) {
mBoundToMethod = false;
- IInputMethod curMethod = getCurMethodLocked();
+ IInputMethodInvoker curMethod = getCurMethodLocked();
if (curMethod != null) {
- try {
- curMethod.unbindInput();
- } catch (RemoteException e) {
- // There is nothing interesting about the method dying.
- }
+ curMethod.unbindInput();
}
}
scheduleSetActiveToClient(mCurClient, false /* active */, false /* fullscreen */,
false /* reportToImeController */);
- executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
+ executeOrSendMessage(mCurClient.client, mHandler.obtainMessage(
MSG_UNBIND_CLIENT, getSequenceNumberLocked(), unbindClientReason,
mCurClient.client));
mCurClient.sessionRequested = false;
@@ -2285,11 +2286,7 @@
@NonNull
InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) {
if (!mBoundToMethod) {
- IInputMethod curMethod = getCurMethodLocked();
- try {
- curMethod.bindInput(mCurClient.binding);
- } catch (RemoteException e) {
- }
+ getCurMethodLocked().bindInput(mCurClient.binding);
mBoundToMethod = true;
}
@@ -2316,11 +2313,8 @@
}
final SessionState session = mCurClient.curSession;
- try {
- setEnabledSessionLocked(session);
- session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting);
- } catch (RemoteException e) {
- }
+ setEnabledSessionLocked(session);
+ session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting);
if (mShowRequested) {
if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
@@ -2514,28 +2508,26 @@
}
@GuardedBy("ImfLock.class")
- void initializeImeLocked(@NonNull IInputMethod inputMethod, @NonNull IBinder token,
+ void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token,
@android.content.pm.ActivityInfo.Config int configChanges, boolean supportStylusHw) {
if (DEBUG) {
Slog.v(TAG, "Sending attach of token: " + token + " for display: "
+ mCurTokenDisplayId);
}
- try {
- inputMethod.initializeInternal(token,
- new InputMethodPrivilegedOperationsImpl(this, token), configChanges,
- supportStylusHw);
- } catch (RemoteException e) {
- }
+ inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token),
+ configChanges, supportStylusHw);
}
@AnyThread
void scheduleNotifyImeUidToAudioService(int uid) {
- mCaller.removeMessages(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE);
- mCaller.obtainMessageI(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE, uid).sendToTarget();
+ mHandler.removeMessages(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE);
+ mHandler.obtainMessage(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE, uid, 0 /* unused */)
+ .sendToTarget();
}
@BinderThread
- void onSessionCreated(IInputMethod method, IInputMethodSession session, InputChannel channel) {
+ void onSessionCreated(IInputMethodInvoker method, IInputMethodSession session,
+ InputChannel channel) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onSessionCreated");
try {
synchronized (ImfLock.class) {
@@ -2545,7 +2537,7 @@
channel.dispose();
return;
}
- IInputMethod curMethod = getCurMethodLocked();
+ IInputMethodInvoker curMethod = getCurMethodLocked();
if (curMethod != null && method != null
&& curMethod.asBinder() == method.asBinder()) {
if (mCurClient != null) {
@@ -2555,7 +2547,7 @@
InputBindResult res = attachNewInputLocked(
StartInputReason.SESSION_CREATED_BY_IME, true);
if (res.method != null) {
- executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
+ executeOrSendMessage(mCurClient.client, obtainMessageOO(
MSG_BIND_CLIENT, mCurClient.client, res));
}
return;
@@ -2609,7 +2601,7 @@
cs.sessionRequested = true;
- final IInputMethod curMethod = getCurMethodLocked();
+ final IInputMethodInvoker curMethod = getCurMethodLocked();
final IInputSessionCallback.Stub callback = new IInputSessionCallback.Stub() {
@Override
public void sessionCreated(IInputMethodSession session) {
@@ -2624,7 +2616,6 @@
try {
curMethod.createSession(clientChannel, callback);
- } catch (RemoteException e) {
} finally {
// Dispose the channel because the remote proxy will get its own copy when
// unparceled.
@@ -3012,14 +3003,10 @@
}
if (newSubtype != oldSubtype) {
setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
- IInputMethod curMethod = getCurMethodLocked();
+ IInputMethodInvoker curMethod = getCurMethodLocked();
if (curMethod != null) {
- try {
- updateSystemUiLocked(mImeWindowVis, mBackDisposition);
- curMethod.changeInputMethodSubtype(newSubtype);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call changeInputMethodSubtype");
- }
+ updateSystemUiLocked(mImeWindowVis, mBackDisposition);
+ curMethod.changeInputMethodSubtype(newSubtype);
}
}
return;
@@ -3096,13 +3083,9 @@
return;
}
if (DEBUG) Slog.v(TAG, "Client requesting Stylus Handwriting to be started");
- final IInputMethod curMethod = getCurMethodLocked();
+ final IInputMethodInvoker curMethod = getCurMethodLocked();
if (curMethod != null) {
- try {
- curMethod.canStartStylusHandwriting(++mHwRequestId);
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException calling canStartStylusHandwriting(): ", e);
- }
+ curMethod.canStartStylusHandwriting(++mHwRequestId);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -3153,21 +3136,20 @@
}
mBindingController.setCurrentMethodVisible();
- final IInputMethod curMethod = getCurMethodLocked();
+ final IInputMethodInvoker curMethod = getCurMethodLocked();
if (curMethod != null) {
// create a placeholder token for IMS so that IMS cannot inject windows into client app.
Binder showInputToken = new Binder();
mShowRequestWindowMap.put(showInputToken, windowToken);
final int showFlags = getImeShowFlagsLocked();
- try {
- if (DEBUG) {
- Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken
- + ", " + showFlags + ", " + resultReceiver + ") for reason: "
- + InputMethodDebug.softInputDisplayReasonToString(reason));
- }
- curMethod.showSoftInput(showInputToken, showFlags, resultReceiver);
+ if (DEBUG) {
+ Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken
+ + ", " + showFlags + ", " + resultReceiver + ") for reason: "
+ + InputMethodDebug.softInputDisplayReasonToString(reason));
+ }
+ // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
+ if (curMethod.showSoftInput(showInputToken, showFlags, resultReceiver)) {
onShowHideSoftInputRequested(true /* show */, windowToken, reason);
- } catch (RemoteException e) {
}
mInputShown = true;
return true;
@@ -3236,7 +3218,7 @@
// since Android Eclair. That's why we need to accept IMM#hideSoftInput() even when only
// IMMS#InputShown indicates that the software keyboard is shown.
// TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested.
- IInputMethod curMethod = getCurMethodLocked();
+ IInputMethodInvoker curMethod = getCurMethodLocked();
final boolean shouldHideSoftInput = (curMethod != null) && (mInputShown
|| (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
boolean res;
@@ -3252,10 +3234,9 @@
+ ", " + resultReceiver + ") for reason: "
+ InputMethodDebug.softInputDisplayReasonToString(reason));
}
- try {
- curMethod.hideSoftInput(hideInputToken, 0 /* flags */, resultReceiver);
+ // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
+ if (curMethod.hideSoftInput(hideInputToken, 0 /* flags */, resultReceiver)) {
onShowHideSoftInputRequested(false /* show */, windowToken, reason);
- } catch (RemoteException e) {
}
res = true;
} else {
@@ -3671,9 +3652,10 @@
// Always call subtype picker, because subtype picker is a superset of input method
// picker.
- mHandler.sendMessage(mCaller.obtainMessageII(
- MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode,
- (mCurClient != null) ? mCurClient.selfReportedDisplayId : DEFAULT_DISPLAY));
+ final int displayId =
+ (mCurClient != null) ? mCurClient.selfReportedDisplayId : DEFAULT_DISPLAY;
+ mHandler.obtainMessage(MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId)
+ .sendToTarget();
}
}
@@ -3688,8 +3670,8 @@
}
// Always call subtype picker, because subtype picker is a superset of input method
// picker.
- mHandler.sendMessage(mCaller.obtainMessageII(
- MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId));
+ mHandler.obtainMessage(MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId)
+ .sendToTarget();
}
/**
@@ -3946,7 +3928,7 @@
@Override
public void removeImeSurface() {
mContext.enforceCallingPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW, null);
- mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE));
+ mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
}
@Override
@@ -4160,7 +4142,7 @@
}
}
- /** Called right after {@link IInputMethod#showSoftInput}. */
+ /** Called right after {@link com.android.internal.view.IInputMethod#showSoftInput}. */
@GuardedBy("ImfLock.class")
private void onShowHideSoftInputRequested(boolean show, IBinder requestToken,
@SoftInputShowHideReason int reason) {
@@ -4215,19 +4197,13 @@
void setEnabledSessionLocked(SessionState session) {
if (mEnabledSession != session) {
if (mEnabledSession != null && mEnabledSession.session != null) {
- try {
- if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
- mEnabledSession.method.setSessionEnabled(mEnabledSession.session, false);
- } catch (RemoteException e) {
- }
+ if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
+ mEnabledSession.method.setSessionEnabled(mEnabledSession.session, false);
}
mEnabledSession = session;
if (mEnabledSession != null && mEnabledSession.session != null) {
- try {
- if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
- mEnabledSession.method.setSessionEnabled(mEnabledSession.session, true);
- } catch (RemoteException e) {
- }
+ if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
+ mEnabledSession.method.setSessionEnabled(mEnabledSession.session, true);
}
}
}
@@ -4394,12 +4370,8 @@
return;
}
- try {
- // TODO: replace null with actual Channel, MotionEvents
- getCurMethodLocked().startStylusHandwriting(null, null);
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException calling startStylusHandwriting(): ", e);
- }
+ // TODO: replace null with actual Channel, MotionEvents
+ getCurMethodLocked().startStylusHandwriting(null, null);
}
}
@@ -4424,8 +4396,8 @@
private void scheduleSetActiveToClient(ClientState state, boolean active, boolean fullscreen,
boolean reportToImeController) {
- executeOrSendMessage(state.client, mCaller.obtainMessageIIIIO(MSG_SET_ACTIVE,
- active ? 1 : 0, fullscreen ? 1 : 0, reportToImeController ? 1 : 0, 0, state));
+ executeOrSendMessage(state.client, obtainMessageIIIO(MSG_SET_ACTIVE,
+ active ? 1 : 0, fullscreen ? 1 : 0, reportToImeController ? 1 : 0, state));
}
@GuardedBy("ImfLock.class")
@@ -4988,14 +4960,13 @@
@Override
public void removeImeSurface() {
- mService.mHandler.sendMessage(mService.mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE));
+ mService.mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
}
@Override
public void updateImeWindowStatus(boolean disableImeIcon) {
- mService.mHandler.sendMessage(
- mService.mHandler.obtainMessage(MSG_UPDATE_IME_WINDOW_STATUS,
- disableImeIcon ? 1 : 0, 0));
+ mService.mHandler.obtainMessage(MSG_UPDATE_IME_WINDOW_STATUS, disableImeIcon ? 1 : 0, 0)
+ .sendToTarget();
}
}
@@ -5061,8 +5032,9 @@
}
if (mCurClient != null && mCurClient.client != null) {
mInFullscreenMode = fullscreen;
- executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
- MSG_REPORT_FULLSCREEN_MODE, fullscreen ? 1 : 0, mCurClient));
+ executeOrSendMessage(mCurClient.client, mHandler.obtainMessage(
+ MSG_REPORT_FULLSCREEN_MODE, fullscreen ? 1 : 0, 0 /* unused */,
+ mCurClient));
}
}
}
@@ -5132,7 +5104,7 @@
@BinderThread
private void dumpAsStringNoCheck(FileDescriptor fd, PrintWriter pw, String[] args,
boolean isCritical) {
- IInputMethod method;
+ IInputMethodInvoker method;
ClientState client;
ClientState focusedWindowClient;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index fcb1be0..348bb2d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -16,22 +16,19 @@
package com.android.server.inputmethod;
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.server.inputmethod.InputMethodManagerService.DEBUG;
import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
-import android.app.ActivityThread;
+import android.annotation.Nullable;
import android.app.AlertDialog;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
-import android.os.IBinder;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Slog;
-import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -45,7 +42,6 @@
import android.widget.TextView;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
import com.android.server.wm.WindowManagerInternal;
@@ -53,8 +49,7 @@
import java.util.List;
/** A controller to show/hide the input method menu */
-@VisibleForTesting(visibility = PACKAGE)
-public class InputMethodMenuController {
+final class InputMethodMenuController {
private static final String TAG = InputMethodMenuController.class.getSimpleName();
private final InputMethodManagerService mService;
@@ -64,17 +59,18 @@
private final KeyguardManager mKeyguardManager;
private final WindowManagerInternal mWindowManagerInternal;
- private Context mSettingsContext;
private AlertDialog.Builder mDialogBuilder;
private AlertDialog mSwitchingDialog;
- private IBinder mSwitchingDialogToken;
private View mSwitchingDialogTitleView;
private InputMethodInfo[] mIms;
private int[] mSubtypeIds;
private boolean mShowImeWithHardKeyboard;
- @VisibleForTesting(visibility = PACKAGE)
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ private InputMethodDialogWindowContext mDialogWindowContext;
+
public InputMethodMenuController(InputMethodManagerService service) {
mService = service;
mSettings = mService.mSettings;
@@ -132,8 +128,11 @@
}
}
- final Context settingsContext = getSettingsContext(displayId);
- mDialogBuilder = new AlertDialog.Builder(settingsContext);
+ if (mDialogWindowContext == null) {
+ mDialogWindowContext = new InputMethodDialogWindowContext();
+ }
+ final Context dialogWindowContext = mDialogWindowContext.get(displayId);
+ mDialogBuilder = new AlertDialog.Builder(dialogWindowContext);
mDialogBuilder.setOnCancelListener(dialog -> hideInputMethodMenu());
final Context dialogContext = mDialogBuilder.getContext();
@@ -199,7 +198,7 @@
// Use an alternate token for the dialog for that window manager can group the token
// with other IME windows based on type vs. grouping based on whichever token happens
// to get selected by the system later on.
- attrs.token = mSwitchingDialogToken;
+ attrs.token = dialogWindowContext.getWindowContextToken();
attrs.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
attrs.setTitle("Select input method");
w.setAttributes(attrs);
@@ -208,27 +207,6 @@
}
}
- /**
- * Returns the window context for IME switch dialogs to receive configuration changes.
- *
- * This method initializes the window context if it was not initialized. This method also moves
- * the context to the targeted display if the current display of context is different than
- * the display specified by {@code displayId}.
- */
- @VisibleForTesting
- public Context getSettingsContext(int displayId) {
- if (mSettingsContext == null || mSettingsContext.getDisplayId() != displayId) {
- final Context systemUiContext = ActivityThread.currentActivityThread()
- .getSystemUiContext(displayId);
- final Context windowContext = systemUiContext.createWindowContext(
- WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG, null /* options */);
- mSettingsContext = new ContextThemeWrapper(
- windowContext, com.android.internal.R.style.Theme_DeviceDefault_Settings);
- mSwitchingDialogToken = mSettingsContext.getWindowContextToken();
- }
- return mSettingsContext;
- }
-
private boolean isScreenLocked() {
return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked()
&& mKeyguardManager.isKeyguardSecure();
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index 6676987..d48ccd5 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -69,7 +69,7 @@
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageUserStateInternal;
import java.io.ByteArrayInputStream;
import java.io.File;
@@ -598,7 +598,7 @@
0,
0,
null,
- PackageUserState.DEFAULT,
+ PackageUserStateInternal.DEFAULT,
UserHandle.getCallingUserId(),
null);
} catch (Exception e) {
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
new file mode 100644
index 0000000..ff6372ae
--- /dev/null
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.logcat;
+
+import android.content.Context;
+import android.os.ILogd;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.logcat.ILogcatManagerService;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Service responsible for manage the access to Logcat.
+ */
+public final class LogcatManagerService extends SystemService {
+
+ private static final String TAG = "LogcatManagerService";
+ private final Context mContext;
+ private final BinderService mBinderService;
+ private final ExecutorService mThreadExecutor;
+ private ILogd mLogdService;
+
+ private final class BinderService extends ILogcatManagerService.Stub {
+ @Override
+ public void startThread(int uid, int gid, int pid, int fd) {
+ mThreadExecutor.execute(new LogdMonitor(uid, gid, pid, fd, true));
+ }
+
+ @Override
+ public void finishThread(int uid, int gid, int pid, int fd) {
+ // TODO This thread will be used to notify the AppOpsManager that
+ // the logd data access is finished.
+ mThreadExecutor.execute(new LogdMonitor(uid, gid, pid, fd, false));
+ }
+ }
+
+ private class LogdMonitor implements Runnable {
+
+ private final int mUid;
+ private final int mGid;
+ private final int mPid;
+ private final int mFd;
+ private final boolean mStart;
+
+ /**
+ * For starting a thread, the start value is true.
+ * For finishing a thread, the start value is false.
+ */
+ LogdMonitor(int uid, int gid, int pid, int fd, boolean start) {
+ mUid = uid;
+ mGid = gid;
+ mPid = pid;
+ mFd = fd;
+ mStart = start;
+ }
+
+ /**
+ * The current version grant the permission by default.
+ * And track the logd access.
+ * The next version will generate a prompt for users.
+ * The users decide whether the logd access is allowed.
+ */
+ @Override
+ public void run() {
+ if (mLogdService == null) {
+ LogcatManagerService.this.addLogdService();
+ }
+
+ if (mStart) {
+ try {
+ mLogdService.approve(mUid, mGid, mPid, mFd);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Fails to call remote functions ", ex);
+ }
+ }
+ }
+ }
+
+ public LogcatManagerService(Context context) {
+ super(context);
+ mContext = context;
+ mBinderService = new BinderService();
+ mThreadExecutor = Executors.newCachedThreadPool();
+ }
+
+ @Override
+ public void onStart() {
+ try {
+ publishBinderService("logcat", mBinderService);
+ } catch (Throwable t) {
+ Slog.e(TAG, "Could not start the LogcatManagerService.", t);
+ }
+ }
+
+ private void addLogdService() {
+ mLogdService = ILogd.Stub.asInterface(ServiceManager.getService("logd"));
+ }
+
+}
diff --git a/services/core/java/com/android/server/logcat/OWNERS b/services/core/java/com/android/server/logcat/OWNERS
new file mode 100644
index 0000000..9588fa9
--- /dev/null
+++ b/services/core/java/com/android/server/logcat/OWNERS
@@ -0,0 +1,5 @@
+cbrubaker@google.com
+eunjeongshin@google.com
+jsharkey@google.com
+vishwath@google.com
+wenhaowang@google.com
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index e555c13..c01851a 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -24,7 +24,6 @@
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.NETWORK_STACK;
import static android.Manifest.permission.OBSERVE_NETWORK_POLICY;
-import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
import static android.Manifest.permission.READ_PHONE_STATE;
import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
@@ -130,7 +129,6 @@
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
-import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
@@ -1010,10 +1008,11 @@
userFilter.addAction(ACTION_USER_REMOVED);
mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
- // listen for stats update events
- final IntentFilter statsFilter = new IntentFilter(ACTION_NETWORK_STATS_UPDATED);
- mContext.registerReceiver(
- mStatsReceiver, statsFilter, READ_NETWORK_USAGE_HISTORY, mHandler);
+ // listen for stats updated callbacks for interested network types.
+ mNetworkStats.registerUsageCallback(new NetworkTemplate.Builder(MATCH_MOBILE).build(),
+ 0 /* thresholdBytes */, new HandlerExecutor(mHandler), mStatsCallback);
+ mNetworkStats.registerUsageCallback(new NetworkTemplate.Builder(MATCH_WIFI).build(),
+ 0 /* thresholdBytes */, new HandlerExecutor(mHandler), mStatsCallback);
// Listen for snooze from notifications
mContext.registerReceiver(mSnoozeReceiver,
@@ -1214,19 +1213,16 @@
};
/**
- * Receiver that watches for {@link NetworkStatsManager} updates, which we
- * use to check against {@link NetworkPolicy#warningBytes}.
+ * Listener that watches for {@link NetworkStatsManager} updates, which
+ * NetworkPolicyManagerService uses to check against {@link NetworkPolicy#warningBytes}.
*/
- 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.
+ private final StatsCallback mStatsCallback = new StatsCallback();
+ private class StatsCallback extends NetworkStatsManager.UsageCallback {
+ private boolean mIsAnyCallbackReceived = false;
- mIsAnyIntentReceived = true;
+ @Override
+ public void onThresholdReached(int networkType, String subscriberId) {
+ mIsAnyCallbackReceived = true;
synchronized (mNetworkPoliciesSecondLock) {
updateNetworkRulesNL();
@@ -1236,11 +1232,11 @@
}
/**
- * Return whether any {@code ACTION_NETWORK_STATS_UPDATED} intent is received.
+ * Return whether any callback is received.
* Used to determine if NetworkStatsService is ready.
*/
- public boolean isAnyIntentReceived() {
- return mIsAnyIntentReceived;
+ public boolean isAnyCallbackReceived() {
+ return mIsAnyCallbackReceived;
}
};
@@ -1453,7 +1449,7 @@
// 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;
+ if (!mStatsCallback.isAnyCallbackReceived()) return null;
final List<NetworkStats.Bucket> stats = mDeps.getNetworkUidBytes(template, start, end);
for (final NetworkStats.Bucket entry : stats) {
@@ -5450,7 +5446,7 @@
private long getTotalBytes(NetworkTemplate template, long start, long end) {
// 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;
+ if (!mStatsCallback.isAnyCallbackReceived()) return 0;
return mDeps.getNetworkTotalBytes(template, start, end);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 86b385b..6b27321f 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -499,6 +499,7 @@
private IPlatformCompat mPlatformCompat;
private ShortcutHelper mShortcutHelper;
private PermissionHelper mPermissionHelper;
+ private UsageStatsManagerInternal mUsageStatsManagerInternal;
final IBinder mForegroundToken = new Binder();
private WorkerHandler mHandler;
@@ -2092,7 +2093,8 @@
UserManager userManager,
NotificationHistoryManager historyManager, StatsManager statsManager,
TelephonyManager telephonyManager, ActivityManagerInternal ami,
- MultiRateLimiter toastRateLimiter, PermissionHelper permissionHelper) {
+ MultiRateLimiter toastRateLimiter, PermissionHelper permissionHelper,
+ UsageStatsManagerInternal usageStatsManagerInternal) {
mHandler = handler;
Resources resources = getContext().getResources();
mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
@@ -2110,6 +2112,7 @@
mPackageManagerClient = packageManagerClient;
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class);
+ mUsageStatsManagerInternal = usageStatsManagerInternal;
mAppOps = appOps;
mAppOpsService = iAppOps;
try {
@@ -2411,7 +2414,8 @@
LocalServices.getService(ActivityManagerInternal.class),
createToastRateLimiter(), new PermissionHelper(LocalServices.getService(
PermissionManagerServiceInternal.class), AppGlobals.getPackageManager(),
- AppGlobals.getPermissionManager(), mEnableAppSettingMigration));
+ AppGlobals.getPermissionManager(), mEnableAppSettingMigration),
+ LocalServices.getService(UsageStatsManagerInternal.class));
publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);
@@ -7259,6 +7263,8 @@
if (index < 0) {
mNotificationList.add(r);
mUsageStats.registerPostedByApp(r);
+ mUsageStatsManagerInternal.reportNotificationPosted(r.getSbn().getOpPkg(),
+ r.getSbn().getUser(), SystemClock.elapsedRealtime());
final boolean isInterruptive = isVisuallyInterruptive(null, r);
r.setInterruptive(isInterruptive);
r.setTextChanged(isInterruptive);
@@ -7266,6 +7272,8 @@
old = mNotificationList.get(index); // Potentially *changes* old
mNotificationList.set(index, r);
mUsageStats.registerUpdatedByApp(r, old);
+ mUsageStatsManagerInternal.reportNotificationUpdated(r.getSbn().getOpPkg(),
+ r.getSbn().getUser(), SystemClock.elapsedRealtime());
// Make sure we don't lose the foreground service state.
notification.flags |=
old.getNotification().flags & FLAG_FOREGROUND_SERVICE;
@@ -8748,6 +8756,8 @@
case REASON_APP_CANCEL:
case REASON_APP_CANCEL_ALL:
mUsageStats.registerRemovedByApp(r);
+ mUsageStatsManagerInternal.reportNotificationRemoved(r.getSbn().getOpPkg(),
+ r.getUser(), SystemClock.elapsedRealtime());
break;
}
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index cd4244b..ba003d2 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/ComponentResolver.java
@@ -36,14 +36,6 @@
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import com.android.server.pm.pkg.component.ComponentMutateUtils;
-import com.android.server.pm.pkg.component.ParsedActivity;
-import com.android.server.pm.pkg.component.ParsedComponent;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
-import com.android.server.pm.pkg.component.ParsedProvider;
-import com.android.server.pm.pkg.component.ParsedProviderImpl;
-import com.android.server.pm.pkg.component.ParsedService;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -62,7 +54,15 @@
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.pkg.PackageStateInternal;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageUserStateInternal;
+import com.android.server.pm.pkg.component.ComponentMutateUtils;
+import com.android.server.pm.pkg.component.ParsedActivity;
+import com.android.server.pm.pkg.component.ParsedComponent;
+import com.android.server.pm.pkg.component.ParsedIntentInfo;
+import com.android.server.pm.pkg.component.ParsedMainComponent;
+import com.android.server.pm.pkg.component.ParsedProvider;
+import com.android.server.pm.pkg.component.ParsedProviderImpl;
+import com.android.server.pm.pkg.component.ParsedService;
import com.android.server.utils.Snappable;
import com.android.server.utils.SnapshotCache;
import com.android.server.utils.WatchableImpl;
@@ -380,14 +380,13 @@
continue;
}
// See PM.queryContentProviders()'s javadoc for why we have the metaData parameter.
- if (metaDataKey != null
- && (p.getMetaData() == null || !p.getMetaData().containsKey(metaDataKey))) {
+ if (metaDataKey != null && !p.getMetaData().containsKey(metaDataKey)) {
continue;
}
if (appInfoGenerator == null) {
appInfoGenerator = new CachedApplicationInfoGenerator();
}
- final PackageUserState state = ps.getUserStateOrDefault(userId);
+ final PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
final ApplicationInfo appInfo =
appInfoGenerator.generate(pkg, flags, state, userId, ps);
if (appInfo == null) {
@@ -424,7 +423,7 @@
if (pkg == null) {
return null;
}
- final PackageUserState state = ps.getUserStateOrDefault(userId);
+ final PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(
pkg, flags, state, userId, ps);
if (appInfo == null) {
@@ -461,7 +460,7 @@
if (appInfoGenerator == null) {
appInfoGenerator = new CachedApplicationInfoGenerator();
}
- final PackageUserState state = ps.getUserStateOrDefault(userId);
+ final PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
final ApplicationInfo appInfo =
appInfoGenerator.generate(pkg, 0, state, userId, ps);
if (appInfo == null) {
@@ -1537,7 +1536,7 @@
}
return null;
}
- final PackageUserState userState = ps.getUserStateOrDefault(userId);
+ final PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
ActivityInfo ai = PackageInfoUtils.generateActivityInfo(pkg, activity, mFlags,
userState, userId, ps);
if (ai == null) {
@@ -1854,7 +1853,7 @@
if (ps == null) {
return null;
}
- final PackageUserState userState = ps.getUserStateOrDefault(userId);
+ final PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
final boolean matchVisibleToInstantApp = (mFlags
& PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
final boolean isInstantApp = (mFlags & PackageManager.MATCH_INSTANT) != 0;
@@ -2099,7 +2098,7 @@
if (ps == null) {
return null;
}
- final PackageUserState userState = ps.getUserStateOrDefault(userId);
+ final PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
ServiceInfo si = PackageInfoUtils.generateServiceInfo(pkg, service, mFlags,
userState, userId, ps);
if (si == null) {
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 69c475a..cca1b97 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -823,7 +823,7 @@
}
if (resolveComponentName().equals(component)) {
return PackageInfoWithoutStateUtils.generateDelegateActivityInfo(mResolveActivity,
- flags, PackageUserState.DEFAULT, userId);
+ flags, PackageUserStateInternal.DEFAULT, userId);
}
return null;
}
@@ -1547,7 +1547,7 @@
flags |= MATCH_ANY_USER;
}
- final PackageUserState state = ps.getUserStateOrDefault(userId);
+ final PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
AndroidPackage p = ps.getPkg();
if (p != null) {
// Compute GIDs only if requested
@@ -3548,7 +3548,7 @@
if (shouldFilterApplication(ps, callingUid, userId)) {
return false;
}
- final PackageUserState state = ps.getUserStateOrDefault(userId);
+ final PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
if (state != null) {
return PackageUserStateUtils.isAvailable(state, 0);
}
@@ -4011,7 +4011,7 @@
ps, callingUid, component, TYPE_PROVIDER, userId)) {
return null;
}
- PackageUserState state = ps.getUserStateOrDefault(userId);
+ PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
final ApplicationInfo appInfo =
PackageInfoUtils.generateApplicationInfo(ps.getPkg(), flags, state, userId, ps);
if (appInfo == null) {
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index a3f1435..a3134a0 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -199,30 +199,6 @@
.addDataType("video/*")
.build();
- // TODO(b/199068419): Remove once GEM enables the intent for Googlers
- /** Pick images can be forwarded to work profile. */
- private static final DefaultCrossProfileIntentFilter ACTION_PICK_IMAGES_TO_PROFILE =
- new DefaultCrossProfileIntentFilter.Builder(
- DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
- /* flags= */ 0,
- /* letsPersonalDataIntoProfile= */ true)
- .addAction(MediaStore.ACTION_PICK_IMAGES)
- .addCategory(Intent.CATEGORY_DEFAULT)
- .build();
- // TODO(b/199068419): Remove once GEM enables the intent for Googlers
- /** Pick images can be forwarded to work profile. */
- private static final DefaultCrossProfileIntentFilter
- ACTION_PICK_IMAGES_WITH_DATA_TYPES_TO_PROFILE =
- new DefaultCrossProfileIntentFilter.Builder(
- DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
- /* flags= */ 0,
- /* letsPersonalDataIntoProfile= */ true)
- .addAction(MediaStore.ACTION_PICK_IMAGES)
- .addCategory(Intent.CATEGORY_DEFAULT)
- .addDataType("image/*")
- .addDataType("video/*")
- .build();
-
/** Open document intent can be forwarded to parent user. */
private static final DefaultCrossProfileIntentFilter OPEN_DOCUMENT =
new DefaultCrossProfileIntentFilter.Builder(
@@ -336,8 +312,6 @@
ACTION_PICK_DATA,
ACTION_PICK_IMAGES,
ACTION_PICK_IMAGES_WITH_DATA_TYPES,
- ACTION_PICK_IMAGES_TO_PROFILE,
- ACTION_PICK_IMAGES_WITH_DATA_TYPES_TO_PROFILE,
OPEN_DOCUMENT,
GET_CONTENT,
USB_DEVICE_ATTACHED,
diff --git a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
index 9efe81a..06405ae 100644
--- a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
@@ -30,8 +30,10 @@
import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS;
import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_FRAMEWORK_RES_SPLITS;
import android.annotation.Nullable;
+import android.content.pm.parsing.ApkLiteParseUtils;
import android.os.Environment;
import android.os.SystemClock;
import android.os.Trace;
@@ -91,6 +93,29 @@
mSystemScanFlags = scanFlags | SCAN_AS_SYSTEM;
}
+ private List<File> getFrameworkResApkSplitFiles() {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanFrameworkResApkSplits");
+ try {
+ final List<File> splits = new ArrayList<>();
+ final List<ApexManager.ActiveApexInfo> activeApexInfos =
+ mPm.mApexManager.getActiveApexInfos();
+ for (int i = 0; i < activeApexInfos.size(); i++) {
+ ApexManager.ActiveApexInfo apexInfo = activeApexInfos.get(i);
+ File splitsFolder = new File(apexInfo.apexDirectory, "etc/splits");
+ if (splitsFolder.isDirectory()) {
+ for (File file : splitsFolder.listFiles()) {
+ if (ApkLiteParseUtils.isApkFile(file)) {
+ splits.add(file);
+ }
+ }
+ }
+ }
+ return splits;
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+ }
+
private List<ScanPartition> getSystemScanPartitions() {
final List<ScanPartition> scanPartitions = new ArrayList<>();
scanPartitions.addAll(mPm.mInjector.getSystemPartitions());
@@ -184,7 +209,8 @@
if (!mPm.isOnlyCoreApps()) {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
SystemClock.uptimeMillis());
- scanDirTracedLI(mPm.getAppInstallDir(), 0, mScanFlags | SCAN_REQUIRE_KNOWN, 0,
+ scanDirTracedLI(mPm.getAppInstallDir(), /* frameworkSplits= */ null, 0,
+ mScanFlags | SCAN_REQUIRE_KNOWN, 0,
packageParser, executorService);
}
@@ -247,12 +273,14 @@
if (partition.getOverlayFolder() == null) {
continue;
}
- scanDirTracedLI(partition.getOverlayFolder(), mSystemParseFlags,
- mSystemScanFlags | partition.scanFlag, 0,
+ scanDirTracedLI(partition.getOverlayFolder(), /* frameworkSplits= */ null,
+ mSystemParseFlags, mSystemScanFlags | partition.scanFlag, 0,
packageParser, executorService);
}
- scanDirTracedLI(frameworkDir, mSystemParseFlags,
+ List<File> frameworkSplits = getFrameworkResApkSplitFiles();
+ scanDirTracedLI(frameworkDir, frameworkSplits,
+ mSystemParseFlags | PARSE_FRAMEWORK_RES_SPLITS,
mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0,
packageParser, executorService);
if (!mPm.mPackages.containsKey("android")) {
@@ -263,12 +291,13 @@
for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
final ScanPartition partition = mDirsToScanAsSystem.get(i);
if (partition.getPrivAppFolder() != null) {
- scanDirTracedLI(partition.getPrivAppFolder(), mSystemParseFlags,
+ scanDirTracedLI(partition.getPrivAppFolder(), /* frameworkSplits= */ null,
+ mSystemParseFlags,
mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0,
packageParser, executorService);
}
- scanDirTracedLI(partition.getAppFolder(), mSystemParseFlags,
- mSystemScanFlags | partition.scanFlag, 0,
+ scanDirTracedLI(partition.getAppFolder(), /* frameworkSplits= */ null,
+ mSystemParseFlags, mSystemScanFlags | partition.scanFlag, 0,
packageParser, executorService);
}
}
@@ -285,12 +314,13 @@
}
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
- private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags,
+ private void scanDirTracedLI(File scanDir, List<File> frameworkSplits,
+ final int parseFlags, int scanFlags,
long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
try {
- mInstallPackageHelper.installPackagesFromDir(scanDir, parseFlags, scanFlags,
- currentTime, packageParser, executorService);
+ mInstallPackageHelper.installPackagesFromDir(scanDir, frameworkSplits, parseFlags,
+ scanFlags, currentTime, 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 80699ac..d98626f 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1806,10 +1806,8 @@
final PackageSetting ps = mPm.mSettings.getPackageLPr(pkg.getPackageName());
if (ps != null && ps.isPrivileged()) {
fsverityCandidates.put(pkg.getBaseApkPath(), null);
- if (pkg.getSplitCodePaths() != null) {
- for (String splitPath : pkg.getSplitCodePaths()) {
- fsverityCandidates.put(splitPath, null);
- }
+ for (String splitPath : pkg.getSplitCodePaths()) {
+ fsverityCandidates.put(splitPath, null);
}
}
}
@@ -1825,15 +1823,13 @@
fsverityCandidates.put(dmPath, VerityUtils.getFsveritySignatureFilePath(dmPath));
}
- if (pkg.getSplitCodePaths() != null) {
- for (String path : pkg.getSplitCodePaths()) {
- fsverityCandidates.put(path, VerityUtils.getFsveritySignatureFilePath(path));
+ for (String path : pkg.getSplitCodePaths()) {
+ fsverityCandidates.put(path, VerityUtils.getFsveritySignatureFilePath(path));
- final String splitDmPath = DexMetadataHelper.buildDexMetadataPathForApk(path);
- if (new File(splitDmPath).exists()) {
- fsverityCandidates.put(splitDmPath,
- VerityUtils.getFsveritySignatureFilePath(splitDmPath));
- }
+ final String splitDmPath = DexMetadataHelper.buildDexMetadataPathForApk(path);
+ if (new File(splitDmPath).exists()) {
+ fsverityCandidates.put(splitDmPath,
+ VerityUtils.getFsveritySignatureFilePath(splitDmPath));
}
}
}
@@ -1989,9 +1985,7 @@
oldCodePaths = new ArraySet<>();
}
Collections.addAll(oldCodePaths, oldPackage.getBaseApkPath());
- if (oldPackage.getSplitCodePaths() != null) {
- Collections.addAll(oldCodePaths, oldPackage.getSplitCodePaths());
- }
+ Collections.addAll(oldCodePaths, oldPackage.getSplitCodePaths());
ps1.setOldCodePaths(oldCodePaths);
} else {
ps1.setOldCodePaths(null);
@@ -3412,8 +3406,9 @@
}
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
- public void installPackagesFromDir(File scanDir, int parseFlags, int scanFlags,
- long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
+ public void installPackagesFromDir(File scanDir, List<File> frameworkSplits, int parseFlags,
+ int scanFlags, long currentTime, PackageParser2 packageParser,
+ ExecutorService executorService) {
final File[] files = scanDir.listFiles();
if (ArrayUtils.isEmpty(files)) {
Log.d(TAG, "No files in app dir " + scanDir);
@@ -3425,7 +3420,7 @@
+ " flags=0x" + Integer.toHexString(parseFlags));
}
ParallelPackageParser parallelPackageParser =
- new ParallelPackageParser(packageParser, executorService);
+ new ParallelPackageParser(packageParser, executorService, frameworkSplits);
// Submit files for parsing in parallel
int fileCount = 0;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e00f4f5..e0d404a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -232,7 +232,7 @@
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageStateUtils;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageUserStateInternal;
import com.android.server.pm.pkg.component.ParsedInstrumentation;
import com.android.server.pm.pkg.component.ParsedMainComponent;
import com.android.server.pm.pkg.mutate.PackageStateMutator;
@@ -8765,7 +8765,7 @@
// The instance created in PackageManagerService is special cased to be non-user
// specific, so initialize all the needed fields here.
ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(pkg, 0,
- PackageUserState.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
+ PackageUserStateInternal.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
// Set up information for custom user intent resolution activity.
mResolveActivity.applicationInfo = appInfo;
@@ -8797,7 +8797,7 @@
// The instance stored in PackageManagerService is special cased to be non-user
// specific, so initialize all the needed fields here.
mAndroidApplication = PackageInfoUtils.generateApplicationInfo(pkg, 0,
- PackageUserState.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
+ PackageUserStateInternal.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
if (!mResolverReplaced) {
mResolveActivity.applicationInfo = mAndroidApplication;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index e03cf0a..1d2b829 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -228,11 +228,9 @@
}
final File baseFile = new File(pkg.getBaseApkPath());
long maxModifiedTime = baseFile.lastModified();
- if (pkg.getSplitCodePaths() != null) {
- for (int i = pkg.getSplitCodePaths().length - 1; i >=0; --i) {
- final File splitFile = new File(pkg.getSplitCodePaths()[i]);
- maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
- }
+ for (int i = pkg.getSplitCodePaths().length - 1; i >=0; --i) {
+ final File splitFile = new File(pkg.getSplitCodePaths()[i]);
+ maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
}
return maxModifiedTime;
}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 9dbf57d..5fc840c 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -295,14 +295,12 @@
proto.write(PackageProto.SplitProto.REVISION_CODE, pkg.getBaseRevisionCode());
proto.end(splitToken);
- if (pkg.getSplitNames() != null) {
- for (int i = 0; i < pkg.getSplitNames().length; i++) {
- splitToken = proto.start(PackageProto.SPLITS);
- proto.write(PackageProto.SplitProto.NAME, pkg.getSplitNames()[i]);
- proto.write(PackageProto.SplitProto.REVISION_CODE,
- pkg.getSplitRevisionCodes()[i]);
- proto.end(splitToken);
- }
+ for (int i = 0; i < pkg.getSplitNames().length; i++) {
+ splitToken = proto.start(PackageProto.SPLITS);
+ proto.write(PackageProto.SplitProto.NAME, pkg.getSplitNames()[i]);
+ proto.write(PackageProto.SplitProto.REVISION_CODE,
+ pkg.getSplitRevisionCodes()[i]);
+ proto.end(splitToken);
}
long sourceToken = proto.start(PackageProto.INSTALL_SOURCE);
@@ -1263,8 +1261,8 @@
@Nullable
@Override
- public Integer getSharedUserId() {
- return sharedUser == null ? null : sharedUser.userId;
+ public int getSharedUserId() {
+ return sharedUser == null ? -1 : sharedUser.userId;
}
@NonNull
diff --git a/services/core/java/com/android/server/pm/ParallelPackageParser.java b/services/core/java/com/android/server/pm/ParallelPackageParser.java
index 5625884..45030bf 100644
--- a/services/core/java/com/android/server/pm/ParallelPackageParser.java
+++ b/services/core/java/com/android/server/pm/ParallelPackageParser.java
@@ -27,6 +27,7 @@
import com.android.server.pm.parsing.pkg.ParsedPackage;
import java.io.File;
+import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
@@ -54,9 +55,17 @@
private final ExecutorService mExecutorService;
+ private final List<File> mFrameworkSplits;
+
ParallelPackageParser(PackageParser2 packageParser, ExecutorService executorService) {
+ this(packageParser, executorService, /* frameworkSplits= */ null);
+ }
+
+ ParallelPackageParser(PackageParser2 packageParser, ExecutorService executorService,
+ List<File> frameworkSplits) {
mPackageParser = packageParser;
mExecutorService = executorService;
+ mFrameworkSplits = frameworkSplits;
}
static class ParseResult {
@@ -125,6 +134,6 @@
@VisibleForTesting
protected ParsedPackage parsePackage(File scanFile, int parseFlags)
throws PackageManagerException {
- return mPackageParser.parsePackage(scanFile, parseFlags, true);
+ return mPackageParser.parsePackage(scanFile, parseFlags, true, mFrameworkSplits);
}
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 45837717..f21bc93 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4245,7 +4245,7 @@
final PackageSetting ps = mPackages.get(componentInfo.packageName);
if (ps == null) return false;
- final PackageUserState userState = ps.readUserState(userId);
+ final PackageUserStateInternal userState = ps.readUserState(userId);
return PackageUserStateUtils.isMatch(userState, componentInfo, flags);
}
@@ -4255,7 +4255,7 @@
final PackageSetting ps = mPackages.get(component.getPackageName());
if (ps == null) return false;
- final PackageUserState userState = ps.readUserState(userId);
+ final PackageUserStateInternal userState = ps.readUserState(userId);
return PackageUserStateUtils.isMatch(userState, pkg.isSystem(), pkg.isEnabled(), component,
flags);
}
@@ -4497,13 +4497,11 @@
pw.print(checkinTag); pw.print("-"); pw.print("splt,");
pw.print("base,");
pw.println(pkg.getBaseRevisionCode());
- if (pkg.getSplitNames() != null) {
- int[] splitRevisionCodes = pkg.getSplitRevisionCodes();
- for (int i = 0; i < pkg.getSplitNames().length; i++) {
- pw.print(checkinTag); pw.print("-"); pw.print("splt,");
- pw.print(pkg.getSplitNames()[i]); pw.print(",");
- pw.println(splitRevisionCodes[i]);
- }
+ int[] splitRevisionCodes = pkg.getSplitRevisionCodes();
+ for (int i = 0; i < pkg.getSplitNames().length; i++) {
+ pw.print(checkinTag); pw.print("-"); pw.print("splt,");
+ pw.print(pkg.getSplitNames()[i]); pw.print(",");
+ pw.println(splitRevisionCodes[i]);
}
}
for (UserInfo user : users) {
@@ -5169,13 +5167,11 @@
}
String[] splitNames = pkg.getSplitNames();
int[] splitRevisionCodes = pkg.getSplitRevisionCodes();
- if (splitNames != null) {
- for (int i = 0; i < splitNames.length; i++) {
- pw.print(", ");
- pw.print(splitNames[i]);
- if (splitRevisionCodes[i] != 0) {
- pw.print(":"); pw.print(splitRevisionCodes[i]);
- }
+ for (int i = 0; i < splitNames.length; i++) {
+ pw.print(", ");
+ pw.print(splitNames[i]);
+ if (splitRevisionCodes[i] != 0) {
+ pw.print(":"); pw.print(splitRevisionCodes[i]);
}
}
pw.print("]");
diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
index fc88af9..beea86d 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptUtils.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
@@ -78,7 +78,7 @@
String baseApkContextClassLoader = encodeClassLoader(
"", pkg.getClassLoaderName(), sharedLibrariesContext);
- if (pkg.getSplitCodePaths() == null) {
+ if (ArrayUtils.isEmpty(pkg.getSplitCodePaths())) {
// The application has no splits.
return new String[] {baseApkContextClassLoader};
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl b/services/core/java/com/android/server/pm/package-info.java
similarity index 64%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
rename to services/core/java/com/android/server/pm/package-info.java
index 2b3e961..04b8e0a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
+++ b/services/core/java/com/android/server/pm/package-info.java
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.shared.system.smartspace;
-
-import com.android.systemui.shared.system.smartspace.ISmartspaceCallback;
-
-// Controller that keeps track of SmartSpace instances in remote processes (such as Launcher).
-interface ISmartspaceTransitionController {
- oneway void setSmartspace(ISmartspaceCallback callback);
-}
\ No newline at end of file
+/**
+ * @hide
+ * TODO(b/146466118) remove this javadoc tag
+ */
+@android.annotation.Hide
+package com.android.server.pm;
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 0fa0dc3..2d0a3ef 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -59,7 +59,7 @@
import com.android.server.pm.parsing.pkg.PackageImpl;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageStateUnserialized;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageUserStateInternal;
import libcore.util.EmptyArray;
@@ -85,8 +85,8 @@
@Nullable
public static PackageInfo generate(AndroidPackage pkg, int[] gids,
@PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
- long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId,
- @Nullable PackageStateInternal pkgSetting) {
+ long lastUpdateTime, Set<String> grantedPermissions, PackageUserStateInternal state,
+ @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
return generateWithComponents(pkg, gids, flags, firstInstallTime, lastUpdateTime,
grantedPermissions, state, userId, null, pkgSetting);
}
@@ -98,7 +98,7 @@
public static PackageInfo generate(AndroidPackage pkg, ApexInfo apexInfo, int flags,
@Nullable PackageStateInternal pkgSetting) {
return generateWithComponents(pkg, EmptyArray.INT, flags, 0, 0, Collections.emptySet(),
- PackageUserState.DEFAULT, UserHandle.getCallingUserId(), apexInfo, pkgSetting);
+ PackageUserStateInternal.DEFAULT, UserHandle.getCallingUserId(), apexInfo, pkgSetting);
}
/**
@@ -106,8 +106,9 @@
*/
private static PackageInfo generateWithComponents(AndroidPackage pkg, int[] gids,
@PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
- long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId,
- @Nullable ApexInfo apexInfo, @Nullable PackageStateInternal pkgSetting) {
+ long lastUpdateTime, Set<String> grantedPermissions, PackageUserStateInternal state,
+ @UserIdInt int userId, @Nullable ApexInfo apexInfo,
+ @Nullable PackageStateInternal pkgSetting) {
ApplicationInfo applicationInfo = generateApplicationInfo(pkg, flags, state, userId,
pkgSetting);
if (applicationInfo == null) {
@@ -209,8 +210,9 @@
*/
@Nullable
public static ApplicationInfo generateApplicationInfo(AndroidPackage pkg,
- @PackageManager.ApplicationInfoFlagsBits long flags, @NonNull PackageUserState state,
- int userId, @Nullable PackageStateInternal pkgSetting) {
+ @PackageManager.ApplicationInfoFlagsBits long flags,
+ @NonNull PackageUserStateInternal state, @UserIdInt int userId,
+ @Nullable PackageStateInternal pkgSetting) {
if (pkg == null) {
return null;
}
@@ -255,7 +257,8 @@
*/
@Nullable
public static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a,
- @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state, int userId,
+ @PackageManager.ComponentInfoFlagsBits long flags,
+ @NonNull PackageUserStateInternal state, @UserIdInt int userId,
@Nullable PackageStateInternal pkgSetting) {
return generateActivityInfo(pkg, a, flags, state, null, userId, pkgSetting);
}
@@ -265,9 +268,9 @@
*/
@Nullable
private static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a,
- @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state,
- @Nullable ApplicationInfo applicationInfo, int userId,
- @Nullable PackageStateInternal pkgSetting) {
+ @PackageManager.ComponentInfoFlagsBits long flags,
+ @NonNull PackageUserStateInternal state, @Nullable ApplicationInfo applicationInfo,
+ @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
if (a == null) return null;
if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
return null;
@@ -291,8 +294,8 @@
*/
@Nullable
public static ServiceInfo generateServiceInfo(AndroidPackage pkg, ParsedService s,
- @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state, int userId,
- @Nullable PackageStateInternal pkgSetting) {
+ @PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
+ @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
return generateServiceInfo(pkg, s, flags, state, null, userId, pkgSetting);
}
@@ -301,7 +304,7 @@
*/
@Nullable
private static ServiceInfo generateServiceInfo(AndroidPackage pkg, ParsedService s,
- @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state,
+ @PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
@Nullable ApplicationInfo applicationInfo, int userId,
@Nullable PackageStateInternal pkgSetting) {
if (s == null) return null;
@@ -326,7 +329,7 @@
*/
@Nullable
public static ProviderInfo generateProviderInfo(AndroidPackage pkg, ParsedProvider p,
- @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state,
+ @PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
@NonNull ApplicationInfo applicationInfo, int userId,
@Nullable PackageStateInternal pkgSetting) {
if (p == null) return null;
@@ -418,11 +421,11 @@
}
/**
- * Returns true if the package is installed and not hidden, or if the caller
- * explicitly wanted all uninstalled and hidden packages as well.
+ * Returns true if the package is installed and not hidden, or if the caller explicitly wanted
+ * all uninstalled and hidden packages as well.
*/
public static boolean checkUseInstalledOrHidden(AndroidPackage pkg,
- PackageStateInternal pkgSetting, PackageUserState state,
+ PackageStateInternal pkgSetting, PackageUserStateInternal state,
@PackageManager.PackageInfoFlagsBits long flags) {
// Returns false if the package is hidden system app until installed.
if ((flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) == 0
@@ -466,7 +469,9 @@
return hasFlag ? flag : 0;
}
- /** @see ApplicationInfo#flags */
+ /**
+ * @see ApplicationInfo#flags
+ */
public static int appInfoFlags(AndroidPackage pkg, @Nullable PackageStateInternal pkgSetting) {
// @formatter:off
int pkgWithoutStateFlags = PackageInfoWithoutStateUtils.appInfoFlags(pkg)
@@ -628,7 +633,7 @@
*/
@Nullable
public ApplicationInfo generate(AndroidPackage pkg,
- @PackageManager.ApplicationInfoFlagsBits long flags, PackageUserState state,
+ @PackageManager.ApplicationInfoFlagsBits long flags, PackageUserStateInternal state,
int userId, @Nullable PackageStateInternal pkgSetting) {
ApplicationInfo appInfo = mCache.get(pkg.getPackageName());
if (appInfo != null) {
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
index 08e2f7d..b2e15e7 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
@@ -22,9 +22,6 @@
import android.app.ActivityThread;
import android.content.Context;
import android.content.pm.ApplicationInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
@@ -41,6 +38,9 @@
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.parsing.pkg.PackageImpl;
import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
import java.io.File;
import java.util.List;
@@ -147,6 +147,15 @@
@AnyThread
public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
throws PackageManagerException {
+ return parsePackage(packageFile, flags, useCaches, /* frameworkSplits= */ null);
+ }
+
+ /**
+ * TODO(b/135203078): Document new package parsing
+ */
+ @AnyThread
+ public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches,
+ List<File> frameworkSplits) throws PackageManagerException {
if (useCaches && mCacher != null) {
ParsedPackage parsed = mCacher.getCachedResult(packageFile, flags);
if (parsed != null) {
@@ -156,7 +165,8 @@
long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
ParseInput input = mSharedResult.get().reset();
- ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags);
+ ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags,
+ frameworkSplits);
if (result.isError()) {
throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(),
result.getException());
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 9b3d6d6..8e41c9b 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -73,11 +73,6 @@
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.SigningDetails;
-import com.android.server.pm.pkg.component.ComponentMutateUtils;
-import com.android.server.pm.pkg.component.ParsedPermission;
-import com.android.server.pm.pkg.component.ParsedPermissionGroup;
-import com.android.server.pm.pkg.component.ParsedPermissionUtils;
-
import android.content.pm.permission.SplitPermissionInfoParcelable;
import android.metrics.LogMaker;
import android.os.AsyncTask;
@@ -136,6 +131,10 @@
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.component.ComponentMutateUtils;
+import com.android.server.pm.pkg.component.ParsedPermission;
+import com.android.server.pm.pkg.component.ParsedPermissionGroup;
+import com.android.server.pm.pkg.component.ParsedPermissionUtils;
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.policy.SoftRestrictedPermissionPolicy;
@@ -3172,18 +3171,17 @@
}
} else if (NOTIFICATION_PERMISSIONS.contains(newPerm)) {
//&& (origPs.getPermissionState(newPerm) == null) {
- // TODO(b/205888750): add back line about origPs once propagated through droidfood
+ // TODO(b/205888750): add back line about origPs once all TODO sections below are
+ // propagated through droidfood
Permission bp = mRegistry.getPermission(newPerm);
if (bp == null) {
throw new IllegalStateException("Unknown new permission " + newPerm);
}
- // TODO(b/205888750): remove the line for REVOKE_WHEN_REQUESTED once propagated
- // through droidfood
if (!isUserSetOrPregrantedOrFixed(ps.getPermissionFlags(newPerm))) {
updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
- ps.updatePermissionFlags(bp, PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
- | FLAG_PERMISSION_REVOKE_WHEN_REQUESTED,
- PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED);
+ int setFlag = ps.isPermissionGranted(newPerm)
+ ? 0 : FLAG_PERMISSION_REVIEW_REQUIRED;
+ ps.updatePermissionFlags(bp, FLAG_PERMISSION_REVIEW_REQUIRED, setFlag);
// TODO(b/205888750): remove if/else block once propagated through droidfood
if (ps.isPermissionGranted(newPerm)
&& pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M) {
@@ -3192,6 +3190,10 @@
&& pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) {
ps.grantPermission(bp);
}
+ } else {
+ // TODO(b/205888750): remove once propagated through droidfood
+ ps.updatePermissionFlags(bp, FLAG_PERMISSION_REVOKE_WHEN_REQUESTED
+ | FLAG_PERMISSION_REVIEW_REQUIRED, 0);
}
}
}
@@ -4779,9 +4781,10 @@
// Handle REVIEW_REQUIRED
if ((newFlags & priorityFixedMask) == 0) {
- if (NOTIFICATION_PERMISSIONS.contains(srcState.getName())) {
+ if ((newFlags & (defaultGrantMask | userSettableMask)) == 0
+ && NOTIFICATION_PERMISSIONS.contains(srcState.getName())) {
// For notification permissions, inherit from both states
- // if no priority FIXED flags are set
+ // if no priority FIXED or DEFAULT_GRANT or USER_SET flags are set
newFlags |= (combinedFlags & FLAG_PERMISSION_REVIEW_REQUIRED);
} else if ((newFlags & priorityMask) == 0) {
// Else inherit from destState if no priority flags are set
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java b/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java
index 3fde41a..d3c8c7b 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java
@@ -16,21 +16,337 @@
package com.android.server.pm.pkg;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.FeatureGroupInfo;
+import android.content.pm.FeatureInfo;
+import android.util.SparseArray;
import com.android.server.pm.parsing.pkg.AndroidPackage;
-import com.android.server.pm.parsing.pkg.PkgAppInfo;
-import com.android.server.pm.parsing.pkg.PkgPackageInfo;
+import com.android.server.pm.pkg.component.ParsedActivity;
+import com.android.server.pm.pkg.component.ParsedAttribution;
+import com.android.server.pm.pkg.component.ParsedInstrumentation;
+import com.android.server.pm.pkg.component.ParsedPermission;
+import com.android.server.pm.pkg.component.ParsedProvider;
+import com.android.server.pm.pkg.component.ParsedService;
+
+import java.util.List;
/**
* Explicit interface used for consumers like mainline who need a {@link SystemApi @SystemApi} form
- * of {@link AndroidPackage}.
- * <p>
- * There should be no methods in this class. All of them must come from other interfaces that group
- * the actual methods. This is done to ensure proper separation of the (legacy?) Info object APIs.
+ * of {@link AndroidPackage}. *
+ * @hide
*/
-// TODO(b/173807334): Expose API
//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
-public interface AndroidPackageApi extends PkgPackageInfo, PkgAppInfo {
+public interface AndroidPackageApi {
+ boolean areAttributionsUserVisible();
+
+ @Nullable
+ String getAppComponentFactory();
+
+ int getAutoRevokePermissions();
+
+ @Nullable
+ String getBackupAgentName();
+
+ int getBanner();
+
+ @NonNull
+ String getBaseApkPath();
+
+ int getCategory();
+
+ @Nullable
+ String getClassLoaderName();
+
+ @Nullable
+ String getClassName();
+
+ int getCompatibleWidthLimitDp();
+
+ int getDataExtractionRules();
+
+ int getDescriptionRes();
+
+ int getFullBackupContent();
+
+ int getGwpAsanMode();
+
+ int getIconRes();
+
+ int getInstallLocation();
+
+ int getLabelRes();
+
+ int getLargestWidthLimitDp();
+
+ int getLogo();
+
+ @Nullable
+ String getManageSpaceActivityName();
+
+ float getMaxAspectRatio();
+
+ int getMemtagMode();
+
+ float getMinAspectRatio();
+
+ int getMinSdkVersion();
+
+ int getNativeHeapZeroInitialized();
+
+ int getNetworkSecurityConfigRes();
+
+ @Nullable
+ CharSequence getNonLocalizedLabel();
+
+ @NonNull
+ String getPath();
+
+ @Nullable
+ String getPermission();
+
+ @NonNull
+ String getProcessName();
+
+ int getRequiresSmallestWidthDp();
+
+ @SuppressLint("AutoBoxing")
+ @Nullable
+ Boolean getResizeableActivity();
+
+ int getRoundIconRes();
+
+ @NonNull
+ String[] getSplitClassLoaderNames();
+
+ @NonNull
+ String[] getSplitCodePaths();
+
+ @Nullable
+ SparseArray<int[]> getSplitDependencies();
+
+ int getTargetSdkVersion();
+
+ int getTargetSandboxVersion();
+
+ @Nullable
+ String getTaskAffinity();
+
+ int getTheme();
+
+ int getUiOptions();
+
+ @Nullable
+ String getVolumeUuid();
+
+ @Nullable
+ String getZygotePreloadName();
+
+ boolean hasRequestForegroundServiceExemption();
+
+ @SuppressLint("AutoBoxing")
+ @Nullable
+ Boolean hasRequestRawExternalStorageAccess();
+
+ boolean isAllowAudioPlaybackCapture();
+
+ boolean isAllowBackup();
+
+ boolean isAllowClearUserData();
+
+ boolean isAllowClearUserDataOnFailedRestore();
+
+ boolean isAllowNativeHeapPointerTagging();
+
+ boolean isAllowTaskReparenting();
+
+ boolean isAnyDensity();
+
+ boolean isBackupInForeground();
+
+ boolean isBaseHardwareAccelerated();
+
+ boolean isCantSaveState();
+
+ boolean isCrossProfile();
+
+ boolean isDebuggable();
+
+ boolean isDefaultToDeviceProtectedStorage();
+
+ boolean isDirectBootAware();
+
+ boolean isEnabled();
+
+ boolean isExternalStorage();
+
+ boolean isExtractNativeLibs();
+
+ boolean isFullBackupOnly();
+
+ boolean isHasCode();
+
+ boolean isHasDomainUrls();
+
+ boolean isHasFragileUserData();
+
+ boolean isIsolatedSplitLoading();
+
+ boolean isKillAfterRestore();
+
+ boolean isLargeHeap();
+
+ boolean isMultiArch();
+
+ boolean isOverlay();
+
+ boolean isPartiallyDirectBootAware();
+
+ boolean isPersistent();
+
+ boolean isProfileable();
+
+ boolean isProfileableByShell();
+
+ boolean isRequestLegacyExternalStorage();
+
+ boolean isResizeable();
+
+ boolean isResizeableActivityViaSdkVersion();
+
+ boolean isRestoreAnyVersion();
+
+ boolean isStaticSharedLibrary();
+
+ boolean isSdkLibrary();
+
+ boolean isSupportsExtraLargeScreens();
+
+ boolean isSupportsLargeScreens();
+
+ boolean isSupportsNormalScreens();
+
+ boolean isSupportsRtl();
+
+ boolean isSupportsSmallScreens();
+
+ boolean isTestOnly();
+
+ boolean isUseEmbeddedDex();
+
+ boolean isUsesCleartextTraffic();
+
+ boolean isUsesNonSdkApi();
+
+ boolean isVmSafeMode();
+
+ @NonNull
+ List<ParsedActivity> getActivities();
+
+ @NonNull
+ List<ParsedAttribution> getAttributions();
+
+ @NonNull
+ List<String> getAdoptPermissions();
+
+ int getBaseRevisionCode();
+
+ int getCompileSdkVersion();
+
+ @Nullable
+ String getCompileSdkVersionCodeName();
+
+ @NonNull
+ List<ConfigurationInfo> getConfigPreferences();
+
+ @NonNull
+ List<FeatureGroupInfo> getFeatureGroups();
+
+ @NonNull
+ List<ParsedInstrumentation> getInstrumentations();
+
+ long getLongVersionCode();
+
+ @NonNull
+ String getPackageName();
+
+ @NonNull
+ List<ParsedPermission> getPermissions();
+
+ @NonNull
+ List<ParsedProvider> getProviders();
+
+ @NonNull
+ List<ParsedActivity> getReceivers();
+
+ @NonNull
+ List<FeatureInfo> getRequestedFeatures();
+
+ @NonNull
+ List<String> getRequestedPermissions();
+
+ @Nullable
+ String getRequiredAccountType();
+
+ @Nullable
+ String getRestrictedAccountType();
+
+ @NonNull
+ List<ParsedService> getServices();
+
+ @Nullable
+ String getSharedUserId();
+
+ int getSharedUserLabel();
+
+ @NonNull
+ String[] getSplitNames();
+
+ @NonNull
+ int[] getSplitRevisionCodes();
+
+ @Nullable
+ String getVersionName();
+
+ boolean isRequiredForAllUsers();
+
+ @Nullable
+ String getNativeLibraryDir();
+
+ @Nullable
+ String getNativeLibraryRootDir();
+
+ @Nullable
+ String getSecondaryNativeLibraryDir();
+
+ int getUid();
+
+ boolean isFactoryTest();
+
+ boolean isNativeLibraryRootRequiresIsa();
+
+ boolean isOdm();
+
+ boolean isOem();
+
+ boolean isPrivileged();
+
+ boolean isProduct();
+
+ boolean isSignedWithPlatformKey();
+
+ boolean isSystem();
+
+ boolean isSystemExt();
+
+ boolean isVendor();
+
+ boolean isCoreApp();
+
+ boolean isStub();
}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index f5ee8d9..9fa9644 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -54,7 +54,6 @@
*
* @hide
*/
-// TODO(b/173807334): Expose API
//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
public interface PackageState {
@@ -163,10 +162,9 @@
* Retrieves the shared user ID. Note that the actual shared user data is not available here and
* must be queried separately.
*
- * @return the shared user this package is a part of, or null if it's not part of a shared user.
+ * @return the shared user this package is a part of, or -1 if it's not part of a shared user.
*/
- @Nullable
- Integer getSharedUserId();
+ int getSharedUserId();
@NonNull
SigningInfo getSigningInfo();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index a5d399e..9395ca5 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -127,7 +127,7 @@
@Nullable
private final String mSecondaryCpuAbi;
@Nullable
- private final Integer mSharedUserId;
+ private final int mSharedUserId;
@NonNull
private final String[] mUsesSdkLibraries;
@NonNull
@@ -615,7 +615,7 @@
}
@DataClass.Generated.Member
- public @Nullable Integer getSharedUserId() {
+ public @Nullable int getSharedUserId() {
return mSharedUserId;
}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java
index 656c445..91e9b2f 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java
@@ -58,7 +58,7 @@
ComponentInfo componentInfo, long flags, int userId) {
if (packageState == null) return false;
- final PackageUserState userState = packageState.getUserStateOrDefault(userId);
+ final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
return PackageUserStateUtils.isMatch(userState, componentInfo, flags);
}
@@ -72,7 +72,7 @@
if (pkg == null) {
return false;
}
- final PackageUserState userState = packageState.getUserStateOrDefault(userId);
+ final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
return PackageUserStateUtils.isMatch(userState, packageState.isSystem(),
pkg.isEnabled(), component, flags);
}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
index d47c5ec..bed1401 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
@@ -18,6 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
import android.content.pm.PackageManager;
import android.content.pm.overlay.OverlayPaths;
import android.os.UserHandle;
@@ -30,15 +32,20 @@
*
* The parent of this class is {@link PackageState}, which handles non-user state, exposing this
* interface for per-user state.
+ *
+ * @hide
*/
// TODO(b/173807334): Expose API
//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
public interface PackageUserState {
+ /** @hide */
+ @NonNull
PackageUserState DEFAULT = PackageUserStateInternal.DEFAULT;
/**
* Combination of {@link #getOverlayPaths()} and {@link #getSharedLibraryOverlayPaths()}
+ * @hide
*/
@Nullable
OverlayPaths getAllOverlayPaths();
@@ -84,9 +91,11 @@
@Nullable
String getLastDisableAppCaller();
+ /** @hide */
@Nullable
OverlayPaths getOverlayPaths();
+ /** @hide */
@NonNull
Map<String, OverlayPaths> getSharedLibraryOverlayPaths();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
index bd8b3ab..bc521ce 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
+import android.content.pm.pkg.FrameworkPackageUserState;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
@@ -28,7 +29,7 @@
* still read-only and should be used inside system server code when possible over the
* implementation.
*/
-public interface PackageUserStateInternal extends PackageUserState {
+public interface PackageUserStateInternal extends PackageUserState, FrameworkPackageUserState {
PackageUserStateInternal DEFAULT = new PackageUserStateDefault();
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
index 6d978c4..c2b3cbc 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
@@ -21,11 +21,13 @@
import android.content.pm.ActivityInfo;
/** @hide **/
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
public interface ParsedActivity extends ParsedMainComponent {
/**
* Generate activity object that forwards user to App Details page automatically.
* This activity should be invisible to user and user should not know or see it.
+ * @hide
*/
@NonNull
static ParsedActivity makeAppDetailsActivity(String packageName, String processName,
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
index ff97c13..91c0b07 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
@@ -20,16 +20,16 @@
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
+import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityTaskManager;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -37,11 +37,15 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DataClass;
import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
-/** @hide **/
+/**
+ * @hide
+ **/
@DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false)
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedActivityImpl extends ParsedMainComponentImpl implements ParsedActivity {
+public class ParsedActivityImpl extends ParsedMainComponentImpl implements ParsedActivity,
+ Parcelable {
private int theme;
private int uiOptions;
@@ -112,8 +116,8 @@
}
/**
- * Generate activity object that forwards user to App Details page automatically.
- * This activity should be invisible to user and user should not know or see it.
+ * Generate activity object that forwards user to App Details page automatically. This activity
+ * should be invisible to user and user should not know or see it.
*/
@NonNull
static ParsedActivityImpl makeAppDetailsActivity(String packageName, String processName,
@@ -641,10 +645,10 @@
}
@DataClass.Generated(
- time = 1630600615936L,
+ time = 1641431949361L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedActivityImpl.java",
- inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull android.content.pm.parsing.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull android.content.pm.parsing.component.ParsedActivityImpl makeAlias(java.lang.String,android.content.pm.parsing.component.ParsedActivity)\npublic android.content.pm.parsing.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic android.content.pm.parsing.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic android.content.pm.parsing.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic android.content.pm.parsing.component.ParsedActivityImpl setPermission(java.lang.String)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedActivity]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+ inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull android.content.pm.parsing.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull android.content.pm.parsing.component.ParsedActivityImpl makeAlias(java.lang.String,android.content.pm.parsing.component.ParsedActivity)\npublic android.content.pm.parsing.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic android.content.pm.parsing.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic android.content.pm.parsing.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic android.content.pm.parsing.component.ParsedActivityImpl setPermission(java.lang.String)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
index db8815e..a6c22a18 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
@@ -18,8 +18,9 @@
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET;
+
import static com.android.server.pm.pkg.component.ComponentParseUtils.flag;
+import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -27,9 +28,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseInput.DeferredError;
import android.content.pm.parsing.result.ParseResult;
@@ -49,6 +47,9 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -353,10 +354,10 @@
final ParseResult result;
if (parser.getName().equals("intent-filter")) {
- ParseResult<ParsedIntentInfo> intentResult = parseIntentFilter(pkg, activity,
+ ParseResult<ParsedIntentInfoImpl> intentResult = parseIntentFilter(pkg, activity,
!isReceiver, visibleToEphemeral, resources, parser, input);
if (intentResult.isSuccess()) {
- ParsedIntentInfo intentInfo = intentResult.getResult();
+ ParsedIntentInfoImpl intentInfo = intentResult.getResult();
if (intentInfo != null) {
IntentFilter intentFilter = intentInfo.getIntentFilter();
activity.setOrder(Math.max(intentFilter.getOrder(), activity.getOrder()));
@@ -386,11 +387,11 @@
} else if (parser.getName().equals("property")) {
result = ParsedComponentUtils.addProperty(activity, pkg, resources, parser, input);
} else if (!isReceiver && !isAlias && parser.getName().equals("preferred")) {
- ParseResult<ParsedIntentInfo> intentResult = parseIntentFilter(pkg, activity,
+ ParseResult<ParsedIntentInfoImpl> intentResult = parseIntentFilter(pkg, activity,
true /*allowImplicitEphemeralVisibility*/, visibleToEphemeral,
resources, parser, input);
if (intentResult.isSuccess()) {
- ParsedIntentInfo intent = intentResult.getResult();
+ ParsedIntentInfoImpl intent = intentResult.getResult();
if (intent != null) {
pkg.addPreferredActivityFilter(activity.getClassName(), intent);
}
@@ -413,7 +414,7 @@
}
if (!isAlias && activity.getLaunchMode() != LAUNCH_SINGLE_INSTANCE_PER_TASK
- && activity.getMetaData() != null && activity.getMetaData().containsKey(
+ && activity.getMetaData().containsKey(
ParsingPackageUtils.METADATA_ACTIVITY_LAUNCH_MODE)) {
final String launchMode = activity.getMetaData().getString(
ParsingPackageUtils.METADATA_ACTIVITY_LAUNCH_MODE);
@@ -427,7 +428,7 @@
// set to false.
boolean canDisplayOnRemoteDevices = array.getBoolean(
R.styleable.AndroidManifestActivity_canDisplayOnRemoteDevices, true);
- if (activity.getMetaData() != null && !activity.getMetaData().getBoolean(
+ if (!activity.getMetaData().getBoolean(
ParsingPackageUtils.METADATA_CAN_DISPLAY_ON_REMOTE_DEVICES, true)) {
canDisplayOnRemoteDevices = false;
}
@@ -463,11 +464,11 @@
}
@NonNull
- private static ParseResult<ParsedIntentInfo> parseIntentFilter(ParsingPackage pkg,
+ private static ParseResult<ParsedIntentInfoImpl> parseIntentFilter(ParsingPackage pkg,
ParsedActivityImpl activity, boolean allowImplicitEphemeralVisibility,
boolean visibleToEphemeral, Resources resources, XmlResourceParser parser,
ParseInput input) throws IOException, XmlPullParserException {
- ParseResult<ParsedIntentInfo> result = ParsedMainComponentUtils.parseIntentFilter(activity,
+ ParseResult<ParsedIntentInfoImpl> result = ParsedMainComponentUtils.parseIntentFilter(activity,
pkg, resources, parser, visibleToEphemeral, true /*allowGlobs*/,
true /*allowAutoVerify*/, allowImplicitEphemeralVisibility,
true /*failOnNoActions*/, input);
@@ -475,7 +476,7 @@
return input.error(result);
}
- ParsedIntentInfo intent = result.getResult();
+ ParsedIntentInfoImpl intent = result.getResult();
if (intent != null) {
final IntentFilter intentFilter = intent.getIntentFilter();
if (intentFilter.isVisibleToInstantApp()) {
@@ -574,7 +575,7 @@
private static ParseResult<ActivityInfo.WindowLayout> resolveActivityWindowLayout(
ParsedActivity activity, ParseInput input) {
// There isn't a metadata for us to fall back. Whatever is in layout is correct.
- if (activity.getMetaData() == null || !activity.getMetaData().containsKey(
+ if (!activity.getMetaData().containsKey(
ParsingPackageUtils.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY)) {
return input.success(activity.getWindowLayout());
}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java
index 7690818..586d2c4 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java
@@ -18,10 +18,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.os.Parcelable;
/** @hide */
-public interface ParsedApexSystemService extends Parcelable {
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface ParsedApexSystemService {
@NonNull
String getName();
@@ -34,5 +34,4 @@
@Nullable
String getMaxSdkVersion();
-
}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
index 8c4d8f0..1e427d0 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
@@ -19,15 +19,18 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Parcelable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DataClass;
import com.android.internal.util.Parcelling;
-/** @hide **/
+/**
+ * @hide
+ **/
@DataClass(genGetters = true, genAidl = false, genSetters = true, genParcelable = true)
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedApexSystemServiceImpl implements ParsedApexSystemService {
+public class ParsedApexSystemServiceImpl implements ParsedApexSystemService, Parcelable {
@DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedString.class)
@NonNull
@@ -49,6 +52,7 @@
}
+
// Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
@@ -213,8 +217,8 @@
}
@DataClass.Generated.Member
- public static final @NonNull android.os.Parcelable.Creator<ParsedApexSystemServiceImpl> CREATOR
- = new android.os.Parcelable.Creator<ParsedApexSystemServiceImpl>() {
+ public static final @NonNull Parcelable.Creator<ParsedApexSystemServiceImpl> CREATOR
+ = new Parcelable.Creator<ParsedApexSystemServiceImpl>() {
@Override
public ParsedApexSystemServiceImpl[] newArray(int size) {
return new ParsedApexSystemServiceImpl[size];
@@ -227,10 +231,10 @@
};
@DataClass.Generated(
- time = 1638903241144L,
+ time = 1641431950080L,
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\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java b/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java
index 3b91f28..1a5d110 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.StringRes;
-import android.os.Parcelable;
import java.util.List;
@@ -28,7 +27,8 @@
*
* @hide
*/
-public interface ParsedAttribution extends Parcelable {
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface ParsedAttribution {
/**
* Maximum length of attribution tag
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java
index a4eb4f1..b59f511 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java
@@ -35,7 +35,7 @@
*/
@DataClass(genAidl = false, genSetters = true, genBuilder = false, genParcelable = true)
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedAttributionImpl implements ParsedAttribution {
+public class ParsedAttributionImpl implements ParsedAttribution, Parcelable {
/** Maximum amount of attributions per package */
static final int MAX_NUM_ATTRIBUTIONS = 10000;
@@ -206,10 +206,10 @@
};
@DataClass.Generated(
- time = 1627594502974L,
+ time = 1641431950829L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedAttributionImpl.java",
- inputSignatures = "static final int MAX_NUM_ATTRIBUTIONS\nprivate @android.annotation.NonNull java.lang.String tag\nprivate @android.annotation.StringRes int label\nprivate @android.annotation.NonNull java.util.List<java.lang.String> inheritFrom\nclass ParsedAttributionImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedAttribution]\n@com.android.internal.util.DataClass(genAidl=false, genSetters=true, genBuilder=false, genParcelable=true)")
+ inputSignatures = "static final int MAX_NUM_ATTRIBUTIONS\nprivate @android.annotation.NonNull java.lang.String tag\nprivate @android.annotation.StringRes int label\nprivate @android.annotation.NonNull java.util.List<java.lang.String> inheritFrom\nclass ParsedAttributionImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedAttribution, android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=false, genSetters=true, genBuilder=false, genParcelable=true)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java
index 1a8230d..5b6ecba 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java
@@ -21,13 +21,13 @@
import android.content.ComponentName;
import android.content.pm.PackageManager.Property;
import android.os.Bundle;
-import android.os.Parcelable;
import java.util.List;
import java.util.Map;
/** @hide */
-public interface ParsedComponent extends Parcelable {
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface ParsedComponent {
int getBanner();
@@ -47,7 +47,7 @@
int getLogo();
- @Nullable
+ @NonNull
Bundle getMetaData();
@NonNull
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java
index 9125e8c..c1a4b2e7 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java
@@ -25,28 +25,31 @@
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.pm.PackageManager.Property;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
import android.os.Bundle;
import android.os.Parcel;
+import android.os.Parcelable;
import android.text.TextUtils;
import android.util.ArrayMap;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DataClass;
-import com.android.internal.util.Parcelling;
import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
-/** @hide */
-@DataClass(genGetters = true, genSetters = true, genConstructor = false, genBuilder = false)
+/**
+ * @hide
+ */
+@DataClass(genGetters = true, genSetters = true, genConstructor = false, genBuilder = false,
+ genParcelable = false)
@DataClass.Suppress({"setComponentName", "setProperties", "setIntents"})
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public abstract class ParsedComponentImpl implements ParsedComponent {
+public abstract class ParsedComponentImpl implements ParsedComponent, Parcelable {
@NonNull
@DataClass.ParcelWith(ForInternedString.class)
@@ -68,7 +71,7 @@
@NonNull
@DataClass.PluralOf("intent")
- private List<ParsedIntentInfo> intents = Collections.emptyList();
+ private List<ParsedIntentInfoImpl> intents = Collections.emptyList();
@Nullable
private ComponentName componentName;
@@ -95,16 +98,18 @@
this.flags = other.getFlags();
this.packageName = other.getPackageName();
this.componentName = other.getComponentName();
- this.intents = new ArrayList<>(other.getIntents());
+ this.intents = new ArrayList<>(((ParsedComponentImpl) other).intents);
this.mProperties = new ArrayMap<>();
this.mProperties.putAll(other.getProperties());
}
- public void addIntent(ParsedIntentInfo intent) {
+ public void addIntent(ParsedIntentInfoImpl intent) {
this.intents = CollectionUtils.add(this.intents, intent);
}
- /** Add a property to the component */
+ /**
+ * Add a property to the component
+ */
public void addProperty(@NonNull Property property) {
this.mProperties = CollectionUtils.add(this.mProperties, property.getName(), property);
}
@@ -133,6 +138,18 @@
return componentName;
}
+ @NonNull
+ @Override
+ public Bundle getMetaData() {
+ return metaData == null ? Bundle.EMPTY : metaData;
+ }
+
+ @NonNull
+ @Override
+ public List<ParsedIntentInfo> getIntents() {
+ return new ArrayList<>(intents);
+ }
+
@Override
public int describeContents() {
return 0;
@@ -149,7 +166,7 @@
dest.writeInt(this.getDescriptionRes());
dest.writeInt(this.getFlags());
sForInternedString.parcel(this.packageName, dest, flags);
- dest.writeTypedList(this.getIntents());
+ dest.writeTypedList(this.intents);
dest.writeBundle(this.metaData);
dest.writeMap(this.mProperties);
}
@@ -234,16 +251,6 @@
}
@DataClass.Generated.Member
- public @NonNull List<ParsedIntentInfo> getIntents() {
- return intents;
- }
-
- @DataClass.Generated.Member
- public @Nullable Bundle getMetaData() {
- return metaData;
- }
-
- @DataClass.Generated.Member
public @NonNull Map<String,Property> getProperties() {
return mProperties;
}
@@ -297,10 +304,10 @@
}
@DataClass.Generated(
- time = 1627680195484L,
+ time = 1641414207885L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedComponentImpl.java",
- inputSignatures = "private @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String name\nprivate int icon\nprivate int labelRes\nprivate @android.annotation.Nullable java.lang.CharSequence nonLocalizedLabel\nprivate int logo\nprivate int banner\nprivate int descriptionRes\nprivate int flags\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String packageName\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"intent\") java.util.List<android.content.pm.parsing.component.ParsedIntentInfo> intents\nprivate @android.annotation.Nullable android.content.ComponentName componentName\nprivate @android.annotation.Nullable android.os.Bundle metaData\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.PackageManager.Property> mProperties\npublic void addIntent(android.content.pm.parsing.component.ParsedIntentInfo)\npublic void addProperty(android.content.pm.PackageManager.Property)\npublic android.content.pm.parsing.component.ParsedComponentImpl setName(java.lang.String)\npublic @android.annotation.CallSuper void setPackageName(java.lang.String)\npublic @java.lang.Override @android.annotation.NonNull android.content.ComponentName getComponentName()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedComponentImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedComponent]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genConstructor=false, genBuilder=false)")
+ inputSignatures = "private @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String name\nprivate int icon\nprivate int labelRes\nprivate @android.annotation.Nullable java.lang.CharSequence nonLocalizedLabel\nprivate int logo\nprivate int banner\nprivate int descriptionRes\nprivate int flags\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String packageName\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"intent\") java.util.List<android.content.pm.parsing.component.ParsedIntentInfoImpl> intents\nprivate @android.annotation.Nullable android.content.ComponentName componentName\nprivate @android.annotation.Nullable android.os.Bundle metaData\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.PackageManager.Property> mProperties\n void addIntent(android.content.pm.parsing.component.ParsedIntentInfoImpl)\n void addProperty(android.content.pm.PackageManager.Property)\npublic android.content.pm.parsing.component.ParsedComponentImpl setName(java.lang.String)\npublic @android.annotation.CallSuper void setPackageName(java.lang.String)\npublic @java.lang.Override @android.annotation.NonNull android.content.ComponentName getComponentName()\npublic @android.annotation.NonNull @java.lang.Override android.os.Bundle getMetaData()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<android.content.pm.parsing.component.ParsedIntentInfo> getIntents()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedComponentImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedComponent, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genConstructor=false, genBuilder=false, genParcelable=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java
index e208854..c6b9b1a 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java
@@ -21,9 +21,6 @@
import android.annotation.NonNull;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.Property;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.res.Resources;
@@ -34,6 +31,9 @@
import android.util.TypedValue;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
/** @hide */
class ParsedComponentUtils {
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java
index a0eae8c..c325d8d 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
/** @hide */
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
public interface ParsedInstrumentation extends ParsedComponent {
@Nullable
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java
index c8baa9e..23ebdc8 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java
@@ -33,7 +33,7 @@
@DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false)
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public class ParsedInstrumentationImpl extends ParsedComponentImpl implements
- ParsedInstrumentation {
+ ParsedInstrumentation, Parcelable {
@Nullable
@DataClass.ParcelWith(ForInternedString.class)
@@ -165,10 +165,10 @@
}
@DataClass.Generated(
- time = 1627595809880L,
+ time = 1641431951575L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedInstrumentationImpl.java",
- inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetPackage\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetProcesses\nprivate boolean handleProfiling\nprivate boolean functionalTest\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedInstrumentationImpl> CREATOR\npublic android.content.pm.parsing.component.ParsedInstrumentationImpl setTargetPackage(java.lang.String)\npublic android.content.pm.parsing.component.ParsedInstrumentationImpl setTargetProcesses(java.lang.String)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedInstrumentationImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedInstrumentation]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+ inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetPackage\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetProcesses\nprivate boolean handleProfiling\nprivate boolean functionalTest\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedInstrumentationImpl> CREATOR\npublic android.content.pm.parsing.component.ParsedInstrumentationImpl setTargetPackage(java.lang.String)\npublic android.content.pm.parsing.component.ParsedInstrumentationImpl setTargetProcesses(java.lang.String)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedInstrumentationImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedInstrumentation, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java
index 51e1428..c63a689 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java
@@ -19,7 +19,6 @@
import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET;
import android.annotation.NonNull;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.res.Resources;
@@ -27,12 +26,15 @@
import android.content.res.XmlResourceParser;
import com.android.internal.R;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
-/** @hide */
+/**
+ * @hide
+ */
public class ParsedInstrumentationUtils {
@NonNull
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java
index 57b486a..a7f7b00 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java
@@ -19,10 +19,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.IntentFilter;
-import android.os.Parcelable;
/** @hide **/
-public interface ParsedIntentInfo extends Parcelable {
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface ParsedIntentInfo {
boolean isHasDefault();
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java
index 1c816da..5b6375d 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java
@@ -32,7 +32,7 @@
genBuilder = false, genConstructor = false)
@DataClass.Suppress({"setIntentFilter"})
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedIntentInfoImpl implements ParsedIntentInfo {
+public class ParsedIntentInfoImpl implements ParsedIntentInfo, Parcelable {
private boolean mHasDefault;
private int mLabelRes;
@@ -169,10 +169,10 @@
};
@DataClass.Generated(
- time = 1627691925408L,
+ time = 1641431952314L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedIntentInfoImpl.java",
- inputSignatures = "private boolean mHasDefault\nprivate int mLabelRes\nprivate @android.annotation.Nullable java.lang.CharSequence mNonLocalizedLabel\nprivate int mIcon\nprivate @android.annotation.NonNull android.content.IntentFilter mIntentFilter\nclass ParsedIntentInfoImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedIntentInfo]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false, genConstructor=false)")
+ inputSignatures = "private boolean mHasDefault\nprivate int mLabelRes\nprivate @android.annotation.Nullable java.lang.CharSequence mNonLocalizedLabel\nprivate int mIcon\nprivate @android.annotation.NonNull android.content.IntentFilter mIntentFilter\nclass ParsedIntentInfoImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedIntentInfo, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false, genConstructor=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java
index 1e6f630..4f0a504 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java
@@ -21,9 +21,6 @@
import android.annotation.NonNull;
import android.content.Intent;
import android.content.IntentFilter;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.res.Resources;
@@ -34,6 +31,9 @@
import android.util.TypedValue;
import com.android.internal.R;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -49,7 +49,7 @@
public static final boolean DEBUG = false;
@NonNull
- public static ParseResult<ParsedIntentInfo> parseIntentInfo(String className,
+ public static ParseResult<ParsedIntentInfoImpl> parseIntentInfo(String className,
ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean allowGlobs,
boolean allowAutoVerify, ParseInput input)
throws XmlPullParserException, IOException {
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java
index 8c1d6c8..b926d53 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java
@@ -16,17 +16,20 @@
package com.android.server.pm.pkg.component;
+import android.annotation.NonNull;
import android.annotation.Nullable;
/** @hide */
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
public interface ParsedMainComponent extends ParsedComponent {
- @Nullable
+ @NonNull
String[] getAttributionTags();
/**
* A main component's name is a class name. This makes code slightly more readable.
*/
+ @NonNull
String getClassName();
boolean isDirectBootAware();
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java
index 9b57f48..a2f2f93 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java
@@ -18,6 +18,7 @@
import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -27,10 +28,15 @@
import com.android.internal.util.DataClass;
import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
-/** @hide */
+import libcore.util.EmptyArray;
+
+/**
+ * @hide
+ */
@DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false)
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedMainComponentImpl extends ParsedComponentImpl implements ParsedMainComponent {
+public class ParsedMainComponentImpl extends ParsedComponentImpl implements ParsedMainComponent,
+ Parcelable {
@Nullable
@DataClass.ParcelWith(ForInternedString.class)
@@ -71,6 +77,12 @@
return getName();
}
+ @NonNull
+ @Override
+ public String[] getAttributionTags() {
+ return attributionTags == null ? EmptyArray.STRING : attributionTags;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -178,51 +190,46 @@
}
@DataClass.Generated.Member
- public @Nullable String[] getAttributionTags() {
- return attributionTags;
- }
-
- @DataClass.Generated.Member
- public @android.annotation.NonNull ParsedMainComponentImpl setDirectBootAware( boolean value) {
+ public @NonNull ParsedMainComponentImpl setDirectBootAware( boolean value) {
directBootAware = value;
return this;
}
@DataClass.Generated.Member
- public @android.annotation.NonNull ParsedMainComponentImpl setEnabled( boolean value) {
+ public @NonNull ParsedMainComponentImpl setEnabled( boolean value) {
enabled = value;
return this;
}
@DataClass.Generated.Member
- public @android.annotation.NonNull ParsedMainComponentImpl setExported( boolean value) {
+ public @NonNull ParsedMainComponentImpl setExported( boolean value) {
exported = value;
return this;
}
@DataClass.Generated.Member
- public @android.annotation.NonNull ParsedMainComponentImpl setOrder( int value) {
+ public @NonNull ParsedMainComponentImpl setOrder( int value) {
order = value;
return this;
}
@DataClass.Generated.Member
- public @android.annotation.NonNull ParsedMainComponentImpl setSplitName(@android.annotation.NonNull String value) {
+ public @NonNull ParsedMainComponentImpl setSplitName(@NonNull String value) {
splitName = value;
return this;
}
@DataClass.Generated.Member
- public @android.annotation.NonNull ParsedMainComponentImpl setAttributionTags(@android.annotation.NonNull String... value) {
+ public @NonNull ParsedMainComponentImpl setAttributionTags(@NonNull String... value) {
attributionTags = value;
return this;
}
@DataClass.Generated(
- time = 1627324857874L,
+ time = 1641414540422L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedMainComponentImpl.java",
- inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String processName\nprivate boolean directBootAware\nprivate boolean enabled\nprivate boolean exported\nprivate int order\nprivate @android.annotation.Nullable java.lang.String splitName\nprivate @android.annotation.Nullable java.lang.String[] attributionTags\npublic static final android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedMainComponentImpl> CREATOR\npublic android.content.pm.parsing.component.ParsedMainComponentImpl setProcessName(java.lang.String)\npublic java.lang.String getClassName()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedMainComponentImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedMainComponent]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+ inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String processName\nprivate boolean directBootAware\nprivate boolean enabled\nprivate boolean exported\nprivate int order\nprivate @android.annotation.Nullable java.lang.String splitName\nprivate @android.annotation.Nullable java.lang.String[] attributionTags\npublic static final android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedMainComponentImpl> CREATOR\npublic android.content.pm.parsing.component.ParsedMainComponentImpl setProcessName(java.lang.String)\npublic java.lang.String getClassName()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getAttributionTags()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedMainComponentImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedMainComponent, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java
index 2a3e653..f52ad13 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java
@@ -21,8 +21,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.IntentFilter;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.res.Configuration;
@@ -33,6 +31,8 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
import org.xmlpull.v1.XmlPullParserException;
@@ -110,13 +110,13 @@
return input.success(component);
}
- static ParseResult<ParsedIntentInfo> parseIntentFilter(
+ static ParseResult<ParsedIntentInfoImpl> parseIntentFilter(
ParsedMainComponent mainComponent,
ParsingPackage pkg, Resources resources, XmlResourceParser parser,
boolean visibleToEphemeral, boolean allowGlobs, boolean allowAutoVerify,
boolean allowImplicitEphemeralVisibility, boolean failOnNoActions,
ParseInput input) throws IOException, XmlPullParserException {
- ParseResult<ParsedIntentInfo> intentResult = ParsedIntentInfoUtils.parseIntentInfo(
+ ParseResult<ParsedIntentInfoImpl> intentResult = ParsedIntentInfoUtils.parseIntentInfo(
mainComponent.getName(), pkg, resources, parser, allowGlobs,
allowAutoVerify, input);
if (intentResult.isError()) {
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java
index 4a6d2c3..dc5347a 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java
@@ -16,11 +16,13 @@
package com.android.server.pm.pkg.component;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import java.util.Set;
/** @hide */
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
public interface ParsedPermission extends ParsedComponent {
@Nullable
@@ -29,7 +31,7 @@
@Nullable
String getGroup();
- @Nullable
+ @NonNull
Set<String> getKnownCerts();
@Nullable
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java
index 73b5ffa..64f4fbd 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java
@@ -17,15 +17,16 @@
package com.android.server.pm.pkg.component;
/** @hide */
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
public interface ParsedPermissionGroup extends ParsedComponent {
- int getBackgroundRequestDetailResourceId();
+ int getBackgroundRequestDetailRes();
- int getBackgroundRequestResourceId();
+ int getBackgroundRequestRes();
int getPriority();
- int getRequestDetailResourceId();
+ int getRequestDetailRes();
int getRequestRes();
}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java
index f47fb75..59075de 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java
@@ -16,21 +16,25 @@
package com.android.server.pm.pkg.component;
+import android.annotation.NonNull;
import android.os.Parcel;
+import android.os.Parcelable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DataClass;
-/** @hide */
+/**
+ * @hide
+ */
@DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = true,
genAidl = false)
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public class ParsedPermissionGroupImpl extends ParsedComponentImpl implements
- ParsedPermissionGroup {
+ ParsedPermissionGroup, Parcelable {
- private int requestDetailResourceId;
- private int backgroundRequestResourceId;
- private int backgroundRequestDetailResourceId;
+ private int requestDetailRes;
+ private int backgroundRequestRes;
+ private int backgroundRequestDetailRes;
private int requestRes;
private int priority;
@@ -43,6 +47,25 @@
public ParsedPermissionGroupImpl() {
}
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(requestDetailRes);
+ dest.writeInt(backgroundRequestRes);
+ dest.writeInt(backgroundRequestDetailRes);
+ dest.writeInt(requestRes);
+ dest.writeInt(priority);
+ }
+
+ protected ParsedPermissionGroupImpl(@NonNull Parcel in) {
+ super(in);
+ this.requestDetailRes = in.readInt();
+ this.backgroundRequestRes = in.readInt();
+ this.backgroundRequestDetailRes = in.readInt();
+ this.requestRes = in.readInt();
+ this.priority = in.readInt();
+ }
+
// Code below generated by codegen v1.0.23.
@@ -51,7 +74,7 @@
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedPermissionGroupImpl.java
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -60,14 +83,14 @@
@DataClass.Generated.Member
public ParsedPermissionGroupImpl(
- int requestDetailResourceId,
- int backgroundRequestResourceId,
- int backgroundRequestDetailResourceId,
+ int requestDetailRes,
+ int backgroundRequestRes,
+ int backgroundRequestDetailRes,
int requestRes,
int priority) {
- this.requestDetailResourceId = requestDetailResourceId;
- this.backgroundRequestResourceId = backgroundRequestResourceId;
- this.backgroundRequestDetailResourceId = backgroundRequestDetailResourceId;
+ this.requestDetailRes = requestDetailRes;
+ this.backgroundRequestRes = backgroundRequestRes;
+ this.backgroundRequestDetailRes = backgroundRequestDetailRes;
this.requestRes = requestRes;
this.priority = priority;
@@ -75,18 +98,18 @@
}
@DataClass.Generated.Member
- public int getRequestDetailResourceId() {
- return requestDetailResourceId;
+ public int getRequestDetailRes() {
+ return requestDetailRes;
}
@DataClass.Generated.Member
- public int getBackgroundRequestResourceId() {
- return backgroundRequestResourceId;
+ public int getBackgroundRequestRes() {
+ return backgroundRequestRes;
}
@DataClass.Generated.Member
- public int getBackgroundRequestDetailResourceId() {
- return backgroundRequestDetailResourceId;
+ public int getBackgroundRequestDetailRes() {
+ return backgroundRequestDetailRes;
}
@DataClass.Generated.Member
@@ -100,97 +123,58 @@
}
@DataClass.Generated.Member
- public @android.annotation.NonNull ParsedPermissionGroupImpl setRequestDetailResourceId( int value) {
- requestDetailResourceId = value;
+ public @NonNull ParsedPermissionGroupImpl setRequestDetailRes( int value) {
+ requestDetailRes = value;
return this;
}
@DataClass.Generated.Member
- public @android.annotation.NonNull ParsedPermissionGroupImpl setBackgroundRequestResourceId( int value) {
- backgroundRequestResourceId = value;
+ public @NonNull ParsedPermissionGroupImpl setBackgroundRequestRes( int value) {
+ backgroundRequestRes = value;
return this;
}
@DataClass.Generated.Member
- public @android.annotation.NonNull ParsedPermissionGroupImpl setBackgroundRequestDetailResourceId( int value) {
- backgroundRequestDetailResourceId = value;
+ public @NonNull ParsedPermissionGroupImpl setBackgroundRequestDetailRes( int value) {
+ backgroundRequestDetailRes = value;
return this;
}
@DataClass.Generated.Member
- public @android.annotation.NonNull ParsedPermissionGroupImpl setRequestRes( int value) {
+ public @NonNull ParsedPermissionGroupImpl setRequestRes( int value) {
requestRes = value;
return this;
}
@DataClass.Generated.Member
- public @android.annotation.NonNull ParsedPermissionGroupImpl setPriority( int value) {
+ public @NonNull ParsedPermissionGroupImpl setPriority( int value) {
priority = value;
return this;
}
@Override
@DataClass.Generated.Member
- public void writeToParcel(@android.annotation.NonNull Parcel dest, int flags) {
- // You can override field parcelling by defining methods like:
- // void parcelFieldName(Parcel dest, int flags) { ... }
-
- super.writeToParcel(dest, flags);
-
- dest.writeInt(requestDetailResourceId);
- dest.writeInt(backgroundRequestResourceId);
- dest.writeInt(backgroundRequestDetailResourceId);
- dest.writeInt(requestRes);
- dest.writeInt(priority);
- }
-
- @Override
- @DataClass.Generated.Member
public int describeContents() { return 0; }
- /** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- protected ParsedPermissionGroupImpl(@android.annotation.NonNull Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- super(in);
-
- int _requestDetailResourceId = in.readInt();
- int _backgroundRequestResourceId = in.readInt();
- int _backgroundRequestDetailResourceId = in.readInt();
- int _requestRes = in.readInt();
- int _priority = in.readInt();
-
- this.requestDetailResourceId = _requestDetailResourceId;
- this.backgroundRequestResourceId = _backgroundRequestResourceId;
- this.backgroundRequestDetailResourceId = _backgroundRequestDetailResourceId;
- this.requestRes = _requestRes;
- this.priority = _priority;
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public static final @android.annotation.NonNull android.os.Parcelable.Creator<ParsedPermissionGroupImpl> CREATOR
- = new android.os.Parcelable.Creator<ParsedPermissionGroupImpl>() {
+ public static final @NonNull Parcelable.Creator<ParsedPermissionGroupImpl> CREATOR
+ = new Parcelable.Creator<ParsedPermissionGroupImpl>() {
@Override
public ParsedPermissionGroupImpl[] newArray(int size) {
return new ParsedPermissionGroupImpl[size];
}
@Override
- public ParsedPermissionGroupImpl createFromParcel(@android.annotation.NonNull Parcel in) {
+ public ParsedPermissionGroupImpl createFromParcel(@NonNull Parcel in) {
return new ParsedPermissionGroupImpl(in);
}
};
@DataClass.Generated(
- time = 1627602253988L,
+ time = 1642132854167L,
codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedPermissionGroupImpl.java",
- inputSignatures = "private int requestDetailResourceId\nprivate int backgroundRequestResourceId\nprivate int backgroundRequestDetailResourceId\nprivate int requestRes\nprivate int priority\npublic java.lang.String toString()\nclass ParsedPermissionGroupImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedPermissionGroup]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
+ sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java",
+ inputSignatures = "private int requestDetailRes\nprivate int backgroundRequestRes\nprivate int backgroundRequestDetailRes\nprivate int requestRes\nprivate int priority\npublic java.lang.String toString()\npublic @java.lang.Override @com.android.internal.util.DataClass.Generated.Member void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionGroupImpl extends com.android.server.pm.pkg.component.ParsedComponentImpl implements [com.android.server.pm.pkg.component.ParsedPermissionGroup, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java
index 98007ff..70986c3 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java
@@ -29,13 +29,17 @@
import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
import com.android.internal.util.Parcelling.BuiltIn.ForStringSet;
+import java.util.Collections;
import java.util.Locale;
import java.util.Set;
-/** @hide */
+/**
+ * @hide
+ */
@DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false)
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedPermissionImpl extends ParsedComponentImpl implements ParsedPermission {
+public class ParsedPermissionImpl extends ParsedComponentImpl implements ParsedPermission,
+ Parcelable {
private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
@@ -56,14 +60,8 @@
public ParsedPermissionImpl() {
}
- public ParsedPermissionImpl(ParsedPermission other) {
- super(other);
- this.backgroundPermission = other.getBackgroundPermission();
- this.group = other.getGroup();
- this.requestRes = other.getRequestRes();
- this.protectionLevel = other.getProtectionLevel();
- this.tree = other.isTree();
- this.parsedPermissionGroup = other.getParsedPermissionGroup();
+ public ParsedPermissionGroup getParsedPermissionGroup() {
+ return parsedPermissionGroup;
}
public ParsedPermissionImpl setGroup(String group) {
@@ -84,6 +82,12 @@
}
}
+ @NonNull
+ @Override
+ public Set<String> getKnownCerts() {
+ return knownCerts == null ? Collections.emptySet() : knownCerts;
+ }
+
public String toString() {
return "Permission{"
+ Integer.toHexString(System.identityHashCode(this))
@@ -103,7 +107,7 @@
dest.writeInt(this.requestRes);
dest.writeInt(this.protectionLevel);
dest.writeBoolean(this.tree);
- dest.writeParcelable(this.parsedPermissionGroup, flags);
+ dest.writeParcelable((ParsedPermissionGroupImpl) this.parsedPermissionGroup, flags);
sForStringSet.parcel(knownCerts, dest, flags);
}
@@ -115,7 +119,7 @@
this.protectionLevel = in.readInt();
this.tree = in.readBoolean();
this.parsedPermissionGroup = in.readParcelable(ParsedPermissionGroup.class.getClassLoader(),
- ParsedPermissionGroup.class);
+ ParsedPermissionGroupImpl.class);
this.knownCerts = sForStringSet.unparcel(in);
}
@@ -155,7 +159,7 @@
int requestRes,
int protectionLevel,
boolean tree,
- @Nullable ParsedPermissionGroup parsedPermissionGroup,
+ @Nullable ParsedPermissionGroupImpl parsedPermissionGroup,
@Nullable Set<String> knownCerts) {
this.backgroundPermission = backgroundPermission;
this.group = group;
@@ -194,16 +198,6 @@
}
@DataClass.Generated.Member
- public @Nullable ParsedPermissionGroup getParsedPermissionGroup() {
- return parsedPermissionGroup;
- }
-
- @DataClass.Generated.Member
- public @Nullable Set<String> getKnownCerts() {
- return knownCerts;
- }
-
- @DataClass.Generated.Member
public @NonNull ParsedPermissionImpl setBackgroundPermission(@NonNull String value) {
backgroundPermission = value;
return this;
@@ -240,10 +234,10 @@
}
@DataClass.Generated(
- time = 1627598236506L,
+ time = 1641414649731L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java",
- inputSignatures = "private static com.android.internal.util.Parcelling.BuiltIn.ForStringSet sForStringSet\nprivate @android.annotation.Nullable java.lang.String backgroundPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String group\nprivate int requestRes\nprivate int protectionLevel\nprivate boolean tree\nprivate @android.annotation.Nullable android.content.pm.parsing.component.ParsedPermissionGroup parsedPermissionGroup\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> knownCerts\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedPermissionImpl> CREATOR\npublic android.content.pm.parsing.component.ParsedPermissionImpl setGroup(java.lang.String)\nprotected void setKnownCert(java.lang.String)\nprotected void setKnownCerts(java.lang.String[])\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedPermission]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+ inputSignatures = "private static com.android.internal.util.Parcelling.BuiltIn.ForStringSet sForStringSet\nprivate @android.annotation.Nullable java.lang.String backgroundPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String group\nprivate int requestRes\nprivate int protectionLevel\nprivate boolean tree\nprivate @android.annotation.Nullable android.content.pm.parsing.component.ParsedPermissionGroupImpl parsedPermissionGroup\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> knownCerts\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedPermissionImpl> CREATOR\npublic android.content.pm.parsing.component.ParsedPermissionGroup getParsedPermissionGroup()\npublic android.content.pm.parsing.component.ParsedPermissionImpl setGroup(java.lang.String)\nprotected void setKnownCert(java.lang.String)\nprotected void setKnownCerts(java.lang.String[])\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownCerts()\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedPermission, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java
index 8562fdf..f2e2f4f 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java
@@ -20,8 +20,6 @@
import android.annotation.NonNull;
import android.content.pm.PermissionInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.res.Resources;
@@ -31,13 +29,17 @@
import android.util.Slog;
import com.android.internal.R;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.List;
-/** @hide */
+/**
+ * @hide
+ */
public class ParsedPermissionUtils {
private static final String TAG = ParsingUtils.TAG;
@@ -107,7 +109,7 @@
permission.setKnownCert(knownCert);
}
}
- if (permission.getKnownCerts() == null) {
+ if (permission.getKnownCerts().isEmpty()) {
Slog.w(TAG, packageName + " defines a knownSigner permission but"
+ " the provided knownCerts resource is null");
}
@@ -228,9 +230,9 @@
}
// @formatter:off
- permissionGroup.setRequestDetailResourceId(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_requestDetail, 0))
- .setBackgroundRequestResourceId(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequest, 0))
- .setBackgroundRequestDetailResourceId(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequestDetail, 0))
+ permissionGroup.setRequestDetailRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_requestDetail, 0))
+ .setBackgroundRequestRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequest, 0))
+ .setBackgroundRequestDetailRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequestDetail, 0))
.setRequestRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_request, 0))
.setPriority(sa.getInt(R.styleable.AndroidManifestPermissionGroup_priority, 0))
.setFlags(sa.getInt(R.styleable.AndroidManifestPermissionGroup_permissionGroupFlags,0));
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java
index ff391ff..608d08e 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java
@@ -17,14 +17,15 @@
package com.android.server.pm.pkg.component;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.content.pm.ApplicationInfo;
-import android.os.Parcelable;
import android.util.ArrayMap;
import java.util.Set;
/** @hide */
-public interface ParsedProcess extends Parcelable {
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface ParsedProcess {
@NonNull
Set<String> getDeniedPermissions();
@@ -44,6 +45,7 @@
* It's a map, because in shared processes, different packages can have different application
* classes.
*/
+ @SuppressLint("ConcreteCollection")
@NonNull
ArrayMap<String, String> getAppClassNamesByPackage();
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java
index 96560c7..6d52f65 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java
@@ -36,7 +36,7 @@
@DataClass(genGetters = true, genSetters = true, genParcelable = true, genAidl = false,
genBuilder = false)
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedProcessImpl implements ParsedProcess {
+public class ParsedProcessImpl implements ParsedProcess, Parcelable {
@NonNull
private String name;
@@ -305,10 +305,10 @@
};
@DataClass.Generated(
- time = 1639076603310L,
+ time = 1641431953775L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java",
- inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\npublic void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedProcess]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
+ inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\npublic void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedProcess, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java
index d03f153..4f4c2d5 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java
@@ -18,8 +18,6 @@
import android.annotation.NonNull;
import android.content.pm.ApplicationInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.res.Resources;
@@ -31,6 +29,8 @@
import com.android.internal.R;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.XmlUtils;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java
index 8cc6fc7..ba85e07 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java
@@ -16,11 +16,15 @@
package com.android.server.pm.pkg.component;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.PathPermission;
import android.os.PatternMatcher;
+import java.util.List;
+
/** @hide **/
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
public interface ParsedProvider extends ParsedMainComponent {
@Nullable
@@ -30,13 +34,17 @@
boolean isMultiProcess();
- @Nullable PathPermission[] getPathPermissions();
+ @NonNull
+ List<PathPermission> getPathPermissions();
- @Nullable String getReadPermission();
+ @Nullable
+ String getReadPermission();
- @Nullable PatternMatcher[] getUriPermissionPatterns();
+ @NonNull
+ List<PatternMatcher> getUriPermissionPatterns();
- @Nullable String getWritePermission();
+ @Nullable
+ String getWritePermission();
boolean isForceUriPermissions();
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java
index e04fc86..e77ecb3 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java
@@ -28,13 +28,22 @@
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DataClass;
import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
-/** @hide **/
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @hide
+ **/
@DataClass(genSetters = true, genGetters = true, genParcelable = false, genBuilder = false)
+@DataClass.Suppress({"setUriPermissionPatterns", "setPathPermissions"})
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedProviderImpl extends ParsedMainComponentImpl implements ParsedProvider {
+public class ParsedProviderImpl extends ParsedMainComponentImpl implements ParsedProvider,
+ Parcelable {
@Nullable
@DataClass.ParcelWith(ForInternedString.class)
@@ -50,10 +59,10 @@
private boolean forceUriPermissions;
private boolean multiProcess;
private int initOrder;
- @Nullable
- private PatternMatcher[] uriPermissionPatterns;
- @Nullable
- private PathPermission[] pathPermissions;
+ @NonNull
+ private List<PatternMatcher> uriPermissionPatterns = Collections.emptyList();
+ @NonNull
+ private List<PathPermission> pathPermissions = Collections.emptyList();
public ParsedProviderImpl(ParsedProvider other) {
super(other);
@@ -66,8 +75,8 @@
this.forceUriPermissions = other.isForceUriPermissions();
this.multiProcess = other.isMultiProcess();
this.initOrder = other.getInitOrder();
- this.uriPermissionPatterns = other.getUriPermissionPatterns();
- this.pathPermissions = other.getPathPermissions();
+ this.uriPermissionPatterns = new ArrayList<>(other.getUriPermissionPatterns());
+ this.pathPermissions = new ArrayList<>(other.getPathPermissions());
}
public ParsedProviderImpl setReadPermission(String readPermission) {
@@ -84,6 +93,18 @@
return this;
}
+ @NonNull
+ public ParsedProviderImpl addUriPermissionPattern(@NonNull PatternMatcher value) {
+ uriPermissionPatterns = CollectionUtils.add(uriPermissionPatterns, value);
+ return this;
+ }
+
+ @NonNull
+ public ParsedProviderImpl addPathPermission(@NonNull PathPermission value) {
+ pathPermissions = CollectionUtils.add(pathPermissions, value);
+ return this;
+ }
+
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append("Provider{");
@@ -110,8 +131,8 @@
dest.writeBoolean(this.forceUriPermissions);
dest.writeBoolean(this.multiProcess);
dest.writeInt(this.initOrder);
- dest.writeTypedArray(this.uriPermissionPatterns, flags);
- dest.writeTypedArray(this.pathPermissions, flags);
+ dest.writeTypedList(this.uriPermissionPatterns, flags);
+ dest.writeTypedList(this.pathPermissions, flags);
}
public ParsedProviderImpl() {
@@ -127,8 +148,8 @@
this.forceUriPermissions = in.readBoolean();
this.multiProcess = in.readBoolean();
this.initOrder = in.readInt();
- this.uriPermissionPatterns = in.createTypedArray(PatternMatcher.CREATOR);
- this.pathPermissions = in.createTypedArray(PathPermission.CREATOR);
+ this.uriPermissionPatterns = in.createTypedArrayList(PatternMatcher.CREATOR);
+ this.pathPermissions = in.createTypedArrayList(PathPermission.CREATOR);
}
@NonNull
@@ -153,7 +174,7 @@
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedProviderImpl.java
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -170,8 +191,8 @@
boolean forceUriPermissions,
boolean multiProcess,
int initOrder,
- @Nullable PatternMatcher[] uriPermissionPatterns,
- @Nullable PathPermission[] pathPermissions) {
+ @NonNull List<PatternMatcher> uriPermissionPatterns,
+ @NonNull List<PathPermission> pathPermissions) {
this.authority = authority;
this.syncable = syncable;
this.readPermission = readPermission;
@@ -181,7 +202,11 @@
this.multiProcess = multiProcess;
this.initOrder = initOrder;
this.uriPermissionPatterns = uriPermissionPatterns;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, uriPermissionPatterns);
this.pathPermissions = pathPermissions;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, pathPermissions);
// onConstructed(); // You can define this method to get a callback
}
@@ -227,12 +252,12 @@
}
@DataClass.Generated.Member
- public @Nullable PatternMatcher[] getUriPermissionPatterns() {
+ public @NonNull List<PatternMatcher> getUriPermissionPatterns() {
return uriPermissionPatterns;
}
@DataClass.Generated.Member
- public @Nullable PathPermission[] getPathPermissions() {
+ public @NonNull List<PathPermission> getPathPermissions() {
return pathPermissions;
}
@@ -272,23 +297,11 @@
return this;
}
- @DataClass.Generated.Member
- public @NonNull ParsedProviderImpl setUriPermissionPatterns(@NonNull PatternMatcher... value) {
- uriPermissionPatterns = value;
- return this;
- }
-
- @DataClass.Generated.Member
- public @NonNull ParsedProviderImpl setPathPermissions(@NonNull PathPermission... value) {
- pathPermissions = value;
- return this;
- }
-
@DataClass.Generated(
- time = 1627590522169L,
+ time = 1642560323360L,
codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedProviderImpl.java",
- inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate boolean grantUriPermissions\nprivate boolean forceUriPermissions\nprivate boolean multiProcess\nprivate int initOrder\nprivate @android.annotation.Nullable android.os.PatternMatcher[] uriPermissionPatterns\nprivate @android.annotation.Nullable android.content.pm.PathPermission[] pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedProviderImpl> CREATOR\npublic android.content.pm.parsing.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic android.content.pm.parsing.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedProvider]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
+ sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java",
+ inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate boolean grantUriPermissions\nprivate boolean forceUriPermissions\nprivate boolean multiProcess\nprivate int initOrder\nprivate @android.annotation.NonNull java.util.List<android.os.PatternMatcher> uriPermissionPatterns\nprivate @android.annotation.NonNull java.util.List<android.content.pm.PathPermission> pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedProviderImpl> CREATOR\npublic com.android.server.pm.pkg.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic com.android.server.pm.pkg.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addUriPermissionPattern(android.os.PatternMatcher)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addPathPermission(android.content.pm.PathPermission)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.server.pm.pkg.component.ParsedProvider, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java
index 9d3129b..4cb4dd0 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java
@@ -16,16 +16,14 @@
package com.android.server.pm.pkg.component;
-import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER;
import static com.android.server.pm.pkg.component.ComponentParseUtils.flag;
+import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.IntentFilter;
import android.content.pm.PathPermission;
import android.content.pm.ProviderInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.res.Resources;
@@ -36,6 +34,8 @@
import android.util.Slog;
import com.android.internal.R;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -173,14 +173,14 @@
final ParseResult result;
switch (name) {
case "intent-filter":
- ParseResult<ParsedIntentInfo> intentResult = ParsedMainComponentUtils
+ ParseResult<ParsedIntentInfoImpl> intentResult = ParsedMainComponentUtils
.parseIntentFilter(provider, pkg, res, parser, visibleToEphemeral,
true /*allowGlobs*/, false /*allowAutoVerify*/,
false /*allowImplicitEphemeralVisibility*/,
false /*failOnNoActions*/, input);
result = intentResult;
if (intentResult.isSuccess()) {
- ParsedIntentInfo intent = intentResult.getResult();
+ ParsedIntentInfoImpl intent = intentResult.getResult();
IntentFilter intentFilter = intent.getIntentFilter();
provider.setOrder(Math.max(intentFilter.getOrder(), provider.getOrder()));
provider.addIntent(intent);
@@ -253,16 +253,7 @@
}
if (pa != null) {
- if (provider.getUriPermissionPatterns() == null) {
- provider.setUriPermissionPatterns(new PatternMatcher[1]);
- provider.getUriPermissionPatterns()[0] = pa;
- } else {
- final int N = provider.getUriPermissionPatterns().length;
- PatternMatcher[] newp = new PatternMatcher[N + 1];
- System.arraycopy(provider.getUriPermissionPatterns(), 0, newp, 0, N);
- newp[N] = pa;
- provider.setUriPermissionPatterns(newp);
- }
+ provider.addUriPermissionPattern(pa);
provider.setGrantUriPermissions(true);
} else {
if (RIGID_PARSER) {
@@ -357,16 +348,7 @@
}
if (pa != null) {
- if (provider.getPathPermissions() == null) {
- provider.setPathPermissions(new PathPermission[1]);
- provider.getPathPermissions()[0] = pa;
- } else {
- final int N = provider.getPathPermissions().length;
- PathPermission[] newp = new PathPermission[N + 1];
- System.arraycopy(provider.getPathPermissions(), 0, newp, 0, N);
- newp[N] = pa;
- provider.setPathPermissions(newp);
- }
+ provider.addPathPermission(pa);
} else {
if (RIGID_PARSER) {
return input.error(
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedService.java b/services/core/java/com/android/server/pm/pkg/component/ParsedService.java
index 11696be..5fc251c 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedService.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedService.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
/** @hide **/
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
public interface ParsedService extends ParsedMainComponent {
int getForegroundServiceType();
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java
index 0171c49..c00e01a 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java
@@ -32,7 +32,8 @@
/** @hide **/
@DataClass(genSetters = true, genGetters = true, genParcelable = false, genBuilder = false)
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedServiceImpl extends ParsedMainComponentImpl implements ParsedService {
+public class ParsedServiceImpl extends ParsedMainComponentImpl implements ParsedService,
+ Parcelable {
private int foregroundServiceType;
@Nullable
@@ -138,10 +139,10 @@
}
@DataClass.Generated(
- time = 1627592563052L,
+ time = 1641431954479L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedServiceImpl.java",
- inputSignatures = "private int foregroundServiceType\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedServiceImpl> CREATOR\npublic android.content.pm.parsing.component.ParsedMainComponent setPermission(java.lang.String)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedServiceImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedService]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
+ inputSignatures = "private int foregroundServiceType\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedServiceImpl> CREATOR\npublic android.content.pm.parsing.component.ParsedMainComponent setPermission(java.lang.String)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedServiceImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
index 6fe9411..1940eb5 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
@@ -23,8 +23,6 @@
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ServiceInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseInput.DeferredError;
import android.content.pm.parsing.result.ParseResult;
@@ -34,6 +32,8 @@
import android.os.Build;
import com.android.internal.R;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -133,14 +133,14 @@
final ParseResult parseResult;
switch (parser.getName()) {
case "intent-filter":
- ParseResult<ParsedIntentInfo> intentResult = ParsedMainComponentUtils
+ ParseResult<ParsedIntentInfoImpl> intentResult = ParsedMainComponentUtils
.parseIntentFilter(service, pkg, res, parser, visibleToEphemeral,
true /*allowGlobs*/, false /*allowAutoVerify*/,
false /*allowImplicitEphemeralVisibility*/,
false /*failOnNoActions*/, input);
parseResult = intentResult;
if (intentResult.isSuccess()) {
- ParsedIntentInfo intent = intentResult.getResult();
+ ParsedIntentInfoImpl intent = intentResult.getResult();
IntentFilter intentFilter = intent.getIntentFilter();
service.setOrder(Math.max(intentFilter.getOrder(), service.getOrder()));
service.addIntent(intent);
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java
index 8e3401e..e17d1c4 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java
@@ -19,7 +19,6 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.content.pm.PackageInfo;
-import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -30,7 +29,8 @@
*
* @hide
*/
-public interface ParsedUsesPermission extends Parcelable {
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface ParsedUsesPermission {
/**
* Strong assertion by a developer that they will never use this permission to derive the
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java
index 70d6f24..9b89373 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java
@@ -33,7 +33,7 @@
@DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = true,
genAidl = false)
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedUsesPermissionImpl implements ParsedUsesPermission {
+public class ParsedUsesPermissionImpl implements ParsedUsesPermission, Parcelable {
@DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedString.class)
@NonNull
@@ -157,10 +157,10 @@
};
@DataClass.Generated(
- time = 1627674645598L,
+ time = 1641431955242L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedUsesPermissionImpl.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 @android.content.pm.parsing.component.ParsedUsesPermission.UsesPermissionFlags int usesPermissionFlags\nclass ParsedUsesPermissionImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedUsesPermission]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
+ inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @android.content.pm.parsing.component.ParsedUsesPermission.UsesPermissionFlags int usesPermissionFlags\nclass ParsedUsesPermissionImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedUsesPermission, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java
index 2d6c616..b92b845 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java
@@ -32,6 +32,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PathPermission;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.ProviderInfo;
@@ -41,6 +42,7 @@
import android.content.pm.SigningInfo;
import android.content.pm.overlay.OverlayPaths;
import android.os.Environment;
+import android.os.PatternMatcher;
import android.os.UserHandle;
import com.android.internal.util.ArrayUtils;
@@ -651,8 +653,8 @@
pi.forceUriPermissions = p.isForceUriPermissions();
pi.multiprocess = p.isMultiProcess();
pi.initOrder = p.getInitOrder();
- pi.uriPermissionPatterns = p.getUriPermissionPatterns();
- pi.pathPermissions = p.getPathPermissions();
+ pi.uriPermissionPatterns = p.getUriPermissionPatterns().toArray(new PatternMatcher[0]);
+ pi.pathPermissions = p.getPathPermissions().toArray(new PathPermission[0]);
if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) {
pi.uriPermissionPatterns = null;
}
@@ -690,9 +692,11 @@
ii.sourceDir = pkg.getBaseApkPath();
ii.publicSourceDir = pkg.getBaseApkPath();
ii.splitNames = pkg.getSplitNames();
- ii.splitSourceDirs = pkg.getSplitCodePaths();
- ii.splitPublicSourceDirs = pkg.getSplitCodePaths();
- ii.splitDependencies = pkg.getSplitDependencies();
+ ii.splitSourceDirs = pkg.getSplitCodePaths().length == 0 ? null : pkg.getSplitCodePaths();
+ ii.splitPublicSourceDirs = pkg.getSplitCodePaths().length == 0
+ ? null : pkg.getSplitCodePaths();
+ ii.splitDependencies = pkg.getSplitDependencies().size() == 0
+ ? null : pkg.getSplitDependencies();
if (assignUserFields) {
assignUserFields(pkg, ii, userId);
@@ -734,9 +738,9 @@
if (pg == null) return null;
PermissionGroupInfo pgi = new PermissionGroupInfo(
- pg.getRequestDetailResourceId(),
- pg.getBackgroundRequestResourceId(),
- pg.getBackgroundRequestDetailResourceId()
+ pg.getRequestDetailRes(),
+ pg.getBackgroundRequestRes(),
+ pg.getBackgroundRequestDetailRes()
);
assignSharedFieldsForPackageItemInfo(pgi, pg);
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
index 1f21938..84422cd 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
@@ -24,6 +24,7 @@
import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
@@ -81,11 +82,8 @@
import com.android.server.pm.pkg.component.ParsedServiceImpl;
import com.android.server.pm.pkg.component.ParsedUsesPermission;
import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
-import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageHidden;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
+
+import libcore.util.EmptyArray;
import java.security.PublicKey;
import java.util.Collections;
@@ -107,6 +105,7 @@
*/
public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, Parcelable {
+ private static final SparseArray<int[]> EMPTY_INT_ARRAY_SPARSE_ARRAY = new SparseArray<>();
public static ForBoolean sForBoolean = Parcelling.Cache.getOrCreate(ForBoolean.class);
public static ForInternedString sForInternedString = Parcelling.Cache.getOrCreate(
ForInternedString.class);
@@ -660,6 +659,7 @@
return this;
}
+ @SuppressLint("MissingSuperCall")
@CallSuper
@Override
public Object hideAsParsed() {
@@ -1125,7 +1125,8 @@
// appInfo.sharedLibraryInfos
// appInfo.showUserIcon
appInfo.splitClassLoaderNames = splitClassLoaderNames;
- appInfo.splitDependencies = splitDependencies;
+ appInfo.splitDependencies = (splitDependencies == null || splitDependencies.size() == 0)
+ ? null : splitDependencies;
appInfo.splitNames = splitNames;
appInfo.storageUuid = mStorageUuid;
appInfo.targetSandboxVersion = targetSandboxVersion;
@@ -1144,8 +1145,8 @@
appInfo.setBaseResourcePath(mBaseApkPath);
appInfo.setCodePath(mPath);
appInfo.setResourcePath(mPath);
- appInfo.setSplitCodePaths(splitCodePaths);
- appInfo.setSplitResourcePaths(splitCodePaths);
+ appInfo.setSplitCodePaths(ArrayUtils.size(splitCodePaths) == 0 ? null : splitCodePaths);
+ appInfo.setSplitResourcePaths(ArrayUtils.size(splitCodePaths) == 0 ? null : splitCodePaths);
appInfo.setVersionCode(mLongVersionCode);
appInfo.setAppClassNamesByProcess(buildAppClassNamesByProcess());
appInfo.setLocaleConfigRes(mLocaleConfigRes);
@@ -1257,20 +1258,20 @@
dest.writeStringList(this.originalPackages);
sForInternedStringList.parcel(this.adoptPermissions, dest, flags);
sForInternedStringList.parcel(this.requestedPermissions, dest, flags);
- dest.writeTypedList(this.usesPermissions);
+ ParsingUtils.writeParcelableList(dest, this.usesPermissions);
sForInternedStringList.parcel(this.implicitPermissions, dest, flags);
sForStringSet.parcel(this.upgradeKeySets, dest, flags);
ParsingPackageUtils.writeKeySetMapping(dest, this.keySetMapping);
sForInternedStringList.parcel(this.protectedBroadcasts, dest, flags);
- dest.writeTypedList(this.activities);
- dest.writeTypedList(this.apexSystemServices);
- dest.writeTypedList(this.receivers);
- dest.writeTypedList(this.services);
- dest.writeTypedList(this.providers);
- dest.writeTypedList(this.attributions);
- dest.writeTypedList(this.permissions);
- dest.writeTypedList(this.permissionGroups);
- dest.writeTypedList(this.instrumentations);
+ ParsingUtils.writeParcelableList(dest, this.activities);
+ ParsingUtils.writeParcelableList(dest, this.apexSystemServices);
+ ParsingUtils.writeParcelableList(dest, this.receivers);
+ ParsingUtils.writeParcelableList(dest, this.services);
+ ParsingUtils.writeParcelableList(dest, this.providers);
+ ParsingUtils.writeParcelableList(dest, this.attributions);
+ ParsingUtils.writeParcelableList(dest, this.permissions);
+ ParsingUtils.writeParcelableList(dest, this.permissionGroups);
+ ParsingUtils.writeParcelableList(dest, this.instrumentations);
sForIntentInfoPairs.parcel(this.preferredActivityFilters, dest, flags);
dest.writeMap(this.processes);
dest.writeBundle(this.metaData);
@@ -1906,22 +1907,22 @@
return queriesProviders;
}
- @Nullable
+ @NonNull
@Override
public String[] getSplitClassLoaderNames() {
- return splitClassLoaderNames;
+ return splitClassLoaderNames == null ? EmptyArray.STRING : splitClassLoaderNames;
}
- @Nullable
+ @NonNull
@Override
public String[] getSplitCodePaths() {
- return splitCodePaths;
+ return splitCodePaths == null ? EmptyArray.STRING : splitCodePaths;
}
@Nullable
@Override
public SparseArray<int[]> getSplitDependencies() {
- return splitDependencies;
+ return splitDependencies == null ? EMPTY_INT_ARRAY_SPARSE_ARRAY : splitDependencies;
}
@Nullable
@@ -1930,16 +1931,16 @@
return splitFlags;
}
- @Nullable
+ @NonNull
@Override
public String[] getSplitNames() {
- return splitNames;
+ return splitNames == null ? EmptyArray.STRING : splitNames;
}
- @Nullable
+ @NonNull
@Override
public int[] getSplitRevisionCodes() {
- return splitRevisionCodes;
+ return splitRevisionCodes == null ? EmptyArray.INT : splitRevisionCodes;
}
@Nullable
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index bf7c55f..e8f03ff 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -65,6 +65,7 @@
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.os.Environment;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -101,6 +102,7 @@
import com.android.server.pm.pkg.component.ParsedInstrumentation;
import com.android.server.pm.pkg.component.ParsedInstrumentationUtils;
import com.android.server.pm.pkg.component.ParsedIntentInfo;
+import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
import com.android.server.pm.pkg.component.ParsedIntentInfoUtils;
import com.android.server.pm.pkg.component.ParsedMainComponent;
import com.android.server.pm.pkg.component.ParsedPermission;
@@ -230,6 +232,8 @@
* of required system property within the overlay tag.
*/
public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7;
+ public static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8;
+
public static final int PARSE_CHATTY = 1 << 31;
@IntDef(flag = true, prefix = { "PARSE_" }, value = {
@@ -241,6 +245,7 @@
PARSE_IS_SYSTEM_DIR,
PARSE_MUST_BE_APK,
PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY,
+ PARSE_FRAMEWORK_RES_SPLITS
})
@Retention(RetentionPolicy.SOURCE)
public @interface ParseFlags {}
@@ -289,7 +294,7 @@
return new ParsingPackageImpl(packageName, baseApkPath, path, manifestArray);
}
});
- result = parser.parsePackage(input, file, parseFlags);
+ result = parser.parsePackage(input, file, parseFlags, /* frameworkSplits= */ null);
if (result.isError()) {
return input.error(result);
}
@@ -343,26 +348,44 @@
* not check whether {@code packageFile} has changed since the last parse, it's up to callers to
* do so.
*/
- public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags) {
- if (packageFile.isDirectory()) {
- return parseClusterPackage(input, packageFile, flags);
+ public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags,
+ List<File> frameworkSplits) {
+ if (((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0)
+ && frameworkSplits.size() > 0
+ && packageFile.getAbsolutePath().endsWith("/framework-res.apk")) {
+ return parseClusterPackage(input, packageFile, frameworkSplits, flags);
+ } else if (packageFile.isDirectory()) {
+ return parseClusterPackage(input, packageFile, /* frameworkSplits= */null, flags);
} else {
return parseMonolithicPackage(input, packageFile, flags);
}
}
/**
- * Parse all APKs contained in the given directory, treating them as a single package. This also
- * performs validity checking, such as requiring identical package name and version codes, a
- * single base APK, and unique split names.
+ * Parse all APKs contained in the given directory, treating them as a
+ * single package. This also performs validity checking, such as requiring
+ * identical package name and version codes, a single base APK, and unique
+ * split names.
+ * <p>
+ * Can also be passed the framework-res.apk file and a list of split apks coming from apexes
+ * (via {@code frameworkSplits}) in which case they will be parsed similar to cluster packages
+ * even if they are in different folders. Note that this code path may have other behaviour
+ * differences.
* <p>
* Note that this <em>does not</em> perform signature verification; that must be done separately
* in {@link #getSigningDetails(ParseInput, ParsingPackageRead, boolean)}.
*/
private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir,
- int flags) {
+ List<File> frameworkSplits, int flags) {
+ // parseClusterPackageLite should receive no flags (0) for regular splits but we want to
+ // pass the flags for framework splits
+ int liteParseFlags = 0;
+ if ((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0) {
+ liteParseFlags = flags;
+ }
final ParseResult<PackageLite> liteResult =
- ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, 0);
+ ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, frameworkSplits,
+ liteParseFlags);
if (liteResult.isError()) {
return input.error(liteResult);
}
@@ -1673,7 +1696,7 @@
continue;
}
if (parser.getName().equals("intent")) {
- ParseResult<ParsedIntentInfo> result = ParsedIntentInfoUtils.parseIntentInfo(
+ ParseResult<ParsedIntentInfoImpl> result = ParsedIntentInfoUtils.parseIntentInfo(
null /*className*/, pkg, res, parser, true /*allowGlobs*/,
true /*allowAutoVerify*/, input);
if (result.isError()) {
@@ -2679,9 +2702,8 @@
// as it would have already been set when we processed the activity. We wait to
// process the meta data here since this method is called at the end of processing
// the application and all meta data is guaranteed.
- final float activityAspectRatio = activity.getMetaData() != null
- ? activity.getMetaData().getFloat(METADATA_MAX_ASPECT_RATIO, maxAspectRatio)
- : maxAspectRatio;
+ final float activityAspectRatio = activity.getMetaData()
+ .getFloat(METADATA_MAX_ASPECT_RATIO, maxAspectRatio);
ComponentMutateUtils.setMaxAspectRatio(activity, activity.getResizeMode(),
activityAspectRatio);
@@ -2716,9 +2738,8 @@
int activitiesSize = activities.size();
for (int index = 0; index < activitiesSize; index++) {
ParsedActivity activity = activities.get(index);
- if (supportsSizeChanges || (activity.getMetaData() != null
- && activity.getMetaData().getBoolean(
- METADATA_SUPPORTS_SIZE_CHANGES, false))) {
+ if (supportsSizeChanges || activity.getMetaData()
+ .getBoolean(METADATA_SUPPORTS_SIZE_CHANGES, false)) {
ComponentMutateUtils.setSupportsSizeChanges(activity, true);
}
}
@@ -2991,9 +3012,12 @@
}
signingDetails = result.getResult();
-
+ final File frameworkRes = new File(Environment.getRootDirectory(),
+ "framework/framework-res.apk");
+ boolean isFrameworkResSplit = frameworkRes.getAbsolutePath()
+ .equals(pkg.getBaseApkPath());
String[] splitCodePaths = pkg.getSplitCodePaths();
- if (!ArrayUtils.isEmpty(splitCodePaths)) {
+ if (!ArrayUtils.isEmpty(splitCodePaths) && !isFrameworkResSplit) {
for (int i = 0; i < splitCodePaths.length; i++) {
result = getSigningDetails(
input,
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
index 95fec36..9430e98 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
@@ -20,8 +20,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.res.XmlResourceParser;
@@ -32,6 +30,8 @@
import com.android.internal.util.Parcelling;
import com.android.internal.util.XmlUtils;
+import com.android.server.pm.pkg.component.ParsedIntentInfo;
+import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
import org.xmlpull.v1.XmlPullParserException;
@@ -84,7 +84,8 @@
}
/**
- * Use with {@link Parcel#writeTypedList(List)}
+ * Use with {@link Parcel#writeTypedList(List)} or
+ * {@link #writeInterfaceAsImplList(Parcel, List)}
*
* @see Parcel#createTypedArrayList(Parcelable.Creator)
*/
@@ -103,6 +104,29 @@
return list;
}
+ /**
+ * Use with {@link #createTypedInterfaceList(Parcel, Parcelable.Creator)}.
+ *
+ * Writes a list that can be cast as Parcelable types at runtime.
+ * TODO: Remove with ImmutableList multi-casting support
+ *
+ * @see Parcel#writeTypedList(List)
+ */
+ @NonNull
+ public static void writeParcelableList(@NonNull Parcel parcel, List<?> list) {
+ if (list == null) {
+ parcel.writeInt(-1);
+ return;
+ }
+ int size = list.size();
+ int index = 0;
+ parcel.writeInt(size);
+ while (index < size) {
+ parcel.writeTypedObject((Parcelable) list.get(index), 0);
+ index++;
+ }
+ }
+
public static class StringPairListParceler implements
Parcelling<List<Pair<String, ParsedIntentInfo>>> {
@@ -120,7 +144,7 @@
for (int index = 0; index < size; index++) {
Pair<String, ParsedIntentInfo> pair = item.get(index);
dest.writeString(pair.first);
- dest.writeParcelable(pair.second, parcelFlags);
+ dest.writeParcelable((Parcelable) pair.second, parcelFlags);
}
}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java
index a323e20..2ef90ac 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java
@@ -324,13 +324,13 @@
* @see ApplicationInfo#splitSourceDirs
* @see ApplicationInfo#getSplitCodePaths
*/
- @Nullable
+ @NonNull
String[] getSplitCodePaths();
/**
* @see ApplicationInfo#splitDependencies
*/
- @Nullable
+ @NonNull
SparseArray<int[]> getSplitDependencies();
/**
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStatePackageInfo.java b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStatePackageInfo.java
index 2bc4ee7..78ce6f1 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStatePackageInfo.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStatePackageInfo.java
@@ -249,12 +249,13 @@
* @see ApplicationInfo#splitNames
* @see PackageInfo#splitNames
*/
- @Nullable
+ @NonNull
String[] getSplitNames();
/**
* @see PackageInfo#splitRevisionCodes
*/
+ @NonNull
int[] getSplitRevisionCodes();
/**
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index d0b50d27..d6c89f7 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -60,7 +60,7 @@
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageStateUtils;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageUserStateInternal;
import com.android.server.pm.pkg.PackageUserStateUtils;
import com.android.server.pm.pkg.component.ParsedActivity;
import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState;
@@ -1751,7 +1751,7 @@
int approvalLevel = approvalLevelForDomainInternal(pkgSetting, host, includeNegative,
userId, debugObject);
if (includeNegative && approvalLevel == APPROVAL_LEVEL_NONE) {
- PackageUserState pkgUserState = pkgSetting.getUserStateOrDefault(userId);
+ PackageUserStateInternal pkgUserState = pkgSetting.getUserStateOrDefault(userId);
if (!pkgUserState.isInstalled()) {
return APPROVAL_LEVEL_NOT_INSTALLED;
}
@@ -1783,7 +1783,7 @@
return APPROVAL_LEVEL_UNDECLARED;
}
- final PackageUserState pkgUserState = pkgSetting.getUserStates().get(userId);
+ final PackageUserStateInternal pkgUserState = pkgSetting.getUserStates().get(userId);
if (pkgUserState == null) {
if (DEBUG_APPROVAL) {
debugApproval(packageName, debugObject, userId, false,
diff --git a/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java b/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
index 154f9a4..7754944 100644
--- a/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
@@ -27,12 +27,11 @@
*
* @see DeviceStateProviderImpl
*/
-public final class DeviceStatePolicyImpl implements DeviceStatePolicy {
- private final Context mContext;
+public final class DeviceStatePolicyImpl extends DeviceStatePolicy {
private final DeviceStateProvider mProvider;
- public DeviceStatePolicyImpl(Context context) {
- mContext = context;
+ public DeviceStatePolicyImpl(@NonNull Context context) {
+ super(context);
mProvider = DeviceStateProviderImpl.create(mContext);
}
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 b7ca4de..e180032 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -18,9 +18,6 @@
import static android.app.AppOpsManager.OP_FLAG_SELF;
import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
-import static android.app.usage.NetworkStatsManager.FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN;
-import static android.app.usage.NetworkStatsManager.FLAG_POLL_FORCE;
-import static android.app.usage.NetworkStatsManager.FLAG_POLL_ON_OPEN;
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -82,6 +79,7 @@
import android.app.RuntimeAppOpAccessMessage;
import android.app.StatsManager;
import android.app.StatsManager.PullAtomMetadata;
+import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothActivityEnergyInfo;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.UidTraffic;
@@ -100,8 +98,6 @@
import android.media.MediaDrm;
import android.media.UnsupportedSchemeException;
import android.net.ConnectivityManager;
-import android.net.INetworkStatsService;
-import android.net.INetworkStatsSession;
import android.net.Network;
import android.net.NetworkRequest;
import android.net.NetworkStats;
@@ -197,6 +193,7 @@
import com.android.internal.os.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.net.module.util.NetworkStatsUtils;
import com.android.role.RoleManagerLocal;
import com.android.server.BinderCallsStatsService;
import com.android.server.LocalManagerRegistry;
@@ -244,7 +241,7 @@
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import java.util.function.BiConsumer;
+import java.util.function.Function;
/**
* SystemService containing PullAtomCallbacks that are registered with statsd.
@@ -340,6 +337,7 @@
private WifiManager mWifiManager;
private TelephonyManager mTelephony;
private SubscriptionManager mSubscriptionManager;
+ private NetworkStatsManager mNetworkStatsManager;
@GuardedBy("mKernelWakelockLock")
private KernelWakelockReader mKernelWakelockReader;
@@ -788,7 +786,7 @@
mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
mStatsSubscriptionsListener = new StatsSubscriptionsListener(mSubscriptionManager);
mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class);
-
+ mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class);
// Initialize DiskIO
mStoragedUidIoStatsReader = new StoragedUidIoStatsReader();
@@ -981,32 +979,6 @@
registerOemManagedBytesTransfer();
}
- /**
- * Return the {@code INetworkStatsSession} object that holds the necessary properties needed
- * for the subsequent queries to {@link com.android.server.net.NetworkStatsService}. Or
- * null if the service or binder cannot be obtained. Calling this method will trigger poll
- * in NetworkStatsService with once per 15 seconds rate-limit, unless {@code bypassRateLimit}
- * is set to true. This is needed in {@link #getUidNetworkStatsSnapshotForTemplate}, where
- * bypassing the limit is necessary for perfd to supply realtime stats to developers looking at
- * the network usage of their app.
- */
- @Nullable
- private INetworkStatsSession getNetworkStatsSession(boolean bypassRateLimit) {
- final INetworkStatsService networkStatsService =
- INetworkStatsService.Stub.asInterface(
- ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
- if (networkStatsService == null) return null;
-
- try {
- return networkStatsService.openSessionForUsageStats(
- FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN | (bypassRateLimit ? FLAG_POLL_FORCE
- : FLAG_POLL_ON_OPEN), mContext.getOpPackageName());
- } catch (RemoteException e) {
- Slog.e(TAG, "Cannot get NetworkStats session", e);
- return null;
- }
- }
-
private IThermalService getIThermalService() {
synchronized (mThermalLock) {
if (mThermalService == null) {
@@ -1137,8 +1109,8 @@
case FrameworkStatsLog.WIFI_BYTES_TRANSFER: {
final NetworkStats stats = getUidNetworkStatsSnapshotForTransport(TRANSPORT_WIFI);
if (stats != null) {
- ret.add(new NetworkStatsExt(stats.groupedByUid(), new int[] {TRANSPORT_WIFI},
- /*slicedByFgbg=*/false));
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUid(stats),
+ new int[] {TRANSPORT_WIFI}, /*slicedByFgbg=*/false));
}
break;
}
@@ -1154,7 +1126,7 @@
final NetworkStats stats =
getUidNetworkStatsSnapshotForTransport(TRANSPORT_CELLULAR);
if (stats != null) {
- ret.add(new NetworkStatsExt(stats.groupedByUid(),
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUid(stats),
new int[] {TRANSPORT_CELLULAR}, /*slicedByFgbg=*/false));
}
break;
@@ -1245,23 +1217,19 @@
private void addNetworkStats(int atomTag, @NonNull List<StatsEvent> ret,
@NonNull NetworkStatsExt statsExt) {
- int size = statsExt.stats.size();
- final NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling
- for (int j = 0; j < size; j++) {
- statsExt.stats.getValues(j, entry);
+ for (NetworkStats.Entry entry : statsExt.stats) {
StatsEvent statsEvent;
-
if (statsExt.slicedByFgbg) {
// MobileBytesTransferByFgBg atom or WifiBytesTransferByFgBg atom.
statsEvent = FrameworkStatsLog.buildStatsEvent(
- atomTag, entry.uid,
- (entry.set > 0), entry.rxBytes, entry.rxPackets, entry.txBytes,
- entry.txPackets);
+ atomTag, entry.getUid(),
+ (entry.getSet() > 0), entry.getRxBytes(), entry.getRxPackets(),
+ entry.getTxBytes(), entry.getTxPackets());
} else {
// MobileBytesTransfer atom or WifiBytesTransfer atom.
statsEvent = FrameworkStatsLog.buildStatsEvent(
- atomTag, entry.uid, entry.rxBytes,
- entry.rxPackets, entry.txBytes, entry.txPackets);
+ atomTag, entry.getUid(), entry.getRxBytes(),
+ entry.getRxPackets(), entry.getTxBytes(), entry.getTxPackets());
}
ret.add(statsEvent);
}
@@ -1269,13 +1237,12 @@
private void addBytesTransferByTagAndMeteredAtoms(@NonNull NetworkStatsExt statsExt,
@NonNull List<StatsEvent> pulledData) {
- final NetworkStats.Entry entry = new NetworkStats.Entry(); // for recycling
- for (int i = 0; i < statsExt.stats.size(); i++) {
- statsExt.stats.getValues(i, entry);
+ for (NetworkStats.Entry entry : statsExt.stats) {
pulledData.add(FrameworkStatsLog.buildStatsEvent(
- FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED, entry.uid,
- entry.metered == NetworkStats.METERED_YES, entry.tag, entry.rxBytes,
- entry.rxPackets, entry.txBytes, entry.txPackets));
+ FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED, entry.getUid(),
+ entry.getMetered() == NetworkStats.METERED_YES, entry.getTag(),
+ entry.getRxBytes(), entry.getRxPackets(), entry.getTxBytes(),
+ entry.getTxPackets()));
}
}
@@ -1290,12 +1257,11 @@
// Report NR connected in 5G non-standalone mode, or if the RAT type is NR to begin with.
final boolean isNR = is5GNsa || statsExt.ratType == TelephonyManager.NETWORK_TYPE_NR;
- final NetworkStats.Entry entry = new NetworkStats.Entry(); // for recycling
- for (int i = 0; i < statsExt.stats.size(); i++) {
- statsExt.stats.getValues(i, entry);
+ for (NetworkStats.Entry entry : statsExt.stats) {
pulledData.add(FrameworkStatsLog.buildStatsEvent(
- FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER, entry.set, entry.rxBytes,
- entry.rxPackets, entry.txBytes, entry.txPackets,
+ FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER,
+ entry.getSet(), entry.getRxBytes(), entry.getRxPackets(),
+ entry.getTxBytes(), entry.getTxPackets(),
is5GNsa ? TelephonyManager.NETWORK_TYPE_LTE : statsExt.ratType,
// Fill information about subscription, these cannot be null since invalid data
// would be filtered when adding into subInfo list.
@@ -1309,15 +1275,13 @@
private void addOemDataUsageBytesTransferAtoms(@NonNull NetworkStatsExt statsExt,
@NonNull List<StatsEvent> pulledData) {
- final NetworkStats.Entry entry = new NetworkStats.Entry(); // for recycling
final int oemManaged = statsExt.oemManaged;
for (final int transport : statsExt.transports) {
- for (int i = 0; i < statsExt.stats.size(); i++) {
- statsExt.stats.getValues(i, entry);
+ for (NetworkStats.Entry entry : statsExt.stats) {
pulledData.add(FrameworkStatsLog.buildStatsEvent(
- FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER, entry.uid, (entry.set > 0),
- oemManaged, transport, entry.rxBytes, entry.rxPackets, entry.txBytes,
- entry.txPackets));
+ FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER, entry.getUid(),
+ (entry.getSet() > 0), oemManaged, transport, entry.getRxBytes(),
+ entry.getRxPackets(), entry.getTxBytes(), entry.getTxPackets()));
}
}
}
@@ -1385,22 +1349,32 @@
final long currentTimeInMillis = MICROSECONDS.toMillis(SystemClock.currentTimeMicro());
final long bucketDuration = Settings.Global.getLong(mContext.getContentResolver(),
NETSTATS_UID_BUCKET_DURATION, NETSTATS_UID_DEFAULT_BUCKET_DURATION_MS);
- try {
- // TODO (b/156313635): This is short-term hack to allow perfd gets updated networkStats
- // history when query in every second in order to show realtime statistics. However,
- // this is not a good long-term solution since NetworkStatsService will make frequent
- // I/O and also block main thread when polling.
- // Consider making perfd queries NetworkStatsService directly.
- final NetworkStats stats = getNetworkStatsSession(template.getMatchRule()
- == NetworkTemplate.MATCH_WIFI_WILDCARD).getSummaryForAllUid(template,
- currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
- currentTimeInMillis, includeTags);
- return stats;
- } catch (RemoteException | NullPointerException e) {
- Slog.e(TAG, "Pulling netstats for template=" + template + " and includeTags="
- + includeTags + " causes error", e);
+
+ // TODO (b/156313635): This is short-term hack to allow perfd gets updated networkStats
+ // history when query in every second in order to show realtime statistics. However,
+ // this is not a good long-term solution since NetworkStatsService will make frequent
+ // I/O and also block main thread when polling.
+ // Consider making perfd queries NetworkStatsService directly.
+ if (template.getMatchRule() == MATCH_WIFI && template.getSubscriberIds().isEmpty()) {
+ mNetworkStatsManager.forceUpdate();
}
- return null;
+
+ final android.app.usage.NetworkStats queryNonTaggedStats =
+ mNetworkStatsManager.querySummary(
+ template, currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
+ currentTimeInMillis);
+
+ final NetworkStats nonTaggedStats =
+ NetworkStatsUtils.fromPublicNetworkStats(queryNonTaggedStats);
+ if (!includeTags) return nonTaggedStats;
+
+ final android.app.usage.NetworkStats quaryTaggedStats =
+ mNetworkStatsManager.queryTaggedSummary(template,
+ currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
+ currentTimeInMillis);
+ final NetworkStats taggedStats =
+ NetworkStatsUtils.fromPublicNetworkStats(quaryTaggedStats);
+ return nonTaggedStats.add(taggedStats);
}
@NonNull private List<NetworkStatsExt> getDataUsageBytesTransferSnapshotForSub(
@@ -1424,27 +1398,51 @@
return ret;
}
+ @NonNull private NetworkStats sliceNetworkStatsByUid(@NonNull NetworkStats stats) {
+ return sliceNetworkStats(stats,
+ (entry) -> {
+ return new NetworkStats.Entry(null /* IFACE_ALL */, entry.getUid(),
+ NetworkStats.SET_ALL, NetworkStats.TAG_NONE,
+ NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL,
+ NetworkStats.DEFAULT_NETWORK_ALL,
+ entry.getRxBytes(), entry.getRxPackets(),
+ entry.getTxBytes(), entry.getTxPackets(), 0);
+ });
+ }
+
@NonNull private NetworkStats sliceNetworkStatsByFgbg(@NonNull NetworkStats stats) {
return sliceNetworkStats(stats,
- (newEntry, oldEntry) -> {
- newEntry.set = oldEntry.set;
+ (entry) -> {
+ return new NetworkStats.Entry(null /* IFACE_ALL */, NetworkStats.UID_ALL,
+ entry.getSet(), NetworkStats.TAG_NONE,
+ NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL,
+ NetworkStats.DEFAULT_NETWORK_ALL,
+ entry.getRxBytes(), entry.getRxPackets(),
+ entry.getTxBytes(), entry.getTxPackets(), 0);
});
}
@NonNull private NetworkStats sliceNetworkStatsByUidAndFgbg(@NonNull NetworkStats stats) {
return sliceNetworkStats(stats,
- (newEntry, oldEntry) -> {
- newEntry.uid = oldEntry.uid;
- newEntry.set = oldEntry.set;
+ (entry) -> {
+ return new NetworkStats.Entry(null /* IFACE_ALL */, entry.getUid(),
+ entry.getSet(), NetworkStats.TAG_NONE,
+ NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL,
+ NetworkStats.DEFAULT_NETWORK_ALL,
+ entry.getRxBytes(), entry.getRxPackets(),
+ entry.getTxBytes(), entry.getTxPackets(), 0);
});
}
@NonNull private NetworkStats sliceNetworkStatsByUidTagAndMetered(@NonNull NetworkStats stats) {
return sliceNetworkStats(stats,
- (newEntry, oldEntry) -> {
- newEntry.uid = oldEntry.uid;
- newEntry.tag = oldEntry.tag;
- newEntry.metered = oldEntry.metered;
+ (entry) -> {
+ return new NetworkStats.Entry(null /* IFACE_ALL */, entry.getUid(),
+ NetworkStats.SET_ALL, entry.getTag(),
+ entry.getMetered(), NetworkStats.ROAMING_ALL,
+ NetworkStats.DEFAULT_NETWORK_ALL,
+ entry.getRxBytes(), entry.getRxPackets(),
+ entry.getTxBytes(), entry.getTxPackets(), 0);
});
}
@@ -1454,46 +1452,31 @@
*
* This function iterates through each NetworkStats.Entry, sets its dimensions equal to the
* default state (with the presumption that we don't want to slice on anything), and then
- * applies the slicer lambda to allow users to control which dimensions to slice on. This is
- * adapted from groupedByUid within NetworkStats.java
+ * applies the slicer lambda to allow users to control which dimensions to slice on.
*
- * @param slicer An operation taking into two parameters, new NetworkStats.Entry and old
- * NetworkStats.Entry, that should be used to copy state from the old to the new.
+ * @param slicer An operation taking one parameter, NetworkStats.Entry, that should be used to
+ * get the state from entry to replace the default value.
* This is useful for slicing by particular dimensions. For example, if we wished
* to slice by uid and tag, we could write the following lambda:
- * (new, old) -> {
- * new.uid = old.uid;
- * new.tag = old.tag;
+ * (entry) -> {
+ * return new NetworkStats.Entry(null, entry.getUid(),
+ * NetworkStats.SET_ALL, entry.getTag(),
+ * NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL,
+ * NetworkStats.DEFAULT_NETWORK_ALL,
+ * entry.getRxBytes(), entry.getRxPackets(),
+ * entry.getTxBytes(), entry.getTxPackets(), 0);
* }
- * If no slicer is provided, the data is not sliced by any dimensions.
* @return new NeworkStats object appropriately sliced
*/
@NonNull private NetworkStats sliceNetworkStats(@NonNull NetworkStats stats,
- @Nullable BiConsumer<NetworkStats.Entry, NetworkStats.Entry> slicer) {
- final NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1);
-
- final NetworkStats.Entry entry = new NetworkStats.Entry();
- entry.uid = NetworkStats.UID_ALL;
- entry.iface = NetworkStats.IFACE_ALL;
- entry.set = NetworkStats.SET_ALL;
- entry.tag = NetworkStats.TAG_NONE;
- entry.metered = NetworkStats.METERED_ALL;
- entry.roaming = NetworkStats.ROAMING_ALL;
- entry.defaultNetwork = NetworkStats.DEFAULT_NETWORK_ALL;
-
- final NetworkStats.Entry recycle = new NetworkStats.Entry(); // used for retrieving values
- for (int i = 0; i < stats.size(); i++) {
- stats.getValues(i, recycle);
+ @NonNull Function<NetworkStats.Entry, NetworkStats.Entry> slicer) {
+ NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1);
+ NetworkStats.Entry entry = new NetworkStats.Entry();
+ for (NetworkStats.Entry e : stats) {
if (slicer != null) {
- slicer.accept(entry, recycle);
+ entry = slicer.apply(e);
}
-
- entry.rxBytes = recycle.rxBytes;
- entry.rxPackets = recycle.rxPackets;
- entry.txBytes = recycle.txBytes;
- entry.txPackets = recycle.txPackets;
- // Operations purposefully omitted since we don't use them for statsd.
- ret.combineValues(entry);
+ ret = ret.addEntry(entry);
}
return ret;
}
diff --git a/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java b/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
index 8189e74..160f4f9 100644
--- a/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
+++ b/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
@@ -26,8 +26,8 @@
import java.util.List;
/**
- * Adapter that clips frequency values to {@link VibratorInfo#getFrequencyRangeHz()} and
- * amplitude values to respective {@link VibratorInfo#getMaxAmplitude}.
+ * Adapter that clips frequency values to the ones specified by the
+ * {@link VibratorInfo.FrequencyProfile}.
*
* <p>Devices with no frequency control will collapse all frequencies to the resonant frequency and
* leave amplitudes unchanged.
@@ -69,20 +69,20 @@
}
private float clampFrequency(VibratorInfo info, float frequencyHz) {
- Range<Float> frequencyRangeHz = info.getFrequencyRangeHz();
+ Range<Float> frequencyRangeHz = info.getFrequencyProfile().getFrequencyRangeHz();
if (frequencyHz == 0 || frequencyRangeHz == null) {
- return info.getResonantFrequency();
+ return info.getResonantFrequencyHz();
}
return frequencyRangeHz.clamp(frequencyHz);
}
private float clampAmplitude(VibratorInfo info, float frequencyHz, float amplitude) {
- Range<Float> frequencyRangeHz = info.getFrequencyRangeHz();
- if (frequencyRangeHz == null) {
- // No frequency range was specified, leave amplitude unchanged, the frequency will be
- // clamped to the device's resonant frequency.
+ VibratorInfo.FrequencyProfile mapping = info.getFrequencyProfile();
+ if (mapping.isEmpty()) {
+ // No frequency mapping was specified so leave amplitude unchanged.
+ // The frequency will be clamped to the device's resonant frequency.
return amplitude;
}
- return MathUtils.min(amplitude, info.getMaxAmplitude(frequencyHz));
+ return MathUtils.min(amplitude, mapping.getMaxAmplitude(frequencyHz));
}
}
diff --git a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java
index c592a70..c943bb2 100644
--- a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java
+++ b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java
@@ -94,6 +94,6 @@
}
private static float fillEmptyFrequency(VibratorInfo info, float frequencyHz) {
- return frequencyHz == 0 ? info.getResonantFrequency() : frequencyHz;
+ return frequencyHz == 0 ? info.getResonantFrequencyHz() : frequencyHz;
}
}
diff --git a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java
index 5ace389..86fc642 100644
--- a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java
+++ b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java
@@ -148,6 +148,6 @@
}
private static float fillEmptyFrequency(VibratorInfo info, float frequencyHz) {
- return frequencyHz == 0 ? info.getResonantFrequency() : frequencyHz;
+ return frequencyHz == 0 ? info.getResonantFrequencyHz() : frequencyHz;
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 76434c7..49d6a34 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -95,6 +95,7 @@
import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
import static android.os.Build.VERSION_CODES.HONEYCOMB;
import static android.os.Build.VERSION_CODES.O;
+import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.COLOR_MODE_DEFAULT;
@@ -635,7 +636,7 @@
private boolean mOccludesParent;
// The input dispatching timeout for this application token in milliseconds.
- long mInputDispatchingTimeoutMillis;
+ long mInputDispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
private boolean mShowWhenLocked;
private boolean mInheritShownWhenLocked;
@@ -1443,7 +1444,6 @@
if (oldParent == null && newParent != null) {
// First time we are adding the activity to the system.
mVoiceInteraction = newTask.voiceSession != null;
- mInputDispatchingTimeoutMillis = getInputDispatchingTimeoutMillisLocked(this);
// TODO(b/36505427): Maybe this call should be moved inside
// updateOverrideConfiguration()
@@ -1995,6 +1995,7 @@
task.setRootProcess(proc);
}
proc.addActivityIfNeeded(this);
+ mInputDispatchingTimeoutMillis = getInputDispatchingTimeoutMillisLocked(this);
// Update the associated task fragment after setting the process, since it's required for
// filtering to only report activities that belong to the same process.
@@ -3578,6 +3579,7 @@
app.removeActivity(this, false /* keepAssociation */);
}
app = null;
+ mInputDispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
}
void makeFinishingLocked() {
diff --git a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
similarity index 97%
rename from services/core/java/com/android/server/wm/FadeRotationAnimationController.java
rename to services/core/java/com/android/server/wm/AsyncRotationController.java
index 2cefd99..0990f1f 100644
--- a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -35,7 +35,7 @@
* both fixed rotation and normal rotation to hide some non-activity windows. The caller should show
* the windows until they are drawn with the new rotation.
*/
-public class FadeRotationAnimationController extends FadeAnimationController {
+public class AsyncRotationController extends FadeAnimationController {
/** The map of window token to its animation leash. */
private final ArrayMap<WindowToken, SurfaceControl> mTargetWindowTokens = new ArrayMap<>();
@@ -70,7 +70,7 @@
private final int mOriginalRotation;
private final boolean mHasScreenRotationAnimation;
- public FadeRotationAnimationController(DisplayContent displayContent) {
+ public AsyncRotationController(DisplayContent displayContent) {
super(displayContent);
mService = displayContent.mWmService;
mOriginalRotation = displayContent.getWindowConfiguration().getRotation();
@@ -81,7 +81,7 @@
mIsStartTransactionCommitted = !mIsChangeTransition;
mTimeoutRunnable = displayContent.inTransition() ? () -> {
synchronized (mService.mGlobalLock) {
- displayContent.finishFadeRotationAnimationIfPossible();
+ displayContent.finishAsyncRotationIfPossible();
mService.mWindowPlacerLocked.performSurfacePlacement();
}
} : null;
@@ -277,7 +277,7 @@
mIsStartTransactionCommitted = true;
if (mPendingShowTokens == null) return;
for (int i = mPendingShowTokens.size() - 1; i >= 0; i--) {
- mDisplayContent.finishFadeRotationAnimation(mPendingShowTokens.get(i));
+ mDisplayContent.finishAsyncRotation(mPendingShowTokens.get(i));
}
mPendingShowTokens = null;
}
@@ -298,7 +298,7 @@
// Only fade in the drawn windows. If the remaining windows are drawn later,
// show(WindowToken) will be called to fade in them.
if (token.getChildAt(j).isDrawFinishedLw()) {
- mDisplayContent.finishFadeRotationAnimation(token);
+ mDisplayContent.finishAsyncRotation(token);
break;
}
}
@@ -320,7 +320,7 @@
}
return true;
}
- mDisplayContent.finishFadeRotationAnimation(w.mToken);
+ mDisplayContent.finishAsyncRotation(w.mToken);
return false;
}
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index bb4519c..5c1ddd9 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -64,6 +64,11 @@
void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction);
}
+ interface SyncEngineListener {
+ /** Called when there is no more active sync set. */
+ void onSyncEngineFree();
+ }
+
/**
* Holds state associated with a single synchronous set of operations.
*/
@@ -137,6 +142,9 @@
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
mActiveSyncs.remove(mSyncId);
mWm.mH.removeCallbacks(mOnTimeout);
+ if (mSyncEngineListener != null && mActiveSyncs.size() == 0) {
+ mSyncEngineListener.onSyncEngineFree();
+ }
}
private void setReady(boolean ready) {
@@ -175,22 +183,54 @@
private final WindowManagerService mWm;
private int mNextSyncId = 0;
private final SparseArray<SyncGroup> mActiveSyncs = new SparseArray<>();
+ private SyncEngineListener mSyncEngineListener;
BLASTSyncEngine(WindowManagerService wms) {
mWm = wms;
}
+ /** Sets listener listening to whether the sync engine is free. */
+ void setSyncEngineListener(SyncEngineListener listener) {
+ mSyncEngineListener = listener;
+ }
+
+ /**
+ * Prepares a {@link SyncGroup} that is not active yet. Caller must call {@link #startSyncSet}
+ * before calling {@link #addToSyncSet(int, WindowContainer)} on any {@link WindowContainer}.
+ */
+ SyncGroup prepareSyncSet(TransactionReadyListener listener, String name) {
+ return new SyncGroup(listener, mNextSyncId++, name);
+ }
+
int startSyncSet(TransactionReadyListener listener) {
return startSyncSet(listener, WindowState.BLAST_TIMEOUT_DURATION, "");
}
int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name) {
- final int id = mNextSyncId++;
- final SyncGroup s = new SyncGroup(listener, id, name);
- mActiveSyncs.put(id, s);
- ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started for listener: %s", id, listener);
+ final SyncGroup s = prepareSyncSet(listener, name);
+ startSyncSet(s, timeoutMs);
+ return s.mSyncId;
+ }
+
+ void startSyncSet(SyncGroup s) {
+ startSyncSet(s, WindowState.BLAST_TIMEOUT_DURATION);
+ }
+
+ void startSyncSet(SyncGroup s, long timeoutMs) {
+ if (mActiveSyncs.size() != 0) {
+ // We currently only support one sync at a time, so start a new SyncGroup when there is
+ // another may cause issue.
+ ProtoLog.w(WM_DEBUG_SYNC_ENGINE,
+ "SyncGroup %d: Started when there is other active SyncGroup", s.mSyncId);
+ }
+ mActiveSyncs.put(s.mSyncId, s);
+ ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started for listener: %s",
+ s.mSyncId, s.mListener);
scheduleTimeout(s, timeoutMs);
- return id;
+ }
+
+ boolean hasActiveSync() {
+ return mActiveSyncs.size() != 0;
}
@VisibleForTesting
@@ -199,11 +239,11 @@
}
void addToSyncSet(int id, WindowContainer wc) {
- mActiveSyncs.get(id).addToSync(wc);
+ getSyncGroup(id).addToSync(wc);
}
void setReady(int id, boolean ready) {
- mActiveSyncs.get(id).setReady(ready);
+ getSyncGroup(id).setReady(ready);
}
void setReady(int id) {
@@ -211,14 +251,22 @@
}
boolean isReady(int id) {
- return mActiveSyncs.get(id).mReady;
+ return getSyncGroup(id).mReady;
}
/**
* Aborts the sync (ie. it doesn't wait for ready or anything to finish)
*/
void abort(int id) {
- mActiveSyncs.get(id).finishNow();
+ getSyncGroup(id).finishNow();
+ }
+
+ private SyncGroup getSyncGroup(int id) {
+ final SyncGroup syncGroup = mActiveSyncs.get(id);
+ if (syncGroup == null) {
+ throw new IllegalStateException("SyncGroup is not started yet id=" + id);
+ }
+ return syncGroup;
}
void onSurfacePlacement() {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e449dde..5facc0d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -123,6 +123,7 @@
import static com.android.server.wm.DisplayContentProto.INSETS_SOURCE_PROVIDERS;
import static com.android.server.wm.DisplayContentProto.IS_SLEEPING;
import static com.android.server.wm.DisplayContentProto.KEEP_CLEAR_AREAS;
+import static com.android.server.wm.DisplayContentProto.MIN_SIZE_OF_RESIZEABLE_TASK_DP;
import static com.android.server.wm.DisplayContentProto.OPENING_APPS;
import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
@@ -193,13 +194,13 @@
import android.util.DisplayMetrics;
import android.util.IntArray;
import android.util.RotationUtils;
+import android.util.Size;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.view.DisplayCutout;
-import android.view.DisplayCutout.CutoutPathParserInfo;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.IDisplayWindowInsetsController;
@@ -238,7 +239,6 @@
import com.android.internal.util.function.pooled.PooledPredicate;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.wm.utils.DisplayRotationUtil;
import com.android.server.wm.utils.RotationCache;
import com.android.server.wm.utils.WmDisplayCutout;
@@ -323,6 +323,11 @@
*/
private Rect mLastMirroredDisplayAreaBounds = null;
+ /**
+ * The default per Display minimal size of tasks. Calculated at construction.
+ */
+ int mMinSizeOfResizeableTaskDp = -1;
+
// Contains all IME window containers. Note that the z-ordering of the IME windows will depend
// on the IME target. We mainly have this container grouping so we can keep track of all the IME
// window containers together and move them in-sync if/when needed. We use a subclass of
@@ -547,7 +552,7 @@
/** The delay to avoid toggling the animation quickly. */
private static final long FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS = 250;
- private FadeRotationAnimationController mFadeRotationAnimationController;
+ private AsyncRotationController mAsyncRotationController;
final FixedRotationTransitionListener mFixedRotationTransitionListener =
new FixedRotationTransitionListener();
@@ -578,8 +583,6 @@
/** Caches the value whether told display manager that we have content. */
private boolean mLastHasContent;
- private static DisplayRotationUtil sRotationUtil = new DisplayRotationUtil();
-
/**
* The input method window for this display.
*/
@@ -1098,7 +1101,7 @@
mInputMonitor = new InputMonitor(mWmService, this);
mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
-
+ mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Creating display=" + display);
mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
@@ -1554,6 +1557,19 @@
return config;
}
+ private int getMinimalTaskSizeDp() {
+ final Context displayConfigurationContext =
+ mAtmService.mContext.createConfigurationContext(getConfiguration());
+ final float minimalSize =
+ displayConfigurationContext.getResources().getDimension(
+ com.android.internal.R.dimen.default_minimal_size_resizable_task);
+ if (Double.compare(mDisplayMetrics.density, 0.0) == 0) {
+ throw new IllegalArgumentException("Display with ID=" + getDisplayId() + "has invalid "
+ + "DisplayMetrics.density= 0.0");
+ }
+ return (int) (minimalSize / mDisplayMetrics.density);
+ }
+
private boolean updateOrientation(boolean forceUpdate) {
final int orientation = getOrientation();
// The last orientation source is valid only after getOrientation.
@@ -1712,8 +1728,8 @@
}
@VisibleForTesting
- @Nullable FadeRotationAnimationController getFadeRotationAnimationController() {
- return mFadeRotationAnimationController;
+ @Nullable AsyncRotationController getAsyncRotationController() {
+ return mAsyncRotationController;
}
void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r) {
@@ -1723,13 +1739,13 @@
void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) {
if (mFixedRotationLaunchingApp == null && r != null) {
mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation);
- startFadeRotationAnimation(
+ startAsyncRotation(
// Delay the hide animation to avoid blinking by clicking navigation bar that
// may toggle fixed rotation in a short time.
r == mFixedRotationTransitionListener.mAnimatingRecents /* shouldDebounce */);
} else if (mFixedRotationLaunchingApp != null && r == null) {
mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this);
- finishFadeRotationAnimationIfPossible();
+ finishAsyncRotationIfPossible();
}
mFixedRotationLaunchingApp = r;
}
@@ -1842,9 +1858,9 @@
return mDisplayRotation.getRotation() != getWindowConfiguration().getRotation();
}
- private void startFadeRotationAnimationIfNeeded() {
+ private void startAsyncRotationIfNeeded() {
if (isRotationChanging()) {
- startFadeRotationAnimation(false /* shouldDebounce */);
+ startAsyncRotation(false /* shouldDebounce */);
}
}
@@ -1853,12 +1869,12 @@
*
* @return {@code true} if the animation is executed right now.
*/
- private boolean startFadeRotationAnimation(boolean shouldDebounce) {
+ private boolean startAsyncRotation(boolean shouldDebounce) {
if (shouldDebounce) {
mWmService.mH.postDelayed(() -> {
synchronized (mWmService.mGlobalLock) {
if (mFixedRotationLaunchingApp != null
- && startFadeRotationAnimation(false /* shouldDebounce */)) {
+ && startAsyncRotation(false /* shouldDebounce */)) {
// Apply the transaction so the animation leash can take effect immediately.
getPendingTransaction().apply();
}
@@ -1866,28 +1882,28 @@
}, FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS);
return false;
}
- if (mFadeRotationAnimationController == null) {
- mFadeRotationAnimationController = new FadeRotationAnimationController(this);
- mFadeRotationAnimationController.hide();
+ if (mAsyncRotationController == null) {
+ mAsyncRotationController = new AsyncRotationController(this);
+ mAsyncRotationController.hide();
return true;
}
return false;
}
/** Re-show the previously hidden windows if all seamless rotated windows are done. */
- void finishFadeRotationAnimationIfPossible() {
- final FadeRotationAnimationController controller = mFadeRotationAnimationController;
+ void finishAsyncRotationIfPossible() {
+ final AsyncRotationController controller = mAsyncRotationController;
if (controller != null && !mDisplayRotation.hasSeamlessRotatingWindow()) {
controller.show();
- mFadeRotationAnimationController = null;
+ mAsyncRotationController = null;
}
}
/** Shows the given window which may be hidden for screen rotation. */
- void finishFadeRotationAnimation(WindowToken windowToken) {
- final FadeRotationAnimationController controller = mFadeRotationAnimationController;
+ void finishAsyncRotation(WindowToken windowToken) {
+ final AsyncRotationController controller = mAsyncRotationController;
if (controller != null && controller.show(windowToken)) {
- mFadeRotationAnimationController = null;
+ mAsyncRotationController = null;
}
}
@@ -1897,7 +1913,7 @@
// The window should look no different before and after rotation.
return false;
}
- final FadeRotationAnimationController controller = mFadeRotationAnimationController;
+ final AsyncRotationController controller = mAsyncRotationController;
return controller == null || !controller.isHandledToken(w.mToken);
}
@@ -2077,20 +2093,12 @@
return WmDisplayCutout.computeSafeInsets(
cutout, displayWidth, displayHeight);
}
- final Insets waterfallInsets =
- RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation);
+ final DisplayCutout rotatedCutout =
+ cutout.getRotated(displayWidth, displayHeight, ROTATION_0, rotation);
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- final Rect[] newBounds = sRotationUtil.getRotatedBounds(
- cutout.getBoundingRectsAll(),
- rotation, displayWidth, displayHeight);
- final CutoutPathParserInfo info = cutout.getCutoutPathParserInfo();
- final CutoutPathParserInfo newInfo = new CutoutPathParserInfo(
- info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(),
- info.getCutoutSpec(), rotation, info.getScale());
- return WmDisplayCutout.computeSafeInsets(
- DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo),
+ return new WmDisplayCutout(rotatedCutout, new Size(
rotated ? displayHeight : displayWidth,
- rotated ? displayWidth : displayHeight);
+ rotated ? displayWidth : displayHeight));
}
private WmDisplayCutout calculateDisplayCutoutForRotationUncached(
@@ -2711,6 +2719,7 @@
// layout.
mInsetsStateController.onDisplayInfoUpdated(false /* notifyInsetsChanged */);
}
+ mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
mInputMonitor.layoutInputConsumers(info.logicalWidth, info.logicalHeight);
mDisplayPolicy.onDisplayInfoChanged(info);
}
@@ -3206,7 +3215,7 @@
// Hide the windows which are not significant in rotation animation. So that the windows
// don't need to block the unfreeze time.
if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
- startFadeRotationAnimationIfNeeded();
+ startAsyncRotationIfNeeded();
}
}
@@ -3228,7 +3237,7 @@
}
if (!controller.isCollecting(this)) {
controller.collect(this);
- startFadeRotationAnimationIfNeeded();
+ startAsyncRotationIfNeeded();
}
return;
}
@@ -3240,7 +3249,7 @@
mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
controller.mTransitionMetricsReporter.associate(t,
startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
- startFadeRotationAnimation(false /* shouldDebounce */);
+ startAsyncRotation(false /* shouldDebounce */);
}
t.setKnownConfigChanges(this, changes);
}
@@ -3272,6 +3281,7 @@
screenRotationAnimation.dumpDebug(proto, SCREEN_ROTATION_ANIMATION);
}
mDisplayFrames.dumpDebug(proto, DISPLAY_FRAMES);
+ proto.write(MIN_SIZE_OF_RESIZEABLE_TASK_DP, mMinSizeOfResizeableTaskDp);
if (mTransitionController.isShellTransitionsEnabled()) {
mTransitionController.dumpDebugLegacy(proto, APP_TRANSITION);
} else {
@@ -3349,6 +3359,7 @@
pw.print(subPrefix); pw.print("init="); pw.print(mInitialDisplayWidth); pw.print("x");
pw.print(mInitialDisplayHeight); pw.print(" "); pw.print(mInitialDisplayDensity);
pw.print("dpi");
+ pw.print(" mMinSizeOfResizeableTaskDp="); pw.print(mMinSizeOfResizeableTaskDp);
if (mInitialDisplayWidth != mBaseDisplayWidth
|| mInitialDisplayHeight != mBaseDisplayHeight
|| mInitialDisplayDensity != mBaseDisplayDensity) {
@@ -3983,8 +3994,8 @@
mInputMethodWindow.mToken.linkFixedRotationTransform(mImeLayeringTarget.mToken);
// Hide the window until the rotation is done to avoid intermediate artifacts if the
// parent surface of IME container is changed.
- if (mFadeRotationAnimationController != null) {
- mFadeRotationAnimationController.hideImmediately(mInputMethodWindow.mToken);
+ if (mAsyncRotationController != null) {
+ mAsyncRotationController.hideImmediately(mInputMethodWindow.mToken);
}
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index ab1e349..1888554 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -305,10 +305,10 @@
private WindowState mRoundedCornerWindow;
/**
- * Windows to determine the color of status bar. See {@link #mNavBarColorWindowCandidate} for
- * the conditions of being candidate window.
+ * A collection of {@link AppearanceRegion} to indicate that which region of status bar applies
+ * which appearance.
*/
- private final ArrayList<WindowState> mStatusBarColorWindows = new ArrayList<>();
+ private final ArrayList<AppearanceRegion> mStatusBarAppearanceRegionList = new ArrayList<>();
/**
* Windows to determine opacity and background of translucent status bar. The window needs to be
@@ -323,7 +323,7 @@
private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
private AppearanceRegion[] mLastStatusBarAppearanceRegions;
- /** The union of checked bounds while fetching {@link #mStatusBarColorWindows}. */
+ /** The union of checked bounds while building {@link #mStatusBarAppearanceRegionList}. */
private final Rect mStatusBarColorCheckedBounds = new Rect();
/** The union of checked bounds while fetching {@link #mStatusBarBackgroundWindows}. */
@@ -336,6 +336,7 @@
private long mPendingPanicGestureUptime;
private static final Rect sTmpRect = new Rect();
+ private static final Rect sTmpRect2 = new Rect();
private static final Rect sTmpLastParentFrame = new Rect();
private static final Rect sTmpDisplayCutoutSafe = new Rect();
private static final Rect sTmpDisplayFrame = new Rect();
@@ -1526,7 +1527,7 @@
mTopFullscreenOpaqueWindowState = null;
mNavBarColorWindowCandidate = null;
mNavBarBackgroundWindow = null;
- mStatusBarColorWindows.clear();
+ mStatusBarAppearanceRegionList.clear();
mStatusBarBackgroundWindows.clear();
mStatusBarColorCheckedBounds.setEmpty();
mStatusBarBackgroundCheckedBounds.setEmpty();
@@ -1606,7 +1607,9 @@
mStatusBarBackgroundWindows.add(win);
mStatusBarBackgroundCheckedBounds.union(sTmpRect);
if (!mStatusBarColorCheckedBounds.contains(sTmpRect)) {
- mStatusBarColorWindows.add(win);
+ mStatusBarAppearanceRegionList.add(new AppearanceRegion(
+ win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
+ new Rect(win.getFrame())));
mStatusBarColorCheckedBounds.union(sTmpRect);
}
}
@@ -1626,13 +1629,10 @@
}
}
} else if (win.isDimming()) {
- // For dimming window whose host bounds is overlapping with system bars, it can be
- // used to determine colors but not opacity of system bars.
- if (mStatusBar != null
- && sTmpRect.setIntersect(win.getBounds(), mStatusBar.getFrame())
- && !mStatusBarColorCheckedBounds.contains(sTmpRect)) {
- mStatusBarColorWindows.add(win);
- mStatusBarColorCheckedBounds.union(sTmpRect);
+ if (mStatusBar != null) {
+ addStatusBarAppearanceRegionsForDimmingWindow(
+ win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
+ mStatusBar.getFrame(), win.getBounds(), win.getFrame());
}
if (isOverlappingWithNavBar && mNavBarColorWindowCandidate == null) {
mNavBarColorWindowCandidate = win;
@@ -1640,6 +1640,48 @@
}
}
+ private void addStatusBarAppearanceRegionsForDimmingWindow(int appearance, Rect statusBarFrame,
+ Rect winBounds, Rect winFrame) {
+ if (!sTmpRect.setIntersect(winBounds, statusBarFrame)) {
+ return;
+ }
+ if (mStatusBarColorCheckedBounds.contains(sTmpRect)) {
+ return;
+ }
+ if (appearance == 0 || !sTmpRect2.setIntersect(winFrame, statusBarFrame)) {
+ mStatusBarAppearanceRegionList.add(new AppearanceRegion(0, new Rect(winBounds)));
+ mStatusBarColorCheckedBounds.union(sTmpRect);
+ return;
+ }
+ // A dimming window can divide status bar into different appearance regions (up to 3).
+ // +---------+-------------+---------+
+ // |/////////| |/////////| <-- Status Bar
+ // +---------+-------------+---------+
+ // |/////////| |/////////|
+ // |/////////| |/////////|
+ // |/////////| |/////////|
+ // |/////////| |/////////|
+ // |/////////| |/////////|
+ // +---------+-------------+---------+
+ // ^ ^ ^
+ // dim layer window dim layer
+ mStatusBarAppearanceRegionList.add(new AppearanceRegion(appearance, new Rect(winFrame)));
+ if (!sTmpRect.equals(sTmpRect2)) {
+ if (sTmpRect.height() == sTmpRect2.height()) {
+ if (sTmpRect.left != sTmpRect2.left) {
+ mStatusBarAppearanceRegionList.add(new AppearanceRegion(0, new Rect(
+ winBounds.left, winBounds.top, sTmpRect2.left, winBounds.bottom)));
+ }
+ if (sTmpRect.right != sTmpRect2.right) {
+ mStatusBarAppearanceRegionList.add(new AppearanceRegion(0, new Rect(
+ sTmpRect2.right, winBounds.top, winBounds.right, winBounds.bottom)));
+ }
+ }
+ // We don't have vertical status bar yet, so we don't handle the other orientation.
+ }
+ mStatusBarColorCheckedBounds.union(sTmpRect);
+ }
+
/**
* Called following layout of all windows and after policy has been applied
* to each window. If in this function you do
@@ -2270,14 +2312,9 @@
final String focusedApp = win.mAttrs.packageName;
final boolean isFullscreen = !win.getRequestedVisibility(ITYPE_STATUS_BAR)
|| !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR);
- final AppearanceRegion[] appearanceRegions =
- new AppearanceRegion[mStatusBarColorWindows.size()];
- for (int i = mStatusBarColorWindows.size() - 1; i >= 0; i--) {
- final WindowState windowState = mStatusBarColorWindows.get(i);
- appearanceRegions[i] = new AppearanceRegion(
- getStatusBarAppearance(windowState, windowState),
- new Rect(windowState.getFrame()));
- }
+ final AppearanceRegion[] statusBarAppearanceRegions =
+ new AppearanceRegion[mStatusBarAppearanceRegionList.size()];
+ mStatusBarAppearanceRegionList.toArray(statusBarAppearanceRegions);
if (mLastDisableFlags != disableFlags) {
mLastDisableFlags = disableFlags;
final String cause = win.toString();
@@ -2289,7 +2326,7 @@
&& mRequestedVisibilities.equals(win.getRequestedVisibilities())
&& Objects.equals(mFocusedApp, focusedApp)
&& mLastFocusIsFullscreen == isFullscreen
- && Arrays.equals(mLastStatusBarAppearanceRegions, appearanceRegions)) {
+ && Arrays.equals(mLastStatusBarAppearanceRegions, statusBarAppearanceRegions)) {
return;
}
if (mDisplayContent.isDefaultDisplay && mLastFocusIsFullscreen != isFullscreen
@@ -2304,20 +2341,12 @@
mRequestedVisibilities = requestedVisibilities;
mFocusedApp = focusedApp;
mLastFocusIsFullscreen = isFullscreen;
- mLastStatusBarAppearanceRegions = appearanceRegions;
+ mLastStatusBarAppearanceRegions = statusBarAppearanceRegions;
callStatusBarSafely(statusBar -> statusBar.onSystemBarAttributesChanged(displayId,
- appearance, appearanceRegions, isNavbarColorManagedByIme, behavior,
+ appearance, statusBarAppearanceRegions, isNavbarColorManagedByIme, behavior,
requestedVisibilities, focusedApp));
}
- private int getStatusBarAppearance(WindowState opaque, WindowState opaqueOrDimming) {
- final boolean onKeyguard = isKeyguardShowing() && !isKeyguardOccluded();
- final WindowState colorWin = onKeyguard ? mNotificationShade : opaqueOrDimming;
- return isLightBarAllowed(colorWin, Type.statusBars()) && (colorWin == opaque || onKeyguard)
- ? (colorWin.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS)
- : 0;
- }
-
private void callStatusBarSafely(Consumer<StatusBarManagerInternal> consumer) {
mHandler.post(() -> {
StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
@@ -2711,11 +2740,10 @@
pw.print(prefix); pw.print("mNavBarBackgroundWindow=");
pw.println(mNavBarBackgroundWindow);
}
- if (!mStatusBarColorWindows.isEmpty()) {
- pw.print(prefix); pw.println("mStatusBarColorWindows=");
- for (int i = mStatusBarColorWindows.size() - 1; i >= 0; i--) {
- final WindowState win = mStatusBarColorWindows.get(i);
- pw.print(prefixInner); pw.println(win);
+ if (mLastStatusBarAppearanceRegions != null) {
+ pw.print(prefix); pw.println("mLastStatusBarAppearanceRegions=");
+ for (int i = mLastStatusBarAppearanceRegions.length - 1; i >= 0; i--) {
+ pw.print(prefixInner); pw.println(mLastStatusBarAppearanceRegions[i]);
}
}
if (!mStatusBarBackgroundWindows.isEmpty()) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 8c8b33f..7387ea3 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -638,7 +638,7 @@
}, true /* traverseTopToBottom */);
mSeamlessRotationCount = 0;
mRotatingSeamlessly = false;
- mDisplayContent.finishFadeRotationAnimationIfPossible();
+ mDisplayContent.finishAsyncRotationIfPossible();
}
private void prepareSeamlessRotation() {
@@ -729,7 +729,7 @@
"Performing post-rotate rotation after seamless rotation");
// Finish seamless rotation.
mRotatingSeamlessly = false;
- mDisplayContent.finishFadeRotationAnimationIfPossible();
+ mDisplayContent.finishAsyncRotationIfPossible();
updateRotationAndSendNewConfigIfChanged();
}
@@ -1103,6 +1103,21 @@
return oldRotation != rotation;
}
+
+ /**
+ * Resets whether the screen can be rotated via the accelerometer in all 4 rotations as the
+ * default behavior.
+ *
+ * To be called if there is potential that the value changed. For example if the active display
+ * changed.
+ *
+ * At the moment it is called from
+ * {@link DisplayWindowSettings#applyRotationSettingsToDisplayLocked}.
+ */
+ void resetAllowAllRotations() {
+ mAllowAllRotations = ALLOW_ALL_ROTATIONS_UNDEFINED;
+ }
+
/**
* Given an orientation constant, returns the appropriate surface rotation, taking into account
* sensors, docking mode, rotation lock, and other factors.
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 483c799..70c769d 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -297,6 +297,8 @@
final boolean ignoreOrientationRequest = settings.mIgnoreOrientationRequest != null
? settings.mIgnoreOrientationRequest : false;
dc.setIgnoreOrientationRequest(ignoreOrientationRequest);
+
+ dc.getDisplayRotation().resetAllowAllRotations();
}
/**
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index a8a9231..21eea94 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -298,9 +298,9 @@
private Point getWindowFrameSurfacePosition() {
if (mControl != null) {
- final FadeRotationAnimationController fadeController =
- mWin.mDisplayContent.getFadeRotationAnimationController();
- if (fadeController != null && fadeController.shouldFreezeInsetsPosition(mWin)) {
+ final AsyncRotationController controller =
+ mWin.mDisplayContent.getAsyncRotationController();
+ if (controller != null && controller.shouldFreezeInsetsPosition(mWin)) {
// Use previous position because the fade-out animation runs in old rotation.
return mControl.getSurfacePosition();
}
diff --git a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
index 80f2ab6..0ae119a 100644
--- a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
+++ b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
@@ -80,8 +80,8 @@
* @param show true for fade-in, otherwise for fade-out.
*/
public void fadeWindowToken(boolean show) {
- final FadeRotationAnimationController controller =
- mDisplayContent.getFadeRotationAnimationController();
+ final AsyncRotationController controller =
+ mDisplayContent.getAsyncRotationController();
final Runnable fadeAnim = () -> fadeWindowToken(show, mNavigationBar.mToken,
ANIMATION_TYPE_APP_TRANSITION);
if (controller == null) {
diff --git a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
index 49d30cd..36c092b 100644
--- a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
@@ -92,7 +92,7 @@
|| transit == TRANSIT_OLD_WALLPAPER_CLOSE)
&& displayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
&& service.getRecentsAnimationController() == null
- && displayContent.getFadeRotationAnimationController() == null;
+ && displayContent.getAsyncRotationController() == null;
}
/**
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index f97a48b..1183094 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -614,7 +614,7 @@
private void attachNavigationBarToApp() {
if (!mShouldAttachNavBarToAppDuringTransition
// Skip the case where the nav bar is controlled by fade rotation.
- || mDisplayContent.getFadeRotationAnimationController() != null) {
+ || mDisplayContent.getAsyncRotationController() != null) {
return;
}
boolean shouldTranslateNavBar = false;
@@ -701,7 +701,7 @@
void animateNavigationBarForAppLaunch(long duration) {
if (!mShouldAttachNavBarToAppDuringTransition
// Skip the case where the nav bar is controlled by fade rotation.
- || mDisplayContent.getFadeRotationAnimationController() != null
+ || mDisplayContent.getAsyncRotationController() != null
|| mNavigationBarAttachedToApp
|| mNavBarAttachedApp == null) {
return;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index d031bec..4c72d02b 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -73,7 +73,6 @@
import static com.android.server.wm.ActivityTaskSupervisor.dumpHistoryList;
import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
import static com.android.server.wm.KeyguardController.KEYGUARD_SLEEP_TOKEN_TAG;
-import static com.android.server.wm.RootWindowContainerProto.DEFAULT_MIN_SIZE_RESIZABLE_TASK;
import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT;
import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER;
import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER;
@@ -111,7 +110,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
@@ -134,7 +132,6 @@
import android.service.voice.IVoiceInteractionSession;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.DisplayMetrics;
import android.util.IntArray;
import android.util.Pair;
import android.util.Slog;
@@ -1220,11 +1217,6 @@
pw.println(mTopFocusedDisplayId);
}
- void dumpDefaultMinSizeOfResizableTask(PrintWriter pw) {
- pw.print(" mDefaultMinSizeOfResizeableTaskDp=");
- pw.println(mDefaultMinSizeOfResizeableTaskDp);
- }
-
void dumpLayoutNeededDisplayIds(PrintWriter pw) {
if (!isLayoutNeeded()) {
return;
@@ -1271,7 +1263,6 @@
mTaskSupervisor.getKeyguardController().dumpDebug(proto, KEYGUARD_CONTROLLER);
proto.write(IS_HOME_RECENTS_COMPONENT,
mTaskSupervisor.mRecentTasks.isRecentsComponentHomeActivity(mCurrentUser));
- proto.write(DEFAULT_MIN_SIZE_RESIZABLE_TASK, mDefaultMinSizeOfResizeableTaskDp);
proto.end(token);
}
@@ -1359,7 +1350,6 @@
mDefaultDisplay = displayContent;
}
}
- calculateDefaultMinimalSizeOfResizeableTasks();
final TaskDisplayArea defaultTaskDisplayArea = getDefaultTaskDisplayArea();
defaultTaskDisplayArea.getOrCreateRootHomeTask(ON_TOP);
@@ -3477,17 +3467,6 @@
mService.startLaunchPowerMode(reason);
}
- // TODO(b/191434136): handle this properly when we add multi-window support on secondary
- // display.
- private void calculateDefaultMinimalSizeOfResizeableTasks() {
- final Resources res = mService.mContext.getResources();
- final float minimalSize = res.getDimension(
- com.android.internal.R.dimen.default_minimal_size_resizable_task);
- final DisplayMetrics dm = res.getDisplayMetrics();
-
- mDefaultMinSizeOfResizeableTaskDp = (int) (minimalSize / dm.density);
- }
-
/**
* Dumps the activities matching the given {@param name} in the either the focused root task
* or all visible root tasks if {@param dumpVisibleRootTasksOnly} is true.
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 7acc0c5..9b94f44 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -45,7 +45,6 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ShortcutServiceInternal;
-import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Binder;
@@ -222,17 +221,17 @@
@Override
public int relayout(IWindow window, WindowManager.LayoutParams attrs,
- int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber,
+ int requestedWidth, int requestedHeight, int viewFlags, int flags,
ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
SurfaceControl outSurfaceControl, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls, Point outSurfaceSize) {
+ InsetsSourceControl[] outActiveControls) {
if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from "
+ Binder.getCallingPid());
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag);
int res = mService.relayoutWindow(this, window, attrs,
- requestedWidth, requestedHeight, viewFlags, flags, frameNumber,
+ requestedWidth, requestedHeight, viewFlags, flags,
outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
- outActiveControls, outSurfaceSize);
+ outActiveControls);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to "
+ Binder.getCallingPid());
@@ -252,6 +251,11 @@
}
@Override
+ public void clearTouchableRegion(IWindow window) {
+ mService.clearTouchableRegion(this, window);
+ }
+
+ @Override
public void finishDrawing(IWindow window,
@Nullable SurfaceControl.Transaction postDrawTransaction) {
if (DEBUG) Slog.v(TAG_WM, "IWindow finishDrawing called for " + window);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 2331dc4..ff9d9f7 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -281,6 +281,8 @@
// code.
static final int PERSIST_TASK_VERSION = 1;
+ private static final int DEFAULT_MIN_TASK_SIZE_DP = 220;
+
private float mShadowRadius = 0;
/**
@@ -2052,7 +2054,9 @@
// so that the user can not render the task fragment too small to manipulate. We don't need
// to do this for the root pinned task as the bounds are controlled by the system.
if (!inPinnedWindowingMode()) {
- final int defaultMinSizeDp = mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp;
+ // Use Display specific min sizes when there is one associated with this Task.
+ final int defaultMinSizeDp = mDisplayContent == null
+ ? DEFAULT_MIN_TASK_SIZE_DP : mDisplayContent.mMinSizeOfResizeableTaskDp;
final float density = (float) parentConfig.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
final int defaultMinSize = (int) (defaultMinSizeDp * density);
@@ -3413,7 +3417,8 @@
info.isResizeable = isResizeable();
info.minWidth = mMinWidth;
info.minHeight = mMinHeight;
- info.defaultMinSize = mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp;
+ info.defaultMinSize = mDisplayContent == null
+ ? DEFAULT_MIN_TASK_SIZE_DP : mDisplayContent.mMinSizeOfResizeableTaskDp;
info.positionInParent = getRelativePosition();
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index b13c9a9..ded58f4 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -37,7 +37,6 @@
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -72,6 +71,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.animation.Animation;
@@ -101,6 +101,9 @@
/** The default package for resources */
private static final String DEFAULT_PACKAGE = "android";
+ /** The transition has been created but isn't collecting yet. */
+ private static final int STATE_PENDING = -1;
+
/** The transition has been created and is collecting, but hasn't formally started. */
private static final int STATE_COLLECTING = 0;
@@ -122,6 +125,7 @@
private static final int STATE_ABORT = 3;
@IntDef(prefix = { "STATE_" }, value = {
+ STATE_PENDING,
STATE_COLLECTING,
STATE_STARTED,
STATE_PLAYING,
@@ -131,7 +135,7 @@
@interface TransitionState {}
final @TransitionType int mType;
- private int mSyncId;
+ private int mSyncId = -1;
private @TransitionFlags int mFlags;
private final TransitionController mController;
private final BLASTSyncEngine mSyncEngine;
@@ -152,7 +156,7 @@
final ArraySet<WindowContainer> mParticipants = new ArraySet<>();
/** The final animation targets derived from participants after promotion. */
- private ArraySet<WindowContainer> mTargets = null;
+ private ArrayList<WindowContainer> mTargets;
/** The main display running this transition. */
private DisplayContent mTargetDisplay;
@@ -171,7 +175,7 @@
private IRemoteCallback mClientAnimationStartCallback = null;
private IRemoteCallback mClientAnimationFinishCallback = null;
- private @TransitionState int mState = STATE_COLLECTING;
+ private @TransitionState int mState = STATE_PENDING;
private final ReadyTracker mReadyTracker = new ReadyTracker();
// TODO(b/188595497): remove when not needed.
@@ -179,13 +183,12 @@
private boolean mNavBarAttachedToApp = false;
private int mRecentsDisplayId = INVALID_DISPLAY;
- Transition(@TransitionType int type, @TransitionFlags int flags, long timeoutMs,
+ Transition(@TransitionType int type, @TransitionFlags int flags,
TransitionController controller, BLASTSyncEngine syncEngine) {
mType = type;
mFlags = flags;
mController = controller;
mSyncEngine = syncEngine;
- mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG);
}
void addFlag(int flag) {
@@ -216,13 +219,24 @@
return mFlags;
}
+ /** Starts collecting phase. Once this starts, all relevant surface operations are sync. */
+ void startCollecting(long timeoutMs) {
+ if (mState != STATE_PENDING) {
+ throw new IllegalStateException("Attempting to re-use a transition");
+ }
+ mState = STATE_COLLECTING;
+ mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG);
+ }
+
/**
* Formally starts the transition. Participants can be collected before this is started,
* but this won't consider itself ready until started -- even if all the participants have
* drawn.
*/
void start() {
- if (mState >= STATE_STARTED) {
+ if (mState < STATE_COLLECTING) {
+ throw new IllegalStateException("Can't start Transition which isn't collecting.");
+ } else if (mState >= STATE_STARTED) {
Slog.w(TAG, "Transition already started: " + mSyncId);
}
mState = STATE_STARTED;
@@ -235,6 +249,9 @@
* Adds wc to set of WindowContainers participating in this transition.
*/
void collect(@NonNull WindowContainer wc) {
+ if (mState < STATE_COLLECTING) {
+ throw new IllegalStateException("Transition hasn't started collecting.");
+ }
if (mSyncId < 0) return;
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
mSyncId, wc);
@@ -368,7 +385,7 @@
// usually only size 1
final ArraySet<DisplayContent> displays = new ArraySet<>();
for (int i = mTargets.size() - 1; i >= 0; --i) {
- final WindowContainer target = mTargets.valueAt(i);
+ final WindowContainer target = mTargets.get(i);
if (target.getParent() != null) {
final SurfaceControl targetLeash = getLeashSurface(target);
final SurfaceControl origParent = getOrigParentSurface(target);
@@ -493,10 +510,10 @@
dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */);
}
- final FadeRotationAnimationController fadeRotationController =
- mTargetDisplay.getFadeRotationAnimationController();
- if (fadeRotationController != null) {
- fadeRotationController.onTransitionFinished();
+ final AsyncRotationController asyncRotationController =
+ mTargetDisplay.getAsyncRotationController();
+ if (asyncRotationController != null) {
+ asyncRotationController.onTransitionFinished();
}
// Transient-launch activities cannot be IME target (WindowState#canBeImeTarget),
// so re-compute in case the IME target is changed after transition.
@@ -638,7 +655,7 @@
// This is non-null only if display has changes. It handles the visible windows that don't
// need to be participated in the transition.
- final FadeRotationAnimationController controller = dc.getFadeRotationAnimationController();
+ final AsyncRotationController controller = dc.getAsyncRotationController();
if (controller != null) {
controller.setupStartTransaction(transaction);
}
@@ -728,7 +745,7 @@
if (!dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
// Skip the case where the nav bar is controlled by fade rotation.
- || dc.getFadeRotationAnimationController() != null) {
+ || dc.getAsyncRotationController() != null) {
return;
}
@@ -801,7 +818,7 @@
// Search for the home task. If it is supposed to be visible, then the navbar is not at
// the bottom of the screen, so we need to animate it.
for (int i = 0; i < mTargets.size(); ++i) {
- final Task task = mTargets.valueAt(i).asTask();
+ final Task task = mTargets.get(i).asTask();
if (task == null || !task.isHomeOrRecentsRootTask()) continue;
animate = task.isVisibleRequested();
break;
@@ -884,23 +901,8 @@
private static boolean reportIfNotTop(WindowContainer wc) {
// Organized tasks need to be reported anyways because Core won't show() their surfaces
// and we can't rely on onTaskAppeared because it isn't in sync.
- // Also report wallpaper so it can be handled properly during display change/rotation.
// TODO(shell-transitions): switch onTaskAppeared usage over to transitions OPEN.
- return wc.isOrganized() || isWallpaper(wc);
- }
-
- /** @return the depth of child within ancestor, 0 if child == ancestor, or -1 if not a child. */
- private static int getChildDepth(WindowContainer child, WindowContainer ancestor) {
- WindowContainer parent = child;
- int depth = 0;
- while (parent != null) {
- if (parent == ancestor) {
- return depth;
- }
- parent = parent.getParent();
- ++depth;
- }
- return -1;
+ return wc.isOrganized();
}
private static boolean isWallpaper(WindowContainer wc) {
@@ -930,61 +932,48 @@
*
* @return {@code true} if transition in target can be promoted to its parent.
*/
- private static boolean canPromote(WindowContainer target, ArraySet<WindowContainer> topTargets,
+ private static boolean canPromote(WindowContainer<?> target, Targets targets,
ArrayMap<WindowContainer, ChangeInfo> changes) {
- final WindowContainer parent = target.getParent();
- final ChangeInfo parentChanges = parent != null ? changes.get(parent) : null;
- if (parent == null || !parent.canCreateRemoteAnimationTarget()
- || parentChanges == null || !parentChanges.hasChanged(parent)) {
+ final WindowContainer<?> parent = target.getParent();
+ final ChangeInfo parentChange = changes.get(parent);
+ if (!parent.canCreateRemoteAnimationTarget()
+ || parentChange == null || !parentChange.hasChanged(parent)) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: %s",
- parent == null ? "no parent" : ("parent can't be target " + parent));
+ "parent can't be target " + parent);
return false;
}
if (isWallpaper(target)) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: is wallpaper");
return false;
}
- @TransitionInfo.TransitionMode int mode = TRANSIT_NONE;
- // Go through all siblings of this target to see if any of them would prevent
- // the target from promoting.
- siblingLoop:
+
+ final @TransitionInfo.TransitionMode int mode = changes.get(target).getTransitMode(target);
for (int i = parent.getChildCount() - 1; i >= 0; --i) {
- final WindowContainer sibling = parent.getChildAt(i);
+ final WindowContainer<?> sibling = parent.getChildAt(i);
+ if (target == sibling) continue;
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " check sibling %s",
sibling);
- // Check if any topTargets are the sibling or within it
- for (int j = topTargets.size() - 1; j >= 0; --j) {
- final int depth = getChildDepth(topTargets.valueAt(j), sibling);
- if (depth < 0) continue;
- if (depth == 0) {
- final int siblingMode = changes.get(sibling).getTransitMode(sibling);
+ final ChangeInfo siblingChange = changes.get(sibling);
+ if (siblingChange == null || !targets.wasParticipated(sibling)) {
+ if (sibling.isVisibleRequested()) {
+ // Sibling is visible but not animating, so no promote.
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " sibling is a top target with mode %s",
- TransitionInfo.modeToString(siblingMode));
- if (mode == TRANSIT_NONE) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " no common mode yet, so set it");
- mode = siblingMode;
- } else if (mode != siblingMode) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " SKIP: common mode mismatch. was %s",
- TransitionInfo.modeToString(mode));
- return false;
- }
- continue siblingLoop;
- } else {
- // Sibling subtree may not be promotable.
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " SKIP: sibling contains top target %s",
- topTargets.valueAt(j));
+ " SKIP: sibling is visible but not part of transition");
return false;
}
- }
- // No other animations are playing in this sibling
- if (sibling.isVisibleRequested()) {
- // Sibling is visible but not animating, so no promote.
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " SKIP: sibling is visible but not part of transition");
+ " unrelated invisible sibling %s", sibling);
+ continue;
+ }
+
+ final int siblingMode = siblingChange.getTransitMode(sibling);
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " sibling is a participant with mode %s",
+ TransitionInfo.modeToString(siblingMode));
+ if (mode != siblingMode) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " SKIP: common mode mismatch. was %s",
+ TransitionInfo.modeToString(mode));
return false;
}
}
@@ -994,58 +983,40 @@
/**
* Go through topTargets and try to promote (see {@link #canPromote}) one of them.
*
- * @param topTargets set of just the top-most targets in the hierarchy of participants.
* @param targets all targets that will be sent to the player.
- * @return {@code true} if something was promoted.
*/
- private static boolean tryPromote(ArraySet<WindowContainer> topTargets,
- ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " --- Start combine pass ---");
- // Go through each target until we find one that can be promoted.
- for (WindowContainer targ : topTargets) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " checking %s", targ);
- if (!canPromote(targ, topTargets, changes)) {
+ private static void tryPromote(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
+ WindowContainer<?> lastNonPromotableParent = null;
+ // Go through from the deepest target.
+ for (int i = targets.mArray.size() - 1; i >= 0; --i) {
+ final WindowContainer<?> target = targets.mArray.valueAt(i);
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " checking %s", target);
+ final WindowContainer<?> parent = target.getParent();
+ if (parent == lastNonPromotableParent) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " SKIP: its sibling was rejected");
continue;
}
- // No obstructions found to promotion, so promote
- final WindowContainer parent = targ.getParent();
- final ChangeInfo parentInfo = changes.get(parent);
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " CAN PROMOTE: promoting to parent %s", parent);
- targets.add(parent);
-
- // Go through all children of newly-promoted container and remove them from the
- // top-targets.
- for (int i = parent.getChildCount() - 1; i >= 0; --i) {
- final WindowContainer child = parent.getChildAt(i);
- int idx = targets.indexOf(child);
- if (idx >= 0) {
- final ChangeInfo childInfo = changes.get(child);
- if (reportIfNotTop(child)) {
- childInfo.mParent = parent;
- parentInfo.addChild(child);
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " keep as target %s", child);
- } else {
- if (childInfo.mChildren != null) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " merging children in from %s: %s", child,
- childInfo.mChildren);
- parentInfo.addChildren(childInfo.mChildren);
- }
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " remove from targets %s", child);
- targets.removeAt(idx);
- }
- }
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " remove from topTargets %s", child);
- topTargets.remove(child);
+ if (!canPromote(target, targets, changes)) {
+ lastNonPromotableParent = parent;
+ continue;
}
- topTargets.add(parent);
- return true;
+ if (reportIfNotTop(target)) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " keep as target %s", target);
+ } else {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " remove from targets %s", target);
+ targets.remove(i, target);
+ }
+ if (targets.mArray.indexOfValue(parent) < 0) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " CAN PROMOTE: promoting to parent %s", parent);
+ // The parent has lower depth, so it will be checked in the later iteration.
+ i++;
+ targets.add(parent);
+ }
}
- return false;
}
/**
@@ -1054,22 +1025,15 @@
*/
@VisibleForTesting
@NonNull
- static ArraySet<WindowContainer> calculateTargets(ArraySet<WindowContainer> participants,
+ static ArrayList<WindowContainer> calculateTargets(ArraySet<WindowContainer> participants,
ArrayMap<WindowContainer, ChangeInfo> changes) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Start calculating TransitionInfo based on participants: %s", participants);
- final ArraySet<WindowContainer> topTargets = new ArraySet<>();
- // The final animation targets which cannot promote to higher level anymore.
- final ArraySet<WindowContainer> targets = new ArraySet<>();
-
- final ArrayList<WindowContainer> tmpList = new ArrayList<>();
-
- // Build initial set of top-level participants by removing any participants that are no-ops
- // or children of other participants or are otherwise invalid; however, keep around a list
- // of participants that should always be reported even if they aren't top.
- for (WindowContainer wc : participants) {
- // Don't include detached windows.
+ // Add all valid participants to the target container.
+ final Targets targets = new Targets();
+ for (int i = participants.size() - 1; i >= 0; --i) {
+ final WindowContainer<?> wc = participants.valueAt(i);
if (!wc.isAttached()) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
" Rejecting as detached: %s", wc);
@@ -1084,70 +1048,62 @@
" Rejecting as no-op: %s", wc);
continue;
}
-
- // Search through ancestors to find the top-most participant (if one exists)
- WindowContainer topParent = null;
- tmpList.clear();
- if (reportIfNotTop(wc)) {
- tmpList.add(wc);
- }
- // Wallpaper must be the top (regardless of how nested it is in DisplayAreas).
- boolean skipIntermediateReports = isWallpaper(wc);
- for (WindowContainer p = wc.getParent(); p != null; p = p.getParent()) {
- if (!p.isAttached() || changes.get(p) == null || !changes.get(p).hasChanged(p)) {
- // Again, we're skipping no-ops
- break;
- }
- if (participants.contains(p)) {
- topParent = p;
- break;
- } else if (isWallpaper(p)) {
- skipIntermediateReports = true;
- } else if (reportIfNotTop(p) && !skipIntermediateReports) {
- tmpList.add(p);
- }
- }
- if (topParent != null) {
- // There was an ancestor participant, so don't add wc to targets unless always-
- // report. Similarly, add any always-report parents along the way.
- for (int i = 0; i < tmpList.size(); ++i) {
- targets.add(tmpList.get(i));
- final ChangeInfo info = changes.get(tmpList.get(i));
- info.mParent = i < tmpList.size() - 1 ? tmpList.get(i + 1) : topParent;
- }
- continue;
- }
- // No ancestors in participant-list, so wc is a top target.
targets.add(wc);
- topTargets.add(wc);
+ targets.mValidParticipants.add(wc);
}
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Initial targets: %s",
+ targets.mArray);
+ // Combine the targets from bottom to top if possible.
+ tryPromote(targets, changes);
+ // Establish the relationship between the targets and their top changes.
+ populateParentChanges(targets, changes);
- // Populate children lists
- for (int i = targets.size() - 1; i >= 0; --i) {
- if (changes.get(targets.valueAt(i)).mParent != null) {
- changes.get(changes.get(targets.valueAt(i)).mParent).addChild(targets.valueAt(i));
- }
- }
-
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Initial targets: %s", targets);
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Top targets: %s", topTargets);
-
- // Combine targets by repeatedly going through the topTargets to see if they can be
- // promoted until there aren't any promotions possible.
- while (tryPromote(topTargets, targets, changes)) {
- // Empty on purpose
- }
- return targets;
+ final ArrayList<WindowContainer> targetList = targets.getListSortedByZ();
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Final targets: %s", targetList);
+ return targetList;
}
- /** Add any of `members` within `root` to `out` in top-to-bottom z-order. */
- private static void addMembersInOrder(WindowContainer root, ArraySet<WindowContainer> members,
- ArrayList<WindowContainer> out) {
- for (int i = root.getChildCount() - 1; i >= 0; --i) {
- final WindowContainer child = root.getChildAt(i);
- addMembersInOrder(child, members, out);
- if (members.contains(child)) {
- out.add(child);
+ /** Populates parent to the change info and collects intermediate targets. */
+ private static void populateParentChanges(Targets targets,
+ ArrayMap<WindowContainer, ChangeInfo> changes) {
+ final ArrayList<WindowContainer<?>> intermediates = new ArrayList<>();
+ for (int i = targets.mValidParticipants.size() - 1; i >= 0; --i) {
+ WindowContainer<?> wc = targets.mValidParticipants.get(i);
+ // Go up if the participant has been represented by its parent.
+ while (targets.mArray.indexOfValue(wc) < 0 && wc.getParent() != null) {
+ wc = wc.getParent();
+ }
+ // Wallpaper must belong to the top (regardless of how nested it is in DisplayAreas).
+ final boolean skipIntermediateReports = isWallpaper(wc);
+ intermediates.clear();
+ // Collect the intermediate parents between target and top changed parent.
+ for (WindowContainer<?> p = wc.getParent(); p != null; p = p.getParent()) {
+ final ChangeInfo parentChange = changes.get(p);
+ if (parentChange == null || !parentChange.hasChanged(p)) break;
+ if (parentChange.mParent != null && !skipIntermediateReports) {
+ changes.get(wc).mParent = p;
+ // The chain above the parent was processed.
+ break;
+ }
+ if (targets.mValidParticipants.contains(p)) {
+ if (skipIntermediateReports) {
+ changes.get(wc).mParent = p;
+ } else {
+ intermediates.add(p);
+ }
+ // The parent reaches a participant.
+ break;
+ } else if (reportIfNotTop(p) && !skipIntermediateReports) {
+ intermediates.add(p);
+ }
+ }
+ if (intermediates.isEmpty()) continue;
+ // Add any always-report parents along the way.
+ changes.get(wc).mParent = intermediates.get(0);
+ for (int j = 0; j < intermediates.size() - 1; j++) {
+ final WindowContainer<?> intermediate = intermediates.get(j);
+ changes.get(intermediate).mParent = intermediates.get(j + 1);
+ targets.add(intermediate);
}
}
}
@@ -1184,32 +1140,36 @@
/**
* Construct a TransitionInfo object from a set of targets and changes. Also populates the
* root surface.
+ * @param sortedTargets The targets sorted by z-order from top (index 0) to bottom.
*/
@VisibleForTesting
@NonNull
static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags,
- ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
+ ArrayList<WindowContainer> sortedTargets,
+ ArrayMap<WindowContainer, ChangeInfo> changes) {
final TransitionInfo out = new TransitionInfo(type, flags);
- final ArraySet<WindowContainer> appTargets = new ArraySet<>();
- final ArraySet<WindowContainer> wallpapers = new ArraySet<>();
- for (int i = targets.size() - 1; i >= 0; --i) {
- (isWallpaper(targets.valueAt(i)) ? wallpapers : appTargets).add(targets.valueAt(i));
+ WindowContainer<?> topApp = null;
+ for (int i = 0; i < sortedTargets.size(); i++) {
+ final WindowContainer<?> wc = sortedTargets.get(i);
+ if (!isWallpaper(wc)) {
+ topApp = wc;
+ break;
+ }
}
-
- // Find the top-most shared ancestor of app targets
- if (appTargets.isEmpty()) {
+ if (topApp == null) {
out.setRootLeash(new SurfaceControl(), 0, 0);
return out;
}
- WindowContainer ancestor = appTargets.valueAt(appTargets.size() - 1).getParent();
+ // Find the top-most shared ancestor of app targets.
+ WindowContainer<?> ancestor = topApp.getParent();
// Go up ancestor parent chain until all targets are descendants.
ancestorLoop:
while (ancestor != null) {
- for (int i = appTargets.size() - 1; i >= 0; --i) {
- final WindowContainer wc = appTargets.valueAt(i);
- if (!wc.isDescendantOf(ancestor)) {
+ for (int i = sortedTargets.size() - 1; i >= 0; --i) {
+ final WindowContainer wc = sortedTargets.get(i);
+ if (!isWallpaper(wc) && !wc.isDescendantOf(ancestor)) {
ancestor = ancestor.getParent();
continue ancestorLoop;
}
@@ -1217,11 +1177,6 @@
break;
}
- // Sort targets top-to-bottom in Z. Check ALL targets here in case the display area itself
- // is animating: then we want to include wallpapers at the right position.
- ArrayList<WindowContainer> sortedTargets = new ArrayList<>();
- addMembersInOrder(ancestor, targets, sortedTargets);
-
// make leash based on highest (z-order) direct child of ancestor with a participant.
WindowContainer leashReference = sortedTargets.get(0);
while (leashReference.getParent() != ancestor) {
@@ -1235,14 +1190,6 @@
t.close();
out.setRootLeash(rootLeash, ancestor.getBounds().left, ancestor.getBounds().top);
- // add the wallpapers at the bottom
- for (int i = wallpapers.size() - 1; i >= 0; --i) {
- final WindowContainer wc = wallpapers.valueAt(i);
- // If the displayarea itself is animating, then the wallpaper was already added.
- if (wc.isDescendantOf(ancestor)) break;
- sortedTargets.add(wc);
- }
-
// Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
final int count = sortedTargets.size();
for (int i = 0; i < count; ++i) {
@@ -1386,7 +1333,6 @@
static class ChangeInfo {
// Usually "post" change state.
WindowContainer mParent;
- ArraySet<WindowContainer> mChildren;
// State tracking
boolean mExistenceChanged = false;
@@ -1481,19 +1427,6 @@
}
return flags;
}
-
- void addChild(@NonNull WindowContainer wc) {
- if (mChildren == null) {
- mChildren = new ArraySet<>();
- }
- mChildren.add(wc);
- }
- void addChildren(@NonNull ArraySet<WindowContainer> wcs) {
- if (mChildren == null) {
- mChildren = new ArraySet<>();
- }
- mChildren.addAll(wcs);
- }
}
/**
@@ -1586,4 +1519,64 @@
return b.toString();
}
}
+
+ /**
+ * The container to represent the depth relation for calculating transition targets. The window
+ * container with larger depth is put at larger index. For the same depth, higher z-order has
+ * larger index.
+ */
+ private static class Targets {
+ /** All targets. Its keys (depth) are sorted in ascending order naturally. */
+ final SparseArray<WindowContainer<?>> mArray = new SparseArray<>();
+ /** The initial participants which have changes. */
+ final ArrayList<WindowContainer<?>> mValidParticipants = new ArrayList<>();
+ /** The targets which were represented by their parent. */
+ private ArrayList<WindowContainer<?>> mRemovedTargets;
+ private int mDepthFactor;
+
+ void add(WindowContainer<?> target) {
+ // The number of slots per depth is larger than the total number of window container,
+ // so the depth score (key) won't have collision.
+ if (mDepthFactor == 0) {
+ mDepthFactor = target.mWmService.mRoot.getTreeWeight() + 1;
+ }
+ int score = target.getPrefixOrderIndex();
+ WindowContainer<?> wc = target;
+ while (wc != null) {
+ final WindowContainer<?> parent = wc.getParent();
+ if (parent != null) {
+ score += mDepthFactor;
+ }
+ wc = parent;
+ }
+ mArray.put(score, target);
+ }
+
+ void remove(int index, WindowContainer<?> removingTarget) {
+ mArray.removeAt(index);
+ if (mRemovedTargets == null) {
+ mRemovedTargets = new ArrayList<>();
+ }
+ mRemovedTargets.add(removingTarget);
+ }
+
+ boolean wasParticipated(WindowContainer<?> wc) {
+ return mArray.indexOfValue(wc) >= 0
+ || (mRemovedTargets != null && mRemovedTargets.contains(wc));
+ }
+
+ /** Returns the target list sorted by z-order in ascending order (index 0 is top). */
+ ArrayList<WindowContainer> getListSortedByZ() {
+ final SparseArray<WindowContainer<?>> arrayByZ = new SparseArray<>(mArray.size());
+ for (int i = mArray.size() - 1; i >= 0; --i) {
+ final int zOrder = mArray.keyAt(i) % mDepthFactor;
+ arrayByZ.put(zOrder, mArray.valueAt(i));
+ }
+ final ArrayList<WindowContainer> sortedTargets = new ArrayList<>(arrayByZ.size());
+ for (int i = arrayByZ.size() - 1; i >= 0; --i) {
+ sortedTargets.add(arrayByZ.valueAt(i));
+ }
+ return sortedTargets;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index fe968ec..7a031db 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -128,16 +128,46 @@
throw new IllegalStateException("Shell Transitions not enabled");
}
if (mCollectingTransition != null) {
- throw new IllegalStateException("Simultaneous transitions not supported yet.");
+ throw new IllegalStateException("Simultaneous transition collection not supported"
+ + " yet. Use {@link #createPendingTransition} for explicit queueing.");
}
+ Transition transit = new Transition(type, flags, this, mAtm.mWindowManager.mSyncEngine);
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s", transit);
+ moveToCollecting(transit);
+ return transit;
+ }
+
+ /** Starts Collecting */
+ private void moveToCollecting(@NonNull Transition transition) {
+ if (mCollectingTransition != null) {
+ throw new IllegalStateException("Simultaneous transition collection not supported.");
+ }
+ mCollectingTransition = transition;
// Distinguish change type because the response time is usually expected to be not too long.
- final long timeoutMs = type == TRANSIT_CHANGE ? CHANGE_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
- mCollectingTransition = new Transition(type, flags, timeoutMs, this,
- mAtm.mWindowManager.mSyncEngine);
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s",
+ final long timeoutMs =
+ transition.mType == TRANSIT_CHANGE ? CHANGE_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
+ mCollectingTransition.startCollecting(timeoutMs);
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Start collecting in Transition: %s",
mCollectingTransition);
dispatchLegacyAppTransitionPending();
- return mCollectingTransition;
+ }
+
+ /** Creates a transition representation but doesn't start collecting. */
+ @NonNull
+ PendingStartTransition createPendingTransition(@WindowManager.TransitionType int type) {
+ if (mTransitionPlayer == null) {
+ throw new IllegalStateException("Shell Transitions not enabled");
+ }
+ final PendingStartTransition out = new PendingStartTransition(new Transition(type,
+ 0 /* flags */, this, mAtm.mWindowManager.mSyncEngine));
+ // We want to start collecting immediately when the engine is free, otherwise it may
+ // be busy again.
+ out.setStartSync(() -> {
+ moveToCollecting(out.mTransition);
+ });
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating PendingTransition: %s",
+ out.mTransition);
+ return out;
}
void registerTransitionPlayer(@Nullable ITransitionPlayer player,
@@ -507,6 +537,15 @@
proto.end(token);
}
+ /** Represents a startTransition call made while there is other active BLAST SyncGroup. */
+ class PendingStartTransition extends WindowOrganizerController.PendingTransaction {
+ final Transition mTransition;
+
+ PendingStartTransition(Transition transition) {
+ mTransition = transition;
+ }
+ }
+
static class TransitionMetricsReporter extends ITransitionMetricsReporter.Stub {
private final ArrayMap<IBinder, LongConsumer> mMetricConsumers = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index fc154a8..36bb55e 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -28,9 +28,6 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.view.DisplayInfo;
-import android.view.ViewGroup;
-import android.view.WindowManager;
import android.view.animation.Animation;
import com.android.internal.protolog.common.ProtoLog;
@@ -190,23 +187,6 @@
setVisible(visible);
}
- @Override
- void adjustWindowParams(WindowState win, WindowManager.LayoutParams attrs) {
- if (attrs.height == ViewGroup.LayoutParams.MATCH_PARENT
- || attrs.width == ViewGroup.LayoutParams.MATCH_PARENT) {
- return;
- }
-
- final DisplayInfo displayInfo = win.getDisplayInfo();
-
- final float layoutScale = Math.max(
- (float) displayInfo.logicalHeight / (float) attrs.height,
- (float) displayInfo.logicalWidth / (float) attrs.width);
- attrs.height = (int) (attrs.height * layoutScale);
- attrs.width = (int) (attrs.width * layoutScale);
- attrs.flags |= WindowManager.LayoutParams.FLAG_SCALED;
- }
-
boolean hasVisibleNotDrawnWallpaper() {
if (!isVisible()) return false;
for (int j = mChildren.size() - 1; j >= 0; --j) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 4006848..11d1983 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -85,8 +85,8 @@
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
import android.view.SurfaceControl.Builder;
+import android.view.SurfaceControlViewHost;
import android.view.SurfaceSession;
import android.view.TaskTransitionSpec;
import android.view.WindowManager;
@@ -661,6 +661,11 @@
}
}
+ /** Returns the total number of descendants, including self. */
+ int getTreeWeight() {
+ return mTreeWeight;
+ }
+
/**
* @return The index of this element in the hierarchy tree in prefix order.
*/
@@ -3331,7 +3336,10 @@
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "setSyncGroup #%d on %s", group.mSyncId, this);
if (group != null) {
if (mSyncGroup != null && mSyncGroup != group) {
- throw new IllegalStateException("Can't sync on 2 engines simultaneously");
+ // This can still happen if WMCore starts a new transition when there is ongoing
+ // sync transaction from Shell. Please file a bug if it happens.
+ throw new IllegalStateException("Can't sync on 2 engines simultaneously"
+ + " currentSyncId=" + mSyncGroup.mSyncId + " newSyncId=" + group.mSyncId);
}
}
mSyncGroup = group;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c1a8ceb..3ba332b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2105,6 +2105,19 @@
Slog.i(tag, s, e);
}
+ void clearTouchableRegion(Session session, IWindow client) {
+ int uid = Binder.getCallingUid();
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ WindowState w = windowForClientLocked(session, client, false);
+ w.clearClientTouchableRegion();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
void setInsetsWindow(Session session, IWindow client, int touchableInsets, Rect contentInsets,
Rect visibleInsets, Region touchableRegion) {
int uid = Binder.getCallingUid();
@@ -2130,6 +2143,7 @@
}
w.setDisplayLayoutNeeded();
mWindowPlacerLocked.performSurfacePlacement();
+ w.getDisplayContent().getInputMonitor().updateInputWindowsLw(true);
// We need to report touchable region changes to accessibility.
if (mAccessibilityController.hasCallbacks()) {
@@ -2180,9 +2194,9 @@
public int relayoutWindow(Session session, IWindow client, LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility, int flags,
- long frameNumber, ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
+ ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
SurfaceControl outSurfaceControl, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls, Point outSurfaceSize) {
+ InsetsSourceControl[] outActiveControls) {
Arrays.fill(outActiveControls, null);
int result = 0;
boolean configChanged;
@@ -2202,14 +2216,11 @@
win.setRequestedSize(requestedWidth, requestedHeight);
}
- win.setFrameNumber(frameNumber);
-
int attrChanges = 0;
int flagChanges = 0;
int privateFlagChanges = 0;
if (attrs != null) {
displayPolicy.adjustWindowParamsLw(win, attrs);
- win.mToken.adjustWindowParams(win, attrs);
attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), uid, pid);
attrs.inputFeatures = sanitizeSpyWindow(attrs.inputFeatures, win.getName(), uid,
pid);
@@ -2497,11 +2508,6 @@
displayContent.sendNewConfiguration();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
- if (winAnimator.mSurfaceController != null) {
- win.calculateSurfaceBounds(win.getLayoutingAttrs(
- win.getWindowConfiguration().getRotation()), mTmpRect);
- outSurfaceSize.set(mTmpRect.width(), mTmpRect.height());
- }
getInsetsSourceControls(win, outActiveControls);
}
@@ -2580,7 +2586,7 @@
WindowSurfaceController surfaceController;
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");
- surfaceController = winAnimator.createSurfaceLocked(win.mAttrs.type);
+ surfaceController = winAnimator.createSurfaceLocked();
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@@ -5680,6 +5686,11 @@
}
@Override
+ public void saveWindowTraceToFile() {
+ mWindowTracing.saveForBugreport(null /* printwriter */);
+ }
+
+ @Override
public boolean isWindowTraceEnabled() {
return mWindowTracing.isEnabled();
}
@@ -6415,7 +6426,6 @@
pw.print(" mGlobalConfiguration="); pw.println(mRoot.getConfiguration());
pw.print(" mHasPermanentDpad="); pw.println(mHasPermanentDpad);
mRoot.dumpTopFocusedDisplayId(pw);
- mRoot.dumpDefaultMinSizeOfResizableTask(pw);
mRoot.forAllDisplays(dc -> {
final int displayId = dc.getDisplayId();
final InsetsControlTarget imeLayeringTarget = dc.getImeTarget(IME_TARGET_LAYERING);
@@ -6433,6 +6443,8 @@
pw.print(" imeControlTarget in display# "); pw.print(displayId);
pw.print(' '); pw.println(imeControlTarget);
}
+ pw.print(" Minimum task size of display#"); pw.print(displayId);
+ pw.print(' '); pw.print(dc.mMinSizeOfResizeableTaskDp);
});
pw.print(" mInTouchMode="); pw.println(mInTouchMode);
pw.print(" mBlurEnabled="); pw.println(mBlurController.getBlurEnabled());
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 455856c..27024ce 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -76,6 +76,7 @@
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.function.pooled.PooledConsumer;
@@ -94,7 +95,7 @@
* @see android.window.WindowOrganizer
*/
class WindowOrganizerController extends IWindowOrganizerController.Stub
- implements BLASTSyncEngine.TransactionReadyListener {
+ implements BLASTSyncEngine.TransactionReadyListener, BLASTSyncEngine.SyncEngineListener {
private static final String TAG = "WindowOrganizerController";
@@ -117,6 +118,21 @@
private final HashMap<Integer, IWindowContainerTransactionCallback>
mTransactionCallbacksByPendingSyncId = new HashMap();
+ /**
+ * A queue of transaction waiting for their turn to sync. Currently {@link BLASTSyncEngine} only
+ * supports 1 sync at a time, so we have to queue them.
+ *
+ * WMCore has enough information to ensure that it won't end up collecting multiple transitions
+ * in parallel by itself; however, Shell can start transitions/apply sync transaction at
+ * arbitrary times via {@link WindowOrganizerController#startTransition} and
+ * {@link WindowOrganizerController#applySyncTransaction}, so we have to support those coming in
+ * at any time (even while already syncing).
+ *
+ * This is really just a back-up for unrealistic situations (eg. during tests). In practice,
+ * this shouldn't ever happen.
+ */
+ private final ArrayList<PendingTransaction> mPendingTransactions = new ArrayList<>();
+
final TaskOrganizerController mTaskOrganizerController;
final DisplayAreaOrganizerController mDisplayAreaOrganizerController;
final TaskFragmentOrganizerController mTaskFragmentOrganizerController;
@@ -140,6 +156,7 @@
void setWindowManager(WindowManagerService wms) {
mTransitionController = new TransitionController(mService, wms.mTaskSnapshotController);
mTransitionController.registerLegacyListener(wms.mActivityManagerAppTransitionNotifier);
+ wms.mSyncEngine.setSyncEngineListener(this);
}
TransitionController getTransitionController() {
@@ -184,6 +201,11 @@
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
+ if (callback == null) {
+ applyTransaction(t, -1 /* syncId*/, null /*transition*/, caller);
+ return -1;
+ }
+
/**
* If callback is non-null we are looking to synchronize this transaction by
* collecting all the results in to a SurfaceFlinger transaction and then delivering
@@ -196,13 +218,25 @@
* all the WindowContainers will eventually finish applying their changes and notify
* the BLASTSyncEngine which will deliver the Transaction to the callback.
*/
- int syncId = -1;
- if (callback != null) {
- syncId = startSyncWithOrganizer(callback);
- }
- applyTransaction(t, syncId, null /*transition*/, caller);
- if (syncId >= 0) {
+ final BLASTSyncEngine.SyncGroup syncGroup = prepareSyncWithOrganizer(callback);
+ final int syncId = syncGroup.mSyncId;
+ if (!mService.mWindowManager.mSyncEngine.hasActiveSync()) {
+ mService.mWindowManager.mSyncEngine.startSyncSet(syncGroup);
+ applyTransaction(t, syncId, null /*transition*/, caller);
setSyncReady(syncId);
+ } else {
+ // Because the BLAST engine only supports one sync at a time, queue the
+ // transaction.
+ final PendingTransaction pt = new PendingTransaction();
+ // Start sync group immediately when the SyncEngine is free.
+ pt.setStartSync(() ->
+ mService.mWindowManager.mSyncEngine.startSyncSet(syncGroup));
+ // Those will be post so that it won't interrupt ongoing transition.
+ pt.setStartTransaction(() -> {
+ applyTransaction(t, syncId, null /*transition*/, caller);
+ setSyncReady(syncId);
+ });
+ mPendingTransactions.add(pt);
}
return syncId;
}
@@ -220,31 +254,49 @@
try {
synchronized (mGlobalLock) {
Transition transition = Transition.fromBinder(transitionToken);
+ if (mTransitionController.getTransitionPlayer() == null && transition == null) {
+ Slog.w(TAG, "Using shell transitions API for legacy transitions.");
+ if (t == null) {
+ throw new IllegalArgumentException("Can't use legacy transitions in"
+ + " compatibility mode with no WCT.");
+ }
+ applyTransaction(t, -1 /* syncId */, null, caller);
+ return null;
+ }
// In cases where transition is already provided, the "readiness lifecycle" of the
// transition is determined outside of this transaction. However, if this is a
// direct call from shell, the entire transition lifecycle is contained in the
// provided transaction and thus we can setReady immediately after apply.
- boolean needsSetReady = transition == null && t != null;
+ final boolean needsSetReady = transition == null && t != null;
+ final WindowContainerTransaction wct =
+ t != null ? t : new WindowContainerTransaction();
if (transition == null) {
if (type < 0) {
throw new IllegalArgumentException("Can't create transition with no type");
}
- if (mTransitionController.getTransitionPlayer() == null) {
- Slog.w(TAG, "Using shell transitions API for legacy transitions.");
- if (t == null) {
- throw new IllegalArgumentException("Can't use legacy transitions in"
- + " compatibility mode with no WCT.");
- }
- applyTransaction(t, -1 /* syncId */, null, caller);
- return null;
+ // If there is already a collecting transition, queue up a new transition and
+ // return that. The actual start and apply will then be deferred until that
+ // transition starts collecting. This should almost never happen except during
+ // tests.
+ if (mService.mWindowManager.mSyncEngine.hasActiveSync()) {
+ Slog.e(TAG, "startTransition() while one is already collecting.");
+ final TransitionController.PendingStartTransition pt =
+ mTransitionController.createPendingTransition(type);
+ // Those will be post so that it won't interrupt ongoing transition.
+ pt.setStartTransaction(() -> {
+ pt.mTransition.start();
+ applyTransaction(wct, -1 /*syncId*/, pt.mTransition, caller);
+ if (needsSetReady) {
+ pt.mTransition.setAllReady();
+ }
+ });
+ mPendingTransactions.add(pt);
+ return pt.mTransition;
}
transition = mTransitionController.createTransition(type);
}
transition.start();
- if (t == null) {
- t = new WindowContainerTransaction();
- }
- applyTransaction(t, -1 /*syncId*/, transition, caller);
+ applyTransaction(wct, -1 /*syncId*/, transition, caller);
if (needsSetReady) {
transition.setAllReady();
}
@@ -1036,11 +1088,24 @@
return mTaskFragmentOrganizerController;
}
+ /**
+ * This will prepare a {@link BLASTSyncEngine.SyncGroup} for the organizer to track, but the
+ * {@link BLASTSyncEngine.SyncGroup} may not be active until the {@link BLASTSyncEngine} is
+ * free.
+ */
+ private BLASTSyncEngine.SyncGroup prepareSyncWithOrganizer(
+ IWindowContainerTransactionCallback callback) {
+ final BLASTSyncEngine.SyncGroup s = mService.mWindowManager.mSyncEngine
+ .prepareSyncSet(this, "");
+ mTransactionCallbacksByPendingSyncId.put(s.mSyncId, callback);
+ return s;
+ }
+
@VisibleForTesting
int startSyncWithOrganizer(IWindowContainerTransactionCallback callback) {
- int id = mService.mWindowManager.mSyncEngine.startSyncSet(this);
- mTransactionCallbacksByPendingSyncId.put(id, callback);
- return id;
+ final BLASTSyncEngine.SyncGroup s = prepareSyncWithOrganizer(callback);
+ mService.mWindowManager.mSyncEngine.startSyncSet(s);
+ return s.mSyncId;
}
@VisibleForTesting
@@ -1072,6 +1137,19 @@
}
@Override
+ public void onSyncEngineFree() {
+ if (mPendingTransactions.isEmpty()) {
+ return;
+ }
+
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "PendingStartTransaction found");
+ final PendingTransaction pt = mPendingTransactions.remove(0);
+ pt.startSync();
+ // Post this so that the now-playing transition setup isn't interrupted.
+ mService.mH.post(pt::startTransaction);
+ }
+
+ @Override
public void registerTransitionPlayer(ITransitionPlayer player) {
enforceTaskPermission("registerTransitionPlayer()");
final int callerPid = Binder.getCallingPid();
@@ -1329,4 +1407,38 @@
+ result + " when starting " + intent);
}
}
+
+ /**
+ * Represents a sync {@link WindowContainerTransaction} call made while there is other active
+ * {@link BLASTSyncEngine.SyncGroup}.
+ */
+ static class PendingTransaction {
+ private Runnable mStartSync;
+ private Runnable mStartTransaction;
+
+ /**
+ * The callback will be called immediately when the {@link BLASTSyncEngine} is free. One
+ * should call {@link BLASTSyncEngine#startSyncSet(BLASTSyncEngine.SyncGroup)} here to
+ * reserve the {@link BLASTSyncEngine}.
+ */
+ void setStartSync(@NonNull Runnable callback) {
+ mStartSync = callback;
+ }
+
+ /**
+ * The callback will be post to the main handler after the {@link BLASTSyncEngine} is free
+ * to apply the pending {@link WindowContainerTransaction}.
+ */
+ void setStartTransaction(@NonNull Runnable callback) {
+ mStartTransaction = callback;
+ }
+
+ private void startSync() {
+ mStartSync.run();
+ }
+
+ private void startTransaction() {
+ mStartTransaction.run();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 1f83767..a228d6a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -163,7 +163,6 @@
import static com.android.server.wm.WindowStateProto.ATTRIBUTES;
import static com.android.server.wm.WindowStateProto.DESTROYING;
import static com.android.server.wm.WindowStateProto.DISPLAY_ID;
-import static com.android.server.wm.WindowStateProto.FINISHED_SEAMLESS_ROTATION_FRAME;
import static com.android.server.wm.WindowStateProto.FORCE_SEAMLESS_ROTATION;
import static com.android.server.wm.WindowStateProto.GIVEN_CONTENT_INSETS;
import static com.android.server.wm.WindowStateProto.GLOBAL_SCALE;
@@ -380,7 +379,6 @@
*/
final boolean mForceSeamlesslyRotate;
SeamlessRotator mPendingSeamlessRotate;
- long mFinishSeamlessRotateFrameNumber;
private RemoteCallbackList<IWindowFocusObserver> mFocusCallbacks;
@@ -654,6 +652,7 @@
private final Rect mTmpRect = new Rect();
private final Point mTmpPoint = new Point();
+ private final Region mTmpRegion = new Region();
private final Transaction mTmpTransaction;
@@ -706,11 +705,6 @@
*/
private PowerManagerWrapper mPowerManagerWrapper;
- /**
- * A frame number in which changes requested in this layout will be rendered.
- */
- private long mFrameNumber = -1;
-
private static final StringBuilder sTmpSB = new StringBuilder();
/**
@@ -969,7 +963,6 @@
}
mPendingSeamlessRotate.finish(t, this);
- mFinishSeamlessRotateFrameNumber = getFrameNumber();
mPendingSeamlessRotate = null;
getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
@@ -1418,8 +1411,8 @@
return mWindowFrames.mParentFrame;
}
- void getCompatFrameSize(Rect outFrame) {
- outFrame.set(0, 0, mWindowFrames.mCompatFrame.width(), mWindowFrames.mCompatFrame.height());
+ Rect getCompatFrame() {
+ return mWindowFrames.mCompatFrame;
}
WindowManager.LayoutParams getAttrs() {
@@ -1563,7 +1556,7 @@
}
} else {
// The orientation change is completed. If it was hidden by the animation, reshow it.
- mDisplayContent.finishFadeRotationAnimation(mToken);
+ mDisplayContent.finishAsyncRotation(mToken);
}
}
@@ -1608,6 +1601,15 @@
return getDisplayContent().getDisplayInfo();
}
+ @Override
+ public Rect getMaxBounds() {
+ final Rect maxBounds = mToken.getFixedRotationTransformMaxBounds();
+ if (maxBounds != null) {
+ return maxBounds;
+ }
+ return super.getMaxBounds();
+ }
+
/**
* Returns the insets state for the window. Its sources may be the copies with visibility
* modification according to the state of transient bars.
@@ -2773,6 +2775,7 @@
region.set(-dw, -dh, dw + dw, dh + dh);
}
subtractTouchExcludeRegionIfNeeded(region);
+
} else {
// Not modal
getTouchableRegion(region);
@@ -2783,6 +2786,14 @@
if (frame.left != 0 || frame.top != 0) {
region.translate(-frame.left, -frame.top);
}
+ if (modal && mTouchableInsets == TOUCHABLE_INSETS_REGION) {
+ // The client gave us a touchable region and so first
+ // we calculate the untouchable region, then punch that out of our
+ // expanded modal region.
+ mTmpRegion.set(0, 0, frame.right, frame.bottom);
+ mTmpRegion.op(mGivenTouchableRegion, Region.Op.DIFFERENCE);
+ region.op(mTmpRegion, Region.Op.DIFFERENCE);
+ }
// TODO(b/139804591): sizecompat layout needs to be reworked. Currently mFrame is post-
// scaling but the existing logic doesn't expect that. The result is that the already-
@@ -4113,7 +4124,6 @@
proto.write(IS_ON_SCREEN, isOnScreen());
proto.write(IS_VISIBLE, isVisible);
proto.write(PENDING_SEAMLESS_ROTATION, mPendingSeamlessRotate != null);
- proto.write(FINISHED_SEAMLESS_ROTATION_FRAME, mFinishSeamlessRotateFrameNumber);
proto.write(FORCE_SEAMLESS_ROTATION, mForceSeamlesslyRotate);
proto.write(HAS_COMPAT_SCALE, hasCompatScale());
proto.write(GLOBAL_SCALE, mGlobalScale);
@@ -4255,7 +4265,6 @@
} else {
pw.print("null");
}
- pw.println(" finishedFrameNumber=" + mFinishSeamlessRotateFrameNumber);
if (mHScale != 1 || mVScale != 1) {
pw.println(prefix + "mHScale=" + mHScale
@@ -5624,16 +5633,6 @@
return target != null ? target.getWindow() : null;
}
- long getFrameNumber() {
- // Return the frame number in which changes requested in this layout will be rendered or
- // -1 if we do not expect the frame to be rendered.
- return getFrame().isEmpty() ? -1 : mFrameNumber;
- }
-
- void setFrameNumber(long frameNumber) {
- mFrameNumber = frameNumber;
- }
-
void forceReportingResized() {
mWindowFrames.forceReportingResized();
}
@@ -5820,10 +5819,10 @@
boolean skipLayout = false;
// Control the timing to switch the appearance of window with different rotations.
- final FadeRotationAnimationController fadeRotationController =
- mDisplayContent.getFadeRotationAnimationController();
- if (fadeRotationController != null
- && fadeRotationController.handleFinishDrawing(this, postDrawTransaction)) {
+ final AsyncRotationController asyncRotationController =
+ mDisplayContent.getAsyncRotationController();
+ if (asyncRotationController != null
+ && asyncRotationController.handleFinishDrawing(this, postDrawTransaction)) {
// Consume the transaction because the controller will apply it with fade animation.
// Layout is not needed because the window will be hidden by the fade leash. Clear
// sync state because its sync transaction doesn't need to be merged to sync group.
@@ -5904,39 +5903,6 @@
mRedrawForSyncReported = false;
}
- void calculateSurfaceBounds(WindowManager.LayoutParams attrs, Rect outSize) {
- outSize.setEmpty();
- if ((attrs.flags & FLAG_SCALED) != 0) {
- // For a scaled surface, we always want the requested size.
- outSize.right = mRequestedWidth;
- outSize.bottom = mRequestedHeight;
- } else {
- // When we're doing a drag-resizing, request a surface that's fullscreen size,
- // so that we don't need to reallocate during the process. This also prevents
- // buffer drops due to size mismatch.
- if (isDragResizing()) {
- final DisplayInfo displayInfo = getDisplayInfo();
- outSize.right = displayInfo.logicalWidth;
- outSize.bottom = displayInfo.logicalHeight;
- } else {
- getCompatFrameSize(outSize);
- }
- }
-
- // This doesn't necessarily mean that there is an error in the system. The sizes might be
- // incorrect, because it is before the first layout or draw.
- if (outSize.width() < 1) {
- outSize.right = 1;
- }
- if (outSize.height() < 1) {
- outSize.bottom = 1;
- }
-
- // Adjust for surface insets.
- outSize.inset(-attrs.surfaceInsets.left, -attrs.surfaceInsets.top,
- -attrs.surfaceInsets.right, -attrs.surfaceInsets.bottom);
- }
-
/**
* This method is used to control whether we return the BLAST_SYNC flag
* from relayoutWindow calls on this window (triggering the client to redirect
@@ -6062,4 +6028,9 @@
}
mWmService.handleTaskFocusChange(getTask(), mActivityRecord);
}
+
+ void clearClientTouchableRegion() {
+ mTouchableInsets = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
+ mGivenTouchableRegion.setEmpty();
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 316051e..c17961e 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -150,8 +150,6 @@
int mAttrType;
- private final Rect mTmpSize = new Rect();
-
/**
* Handles surface changes synchronized to after the client has drawn the surface. This
* transaction is currently used to reparent the old surface children to the new surface once
@@ -291,7 +289,7 @@
}
}
- WindowSurfaceController createSurfaceLocked(int windowType) {
+ WindowSurfaceController createSurfaceLocked() {
final WindowState w = mWin;
if (mSurfaceController != null) {
@@ -319,16 +317,9 @@
flags |= SurfaceControl.SKIP_SCREENSHOT;
}
- w.calculateSurfaceBounds(attrs, mTmpSize);
-
- final int width = mTmpSize.width();
- final int height = mTmpSize.height();
-
if (DEBUG_VISIBILITY) {
Slog.v(TAG, "Creating surface in session "
+ mSession.mSurfaceSession + " window " + this
- + " w=" + width + " h=" + height
- + " x=" + mTmpSize.left + " y=" + mTmpSize.top
+ " format=" + attrs.format + " flags=" + flags);
}
@@ -339,8 +330,8 @@
final boolean isHwAccelerated = (attrs.flags & FLAG_HARDWARE_ACCELERATED) != 0;
final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format;
- mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), width,
- height, format, flags, this, windowType);
+ mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), format,
+ flags, this, attrs.type);
mSurfaceController.setColorSpaceAgnostic((attrs.privateFlags
& WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0);
@@ -372,8 +363,7 @@
if (SHOW_LIGHT_TRANSACTIONS) {
Slog.i(TAG, ">>> OPEN TRANSACTION createSurfaceLocked");
WindowManagerService.logSurface(w, "CREATE pos=("
- + w.getFrame().left + "," + w.getFrame().top + ") ("
- + width + "x" + height + ")" + " HIDE", false);
+ + w.getFrame().left + "," + w.getFrame().top + ") HIDE", false);
}
mLastHidden = true;
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index fa0d708..665857c 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -31,7 +31,6 @@
import static com.android.server.wm.WindowSurfaceControllerProto.LAYER;
import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN;
-import android.graphics.Region;
import android.os.Debug;
import android.os.Trace;
import android.util.Slog;
@@ -76,8 +75,8 @@
// Used to track whether we have called detach children on the way to invisibility.
boolean mChildrenDetached;
- WindowSurfaceController(String name, int w, int h, int format,
- int flags, WindowStateAnimator animator, int windowType) {
+ WindowSurfaceController(String name, int format, int flags, WindowStateAnimator animator,
+ int windowType) {
mAnimator = animator;
title = name;
@@ -91,7 +90,6 @@
final SurfaceControl.Builder b = win.makeSurface()
.setParent(win.getSurfaceControl())
.setName(name)
- .setBufferSize(w, h)
.setFormat(format)
.setFlags(flags)
.setMetadata(METADATA_WINDOW_TYPE, windowType)
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index e5a3b7a..6d8203c 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -430,6 +430,13 @@
return isFixedRotationTransforming() ? mFixedRotationTransformState.mDisplayFrames : null;
}
+ Rect getFixedRotationTransformMaxBounds() {
+ return isFixedRotationTransforming()
+ ? mFixedRotationTransformState.mRotatedOverrideConfiguration.windowConfiguration
+ .getMaxBounds()
+ : null;
+ }
+
Rect getFixedRotationTransformDisplayBounds() {
return isFixedRotationTransforming()
? mFixedRotationTransformState.mRotatedOverrideConfiguration.windowConfiguration
@@ -646,14 +653,6 @@
}
}
- /**
- * Gives a chance to this {@link WindowToken} to adjust the {@link
- * android.view.WindowManager.LayoutParams} of its windows.
- */
- void adjustWindowParams(WindowState win, WindowManager.LayoutParams attrs) {
- }
-
-
@CallSuper
@Override
public void dumpDebug(ProtoOutputStream proto, long fieldId,
diff --git a/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java b/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java
deleted file mode 100644
index 59abaab..0000000
--- a/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java
+++ /dev/null
@@ -1,96 +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.wm.utils;
-
-import static android.view.DisplayCutout.BOUNDS_POSITION_LENGTH;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
-
-import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates;
-
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-/**
- * Utility to compute bounds after rotating the screen.
- */
-public class DisplayRotationUtil {
- private final Matrix mTmpMatrix = new Matrix();
-
- private static int getRotationToBoundsOffset(int rotation) {
- switch (rotation) {
- case ROTATION_0:
- return 0;
- case ROTATION_90:
- return -1;
- case ROTATION_180:
- return 2;
- case ROTATION_270:
- return 1;
- default:
- // should not happen
- return 0;
- }
- }
-
- @VisibleForTesting
- static int getBoundIndexFromRotation(int i, int rotation) {
- return Math.floorMod(i + getRotationToBoundsOffset(rotation),
- BOUNDS_POSITION_LENGTH);
- }
-
- /**
- * Compute bounds after rotating the screen.
- *
- * @param bounds Bounds before the rotation. The array must contain exactly 4 non-null elements.
- * @param rotation rotation constant defined in android.view.Surface.
- * @param initialDisplayWidth width of the display before the rotation.
- * @param initialDisplayHeight height of the display before the rotation.
- * @return Bounds after the rotation.
- *
- * @hide
- */
- public Rect[] getRotatedBounds(
- Rect[] bounds, int rotation, int initialDisplayWidth, int initialDisplayHeight) {
- if (bounds.length != BOUNDS_POSITION_LENGTH) {
- throw new IllegalArgumentException(
- "bounds must have exactly 4 elements: bounds=" + bounds);
- }
- if (rotation == ROTATION_0) {
- return bounds;
- }
- transformPhysicalToLogicalCoordinates(rotation, initialDisplayWidth, initialDisplayHeight,
- mTmpMatrix);
- Rect[] newBounds = new Rect[BOUNDS_POSITION_LENGTH];
- for (int i = 0; i < bounds.length; i++) {
-
- final Rect rect = bounds[i];
- if (!rect.isEmpty()) {
- final RectF rectF = new RectF(rect);
- mTmpMatrix.mapRect(rectF);
- rectF.round(rect);
- }
- newBounds[getBoundIndexFromRotation(i, rotation)] = rect;
- }
- return newBounds;
- }
-}
diff --git a/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java b/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
index ee3f4f4d..a45771e 100644
--- a/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
+++ b/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
@@ -19,7 +19,6 @@
import android.graphics.Rect;
import android.util.Size;
import android.view.DisplayCutout;
-import android.view.Gravity;
import java.util.Objects;
@@ -52,7 +51,7 @@
return NO_CUTOUT;
}
final Size displaySize = new Size(displayWidth, displayHeight);
- final Rect safeInsets = computeSafeInsets(displaySize, inner);
+ final Rect safeInsets = DisplayCutout.computeSafeInsets(displayWidth, displayHeight, inner);
return new WmDisplayCutout(inner.replaceSafeInsets(safeInsets), displaySize);
}
@@ -66,45 +65,6 @@
return computeSafeInsets(mInner, width, height);
}
- private static Rect computeSafeInsets(Size displaySize, DisplayCutout cutout) {
- if (displaySize.getWidth() == displaySize.getHeight()) {
- throw new UnsupportedOperationException("not implemented: display=" + displaySize +
- " cutout=" + cutout);
- }
-
- int leftInset = Math.max(cutout.getWaterfallInsets().left,
- findCutoutInsetForSide(displaySize, cutout.getBoundingRectLeft(), Gravity.LEFT));
- int topInset = Math.max(cutout.getWaterfallInsets().top,
- findCutoutInsetForSide(displaySize, cutout.getBoundingRectTop(), Gravity.TOP));
- int rightInset = Math.max(cutout.getWaterfallInsets().right,
- findCutoutInsetForSide(displaySize, cutout.getBoundingRectRight(), Gravity.RIGHT));
- int bottomInset = Math.max(cutout.getWaterfallInsets().bottom,
- findCutoutInsetForSide(displaySize, cutout.getBoundingRectBottom(),
- Gravity.BOTTOM));
-
- return new Rect(leftInset, topInset, rightInset, bottomInset);
- }
-
- private static int findCutoutInsetForSide(Size display, Rect boundingRect, int gravity) {
- if (boundingRect.isEmpty()) {
- return 0;
- }
-
- int inset = 0;
- switch (gravity) {
- case Gravity.TOP:
- return Math.max(inset, boundingRect.bottom);
- case Gravity.BOTTOM:
- return Math.max(inset, display.getHeight() - boundingRect.top);
- case Gravity.LEFT:
- return Math.max(inset, boundingRect.right);
- case Gravity.RIGHT:
- return Math.max(inset, display.getWidth() - boundingRect.left);
- default:
- throw new IllegalArgumentException("unknown gravity: " + gravity);
- }
- }
-
public DisplayCutout getDisplayCutout() {
return mInner;
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 4524bb7..df5fb28 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -277,6 +277,7 @@
void setInputDispatchMode(bool enabled, bool frozen);
void setSystemUiLightsOut(bool lightsOut);
void setPointerSpeed(int32_t speed);
+ void setPointerAcceleration(float acceleration);
void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
void setShowTouches(bool enabled);
void setInteractive(bool interactive);
@@ -363,6 +364,9 @@
// Pointer speed.
int32_t pointerSpeed;
+ // Pointer acceleration.
+ float pointerAcceleration;
+
// True if pointer gestures are enabled.
bool pointerGesturesEnabled;
@@ -412,6 +416,7 @@
AutoMutex _l(mLock);
mLocked.systemUiLightsOut = false;
mLocked.pointerSpeed = 0;
+ mLocked.pointerAcceleration = android::os::IInputConstants::DEFAULT_POINTER_ACCELERATION;
mLocked.pointerGesturesEnabled = true;
mLocked.showTouches = false;
mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;
@@ -439,6 +444,7 @@
dump += StringPrintf(INDENT "System UI Lights Out: %s\n",
toString(mLocked.systemUiLightsOut));
dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed);
+ dump += StringPrintf(INDENT "Pointer Acceleration: %0.3f\n", mLocked.pointerAcceleration);
dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
toString(mLocked.pointerGesturesEnabled));
dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches));
@@ -628,6 +634,7 @@
outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed
* POINTER_SPEED_EXPONENT);
+ outConfig->pointerVelocityControlParameters.acceleration = mLocked.pointerAcceleration;
outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled;
outConfig->showTouches = mLocked.showTouches;
@@ -1066,6 +1073,22 @@
InputReaderConfiguration::CHANGE_POINTER_SPEED);
}
+void NativeInputManager::setPointerAcceleration(float acceleration) {
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (mLocked.pointerAcceleration == acceleration) {
+ return;
+ }
+
+ ALOGI("Setting pointer acceleration to %0.3f", acceleration);
+ mLocked.pointerAcceleration = acceleration;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::CHANGE_POINTER_SPEED);
+}
+
void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
{ // acquire lock
AutoMutex _l(mLock);
@@ -1586,6 +1609,13 @@
return result;
}
+static jint nativeGetKeyCodeForKeyLocation(JNIEnv* env, jclass /* clazz */, jlong ptr,
+ jint deviceId, jint locationKeyCode) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ return (jint)im->getInputManager()->getReader().getKeyCodeForKeyLocation(deviceId,
+ locationKeyCode);
+}
+
static void handleInputChannelDisposed(JNIEnv* env, jobject /* inputChannelObj */,
const std::shared_ptr<InputChannel>& inputChannel,
void* data) {
@@ -1875,6 +1905,13 @@
im->setPointerSpeed(speed);
}
+static void nativeSetPointerAcceleration(JNIEnv* /* env */, jclass /* clazz */, jlong ptr,
+ jfloat acceleration) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ im->setPointerAcceleration(acceleration);
+}
+
static void nativeSetShowTouches(JNIEnv* /* env */,
jclass /* clazz */, jlong ptr, jboolean enabled) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -2337,6 +2374,7 @@
{"nativeGetKeyCodeState", "(JIII)I", (void*)nativeGetKeyCodeState},
{"nativeGetSwitchState", "(JIII)I", (void*)nativeGetSwitchState},
{"nativeHasKeys", "(JII[I[Z)Z", (void*)nativeHasKeys},
+ {"nativeGetKeyCodeForKeyLocation", "(JII)I", (void*)nativeGetKeyCodeForKeyLocation},
{"nativeCreateInputChannel", "(JLjava/lang/String;)Landroid/view/InputChannel;",
(void*)nativeCreateInputChannel},
{"nativeCreateInputMonitor", "(JIZLjava/lang/String;I)Landroid/view/InputChannel;",
@@ -2365,6 +2403,7 @@
(void*)nativeTransferTouchFocus},
{"nativeTransferTouch", "(JLandroid/os/IBinder;)Z", (void*)nativeTransferTouch},
{"nativeSetPointerSpeed", "(JI)V", (void*)nativeSetPointerSpeed},
+ {"nativeSetPointerAcceleration", "(JF)V", (void*)nativeSetPointerAcceleration},
{"nativeSetShowTouches", "(JZ)V", (void*)nativeSetShowTouches},
{"nativeSetInteractive", "(JZ)V", (void*)nativeSetInteractive},
{"nativeReloadCalibration", "(J)V", (void*)nativeReloadCalibration},
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index b484796..f5e6c45 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -39,8 +39,8 @@
static JavaVM* sJvm = nullptr;
static jmethodID sMethodIdOnComplete;
-static jclass sFrequencyMappingClass;
-static jmethodID sFrequencyMappingCtor;
+static jclass sFrequencyProfileClass;
+static jmethodID sFrequencyProfileCtor;
static struct {
jmethodID setCapabilities;
jmethodID setSupportedEffects;
@@ -51,7 +51,7 @@
jmethodID setPrimitiveDelayMax;
jmethodID setCompositionSizeMax;
jmethodID setQFactor;
- jmethodID setFrequencyMapping;
+ jmethodID setFrequencyProfile;
} sVibratorInfoBuilderClassInfo;
static struct {
jfieldID id;
@@ -437,11 +437,11 @@
env->SetFloatArrayRegion(maxAmplitudes, 0, amplitudes.size(),
reinterpret_cast<jfloat*>(amplitudes.data()));
}
- jobject frequencyMapping =
- env->NewObject(sFrequencyMappingClass, sFrequencyMappingCtor, resonantFrequency,
+ jobject frequencyProfile =
+ env->NewObject(sFrequencyProfileClass, sFrequencyProfileCtor, resonantFrequency,
minFrequency, frequencyResolution, maxAmplitudes);
- env->CallObjectMethod(vibratorInfoBuilder, sVibratorInfoBuilderClassInfo.setFrequencyMapping,
- frequencyMapping);
+ env->CallObjectMethod(vibratorInfoBuilder, sVibratorInfoBuilderClassInfo.setFrequencyProfile,
+ frequencyProfile);
return info.isFailedLogged("vibratorGetInfo") ? JNI_FALSE : JNI_TRUE;
}
@@ -485,9 +485,9 @@
sRampClassInfo.endFrequencyHz = GetFieldIDOrDie(env, rampClass, "mEndFrequencyHz", "F");
sRampClassInfo.duration = GetFieldIDOrDie(env, rampClass, "mDuration", "I");
- jclass frequencyMappingClass = FindClassOrDie(env, "android/os/VibratorInfo$FrequencyMapping");
- sFrequencyMappingClass = static_cast<jclass>(env->NewGlobalRef(frequencyMappingClass));
- sFrequencyMappingCtor = GetMethodIDOrDie(env, sFrequencyMappingClass, "<init>", "(FFF[F)V");
+ jclass frequencyProfileClass = FindClassOrDie(env, "android/os/VibratorInfo$FrequencyProfile");
+ sFrequencyProfileClass = static_cast<jclass>(env->NewGlobalRef(frequencyProfileClass));
+ sFrequencyProfileCtor = GetMethodIDOrDie(env, sFrequencyProfileClass, "<init>", "(FFF[F)V");
jclass vibratorInfoBuilderClass = FindClassOrDie(env, "android/os/VibratorInfo$Builder");
sVibratorInfoBuilderClassInfo.setCapabilities =
@@ -517,9 +517,9 @@
sVibratorInfoBuilderClassInfo.setQFactor =
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setQFactor",
"(F)Landroid/os/VibratorInfo$Builder;");
- sVibratorInfoBuilderClassInfo.setFrequencyMapping =
- GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setFrequencyMapping",
- "(Landroid/os/VibratorInfo$FrequencyMapping;)"
+ sVibratorInfoBuilderClassInfo.setFrequencyProfile =
+ GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setFrequencyProfile",
+ "(Landroid/os/VibratorInfo$FrequencyProfile;)"
"Landroid/os/VibratorInfo$Builder;");
return jniRegisterNativeMethods(env,
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index baf2ede..574dbfd 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -38,7 +38,8 @@
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
- <xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0" maxOccurs="1"/>
+ <xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0"
+ maxOccurs="1"/>
<xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1" />
<xs:element type="nonNegativeDecimal" name="screenBrightnessRampFastDecrease">
<xs:annotation name="final"/>
@@ -67,6 +68,19 @@
<xs:element type="xs:nonNegativeInteger" name="ambientLightHorizonShort">
<xs:annotation name="final"/>
</xs:element>
+
+ <!-- Set of thresholds that dictate the change needed for screen brightness
+ adaptations -->
+ <xs:element type="thresholds" name="displayBrightnessChangeThresholds">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- Set of thresholds that dictate the change needed for ambient brightness
+ adaptations -->
+ <xs:element type="thresholds" name="ambientBrightnessChangeThresholds">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
@@ -81,7 +95,8 @@
<xs:complexType name="highBrightnessMode">
<xs:all>
- <xs:element name="transitionPoint" type="nonNegativeDecimal" minOccurs="1" maxOccurs="1">
+ <xs:element name="transitionPoint" type="nonNegativeDecimal" minOccurs="1"
+ maxOccurs="1">
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
@@ -110,7 +125,8 @@
<xs:complexType name="hbmTiming">
<xs:all>
- <xs:element name="timeWindowSecs" type="xs:nonNegativeInteger" minOccurs="1" maxOccurs="1">
+ <xs:element name="timeWindowSecs" type="xs:nonNegativeInteger" minOccurs="1"
+ maxOccurs="1">
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
@@ -216,5 +232,33 @@
<xs:annotation name="final"/>
</xs:element>
</xs:sequence>
+ </xs:complexType>
+
+ <!-- Thresholds for brightness changes. -->
+ <xs:complexType name="thresholds">
+ <xs:sequence>
+ <!-- Brightening thresholds. -->
+ <xs:element name="brighteningThresholds" type="brightnessThresholds" minOccurs="0"
+ maxOccurs="1" >
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- Darkening thresholds. -->
+ <xs:element name="darkeningThresholds" type="brightnessThresholds" minOccurs="0"
+ maxOccurs="1" >
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
</xs:complexType>
+
+ <!-- Brightening and darkening minimum change thresholds. -->
+ <xs:complexType name="brightnessThresholds">
+ <!-- Minimum brightness change needed. -->
+ <xs:element name="minimum" type="nonNegativeDecimal" minOccurs="0" maxOccurs="1" >
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:complexType>
+
</xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 6f97431..04f0916 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -1,6 +1,12 @@
// Signature format: 2.0
package com.android.server.display.config {
+ public class BrightnessThresholds {
+ ctor public BrightnessThresholds();
+ method @NonNull public final java.math.BigDecimal getMinimum();
+ method public final void setMinimum(@NonNull java.math.BigDecimal);
+ }
+
public class Density {
ctor public Density();
method @NonNull public final java.math.BigInteger getDensity();
@@ -18,9 +24,11 @@
public class DisplayConfiguration {
ctor public DisplayConfiguration();
+ method @NonNull public final com.android.server.display.config.Thresholds getAmbientBrightnessChangeThresholds();
method public final java.math.BigInteger getAmbientLightHorizonLong();
method public final java.math.BigInteger getAmbientLightHorizonShort();
method @Nullable public final com.android.server.display.config.DensityMap getDensityMap();
+ method @NonNull public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholds();
method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
method public final com.android.server.display.config.SensorDetails getLightSensor();
method public final com.android.server.display.config.SensorDetails getProxSensor();
@@ -31,9 +39,11 @@
method public final java.math.BigDecimal getScreenBrightnessRampFastIncrease();
method public final java.math.BigDecimal getScreenBrightnessRampSlowDecrease();
method public final java.math.BigDecimal getScreenBrightnessRampSlowIncrease();
+ method public final void setAmbientBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
method public final void setAmbientLightHorizonLong(java.math.BigInteger);
method public final void setAmbientLightHorizonShort(java.math.BigInteger);
method public final void setDensityMap(@Nullable com.android.server.display.config.DensityMap);
+ method public final void setDisplayBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
method public final void setLightSensor(com.android.server.display.config.SensorDetails);
method public final void setProxSensor(com.android.server.display.config.SensorDetails);
@@ -121,6 +131,14 @@
enum_constant public static final com.android.server.display.config.ThermalStatus shutdown;
}
+ public class Thresholds {
+ ctor public Thresholds();
+ method @NonNull public final com.android.server.display.config.BrightnessThresholds getBrighteningThresholds();
+ method @NonNull public final com.android.server.display.config.BrightnessThresholds getDarkeningThresholds();
+ method public final void setBrighteningThresholds(@NonNull com.android.server.display.config.BrightnessThresholds);
+ method public final void setDarkeningThresholds(@NonNull com.android.server.display.config.BrightnessThresholds);
+ }
+
public class XmlParser {
ctor public XmlParser();
method public static com.android.server.display.config.DisplayConfiguration read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index f19202a..2090ab3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -32,6 +32,7 @@
import android.app.admin.DevicePolicyManager;
import android.app.admin.FactoryResetProtectionPolicy;
import android.app.admin.PasswordPolicy;
+import android.app.admin.PreferentialNetworkServiceConfig;
import android.graphics.Color;
import android.os.Bundle;
import android.os.PersistableBundle;
@@ -306,6 +307,8 @@
public boolean mAdminCanGrantSensorsPermissions;
public boolean mPreferentialNetworkServiceEnabled =
DevicePolicyManager.PREFERENTIAL_NETWORK_SERVICE_ENABLED_DEFAULT;
+ public PreferentialNetworkServiceConfig mPreferentialNetworkServiceConfig =
+ PreferentialNetworkServiceConfig.DEFAULT;
private static final boolean USB_DATA_SIGNALING_ENABLED_DEFAULT = true;
boolean mUsbDataSignalingEnabled = USB_DATA_SIGNALING_ENABLED_DEFAULT;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 9b87b9d..200b120 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -171,11 +171,11 @@
public void setDrawables(@NonNull List<DevicePolicyDrawableResource> drawables){}
@Override
- public void resetDrawables(@NonNull int[] drawableIds){}
+ public void resetDrawables(@NonNull String[] drawableIds){}
@Override
public ParcelableResource getDrawable(
- int drawableId, int drawableStyle, int drawableSource) {
+ String drawableId, String drawableStyle, String drawableSource) {
return null;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java
index 9a98235..e70c071 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java
@@ -16,10 +16,10 @@
package com.android.server.devicepolicy;
-import static android.app.admin.DevicePolicyResources.Drawable.Source.UPDATABLE_DRAWABLE_SOURCES;
-import static android.app.admin.DevicePolicyResources.Drawable.Style;
-import static android.app.admin.DevicePolicyResources.Drawable.Style.UPDATABLE_DRAWABLE_STYLES;
-import static android.app.admin.DevicePolicyResources.Drawable.UPDATABLE_DRAWABLE_IDS;
+import static android.app.admin.DevicePolicyResources.Drawables.Source.UPDATABLE_DRAWABLE_SOURCES;
+import static android.app.admin.DevicePolicyResources.Drawables.Style;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.UPDATABLE_DRAWABLE_STYLES;
+import static android.app.admin.DevicePolicyResources.Drawables.UPDATABLE_DRAWABLE_IDS;
import static android.app.admin.DevicePolicyResources.Strings.UPDATABLE_STRING_IDS;
import static java.util.Objects.requireNonNull;
@@ -27,7 +27,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.DevicePolicyDrawableResource;
-import android.app.admin.DevicePolicyResources;
+import android.app.admin.DevicePolicyResources.Drawables;
import android.app.admin.DevicePolicyStringResource;
import android.app.admin.ParcelableResource;
import android.os.Environment;
@@ -72,13 +72,13 @@
/**
* Map of <drawable_id, <style_id, resource_value>>
*/
- private final Map<Integer, Map<Integer, ParcelableResource>>
+ private final Map<String, Map<String, ParcelableResource>>
mUpdatedDrawablesForStyle = new HashMap<>();
/**
* Map of <drawable_id, <source_id, resource_value>>
*/
- private final Map<Integer, Map<Integer, ParcelableResource>>
+ private final Map<String, Map<String, ParcelableResource>>
mUpdatedDrawablesForSource = new HashMap<>();
/**
@@ -103,14 +103,17 @@
boolean updateDrawables(@NonNull List<DevicePolicyDrawableResource> drawables) {
boolean updated = false;
for (int i = 0; i < drawables.size(); i++) {
- int drawableId = drawables.get(i).getDrawableId();
- int drawableStyle = drawables.get(i).getDrawableStyle();
- int drawableSource = drawables.get(i).getDrawableSource();
+ String drawableId = drawables.get(i).getDrawableId();
+ String drawableStyle = drawables.get(i).getDrawableStyle();
+ String drawableSource = drawables.get(i).getDrawableSource();
ParcelableResource resource = drawables.get(i).getResource();
+ Objects.requireNonNull(drawableId, "drawableId must be provided.");
+ Objects.requireNonNull(drawableStyle, "drawableStyle must be provided.");
+ Objects.requireNonNull(drawableSource, "drawableSource must be provided.");
Objects.requireNonNull(resource, "ParcelableResource must be provided.");
- if (drawableSource == DevicePolicyResources.Drawable.Source.UNDEFINED) {
+ if (Drawables.Source.UNDEFINED.equals(drawableSource)) {
updated |= updateDrawable(drawableId, drawableStyle, resource);
} else {
updated |= updateDrawableForSource(drawableId, drawableSource, resource);
@@ -126,7 +129,7 @@
}
private boolean updateDrawable(
- int drawableId, int drawableStyle, ParcelableResource updatableResource) {
+ String drawableId, String drawableStyle, ParcelableResource updatableResource) {
if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) {
Log.w(TAG, "Updating a resource for an unknown drawable id " + drawableId);
}
@@ -149,7 +152,7 @@
// TODO(b/214576716): change this to respect style
private boolean updateDrawableForSource(
- int drawableId, int drawableSource, ParcelableResource updatableResource) {
+ String drawableId, String drawableSource, ParcelableResource updatableResource) {
if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) {
Log.w(TAG, "Updating a resource for an unknown drawable id " + drawableId);
}
@@ -173,11 +176,11 @@
/**
* Returns {@code false} if no resources were removed.
*/
- boolean removeDrawables(@NonNull int[] drawableIds) {
+ boolean removeDrawables(@NonNull String[] drawableIds) {
synchronized (mLock) {
boolean removed = false;
for (int i = 0; i < drawableIds.length; i++) {
- int drawableId = drawableIds[i];
+ String drawableId = drawableIds[i];
removed |= mUpdatedDrawablesForStyle.remove(drawableId) != null
|| mUpdatedDrawablesForSource.remove(drawableId) != null;
}
@@ -191,7 +194,7 @@
@Nullable
ParcelableResource getDrawable(
- int drawableId, int drawableStyle, int drawableSource) {
+ String drawableId, String drawableStyle, String drawableSource) {
if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) {
Log.w(TAG, "Getting an updated resource for an unknown drawable id " + drawableId);
}
@@ -231,7 +234,9 @@
String stringId = strings.get(i).getStringId();
ParcelableResource resource = strings.get(i).getResource();
+ Objects.requireNonNull(stringId, "stringId must be provided.");
Objects.requireNonNull(resource, "ParcelableResource must be provided.");
+
updated |= updateString(stringId, resource);
}
if (!updated) {
@@ -392,19 +397,19 @@
void writeInner(TypedXmlSerializer out) throws IOException {
if (mUpdatedDrawablesForStyle != null && !mUpdatedDrawablesForStyle.isEmpty()) {
- for (Map.Entry<Integer, Map<Integer, ParcelableResource>> drawableEntry
+ for (Map.Entry<String, Map<String, ParcelableResource>> drawableEntry
: mUpdatedDrawablesForStyle.entrySet()) {
out.startTag(/* namespace= */ null, TAG_DRAWABLE_STYLE_ENTRY);
- out.attributeInt(
+ out.attribute(
/* namespace= */ null, ATTR_DRAWABLE_ID, drawableEntry.getKey());
out.attributeInt(
/* namespace= */ null,
ATTR_DRAWABLE_STYLE_SIZE,
drawableEntry.getValue().size());
int counter = 0;
- for (Map.Entry<Integer, ParcelableResource> styleEntry
+ for (Map.Entry<String, ParcelableResource> styleEntry
: drawableEntry.getValue().entrySet()) {
- out.attributeInt(
+ out.attribute(
/* namespace= */ null,
ATTR_DRAWABLE_STYLE + (counter++),
styleEntry.getKey());
@@ -414,19 +419,19 @@
}
}
if (mUpdatedDrawablesForSource != null && !mUpdatedDrawablesForSource.isEmpty()) {
- for (Map.Entry<Integer, Map<Integer, ParcelableResource>> drawableEntry
+ for (Map.Entry<String, Map<String, ParcelableResource>> drawableEntry
: mUpdatedDrawablesForSource.entrySet()) {
out.startTag(/* namespace= */ null, TAG_DRAWABLE_SOURCE_ENTRY);
- out.attributeInt(
+ out.attribute(
/* namespace= */ null, ATTR_DRAWABLE_ID, drawableEntry.getKey());
out.attributeInt(
/* namespace= */ null,
ATTR_DRAWABLE_SOURCE_SIZE,
drawableEntry.getValue().size());
int counter = 0;
- for (Map.Entry<Integer, ParcelableResource> sourceEntry
+ for (Map.Entry<String, ParcelableResource> sourceEntry
: drawableEntry.getValue().entrySet()) {
- out.attributeInt(
+ out.attribute(
/* namespace= */ null,
ATTR_DRAWABLE_SOURCE + (counter++),
sourceEntry.getKey());
@@ -457,7 +462,7 @@
}
switch (tag) {
case TAG_DRAWABLE_STYLE_ENTRY:
- int drawableId = parser.getAttributeInt(
+ String drawableId = parser.getAttributeValue(
/* namespace= */ null, ATTR_DRAWABLE_ID);
mUpdatedDrawablesForStyle.put(
drawableId,
@@ -465,7 +470,7 @@
int size = parser.getAttributeInt(
/* namespace= */ null, ATTR_DRAWABLE_STYLE_SIZE);
for (int i = 0; i < size; i++) {
- int style = parser.getAttributeInt(
+ String style = parser.getAttributeValue(
/* namespace= */ null, ATTR_DRAWABLE_STYLE + i);
mUpdatedDrawablesForStyle.get(drawableId).put(
style,
@@ -473,13 +478,13 @@
}
break;
case TAG_DRAWABLE_SOURCE_ENTRY:
- drawableId = parser.getAttributeInt(
+ drawableId = parser.getAttributeValue(
/* namespace= */ null, ATTR_DRAWABLE_ID);
mUpdatedDrawablesForSource.put(drawableId, new HashMap<>());
size = parser.getAttributeInt(
/* namespace= */ null, ATTR_DRAWABLE_SOURCE_SIZE);
for (int i = 0; i < size; i++) {
- int source = parser.getAttributeInt(
+ String source = parser.getAttributeValue(
/* namespace= */ null, ATTR_DRAWABLE_SOURCE + i);
mUpdatedDrawablesForSource.get(drawableId).put(
source,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 92952cd..e0b6273 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -125,6 +125,8 @@
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
@@ -198,6 +200,7 @@
import android.app.admin.ParcelableResource;
import android.app.admin.PasswordMetrics;
import android.app.admin.PasswordPolicy;
+import android.app.admin.PreferentialNetworkServiceConfig;
import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
import android.app.admin.StartInstallingUpdateCallback;
@@ -244,6 +247,7 @@
import android.database.Cursor;
import android.graphics.Bitmap;
import android.hardware.usb.UsbManager;
+import android.location.Location;
import android.location.LocationManager;
import android.media.AudioManager;
import android.media.IAudioService;
@@ -259,6 +263,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.CancellationSignal;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
@@ -324,6 +329,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.LocalePicker;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.net.NetworkUtilsInternal;
@@ -403,6 +409,8 @@
protected static final String LOG_TAG = "DevicePolicyManager";
+ private static final String ATTRIBUTION_TAG = "DevicePolicyManagerService";
+
static final boolean VERBOSE_LOG = false; // DO NOT SUBMIT WITH TRUE
static final String DEVICE_POLICIES_XML = "device_policies.xml";
@@ -1771,7 +1779,7 @@
* Instantiates the service.
*/
public DevicePolicyManagerService(Context context) {
- this(new Injector(context));
+ this(new Injector(context.createAttributionContext(ATTRIBUTION_TAG)));
}
@VisibleForTesting
@@ -3356,14 +3364,14 @@
updatePermissionPolicyCache(userId);
updateAdminCanGrantSensorsPermissionCache(userId);
- final boolean preferentialNetworkServiceEnabled;
+ final PreferentialNetworkServiceConfig preferentialNetworkServiceConfig;
synchronized (getLockObject()) {
ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
- preferentialNetworkServiceEnabled = owner != null
- ? owner.mPreferentialNetworkServiceEnabled
- : DevicePolicyManager.PREFERENTIAL_NETWORK_SERVICE_ENABLED_DEFAULT;
+ preferentialNetworkServiceConfig = owner != null
+ ? owner.mPreferentialNetworkServiceConfig
+ : PreferentialNetworkServiceConfig.DEFAULT;
}
- updateNetworkPreferenceForUser(userId, preferentialNetworkServiceEnabled);
+ updateNetworkPreferenceForUser(userId, preferentialNetworkServiceConfig);
startOwnerService(userId, "start-user");
}
@@ -3380,7 +3388,7 @@
@Override
void handleStopUser(int userId) {
- updateNetworkPreferenceForUser(userId, false);
+ updateNetworkPreferenceForUser(userId, PreferentialNetworkServiceConfig.DEFAULT);
stopOwnerService(userId, "stop-user");
}
@@ -7208,6 +7216,64 @@
return getFrpManagementAgentUid() != -1;
}
+ @Override
+ public void sendLostModeLocationUpdate(AndroidFuture<Boolean> future) {
+ if (!mHasFeature) {
+ future.complete(false);
+ return;
+ }
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.SEND_LOST_MODE_LOCATION_UPDATES));
+
+ synchronized (getLockObject()) {
+ final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+ UserHandle.USER_SYSTEM);
+ Preconditions.checkState(admin != null,
+ "Lost mode location updates can only be sent on an organization-owned device.");
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ final List<String> providers =
+ mInjector.getLocationManager().getAllProviders().stream()
+ .filter(mInjector.getLocationManager()::isProviderEnabled)
+ .collect(Collectors.toList());
+ if (providers.isEmpty()) {
+ future.complete(false);
+ return;
+ }
+
+ final CancellationSignal cancellationSignal = new CancellationSignal();
+ List<String> providersWithNullLocation = new ArrayList<String>();
+ for (String provider : providers) {
+ mInjector.getLocationManager().getCurrentLocation(provider, cancellationSignal,
+ mContext.getMainExecutor(), location -> {
+ if (cancellationSignal.isCanceled()) {
+ return;
+ } else if (location != null) {
+ sendLostModeLocationUpdate(admin, location);
+ cancellationSignal.cancel();
+ future.complete(true);
+ } else {
+ // location == null, provider wasn't able to get location, see
+ // if there are more providers
+ providersWithNullLocation.add(provider);
+ if (providers.size() == providersWithNullLocation.size()) {
+ future.complete(false);
+ }
+ }
+ }
+ );
+ }
+ });
+ }
+ }
+
+ private void sendLostModeLocationUpdate(ActiveAdmin admin, Location location) {
+ final Intent intent = new Intent(
+ DevicePolicyManager.ACTION_LOST_MODE_LOCATION_UPDATE);
+ intent.putExtra(DevicePolicyManager.EXTRA_LOST_MODE_LOCATION, location);
+ intent.setPackage(admin.info.getPackageName());
+ mContext.sendBroadcastAsUser(intent, admin.getUserHandle());
+ }
+
/**
* Called by a privileged caller holding {@code BIND_DEVICE_ADMIN} permission to retrieve
* the remove warning for the given device admin.
@@ -12104,7 +12170,7 @@
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(isProfileOwner(caller),
"Caller is not profile owner;"
- + " only profile owner may control the preferntial network service");
+ + " only profile owner may control the preferential network service");
synchronized (getLockObject()) {
final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(
caller.getUserId());
@@ -12141,6 +12207,47 @@
}
@Override
+ public void setPreferentialNetworkServiceConfig(
+ PreferentialNetworkServiceConfig preferentialNetworkServiceConfig) {
+ if (!mHasFeature) {
+ return;
+ }
+ final CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(isProfileOwner(caller),
+ "Caller is not profile owner;"
+ + " only profile owner may control the preferential network service");
+ synchronized (getLockObject()) {
+ final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(
+ caller.getUserId());
+ if (!requiredAdmin.mPreferentialNetworkServiceConfig.equals(
+ preferentialNetworkServiceConfig)) {
+ requiredAdmin.mPreferentialNetworkServiceConfig = preferentialNetworkServiceConfig;
+ saveSettingsLocked(caller.getUserId());
+ }
+ }
+ updateNetworkPreferenceForUser(caller.getUserId(), preferentialNetworkServiceConfig);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_PREFERENTIAL_NETWORK_SERVICE_ENABLED)
+ .setBoolean(preferentialNetworkServiceConfig.isEnabled())
+ .write();
+ }
+
+ @Override
+ public PreferentialNetworkServiceConfig getPreferentialNetworkServiceConfig() {
+ if (!mHasFeature) {
+ return PreferentialNetworkServiceConfig.DEFAULT;
+ }
+
+ final CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(isProfileOwner(caller),
+ "Caller is not profile owner");
+ synchronized (getLockObject()) {
+ final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(caller.getUserId());
+ return requiredAdmin.mPreferentialNetworkServiceConfig;
+ }
+ }
+
+ @Override
public void setLockTaskPackages(ComponentName who, String[] packages)
throws SecurityException {
Objects.requireNonNull(who, "ComponentName is null");
@@ -17963,11 +18070,48 @@
if (!isManagedProfile(userId)) {
return;
}
- int networkPreference = preferentialNetworkServiceEnabled
- ? PROFILE_NETWORK_PREFERENCE_ENTERPRISE : PROFILE_NETWORK_PREFERENCE_DEFAULT;
ProfileNetworkPreference.Builder preferenceBuilder =
new ProfileNetworkPreference.Builder();
- preferenceBuilder.setPreference(networkPreference);
+ if (preferentialNetworkServiceEnabled) {
+ preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ preferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ } else {
+ preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT);
+ }
+ List<ProfileNetworkPreference> preferences = new ArrayList<>();
+ preferences.add(preferenceBuilder.build());
+ mInjector.binderWithCleanCallingIdentity(() ->
+ mInjector.getConnectivityManager().setProfileNetworkPreferences(
+ UserHandle.of(userId), preferences,
+ null /* executor */, null /* listener */));
+ }
+
+ private void updateNetworkPreferenceForUser(int userId,
+ PreferentialNetworkServiceConfig preferentialNetworkServiceConfig) {
+ if (!isManagedProfile(userId)) {
+ return;
+ }
+ ProfileNetworkPreference.Builder preferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ if (preferentialNetworkServiceConfig.isEnabled()) {
+ if (preferentialNetworkServiceConfig.isFallbackToDefaultConnectionAllowed()) {
+ preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ } else {
+ preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ }
+ } else {
+ preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT);
+ }
+ List<Integer> allowedUids = Arrays.stream(
+ preferentialNetworkServiceConfig.getIncludedUids()).boxed().collect(
+ Collectors.toList());
+ List<Integer> excludedUids = Arrays.stream(
+ preferentialNetworkServiceConfig.getExcludedUids()).boxed().collect(
+ Collectors.toList());
+ preferenceBuilder.setIncludedUids(allowedUids);
+ preferenceBuilder.setExcludedUids(excludedUids);
+ preferenceBuilder.setPreferenceEnterpriseId(
+ preferentialNetworkServiceConfig.getNetworkId());
List<ProfileNetworkPreference> preferences = new ArrayList<>();
preferences.add(preferenceBuilder.build());
mInjector.binderWithCleanCallingIdentity(() ->
@@ -18215,13 +18359,13 @@
mInjector.binderWithCleanCallingIdentity(() -> {
if (mDeviceManagementResourcesProvider.updateDrawables(drawables)) {
sendDrawableUpdatedBroadcast(
- drawables.stream().mapToInt(d -> d.getDrawableId()).toArray());
+ drawables.stream().map(s -> s.getDrawableId()).toArray(String[]::new));
}
});
}
@Override
- public void resetDrawables(@NonNull int[] drawableIds) {
+ public void resetDrawables(@NonNull String[] drawableIds) {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
@@ -18235,24 +18379,15 @@
}
@Override
- public ParcelableResource getDrawable(int drawableId, int drawableStyle, int drawableSource) {
+ public ParcelableResource getDrawable(
+ String drawableId, String drawableStyle, String drawableSource) {
return mInjector.binderWithCleanCallingIdentity(() ->
mDeviceManagementResourcesProvider.getDrawable(
drawableId, drawableStyle, drawableSource));
}
- private void sendDrawableUpdatedBroadcast(int[] drawableIds) {
- final Intent intent = new Intent(ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
- intent.putExtra(EXTRA_RESOURCE_ID, drawableIds);
- intent.putExtra(EXTRA_RESOURCE_TYPE_DRAWABLE, /* value= */ true);
- intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-
- List<UserInfo> users = mUserManager.getAliveUsers();
- for (int i = 0; i < users.size(); i++) {
- UserHandle user = users.get(i).getUserHandle();
- mContext.sendBroadcastAsUser(intent, user);
- }
+ private void sendDrawableUpdatedBroadcast(String[] drawableIds) {
+ sendResourceUpdatedBroadcast(EXTRA_RESOURCE_TYPE_DRAWABLE, drawableIds);
}
@Override
@@ -18288,9 +18423,13 @@
}
private void sendStringsUpdatedBroadcast(String[] stringIds) {
+ sendResourceUpdatedBroadcast(EXTRA_RESOURCE_TYPE_STRING, stringIds);
+ }
+
+ private void sendResourceUpdatedBroadcast(String resourceType, String[] resourceIds) {
final Intent intent = new Intent(ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
- intent.putExtra(EXTRA_RESOURCE_ID, stringIds);
- intent.putExtra(EXTRA_RESOURCE_TYPE_STRING, /* value= */ true);
+ intent.putExtra(EXTRA_RESOURCE_ID, resourceIds);
+ intent.putExtra(resourceType, /* value= */ true);
intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ad8753d..1fe71f8 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -138,6 +138,7 @@
import com.android.server.lights.LightsService;
import com.android.server.locales.LocaleManagerService;
import com.android.server.location.LocationManagerService;
+import com.android.server.logcat.LogcatManagerService;
import com.android.server.media.MediaRouterService;
import com.android.server.media.metrics.MediaMetricsManagerService;
import com.android.server.media.projection.MediaProjectionManagerService;
@@ -1631,6 +1632,10 @@
mSystemServiceManager.startService(AppIntegrityManagerService.class);
t.traceEnd();
+ t.traceBegin("StartLogcatManager");
+ mSystemServiceManager.startService(LogcatManagerService.class);
+ t.traceEnd();
+
} catch (Throwable e) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting core service");
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 6e72479..d562786 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -864,7 +864,15 @@
if (device == null) {
throw new IllegalArgumentException("no such device for " + deviceInfo);
}
- return device.getDeviceStatus();
+ int uid = Binder.getCallingUid();
+ if (device.isUidAllowed(uid)) {
+ return device.getDeviceStatus();
+ } else {
+ Log.e(TAG, "getDeviceStatus() invalid UID = " + uid);
+ EventLog.writeEvent(0x534e4554, "203549963",
+ uid, "getDeviceStatus: invalid uid");
+ return null;
+ }
}
@Override
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java
index 1d4c94d..99b0f25 100644
--- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java
+++ b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java
@@ -16,6 +16,8 @@
package com.android.server.selectiontoolbar;
+import android.service.selectiontoolbar.DefaultSelectionToolbarRenderService;
+
import com.android.server.infra.ServiceNameResolver;
import java.io.PrintWriter;
@@ -24,7 +26,7 @@
// TODO: move to SysUi or ExtServices
private static final String SELECTION_TOOLBAR_SERVICE_NAME =
- "android/com.android.server.selectiontoolbar.DefaultSelectionToolbarRenderService";
+ "android/" + DefaultSelectionToolbarRenderService.class.getName();
@Override
public String getDefaultServiceName(int userId) {
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
index 57562ef..f266e76 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
@@ -31,9 +31,9 @@
override val creator = ParsedPermissionGroupImpl.CREATOR
override val subclassBaseParams = listOf(
- ParsedPermissionGroup::getRequestDetailResourceId,
- ParsedPermissionGroup::getBackgroundRequestDetailResourceId,
- ParsedPermissionGroup::getBackgroundRequestResourceId,
+ ParsedPermissionGroup::getRequestDetailRes,
+ ParsedPermissionGroup::getBackgroundRequestDetailRes,
+ ParsedPermissionGroup::getBackgroundRequestRes,
ParsedPermissionGroup::getRequestRes,
ParsedPermissionGroup::getPriority,
)
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
index 037da24..0302d57 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
@@ -43,10 +43,9 @@
override fun mainComponentSubclassExtraParams() = listOf(
getSetByValue(
ParsedProvider::getUriPermissionPatterns,
- ParsedProviderImpl::setUriPermissionPatterns,
+ ParsedProviderImpl::addUriPermissionPattern,
PatternMatcher("testPattern", PatternMatcher.PATTERN_LITERAL),
- transformGet = { it?.singleOrNull() },
- transformSet = { arrayOf(it) },
+ transformGet = { it.singleOrNull() },
compare = { first, second ->
equalBy(
first, second,
@@ -57,15 +56,14 @@
),
getSetByValue(
ParsedProvider::getPathPermissions,
- ParsedProviderImpl::setPathPermissions,
+ ParsedProviderImpl::addPathPermission,
PathPermission(
"testPermissionPattern",
PatternMatcher.PATTERN_LITERAL,
"test.READ_PERMISSION",
"test.WRITE_PERMISSION"
),
- transformGet = { it?.singleOrNull() },
- transformSet = { arrayOf(it) },
+ transformGet = { it.singleOrNull() },
compare = { first, second ->
equalBy(
first, second,
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index cfae9a3..153ce17 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -16,6 +16,11 @@
package com.android.server.job.controllers;
+import static android.app.job.JobInfo.PRIORITY_DEFAULT;
+import static android.app.job.JobInfo.PRIORITY_HIGH;
+import static android.app.job.JobInfo.PRIORITY_LOW;
+import static android.app.job.JobInfo.PRIORITY_MIN;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -269,14 +274,14 @@
}
private void setCharging() {
- doReturn(true).when(mJobSchedulerService).isBatteryCharging();
+ when(mJobSchedulerService.isBatteryCharging()).thenReturn(true);
synchronized (mQuotaController.mLock) {
mQuotaController.onBatteryStateChangedLocked();
}
}
private void setDischarging() {
- doReturn(false).when(mJobSchedulerService).isBatteryCharging();
+ when(mJobSchedulerService.isBatteryCharging()).thenReturn(false);
synchronized (mQuotaController.mLock) {
mQuotaController.onBatteryStateChangedLocked();
}
@@ -407,6 +412,14 @@
}
}
+ private void setDeviceConfigFloat(String key, float val) {
+ mDeviceConfigPropertiesBuilder.setFloat(key, val);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForUpdatedConstantsLocked();
+ mQcConstants.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
+ }
+ }
+
private void waitForNonDelayedMessagesProcessed() {
mQuotaController.getHandler().runWithScissors(() -> {}, 15_000);
}
@@ -839,7 +852,7 @@
SOURCE_USER_ID, SOURCE_PACKAGE, inputStats);
assertEquals(expectedStats, inputStats);
assertTrue(mQuotaController.isWithinQuotaLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX));
+ SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX, PRIORITY_DEFAULT));
}
assertTrue("Job not ready: " + jobStatus, jobStatus.isReady());
}
@@ -863,7 +876,7 @@
assertEquals(expectedStats, inputStats);
assertFalse(
mQuotaController.isWithinQuotaLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX));
+ SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX, PRIORITY_DEFAULT));
}
// Quota should be exceeded due to activity in active timer.
@@ -888,7 +901,7 @@
assertEquals(expectedStats, inputStats);
assertFalse(
mQuotaController.isWithinQuotaLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX));
+ SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX, PRIORITY_DEFAULT));
assertFalse("Job unexpectedly ready: " + jobStatus, jobStatus.isReady());
}
}
@@ -1484,7 +1497,7 @@
SOURCE_USER_ID, SOURCE_PACKAGE));
assertEquals(MINUTE_IN_MILLIS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
}
setStandbyBucket(FREQUENT_INDEX);
@@ -1494,7 +1507,7 @@
SOURCE_USER_ID, SOURCE_PACKAGE));
assertEquals(MINUTE_IN_MILLIS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
}
setStandbyBucket(WORKING_INDEX);
@@ -1504,7 +1517,7 @@
SOURCE_USER_ID, SOURCE_PACKAGE));
assertEquals(7 * MINUTE_IN_MILLIS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
}
// ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the
@@ -1516,7 +1529,7 @@
SOURCE_USER_ID, SOURCE_PACKAGE));
assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 9 * MINUTE_IN_MILLIS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
}
}
@@ -1540,7 +1553,7 @@
// Max time will phase out, so should use bucket limit.
assertEquals(10 * MINUTE_IN_MILLIS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
}
mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
@@ -1556,7 +1569,7 @@
SOURCE_USER_ID, SOURCE_PACKAGE));
assertEquals(10 * MINUTE_IN_MILLIS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
}
mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
@@ -1573,7 +1586,7 @@
SOURCE_USER_ID, SOURCE_PACKAGE));
assertEquals(3 * MINUTE_IN_MILLIS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
}
}
@@ -1606,7 +1619,7 @@
// window time.
assertEquals(10 * MINUTE_IN_MILLIS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
}
mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
@@ -1633,15 +1646,115 @@
// Max time only has one minute phase out. Bucket time has 2 minute phase out.
assertEquals(9 * MINUTE_IN_MILLIS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
+ }
+ }
+
+ /**
+ * Test getTimeUntilQuotaConsumedLocked when the determination is based on the job's priority.
+ */
+ @Test
+ public void testGetTimeUntilQuotaConsumedLocked_Priority() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ // Close to RARE boundary.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS),
+ 150 * SECOND_IN_MILLIS, 5), false);
+ // Far away from FREQUENT boundary.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (7 * HOUR_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 5), false);
+ // Overlap WORKING_SET boundary.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
+ 2 * MINUTE_IN_MILLIS, 5), false);
+ // Close to ACTIVE boundary.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+
+ setStandbyBucket(RARE_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(30 * SECOND_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(3 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_HIGH));
+ assertEquals(3 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
+ assertEquals(0,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_LOW));
+ assertEquals(0,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_MIN));
+ }
+
+ setStandbyBucket(FREQUENT_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(3 * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(3 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_HIGH));
+ assertEquals(3 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
+ assertEquals(30 * SECOND_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_LOW));
+ assertEquals(0,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_MIN));
+ }
+
+ setStandbyBucket(WORKING_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(6 * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(7 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_HIGH));
+ assertEquals(7 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
+ assertEquals(4 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_LOW));
+ assertEquals(2 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_MIN));
+ }
+
+ // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the
+ // max execution time.
+ setStandbyBucket(ACTIVE_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(7 * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 7 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_HIGH));
+ assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 7 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
+ assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 7 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_LOW));
+ assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 7 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_MIN));
}
}
@Test
public void testIsWithinQuotaLocked_NeverApp() {
synchronized (mQuotaController.mLock) {
- assertFalse(
- mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX));
+ assertFalse(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test.never", NEVER_INDEX, PRIORITY_DEFAULT));
}
}
@@ -1649,7 +1762,8 @@
public void testIsWithinQuotaLocked_Charging() {
setCharging();
synchronized (mQuotaController.mLock) {
- assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX));
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", RARE_INDEX, PRIORITY_DEFAULT));
}
}
@@ -1663,7 +1777,8 @@
createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
synchronized (mQuotaController.mLock) {
mQuotaController.incrementJobCountLocked(0, "com.android.test", 5);
- assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", WORKING_INDEX, PRIORITY_DEFAULT));
}
}
@@ -1680,7 +1795,7 @@
synchronized (mQuotaController.mLock) {
mQuotaController.incrementJobCountLocked(0, "com.android.test.spam", jobCount);
assertFalse(mQuotaController.isWithinQuotaLocked(
- 0, "com.android.test.spam", WORKING_INDEX));
+ 0, "com.android.test.spam", WORKING_INDEX, PRIORITY_DEFAULT));
}
mQuotaController.saveTimingSession(0, "com.android.test.frequent",
@@ -1690,7 +1805,7 @@
createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 500), false);
synchronized (mQuotaController.mLock) {
assertFalse(mQuotaController.isWithinQuotaLocked(
- 0, "com.android.test.frequent", FREQUENT_INDEX));
+ 0, "com.android.test.frequent", FREQUENT_INDEX, PRIORITY_DEFAULT));
}
}
@@ -1706,7 +1821,8 @@
createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), false);
synchronized (mQuotaController.mLock) {
mQuotaController.incrementJobCountLocked(0, "com.android.test", 5);
- assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+ assertFalse(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", WORKING_INDEX, PRIORITY_DEFAULT));
}
}
@@ -1722,7 +1838,8 @@
false);
synchronized (mQuotaController.mLock) {
mQuotaController.incrementJobCountLocked(0, "com.android.test", jobCount);
- assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+ assertFalse(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", WORKING_INDEX, PRIORITY_DEFAULT));
}
}
@@ -1875,22 +1992,66 @@
assertEquals("Rare has incorrect quota status with " + (i + 1) + " sessions",
i < 2,
- mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX));
+ mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", RARE_INDEX, PRIORITY_DEFAULT));
assertEquals("Frequent has incorrect quota status with " + (i + 1) + " sessions",
i < 3,
mQuotaController.isWithinQuotaLocked(
- 0, "com.android.test", FREQUENT_INDEX));
+ 0, "com.android.test", FREQUENT_INDEX, PRIORITY_DEFAULT));
assertEquals("Working has incorrect quota status with " + (i + 1) + " sessions",
i < 4,
- mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+ mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", WORKING_INDEX, PRIORITY_DEFAULT));
assertEquals("Active has incorrect quota status with " + (i + 1) + " sessions",
i < 5,
- mQuotaController.isWithinQuotaLocked(0, "com.android.test", ACTIVE_INDEX));
+ mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", ACTIVE_INDEX, PRIORITY_DEFAULT));
}
}
}
@Test
+ public void testIsWithinQuotaLocked_Priority() {
+ setDischarging();
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.incrementJobCountLocked(0, "com.android.test", 5);
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", FREQUENT_INDEX, PRIORITY_HIGH));
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", FREQUENT_INDEX, PRIORITY_DEFAULT));
+ assertFalse(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", FREQUENT_INDEX, PRIORITY_LOW));
+ assertFalse(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", FREQUENT_INDEX, PRIORITY_MIN));
+
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", WORKING_INDEX, PRIORITY_HIGH));
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", WORKING_INDEX, PRIORITY_DEFAULT));
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", WORKING_INDEX, PRIORITY_LOW));
+ assertFalse(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", WORKING_INDEX, PRIORITY_MIN));
+
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", ACTIVE_INDEX, PRIORITY_HIGH));
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", ACTIVE_INDEX, PRIORITY_DEFAULT));
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", ACTIVE_INDEX, PRIORITY_LOW));
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", ACTIVE_INDEX, PRIORITY_MIN));
+ }
+ }
+
+ @Test
public void testIsWithinEJQuotaLocked_NeverApp() {
JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_NeverApp", 1);
setStandbyBucket(NEVER_INDEX, js);
@@ -2116,6 +2277,12 @@
final int standbyBucket = ACTIVE_INDEX;
setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+ JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1);
+ setStandbyBucket(standbyBucket, jobStatus);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+ }
+
// No sessions saved yet.
synchronized (mQuotaController.mLock) {
mQuotaController.maybeScheduleStartAlarmLocked(
@@ -2150,10 +2317,7 @@
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
- JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1);
- setStandbyBucket(standbyBucket, jobStatus);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
mQuotaController.prepareForExecutionLocked(jobStatus);
}
advanceElapsedClock(5 * MINUTE_IN_MILLIS);
@@ -2179,19 +2343,24 @@
// Working set window size is 2 hours.
final int standbyBucket = WORKING_INDEX;
+ JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_WorkingSet", 1);
+ setStandbyBucket(standbyBucket, jobStatus);
synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
// No sessions saved yet.
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Test with timing sessions out of window.
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2201,37 +2370,41 @@
// Counting backwards, the quota will come back one minute before the end.
final long expectedAlarmTime =
end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS;
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
new TimingSession(now - 2 * HOUR_IN_MILLIS, end, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Add some more sessions, but still in quota.
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - (50 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Test when out of quota.
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - 30 * MINUTE_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Alarm already scheduled, so make sure it's not scheduled again.
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2244,22 +2417,29 @@
spyOn(mQuotaController);
doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(
+ createJobStatus("testMaybeScheduleStartAlarmLocked_Frequent", 1), null);
+ }
+
// Frequent window size is 8 hours.
final int standbyBucket = FREQUENT_INDEX;
// No sessions saved yet.
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Test with timing sessions out of window.
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2267,37 +2447,41 @@
// Test with timing sessions in window but still in quota.
final long start = now - (6 * HOUR_IN_MILLIS);
final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS;
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Add some more sessions, but still in quota.
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Test when out of quota.
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Alarm already scheduled, so make sure it's not scheduled again.
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2314,6 +2498,11 @@
spyOn(mQuotaController);
doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(
+ createJobStatus("testMaybeScheduleStartAlarmLocked_Never", 1), null);
+ }
+
// The app is really in the NEVER bucket but is elevated somehow (eg via uidActive).
setStandbyBucket(NEVER_INDEX);
final int effectiveStandbyBucket = FREQUENT_INDEX;
@@ -2390,22 +2579,30 @@
// Rare window size is 24 hours.
final int standbyBucket = RARE_INDEX;
+ JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Rare", 1);
+ setStandbyBucket(standbyBucket, jobStatus);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+ }
+
// Prevent timing session throttling from affecting the test.
setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RARE, 50);
// No sessions saved yet.
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Test with timing sessions out of window.
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - 25 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2417,42 +2614,168 @@
final long expectedAlarmTime =
start + MINUTE_IN_MILLIS + 24 * HOUR_IN_MILLIS
+ mQcConstants.IN_QUOTA_BUFFER_MS;
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Add some more sessions, but still in quota.
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Test when out of quota.
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - HOUR_IN_MILLIS, 2 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Alarm already scheduled, so make sure it's not scheduled again.
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
}
+ @Test
+ public void testMaybeScheduleStartAlarmLocked_Priority() {
+ // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
+ // because it schedules an alarm too. Prevent it from doing so.
+ spyOn(mQuotaController);
+ doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RARE, 5);
+
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (24 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1), false);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false);
+
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ JobStatus jobDef = createJobStatus("testMaybeScheduleStartAlarmLocked_Priority",
+ SOURCE_PACKAGE, CALLING_UID,
+ new JobInfo.Builder(1, new ComponentName(mContext, "TestQuotaJobService"))
+ .setPriority(PRIORITY_DEFAULT)
+ .build());
+ JobStatus jobLow = createJobStatus("testMaybeScheduleStartAlarmLocked_Priority",
+ SOURCE_PACKAGE, CALLING_UID,
+ new JobInfo.Builder(2, new ComponentName(mContext, "TestQuotaJobService"))
+ .setPriority(PRIORITY_LOW)
+ .build());
+ JobStatus jobMin = createJobStatus("testMaybeScheduleStartAlarmLocked_Priority",
+ SOURCE_PACKAGE, CALLING_UID,
+ new JobInfo.Builder(3, new ComponentName(mContext, "TestQuotaJobService"))
+ .setPriority(PRIORITY_MIN)
+ .build());
+
+ setStandbyBucket(RARE_INDEX, jobDef, jobLow, jobMin);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(jobMin, null);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX);
+ // Min job requires 5 mins of surplus.
+ long expectedAlarmTime = now + 23 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+ inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mQuotaController.maybeStartTrackingJobLocked(jobLow, null);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX);
+ // Low job requires 2.5 mins of surplus.
+ expectedAlarmTime = now + 17 * HOUR_IN_MILLIS + 90 * SECOND_IN_MILLIS;
+ inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mQuotaController.maybeStartTrackingJobLocked(jobDef, null);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX);
+ // Default+ jobs require IN_QUOTA_BUFFER_MS.
+ expectedAlarmTime = now + mQcConstants.IN_QUOTA_BUFFER_MS;
+ inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mQuotaController.maybeStopTrackingJobLocked(jobMin, null, false);
+ mQuotaController.maybeStopTrackingJobLocked(jobLow, null, false);
+ mQuotaController.maybeStopTrackingJobLocked(jobDef, null, false);
+
+ setStandbyBucket(FREQUENT_INDEX, jobDef, jobLow, jobMin);
+
+ mQuotaController.maybeStartTrackingJobLocked(jobMin, null);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
+ // Min job requires 5 mins of surplus.
+ expectedAlarmTime = now + 7 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+ inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mQuotaController.maybeStartTrackingJobLocked(jobLow, null);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
+ // Low job requires 2.5 mins of surplus.
+ expectedAlarmTime = now + HOUR_IN_MILLIS + 90 * SECOND_IN_MILLIS;
+ inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mQuotaController.maybeStartTrackingJobLocked(jobDef, null);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
+ // Default+ jobs already have enough quota.
+ inOrder.verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mQuotaController.maybeStopTrackingJobLocked(jobMin, null, false);
+ mQuotaController.maybeStopTrackingJobLocked(jobLow, null, false);
+ mQuotaController.maybeStopTrackingJobLocked(jobDef, null, false);
+
+ setStandbyBucket(WORKING_INDEX, jobDef, jobLow, jobMin);
+
+ mQuotaController.maybeStartTrackingJobLocked(jobMin, null);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
+ // Min job requires 5 mins of surplus.
+ expectedAlarmTime = now + HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+ inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mQuotaController.maybeStartTrackingJobLocked(jobLow, null);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
+ // Low job has enough surplus.
+ inOrder.verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mQuotaController.maybeStartTrackingJobLocked(jobDef, null);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
+ // Default+ jobs already have enough quota.
+ inOrder.verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ }
+ }
+
/** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */
@Test
public void testMaybeScheduleStartAlarmLocked_BucketChange() {
@@ -2464,24 +2787,29 @@
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
// Affects rare bucket
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - 12 * HOUR_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3), false);
// Affects frequent and rare buckets
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - 4 * HOUR_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3), false);
// Affects working, frequent, and rare buckets
final long outOfQuotaTime = now - HOUR_IN_MILLIS;
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(outOfQuotaTime, 7 * MINUTE_IN_MILLIS, 10), false);
// Affects all buckets
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - 5 * MINUTE_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 3), false);
InOrder inOrder = inOrder(mAlarmManager);
+ JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_BucketChange", 1);
+
// Start in ACTIVE bucket.
+ setStandbyBucket(ACTIVE_INDEX, jobStatus);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX);
}
inOrder.verify(mAlarmManager, timeout(1000).times(0))
.setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2492,8 +2820,10 @@
final long expectedWorkingAlarmTime =
outOfQuotaTime + (2 * HOUR_IN_MILLIS)
+ mQcConstants.IN_QUOTA_BUFFER_MS;
+ setStandbyBucket(WORKING_INDEX, jobStatus);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
@@ -2502,8 +2832,10 @@
final long expectedFrequentAlarmTime =
outOfQuotaTime + (8 * HOUR_IN_MILLIS)
+ mQcConstants.IN_QUOTA_BUFFER_MS;
+ setStandbyBucket(FREQUENT_INDEX, jobStatus);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
@@ -2512,29 +2844,37 @@
final long expectedRareAlarmTime =
outOfQuotaTime + (24 * HOUR_IN_MILLIS)
+ mQcConstants.IN_QUOTA_BUFFER_MS;
+ setStandbyBucket(RARE_INDEX, jobStatus);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", RARE_INDEX);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX);
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// And back up again.
+ setStandbyBucket(FREQUENT_INDEX, jobStatus);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
eq(TAG_QUOTA_CHECK), any(), any());
+ setStandbyBucket(WORKING_INDEX, jobStatus);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
eq(TAG_QUOTA_CHECK), any(), any());
+ setStandbyBucket(ACTIVE_INDEX, jobStatus);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX);
}
inOrder.verify(mAlarmManager, timeout(1000).times(0))
.setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2551,6 +2891,13 @@
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
final int standbyBucket = WORKING_INDEX;
+
+ JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked", 1);
+ setStandbyBucket(standbyBucket, jobStatus);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+ }
+
ExecutionStats stats;
synchronized (mQuotaController.mLock) {
stats = mQuotaController.getExecutionStatsLocked(
@@ -2646,6 +2993,11 @@
spyOn(mQuotaController);
doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(
+ createJobStatus("testMaybeScheduleStartAlarmLocked", 1), null);
+ }
+
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
// Working set window size is 2 hours.
final int standbyBucket = WORKING_INDEX;
@@ -2671,13 +3023,17 @@
anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
}
-
private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck() {
// saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
// because it schedules an alarm too. Prevent it from doing so.
spyOn(mQuotaController);
doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(
+ createJobStatus("testMaybeScheduleStartAlarmLocked", 1), null);
+ }
+
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
// Working set window size is 2 hours.
final int standbyBucket = WORKING_INDEX;
@@ -2708,6 +3064,8 @@
public void testConstantsUpdating_ValidValues() {
setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 5 * MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 2 * MINUTE_IN_MILLIS);
+ setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, .7f);
+ setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, .2f);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 15 * MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, 30 * MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 45 * MINUTE_IN_MILLIS);
@@ -2748,6 +3106,8 @@
assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
+ assertEquals(.7f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6);
+ assertEquals(.2f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6);
assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
assertEquals(45 * MINUTE_IN_MILLIS,
@@ -2793,6 +3153,8 @@
// Test negatives/too low.
setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, -MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, -MINUTE_IN_MILLIS);
+ setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, -.1f);
+ setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, -.01f);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, -MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, -MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, -MINUTE_IN_MILLIS);
@@ -2831,6 +3193,8 @@
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
assertEquals(0, mQuotaController.getInQuotaBufferMs());
+ assertEquals(0f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6);
+ assertEquals(0f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6);
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
@@ -2878,6 +3242,8 @@
// Test larger than a day. Controller should cap at one day.
setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 25 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 25 * HOUR_IN_MILLIS);
+ setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, 1f);
+ setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, .95f);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 25 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, 25 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 25 * HOUR_IN_MILLIS);
@@ -2905,6 +3271,8 @@
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
+ assertEquals(.9f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6);
+ assertEquals(.9f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
@@ -3669,8 +4037,8 @@
// Wait for some extra time to allow for job processing.
inOrder.verify(mJobSchedulerService,
- timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
- .onControllerStateChanged(any());
+ timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
synchronized (mQuotaController.mLock) {
assertEquals(remainingTimeMs / 2,
mQuotaController.getRemainingExecutionTimeLocked(jobBg));
@@ -3681,8 +4049,8 @@
setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
advanceElapsedClock(remainingTimeMs / 2 + 1);
inOrder.verify(mJobSchedulerService,
- timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
- .onControllerStateChanged(any());
+ timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
// Top job should still be allowed to run.
assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
@@ -3696,7 +4064,7 @@
advanceElapsedClock(20 * SECOND_IN_MILLIS);
setProcessState(ActivityManager.PROCESS_STATE_TOP);
inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
- .onControllerStateChanged(any());
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
trackJobs(jobFg, jobTop);
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobTop);
@@ -3715,7 +4083,7 @@
advanceElapsedClock(20 * SECOND_IN_MILLIS);
setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
- .onControllerStateChanged(any());
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
// App is now in background and out of quota. Fg should now change to out of quota since it
// wasn't started. Top should remain in quota since it started when the app was in TOP.
assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
@@ -3753,7 +4121,7 @@
// Wait for some extra time to allow for job processing.
verify(mJobSchedulerService,
timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(1))
- .onControllerStateChanged(any());
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
assertEquals(JobSchedulerService.sElapsedRealtimeClock.millis(),
jobStatus.getWhenStandbyDeferred());
@@ -3797,7 +4165,7 @@
// Wait for some extra time to allow for job processing.
verify(mJobSchedulerService,
timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
- .onControllerStateChanged(any());
+ .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
// The job used up the remaining quota, but in that time, the same amount of time in the
// old TimingSession also fell out of the quota window, so it should still have the same
@@ -3819,7 +4187,7 @@
// Wait for some extra time to allow for job processing.
verify(mJobSchedulerService,
timeout(12 * SECOND_IN_MILLIS).times(1))
- .onControllerStateChanged(any());
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
verify(handler, never()).sendMessageDelayed(any(), anyInt());
}
@@ -4406,6 +4774,11 @@
spyOn(mQuotaController);
doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(
+ createJobStatus("testMaybeScheduleStartAlarmLocked_EJ", 1), null);
+ }
+
final int standbyBucket = WORKING_INDEX;
setStandbyBucket(standbyBucket);
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 20 * MINUTE_IN_MILLIS);
@@ -4482,6 +4855,11 @@
spyOn(mQuotaController);
doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(
+ createJobStatus("testMaybeScheduleStartAlarmLocked_Ej_BucketChange", 1), null);
+ }
+
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 30 * MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 20 * MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 15 * MINUTE_IN_MILLIS);
@@ -4590,6 +4968,11 @@
spyOn(mQuotaController);
doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(
+ createJobStatus("testMaybeScheduleStartAlarmLocked_Ej_SRQ", 1), null);
+ }
+
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
setStandbyBucket(WORKING_INDEX);
final long contributionMs = mQcConstants.IN_QUOTA_BUFFER_MS / 2;
@@ -5437,8 +5820,8 @@
// Wait for some extra time to allow for job processing.
inOrder.verify(mJobSchedulerService,
- timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
- .onControllerStateChanged(any());
+ timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
synchronized (mQuotaController.mLock) {
assertEquals(remainingTimeMs / 2,
mQuotaController.getRemainingEJExecutionTimeLocked(
@@ -5448,8 +5831,8 @@
setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
advanceElapsedClock(remainingTimeMs / 2 + 1);
inOrder.verify(mJobSchedulerService,
- timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
- .onControllerStateChanged(any());
+ timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
// Top should still be "in quota" since it started before the app ran on top out of quota.
assertFalse(jobBg.isExpeditedQuotaApproved());
assertTrue(jobTop.isExpeditedQuotaApproved());
@@ -5473,7 +5856,7 @@
setProcessState(ActivityManager.PROCESS_STATE_TOP);
// Confirm QC recognizes that jobUnstarted has changed from out-of-quota to in-quota.
inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
- .onControllerStateChanged(any());
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
trackJobs(jobTop2, jobFg);
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobTop2);
@@ -5494,7 +5877,7 @@
advanceElapsedClock(20 * SECOND_IN_MILLIS);
setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
- .onControllerStateChanged(any());
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 3));
// App is now in background and out of quota. Fg should now change to out of quota since it
// wasn't started. Top should remain in quota since it started when the app was in TOP.
assertTrue(jobTop2.isExpeditedQuotaApproved());
@@ -5633,7 +6016,7 @@
// Wait for some extra time to allow for job processing.
verify(mJobSchedulerService,
timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
- .onControllerStateChanged(any());
+ .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
assertTrue(jobStatus.isExpeditedQuotaApproved());
// The job used up the remaining quota, but in that time, the same amount of time in the
// old TimingSession also fell out of the quota window, so it should still have the same
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 555f4b8..1e0f30e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -521,6 +521,8 @@
val parsedPackage = parseResult.hideAsParsed() as ParsedPackage
whenever(mocks.packageParser.parsePackage(
or(eq(path), eq(basePath)), anyInt(), anyBoolean())) { parsedPackage }
+ whenever(mocks.packageParser.parsePackage(
+ or(eq(path), eq(basePath)), anyInt(), anyBoolean(), any())) { parsedPackage }
return parsedPackage
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
index dbd5403..0820a3c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
@@ -33,6 +33,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.argThat
@@ -121,7 +122,8 @@
whenever(rule.mocks().packageParser.parsePackage(
argThat { path: File -> path.path.contains("a.data.package") },
anyInt(),
- anyBoolean()))
+ anyBoolean(),
+ any()))
.thenThrow(PackageManagerException(
PackageManager.INSTALL_FAILED_INVALID_APK, "Oh no!"))
val pm = createPackageManagerService()
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index 53468c8..ff1b6f6 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -19,11 +19,13 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.hardware.input.InputManagerInternal;
import android.os.Binder;
import android.os.IBinder;
+import android.os.IInputConstants;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
@@ -64,9 +66,13 @@
final IBinder deviceToken = new Binder();
mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
/* displayId= */ 1);
+ verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
+ verify(mInputManagerInternalMock).setPointerAcceleration(eq(1f));
mInputController.unregisterInputDevice(deviceToken);
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(
eq(Display.INVALID_DISPLAY));
+ verify(mInputManagerInternalMock).setPointerAcceleration(
+ eq((float) IInputConstants.DEFAULT_POINTER_ACCELERATION));
}
@Test
@@ -75,10 +81,14 @@
mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
/* displayId= */ 1);
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
- mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
+ verify(mInputManagerInternalMock).setPointerAcceleration(eq(1f));
+ final IBinder deviceToken2 = new Binder();
+ mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken2,
/* displayId= */ 2);
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(2));
mInputController.unregisterInputDevice(deviceToken);
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
+ verify(mInputManagerInternalMock, times(0)).setPointerAcceleration(
+ eq((float) IInputConstants.DEFAULT_POINTER_ACCELERATION));
}
}
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 fe7d34a..72100e44 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
@@ -29,6 +29,7 @@
import android.Manifest;
import android.app.admin.DevicePolicyManager;
+import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.VirtualDeviceParams;
import android.content.Context;
import android.content.ContextWrapper;
@@ -81,6 +82,8 @@
private DevicePolicyManager mDevicePolicyManagerMock;
@Mock
private InputManagerInternal mInputManagerInternalMock;
+ @Mock
+ private IVirtualDeviceActivityListener mActivityListener;
@Before
public void setUp() {
@@ -102,7 +105,7 @@
mInputController = new InputController(new Object(), mNativeWrapperMock);
mDeviceImpl = new VirtualDeviceImpl(mContext,
/* association info */ null, new Binder(), /* uid */ 0, mInputController,
- (int associationId) -> {}, mPendingTrampolineCallback,
+ (int associationId) -> {}, mPendingTrampolineCallback, mActivityListener,
new VirtualDeviceParams.Builder().build());
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 8a5b16e..c0ad69f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -42,7 +42,9 @@
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
@@ -93,6 +95,7 @@
import android.app.admin.DevicePolicyManagerLiteInternal;
import android.app.admin.FactoryResetProtectionPolicy;
import android.app.admin.PasswordMetrics;
+import android.app.admin.PreferentialNetworkServiceConfig;
import android.app.admin.SystemUpdatePolicy;
import android.app.admin.WifiSsidPolicy;
import android.content.BroadcastReceiver;
@@ -4135,6 +4138,7 @@
ProfileNetworkPreference preferenceDetails2 =
new ProfileNetworkPreference.Builder()
.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE)
+ .setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1)
.build();
List<ProfileNetworkPreference> preferences2 = new ArrayList<>();
preferences2.add(preferenceDetails);
@@ -4144,6 +4148,164 @@
}
@Test
+ public void testSetPreferentialNetworkServiceConfig_noProfileOwner() throws Exception {
+ assertExpectException(SecurityException.class, null,
+ () -> dpm.setPreferentialNetworkServiceConfig(
+ PreferentialNetworkServiceConfig.DEFAULT));
+ }
+
+ @Test
+ public void testIsPreferentialNetworkServiceEnabled_noProfileOwner() throws Exception {
+ assertExpectException(SecurityException.class, null,
+ () -> dpm.isPreferentialNetworkServiceEnabled());
+ }
+
+ @Test
+ public void testSetPreferentialNetworkServiceConfig_invalidConfig() throws Exception {
+ final int managedProfileUserId = 15;
+ final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+ addManagedProfile(admin1, managedProfileAdminUid, admin1);
+ mContext.binder.callingUid = managedProfileAdminUid;
+
+ PreferentialNetworkServiceConfig.Builder preferentialNetworkServiceConfigBuilder =
+ new PreferentialNetworkServiceConfig.Builder();
+ assertExpectException(NullPointerException.class, null,
+ () -> preferentialNetworkServiceConfigBuilder.setIncludedUids(null));
+ assertExpectException(NullPointerException.class, null,
+ () -> preferentialNetworkServiceConfigBuilder.setExcludedUids(null));
+ assertExpectException(IllegalArgumentException.class, null,
+ () -> preferentialNetworkServiceConfigBuilder.setNetworkId(6));
+ int[] includedUids = new int[]{1, 2};
+ int[] excludedUids = new int[]{3, 4};
+ preferentialNetworkServiceConfigBuilder.setIncludedUids(includedUids);
+ preferentialNetworkServiceConfigBuilder.setExcludedUids(excludedUids);
+
+ assertExpectException(IllegalStateException.class, null,
+ () -> preferentialNetworkServiceConfigBuilder.build());
+ }
+
+ @Test
+ public void testSetPreferentialNetworkServiceConfig_defaultPreference() throws Exception {
+ final int managedProfileUserId = 15;
+ final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+ addManagedProfile(admin1, managedProfileAdminUid, admin1);
+ mContext.binder.callingUid = managedProfileAdminUid;
+
+ dpm.setPreferentialNetworkServiceConfig(PreferentialNetworkServiceConfig.DEFAULT);
+ assertThat(dpm.isPreferentialNetworkServiceEnabled()).isFalse();
+ assertThat(dpm.getPreferentialNetworkServiceConfig()
+ .isEnabled()).isFalse();
+
+ ProfileNetworkPreference preferenceDetails =
+ new ProfileNetworkPreference.Builder()
+ .setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT)
+ .build();
+ List<ProfileNetworkPreference> preferences = new ArrayList<>();
+ preferences.add(preferenceDetails);
+ verify(getServices().connectivityManager, times(1))
+ .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences,
+ null, null);
+ }
+
+ @Test
+ public void testSetPreferentialNetworkServiceConfig_enterprisePreference() throws Exception {
+ PreferentialNetworkServiceConfig preferentialNetworkServiceConfigEnabled =
+ (new PreferentialNetworkServiceConfig.Builder())
+ .setEnabled(true)
+ .setNetworkId(NET_ENTERPRISE_ID_1)
+ .build();
+
+ final int managedProfileUserId = 15;
+ final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+ addManagedProfile(admin1, managedProfileAdminUid, admin1);
+ mContext.binder.callingUid = managedProfileAdminUid;
+
+ dpm.setPreferentialNetworkServiceConfig(preferentialNetworkServiceConfigEnabled);
+ assertThat(dpm.getPreferentialNetworkServiceConfig()
+ .isEnabled()).isTrue();
+ ProfileNetworkPreference preferenceDetails =
+ new ProfileNetworkPreference.Builder()
+ .setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE)
+ .setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1)
+ .build();
+ List<ProfileNetworkPreference> preferences = new ArrayList<>();
+ preferences.add(preferenceDetails);
+ verify(getServices().connectivityManager, times(1))
+ .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences,
+ null, null);
+ }
+
+ @Test
+ public void testSetPreferentialNetworkServiceConfig_enterprisePreferenceIncludedUids()
+ throws Exception {
+ final int managedProfileUserId = 15;
+ final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+ addManagedProfile(admin1, managedProfileAdminUid, admin1);
+ mContext.binder.callingUid = managedProfileAdminUid;
+
+ PreferentialNetworkServiceConfig preferentialNetworkServiceConfigEnabled =
+ (new PreferentialNetworkServiceConfig.Builder())
+ .setEnabled(true)
+ .setNetworkId(NET_ENTERPRISE_ID_1)
+ .setFallbackToDefaultConnectionAllowed(false)
+ .setIncludedUids(new int[]{1, 2})
+ .build();
+ dpm.setPreferentialNetworkServiceConfig(preferentialNetworkServiceConfigEnabled);
+ assertThat(dpm.getPreferentialNetworkServiceConfig()
+ .isEnabled()).isTrue();
+ List<Integer> includedList = new ArrayList<>();
+ includedList.add(1);
+ includedList.add(2);
+ ProfileNetworkPreference preferenceDetails =
+ new ProfileNetworkPreference.Builder()
+ .setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK)
+ .setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1)
+ .setIncludedUids(includedList)
+ .build();
+ List<ProfileNetworkPreference> preferences = new ArrayList<>();
+ preferences.add(preferenceDetails);
+ verify(getServices().connectivityManager, times(1))
+ .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences,
+ null, null);
+ }
+
+ @Test
+ public void testSetPreferentialNetworkServiceConfig_enterprisePreferenceExcludedUids()
+ throws Exception {
+ final int managedProfileUserId = 15;
+ final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+ addManagedProfile(admin1, managedProfileAdminUid, admin1);
+ mContext.binder.callingUid = managedProfileAdminUid;
+
+ PreferentialNetworkServiceConfig preferentialNetworkServiceConfigEnabled =
+ (new PreferentialNetworkServiceConfig.Builder())
+ .setEnabled(true)
+ .setNetworkId(NET_ENTERPRISE_ID_1)
+ .setFallbackToDefaultConnectionAllowed(false)
+ .setExcludedUids(new int[]{1, 2})
+ .build();
+
+ dpm.setPreferentialNetworkServiceConfig(preferentialNetworkServiceConfigEnabled);
+ assertThat(dpm.getPreferentialNetworkServiceConfig()
+ .isEnabled()).isTrue();
+ List<Integer> excludedUids = new ArrayList<>();
+ excludedUids.add(1);
+ excludedUids.add(2);
+ ProfileNetworkPreference preferenceDetails =
+ new ProfileNetworkPreference.Builder()
+ .setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK)
+ .setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1)
+ .setExcludedUids(excludedUids)
+ .build();
+ List<ProfileNetworkPreference> preferences = new ArrayList<>();
+ preferences.clear();
+ preferences.add(preferenceDetails);
+ verify(getServices().connectivityManager, times(1))
+ .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences,
+ null, null);
+ }
+
+ @Test
public void testSetSystemSettingFailWithNonWhitelistedSettings() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
@@ -7960,6 +8122,51 @@
() -> WifiSsidPolicy.createDenylistPolicy(ssids));
}
+ @Test
+ public void testSendLostModeLocationUpdate_noPermission() {
+ assertThrows(SecurityException.class, () -> dpm.sendLostModeLocationUpdate(
+ getServices().executor, /* empty callback */ result -> {}));
+ }
+
+ @Test
+ public void testSendLostModeLocationUpdate_notOrganizationOwnedDevice() {
+ mContext.callerPermissions.add(permission.SEND_LOST_MODE_LOCATION_UPDATES);
+ assertThrows(IllegalStateException.class, () -> dpm.sendLostModeLocationUpdate(
+ getServices().executor, /* empty callback */ result -> {}));
+ }
+
+ @Test
+ public void testSendLostModeLocationUpdate_asDeviceOwner() throws Exception {
+ final String TEST_PROVIDER = "network";
+ mContext.callerPermissions.add(permission.SEND_LOST_MODE_LOCATION_UPDATES);
+ setDeviceOwner();
+ when(getServices().locationManager.getAllProviders()).thenReturn(List.of(TEST_PROVIDER));
+ when(getServices().locationManager.isProviderEnabled(TEST_PROVIDER)).thenReturn(true);
+
+ dpm.sendLostModeLocationUpdate(getServices().executor, /* empty callback */ result -> {});
+
+ verify(getServices().locationManager, times(1)).getCurrentLocation(
+ eq(TEST_PROVIDER), any(), eq(getServices().executor), any());
+ }
+
+ @Test
+ public void testSendLostModeLocationUpdate_asProfileOwnerOfOrgOwnedDevice() throws Exception {
+ final String TEST_PROVIDER = "network";
+ final int MANAGED_PROFILE_ADMIN_UID =
+ UserHandle.getUid(CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID);
+ mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+ mContext.callerPermissions.add(permission.SEND_LOST_MODE_LOCATION_UPDATES);
+ addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+ configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
+ when(getServices().locationManager.getAllProviders()).thenReturn(List.of(TEST_PROVIDER));
+ when(getServices().locationManager.isProviderEnabled(TEST_PROVIDER)).thenReturn(true);
+
+ dpm.sendLostModeLocationUpdate(getServices().executor, /* empty callback */ result -> {});
+
+ verify(getServices().locationManager, times(1)).getCurrentLocation(
+ eq(TEST_PROVIDER), any(), eq(getServices().executor), any());
+ }
+
private void setupVpnAuthorization(String userVpnPackage, int userVpnUid) {
final AppOpsManager.PackageOps vpnOp = new AppOpsManager.PackageOps(userVpnPackage,
userVpnUid, List.of(new AppOpsManager.OpEntry(
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 6eb2085..2cf67f8 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -47,6 +47,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executor;
/**
* Context used throughout DPMS tests.
@@ -234,6 +235,8 @@
return mMockSystemServices.vpnManager;
case Context.DEVICE_POLICY_SERVICE:
return mMockSystemServices.devicePolicyManager;
+ case Context.LOCATION_SERVICE:
+ return mMockSystemServices.locationManager;
}
throw new UnsupportedOperationException();
}
@@ -493,6 +496,11 @@
}
@Override
+ public Executor getMainExecutor() {
+ return mMockSystemServices.executor;
+ }
+
+ @Override
public int checkCallingPermission(String permission) {
return checkPermission(permission);
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 597a165..21fb2da 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -47,6 +47,7 @@
import android.content.pm.UserInfo;
import android.database.Cursor;
import android.hardware.usb.UsbManager;
+import android.location.LocationManager;
import android.media.IAudioService;
import android.net.ConnectivityManager;
import android.net.IIpConnectivityMetrics;
@@ -81,6 +82,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -90,6 +92,7 @@
public final File systemUserDataDir;
public final EnvironmentForMock environment;
public final SystemPropertiesForMock systemProperties;
+ public final Executor executor;
public final UserManager userManager;
public final UserManagerInternal userManagerInternal;
public final UsageStatsManagerInternal usageStatsManagerInternal;
@@ -127,6 +130,7 @@
public final UsbManager usbManager;
public final VpnManager vpnManager;
public final DevicePolicyManager devicePolicyManager;
+ public final LocationManager locationManager;
/** Note this is a partial mock, not a real mock. */
public final PackageManager packageManager;
public final BuildMock buildMock = new BuildMock();
@@ -138,6 +142,7 @@
environment = mock(EnvironmentForMock.class);
systemProperties = mock(SystemPropertiesForMock.class);
+ executor = mock(Executor.class);
userManager = mock(UserManager.class);
userManagerInternal = mock(UserManagerInternal.class);
usageStatsManagerInternal = mock(UsageStatsManagerInternal.class);
@@ -175,6 +180,7 @@
usbManager = mock(UsbManager.class);
vpnManager = mock(VpnManager.class);
devicePolicyManager = mock(DevicePolicyManager.class);
+ locationManager = mock(LocationManager.class);
// Package manager is huge, so we use a partial mock instead.
packageManager = spy(realContext.getPackageManager());
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 03eba9b..d2cff0e 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -551,13 +551,14 @@
Assert.assertTrue(Arrays.equals(expected, actual));
}
- private static final class TestDeviceStatePolicy implements DeviceStatePolicy {
+ private static final class TestDeviceStatePolicy extends DeviceStatePolicy {
private final DeviceStateProvider mProvider;
private int mLastDeviceStateRequestedToConfigure = INVALID_DEVICE_STATE;
private boolean mConfigureBlocked = false;
private Runnable mPendingConfigureCompleteRunnable;
TestDeviceStatePolicy(DeviceStateProvider provider) {
+ super(InstrumentationRegistry.getContext());
mProvider = provider;
}
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStatePolicyProviderTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStatePolicyProviderTest.java
new file mode 100644
index 0000000..0bd81b7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStatePolicyProviderTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.devicestate;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.platform.test.annotations.Presubmit;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Unit tests for the {@link DeviceStatePolicy.Provider}
+ * <p/>
+ * Build/Install/Run:
+ * <code>atest DeviceStatePolicyProviderTest</code>
+ */
+@Presubmit
+public class DeviceStatePolicyProviderTest {
+
+ @Test
+ public void test_emptyPolicyProvider() {
+ Assert.assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider("")),
+ Matchers.instanceOf(DeviceStatePolicy.DefaultProvider.class));
+ }
+
+ @Test
+ public void test_nullPolicyProvider() {
+ Assert.assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(null)),
+ Matchers.instanceOf(DeviceStatePolicy.DefaultProvider.class));
+ }
+
+ @Test
+ public void test_customPolicyProvider() {
+ Assert.assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(
+ TestProvider.class.getName())),
+ Matchers.instanceOf(TestProvider.class));
+ }
+
+ @Test
+ public void test_badPolicyProvider_notImplementingProviderInterface() {
+ assertThrows(IllegalStateException.class, () -> {
+ DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(
+ Object.class.getName()));
+ });
+ }
+
+ @Test
+ public void test_badPolicyProvider_doesntExist() {
+ assertThrows(IllegalStateException.class, () -> {
+ DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(
+ "com.android.devicestate.nonexistent.policy"));
+ });
+ }
+
+ private static Resources resourcesWithProvider(String provider) {
+ final Resources mockResources = mock(Resources.class);
+ when(mockResources.getString(
+ com.android.internal.R.string.config_deviceSpecificDeviceStatePolicyProvider))
+ .thenReturn(provider);
+ return mockResources;
+ }
+
+ // Stub implementation of DeviceStatePolicy.Provider for testing
+ static class TestProvider implements DeviceStatePolicy.Provider {
+ @Override
+ public DeviceStatePolicy instantiate(Context context) {
+ throw new RuntimeException("test stub");
+ }
+ }
+}
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 54945e4..4caa85c 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -391,4 +391,33 @@
listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0));
assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
}
+
+ @Test
+ public void testHysteresisLevels() {
+ int[] ambientBrighteningThresholds = {100, 200};
+ int[] ambientDarkeningThresholds = {400, 500};
+ int[] ambientThresholdLevels = {500};
+ float ambientDarkeningMinChangeThreshold = 3.0f;
+ float ambientBrighteningMinChangeThreshold = 1.5f;
+ HysteresisLevels hysteresisLevels = new HysteresisLevels(ambientBrighteningThresholds,
+ ambientDarkeningThresholds, ambientThresholdLevels,
+ ambientDarkeningMinChangeThreshold, ambientBrighteningMinChangeThreshold);
+
+ // test low, activate minimum change thresholds.
+ assertEquals(1.5f, hysteresisLevels.getBrighteningThreshold(0.0f), EPSILON);
+ assertEquals(0f, hysteresisLevels.getDarkeningThreshold(0.0f), EPSILON);
+ assertEquals(1f, hysteresisLevels.getDarkeningThreshold(4.0f), EPSILON);
+
+ // test max
+ assertEquals(12000f, hysteresisLevels.getBrighteningThreshold(10000.0f), EPSILON);
+ assertEquals(5000f, hysteresisLevels.getDarkeningThreshold(10000.0f), EPSILON);
+
+ // test just below threshold
+ assertEquals(548.9f, hysteresisLevels.getBrighteningThreshold(499f), EPSILON);
+ assertEquals(299.4f, hysteresisLevels.getDarkeningThreshold(499f), EPSILON);
+
+ // test at (considered above) threshold
+ assertEquals(600f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
+ assertEquals(250f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
+ }
}
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 81c9871..e80721a 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -51,6 +51,7 @@
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.net.NetworkStats.METERED_NO;
import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkTemplate.MATCH_MOBILE;
import static android.net.NetworkTemplate.buildTemplateCarrierMetered;
import static android.net.NetworkTemplate.buildTemplateWifi;
import static android.net.TrafficStats.MB_IN_BYTES;
@@ -71,7 +72,6 @@
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;
@@ -95,6 +95,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -495,8 +496,14 @@
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));
+ // Catch UsageCallback during systemReady(). Simulate NetworkStatsService triggered
+ // stats updated callback to signal its readiness.
+ final ArgumentCaptor<NetworkStatsManager.UsageCallback> usageObserver =
+ ArgumentCaptor.forClass(NetworkStatsManager.UsageCallback.class);
+ verify(mStatsManager, times(2))
+ .registerUsageCallback(any(), anyLong(), any(), usageObserver.capture());
+ usageObserver.getValue().onThresholdReached(
+ new NetworkTemplate.Builder(MATCH_MOBILE).build());
NetworkPolicy defaultPolicy = mService.buildDefaultCarrierPolicy(0, "");
mDefaultWarningBytes = defaultPolicy.warningBytes;
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index d8ecf20..31bdec1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -65,7 +65,7 @@
import com.android.server.pm.parsing.pkg.PackageImpl;
import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.permission.CompatibilityPermissionInfo;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageUserStateInternal;
import com.android.server.pm.pkg.component.ParsedActivity;
import com.android.server.pm.pkg.component.ParsedActivityImpl;
import com.android.server.pm.pkg.component.ParsedApexSystemService;
@@ -673,9 +673,9 @@
assertArrayEquals(a.getSplitFlags(), b.getSplitFlags());
PackageInfo aInfo = PackageInfoUtils.generate(a, new int[]{}, 0, 0, 0,
- Collections.emptySet(), PackageUserState.DEFAULT, 0, mockPkgSetting(a));
+ Collections.emptySet(), PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(a));
PackageInfo bInfo = PackageInfoUtils.generate(b, new int[]{}, 0, 0, 0,
- Collections.emptySet(), PackageUserState.DEFAULT, 0, mockPkgSetting(b));
+ Collections.emptySet(), PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(b));
assertApplicationInfoEqual(aInfo.applicationInfo, bInfo.applicationInfo);
assertEquals(ArrayUtils.size(a.getPermissions()), ArrayUtils.size(b.getPermissions()));
@@ -831,9 +831,9 @@
// Validity check for ServiceInfo.
ServiceInfo aInfo = PackageInfoUtils.generateServiceInfo(aPkg, a, 0,
- PackageUserState.DEFAULT, 0, mockPkgSetting(aPkg));
+ PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(aPkg));
ServiceInfo bInfo = PackageInfoUtils.generateServiceInfo(bPkg, b, 0,
- PackageUserState.DEFAULT, 0, mockPkgSetting(bPkg));
+ PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(bPkg));
assertApplicationInfoEqual(aInfo.applicationInfo, bInfo.applicationInfo);
assertEquals(a.getName(), b.getName());
}
@@ -858,9 +858,9 @@
// Validity check for ActivityInfo.
ActivityInfo aInfo = PackageInfoUtils.generateActivityInfo(aPkg, a, 0,
- PackageUserState.DEFAULT, 0, mockPkgSetting(aPkg));
+ PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(aPkg));
ActivityInfo bInfo = PackageInfoUtils.generateActivityInfo(bPkg, b, 0,
- PackageUserState.DEFAULT, 0, mockPkgSetting(bPkg));
+ PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(bPkg));
assertApplicationInfoEqual(aInfo.applicationInfo, bInfo.applicationInfo);
assertEquals(a.getName(), b.getName());
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index 7ff8eec..4a24bbd 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -574,7 +574,7 @@
assertBasicPackageSetting(scanResult, packageName, isInstant, pkgSetting);
final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
- pkgSetting.getPkg(), 0, pkgSetting.readUserState(0), 0, pkgSetting);
+ pkgSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, pkgSetting);
assertBasicApplicationInfo(scanResult, applicationInfo);
}
@@ -612,7 +612,7 @@
private static void assertAbiAndPathssDerived(ScanResult scanResult) {
PackageSetting pkgSetting = scanResult.mPkgSetting;
final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
- pkgSetting.getPkg(), 0, pkgSetting.readUserState(0), 0, pkgSetting);
+ pkgSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, pkgSetting);
assertThat(applicationInfo.primaryCpuAbi, is("derivedPrimary"));
assertThat(applicationInfo.secondaryCpuAbi, is("derivedSecondary"));
@@ -626,7 +626,7 @@
private static void assertPathsNotDerived(ScanResult scanResult) {
PackageSetting pkgSetting = scanResult.mPkgSetting;
final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
- pkgSetting.getPkg(), 0, pkgSetting.readUserState(0), 0, pkgSetting);
+ pkgSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, pkgSetting);
assertThat(applicationInfo.nativeLibraryRootDir, is("getRootDir"));
assertThat(pkgSetting.getLegacyNativeLibraryPath(), is("getRootDir"));
assertThat(applicationInfo.nativeLibraryRootRequiresIsa, is(true));
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 7e5fe04..401cd7f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -309,7 +309,7 @@
@MediumTest
@Test
- public void testRemoveUserOrSetEphemeral_restrictedReturnsError() throws Exception {
+ public void testRemoveUserWhenPossible_restrictedReturnsError() throws Exception {
final int currentUser = ActivityManager.getCurrentUser();
final UserInfo user1 = createUser("User 1", /* flags= */ 0);
mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ true,
@@ -328,7 +328,7 @@
@MediumTest
@Test
- public void testRemoveUserOrSetEphemeral_evenWhenRestricted() throws Exception {
+ public void testRemoveUserWhenPossible_evenWhenRestricted() throws Exception {
final int currentUser = ActivityManager.getCurrentUser();
final UserInfo user1 = createUser("User 1", /* flags= */ 0);
mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ true,
@@ -351,7 +351,7 @@
@MediumTest
@Test
- public void testRemoveUserOrSetEphemeral_systemUserReturnsError() throws Exception {
+ public void testRemoveUserWhenPossible_systemUserReturnsError() throws Exception {
assertThat(mUserManager.removeUserWhenPossible(UserHandle.SYSTEM,
/* overrideDevicePolicy= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR);
@@ -360,7 +360,7 @@
@MediumTest
@Test
- public void testRemoveUserOrSetEphemeral_invalidUserReturnsError() throws Exception {
+ public void testRemoveUserWhenPossible_invalidUserReturnsError() throws Exception {
assertThat(hasUser(Integer.MAX_VALUE)).isFalse();
assertThat(mUserManager.removeUserWhenPossible(UserHandle.of(Integer.MAX_VALUE),
/* overrideDevicePolicy= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR);
@@ -368,7 +368,7 @@
@MediumTest
@Test
- public void testRemoveUserOrSetEphemeral_currentUserSetEphemeral() throws Exception {
+ public void testRemoveUserWhenPossible_currentUserSetEphemeral() throws Exception {
final int startUser = ActivityManager.getCurrentUser();
final UserInfo user1 = createUser("User 1", /* flags= */ 0);
// Switch to the user just created.
@@ -392,7 +392,7 @@
@MediumTest
@Test
- public void testRemoveUserOrSetEphemeral_nonCurrentUserRemoved() throws Exception {
+ public void testRemoveUserWhenPossible_nonCurrentUserRemoved() throws Exception {
final UserInfo user1 = createUser("User 1", /* flags= */ 0);
synchronized (mUserRemoveLock) {
assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java
index 739b3b1..5e9e16a 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java
@@ -53,10 +53,10 @@
private static final float[] TEST_AMPLITUDE_MAP = new float[]{
/* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
- private static final VibratorInfo.FrequencyMapping EMPTY_FREQUENCY_MAPPING =
- new VibratorInfo.FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, null);
- private static final VibratorInfo.FrequencyMapping TEST_FREQUENCY_MAPPING =
- new VibratorInfo.FrequencyMapping(TEST_RESONANT_FREQUENCY, TEST_MIN_FREQUENCY,
+ private static final VibratorInfo.FrequencyProfile EMPTY_FREQUENCY_PROFILE =
+ new VibratorInfo.FrequencyProfile(Float.NaN, Float.NaN, Float.NaN, null);
+ private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
+ new VibratorInfo.FrequencyProfile(TEST_RESONANT_FREQUENCY, TEST_MIN_FREQUENCY,
TEST_FREQUENCY_RESOLUTION, TEST_AMPLITUDE_MAP);
private DeviceVibrationEffectAdapter mAdapter;
@@ -79,8 +79,8 @@
new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.5f, 100)),
/* repeatIndex= */ -1);
- assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(EMPTY_FREQUENCY_MAPPING)));
- assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(TEST_FREQUENCY_MAPPING)));
+ assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(EMPTY_FREQUENCY_PROFILE)));
+ assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(TEST_FREQUENCY_PROFILE)));
}
@Test
@@ -97,7 +97,7 @@
/* repeatIndex= */ 3);
VibrationEffect.Composed adaptedEffect = (VibrationEffect.Composed) mAdapter.apply(effect,
- createVibratorInfo(EMPTY_FREQUENCY_MAPPING));
+ createVibratorInfo(EMPTY_FREQUENCY_PROFILE));
assertTrue(adaptedEffect.getSegments().size() > effect.getSegments().size());
assertTrue(adaptedEffect.getRepeatIndex() >= effect.getRepeatIndex());
@@ -128,7 +128,7 @@
/* startFrequencyHz= */ 200, /* endFrequencyHz= */ 50, /* duration= */ 20)),
/* repeatIndex= */ 2);
- VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_MAPPING,
+ VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_PROFILE,
IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
assertEquals(expected, mAdapter.apply(effect, info));
}
@@ -159,7 +159,7 @@
/* duration= */ 20)),
/* repeatIndex= */ 2);
- VibratorInfo info = createVibratorInfo(EMPTY_FREQUENCY_MAPPING,
+ VibratorInfo info = createVibratorInfo(EMPTY_FREQUENCY_PROFILE,
IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
assertEquals(expected, mAdapter.apply(effect, info));
}
@@ -188,17 +188,17 @@
/* startFrequencyHz= */ 200, /* endFrequencyHz= */ 50, /* duration= */ 20)),
/* repeatIndex= */ 2);
- VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_MAPPING,
+ VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_PROFILE,
IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
assertEquals(expected, mAdapter.apply(effect, info));
}
- private static VibratorInfo createVibratorInfo(VibratorInfo.FrequencyMapping frequencyMapping,
+ private static VibratorInfo createVibratorInfo(VibratorInfo.FrequencyProfile frequencyProfile,
int... capabilities) {
int cap = IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0);
return new VibratorInfo.Builder(0)
.setCapabilities(cap)
- .setFrequencyMapping(frequencyMapping)
+ .setFrequencyProfile(frequencyProfile)
.build();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 2ad0e93..e88e988 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -170,7 +170,7 @@
}
infoBuilder.setCompositionSizeMax(mCompositionSizeMax);
infoBuilder.setQFactor(mQFactor);
- infoBuilder.setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+ infoBuilder.setFrequencyProfile(new VibratorInfo.FrequencyProfile(
mResonantFrequency, mMinFrequency, mFrequencyResolution, mMaxAmplitudes));
return mIsInfoLoadSuccessful;
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java
index 22db917..a9f37f3 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java
@@ -47,8 +47,8 @@
private static final int TEST_STEP_DURATION = 5;
private static final float[] TEST_AMPLITUDE_MAP = new float[]{
/* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
- private static final VibratorInfo.FrequencyMapping TEST_FREQUENCY_MAPPING =
- new VibratorInfo.FrequencyMapping(
+ private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
+ new VibratorInfo.FrequencyProfile(
/* resonantFrequencyHz= */ 150f, /* minFrequencyHz= */ 50f,
/* frequencyResolutionHz= */ 25f, TEST_AMPLITUDE_MAP);
@@ -124,7 +124,7 @@
private static VibratorInfo createVibratorInfo(int... capabilities) {
return new VibratorInfo.Builder(0)
.setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
- .setFrequencyMapping(TEST_FREQUENCY_MAPPING)
+ .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
.build();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java
index 18ff953..54627c4 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java
@@ -46,8 +46,8 @@
public class StepToRampAdapterTest {
private static final float[] TEST_AMPLITUDE_MAP = new float[]{
/* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
- private static final VibratorInfo.FrequencyMapping TEST_FREQUENCY_MAPPING =
- new VibratorInfo.FrequencyMapping(
+ private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
+ new VibratorInfo.FrequencyProfile(
/* resonantFrequencyHz= */ 150f, /* minFrequencyHz= */ 50f,
/* frequencyResolutionHz= */ 25f, TEST_AMPLITUDE_MAP);
@@ -99,7 +99,7 @@
VibratorInfo vibratorInfo = new VibratorInfo.Builder(0)
.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)
.setPwlePrimitiveDurationMax(10)
- .setFrequencyMapping(TEST_FREQUENCY_MAPPING)
+ .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
.build();
// Update repeat index to skip the ramp splits.
@@ -191,7 +191,7 @@
private static VibratorInfo createVibratorInfo(int... capabilities) {
return new VibratorInfo.Builder(0)
.setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
- .setFrequencyMapping(TEST_FREQUENCY_MAPPING)
+ .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
.build();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
index cb4982b..f2c1874 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
@@ -310,13 +310,13 @@
}
private void mockVibratorCapabilities(int capabilities) {
- VibratorInfo.FrequencyMapping frequencyMapping = new VibratorInfo.FrequencyMapping(
+ VibratorInfo.FrequencyProfile frequencyProfile = new VibratorInfo.FrequencyProfile(
Float.NaN, Float.NaN, Float.NaN, null);
when(mNativeWrapperMock.getInfo(any(VibratorInfo.Builder.class)))
.then(invocation -> {
((VibratorInfo.Builder) invocation.getArgument(0))
.setCapabilities(capabilities)
- .setFrequencyMapping(frequencyMapping);
+ .setFrequencyProfile(frequencyProfile);
return true;
});
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index ab86e29..52975ef 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -316,7 +316,7 @@
assertNotNull(info);
assertEquals(1, info.getId());
- assertEquals(123.f, info.getResonantFrequency(), 0.01 /*tolerance*/);
+ assertEquals(123.f, info.getResonantFrequencyHz(), 0.01 /*tolerance*/);
}
@Test
@@ -341,7 +341,7 @@
info.isEffectSupported(VibrationEffect.EFFECT_TICK));
assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));
- assertEquals(123.f, info.getResonantFrequency(), 0.01 /*tolerance*/);
+ assertEquals(123.f, info.getResonantFrequencyHz(), 0.01 /*tolerance*/);
assertTrue(Float.isNaN(info.getQFactor()));
}
@@ -360,7 +360,7 @@
VibratorInfo info = createService().getVibratorInfo(1);
assertNotNull(info);
assertEquals(1, info.getId());
- assertEquals(123.f, info.getResonantFrequency(), 0.01 /*tolerance*/);
+ assertEquals(123.f, info.getResonantFrequencyHz(), 0.01 /*tolerance*/);
}
@Test
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 a192bf8..9f92294 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -483,7 +483,7 @@
mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
mAppOpsManager, mAppOpsService, mUm, mHistoryManager, mStatsManager,
mock(TelephonyManager.class),
- mAmi, mToastRateLimiter, mPermissionHelper);
+ mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class));
// Return first true for RoleObserver main-thread check
when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
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 f6400b6..da5496d 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
@@ -371,7 +371,8 @@
mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAtm,
mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
mAppOpsManager, mock(IAppOpsService.class), mUm, mHistoryManager, mStatsManager,
- mock(TelephonyManager.class), mAmi, mToastRateLimiter, mPermissionHelper);
+ mock(TelephonyManager.class), mAmi, mToastRateLimiter, mPermissionHelper,
+ mock(UsageStatsManagerInternal.class));
// Return first true for RoleObserver main-thread check
when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 59d9a35..1d25b54 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -166,7 +166,8 @@
mUm, mock(NotificationHistoryManager.class),
mock(StatsManager.class), mock(TelephonyManager.class),
mock(ActivityManagerInternal.class),
- mock(MultiRateLimiter.class), mock(PermissionHelper.class));
+ mock(MultiRateLimiter.class), mock(PermissionHelper.class),
+ mock(UsageStatsManagerInternal.class));
} catch (SecurityException e) {
if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
throw e;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 774e5b9..30ad1f9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -40,6 +40,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.Process.NOBODY_UID;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InsetsState.ITYPE_IME;
@@ -83,6 +84,7 @@
import static com.android.server.wm.ActivityRecord.State.STARTED;
import static com.android.server.wm.ActivityRecord.State.STOPPED;
import static com.android.server.wm.ActivityRecord.State.STOPPING;
+import static com.android.server.wm.ActivityTaskManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
@@ -3305,6 +3307,29 @@
CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
}
+ @Test // b/162542125
+ public void testInputDispatchTimeout() throws RemoteException {
+ final ActivityRecord activity = createActivityWithTask();
+ final WindowProcessController wpc = activity.app;
+ spyOn(wpc);
+ doReturn(true).when(wpc).isInstrumenting();
+ final ActivityRecord instrumentingActivity = createActivityOnDisplay(
+ true /* defaultDisplay */, wpc);
+ assertEquals(INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS,
+ instrumentingActivity.mInputDispatchingTimeoutMillis);
+
+ doReturn(false).when(wpc).isInstrumenting();
+ final ActivityRecord nonInstrumentingActivity = createActivityOnDisplay(
+ true /* defaultDisplay */, wpc);
+ assertEquals(DEFAULT_DISPATCHING_TIMEOUT_MILLIS,
+ nonInstrumentingActivity.mInputDispatchingTimeoutMillis);
+
+ final ActivityRecord noProcActivity = createActivityOnDisplay(true /* defaultDisplay */,
+ null);
+ assertEquals(DEFAULT_DISPATCHING_TIMEOUT_MILLIS,
+ noProcActivity.mInputDispatchingTimeoutMillis);
+ }
+
private ICompatCameraControlCallback getCompatCameraControlCallback() {
return new ICompatCameraControlCallback.Stub() {
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 8d58ec0..ea03250 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1340,7 +1340,7 @@
displayContent.getDisplayRotation().setRotation((displayContent.getRotation() + 1) % 4);
displayContent.setRotationAnimation(rotationAnim);
// The fade rotation animation also starts to hide some non-app windows.
- assertNotNull(displayContent.getFadeRotationAnimationController());
+ assertNotNull(displayContent.getAsyncRotationController());
assertTrue(statusBar.isAnimating(PARENTS, ANIMATION_TYPE_FIXED_TRANSFORM));
for (WindowState w : windows) {
@@ -1354,7 +1354,7 @@
// the animation controller should be cleared.
statusBar.setOrientationChanging(false);
navBar.setOrientationChanging(false);
- assertNull(displayContent.getFadeRotationAnimationController());
+ assertNull(displayContent.getAsyncRotationController());
}
@UseTestDisplay(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR,
@@ -1392,7 +1392,7 @@
ROTATION_0 /* oldRotation */, ROTATION_90 /* newRotation */,
false /* forceUpdate */));
- assertNotNull(mDisplayContent.getFadeRotationAnimationController());
+ assertNotNull(mDisplayContent.getAsyncRotationController());
assertTrue(mStatusBarWindow.isAnimating(PARENTS, ANIMATION_TYPE_FIXED_TRANSFORM));
assertTrue(mNavBarWindow.isAnimating(PARENTS, ANIMATION_TYPE_FIXED_TRANSFORM));
// Notification shade may have its own view animation in real case so do not fade out it.
@@ -1482,7 +1482,7 @@
assertFalse(app.hasFixedRotationTransform());
assertFalse(app2.hasFixedRotationTransform());
assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation);
- assertNull(mDisplayContent.getFadeRotationAnimationController());
+ assertNull(mDisplayContent.getAsyncRotationController());
}
@Test
@@ -1598,6 +1598,30 @@
assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
}
+ /**
+ * Creates different types of displays, verifies that minimal task size doesn't change
+ * with density of display.
+ */
+ @Test
+ public void testCalculatesDisplaySpecificMinTaskSizes() {
+ DisplayContent defaultTestDisplay =
+ new TestDisplayContent.Builder(mAtm, 1000, 2000).build();
+ final int defaultMinTaskSize = defaultTestDisplay.mMinSizeOfResizeableTaskDp;
+ DisplayContent firstDisplay = new TestDisplayContent.Builder(mAtm, 1000, 2000)
+ .setDensityDpi(300)
+ .updateDisplayMetrics()
+ .setDefaultMinTaskSizeDp(defaultMinTaskSize + 10)
+ .build();
+ assertEquals(defaultMinTaskSize + 10, firstDisplay.mMinSizeOfResizeableTaskDp);
+
+ DisplayContent secondDisplay = new TestDisplayContent.Builder(mAtm, 200, 200)
+ .setDensityDpi(320)
+ .updateDisplayMetrics()
+ .setDefaultMinTaskSizeDp(defaultMinTaskSize + 20)
+ .build();
+ assertEquals(defaultMinTaskSize + 20, secondDisplay.mMinSizeOfResizeableTaskDp);
+ }
+
@Test
public void testRecentsNotRotatingWithFixedRotation() {
unblockDisplayRotation(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 6342183..25cff61c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -18,6 +18,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT;
import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DISABLED;
@@ -546,6 +547,80 @@
SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
}
+ @Test
+ public void testReturnsSensorRotation_180degrees_allRotationsAllowed()
+ throws Exception {
+ mBuilder.build();
+ when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations))
+ .thenReturn(true);
+ configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+ enableOrientationSensor();
+ mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+
+ assertEquals(Surface.ROTATION_180, mTarget.rotationForOrientation(
+ SCREEN_ORIENTATION_SENSOR, Surface.ROTATION_0));
+ }
+
+ @Test
+ public void testReturnLastRotation_sensor180_allRotationsNotAllowed()
+ throws Exception {
+ mBuilder.build();
+ when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations))
+ .thenReturn(false);
+ configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+ enableOrientationSensor();
+ mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+
+ assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation(
+ SCREEN_ORIENTATION_SENSOR, Surface.ROTATION_0));
+ }
+
+ @Test
+ public void testAllowRotationsIsCached()
+ throws Exception {
+ mBuilder.build();
+ configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+ enableOrientationSensor();
+
+ // Rotate once to read the resource
+ when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations))
+ .thenReturn(true);
+ mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+ mTarget.rotationForOrientation(SCREEN_ORIENTATION_SENSOR, Surface.ROTATION_0);
+
+ // Change resource to disallow all rotations.
+ // Rotate again and 180 degrees rotation should still be returned even if "disallowed".
+ when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations))
+ .thenReturn(false);
+ mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+ assertEquals(Surface.ROTATION_180, mTarget.rotationForOrientation(
+ SCREEN_ORIENTATION_SENSOR, Surface.ROTATION_0));
+ }
+
+ @Test
+ public void testResetAllowRotations()
+ throws Exception {
+ mBuilder.build();
+ configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+ enableOrientationSensor();
+
+ // Rotate once to read the resource
+ when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations))
+ .thenReturn(true);
+ mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+ mTarget.rotationForOrientation(SCREEN_ORIENTATION_SENSOR, Surface.ROTATION_0);
+
+ // Change resource to disallow all rotations.
+ // Reset "allowAllRotations".
+ // Rotate again and 180 degrees rotation should not be allowed anymore.
+ when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations))
+ .thenReturn(false);
+ mTarget.resetAllowAllRotations();
+ mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+ assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation(
+ SCREEN_ORIENTATION_SENSOR, Surface.ROTATION_0));
+ }
+
// =================================
// Tests for Policy based Rotation
// =================================
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 365e749..093be82 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -20,10 +20,10 @@
import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT;
import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DISABLED;
import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_ENABLED;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static android.view.WindowManager.REMOVE_CONTENT_MODE_DESTROY;
import static android.view.WindowManager.REMOVE_CONTENT_MODE_MOVE_TO_PRIMARY;
-import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
-import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -260,6 +260,17 @@
}
@Test
+ public void testResetAllowAllRotations() {
+ final DisplayRotation displayRotation = mock(DisplayRotation.class);
+ spyOn(mPrimaryDisplay);
+ doReturn(displayRotation).when(mPrimaryDisplay).getDisplayRotation();
+
+ mDisplayWindowSettings.applyRotationSettingsToDisplayLocked(mPrimaryDisplay);
+
+ verify(displayRotation).resetAllowAllRotations();
+ }
+
+ @Test
public void testDefaultToFreeUserRotation() {
mDisplayWindowSettings.applySettingsToDisplayLocked(mSecondaryDisplay);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
similarity index 78%
rename from services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
rename to services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
index 407f9cf..670eb75 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
@@ -26,11 +26,13 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
import android.app.ActivityThread;
import android.content.Context;
@@ -43,9 +45,9 @@
import android.view.IWindowManager;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
+import android.window.WindowTokenClient;
-import com.android.server.inputmethod.InputMethodManagerService;
-import com.android.server.inputmethod.InputMethodMenuController;
+import com.android.server.inputmethod.InputMethodDialogWindowContext;
import org.junit.After;
import org.junit.Before;
@@ -57,13 +59,13 @@
// scenario there.
/**
* Build/Install/Run:
- * atest WmTests:InputMethodMenuControllerTest
+ * atest WmTests:InputMethodDialogWindowContextTest
*/
@Presubmit
@RunWith(WindowTestRunner.class)
-public class InputMethodMenuControllerTest extends WindowTestsBase {
+public class InputMethodDialogWindowContextTest extends WindowTestsBase {
- private InputMethodMenuController mController;
+ private InputMethodDialogWindowContext mWindowContext;
private DualDisplayAreaGroupPolicyTest.DualDisplayContent mSecondaryDisplay;
private IWindowManager mIWindowManager;
@@ -76,7 +78,7 @@
new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider();
Mockito.doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider();
- mController = new InputMethodMenuController(mock(InputMethodManagerService.class));
+ mWindowContext = new InputMethodDialogWindowContext();
mSecondaryDisplay = new DualDisplayAreaGroupPolicyTest.DualDisplayContent
.Builder(mAtm, 1000, 1000).build();
mSecondaryDisplay.getDisplayInfo().state = STATE_ON;
@@ -115,30 +117,46 @@
@Test
public void testGetSettingsContext() {
- final Context contextOnDefaultDisplay = mController.getSettingsContext(DEFAULT_DISPLAY);
+ final Context contextOnDefaultDisplay = mWindowContext.get(DEFAULT_DISPLAY);
assertImeSwitchContextMetricsValidity(contextOnDefaultDisplay, mDefaultDisplay);
// Obtain the context again and check if the window metrics match the IME container bounds
// of the secondary display.
- final Context contextOnSecondaryDisplay = mController.getSettingsContext(
- mSecondaryDisplay.getDisplayId());
+ final Context contextOnSecondaryDisplay =
+ mWindowContext.get(mSecondaryDisplay.getDisplayId());
assertImeSwitchContextMetricsValidity(contextOnSecondaryDisplay, mSecondaryDisplay);
}
@Test
public void testGetSettingsContextOnDualDisplayContent() {
- final Context context = mController.getSettingsContext(mSecondaryDisplay.getDisplayId());
+ final Context context = mWindowContext.get(mSecondaryDisplay.getDisplayId());
+ final WindowTokenClient tokenClient = (WindowTokenClient) context.getWindowContextToken();
+ assertNotNull(tokenClient);
+ spyOn(tokenClient);
final DisplayArea.Tokens imeContainer = mSecondaryDisplay.getImeContainer();
+ spyOn(imeContainer);
assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay);
mSecondaryDisplay.mFirstRoot.placeImeContainer(imeContainer);
+
+ verify(imeContainer, atLeastOnce()).onConfigurationChanged(
+ eq(mSecondaryDisplay.mFirstRoot.getConfiguration()));
+ verify(tokenClient, atLeastOnce()).onConfigurationChanged(
+ eq(mSecondaryDisplay.mFirstRoot.getConfiguration()),
+ eq(mSecondaryDisplay.mDisplayId));
assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mFirstRoot);
assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay);
mSecondaryDisplay.mSecondRoot.placeImeContainer(imeContainer);
+
+ verify(imeContainer, atLeastOnce()).onConfigurationChanged(
+ eq(mSecondaryDisplay.mSecondRoot.getConfiguration()));
+ verify(tokenClient, atLeastOnce()).onConfigurationChanged(
+ eq(mSecondaryDisplay.mSecondRoot.getConfiguration()),
+ eq(mSecondaryDisplay.mDisplayId));
assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mSecondRoot);
assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay);
}
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 632a59d..87f76fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -621,9 +621,9 @@
@Test
public void testNotAttachNavigationBar_controlledByFadeRotationAnimation() {
setupForShouldAttachNavBarDuringTransition();
- FadeRotationAnimationController mockController =
- mock(FadeRotationAnimationController.class);
- doReturn(mockController).when(mDefaultDisplay).getFadeRotationAnimationController();
+ AsyncRotationController mockController =
+ mock(AsyncRotationController.class);
+ doReturn(mockController).when(mDefaultDisplay).getAsyncRotationController();
final ActivityRecord homeActivity = createHomeActivity();
initializeRecentsAnimationController(mController, homeActivity);
assertFalse(mController.isNavigationBarAttachedToApp());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 575e082..a4851ad5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -665,9 +665,9 @@
@Test
public void testNonAppTarget_notSendNavBar_controlledByFadeRotation() throws Exception {
- final FadeRotationAnimationController mockController =
- mock(FadeRotationAnimationController.class);
- doReturn(mockController).when(mDisplayContent).getFadeRotationAnimationController();
+ final AsyncRotationController mockController =
+ mock(AsyncRotationController.class);
+ doReturn(mockController).when(mDisplayContent).getAsyncRotationController();
final int transit = TRANSIT_OLD_TASK_OPEN;
setupForNonAppTargetNavBar(transit, true);
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 8b0716c..1e64e46 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -29,18 +29,26 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
import android.annotation.Nullable;
+import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
+import android.util.DisplayMetrics;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
class TestDisplayContent extends DisplayContent {
public static final int DEFAULT_LOGICAL_DISPLAY_DENSITY = 300;
@@ -85,6 +93,11 @@
private boolean mSystemDecorations = false;
private int mStatusBarHeight = 0;
private SettingsEntry mOverrideSettings;
+ private DisplayMetrics mDisplayMetrics;
+ @Mock
+ Context mMockContext;
+ @Mock
+ Resources mResources;
Builder(ActivityTaskManagerService service, int width, int height) {
mService = service;
@@ -97,6 +110,8 @@
// Set unique ID so physical display overrides are not inheritted from
// DisplayWindowSettings.
mInfo.uniqueId = generateUniqueId();
+ mDisplayMetrics = new DisplayMetrics();
+ updateDisplayMetrics();
}
Builder(ActivityTaskManagerService service, DisplayInfo info) {
mService = service;
@@ -153,6 +168,20 @@
mInfo.logicalDensityDpi = dpi;
return this;
}
+ Builder updateDisplayMetrics() {
+ mInfo.getAppMetrics(mDisplayMetrics);
+ return this;
+ }
+ Builder setDefaultMinTaskSizeDp(int valueDp) {
+ MockitoAnnotations.initMocks(this);
+ doReturn(mMockContext).when(mService.mContext).createConfigurationContext(any());
+ doReturn(mResources).when(mMockContext).getResources();
+ doReturn(valueDp * mDisplayMetrics.density)
+ .when(mResources)
+ .getDimension(
+ com.android.internal.R.dimen.default_minimal_size_resizable_task);
+ return this;
+ }
TestDisplayContent createInternal(Display display) {
return new TestDisplayContent(mService.mRootWindowContainer, display);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 141588a..fd523f0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -50,6 +50,7 @@
import android.util.ArraySet;
import android.view.SurfaceControl;
import android.view.TransactionCommittedListener;
+import android.window.IDisplayAreaOrganizer;
import android.window.ITaskOrganizer;
import android.window.ITransitionPlayer;
import android.window.TransitionInfo;
@@ -60,6 +61,8 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -75,7 +78,9 @@
private Transition createTestTransition(int transitType) {
TransitionController controller = mock(TransitionController.class);
final BLASTSyncEngine sync = createTestBLASTSyncEngine();
- return new Transition(transitType, 0 /* flags */, 0 /* timeoutMs */, controller, sync);
+ final Transition t = new Transition(transitType, 0 /* flags */, controller, sync);
+ t.startCollecting(0 /* timeoutMs */);
+ return t;
}
@Test
@@ -104,7 +109,7 @@
// Check basic both tasks participating
participants.add(oldTask);
participants.add(newTask);
- ArraySet<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+ ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
@@ -169,7 +174,7 @@
participants.add(oldTask);
participants.add(opening);
participants.add(opening2);
- ArraySet<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+ ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
@@ -215,15 +220,14 @@
// Check promotion to DisplayArea
participants.add(showing);
participants.add(showing2);
- ArraySet<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+ ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
assertEquals(1, info.getChanges().size());
assertEquals(transit, info.getType());
assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken()));
- ITaskOrganizer mockOrg = mock(ITaskOrganizer.class);
// Check that organized tasks get reported even if not top
- showTask.mTaskOrganizer = mockOrg;
+ makeTaskOrganized(showTask);
targets = Transition.calculateTargets(participants, changes);
info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
assertEquals(2, info.getChanges().size());
@@ -253,7 +257,7 @@
opening.mVisibleRequested = true;
closing.mVisibleRequested = false;
- ArraySet<WindowContainer> targets = Transition.calculateTargets(
+ ArrayList<WindowContainer> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
TransitionInfo info = Transition.calculateTransitionInfo(
0, 0, targets, transition.mChanges);
@@ -290,7 +294,7 @@
tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0;
}
- ArraySet<WindowContainer> targets = Transition.calculateTargets(
+ ArrayList<WindowContainer> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
TransitionInfo info = Transition.calculateTransitionInfo(
0, 0, targets, transition.mChanges);
@@ -339,7 +343,7 @@
tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0;
}
- ArraySet<WindowContainer> targets = Transition.calculateTargets(
+ ArrayList<WindowContainer> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
TransitionInfo info = Transition.calculateTransitionInfo(
0, 0, targets, transition.mChanges);
@@ -361,13 +365,7 @@
mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
// Make DA organized so we can check that they don't get included.
WindowContainer parent = wallpaperWindowToken.getParent();
- while (parent != null && parent != mDisplayContent) {
- if (parent.asDisplayArea() != null) {
- parent.asDisplayArea().setOrganizer(
- mock(android.window.IDisplayAreaOrganizer.class), true /* skipAppear */);
- }
- parent = parent.getParent();
- }
+ makeDisplayAreaOrganized(parent, mDisplayContent);
final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken,
"wallpaperWindow");
wallpaperWindowToken.setVisibleRequested(false);
@@ -379,7 +377,7 @@
mDisplayContent.getWindowConfiguration().setRotation(
(mDisplayContent.getWindowConfiguration().getRotation() + 1) % 4);
- ArraySet<WindowContainer> targets = Transition.calculateTargets(
+ ArrayList<WindowContainer> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
TransitionInfo info = Transition.calculateTransitionInfo(
0, 0, targets, transition.mChanges);
@@ -392,11 +390,37 @@
}
@Test
+ public void testOpenActivityInTheSameTaskWithDisplayChange() {
+ final ActivityRecord closing = createActivityRecord(mDisplayContent);
+ closing.mVisibleRequested = true;
+ final Task task = closing.getTask();
+ makeTaskOrganized(task);
+ final ActivityRecord opening = createActivityRecord(task);
+ opening.mVisibleRequested = false;
+ makeDisplayAreaOrganized(mDisplayContent.getDefaultTaskDisplayArea(), mDisplayContent);
+ final WindowContainer<?>[] wcs = { closing, opening, task, mDisplayContent };
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
+ for (WindowContainer<?> wc : wcs) {
+ transition.collect(wc);
+ }
+ closing.mVisibleRequested = false;
+ opening.mVisibleRequested = true;
+ final int newRotation = mDisplayContent.getWindowConfiguration().getRotation() + 1;
+ for (WindowContainer<?> wc : wcs) {
+ wc.getWindowConfiguration().setRotation(newRotation);
+ }
+
+ final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ transition.mParticipants, transition.mChanges);
+ // Especially the activities must be in the targets.
+ assertTrue(targets.containsAll(Arrays.asList(wcs)));
+ }
+
+ @Test
public void testIndependent() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
- ITaskOrganizer mockOrg = mock(ITaskOrganizer.class);
final Task openTask = createTask(mDisplayContent);
final Task openInOpenTask = createTaskInRootTask(openTask, 0);
@@ -408,10 +432,8 @@
final ActivityRecord changeInChange = createActivityRecord(changeInChangeTask);
final ActivityRecord openInChange = createActivityRecord(openInChangeTask);
// set organizer for everything so that they all get added to transition info
- for (Task t : new Task[]{
- openTask, openInOpenTask, changeTask, changeInChangeTask, openInChangeTask}) {
- t.mTaskOrganizer = mockOrg;
- }
+ makeTaskOrganized(openTask, openInOpenTask, changeTask, changeInChangeTask,
+ openInChangeTask);
// Start states.
changes.put(openTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
@@ -438,7 +460,8 @@
participants.add(openInChange);
// Explicitly add changeTask (to test independence with parents)
participants.add(changeTask);
- ArraySet<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+ final ArrayList<WindowContainer> targets =
+ Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
// Root changes should always be considered independent
assertTrue(isIndependent(
@@ -466,12 +489,13 @@
final BLASTSyncEngine sync = new BLASTSyncEngine(mWm);
final CountDownLatch latch = new CountDownLatch(1);
// When the timeout is reached, it will finish the sync-group and notify transaction ready.
- new Transition(TRANSIT_OPEN, 0 /* flags */, 10 /* timeoutMs */, controller, sync) {
+ final Transition t = new Transition(TRANSIT_OPEN, 0 /* flags */, controller, sync) {
@Override
public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) {
latch.countDown();
}
};
+ t.startCollecting(10 /* timeoutMs */);
assertTrue(awaitInWmLock(() -> latch.await(3, TimeUnit.SECONDS)));
}
@@ -491,9 +515,9 @@
mDisplayContent.setLastHasContent();
mDisplayContent.requestChangeTransitionIfNeeded(1 /* any changes */,
null /* displayChange */);
- final FadeRotationAnimationController fadeController =
- mDisplayContent.getFadeRotationAnimationController();
- assertNotNull(fadeController);
+ final AsyncRotationController asyncRotationController =
+ mDisplayContent.getAsyncRotationController();
+ assertNotNull(asyncRotationController);
for (WindowState w : windows) {
w.setOrientationChanging(true);
}
@@ -506,7 +530,7 @@
// Status bar finishes drawing before the start transaction. Its fade-in animation will be
// executed until the transaction is committed, so it is still in target tokens.
statusBar.setOrientationChanging(false);
- assertTrue(fadeController.isTargetToken(statusBar.mToken));
+ assertTrue(asyncRotationController.isTargetToken(statusBar.mToken));
final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
final ArgumentCaptor<TransactionCommittedListener> listenerCaptor =
@@ -516,13 +540,13 @@
verify(startTransaction).addTransactionCommittedListener(any(), listenerCaptor.capture());
// The transaction is committed, so fade-in animation for status bar is consumed.
listenerCaptor.getValue().onTransactionCommitted();
- assertFalse(fadeController.isTargetToken(statusBar.mToken));
+ assertFalse(asyncRotationController.isTargetToken(statusBar.mToken));
// Status bar finishes drawing after the start transaction, so its fade-in animation can
// execute directly.
navBar.setOrientationChanging(false);
- assertFalse(fadeController.isTargetToken(navBar.mToken));
- assertNull(mDisplayContent.getFadeRotationAnimationController());
+ assertFalse(asyncRotationController.isTargetToken(navBar.mToken));
+ assertNull(mDisplayContent.getAsyncRotationController());
}
@Test
@@ -540,10 +564,10 @@
mDisplayContent.setLastHasContent();
mDisplayContent.requestChangeTransitionIfNeeded(anyChanges, null /* displayChange */);
transition.setKnownConfigChanges(mDisplayContent, anyChanges);
- final FadeRotationAnimationController fadeController =
- mDisplayContent.getFadeRotationAnimationController();
- assertNotNull(fadeController);
- assertTrue(fadeController.shouldFreezeInsetsPosition(statusBar));
+ final AsyncRotationController asyncRotationController =
+ mDisplayContent.getAsyncRotationController();
+ assertNotNull(asyncRotationController);
+ assertTrue(asyncRotationController.shouldFreezeInsetsPosition(statusBar));
statusBar.setOrientationChanging(true);
player.startTransition();
@@ -569,7 +593,7 @@
// The controller should capture the draw transaction and merge it when preparing to run
// fade-in animation.
verify(mDisplayContent.getPendingTransaction()).merge(eq(postDrawTransaction));
- assertNull(mDisplayContent.getFadeRotationAnimationController());
+ assertNull(mDisplayContent.getAsyncRotationController());
}
@Test
@@ -578,17 +602,15 @@
final TransitionController controller = new TransitionController(mAtm, snapshotController);
final ITransitionPlayer player = new ITransitionPlayer.Default();
controller.registerTransitionPlayer(player, null /* appThread */);
- ITaskOrganizer mockOrg = mock(ITaskOrganizer.class);
final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
// Start out with task2 visible and set up a transition that closes task2 and opens task1
final Task task1 = createTask(mDisplayContent);
- task1.mTaskOrganizer = mockOrg;
final ActivityRecord activity1 = createActivityRecord(task1);
activity1.mVisibleRequested = false;
activity1.setVisible(false);
final Task task2 = createTask(mDisplayContent);
- task2.mTaskOrganizer = mockOrg;
+ makeTaskOrganized(task1, task2);
final ActivityRecord activity2 = createActivityRecord(task1);
activity2.mVisibleRequested = true;
activity2.setVisible(true);
@@ -644,17 +666,15 @@
final TransitionController controller = new TransitionController(mAtm, snapshotController);
final ITransitionPlayer player = new ITransitionPlayer.Default();
controller.registerTransitionPlayer(player, null /* appThread */);
- ITaskOrganizer mockOrg = mock(ITaskOrganizer.class);
final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
// Start out with task2 visible and set up a transition that closes task2 and opens task1
final Task task1 = createTask(mDisplayContent);
- task1.mTaskOrganizer = mockOrg;
final ActivityRecord activity1 = createActivityRecord(task1);
activity1.mVisibleRequested = false;
activity1.setVisible(false);
final Task task2 = createTask(mDisplayContent);
- task2.mTaskOrganizer = mockOrg;
+ makeTaskOrganized(task1, task2);
final ActivityRecord activity2 = createActivityRecord(task2);
activity2.mVisibleRequested = true;
activity2.setVisible(true);
@@ -703,6 +723,24 @@
verify(snapshotController, times(1)).recordTaskSnapshot(eq(task1), eq(false));
}
+ private static void makeTaskOrganized(Task... tasks) {
+ final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
+ for (Task t : tasks) {
+ t.mTaskOrganizer = organizer;
+ }
+ }
+
+ private static void makeDisplayAreaOrganized(WindowContainer<?> from,
+ WindowContainer<?> end) {
+ final IDisplayAreaOrganizer organizer = mock(IDisplayAreaOrganizer.class);
+ while (from != null && from != end) {
+ if (from.asDisplayArea() != null) {
+ from.asDisplayArea().mOrganizer = organizer;
+ }
+ from = from.getParent();
+ }
+ }
+
/** Fill the change map with all the parents of top. Change maps are usually fully populated */
private static void fillChangeMap(ArrayMap<WindowContainer, Transition.ChangeInfo> changes,
WindowContainer top) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index caaf4e4..1f68608 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -20,6 +20,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -116,6 +117,7 @@
WindowManager.LayoutParams attrs = wallpaperWindow.getAttrs();
Rect bounds = dc.getBounds();
+ int displayWidth = dc.getBounds().width();
int displayHeight = dc.getBounds().height();
// Use a wallpaper with a different ratio than the display
@@ -123,20 +125,18 @@
int wallpaperHeight = (int) (bounds.height() * 1.10);
// Simulate what would be done on the client's side
- attrs.width = wallpaperWidth;
- attrs.height = wallpaperHeight;
- attrs.flags |= FLAG_LAYOUT_NO_LIMITS;
+ final float layoutScale = Math.max(
+ displayWidth / (float) wallpaperWidth, displayHeight / (float) wallpaperHeight);
+ attrs.width = (int) (wallpaperWidth * layoutScale + .5f);
+ attrs.height = (int) (wallpaperHeight * layoutScale + .5f);
+ attrs.flags |= FLAG_LAYOUT_NO_LIMITS | FLAG_SCALED;
attrs.gravity = Gravity.TOP | Gravity.LEFT;
wallpaperWindow.getWindowFrames().mParentFrame.set(dc.getBounds());
- // Calling layoutWindowLw a first time, so adjustWindowParams gets the correct data
- dc.getDisplayPolicy().layoutWindowLw(wallpaperWindow, null, dc.mDisplayFrames);
-
- wallpaperWindowToken.adjustWindowParams(wallpaperWindow, attrs);
dc.getDisplayPolicy().layoutWindowLw(wallpaperWindow, null, dc.mDisplayFrames);
assertEquals(Configuration.ORIENTATION_PORTRAIT, dc.getConfiguration().orientation);
- int expectedWidth = (int) (wallpaperWidth * (displayHeight / (double) wallpaperHeight));
+ int expectedWidth = (int) (wallpaperWidth * layoutScale + .5f);
// Check that the wallpaper is correctly scaled
assertEquals(expectedWidth, wallpaperWindow.getFrame().width());
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 62c1067..4095728 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -883,7 +883,8 @@
/** Sets the default minimum task size to 1 so that tests can use small task sizes */
public void removeGlobalMinSizeRestriction() {
- mAtm.mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp = 1;
+ mAtm.mRootWindowContainer.forAllDisplays(
+ displayContent -> displayContent.mMinSizeOfResizeableTaskDp = 1);
}
/** Mocks the behavior of taking a snapshot. */
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/DisplayRotationUtilTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/DisplayRotationUtilTest.java
deleted file mode 100644
index 926153d..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/utils/DisplayRotationUtilTest.java
+++ /dev/null
@@ -1,163 +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.wm.utils;
-
-import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM;
-import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
-import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT;
-import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
-
-import static com.android.server.wm.utils.DisplayRotationUtil.getBoundIndexFromRotation;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertThat;
-
-import android.graphics.Rect;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-
-/**
- * Tests for {@link DisplayRotationUtil}
- *
- * Build/Install/Run:
- * atest WmTests:DisplayRotationUtilTest
- */
-@SmallTest
-@Presubmit
-public class DisplayRotationUtilTest {
- private static final Rect ZERO_RECT = new Rect();
-
- @Test
- public void testGetBoundIndexFromRotation_rot0() {
- assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_LEFT, ROTATION_0),
- equalTo(BOUNDS_POSITION_LEFT));
- assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_TOP, ROTATION_0),
- equalTo(BOUNDS_POSITION_TOP));
- assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_RIGHT, ROTATION_0),
- equalTo(BOUNDS_POSITION_RIGHT));
- assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_BOTTOM, ROTATION_0),
- equalTo(BOUNDS_POSITION_BOTTOM));
- }
-
- @Test
- public void testGetBoundIndexFromRotation_rot90() {
- assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_LEFT, ROTATION_90),
- equalTo(BOUNDS_POSITION_BOTTOM));
- assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_TOP, ROTATION_90),
- equalTo(BOUNDS_POSITION_LEFT));
- assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_RIGHT, ROTATION_90),
- equalTo(BOUNDS_POSITION_TOP));
- assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_BOTTOM, ROTATION_90),
- equalTo(BOUNDS_POSITION_RIGHT));
- }
-
- @Test
- public void testGetBoundIndexFromRotation_rot180() {
- assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_LEFT, ROTATION_180),
- equalTo(BOUNDS_POSITION_RIGHT));
- assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_TOP, ROTATION_180),
- equalTo(BOUNDS_POSITION_BOTTOM));
- assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_RIGHT, ROTATION_180),
- equalTo(BOUNDS_POSITION_LEFT));
- assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_BOTTOM, ROTATION_180),
- equalTo(BOUNDS_POSITION_TOP));
- }
-
- @Test
- public void testGetBoundIndexFromRotation_rot270() {
- assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_LEFT, ROTATION_270),
- equalTo(BOUNDS_POSITION_TOP));
- assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_TOP, ROTATION_270),
- equalTo(BOUNDS_POSITION_RIGHT));
- assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_RIGHT, ROTATION_270),
- equalTo(BOUNDS_POSITION_BOTTOM));
- assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_BOTTOM, ROTATION_270),
- equalTo(BOUNDS_POSITION_LEFT));
-
- }
-
- @Test
- public void testGetRotatedBounds_top_rot0() {
- DisplayRotationUtil util = new DisplayRotationUtil();
- Rect[] bounds = new Rect[] {ZERO_RECT, new Rect(50, 0, 150, 10), ZERO_RECT, ZERO_RECT};
- assertThat(util.getRotatedBounds(bounds, ROTATION_0, 200, 300),
- equalTo(bounds));
- }
-
- @Test
- public void testGetRotatedBounds_top_rot90() {
- DisplayRotationUtil util = new DisplayRotationUtil();
- Rect[] bounds = new Rect[] {ZERO_RECT, new Rect(50, 0, 150, 10), ZERO_RECT, ZERO_RECT};
- assertThat(util.getRotatedBounds(bounds, ROTATION_90, 200, 300),
- equalTo(new Rect[] {new Rect(0, 50, 10, 150), ZERO_RECT, ZERO_RECT, ZERO_RECT}));
- }
-
- @Test
- public void testGetRotatedBounds_top_rot180() {
- DisplayRotationUtil util = new DisplayRotationUtil();
- Rect[] bounds = new Rect[] {ZERO_RECT, new Rect(50, 0, 150, 10), ZERO_RECT, ZERO_RECT};
- assertThat(util.getRotatedBounds(bounds, ROTATION_180, 200, 300),
- equalTo(new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, new Rect(50, 290, 150, 300)}));
- }
-
- @Test
- public void testGetRotatedBounds_top_rot270() {
- DisplayRotationUtil util = new DisplayRotationUtil();
- Rect[] bounds = new Rect[] {ZERO_RECT, new Rect(50, 0, 150, 10), ZERO_RECT, ZERO_RECT};
- assertThat(util.getRotatedBounds(bounds, ROTATION_270, 200, 300),
- equalTo(new Rect[] {ZERO_RECT, ZERO_RECT, new Rect(290, 50, 300, 150), ZERO_RECT}));
- }
-
- @Test
- public void testGetRotatedBounds_left_rot0() {
- DisplayRotationUtil util = new DisplayRotationUtil();
- Rect[] bounds = new Rect[] {new Rect(0, 50, 10, 150), ZERO_RECT, ZERO_RECT, ZERO_RECT};
- assertThat(util.getRotatedBounds(bounds, ROTATION_0, 300, 200),
- equalTo(bounds));
- }
-
- @Test
- public void testGetRotatedBounds_left_rot90() {
- DisplayRotationUtil util = new DisplayRotationUtil();
- Rect[] bounds = new Rect[] {new Rect(0, 50, 10, 150), ZERO_RECT, ZERO_RECT, ZERO_RECT};
- assertThat(util.getRotatedBounds(bounds, ROTATION_90, 300, 200),
- equalTo(new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, new Rect(50, 290, 150, 300)}));
- }
-
- @Test
- public void testGetRotatedBounds_left_rot180() {
- DisplayRotationUtil util = new DisplayRotationUtil();
- Rect[] bounds = new Rect[] {new Rect(0, 50, 10, 150), ZERO_RECT, ZERO_RECT, ZERO_RECT};
- assertThat(util.getRotatedBounds(bounds, ROTATION_180, 300, 200),
- equalTo(new Rect[] {ZERO_RECT, ZERO_RECT, new Rect(290, 50, 300, 150), ZERO_RECT}));
- }
-
- @Test
- public void testGetRotatedBounds_left_rot270() {
- DisplayRotationUtil util = new DisplayRotationUtil();
- Rect[] bounds = new Rect[] {new Rect(0, 50, 10, 150), ZERO_RECT, ZERO_RECT, ZERO_RECT};
- assertThat(util.getRotatedBounds(bounds, ROTATION_270, 300, 200),
- equalTo(new Rect[] {ZERO_RECT, new Rect(50, 0, 150, 10), ZERO_RECT, ZERO_RECT}));
- }
-}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 997c883..559eb38 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -33,6 +33,7 @@
import android.Manifest;
import android.annotation.CurrentTimeMillisLong;
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -2947,6 +2948,27 @@
@NonNull EstimatedLaunchTimeChangedListener listener) {
UsageStatsService.this.unregisterLaunchTimeChangedListener(listener);
}
+
+ @Override
+ public void reportBroadcastDispatched(int sourceUid, @NonNull String targetPackage,
+ @NonNull UserHandle targetUser, long idForResponseEvent,
+ @ElapsedRealtimeLong long timestampMs) {
+ }
+
+ @Override
+ public void reportNotificationPosted(@NonNull String packageName,
+ @NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs) {
+ }
+
+ @Override
+ public void reportNotificationUpdated(@NonNull String packageName,
+ @NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs) {
+ }
+
+ @Override
+ public void reportNotificationRemoved(@NonNull String packageName,
+ @NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs) {
+ }
}
private class MyPackageMonitor extends PackageMonitor {
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 98173ad..65b79bf 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -384,6 +384,46 @@
}
/**
+ * Enables USB data when disabled due to {@link UsbPortStatus#USB_DATA_STATUS_DISABLED_DOCK}
+ */
+ public void enableUsbDataWhileDocked(@NonNull String portId, long transactionId,
+ IUsbOperationInternal callback, IndentingPrintWriter pw) {
+ Objects.requireNonNull(portId);
+ final PortInfo portInfo = mPorts.get(portId);
+ if (portInfo == null) {
+ logAndPrint(Log.ERROR, pw, "enableUsbDataWhileDocked: No such port: " + portId
+ + " opId:" + transactionId);
+ try {
+ if (callback != null) {
+ callback.onOperationComplete(USB_OPERATION_ERROR_PORT_MISMATCH);
+ }
+ } catch (RemoteException e) {
+ logAndPrintException(pw,
+ "enableUsbDataWhileDocked: Failed to call OperationComplete. opId:"
+ + transactionId, e);
+ }
+ return;
+ }
+
+ try {
+ try {
+ mUsbPortHal.enableUsbDataWhileDocked(portId, transactionId, callback);
+ } catch (Exception e) {
+ logAndPrintException(pw,
+ "enableUsbDataWhileDocked: Failed to limit power transfer. opId:"
+ + transactionId , e);
+ if (callback != null) {
+ callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+ }
+ }
+ } catch (RemoteException e) {
+ logAndPrintException(pw,
+ "enableUsbDataWhileDocked:Failed to call onOperationComplete. opId:"
+ + transactionId, e);
+ }
+ }
+
+ /**
* Enable/disable the USB data signaling
*
* @param enable enable or disable USB data signaling
@@ -759,8 +799,9 @@
portInfo.contaminantProtectionStatus,
portInfo.supportsEnableContaminantPresenceDetection,
portInfo.contaminantDetectionStatus,
- portInfo.usbDataEnabled,
- portInfo.powerTransferLimited, pw);
+ portInfo.usbDataStatus,
+ portInfo.powerTransferLimited,
+ portInfo.powerBrickStatus, pw);
}
} else {
for (RawPortInfo currentPortInfo : newPortInfo) {
@@ -773,8 +814,9 @@
currentPortInfo.contaminantProtectionStatus,
currentPortInfo.supportsEnableContaminantPresenceDetection,
currentPortInfo.contaminantDetectionStatus,
- currentPortInfo.usbDataEnabled,
- currentPortInfo.powerTransferLimited, pw);
+ currentPortInfo.usbDataStatus,
+ currentPortInfo.powerTransferLimited,
+ currentPortInfo.powerBrickStatus, pw);
}
}
@@ -810,8 +852,9 @@
int contaminantProtectionStatus,
boolean supportsEnableContaminantPresenceDetection,
int contaminantDetectionStatus,
- boolean usbDataEnabled,
+ int[] usbDataStatus,
boolean powerTransferLimited,
+ int powerBrickStatus,
IndentingPrintWriter pw) {
// Only allow mode switch capability for dual role ports.
// Validate that the current mode matches the supported modes we expect.
@@ -870,8 +913,8 @@
currentPowerRole, canChangePowerRole,
currentDataRole, canChangeDataRole,
supportedRoleCombinations, contaminantProtectionStatus,
- contaminantDetectionStatus, usbDataEnabled,
- powerTransferLimited);
+ contaminantDetectionStatus, usbDataStatus,
+ powerTransferLimited, powerBrickStatus);
mPorts.put(portId, portInfo);
} else {
// Validate that ports aren't changing definition out from under us.
@@ -908,8 +951,8 @@
currentPowerRole, canChangePowerRole,
currentDataRole, canChangeDataRole,
supportedRoleCombinations, contaminantProtectionStatus,
- contaminantDetectionStatus, usbDataEnabled,
- powerTransferLimited)) {
+ contaminantDetectionStatus, usbDataStatus,
+ powerTransferLimited, powerBrickStatus)) {
portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED;
} else {
portInfo.mDisposition = PortInfo.DISPOSITION_READY;
@@ -1135,7 +1178,9 @@
!= supportedRoleCombinations) {
mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
supportedRoleCombinations, UsbPortStatus.CONTAMINANT_PROTECTION_NONE,
- UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED, true, false);
+ UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED,
+ new int[]{UsbPortStatus.USB_DATA_STATUS_UNKNOWN}, false,
+ UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN);
dispositionChanged = true;
}
@@ -1150,12 +1195,31 @@
return dispositionChanged;
}
+ private boolean dataStatusEquals(int[] dataStatusL, int[] dataStatusR) {
+ if (dataStatusL == null && dataStatusR == null) {
+ return true;
+ }
+ if ((dataStatusL == null && dataStatusR != null)
+ || (dataStatusL != null && dataStatusR == null)) {
+ return false;
+ }
+ if (dataStatusL.length != dataStatusR.length) {
+ return false;
+ }
+ for (int i = 0; i < dataStatusL.length; i++) {
+ if (dataStatusL[i] != dataStatusR[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
public boolean setStatus(int currentMode, boolean canChangeMode,
int currentPowerRole, boolean canChangePowerRole,
int currentDataRole, boolean canChangeDataRole,
int supportedRoleCombinations, int contaminantProtectionStatus,
- int contaminantDetectionStatus, boolean usbDataEnabled,
- boolean powerTransferLimited) {
+ int contaminantDetectionStatus, int[] usbDataStatus,
+ boolean powerTransferLimited, int powerBrickStatus) {
boolean dispositionChanged = false;
mCanChangeMode = canChangeMode;
@@ -1171,14 +1235,15 @@
!= contaminantProtectionStatus
|| mUsbPortStatus.getContaminantDetectionStatus()
!= contaminantDetectionStatus
- || mUsbPortStatus.getUsbDataStatus()
- != usbDataEnabled
+ || !dataStatusEquals(mUsbPortStatus.getUsbDataStatus(), usbDataStatus)
|| mUsbPortStatus.isPowerTransferLimited()
- != powerTransferLimited) {
+ != powerTransferLimited
+ || mUsbPortStatus.getPowerBrickStatus()
+ != powerBrickStatus) {
mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
supportedRoleCombinations, contaminantProtectionStatus,
- contaminantDetectionStatus, usbDataEnabled,
- powerTransferLimited);
+ contaminantDetectionStatus, usbDataStatus,
+ powerTransferLimited, powerBrickStatus);
dispositionChanged = true;
}
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 51643e7..88ffc7d61 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -814,6 +814,32 @@
}
@Override
+ public void enableUsbDataWhileDocked(String portId, int operationId,
+ IUsbOperationInternal callback) {
+ Objects.requireNonNull(portId, "enableUsbDataWhileDocked: portId must not be null. opId:"
+ + operationId);
+ Objects.requireNonNull(callback,
+ "enableUsbDataWhileDocked: callback must not be null. opId:"
+ + operationId);
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+ final long ident = Binder.clearCallingIdentity();
+ boolean wait;
+ try {
+ if (mPortManager != null) {
+ mPortManager.enableUsbDataWhileDocked(portId, operationId, callback, null);
+ } else {
+ try {
+ callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "enableUsbData: Failed to call onOperationComplete", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public void setUsbDeviceConnectionHandler(ComponentName usbDeviceConnectionHandler) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
synchronized (mLock) {
diff --git a/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java b/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java
index 8dfc859..dd25620 100644
--- a/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java
+++ b/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java
@@ -37,8 +37,9 @@
public int contaminantProtectionStatus;
public boolean supportsEnableContaminantPresenceDetection;
public int contaminantDetectionStatus;
- public boolean usbDataEnabled;
+ public int[] usbDataStatus;
public boolean powerTransferLimited;
+ public int powerBrickStatus;
public RawPortInfo(String portId, int supportedModes) {
this.portId = portId;
@@ -48,8 +49,9 @@
this.contaminantProtectionStatus = UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
this.supportsEnableContaminantPresenceDetection = false;
this.contaminantDetectionStatus = UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
- this.usbDataEnabled = true;
+ this.usbDataStatus[0] = UsbPortStatus.USB_DATA_STATUS_UNKNOWN;
this.powerTransferLimited = false;
+ this.powerBrickStatus = UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN;
}
public RawPortInfo(String portId, int supportedModes, int supportedContaminantProtectionModes,
@@ -60,8 +62,9 @@
int contaminantProtectionStatus,
boolean supportsEnableContaminantPresenceDetection,
int contaminantDetectionStatus,
- boolean usbDataEnabled,
- boolean powerTransferLimited) {
+ int[] usbDataStatus,
+ boolean powerTransferLimited,
+ int powerBrickStatus) {
this.portId = portId;
this.supportedModes = supportedModes;
this.supportedContaminantProtectionModes = supportedContaminantProtectionModes;
@@ -77,8 +80,9 @@
this.supportsEnableContaminantPresenceDetection =
supportsEnableContaminantPresenceDetection;
this.contaminantDetectionStatus = contaminantDetectionStatus;
- this.usbDataEnabled = usbDataEnabled;
+ this.usbDataStatus = usbDataStatus;
this.powerTransferLimited = powerTransferLimited;
+ this.powerBrickStatus = powerBrickStatus;
}
@Override
@@ -101,8 +105,10 @@
dest.writeInt(contaminantProtectionStatus);
dest.writeBoolean(supportsEnableContaminantPresenceDetection);
dest.writeInt(contaminantDetectionStatus);
- dest.writeBoolean(usbDataEnabled);
+ dest.writeInt(usbDataStatus.length);
+ dest.writeIntArray(usbDataStatus);
dest.writeBoolean(powerTransferLimited);
+ dest.writeInt(powerBrickStatus);
}
public static final Parcelable.Creator<RawPortInfo> CREATOR =
@@ -122,8 +128,10 @@
int contaminantProtectionStatus = in.readInt();
boolean supportsEnableContaminantPresenceDetection = in.readBoolean();
int contaminantDetectionStatus = in.readInt();
- boolean usbDataEnabled = in.readBoolean();
+ int[] usbDataStatus = new int[in.readInt()];
+ in.readIntArray(usbDataStatus);
boolean powerTransferLimited = in.readBoolean();
+ int powerBrickStatus = in.readInt();
return new RawPortInfo(id, supportedModes,
supportedContaminantProtectionModes, currentMode, canChangeMode,
currentPowerRole, canChangePowerRole,
@@ -131,8 +139,8 @@
supportsEnableContaminantPresenceProtection,
contaminantProtectionStatus,
supportsEnableContaminantPresenceDetection,
- contaminantDetectionStatus, usbDataEnabled,
- powerTransferLimited);
+ contaminantDetectionStatus, usbDataStatus,
+ powerTransferLimited, powerBrickStatus);
}
@Override
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
index 2460242..5582600 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
@@ -322,6 +322,48 @@
}
}
+ @Override
+ public void enableUsbDataWhileDocked(String portName, long operationID,
+ IUsbOperationInternal callback) {
+ Objects.requireNonNull(portName);
+ long key = operationID;
+ synchronized (mLock) {
+ try {
+ if (mProxy == null) {
+ logAndPrint(Log.ERROR, mPw,
+ "enableUsbDataWhileDocked: Proxy is null. Retry !opID:"
+ + operationID);
+ callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+ return;
+ }
+ while (sCallbacks.get(key) != null) {
+ key = ThreadLocalRandom.current().nextInt();
+ }
+ if (key != operationID) {
+ logAndPrint(Log.INFO, mPw,
+ "enableUsbDataWhileDocked: operationID exists ! opID:"
+ + operationID + " key:" + key);
+ }
+ try {
+ sCallbacks.put(key, callback);
+ mProxy.enableUsbDataWhileDocked(portName, key);
+ } catch (RemoteException e) {
+ logAndPrintException(mPw,
+ "enableUsbDataWhileDocked: error while invoking hal"
+ + "portID=" + portName + " opID:" + operationID, e);
+ if (callback != null) {
+ callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+ }
+ sCallbacks.remove(key);
+ }
+ } catch (RemoteException e) {
+ logAndPrintException(mPw,
+ "enableUsbDataWhileDocked: Failed to call onOperationComplete portID="
+ + portName + " opID:" + operationID, e);
+ }
+ }
+ }
+
private static class HALCallback extends IUsbCallback.Stub {
public IndentingPrintWriter mPw;
public UsbPortManager mPortManager;
@@ -403,6 +445,14 @@
return supportedContaminantProtectionModes;
}
+ private int[] toIntArray(byte[] input) {
+ int[] output = new int[input.length];
+ for (int i = 0; i < input.length; i++) {
+ output[i] = input[i];
+ }
+ return output;
+ }
+
@Override
public void notifyPortStatusChange(
android.hardware.usb.PortStatus[] currentPortStatus, int retval) {
@@ -434,8 +484,9 @@
toContaminantProtectionStatus(current.contaminantProtectionStatus),
current.supportsEnableContaminantPresenceDetection,
current.contaminantDetectionStatus,
- current.usbDataEnabled,
- current.powerTransferLimited);
+ toIntArray(current.usbDataStatus),
+ current.powerTransferLimited,
+ current.powerBrickStatus);
newPortInfo.add(temp);
UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback AIDL V1: "
+ current.portName);
@@ -529,5 +580,30 @@
e);
}
}
+
+ @Override
+ public void notifyEnableUsbDataWhileDockedStatus(String portName, int retval,
+ long operationID) {
+ if (retval == Status.SUCCESS) {
+ UsbPortManager.logAndPrint(Log.INFO, mPw, portName + ": opID:"
+ + operationID + " successful");
+ } else {
+ UsbPortManager.logAndPrint(Log.ERROR, mPw, portName
+ + "notifyEnableUsbDataWhileDockedStatus: opID:"
+ + operationID + " failed. err:" + retval);
+ }
+ try {
+ IUsbOperationInternal callback = sCallbacks.get(operationID);
+ if (callback != null) {
+ sCallbacks.get(operationID).onOperationComplete(retval == Status.SUCCESS
+ ? USB_OPERATION_SUCCESS
+ : USB_OPERATION_ERROR_INTERNAL);
+ }
+ } catch (RemoteException e) {
+ logAndPrintException(mPw,
+ "notifyEnableUsbDataWhileDockedStatus: Failed to call onOperationComplete",
+ e);
+ }
+ }
}
}
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java
index 90c8909..abfdd6f 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java
@@ -158,16 +158,28 @@
* completion.
* @param callback callback object to be invoked when the operation is complete.
* @return True when the operation is asynchronous. The caller of
- * {@link UsbOperationCallbackInternal} must therefore call
- * {@link UsbOperationCallbackInternal#waitForOperationComplete} for processing
+ * {@link UsbOperationInternal} must therefore call
+ * {@link UsbOperationInternal#waitForOperationComplete} for processing
* the result.
* False when the operation is synchronous. Caller can proceed reading the result
- * through {@link UsbOperationCallbackInternal#getStatus}
+ * through {@link UsbOperationInternal#getStatus}
*/
public boolean enableUsbData(String portName, boolean enable, long transactionId,
IUsbOperationInternal callback);
/**
+ * Invoked to enable UsbData when disabled due to docking event.
+ *
+ * @param portName Port Identifier.
+ * @param transactionId Used for tracking the current request and is passed down to the HAL
+ * implementation as needed.
+ * @param callback callback object to be invoked to invoke the status of the operation upon
+ * completion.
+ */
+ public void enableUsbDataWhileDocked(String portName, long transactionId,
+ IUsbOperationInternal callback);
+
+ /**
* Invoked to enableLimitPowerTransfer on the specified port.
*
* @param portName Port Identifier.
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
index 8a0370a..c1d7635 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
@@ -30,8 +30,12 @@
import static android.hardware.usb.UsbPortStatus.MODE_DFP;
import static android.hardware.usb.UsbPortStatus.MODE_DUAL;
import static android.hardware.usb.UsbPortStatus.MODE_UFP;
+import static android.hardware.usb.UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN;
import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK;
import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE;
+import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_DISABLED_FORCE;
+import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_UNKNOWN;
+
import static com.android.server.usb.UsbPortManager.logAndPrint;
import static com.android.server.usb.UsbPortManager.logAndPrintException;
@@ -40,6 +44,7 @@
import android.hardware.usb.IUsbOperationInternal;
import android.hardware.usb.UsbManager.UsbHalVersion;
import android.hardware.usb.UsbPort;
+import android.hardware.usb.UsbPortStatus;
import android.hardware.usb.V1_0.IUsb;
import android.hardware.usb.V1_0.PortRoleType;
import android.hardware.usb.V1_0.Status;
@@ -80,7 +85,7 @@
private HALCallback mHALCallback;
private boolean mSystemReady;
// Workaround since HIDL HAL versions report UsbDataEnabled status in UsbPortStatus;
- private static boolean sUsbDataEnabled = true;
+ private static int sUsbDataStatus = USB_DATA_STATUS_UNKNOWN;
public @UsbHalVersion int getUsbHalVersion() throws RemoteException {
int version;
@@ -282,6 +287,17 @@
}
@Override
+ public void enableUsbDataWhileDocked(String portName, long transactionId,
+ IUsbOperationInternal callback) {
+ /* Not supported in HIDL hals*/
+ try {
+ callback.onOperationComplete(USB_OPERATION_ERROR_NOT_SUPPORTED);
+ } catch (RemoteException e) {
+ logAndPrintException(mPw, "Failed to call onOperationComplete", e);
+ }
+ }
+
+ @Override
public void switchDataRole(String portId, @HalUsbDataRole int newDataRole, long transactionId) {
synchronized (mLock) {
if (mProxy == null) {
@@ -332,7 +348,7 @@
android.hardware.usb.V1_3.IUsb proxy
= android.hardware.usb.V1_3.IUsb.castFrom(mProxy);
success = proxy.enableUsbDataSignal(enable);
- } catch (RemoteException e) {
+ } catch (RemoteException e) {
logAndPrintException(mPw, "Failed enableUsbData: opId:" + transactionId
+ " portId=" + portName , e);
try {
@@ -346,9 +362,8 @@
}
}
if (success) {
- sUsbDataEnabled = enable;
+ sUsbDataStatus = enable ? USB_DATA_STATUS_UNKNOWN : USB_DATA_STATUS_DISABLED_FORCE;
}
-
try {
callback.onOperationComplete(success
? USB_OPERATION_SUCCESS
@@ -393,8 +408,8 @@
current.canChangePowerRole,
current.currentDataRole, current.canChangeDataRole,
false, CONTAMINANT_PROTECTION_NONE,
- false, CONTAMINANT_DETECTION_NOT_SUPPORTED, sUsbDataEnabled,
- false);
+ false, CONTAMINANT_DETECTION_NOT_SUPPORTED, new int[sUsbDataStatus],
+ false, POWER_BRICK_STATUS_UNKNOWN);
newPortInfo.add(temp);
UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_0: "
+ current.portName);
@@ -427,8 +442,8 @@
current.status.canChangePowerRole,
current.status.currentDataRole, current.status.canChangeDataRole,
false, CONTAMINANT_PROTECTION_NONE,
- false, CONTAMINANT_DETECTION_NOT_SUPPORTED, sUsbDataEnabled,
- false);
+ false, CONTAMINANT_DETECTION_NOT_SUPPORTED, new int[sUsbDataStatus],
+ false, POWER_BRICK_STATUS_UNKNOWN);
newPortInfo.add(temp);
UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_1: "
+ current.status.portName);
@@ -465,8 +480,8 @@
current.contaminantProtectionStatus,
current.supportsEnableContaminantPresenceDetection,
current.contaminantDetectionStatus,
- sUsbDataEnabled,
- false);
+ new int[sUsbDataStatus],
+ false, POWER_BRICK_STATUS_UNKNOWN);
newPortInfo.add(temp);
UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_2: "
+ current.status_1_1.status.portName);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index ce9530c..02c1379 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -43,6 +43,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -571,7 +572,7 @@
public static final int CAPABILITY_REMOTE_PARTY_SUPPORTS_RTT = 0x10000000;
//******************************************************************************************
- // Next CAPABILITY value: 0x20000000
+ // Next CAPABILITY value: 0x40000000
//******************************************************************************************
/**
@@ -733,6 +734,8 @@
private final String mContactDisplayName;
private final @CallDirection int mCallDirection;
private final @Connection.VerificationStatus int mCallerNumberVerificationStatus;
+ private final CallEndpoint mActiveCallEndpoint;
+ private final Set<CallEndpoint> mAvailableCallEndpoint;
/**
* Whether the supplied capabilities supports the specified capability.
@@ -1116,32 +1119,52 @@
return mCallerNumberVerificationStatus;
}
+ /**
+ * Return set of available {@link CallEndpoint} which can be used to push or answer this
+ * call via {@link #pushCall(CallEndpoint)} or {@link #answerCall(CallEndpoint, int)}.
+ * @return Set of available call endpoints.
+ */
+ public @NonNull Set<CallEndpoint> getAvailableCallEndpoints() {
+ return mAvailableCallEndpoint;
+ }
+
+ /**
+ * Return the {@link CallEndpoint} which is currently active for a call. If the call does
+ * not take place via any {@link CallEndpoint}, return {@code null}.
+ * @return Current active endpoint.
+ */
+ public @Nullable CallEndpoint getActiveCallEndpoint() {
+ return mActiveCallEndpoint;
+ }
+
@Override
public boolean equals(Object o) {
if (o instanceof Details) {
Details d = (Details) o;
return
- Objects.equals(mState, d.mState) &&
- Objects.equals(mHandle, d.mHandle) &&
- Objects.equals(mHandlePresentation, d.mHandlePresentation) &&
- Objects.equals(mCallerDisplayName, d.mCallerDisplayName) &&
- Objects.equals(mCallerDisplayNamePresentation,
- d.mCallerDisplayNamePresentation) &&
- Objects.equals(mAccountHandle, d.mAccountHandle) &&
- Objects.equals(mCallCapabilities, d.mCallCapabilities) &&
- Objects.equals(mCallProperties, d.mCallProperties) &&
- Objects.equals(mDisconnectCause, d.mDisconnectCause) &&
- Objects.equals(mConnectTimeMillis, d.mConnectTimeMillis) &&
- Objects.equals(mGatewayInfo, d.mGatewayInfo) &&
- Objects.equals(mVideoState, d.mVideoState) &&
- Objects.equals(mStatusHints, d.mStatusHints) &&
- areBundlesEqual(mExtras, d.mExtras) &&
- areBundlesEqual(mIntentExtras, d.mIntentExtras) &&
- Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) &&
- Objects.equals(mContactDisplayName, d.mContactDisplayName) &&
- Objects.equals(mCallDirection, d.mCallDirection) &&
- Objects.equals(mCallerNumberVerificationStatus,
- d.mCallerNumberVerificationStatus);
+ Objects.equals(mState, d.mState)
+ && Objects.equals(mHandle, d.mHandle)
+ && Objects.equals(mHandlePresentation, d.mHandlePresentation)
+ && Objects.equals(mCallerDisplayName, d.mCallerDisplayName)
+ && Objects.equals(mCallerDisplayNamePresentation,
+ d.mCallerDisplayNamePresentation)
+ && Objects.equals(mAccountHandle, d.mAccountHandle)
+ && Objects.equals(mCallCapabilities, d.mCallCapabilities)
+ && Objects.equals(mCallProperties, d.mCallProperties)
+ && Objects.equals(mDisconnectCause, d.mDisconnectCause)
+ && Objects.equals(mConnectTimeMillis, d.mConnectTimeMillis)
+ && Objects.equals(mGatewayInfo, d.mGatewayInfo)
+ && Objects.equals(mVideoState, d.mVideoState)
+ && Objects.equals(mStatusHints, d.mStatusHints)
+ && areBundlesEqual(mExtras, d.mExtras)
+ && areBundlesEqual(mIntentExtras, d.mIntentExtras)
+ && Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis)
+ && Objects.equals(mContactDisplayName, d.mContactDisplayName)
+ && Objects.equals(mCallDirection, d.mCallDirection)
+ && Objects.equals(mCallerNumberVerificationStatus,
+ d.mCallerNumberVerificationStatus)
+ && Objects.equals(mActiveCallEndpoint, d.mActiveCallEndpoint)
+ && Objects.equals(mAvailableCallEndpoint, d.mAvailableCallEndpoint);
}
return false;
}
@@ -1190,7 +1213,9 @@
long creationTimeMillis,
String contactDisplayName,
int callDirection,
- int callerNumberVerificationStatus) {
+ int callerNumberVerificationStatus,
+ CallEndpoint activeCallEndpoint,
+ Set<CallEndpoint> availableCallEndpoints) {
mState = state;
mTelecomCallId = telecomCallId;
mHandle = handle;
@@ -1211,6 +1236,8 @@
mContactDisplayName = contactDisplayName;
mCallDirection = callDirection;
mCallerNumberVerificationStatus = callerNumberVerificationStatus;
+ mActiveCallEndpoint = activeCallEndpoint;
+ mAvailableCallEndpoint = availableCallEndpoints;
}
/** {@hide} */
@@ -1235,7 +1262,9 @@
parcelableCall.getCreationTimeMillis(),
parcelableCall.getContactDisplayName(),
parcelableCall.getCallDirection(),
- parcelableCall.getCallerNumberVerificationStatus());
+ parcelableCall.getCallerNumberVerificationStatus(),
+ parcelableCall.getActiveCallEndpoint(),
+ parcelableCall.getAvailableCallEndpoints());
}
@Override
@@ -1257,6 +1286,10 @@
sb.append(capabilitiesToString(mCallCapabilities));
sb.append(", props: ");
sb.append(propertiesToString(mCallProperties));
+ sb.append(", activeEndpoint: ");
+ sb.append(mActiveCallEndpoint);
+ sb.append(", availableEndpoints: ");
+ sb.append(mAvailableCallEndpoint);
sb.append("]");
return sb.toString();
}
@@ -1356,6 +1389,121 @@
public static final int HANDOVER_FAILURE_UNKNOWN = 5;
/**
+ * @hide
+ */
+ @IntDef(prefix = { "PUSH_FAILED_" },
+ value = {PUSH_FAILED_UNKNOWN_REASON, PUSH_FAILED_ENDPOINT_UNAVAILABLE,
+ PUSH_FAILED_ENDPOINT_TIMEOUT, PUSH_FAILED_ENDPOINT_REJECTED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PushFailedReason {}
+
+ /**
+ * Answer failure reason returned via {@link #onAnswerFailed(CallEndpoint, int)} when a push
+ * fails due to unknown reason.
+ * <p>
+ * For more information on push call, see {@link #pushCall(CallEndpoint)}.
+ */
+ public static final int PUSH_FAILED_UNKNOWN_REASON = 0;
+
+ /**
+ * Push failure reason returned via {@link #onCallPushFailed(CallEndpoint, int)} when a push
+ * fails due to requested endpoint is unavailable.
+ * <p>
+ * For more information on push call, see {@link #pushCall(CallEndpoint)}.
+ */
+ public static final int PUSH_FAILED_ENDPOINT_UNAVAILABLE = 1;
+
+ /**
+ * Push failure reason returned via {@link #onCallPushFailed(CallEndpoint, int)} when a push
+ * fails due to requested endpoint takes too long to handle the request.
+ * <p>
+ * For more information on push call, see {@link #pushCall(CallEndpoint)}.
+ */
+ public static final int PUSH_FAILED_ENDPOINT_TIMEOUT = 2;
+
+ /**
+ * Push failure reason returned via {@link #onCallPushFailed(CallEndpoint, int)} when a push
+ * fails due to endpoint rejected the request.
+ * <p>
+ * For more information on push call, see {@link #pushCall(CallEndpoint)}.
+ */
+ public static final int PUSH_FAILED_ENDPOINT_REJECTED = 3;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "ANSWER_FAILED_" },
+ value = {ANSWER_FAILED_UNKNOWN_REASON, ANSWER_FAILED_ENDPOINT_UNAVAILABLE,
+ ANSWER_FAILED_ENDPOINT_TIMEOUT, ANSWER_FAILED_ENDPOINT_REJECTED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AnswerFailedReason {}
+
+ /**
+ * Answer failure reason returned via {@link #onAnswerFailed(CallEndpoint, int)} when it
+ * fails due to unknown reason.
+ * <p>
+ * For more information on answer call, see {@link #answerCall(CallEndpoint, int)}.
+ */
+ public static final int ANSWER_FAILED_UNKNOWN_REASON = 0;
+
+ /**
+ * Answer failure reason returned via {@link #onAnswerFailed(CallEndpoint, int)} when it
+ * fails due to requested endpoint is unavailable.
+ * <p>
+ * For more information on answer call, see {@link #answerCall(CallEndpoint, int)}.
+ */
+ public static final int ANSWER_FAILED_ENDPOINT_UNAVAILABLE = 1;
+
+ /**
+ * Answer failure reason returned via {@link #onAnswerFailed(CallEndpoint, int)} when it
+ * fails due to requested endpoint takes too long to handle the request.
+ * <p>
+ * For more information on answer call, see {@link #answerCall(CallEndpoint, int)}.
+ */
+ public static final int ANSWER_FAILED_ENDPOINT_TIMEOUT = 2;
+
+ /**
+ * Answer failure reason returned via {@link #onAnswerFailed(CallEndpoint, int)} when it
+ * fails due to endpoint rejected the request.
+ * <p>
+ * For more information on answer call, see {@link #answerCall(CallEndpoint, int)}.
+ */
+ public static final int ANSWER_FAILED_ENDPOINT_REJECTED = 3;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "PULL_FAILED_" },
+ value = {PULL_FAILED_UNKNOWN_REASON, PULL_FAILED_ENDPOINT_TIMEOUT,
+ PULL_FAILED_ENDPOINT_REJECTED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PullFailedReason {}
+
+ /**
+ * Pull failure reason returned via {@link #onCallPullFailed(int)} when it fails due to
+ * unknown reason.
+ * <p>
+ * For more information on pull call, see {@link #pullCall()}.
+ */
+ public static final int PULL_FAILED_UNKNOWN_REASON = 0;
+
+ /**
+ * Pull failure reason returned via {@link #onCallPullFailed(int)} when it fails due to
+ * requested endpoint takes too long to handle the request.
+ * <p>
+ * For more information on pull call, see {@link #pullCall()}.
+ */
+ public static final int PULL_FAILED_ENDPOINT_TIMEOUT = 1;
+
+ /**
+ * Pull failure reason returned via {@link #onCallPullFailed(int)} when it fails due to
+ * endpoint rejected the request.
+ * <p>
+ * For more information on pull call, see {@link #pullCall()}.
+ */
+ public static final int PULL_FAILED_ENDPOINT_REJECTED = 2;
+
+ /**
* Invoked when the state of this {@code Call} has changed. See {@link #getState()}.
*
* @param call The {@code Call} invoking this method.
@@ -1515,6 +1663,31 @@
* @param failureReason Error reason for failure.
*/
public void onHandoverFailed(Call call, @HandoverFailureErrors int failureReason) {}
+
+ /**
+ * Invoked when call push request via {@link #pushCall(CallEndpoint)} has failed.
+ *
+ * @param endpoint The endpoint requested to push the call to.
+ * @param reason Failed reason.
+ */
+ public void onCallPushFailed(@NonNull CallEndpoint endpoint, @PushFailedReason int reason)
+ {}
+
+ /**
+ * Invoked when answer call request via {@link #answerCall(CallEndpoint, int)} has failed.
+ *
+ * @param endpoint The endpoint requested to answer the call.
+ * @param reason Failed reason
+ */
+ public void onAnswerFailed(@NonNull CallEndpoint endpoint, @AnswerFailedReason int reason)
+ {}
+
+ /**
+ * Invoked when pull call request via {@link #pullCall()} has failed.
+ *
+ * @param reason Failed reason
+ */
+ public void onCallPullFailed(@PullFailedReason int reason) {}
}
/**
@@ -1936,8 +2109,21 @@
}
/**
+ * @deprecated Use {@link #pullCall()} instead
+ */
+ @Deprecated
+ public void pullExternalCall() {
+ // If this isn't an external call, ignore the request.
+ if (!mDetails.hasProperty(Details.PROPERTY_IS_EXTERNAL_CALL)) {
+ return;
+ }
+
+ mInCallAdapter.pullExternalCall(mTelecomCallId);
+ }
+
+ /**
* Initiates a request to the {@link ConnectionService} to pull an external call to the local
- * device.
+ * device, or to bring a tethered call back to the local device.
* <p>
* Calls to this method are ignored if the call does not have the
* {@link Call.Details#PROPERTY_IS_EXTERNAL_CALL} property set.
@@ -1946,13 +2132,34 @@
* {@link TelecomManager#METADATA_INCLUDE_EXTERNAL_CALLS} metadata set to {@code true}
* in its manifest.
*/
- public void pullExternalCall() {
- // If this isn't an external call, ignore the request.
- if (!mDetails.hasProperty(Details.PROPERTY_IS_EXTERNAL_CALL)) {
- return;
- }
+ public void pullCall() {
+ pullExternalCall();
+ }
- mInCallAdapter.pullExternalCall(mTelecomCallId);
+ /**
+ * Initiates a request to the {@link ConnectionService} to push a call to a
+ * {@link CallEndpoint}.
+ * <p>
+ *
+ * @param endpoint The call endpoint to which the call will be pushed.
+ */
+ public void pushCall(@NonNull CallEndpoint endpoint) {
+ mInCallAdapter.pushCall(mTelecomCallId, endpoint);
+ }
+
+ /**
+ * Initiates a request to the {@link ConnectionService} to answer a call to a
+ * {@link CallEndpoint}.
+ * <p>
+ * Calls to this method are ignored if the call does not have the
+ * {@link Call.Details#CAPABILITY_CAN_PULL_CALL} capability set.
+ *
+ * @param endpoint The call endpoint on which to answer the call.
+ * @param videoState The video state in which to answer the call.
+ */
+ public void answerCall(@NonNull CallEndpoint endpoint,
+ @VideoProfile.VideoState int videoState) {
+ mInCallAdapter.answerCall(mTelecomCallId, endpoint, videoState);
}
/**
@@ -2633,7 +2840,9 @@
mDetails.getCreationTimeMillis(),
mDetails.getContactDisplayName(),
mDetails.getCallDirection(),
- mDetails.getCallerNumberVerificationStatus()
+ mDetails.getCallerNumberVerificationStatus(),
+ mDetails.getActiveCallEndpoint(),
+ mDetails.getAvailableCallEndpoints()
);
fireDetailsChanged(mDetails);
}
@@ -2675,7 +2884,7 @@
}
/** {@hide} */
- final void internalOnHandoverComplete() {
+ void internalOnHandoverComplete() {
for (CallbackRecord<Callback> record : mCallbackRecords) {
final Call call = this;
final Callback callback = record.getCallback();
@@ -2683,6 +2892,32 @@
}
}
+ /** {@hide} */
+ void internalOnCallPullFailed(@Callback.PullFailedReason int reason) {
+ for (CallbackRecord<Callback> record : mCallbackRecords) {
+ final Callback callback = record.getCallback();
+ record.getHandler().post(() -> callback.onCallPullFailed(reason));
+ }
+ }
+
+ /** {@hide} */
+ void internalOnCallPushFailed(CallEndpoint callEndpoint,
+ @Callback.PushFailedReason int reason) {
+ for (CallbackRecord<Callback> record : mCallbackRecords) {
+ final Callback callback = record.getCallback();
+ record.getHandler().post(() -> callback.onCallPushFailed(callEndpoint, reason));
+ }
+ }
+
+ /** {@hide} */
+ void internalOnAnswerFailed(CallEndpoint callEndpoint,
+ @Callback.AnswerFailedReason int reason) {
+ for (CallbackRecord<Callback> record : mCallbackRecords) {
+ final Callback callback = record.getCallback();
+ record.getHandler().post(() -> callback.onAnswerFailed(callEndpoint, reason));
+ }
+ }
+
private void fireStateChanged(final int newState) {
for (CallbackRecord<Callback> record : mCallbackRecords) {
final Call call = this;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl b/telecomm/java/android/telecom/CallEndpoint.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
copy to telecomm/java/android/telecom/CallEndpoint.aidl
index 2b3e961..5030ffd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
+++ b/telecomm/java/android/telecom/CallEndpoint.aidl
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.shared.system.smartspace;
+package android.telecom;
-import com.android.systemui.shared.system.smartspace.ISmartspaceCallback;
-
-// Controller that keeps track of SmartSpace instances in remote processes (such as Launcher).
-interface ISmartspaceTransitionController {
- oneway void setSmartspace(ISmartspaceCallback callback);
-}
\ No newline at end of file
+/**
+ * {@hide}
+ */
+parcelable CallEndpoint;
diff --git a/telecomm/java/android/telecom/CallEndpoint.java b/telecomm/java/android/telecom/CallEndpoint.java
new file mode 100644
index 0000000..dc70656
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpoint.java
@@ -0,0 +1,150 @@
+/*
+ * 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.telecom;
+
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Represents the endpoint on which a call can be carried by the user.
+ *
+ * For example, the user may be able to carry out a call on another device on their local network
+ * using a call streaming solution, or may be able to carry out a call on another device registered
+ * with the same mobile line of service.
+ */
+public final class CallEndpoint implements Parcelable {
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"ENDPOINT_TYPE_"},
+ value = {ENDPOINT_TYPE_TETHERED, ENDPOINT_TYPE_UNTETHERED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EndpointType {}
+
+ /** Indicates the endpoint contains a complete calling stack and is capable of carrying out a
+ * call on its own. Untethered endpoints are typically other devices which share the same
+ * mobile line of service as the current device.
+ */
+ public static final int ENDPOINT_TYPE_UNTETHERED = 1;
+
+ /** Indicates the endpoint itself doesn't have the required calling infrastructure in order to
+ * complete a call on its own. Tethered endpoints depend on a call streaming solution to
+ * transport the media and control for a call to another device, while depending on the current
+ * device to connect the call to the mobile network.
+ */
+ public static final int ENDPOINT_TYPE_TETHERED = 2;
+
+ private final ParcelUuid mUuid;
+ private CharSequence mDescription;
+ private final int mType;
+ private final ComponentName mComponentName;
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mUuid.writeToParcel(dest, flags);
+ dest.writeCharSequence(mDescription);
+ dest.writeInt(mType);
+ mComponentName.writeToParcel(dest, flags);
+ }
+
+ public static final @android.annotation.NonNull Creator<CallEndpoint> CREATOR =
+ new Creator<CallEndpoint>() {
+ @Override
+ public CallEndpoint createFromParcel(Parcel in) {
+ return new CallEndpoint(in);
+ }
+
+ @Override
+ public CallEndpoint[] newArray(int size) {
+ return new CallEndpoint[size];
+ }
+ };
+
+ public CallEndpoint(@NonNull ParcelUuid uuid, @NonNull CharSequence description, int type,
+ @NonNull ComponentName componentName) {
+ mUuid = uuid;
+ mDescription = description;
+ mType = type;
+ mComponentName = componentName;
+ }
+
+ private CallEndpoint(@NonNull Parcel in) {
+ this(ParcelUuid.CREATOR.createFromParcel(in), in.readCharSequence(), in.readInt(),
+ ComponentName.CREATOR.createFromParcel(in));
+ }
+
+ /**
+ * A unique identifier for this call endpoint. An endpoint provider should take care to use an
+ * identifier which is stable for the current association between an endpoint and the current
+ * device, but which is not globally identifying.
+ * @return the unique identifier.
+ */
+ public @NonNull ParcelUuid getIdentifier() {
+ return mUuid;
+ }
+
+ /**
+ * A human-readable description of this {@link CallEndpoint}. An {@link InCallService} uses
+ * when informing the user of the endpoint.
+ * @return the description.
+ */
+ public @NonNull CharSequence getDescription() {
+ return mDescription;
+ }
+
+ public @EndpointType int getType() {
+ return mType;
+ }
+
+ /**
+ * @hide
+ */
+ public @NonNull ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof CallEndpoint) {
+ CallEndpoint d = (CallEndpoint) o;
+ return Objects.equals(mUuid, d.mUuid)
+ && Objects.equals(mDescription, d.mDescription)
+ && Objects.equals(mType, d.mType)
+ && Objects.equals(mComponentName, d.mComponentName);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUuid, mDescription, mType, mComponentName);
+ }
+}
diff --git a/telecomm/java/android/telecom/CallEndpointCallback.java b/telecomm/java/android/telecom/CallEndpointCallback.java
new file mode 100644
index 0000000..6ba55f1
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpointCallback.java
@@ -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.telecom;
+
+/**
+ * Provides callbacks from telecom to the cross device call streaming app with lifecycle events
+ * related to an {@link CallEndpointSession}.
+ */
+public interface CallEndpointCallback {
+ /**
+ * Invoked by telecom when a {@link CallEndpointSession} is started but the streaming app has
+ * not activated the endpoint in a timely manner and the framework deems the activation request
+ * to have timed out.
+ */
+ void onCallEndpointSessionActivationTimeout();
+
+ /**
+ * Invoked by telecom when {@link CallEndpointSession#setCallEndpointSessionDeactivated()}
+ * called by a cross device call streaming app, or when the app uninstalled. When a tethered
+ * {@link CallEndpoint} is deactivated, the call streaming app should clean up any
+ * audio/network resources and stop relaying call controls from the endpoint.
+ */
+ void onCallEndpointSessionDeactivated();
+}
diff --git a/telecomm/java/android/telecom/CallEndpointSession.java b/telecomm/java/android/telecom/CallEndpointSession.java
new file mode 100644
index 0000000..1e7b30c
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpointSession.java
@@ -0,0 +1,122 @@
+/*
+ * 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.telecom;
+
+import android.annotation.IntDef;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import com.android.internal.telecom.ICallEndpointSession;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+
+/**
+ * Provides method and necessary information for cross device call streaming app to streams calls
+ * and updates to the status of the endpoint.
+ *
+ */
+public class CallEndpointSession {
+ /**
+ * Indicates that this call endpoint session is activated by
+ * {@link Call#answerCall(CallEndpoint, int)} from the original device.
+ */
+ public static final int ANSWER_REQUEST = 1;
+
+ /**
+ * Indicates that this call endpoint session is activated by {@link Call#pushCall(CallEndpoint)}
+ * from the original device.
+ */
+ public static final int PUSH_REQUEST = 2;
+
+ /**
+ * Indicates that this call endpoint session is activated by
+ * {@link TelecomManager#placeCall(Uri, Bundle)} with extra
+ * {@link TelecomManager#EXTRA_START_CALL_ON_ENDPOINT} set.
+ */
+ public static final int PLACE_REQUEST = 3;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"ACTIVATION_FAILURE_"},
+ value = {ACTIVATION_FAILURE_REJECTED, ACTIVATION_FAILURE_UNAVAILABLE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ActivationFailureReason {}
+ /**
+ * Used as reason for {@link #setCallEndpointSessionActivationFailed(int)} to inform the
+ * endpoint is no longer present on the network.
+ */
+ public static final int ACTIVATION_FAILURE_UNAVAILABLE = 0;
+
+ /**
+ * Used as reason for {@link #setCallEndpointSessionActivationFailed(int)} to inform the
+ * remote endpoint rejected the request to start streaming a cross device call.
+ */
+ public static final int ACTIVATION_FAILURE_REJECTED = 1;
+
+ private final ICallEndpointSession mCallEndpointSession;
+
+ /**
+ * {@hide}
+ */
+ public CallEndpointSession(ICallEndpointSession callEndpointSession) {
+ mCallEndpointSession = callEndpointSession;
+ }
+
+ /**
+ * Invoked by cross device call streaming app to inform telecom stack that the call endpoint is
+ * now activated and that the call is being streamed to the endpoint.
+ */
+ public void setCallEndpointSessionActivated() {
+ try {
+ mCallEndpointSession.setCallEndpointSessionActivated();
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Invoked by cross device call streaming app to inform telecom stack that the call endpoint
+ * could not be activated due to error.
+ * Possible errors are:
+ * <ul>
+ * <li>{@link #ACTIVATION_FAILURE_UNAVAILABLE}</li>
+ * <li>{@link #ACTIVATION_FAILURE_REJECTED}</li>
+ * </ul>
+ *
+ * @param reason The reason for activation failure
+ */
+ public void setCallEndpointSessionActivationFailed(@ActivationFailureReason int reason) {
+ try {
+ mCallEndpointSession.setCallEndpointSessionActivationFailed(reason);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Invoked by cross device call streaming app to inform telecom stack that the call endpoint is
+ * no longer active.
+ */
+ public void setCallEndpointSessionDeactivated() {
+ try {
+ mCallEndpointSession.setCallEndpointSessionDeactivated();
+ } catch (RemoteException e) {
+ }
+ }
+}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 30d4959..21a1804 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -561,15 +561,6 @@
*/
public static final int PROPERTY_CROSS_SIM = 1 << 13;
- /**
- * Connection is a tethered external call.
- * <p>
- * Indicates that the {@link Connection} is fixed on this device but the audio streams are
- * re-routed to another device.
- * <p>
- */
- public static final int PROPERTY_TETHERED_CALL = 1 << 14;
-
//**********************************************************************************************
// Next PROPERTY value: 1<<14
//**********************************************************************************************
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index 0f034ad..63b9548 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -111,6 +111,22 @@
*/
public static final String REASON_EMERGENCY_CALL_PLACED = "REASON_EMERGENCY_CALL_PLACED";
+ /**
+ * This reason is set when an call is ended due to {@link CallEndpoint} rejection.
+ * This reason string should only be associated with the {@link #LOCAL} disconnect code returned
+ * from {@link #getCode()}.
+ */
+ public static final String REASON_ENDPOINT_REJECTED = "REASON_ENDPOINT_REJECTED";
+
+ /**
+ * This reason is set when a call is ended due to {@link CallEndpoint} deactivated by
+ * call disconnection or user terminated streaming.
+ * This reason string should only be associated with the {@link #LOCAL} disconnect code returned
+ * from {@link #getCode()}
+ */
+ public static final String REASON_ENDPOINT_SESSION_DEACTIVATED =
+ "REASON_ENDPOINT_SESSION_DEACTIVATED";
+
private int mDisconnectCode;
private CharSequence mDisconnectLabel;
private CharSequence mDisconnectDescription;
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index ab35aff..34e9942 100755
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -373,6 +373,34 @@
}
/**
+ * Instructs Telecom to push a call to the given endpoint.
+ *
+ * @param callId The callId to push.
+ * @param callEndpoint The endpoint to which the call will be pushed.
+ */
+ public void pushCall(String callId, CallEndpoint callEndpoint) {
+ try {
+ mAdapter.pushCall(callId, callEndpoint);
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ /**
+ * Instructs Telecom to answer a call via the given endpoint.
+ *
+ * @param callId The callId to push.
+ * @param callEndpoint The endpoint on which the call will be answered.
+ * @param videoState The video state in which to answer the call.
+ */
+ public void answerCall(String callId, CallEndpoint callEndpoint,
+ @VideoProfile.VideoState int videoState) {
+ try {
+ mAdapter.answerCallViaEndpoint(callId, callEndpoint, videoState);
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ /**
* Intructs Telecom to send a call event.
*
* @param callId The callId to send the event for.
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 0ddd52d..ecd6596 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -30,9 +30,12 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.RemoteException;
import android.view.Surface;
import com.android.internal.os.SomeArgs;
+import com.android.internal.telecom.ICallEndpointCallback;
+import com.android.internal.telecom.ICallEndpointSession;
import com.android.internal.telecom.IInCallAdapter;
import com.android.internal.telecom.IInCallService;
@@ -258,6 +261,10 @@
private static final int MSG_ON_RTT_INITIATION_FAILURE = 11;
private static final int MSG_ON_HANDOVER_FAILED = 12;
private static final int MSG_ON_HANDOVER_COMPLETE = 13;
+ private static final int MSG_ON_PUSH_FAILED = 14;
+ private static final int MSG_ON_PULL_FAILED = 15;
+ private static final int MSG_ON_ANSWER_EXTERNAL_FAILED = 16;
+ private static final int MSG_ON_CALL_ENDPOINT_ACTIVATION_REQUEST = 17;
/** Default Handler used to consolidate binder method calls onto a single thread. */
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@@ -339,6 +346,66 @@
mPhone.internalOnHandoverComplete(callId);
break;
}
+ case MSG_ON_PUSH_FAILED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ String callId = (String) args.arg1;
+ CallEndpoint callEndpoint = (CallEndpoint) args.arg2;
+ int reason = (int) args.arg3;
+ mPhone.internalOnCallPushFailed(callId, callEndpoint, reason);
+ } finally {
+ args.recycle();
+ }
+ break;
+ }
+ case MSG_ON_PULL_FAILED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ String callId = (String) args.arg1;
+ int reason = (int) args.arg2;
+ mPhone.internalOnCallPullFailed(callId, reason);
+ } finally {
+ args.recycle();
+ }
+ break;
+ }
+ case MSG_ON_ANSWER_EXTERNAL_FAILED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ String callId = (String) args.arg1;
+ CallEndpoint callEndpoint = (CallEndpoint) args.arg2;
+ int reason = (int) args.arg3;
+ mPhone.internalOnAnswerFailed(callId, callEndpoint, reason);
+ } finally {
+ args.recycle();
+ }
+ break;
+ }
+ case MSG_ON_CALL_ENDPOINT_ACTIVATION_REQUEST: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ CallEndpoint callEndpoint = (CallEndpoint) args.arg1;
+ ICallEndpointSession iCallEndpointSession =
+ (ICallEndpointSession) args.arg2;
+ try {
+ mCallEndpointCallback = onCallEndpointActivationRequested(callEndpoint,
+ new CallEndpointSession(iCallEndpointSession));
+ } catch (UnsupportedOperationException e) {
+ // This InCallService neglected to implement
+ // onCallEndpointActivationRequested, immediately signal back to Telecom
+ // that the activation failed.
+ try {
+ iCallEndpointSession.setCallEndpointSessionActivationFailed(
+ CallEndpointSession.ACTIVATION_FAILURE_UNAVAILABLE);
+ } catch (RemoteException re) {
+ // Ignore
+ }
+ }
+ } finally {
+ args.recycle();
+ }
+ break;
+ }
default:
break;
}
@@ -353,6 +420,36 @@
}
@Override
+ public ICallEndpointCallback requestCallEndpointActivation(CallEndpoint callEndpoint,
+ ICallEndpointSession callEndpointSession) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callEndpoint;
+ args.arg2 = callEndpointSession;
+ mHandler.obtainMessage(MSG_ON_CALL_ENDPOINT_ACTIVATION_REQUEST, args).sendToTarget();
+
+ return new ICallEndpointCallback.Stub() {
+ @Override
+ public void onCallEndpointSessionActivationTimeout() throws RemoteException {
+ if (mCallEndpointCallback != null) {
+ mCallEndpointCallback.onCallEndpointSessionActivationTimeout();
+ }
+ }
+
+ @Override
+ public void onCallEndpointSessionDeactivated() throws RemoteException {
+ if (mCallEndpointCallback != null) {
+ mCallEndpointCallback.onCallEndpointSessionDeactivated();
+ }
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return this;
+ }
+ };
+ }
+
+ @Override
public void addCall(ParcelableCall call) {
mHandler.obtainMessage(MSG_ADD_CALL, call).sendToTarget();
}
@@ -424,6 +521,32 @@
public void onHandoverComplete(String callId) {
mHandler.obtainMessage(MSG_ON_HANDOVER_COMPLETE, callId).sendToTarget();
}
+
+ @Override
+ public void onCallPullFailed(String callId, int reason) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = reason;
+ mHandler.obtainMessage(MSG_ON_PULL_FAILED, args).sendToTarget();
+ }
+
+ @Override
+ public void onCallPushFailed(String callId, CallEndpoint endpoint, int reason) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = endpoint;
+ args.arg3 = reason;
+ mHandler.obtainMessage(MSG_ON_PUSH_FAILED, args).sendToTarget();
+ }
+
+ @Override
+ public void onAnswerFailed(String callId, CallEndpoint endpoint, int reason) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = endpoint;
+ args.arg3 = reason;
+ mHandler.obtainMessage(MSG_ON_ANSWER_EXTERNAL_FAILED, args).sendToTarget();
+ }
}
private Phone.Listener mPhoneListener = new Phone.Listener() {
@@ -470,6 +593,8 @@
};
private Phone mPhone;
+ private CallEndpointSession mCallEndpointSession;
+ private CallEndpointCallback mCallEndpointCallback;
public InCallService() {
}
@@ -494,6 +619,14 @@
onPhoneDestroyed(oldPhone);
}
+ if (mCallEndpointCallback != null) {
+ mCallEndpointCallback = null;
+ }
+
+ if (mCallEndpointSession != null) {
+ mCallEndpointSession = null;
+ }
+
return false;
}
@@ -704,6 +837,21 @@
}
/**
+ * To handle the request from telecom to activate an endpoint session. Streaming app with
+ * meta-data {@link TelecomManager#METADATA_STREAMING_TETHERED_CALLS}.
+ * @param endpoint The endpoint which is to be activated.
+ * @param session An instance of {@link CallEndpointSession} to let streaming app report updates
+ * of the endpoint.
+ * @return CallEndpointCallback The implementation provided by streaming app. Telecom use this
+ * to report events related to the call endpoint session.
+ */
+ public @NonNull CallEndpointCallback onCallEndpointActivationRequested(
+ @NonNull CallEndpoint endpoint, @NonNull CallEndpointSession session)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* Used to issue commands to the {@link Connection.VideoProvider} associated with a
* {@link Call}.
*/
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index f412a18..c429183 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -16,6 +16,7 @@
package android.telecom;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.net.Uri;
@@ -29,8 +30,11 @@
import com.android.internal.telecom.IVideoProvider;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Information about a call that is used between InCallService and Telecom.
@@ -69,6 +73,8 @@
private int mCallerNumberVerificationStatus;
private String mContactDisplayName;
private String mActiveChildCallId;
+ private CallEndpoint mActiveCallEndpoint;
+ private Set<CallEndpoint> mAvailableCallEndpoints = new HashSet<>();
public ParcelableCallBuilder setId(String id) {
mId = id;
@@ -224,6 +230,27 @@
return this;
}
+ /**
+ * Set active call endpoint
+ * @param callEndpoint
+ * @return
+ */
+ public ParcelableCallBuilder setActiveCallEndpoint(CallEndpoint callEndpoint) {
+ mActiveCallEndpoint = callEndpoint;
+ return this;
+ }
+
+ /**
+ * Set available call endpoints
+ * @param availableCallEndpoints
+ * @return
+ */
+ public ParcelableCallBuilder setAvailableCallEndpoints(
+ Set<CallEndpoint> availableCallEndpoints) {
+ mAvailableCallEndpoints = availableCallEndpoints;
+ return this;
+ }
+
public ParcelableCall createParcelableCall() {
return new ParcelableCall(
mId,
@@ -255,7 +282,9 @@
mCallDirection,
mCallerNumberVerificationStatus,
mContactDisplayName,
- mActiveChildCallId);
+ mActiveChildCallId,
+ mActiveCallEndpoint,
+ mAvailableCallEndpoints);
}
public static ParcelableCallBuilder fromParcelableCall(ParcelableCall parcelableCall) {
@@ -292,6 +321,8 @@
parcelableCall.mCallerNumberVerificationStatus;
newBuilder.mContactDisplayName = parcelableCall.mContactDisplayName;
newBuilder.mActiveChildCallId = parcelableCall.mActiveChildCallId;
+ newBuilder.mActiveCallEndpoint = parcelableCall.mActiveCallEndpoint;
+ newBuilder.mAvailableCallEndpoints = parcelableCall.mAvailableCallEndpoints;
return newBuilder;
}
}
@@ -327,6 +358,8 @@
private final int mCallerNumberVerificationStatus;
private final String mContactDisplayName;
private final String mActiveChildCallId; // Only valid for CDMA conferences
+ private final CallEndpoint mActiveCallEndpoint;
+ private final Set<CallEndpoint> mAvailableCallEndpoints;
public ParcelableCall(
String id,
@@ -358,7 +391,9 @@
int callDirection,
int callerNumberVerificationStatus,
String contactDisplayName,
- String activeChildCallId
+ String activeChildCallId,
+ CallEndpoint activeCallEndpoint,
+ Set<CallEndpoint> availableCallEndpoints
) {
mId = id;
mState = state;
@@ -390,6 +425,8 @@
mCallerNumberVerificationStatus = callerNumberVerificationStatus;
mContactDisplayName = contactDisplayName;
mActiveChildCallId = activeChildCallId;
+ mActiveCallEndpoint = activeCallEndpoint;
+ mAvailableCallEndpoints = availableCallEndpoints;
}
/** The unique ID of the call. */
@@ -614,6 +651,21 @@
return mActiveChildCallId;
}
+ /**
+ * @return The {@link CallEndpoint} which is currently active for this call, or null if the call
+ * does not take place via an {@link CallEndpoint}.
+ */
+ public @Nullable CallEndpoint getActiveCallEndpoint() {
+ return mActiveCallEndpoint;
+ }
+
+ /**
+ * @return A set of available {@link CallEndpoint}
+ */
+ public @NonNull Set<CallEndpoint> getAvailableCallEndpoints() {
+ return mAvailableCallEndpoints;
+ }
+
/** Responsible for creating ParcelableCall objects for deserialized Parcels. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public static final @android.annotation.NonNull Parcelable.Creator<ParcelableCall> CREATOR =
@@ -655,6 +707,9 @@
int callerNumberVerificationStatus = source.readInt();
String contactDisplayName = source.readString();
String activeChildCallId = source.readString();
+ CallEndpoint activeCallEndpoint = source.readParcelable(classLoader);
+ List<CallEndpoint> availablableCallEndpoints = new ArrayList<>();
+ source.readList(availablableCallEndpoints, classLoader);
return new ParcelableCallBuilder()
.setId(id)
.setState(state)
@@ -686,6 +741,8 @@
.setCallerNumberVerificationStatus(callerNumberVerificationStatus)
.setContactDisplayName(contactDisplayName)
.setActiveChildCallId(activeChildCallId)
+ .setActiveCallEndpoint(activeCallEndpoint)
+ .setAvailableCallEndpoints(new HashSet<>(availablableCallEndpoints))
.createParcelableCall();
}
@@ -735,6 +792,8 @@
destination.writeInt(mCallerNumberVerificationStatus);
destination.writeString(mContactDisplayName);
destination.writeString(mActiveChildCallId);
+ destination.writeParcelable(mActiveCallEndpoint, 0);
+ destination.writeList(Arrays.asList(mAvailableCallEndpoints.toArray()));
}
@Override
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index bc0a146..ac91a92 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -292,6 +292,29 @@
}
}
+ void internalOnCallPullFailed(String callId, @Call.Callback.PullFailedReason int reason) {
+ Call call = getCallById(callId);
+ if (call != null) {
+ call.internalOnCallPullFailed(reason);
+ }
+ }
+
+ void internalOnAnswerFailed(String callId, CallEndpoint callEndpoint,
+ @Call.Callback.AnswerFailedReason int reason) {
+ Call call = getCallById(callId);
+ if (call != null) {
+ call.internalOnAnswerFailed(callEndpoint, reason);
+ }
+ }
+
+ void internalOnCallPushFailed(String callId, CallEndpoint callEndpoint,
+ @Call.Callback.PushFailedReason int reason) {
+ Call call = getCallById(callId);
+ if (call != null) {
+ call.internalOnCallPushFailed(callEndpoint, reason);
+ }
+ }
+
/**
* Called to destroy the phone and cleanup any lingering calls.
*/
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 6279bf8..e560f34 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -54,8 +54,10 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
/**
@@ -586,6 +588,14 @@
"android.telecom.extra.START_CALL_WITH_RTT";
/**
+ * A parcelable extra, which when set on the bundle passed into {@link #placeCall(Uri, Bundle)},
+ * indicates that the call should be initiated with an active {@link CallEndpoint} to stream
+ * the call as a tethered call.
+ */
+ public static final String EXTRA_START_CALL_ON_ENDPOINT =
+ "android.telecom.extra.START_CALL_ON_ENDPOINT";
+
+ /**
* Start an activity indicating that the completion of an outgoing call or an incoming call
* which was not blocked by the {@link CallScreeningService}, and which was NOT terminated
* while the call was in {@link Call#STATE_AUDIO_PROCESSING}.
@@ -745,6 +755,23 @@
"android.telecom.INCLUDE_SELF_MANAGED_CALLS";
/**
+ * A boolean meta-data value indicating this {@link InCallService} implementation is aimed at
+ * working as a streaming app for a tethered call. When there's a tethered call
+ * requesting to a {@link CallEndpoint} registered with this app, Telecom will bind to this
+ * streaming app and let the app streaming the call to the requested endpoint.
+ * <p>
+ * This meta-data can only be set for an {@link InCallService} which doesn't set neither
+ * {@link #METADATA_IN_CALL_SERVICE_UI} nor {@link #METADATA_IN_CALL_SERVICE_CAR_MODE_UI}.
+ * Otherwise, the app will be treated as a phone/dialer app or a car-mode app.
+ * <p>
+ * The {@link InCallService} declared this meta-data must implement
+ * {@link InCallService#onCallEndpointActivationRequested(CallEndpoint, CallEndpointSession)}.
+ * See this method for more information.
+ */
+ public static final String METADATA_STREAMING_TETHERED_CALLS =
+ "android.telecom.STREAMING_TETHERED_CALLS";
+
+ /**
* The dual tone multi-frequency signaling character sent to indicate the dialing system should
* pause for a predefined period.
*/
@@ -1294,14 +1321,24 @@
* {@link PhoneAccount#CAPABILITY_SELF_MANAGED}.
* <p>
* Requires permission {@link android.Manifest.permission#READ_PHONE_STATE}, or that the caller
- * is the default dialer app.
+ * is the default dialer app to get all phone account handles.
+ * <P>
+ * If the caller doesn't meet any of the above requirements and has {@link
+ * android.Manifest.permission#MANAGE_OWN_CALLS}, the caller can get only the phone account
+ * handles they have registered.
* <p>
- * A {@link SecurityException} will be thrown if a called is not the default dialer, or lacks
- * the {@link android.Manifest.permission#READ_PHONE_STATE} permission.
+ * A {@link SecurityException} will be thrown if the caller is not the default dialer
+ * or the caller does not have at least one of the following permissions:
+ * {@link android.Manifest.permission#READ_PHONE_STATE} permission,
+ * {@link android.Manifest.permission#MANAGE_OWN_CALLS} permission
*
* @return A list of {@code PhoneAccountHandle} objects.
*/
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ @RequiresPermission(anyOf = {
+ READ_PRIVILEGED_PHONE_STATE,
+ android.Manifest.permission.READ_PHONE_STATE,
+ android.Manifest.permission.MANAGE_OWN_CALLS
+ })
public List<PhoneAccountHandle> getSelfManagedPhoneAccounts() {
ITelecomService service = getTelecomService();
if (service != null) {
@@ -2250,6 +2287,7 @@
* <li>{@link #EXTRA_PHONE_ACCOUNT_HANDLE}</li>
* <li>{@link #EXTRA_START_CALL_WITH_SPEAKERPHONE}</li>
* <li>{@link #EXTRA_START_CALL_WITH_VIDEO_STATE}</li>
+ * <li>{@link #EXTRA_START_CALL_ON_ENDPOINT}</li>
* </ul>
* <p>
* An app which implements the self-managed {@link ConnectionService} API uses
@@ -2579,6 +2617,79 @@
}
}
+ /**
+ * Register a set of {@link CallEndpoint} to telecom. All registered {@link CallEndpoint} can
+ * be provided as options for push, place or answer call externally.
+ *
+ * @param endpoints Endpoints to be registered.
+ */
+ // TODO: add permission requirements
+ // @RequiresPermission{}
+ public void registerCallEndpoints(@NonNull Set<CallEndpoint> endpoints) {
+ ITelecomService service = getTelecomService();
+ List<CallEndpoint> endpointList = new ArrayList<>(endpoints);
+ if (service != null) {
+ try {
+ service.registerCallEndpoints(endpointList, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException registerCallEndpoints: " + e);
+ e.rethrowAsRuntimeException();
+ }
+ } else {
+ throw new IllegalStateException("Telecom service is null.");
+ }
+ }
+
+ /**
+ * Unregister all {@link CallEndpoint} from telecom in the set provided. After un-registration,
+ * telecom will stop tracking and maintaining these {@link CallEndpoint}, user can no longer
+ * carry a call on them.
+ *
+ * @param endpoints
+ */
+ // TODO: add permission requirements
+ // @RequiresPermission{}
+ public void unregisterCallEndpoints(@NonNull Set<CallEndpoint> endpoints) {
+ ITelecomService service = getTelecomService();
+ List<CallEndpoint> endpointList = new ArrayList<>(endpoints);
+ if (service != null) {
+ try {
+ service.unregisterCallEndpoints(endpointList, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException unregisterCallEndpoints: " + e);
+ e.rethrowAsRuntimeException();
+ }
+ } else {
+ throw new IllegalStateException("Telecom service is null.");
+ }
+ }
+
+ /**
+ * Return a set all registered {@link CallEndpoint} that can be used to stream and carry an
+ * external call.
+ *
+ * @return A set of all available {@link CallEndpoint}.
+ */
+ // TODO: add permission requirements
+ // @RequiresPermission{}
+ public @NonNull Set<CallEndpoint> getCallEndpoints() {
+ Set<CallEndpoint> endpoints = new HashSet<>();
+ List<CallEndpoint> endpointList;
+ ITelecomService service = getTelecomService();
+ if (service != null) {
+ try {
+ endpointList = service.getCallEndpoints(mContext.getOpPackageName());
+ return new HashSet<>(endpointList);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException registerCallEndpoints: " + e);
+ e.rethrowAsRuntimeException();
+ }
+ } else {
+ throw new IllegalStateException("Telecom service is null.");
+ }
+ return endpoints;
+ }
+
private boolean isSystemProcess() {
return Process.myUid() == Process.SYSTEM_UID;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl b/telecomm/java/com/android/internal/telecom/ICallEndpointCallback.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
copy to telecomm/java/com/android/internal/telecom/ICallEndpointCallback.aidl
index 2b3e961..dc1cc0f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallEndpointCallback.aidl
@@ -14,11 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.shared.system.smartspace;
+package com.android.internal.telecom;
-import com.android.systemui.shared.system.smartspace.ISmartspaceCallback;
+/**
+ * Internal remote CallEndpointCallback interface for Telecom framework to report event related to
+ * the endpoint session.
+ *
+ * {@hide}
+ */
+oneway interface ICallEndpointCallback {
+ void onCallEndpointSessionActivationTimeout();
-// Controller that keeps track of SmartSpace instances in remote processes (such as Launcher).
-interface ISmartspaceTransitionController {
- oneway void setSmartspace(ISmartspaceCallback callback);
+ void onCallEndpointSessionDeactivated();
}
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/ICallEndpointSession.aidl b/telecomm/java/com/android/internal/telecom/ICallEndpointSession.aidl
new file mode 100644
index 0000000..1c1c29a
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ICallEndpointSession.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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.telecom;
+
+/**
+ * Internal remote CallEndpointSession interface for streaming app to update the status of the
+ * endpoint.
+ *
+ * @see android.telecom.CallEndpointSession
+ *
+ * {@hide}
+ */
+
+oneway interface ICallEndpointSession {
+ void setCallEndpointSessionActivated();
+
+ void setCallEndpointSessionActivationFailed(int reason);
+
+ void setCallEndpointSessionDeactivated();
+}
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index d72f8aa..986871f 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -20,6 +20,7 @@
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
import android.telecom.Logging.Session;
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index edf1cf4..ecca835 100755
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -18,6 +18,7 @@
import android.net.Uri;
import android.os.Bundle;
+import android.telecom.CallEndpoint;
import android.telecom.PhoneAccountHandle;
/**
@@ -95,4 +96,8 @@
void handoverTo(String callId, in PhoneAccountHandle destAcct, int videoState,
in Bundle extras);
+
+ void pushCall(String callId, in CallEndpoint endpoint);
+
+ void answerCallViaEndpoint(String callId, in CallEndpoint endpoint, int videoState);
}
diff --git a/telecomm/java/com/android/internal/telecom/IInCallService.aidl b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
index b9563fa..93d9f28 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
@@ -19,9 +19,12 @@
import android.app.PendingIntent;
import android.os.Bundle;
import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
import android.telecom.ParcelableCall;
import com.android.internal.telecom.IInCallAdapter;
+import com.android.internal.telecom.ICallEndpointCallback;
+import com.android.internal.telecom.ICallEndpointSession;
/**
* Internal remote interface for in-call services.
@@ -30,9 +33,12 @@
*
* {@hide}
*/
-oneway interface IInCallService {
+interface IInCallService {
void setInCallAdapter(in IInCallAdapter inCallAdapter);
+ ICallEndpointCallback requestCallEndpointActivation(in CallEndpoint callEndpoint,
+ in ICallEndpointSession callEndpointSession);
+
void addCall(in ParcelableCall call);
void updateCall(in ParcelableCall call);
@@ -58,4 +64,10 @@
void onHandoverFailed(String callId, int error);
void onHandoverComplete(String callId);
+
+ void onCallPullFailed(String callId, int reason);
+
+ void onCallPushFailed(String callId, in CallEndpoint endpoint, int reason);
+
+ void onAnswerFailed(String callId, in CallEndpoint endpoint, int reason);
}
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index b9936ce..985f6bc 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -18,6 +18,7 @@
import android.content.ComponentName;
import android.content.Intent;
+import android.telecom.CallEndpoint;
import android.telecom.TelecomAnalytics;
import android.telecom.PhoneAccountHandle;
import android.net.Uri;
@@ -368,4 +369,19 @@
* @see TelecomServiceImpl#setTestCallDiagnosticService
*/
void setTestCallDiagnosticService(in String packageName);
+
+ /**
+ * @see TelecomServiceImpl#registerCallEndpoints(in List<CallEndpoint>, in String);
+ */
+ void registerCallEndpoints(in List<CallEndpoint> endpoints, in String packageName);
+
+ /**
+ * @see TelecomServiceImpl#unregisterCallEndpoints(in List<CallEndpoint>, String);
+ */
+ void unregisterCallEndpoints(in List<CallEndpoint> endpoints, in String packageName);
+
+ /**
+ * @see TelecomServiceImpl#getCallEndpoints(in String packageName);
+ */
+ List<CallEndpoint> getCallEndpoints(in String packageName);
}
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index 61de3ac..154bb11 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -268,6 +268,13 @@
@SystemApi
public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED = 11;
+ /**
+ * A capability update has been requested due to IMS being registered over INTERNET PDN.
+ * @hide
+ */
+ @SystemApi
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_INTERNET_PDN = 12;
+
/**@hide*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "ERROR_", value = {
@@ -282,7 +289,8 @@
CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN,
CAPABILITY_UPDATE_TRIGGER_MOVE_TO_IWLAN,
CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_DISABLED,
- CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_INTERNET_PDN
})
public @interface StackPublishTriggerType {}
diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java
index af7373b..70e4ef1 100644
--- a/telephony/java/android/telephony/ims/feature/RcsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java
@@ -488,9 +488,7 @@
* has already been created in the framework.
* @param listener A {@link CapabilityExchangeEventListener} to send the capability exchange
* event to the framework.
- * @hide
*/
- @SystemApi
private void initRcsCapabilityExchangeImplBase(
@NonNull CapabilityExchangeEventListener listener) {
synchronized (mLock) {
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 f2696d8..815ea77 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
@@ -178,7 +178,7 @@
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(repetitions = 1,
+ .getConfigNonRotationTests(repetitions = 5,
// b/190352379 (IME doesn't show on app launch in 90 degrees)
supportedRotations = listOf(Surface.ROTATION_0),
supportedNavigationModes = listOf(
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 5450610..d38a719 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -25,9 +25,7 @@
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -83,11 +81,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()
/** {@inheritDoc} */
@FlakyTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index aac4812..7443f0b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -28,9 +28,7 @@
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.traces.common.WindowManagerConditionsFactory
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -100,47 +98,12 @@
/** {@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()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun entireScreenCovered() {
- // This test doesn't work in shell transitions because of b/204570898
- assumeFalse(isShellTransitionsEnabled)
- super.entireScreenCovered()
- }
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun appWindowReplacesLauncherAsTopWindow() {
- // This test doesn't work in shell transitions because of b/206085788
- assumeFalse(isShellTransitionsEnabled)
- super.appWindowReplacesLauncherAsTopWindow()
- }
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun appLayerReplacesLauncher() {
- // This test doesn't work in shell transitions because of b/206085788
- assumeFalse(isShellTransitionsEnabled)
- super.appLayerReplacesLauncher()
- }
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- // This test doesn't work in shell transitions because of b/206090480
- assumeFalse(isShellTransitionsEnabled)
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
- }
+ override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
/** {@inheritDoc} */
@FlakyTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index a85dcc5..12177ed 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -28,11 +28,9 @@
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.traces.common.FlickerComponentName
import com.google.common.truth.Truth
-import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -181,18 +179,12 @@
/** {@inheritDoc} */
@FlakyTest(bugId = 202936526)
@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()
/** {@inheritDoc} */
@FlakyTest(bugId = 206753786)
@Test
fun statusBarLayerPositionAtEnd() {
- // This test doesn't work in shell transitions because of b/206753786
- assumeFalse(isShellTransitionsEnabled)
testSpec.assertLayersEnd {
val display = this.entry.displays.minByOrNull { it.id }
?: error("There is no display!")
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index cd209b2..304e516 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -25,8 +25,6 @@
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.helpers.setRotation
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
@@ -87,11 +85,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()
/** {@inheritDoc} */
@Presubmit