Merge "Polish ActivityEmbedding enter/exit PiP (1/n)"
diff --git a/ApiDocs.bp b/ApiDocs.bp
index 4bba338..6a323d6 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -123,17 +123,30 @@
}
droidstubs {
- name: "framework-doc-system-stubs",
- defaults: ["framework-doc-stubs-sources-default"],
- args: metalava_framework_docs_args +
- " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ",
- api_levels_annotations_enabled: true,
- api_levels_annotations_dirs: [
- "sdk-dir",
- "api-versions-jars-dir",
+ name: "android-non-updatable-doc-stubs-module-lib",
+ defaults: [
+ "android-non-updatable-doc-stubs-defaults",
+ "module-classpath-stubs-defaults",
],
- api_levels_sdk_type: "system",
- extensions_info_file: ":sdk-extensions-info",
+ args: metalava_framework_docs_args +
+ " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) " +
+ " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\) ",
+ generate_stubs: false, // We're only using this module for the annotations.zip output, disable doc-stubs.
+ write_sdk_values: false,
+}
+
+droidstubs {
+ name: "android-non-updatable-doc-stubs-system-server",
+ defaults: [
+ "android-non-updatable-doc-stubs-defaults",
+ "module-classpath-stubs-defaults",
+ ],
+ args: metalava_framework_docs_args +
+ " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) " +
+ " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\) " +
+ " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\) ",
+ generate_stubs: false, // We're only using this module for the annotations.zip output, disable doc-stubs.
+ write_sdk_values: false,
}
droidstubs {
@@ -151,6 +164,20 @@
extensions_info_file: ":sdk-extensions-info",
}
+droidstubs {
+ name: "framework-doc-system-stubs",
+ defaults: ["framework-doc-stubs-sources-default"],
+ args: metalava_framework_docs_args +
+ " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ",
+ api_levels_annotations_enabled: true,
+ api_levels_annotations_dirs: [
+ "sdk-dir",
+ "api-versions-jars-dir",
+ ],
+ api_levels_sdk_type: "system",
+ extensions_info_file: ":sdk-extensions-info",
+}
+
/////////////////////////////////////////////////////////////////////
// API docs are created from the generated stub source files
// using droiddoc
diff --git a/StubLibraries.bp b/StubLibraries.bp
index bc2e6dd..0e08496 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -411,6 +411,36 @@
],
}
+java_library {
+ name: "android_module_stubs_current_with_test_libs",
+ static_libs: [
+ "android_module_lib_stubs_current",
+ "android.test.base.stubs",
+ "android.test.mock.stubs",
+ "android.test.runner.stubs",
+ ],
+ defaults: ["android.jar_defaults"],
+ visibility: [
+ "//visibility:override",
+ "//visibility:private",
+ ],
+}
+
+java_library {
+ name: "android_system_server_stubs_current_with_test_libs",
+ static_libs: [
+ "android_system_server_stubs_current",
+ "android.test.base.stubs.system",
+ "android.test.mock.stubs.system",
+ "android.test.runner.stubs.system",
+ ],
+ defaults: ["android.jar_defaults"],
+ visibility: [
+ "//visibility:override",
+ "//visibility:private",
+ ],
+}
+
droidstubs {
name: "api_versions_public",
srcs: [":android_stubs_current_with_test_libs{.jar}"],
@@ -420,6 +450,7 @@
"sdk-dir",
"api-versions-jars-dir",
],
+ api_levels_sdk_type: "public",
extensions_info_file: ":sdk-extensions-info",
}
@@ -436,6 +467,32 @@
extensions_info_file: ":sdk-extensions-info",
}
+droidstubs {
+ name: "api_versions_module_lib",
+ srcs: [":android_module_stubs_current_with_test_libs{.jar}"],
+ generate_stubs: false,
+ api_levels_annotations_enabled: true,
+ api_levels_annotations_dirs: [
+ "sdk-dir",
+ "api-versions-jars-dir",
+ ],
+ api_levels_sdk_type: "module-lib",
+ extensions_info_file: ":sdk-extensions-info",
+}
+
+droidstubs {
+ name: "api_versions_system_server",
+ srcs: [":android_system_server_stubs_current_with_test_libs{.jar}"],
+ generate_stubs: false,
+ api_levels_annotations_enabled: true,
+ api_levels_annotations_dirs: [
+ "sdk-dir",
+ "api-versions-jars-dir",
+ ],
+ api_levels_sdk_type: "system-server",
+ extensions_info_file: ":sdk-extensions-info",
+}
+
/////////////////////////////////////////////////////////////////////
// hwbinder.stubs provides APIs required for building HIDL Java
// libraries.
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 901796b..bedfa7f 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -87,6 +87,7 @@
import android.database.ContentObserver;
import android.net.Uri;
import android.os.BatteryManager;
+import android.os.BatteryStatsInternal;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -116,6 +117,7 @@
import android.util.ArraySet;
import android.util.EventLog;
import android.util.IndentingPrintWriter;
+import android.util.IntArray;
import android.util.Log;
import android.util.LongArrayQueue;
import android.util.Pair;
@@ -249,6 +251,7 @@
private ActivityManagerInternal mActivityManagerInternal;
private final EconomyManagerInternal mEconomyManagerInternal;
private PackageManagerInternal mPackageManagerInternal;
+ private BatteryStatsInternal mBatteryStatsInternal;
private RoleManager mRoleManager;
private volatile PermissionManagerServiceInternal mLocalPermissionManager;
@@ -2113,6 +2116,8 @@
LocalServices.getService(AppStandbyInternal.class);
appStandbyInternal.addListener(new AppStandbyTracker());
+ mBatteryStatsInternal = LocalServices.getService(BatteryStatsInternal.class);
+
mRoleManager = getContext().getSystemService(RoleManager.class);
mMetricsHelper.registerPuller(() -> mAlarmStore);
@@ -4783,8 +4788,12 @@
}
final ArraySet<Pair<String, Integer>> triggerPackages =
new ArraySet<>();
+ final IntArray wakeupUids = new IntArray();
for (int i = 0; i < triggerList.size(); i++) {
final Alarm a = triggerList.get(i);
+ if (a.wakeup) {
+ wakeupUids.add(a.uid);
+ }
if (mConstants.USE_TARE_POLICY) {
if (!isExemptFromTare(a)) {
triggerPackages.add(Pair.create(
@@ -4796,6 +4805,11 @@
a.sourcePackage, UserHandle.getUserId(a.creatorUid)));
}
}
+ if (wakeupUids.size() > 0 && mBatteryStatsInternal != null) {
+ mBatteryStatsInternal.noteCpuWakingActivity(
+ BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM, nowELAPSED,
+ wakeupUids.toArray());
+ }
deliverAlarmsLocked(triggerList, nowELAPSED);
mTemporaryQuotaReserve.cleanUpExpiredQuotas(nowELAPSED);
if (mConstants.USE_TARE_POLICY) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java
index a1a541f..b2ca3a0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java
@@ -337,7 +337,6 @@
removeJobFromBillList(jobStatus, billToJobMap.keyAt(i));
}
}
- addJobToBillList(jobStatus, getRunningBill(jobStatus));
final int uid = jobStatus.getSourceUid();
if (mService.getUidBias(uid) == JobInfo.BIAS_TOP_APP) {
@@ -347,6 +346,7 @@
mTopStartedJobs.add(jobStatus);
// Top jobs won't count towards quota so there's no need to involve the EconomyManager.
} else {
+ addJobToBillList(jobStatus, getRunningBill(jobStatus));
mEconomyManagerInternal.noteOngoingEventStarted(userId, pkgName,
getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId()));
}
@@ -357,9 +357,14 @@
public void unprepareFromExecutionLocked(JobStatus jobStatus) {
final int userId = jobStatus.getSourceUserId();
final String pkgName = jobStatus.getSourcePackageName();
- mEconomyManagerInternal.noteOngoingEventStopped(userId, pkgName,
- getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId()));
- mTopStartedJobs.remove(jobStatus);
+ // If this method is called, then jobStatus.madeActive was never updated, so don't use it
+ // to determine if the EconomyManager was notified.
+ if (!mTopStartedJobs.remove(jobStatus)) {
+ // If the job was started while the app was top, then the EconomyManager wasn't notified
+ // of the job start.
+ mEconomyManagerInternal.noteOngoingEventStopped(userId, pkgName,
+ getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId()));
+ }
final ArraySet<ActionBill> bills = getPossibleStartBills(jobStatus);
ArrayMap<ActionBill, ArraySet<JobStatus>> billToJobMap =
@@ -382,9 +387,13 @@
boolean forUpdate) {
final int userId = jobStatus.getSourceUserId();
final String pkgName = jobStatus.getSourcePackageName();
- mEconomyManagerInternal.noteOngoingEventStopped(userId, pkgName,
- getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId()));
- mTopStartedJobs.remove(jobStatus);
+ if (!mTopStartedJobs.remove(jobStatus) && jobStatus.madeActive > 0) {
+ // Only note the job stop if we previously told the EconomyManager that the job started.
+ // If the job was started while the app was top, then the EconomyManager wasn't notified
+ // of the job start.
+ mEconomyManagerInternal.noteOngoingEventStopped(userId, pkgName,
+ getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId()));
+ }
ArrayMap<ActionBill, ArraySet<JobStatus>> billToJobMap =
mRegisteredBillsAndJobs.get(userId, pkgName);
if (billToJobMap != null) {
diff --git a/api/Android.bp b/api/Android.bp
index 07fd850..9306671 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -213,6 +213,24 @@
}
genrule {
+ name: "sdk-annotations-module-lib.zip",
+ defaults: ["sdk-annotations-defaults"],
+ srcs: [
+ ":android-non-updatable-doc-stubs-module-lib{.annotations.zip}",
+ ":all-modules-module-lib-annotations",
+ ],
+}
+
+genrule {
+ name: "sdk-annotations-system-server.zip",
+ defaults: ["sdk-annotations-defaults"],
+ srcs: [
+ ":android-non-updatable-doc-stubs-system-server{.annotations.zip}",
+ ":all-modules-system-server-annotations",
+ ],
+}
+
+genrule {
name: "combined-removed-dex",
visibility: [
"//frameworks/base/boot",
diff --git a/api/api.go b/api/api.go
index f158041..6a6c493 100644
--- a/api/api.go
+++ b/api/api.go
@@ -148,18 +148,35 @@
ctx.CreateModule(genrule.GenRuleFactory, &props)
}
-func createMergedPublicAnnotationsFilegroup(ctx android.LoadHookContext, modules []string) {
- props := fgProps{}
- props.Name = proptools.StringPtr("all-modules-public-annotations")
- props.Srcs = createSrcs(modules, "{.public.annotations.zip}")
- ctx.CreateModule(android.FileGroupFactory, &props)
-}
-
-func createMergedSystemAnnotationsFilegroup(ctx android.LoadHookContext, modules []string) {
- props := fgProps{}
- props.Name = proptools.StringPtr("all-modules-system-annotations")
- props.Srcs = createSrcs(modules, "{.system.annotations.zip}")
- ctx.CreateModule(android.FileGroupFactory, &props)
+func createMergedAnnotationsFilegroups(ctx android.LoadHookContext, modules, system_server_modules []string) {
+ for _, i := range []struct{
+ name string
+ tag string
+ modules []string
+ }{
+ {
+ name: "all-modules-public-annotations",
+ tag: "{.public.annotations.zip}",
+ modules: modules,
+ }, {
+ name: "all-modules-system-annotations",
+ tag: "{.system.annotations.zip}",
+ modules: modules,
+ }, {
+ name: "all-modules-module-lib-annotations",
+ tag: "{.module-lib.annotations.zip}",
+ modules: modules,
+ }, {
+ name: "all-modules-system-server-annotations",
+ tag: "{.system-server.annotations.zip}",
+ modules: system_server_modules,
+ },
+ } {
+ props := fgProps{}
+ props.Name = proptools.StringPtr(i.name)
+ props.Srcs = createSrcs(i.modules, i.tag)
+ ctx.CreateModule(android.FileGroupFactory, &props)
+ }
}
func createFilteredApiVersions(ctx android.LoadHookContext, modules []string) {
@@ -172,17 +189,43 @@
// difficult to achieve.
modules = remove(modules, art)
- props := genruleProps{}
- props.Name = proptools.StringPtr("api-versions-xml-public-filtered")
- props.Tools = []string{"api_versions_trimmer"}
- props.Out = []string{"api-versions-public-filtered.xml"}
- props.Cmd = proptools.StringPtr("$(location api_versions_trimmer) $(out) $(in)")
- // Note: order matters: first parameter is the full api-versions.xml
- // after that the stubs files in any order
- // stubs files are all modules that export API surfaces EXCEPT ART
- props.Srcs = append([]string{":api_versions_public{.api_versions.xml}"}, createSrcs(modules, ".stubs{.jar}")...)
- props.Dists = []android.Dist{{Targets: []string{"sdk"}}}
- ctx.CreateModule(genrule.GenRuleFactory, &props)
+ for _, i := range []struct{
+ name string
+ out string
+ in string
+ }{
+ {
+ // We shouldn't need public-filtered or system-filtered.
+ // public-filtered is currently used to lint things that
+ // use the module sdk or the system server sdk, but those
+ // should be switched over to module-filtered and
+ // system-server-filtered, and then public-filtered can
+ // be removed.
+ name: "api-versions-xml-public-filtered",
+ out: "api-versions-public-filtered.xml",
+ in: ":api_versions_public{.api_versions.xml}",
+ }, {
+ name: "api-versions-xml-module-lib-filtered",
+ out: "api-versions-module-lib-filtered.xml",
+ in: ":api_versions_module_lib{.api_versions.xml}",
+ }, {
+ name: "api-versions-xml-system-server-filtered",
+ out: "api-versions-system-server-filtered.xml",
+ in: ":api_versions_system_server{.api_versions.xml}",
+ },
+ } {
+ props := genruleProps{}
+ props.Name = proptools.StringPtr(i.name)
+ props.Out = []string{i.out}
+ // Note: order matters: first parameter is the full api-versions.xml
+ // after that the stubs files in any order
+ // stubs files are all modules that export API surfaces EXCEPT ART
+ props.Srcs = append([]string{i.in}, createSrcs(modules, ".stubs{.jar}")...)
+ props.Tools = []string{"api_versions_trimmer"}
+ props.Cmd = proptools.StringPtr("$(location api_versions_trimmer) $(out) $(in)")
+ props.Dists = []android.Dist{{Targets: []string{"sdk"}}}
+ ctx.CreateModule(genrule.GenRuleFactory, &props)
+ }
}
func createMergedPublicStubs(ctx android.LoadHookContext, modules []string) {
@@ -279,11 +322,12 @@
func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) {
bootclasspath := a.properties.Bootclasspath
+ system_server_classpath := a.properties.System_server_classpath
if ctx.Config().VendorConfig("ANDROID").Bool("include_nonpublic_framework_api") {
bootclasspath = append(bootclasspath, a.properties.Conditional_bootclasspath...)
sort.Strings(bootclasspath)
}
- createMergedTxts(ctx, bootclasspath, a.properties.System_server_classpath)
+ createMergedTxts(ctx, bootclasspath, system_server_classpath)
createMergedStubsSrcjar(ctx, bootclasspath)
@@ -292,8 +336,7 @@
createMergedFrameworkModuleLibStubs(ctx, bootclasspath)
createMergedFrameworkImpl(ctx, bootclasspath)
- createMergedPublicAnnotationsFilegroup(ctx, bootclasspath)
- createMergedSystemAnnotationsFilegroup(ctx, bootclasspath)
+ createMergedAnnotationsFilegroups(ctx, bootclasspath, system_server_classpath)
createFilteredApiVersions(ctx, bootclasspath)
diff --git a/core/api/current.txt b/core/api/current.txt
index 815ccfe..431f06c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8947,6 +8947,7 @@
public final class AssociationInfo implements android.os.Parcelable {
method public int describeContents();
+ method @Nullable public android.os.Parcelable getAssociatedDevice();
method @Nullable public android.net.MacAddress getDeviceMacAddress();
method @Nullable public String getDeviceProfile();
method @Nullable public CharSequence getDisplayName();
@@ -11595,6 +11596,7 @@
field public static final String ACTION_SESSION_UPDATED = "android.content.pm.action.SESSION_UPDATED";
field public static final String EXTRA_OTHER_PACKAGE_NAME = "android.content.pm.extra.OTHER_PACKAGE_NAME";
field public static final String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME";
+ field public static final String EXTRA_PRE_APPROVAL = "android.content.pm.extra.PRE_APPROVAL";
field public static final String EXTRA_SESSION = "android.content.pm.extra.SESSION";
field public static final String EXTRA_SESSION_ID = "android.content.pm.extra.SESSION_ID";
field public static final String EXTRA_STATUS = "android.content.pm.extra.STATUS";
@@ -11651,6 +11653,7 @@
method public void removeChildSessionId(int);
method public void removeSplit(@NonNull String) throws java.io.IOException;
method public void requestChecksums(@NonNull String, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageManager.OnChecksumsReadyListener) throws java.security.cert.CertificateEncodingException, java.io.FileNotFoundException;
+ method public void requestUserPreapproval(@NonNull android.content.pm.PackageInstaller.PreapprovalDetails, @NonNull android.content.IntentSender);
method @Deprecated public void setChecksums(@NonNull String, @NonNull java.util.List<android.content.pm.Checksum>, @Nullable byte[]) throws java.io.IOException;
method public void setStagingProgress(float);
method public void transfer(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 87960d7..0b428d9 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -7132,6 +7132,7 @@
field public static final int TUNER_VERSION_1_0 = 65536; // 0x10000
field public static final int TUNER_VERSION_1_1 = 65537; // 0x10001
field public static final int TUNER_VERSION_2_0 = 131072; // 0x20000
+ field public static final int TUNER_VERSION_3_0 = 196608; // 0x30000
field public static final int TUNER_VERSION_UNKNOWN = 0; // 0x0
}
@@ -7149,6 +7150,7 @@
method public long read(@NonNull byte[], long, long);
method public long seek(long);
method public void setFileDescriptor(@NonNull android.os.ParcelFileDescriptor);
+ method public int setPlaybackBufferStatusCheckIntervalHint(long);
method public int start();
method public int stop();
field public static final int PLAYBACK_STATUS_ALMOST_EMPTY = 2; // 0x2
@@ -7164,6 +7166,7 @@
method public int detachFilter(@NonNull android.media.tv.tuner.filter.Filter);
method public int flush();
method public void setFileDescriptor(@NonNull android.os.ParcelFileDescriptor);
+ method public int setRecordBufferStatusCheckIntervalHint(long);
method public int start();
method public int stop();
method public long write(long);
@@ -11159,6 +11162,7 @@
public final class ActivityEvent implements android.os.Parcelable {
method public int describeContents();
+ method @NonNull public android.app.assist.ActivityId getActivityId();
method @NonNull public android.content.ComponentName getComponentName();
method public int getEventType();
method public void writeToParcel(@NonNull android.os.Parcel, int);
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 7d19ed4..81aa6da 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -23,6 +23,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager.ProcessCapability;
import android.app.ActivityManager.RestrictionLevel;
+import android.app.assist.ActivityId;
import android.content.ComponentName;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
@@ -343,7 +344,7 @@
*/
public abstract void updateActivityUsageStats(
ComponentName activity, @UserIdInt int userId, int event, IBinder appToken,
- ComponentName taskRoot);
+ ComponentName taskRoot, ActivityId activityId);
public abstract void updateForegroundTimeIfOnBattery(
String packageName, int uid, long cpuTimeDiff);
public abstract void sendForegroundProfileChanged(@UserIdInt int userId);
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index f362204..52ef7fb 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -266,6 +266,12 @@
private static final String KEY_LAUNCH_TASK_ID = "android.activity.launchTaskId";
/**
+ * See {@link #setDisableStartingWindow}.
+ * @hide
+ */
+ private static final String KEY_DISABLE_STARTING_WINDOW = "android.activity.disableStarting";
+
+ /**
* See {@link #setPendingIntentLaunchFlags(int)}
* @hide
*/
@@ -477,6 +483,7 @@
private PictureInPictureParams mLaunchIntoPipParams;
private boolean mDismissKeyguard;
private boolean mIgnorePendingIntentCreatorForegroundState;
+ private boolean mDisableStartingWindow;
/**
* Create an ActivityOptions specifying a custom animation to run when
@@ -1284,6 +1291,7 @@
mDismissKeyguard = opts.getBoolean(KEY_DISMISS_KEYGUARD);
mIgnorePendingIntentCreatorForegroundState = opts.getBoolean(
KEY_IGNORE_PENDING_INTENT_CREATOR_FOREGROUND_STATE);
+ mDisableStartingWindow = opts.getBoolean(KEY_DISABLE_STARTING_WINDOW);
}
/**
@@ -1700,6 +1708,22 @@
}
/**
+ * Sets whether recents disable showing starting window when activity launch.
+ * @hide
+ */
+ @RequiresPermission(START_TASKS_FROM_RECENTS)
+ public void setDisableStartingWindow(boolean disable) {
+ mDisableStartingWindow = disable;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean getDisableStartingWindow() {
+ return mDisableStartingWindow;
+ }
+
+ /**
* Specifies intent flags to be applied for any activity started from a PendingIntent.
*
* @hide
@@ -2210,6 +2234,9 @@
b.putBoolean(KEY_IGNORE_PENDING_INTENT_CREATOR_FOREGROUND_STATE,
mIgnorePendingIntentCreatorForegroundState);
}
+ if (mDisableStartingWindow) {
+ b.putBoolean(KEY_DISABLE_STARTING_WINDOW, mDisableStartingWindow);
+ }
return b;
}
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index 033cffe..e2082f7 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -650,7 +650,7 @@
if (decorView != null) {
Drawable drawable = decorView.getBackground();
if (drawable != null) {
- drawable.setAlpha(1);
+ drawable.setAlpha(255);
}
}
}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index ddfbc68..302d146 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -159,6 +159,7 @@
void clearRequestedListenerHints(in INotificationListener token);
void requestHintsFromListener(in INotificationListener token, int hints);
int getHintsFromListener(in INotificationListener token);
+ int getHintsFromListenerNoToken();
void requestInterruptionFilterFromListener(in INotificationListener token, int interruptionFilter);
int getInterruptionFilterFromListener(in INotificationListener token);
void setOnNotificationPostedTrimFromListener(in INotificationListener token, int trim);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 392f52a..f6d27ad 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -66,9 +66,9 @@
/**
* Class to notify the user of events that happen. This is how you tell
- * the user that something has happened in the background. {@more}
+ * the user that something has happened in the background.
*
- * Notifications can take different forms:
+ * <p>Notifications can take different forms:
* <ul>
* <li>A persistent icon that goes in the status bar and is accessible
* through the launcher, (when the user selects it, a designated Intent
diff --git a/core/java/android/companion/AssociatedDevice.java b/core/java/android/companion/AssociatedDevice.java
new file mode 100644
index 0000000..3758cdb
--- /dev/null
+++ b/core/java/android/companion/AssociatedDevice.java
@@ -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 android.companion;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Loose wrapper around device parcelable. Device can be one of three types:
+ *
+ * <ul>
+ * <li>for classic Bluetooth - {@link android.bluetooth.BluetoothDevice}</li>
+ * <li>for Bluetooth LE - {@link android.bluetooth.le.ScanResult}</li>
+ * <li>for WiFi - {@link android.net.wifi.ScanResult}</li>
+ * </ul>
+ *
+ * This class serves as temporary wrapper to deliver a loosely-typed parcelable object from
+ * {@link com.android.companiondevicemanager.CompanionDeviceActivity} to the Companion app,
+ * and should only be used internally.
+ *
+ * @hide
+ */
+public final class AssociatedDevice implements Parcelable {
+ private static final int CLASSIC_BLUETOOTH = 0;
+ private static final int BLUETOOTH_LE = 1;
+ private static final int WIFI = 2;
+
+ @NonNull private final Parcelable mDevice;
+
+ public AssociatedDevice(@NonNull Parcelable device) {
+ mDevice = device;
+ }
+
+ private AssociatedDevice(Parcel in) {
+ Creator<? extends Parcelable> creator = getDeviceCreator(in.readInt());
+ mDevice = creator.createFromParcel(in);
+ }
+
+ /**
+ * Return device info. Cast to expected device type.
+ */
+ @NonNull
+ public Parcelable getDevice() {
+ return mDevice;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // Parcel device type with int for efficiency
+ dest.writeInt(getDeviceType());
+ mDevice.writeToParcel(dest, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private int getDeviceType() {
+ if (mDevice instanceof android.bluetooth.BluetoothDevice) return CLASSIC_BLUETOOTH;
+ if (mDevice instanceof android.bluetooth.le.ScanResult) return BLUETOOTH_LE;
+ if (mDevice instanceof android.net.wifi.ScanResult) return WIFI;
+ throw new UnsupportedOperationException("Unsupported device type.");
+ }
+
+ private static Creator<? extends Parcelable> getDeviceCreator(int deviceType) {
+ switch (deviceType) {
+ case CLASSIC_BLUETOOTH: return android.bluetooth.BluetoothDevice.CREATOR;
+ case BLUETOOTH_LE: return android.bluetooth.le.ScanResult.CREATOR;
+ case WIFI: return android.net.wifi.ScanResult.CREATOR;
+ default: throw new UnsupportedOperationException("Unsupported device type.");
+ }
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<AssociatedDevice> CREATOR =
+ new Parcelable.Creator<AssociatedDevice>() {
+ @Override
+ public AssociatedDevice[] newArray(int size) {
+ return new AssociatedDevice[size];
+ }
+
+ @Override
+ public AssociatedDevice createFromParcel(@NonNull Parcel in) {
+ return new AssociatedDevice(in);
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "AssociatedDevice { "
+ + "device = " + mDevice
+ + " }";
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ AssociatedDevice that = (AssociatedDevice) o;
+ if (getDeviceType() != that.getDeviceType()) return false;
+
+ // TODO(b/31972115): Take out this whole part ¯\_(ツ)_/¯
+ if (mDevice instanceof android.bluetooth.le.ScanResult
+ || mDevice instanceof android.net.wifi.ScanResult) {
+ return mDevice.toString().equals(that.mDevice.toString());
+ }
+
+ return java.util.Objects.equals(mDevice, that.mDevice);
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(mDevice);
+ }
+}
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 93748f8..93964b3 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -52,6 +52,7 @@
private final @Nullable MacAddress mDeviceMacAddress;
private final @Nullable CharSequence mDisplayName;
private final @Nullable String mDeviceProfile;
+ private final @Nullable AssociatedDevice mAssociatedDevice;
private final boolean mSelfManaged;
private final boolean mNotifyOnDeviceNearby;
@@ -78,8 +79,9 @@
*/
public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName,
@Nullable MacAddress macAddress, @Nullable CharSequence displayName,
- @Nullable String deviceProfile, boolean selfManaged, boolean notifyOnDeviceNearby,
- boolean revoked, long timeApprovedMs, long lastTimeConnectedMs) {
+ @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
+ boolean selfManaged, boolean notifyOnDeviceNearby, boolean revoked,
+ long timeApprovedMs, long lastTimeConnectedMs) {
if (id <= 0) {
throw new IllegalArgumentException("Association ID should be greater than 0");
}
@@ -96,6 +98,7 @@
mDeviceMacAddress = macAddress;
mDisplayName = displayName;
mDeviceProfile = deviceProfile;
+ mAssociatedDevice = associatedDevice;
mSelfManaged = selfManaged;
mNotifyOnDeviceNearby = notifyOnDeviceNearby;
@@ -160,6 +163,24 @@
}
/**
+ * Companion device that was associated. Note that this field is not persisted across sessions.
+ *
+ * Cast to expected device type before use:
+ *
+ * <ul>
+ * <li>for classic Bluetooth - {@link android.bluetooth.BluetoothDevice}</li>
+ * <li>for Bluetooth LE - {@link android.bluetooth.le.ScanResult}</li>
+ * <li>for WiFi - {@link android.net.wifi.ScanResult}</li>
+ * </ul>
+ *
+ * @return the companion device that was associated, or {@code null} if the device is
+ * self-managed.
+ */
+ public @Nullable Parcelable getAssociatedDevice() {
+ return mAssociatedDevice == null ? null : mAssociatedDevice.getDevice();
+ }
+
+ /**
* @return whether the association is managed by the companion application it belongs to.
* @see AssociationRequest.Builder#setSelfManaged(boolean)
* @hide
@@ -260,6 +281,7 @@
+ ", mDisplayName='" + mDisplayName + '\''
+ ", mDeviceProfile='" + mDeviceProfile + '\''
+ ", mSelfManaged=" + mSelfManaged
+ + ", mAssociatedDevice=" + mAssociatedDevice
+ ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby
+ ", mRevoked=" + mRevoked
+ ", mTimeApprovedMs=" + new Date(mTimeApprovedMs)
@@ -284,14 +306,15 @@
&& Objects.equals(mPackageName, that.mPackageName)
&& Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress)
&& Objects.equals(mDisplayName, that.mDisplayName)
- && Objects.equals(mDeviceProfile, that.mDeviceProfile);
+ && Objects.equals(mDeviceProfile, that.mDeviceProfile)
+ && Objects.equals(mAssociatedDevice, that.mAssociatedDevice);
}
@Override
public int hashCode() {
return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName,
- mDeviceProfile, mSelfManaged, mNotifyOnDeviceNearby, mRevoked, mTimeApprovedMs,
- mLastTimeConnectedMs);
+ mDeviceProfile, mAssociatedDevice, mSelfManaged, mNotifyOnDeviceNearby, mRevoked,
+ mTimeApprovedMs, mLastTimeConnectedMs);
}
@Override
@@ -309,6 +332,7 @@
dest.writeTypedObject(mDeviceMacAddress, 0);
dest.writeCharSequence(mDisplayName);
dest.writeString(mDeviceProfile);
+ dest.writeTypedObject(mAssociatedDevice, 0);
dest.writeBoolean(mSelfManaged);
dest.writeBoolean(mNotifyOnDeviceNearby);
@@ -326,6 +350,7 @@
mDeviceMacAddress = in.readTypedObject(MacAddress.CREATOR);
mDisplayName = in.readCharSequence();
mDeviceProfile = in.readString();
+ mAssociatedDevice = in.readTypedObject(AssociatedDevice.CREATOR);
mSelfManaged = in.readBoolean();
mNotifyOnDeviceNearby = in.readBoolean();
@@ -433,6 +458,7 @@
mOriginalInfo.mDeviceMacAddress,
mOriginalInfo.mDisplayName,
mOriginalInfo.mDeviceProfile,
+ mOriginalInfo.mAssociatedDevice,
mOriginalInfo.mSelfManaged,
mNotifyOnDeviceNearby,
mRevoked,
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 75ab115..a2277e9 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -154,6 +154,11 @@
private @Nullable CharSequence mDisplayName;
/**
+ * The device that was associated. Will be null for "self-managed" association.
+ */
+ private @Nullable AssociatedDevice mAssociatedDevice;
+
+ /**
* Whether the association is to be managed by the companion application.
*/
private final boolean mSelfManaged;
@@ -307,6 +312,11 @@
}
/** @hide */
+ public void setAssociatedDevice(AssociatedDevice associatedDevice) {
+ mAssociatedDevice = associatedDevice;
+ }
+
+ /** @hide */
@NonNull
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public List<DeviceFilter<?>> getDeviceFilters() {
@@ -439,6 +449,16 @@
/**
+ * The device that was associated. Will be null for "self-managed" association.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @Nullable AssociatedDevice getAssociatedDevice() {
+ return mAssociatedDevice;
+ }
+
+ /**
* The app package name of the application the association will belong to.
* Populated by the system.
*
@@ -503,6 +523,7 @@
"deviceFilters = " + mDeviceFilters + ", " +
"deviceProfile = " + mDeviceProfile + ", " +
"displayName = " + mDisplayName + ", " +
+ "associatedDevice = " + mAssociatedDevice + ", " +
"selfManaged = " + mSelfManaged + ", " +
"forceConfirmation = " + mForceConfirmation + ", " +
"packageName = " + mPackageName + ", " +
@@ -530,6 +551,7 @@
&& Objects.equals(mDeviceFilters, that.mDeviceFilters)
&& Objects.equals(mDeviceProfile, that.mDeviceProfile)
&& Objects.equals(mDisplayName, that.mDisplayName)
+ && Objects.equals(mAssociatedDevice, that.mAssociatedDevice)
&& mSelfManaged == that.mSelfManaged
&& mForceConfirmation == that.mForceConfirmation
&& Objects.equals(mPackageName, that.mPackageName)
@@ -550,6 +572,7 @@
_hash = 31 * _hash + Objects.hashCode(mDeviceFilters);
_hash = 31 * _hash + Objects.hashCode(mDeviceProfile);
_hash = 31 * _hash + Objects.hashCode(mDisplayName);
+ _hash = 31 * _hash + Objects.hashCode(mAssociatedDevice);
_hash = 31 * _hash + Boolean.hashCode(mSelfManaged);
_hash = 31 * _hash + Boolean.hashCode(mForceConfirmation);
_hash = 31 * _hash + Objects.hashCode(mPackageName);
@@ -568,17 +591,19 @@
int flg = 0;
if (mSingleDevice) flg |= 0x1;
- if (mSelfManaged) flg |= 0x10;
- if (mForceConfirmation) flg |= 0x20;
- if (mSkipPrompt) flg |= 0x400;
+ if (mSelfManaged) flg |= 0x20;
+ if (mForceConfirmation) flg |= 0x40;
+ if (mSkipPrompt) flg |= 0x800;
if (mDeviceProfile != null) flg |= 0x4;
if (mDisplayName != null) flg |= 0x8;
- if (mPackageName != null) flg |= 0x40;
- if (mDeviceProfilePrivilegesDescription != null) flg |= 0x100;
+ if (mAssociatedDevice != null) flg |= 0x10;
+ if (mPackageName != null) flg |= 0x80;
+ if (mDeviceProfilePrivilegesDescription != null) flg |= 0x200;
dest.writeInt(flg);
dest.writeParcelableList(mDeviceFilters, flags);
if (mDeviceProfile != null) dest.writeString(mDeviceProfile);
if (mDisplayName != null) dest.writeCharSequence(mDisplayName);
+ if (mAssociatedDevice != null) dest.writeTypedObject(mAssociatedDevice, flags);
if (mPackageName != null) dest.writeString(mPackageName);
dest.writeInt(mUserId);
if (mDeviceProfilePrivilegesDescription != null) dest.writeString(mDeviceProfilePrivilegesDescription);
@@ -598,18 +623,20 @@
int flg = in.readInt();
boolean singleDevice = (flg & 0x1) != 0;
- boolean selfManaged = (flg & 0x10) != 0;
- boolean forceConfirmation = (flg & 0x20) != 0;
- boolean skipPrompt = (flg & 0x400) != 0;
+ boolean selfManaged = (flg & 0x20) != 0;
+ boolean forceConfirmation = (flg & 0x40) != 0;
+ boolean skipPrompt = (flg & 0x800) != 0;
List<DeviceFilter<?>> deviceFilters = new ArrayList<>();
in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader(),
(Class<android.companion.DeviceFilter<?>>) (Class<?>)
android.companion.DeviceFilter.class);
String deviceProfile = (flg & 0x4) == 0 ? null : in.readString();
CharSequence displayName = (flg & 0x8) == 0 ? null : (CharSequence) in.readCharSequence();
- String packageName = (flg & 0x40) == 0 ? null : in.readString();
+ AssociatedDevice associatedDevice = (flg & 0x10) == 0
+ ? null : (AssociatedDevice) in.readTypedObject(AssociatedDevice.CREATOR);
+ String packageName = (flg & 0x80) == 0 ? null : in.readString();
int userId = in.readInt();
- String deviceProfilePrivilegesDescription = (flg & 0x100) == 0 ? null : in.readString();
+ String deviceProfilePrivilegesDescription = (flg & 0x200) == 0 ? null : in.readString();
long creationTime = in.readLong();
this.mSingleDevice = singleDevice;
@@ -620,6 +647,7 @@
com.android.internal.util.AnnotationValidations.validate(
DeviceProfile.class, null, mDeviceProfile);
this.mDisplayName = displayName;
+ this.mAssociatedDevice = associatedDevice;
this.mSelfManaged = selfManaged;
this.mForceConfirmation = forceConfirmation;
this.mPackageName = packageName;
@@ -648,10 +676,10 @@
};
@DataClass.Generated(
- time = 1649179640045L,
+ time = 1663088980513L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/companion/AssociationRequest.java",
- inputSignatures = "public static final java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_COMPUTER\nprivate final boolean mSingleDevice\nprivate final @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate final @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate final boolean mSelfManaged\nprivate final boolean mForceConfirmation\nprivate @android.annotation.Nullable java.lang.String mPackageName\nprivate @android.annotation.UserIdInt int mUserId\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate final long mCreationTime\nprivate boolean mSkipPrompt\npublic @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String getDeviceProfile()\npublic @android.annotation.Nullable java.lang.CharSequence getDisplayName()\npublic boolean isSelfManaged()\npublic boolean isForceConfirmation()\npublic boolean isSingleDevice()\npublic void setPackageName(java.lang.String)\npublic void setUserId(int)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic void setSkipPrompt(boolean)\npublic void setDisplayName(java.lang.CharSequence)\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate boolean mSelfManaged\nprivate boolean mForceConfirmation\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDisplayName(java.lang.CharSequence)\npublic @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setSelfManaged(boolean)\npublic @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setForceConfirmation(boolean)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genConstDefs=false)")
+ inputSignatures = "public static final java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_COMPUTER\nprivate final boolean mSingleDevice\nprivate final @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate final @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate @android.annotation.Nullable android.companion.AssociatedDevice mAssociatedDevice\nprivate final boolean mSelfManaged\nprivate final boolean mForceConfirmation\nprivate @android.annotation.Nullable java.lang.String mPackageName\nprivate @android.annotation.UserIdInt int mUserId\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate final long mCreationTime\nprivate boolean mSkipPrompt\npublic @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String getDeviceProfile()\npublic @android.annotation.Nullable java.lang.CharSequence getDisplayName()\npublic boolean isSelfManaged()\npublic boolean isForceConfirmation()\npublic boolean isSingleDevice()\npublic void setPackageName(java.lang.String)\npublic void setUserId(int)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic void setSkipPrompt(boolean)\npublic void setDisplayName(java.lang.CharSequence)\npublic void setAssociatedDevice(android.companion.AssociatedDevice)\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate boolean mSelfManaged\nprivate boolean mForceConfirmation\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDisplayName(java.lang.CharSequence)\npublic @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setSelfManaged(boolean)\npublic @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setForceConfirmation(boolean)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 4142bce..90973c3 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -145,7 +145,7 @@
* <li>for WiFi - {@link android.net.wifi.ScanResult}</li>
* </ul>
*
- * @deprecated use {@link #EXTRA_ASSOCIATION} instead.
+ * @deprecated use {@link AssociationInfo#getAssociatedDevice()} instead.
*/
@Deprecated
public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index 8d6c8e8d..1fc6bda 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -20,6 +20,7 @@
import android.content.pm.DataLoaderParamsParcel;
import android.content.pm.IOnChecksumsReadyListener;
import android.content.pm.IPackageInstallObserver2;
+import android.content.pm.PackageInstaller;
import android.content.IntentSender;
import android.os.ParcelFileDescriptor;
@@ -58,4 +59,6 @@
boolean isStaged();
int getInstallFlags();
+
+ void requestUserPreapproval(in PackageInstaller.PreapprovalDetails details, in IntentSender statusReceiver);
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 5b18273..d2fb1fb 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -170,6 +170,10 @@
/** {@hide} */
public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
+ /** @hide */
+ public static final String ACTION_CONFIRM_PRE_APPROVAL =
+ "android.content.pm.action.CONFIRM_PRE_APPROVAL";
+
/**
* An integer session ID that an operation is working with.
*
@@ -207,6 +211,17 @@
public static final String EXTRA_STATUS = "android.content.pm.extra.STATUS";
/**
+ * Indicate if the status is for a pre-approval request.
+ *
+ * If callers use the same {@link IntentSender} for both
+ * {@link Session#requestUserPreapproval(PreapprovalDetails, IntentSender)} and
+ * {@link Session#commit(IntentSender)}, they can use this to differentiate between them.
+ *
+ * @see Intent#getBooleanExtra(String, boolean)
+ */
+ public static final String EXTRA_PRE_APPROVAL = "android.content.pm.extra.PRE_APPROVAL";
+
+ /**
* Detailed string representation of the status, including raw details that
* are useful for debugging.
*
@@ -1667,6 +1682,41 @@
e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Attempt to request the approval before committing this session.
+ *
+ * For installers that have been granted the
+ * {@link android.Manifest.permission#REQUEST_INSTALL_PACKAGES REQUEST_INSTALL_PACKAGES}
+ * permission, they can request the approval from users before
+ * {@link Session#commit(IntentSender)} is called. This may require user intervention as
+ * well. The result of the request will be reported through the given callback.
+ *
+ * @param details the adequate context to this session for requesting the approval from
+ * users prior to commit.
+ * @param statusReceiver called when the state of the session changes.
+ * Intents sent to this receiver contain
+ * {@link #EXTRA_STATUS}. Refer to the individual
+ * status codes on how to handle them.
+ *
+ * @throws IllegalArgumentException when {@link PreapprovalDetails} is {@code null}.
+ * @throws IllegalArgumentException if {@link IntentSender} is {@code null}.
+ * @throws IllegalStateException if called on a multi-package session (no matter
+ * the parent session or any of the children sessions).
+ * @throws IllegalStateException if called again after this method has been called on
+ * this session.
+ * @throws SecurityException when the caller does not own this session.
+ */
+ public void requestUserPreapproval(@NonNull PreapprovalDetails details,
+ @NonNull IntentSender statusReceiver) {
+ Preconditions.checkArgument(details != null, "preapprovalDetails cannot be null.");
+ Preconditions.checkArgument(statusReceiver != null, "statusReceiver cannot be null.");
+ try {
+ mSession.requestUserPreapproval(details, statusReceiver);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
}
/**
@@ -2631,6 +2681,9 @@
/** {@hide} */
public int installerUid;
+ /** @hide */
+ public boolean isPreapprovalRequested;
+
/** {@hide} */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public SessionInfo() {
@@ -2678,6 +2731,7 @@
mSessionErrorCode = source.readInt();
mSessionErrorMessage = source.readString();
isCommitted = source.readBoolean();
+ isPreapprovalRequested = source.readBoolean();
rollbackDataPolicy = source.readInt();
createdMillis = source.readLong();
requireUserAction = source.readInt();
@@ -3099,7 +3153,7 @@
}
/**
- * Returns the set of session IDs that will be committed when this session is commited if
+ * Returns the set of session IDs that will be committed when this session is committed if
* this session is a multi-package session.
*/
@NonNull
@@ -3257,6 +3311,7 @@
dest.writeInt(mSessionErrorCode);
dest.writeString(mSessionErrorMessage);
dest.writeBoolean(isCommitted);
+ dest.writeBoolean(isPreapprovalRequested);
dest.writeInt(rollbackDataPolicy);
dest.writeLong(createdMillis);
dest.writeInt(requireUserAction);
diff --git a/core/java/android/credentials/ui/Entry.java b/core/java/android/credentials/ui/Entry.java
new file mode 100644
index 0000000..122c54a
--- /dev/null
+++ b/core/java/android/credentials/ui/Entry.java
@@ -0,0 +1,116 @@
+/*
+ * 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.credentials.ui;
+
+import android.annotation.NonNull;
+import android.app.slice.Slice;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * A credential, save, or action entry to be rendered.
+ *
+ * @hide
+ */
+public class Entry implements Parcelable {
+ // TODO: move to jetpack.
+ public static final String VERSION = "v1";
+ public static final Uri CREDENTIAL_MANAGER_ENTRY_URI = Uri.parse("credentialmanager.slice");
+ public static final String HINT_TITLE = "hint_title";
+ public static final String HINT_SUBTITLE = "hint_subtitle";
+ public static final String HINT_ICON = "hint_icon";
+
+ /**
+ * The intent extra key for the action chip {@code Entry} list when launching the UX activities.
+ */
+ public static final String EXTRA_ENTRY_LIST_ACTION_CHIP =
+ "android.credentials.ui.extra.ENTRY_LIST_ACTION_CHIP";
+ /**
+ * The intent extra key for the credential / save {@code Entry} list when launching the UX
+ * activities.
+ */
+ public static final String EXTRA_ENTRY_LIST_CREDENTIAL =
+ "android.credentials.ui.extra.ENTRY_LIST_CREDENTIAL";
+ /**
+ * The intent extra key for the authentication action {@code Entry} when launching the UX
+ * activities.
+ */
+ public static final String EXTRA_ENTRY_AUTHENTICATION_ACTION =
+ "android.credentials.ui.extra.ENTRY_AUTHENTICATION_ACTION";
+
+ // TODO: may be changed to other type depending on the service implementation.
+ private final int mId;
+
+ @NonNull
+ private final Slice mSlice;
+
+ protected Entry(@NonNull Parcel in) {
+ int entryId = in.readInt();
+ Slice slice = Slice.CREATOR.createFromParcel(in);
+
+ mId = entryId;
+ mSlice = slice;
+ AnnotationValidations.validate(NonNull.class, null, mSlice);
+ }
+
+ public Entry(int id, @NonNull Slice slice) {
+ mId = id;
+ mSlice = slice;
+ }
+
+ /**
+ * Returns the id of this entry that's unique within the context of the CredentialManager
+ * request.
+ */
+ public int getEntryId() {
+ return mId;
+ }
+
+ /**
+ * Returns the Slice to be rendered.
+ */
+ @NonNull
+ public Slice getSlice() {
+ return mSlice;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mId);
+ mSlice.writeToParcel(dest, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<Entry> CREATOR = new Creator<Entry>() {
+ @Override
+ public Entry createFromParcel(@NonNull Parcel in) {
+ return new Entry(in);
+ }
+
+ @Override
+ public Entry[] newArray(int size) {
+ return new Entry[size];
+ }
+ };
+}
diff --git a/core/java/android/credentials/ui/ProviderData.java b/core/java/android/credentials/ui/ProviderData.java
index 49e5e49..18e6ba4 100644
--- a/core/java/android/credentials/ui/ProviderData.java
+++ b/core/java/android/credentials/ui/ProviderData.java
@@ -17,11 +17,15 @@
package android.credentials.ui;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.AnnotationValidations;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Holds metadata and credential entries for a single provider.
*
@@ -36,13 +40,24 @@
public static final String EXTRA_PROVIDER_DATA_LIST =
"android.credentials.ui.extra.PROVIDER_DATA_LIST";
- // TODO: add entry data.
-
@NonNull
private final String mPackageName;
+ @NonNull
+ private final List<Entry> mCredentialEntries;
+ @NonNull
+ private final List<Entry> mActionChips;
+ @Nullable
+ private final Entry mAuthenticationEntry;
- public ProviderData(@NonNull String packageName) {
+ public ProviderData(
+ @NonNull String packageName,
+ @NonNull List<Entry> credentialEntries,
+ @NonNull List<Entry> actionChips,
+ @Nullable Entry authenticationEntry) {
mPackageName = packageName;
+ mCredentialEntries = credentialEntries;
+ mActionChips = actionChips;
+ mAuthenticationEntry = authenticationEntry;
}
/** Returns the provider package name. */
@@ -51,15 +66,46 @@
return mPackageName;
}
+ @NonNull
+ public List<Entry> getCredentialEntries() {
+ return mCredentialEntries;
+ }
+
+ @NonNull
+ public List<Entry> getActionChips() {
+ return mActionChips;
+ }
+
+ @Nullable
+ public Entry getAuthenticationEntry() {
+ return mAuthenticationEntry;
+ }
+
protected ProviderData(@NonNull Parcel in) {
String packageName = in.readString8();
mPackageName = packageName;
AnnotationValidations.validate(NonNull.class, null, mPackageName);
+
+ List<Entry> credentialEntries = new ArrayList<>();
+ in.readTypedList(credentialEntries, Entry.CREATOR);
+ mCredentialEntries = credentialEntries;
+ AnnotationValidations.validate(NonNull.class, null, mCredentialEntries);
+
+ List<Entry> actionChips = new ArrayList<>();
+ in.readTypedList(actionChips, Entry.CREATOR);
+ mActionChips = actionChips;
+ AnnotationValidations.validate(NonNull.class, null, mActionChips);
+
+ Entry authenticationEntry = in.readTypedObject(Entry.CREATOR);
+ mAuthenticationEntry = authenticationEntry;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mPackageName);
+ dest.writeTypedList(mCredentialEntries);
+ dest.writeTypedList(mActionChips);
+ dest.writeTypedObject(mAuthenticationEntry, flags);
}
@Override
diff --git a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
index 8305843..bc63686 100644
--- a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
+++ b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
@@ -299,7 +299,7 @@
if (isLegacyCompatibilityWalEnabled()) {
return SQLiteCompatibilityWalFlags.getWALSyncMode();
} else {
- return SQLiteGlobal.getDefaultSyncMode();
+ return SQLiteGlobal.getWALSyncMode();
}
} else {
return SQLiteGlobal.getDefaultSyncMode();
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index adeb722..7a55a5c 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -2017,7 +2017,7 @@
public static final int EVENT_PACKAGE_INSTALLED = 0x000b;
// Event for a package being uninstalled.
public static final int EVENT_PACKAGE_UNINSTALLED = 0x000c;
- // Event for a package being uninstalled.
+ // Event for an alarm being sent out to an app.
public static final int EVENT_ALARM = 0x000d;
// Record that we have decided we need to collect new stats data.
public static final int EVENT_COLLECT_EXTERNAL_STATS = 0x000e;
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 2c0be87..3bf9ca0 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -115,6 +115,7 @@
private final int mMaxStreamVolume;
private boolean mAffectedByRingerMode;
private boolean mNotificationOrRing;
+ private final boolean mNotifAliasRing;
private final Receiver mReceiver = new Receiver();
private Handler mHandler;
@@ -179,6 +180,8 @@
if (mNotificationOrRing) {
mRingerMode = mAudioManager.getRingerModeInternal();
}
+ mNotifAliasRing = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_alias_ring_notif_stream_types);
mZenMode = mNotificationManager.getZenMode();
if (hasAudioProductStrategies()) {
@@ -280,7 +283,15 @@
if (zenMuted) {
mSeekBar.setProgress(mLastAudibleStreamVolume, true);
} else if (mNotificationOrRing && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
- mSeekBar.setProgress(0, true);
+ /**
+ * the first variable above is preserved and the conditions below are made explicit
+ * so that when user attempts to slide the notification seekbar out of vibrate the
+ * seekbar doesn't wrongly snap back to 0 when the streams aren't aliased
+ */
+ if (mNotifAliasRing || mStreamType == AudioManager.STREAM_RING
+ || (mStreamType == AudioManager.STREAM_NOTIFICATION && mMuted)) {
+ mSeekBar.setProgress(0, true);
+ }
} else if (mMuted) {
mSeekBar.setProgress(0, true);
} else {
@@ -354,6 +365,7 @@
// set the time of stop volume
if ((mStreamType == AudioManager.STREAM_VOICE_CALL
|| mStreamType == AudioManager.STREAM_RING
+ || (!mNotifAliasRing && mStreamType == AudioManager.STREAM_NOTIFICATION)
|| mStreamType == AudioManager.STREAM_ALARM)) {
sStopVolumeTime = java.lang.System.currentTimeMillis();
}
@@ -632,8 +644,8 @@
}
private void updateVolumeSlider(int streamType, int streamValue) {
- final boolean streamMatch = mNotificationOrRing ? isNotificationOrRing(streamType)
- : (streamType == mStreamType);
+ final boolean streamMatch = mNotifAliasRing && mNotificationOrRing
+ ? isNotificationOrRing(streamType) : streamType == mStreamType;
if (mSeekBar != null && streamMatch && streamValue != -1) {
final boolean muted = mAudioManager.isStreamMute(mStreamType)
|| streamValue == 0;
diff --git a/core/java/android/service/contentcapture/ActivityEvent.java b/core/java/android/service/contentcapture/ActivityEvent.java
index d286942..699f42b 100644
--- a/core/java/android/service/contentcapture/ActivityEvent.java
+++ b/core/java/android/service/contentcapture/ActivityEvent.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.app.assist.ActivityId;
import android.app.usage.UsageEvents.Event;
import android.content.ComponentName;
import android.os.Parcel;
@@ -80,14 +81,25 @@
private final @NonNull ComponentName mComponentName;
private final @ActivityEventType int mType;
+ private final @NonNull ActivityId mActivityId;
/** @hide */
- public ActivityEvent(@NonNull ComponentName componentName, @ActivityEventType int type) {
+ public ActivityEvent(@NonNull ActivityId activityId,
+ @NonNull ComponentName componentName, @ActivityEventType int type) {
+ mActivityId = activityId;
mComponentName = componentName;
mType = type;
}
/**
+ * Gets the ActivityId of the activity associated with the event.
+ */
+ @NonNull
+ public ActivityId getActivityId() {
+ return mActivityId;
+ }
+
+ /**
* Gests the {@link ComponentName} of the activity associated with the event.
*/
@NonNull
@@ -129,6 +141,7 @@
@Override
public String toString() {
return new StringBuilder("ActivityEvent[").append(mComponentName.toShortString())
+ .append(", ActivityId: ").append(mActivityId)
.append("]:").append(getTypeAsString(mType)).toString();
}
@@ -141,6 +154,7 @@
public void writeToParcel(@NonNull Parcel parcel, int flags) {
parcel.writeParcelable(mComponentName, flags);
parcel.writeInt(mType);
+ parcel.writeParcelable(mActivityId, flags);
}
public static final @android.annotation.NonNull Creator<ActivityEvent> CREATOR =
@@ -149,9 +163,12 @@
@Override
@NonNull
public ActivityEvent createFromParcel(@NonNull Parcel parcel) {
- final ComponentName componentName = parcel.readParcelable(null, android.content.ComponentName.class);
+ final ComponentName componentName =
+ parcel.readParcelable(null, ComponentName.class);
final int eventType = parcel.readInt();
- return new ActivityEvent(componentName, eventType);
+ final ActivityId activityId =
+ parcel.readParcelable(null, ActivityId.class);
+ return new ActivityEvent(activityId, componentName, eventType);
}
@Override
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 4324442..aa45c20 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -42,8 +42,11 @@
private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
@Override
public void startDream(WindowManager.LayoutParams layoutParams,
- IDreamOverlayCallback callback) {
+ IDreamOverlayCallback callback, String dreamComponent,
+ boolean shouldShowComplications) {
mDreamOverlayCallback = callback;
+ mDreamComponent = ComponentName.unflattenFromString(dreamComponent);
+ mShowComplications = shouldShowComplications;
onStartDream(layoutParams);
}
};
@@ -56,10 +59,6 @@
@Nullable
@Override
public final IBinder onBind(@NonNull Intent intent) {
- mShowComplications = intent.getBooleanExtra(DreamService.EXTRA_SHOW_COMPLICATIONS,
- DreamService.DEFAULT_SHOW_COMPLICATIONS);
- mDreamComponent = intent.getParcelableExtra(DreamService.EXTRA_DREAM_COMPONENT,
- ComponentName.class);
return mDreamOverlay.asBinder();
}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 1391326..3c1fef0 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -31,9 +31,9 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
@@ -68,6 +68,8 @@
import com.android.internal.R;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.ObservableServiceConnection;
+import com.android.internal.util.PersistentServiceConnection;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -75,7 +77,8 @@
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
-import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -211,20 +214,8 @@
private static final String DREAM_META_DATA_ROOT_TAG = "dream";
/**
- * Extra containing a boolean for whether to show complications on the overlay.
- * @hide
- */
- public static final String EXTRA_SHOW_COMPLICATIONS =
- "android.service.dreams.SHOW_COMPLICATIONS";
-
- /**
- * Extra containing the component name for the active dream.
- * @hide
- */
- public static final String EXTRA_DREAM_COMPONENT = "android.service.dreams.DREAM_COMPONENT";
-
- /**
* The default value for whether to show complications on the overlay.
+ *
* @hide
*/
public static final boolean DEFAULT_SHOW_COMPLICATIONS = false;
@@ -248,80 +239,72 @@
private boolean mDebug = false;
+ private ComponentName mDreamComponent;
+ private boolean mShouldShowComplications;
+
private DreamServiceWrapper mDreamServiceWrapper;
private Runnable mDispatchAfterOnAttachedToWindow;
- private final OverlayConnection mOverlayConnection;
+ private OverlayConnection mOverlayConnection;
- private static class OverlayConnection implements ServiceConnection {
+ private static class OverlayConnection extends PersistentServiceConnection<IDreamOverlay> {
// Overlay set during onBind.
private IDreamOverlay mOverlay;
- // A Queue of pending requests to execute on the overlay.
- private final ArrayDeque<Consumer<IDreamOverlay>> mRequests;
+ // A list of pending requests to execute on the overlay.
+ private final ArrayList<Consumer<IDreamOverlay>> mConsumers = new ArrayList<>();
- private boolean mBound;
-
- OverlayConnection() {
- mRequests = new ArrayDeque<>();
- }
-
- public void bind(Context context, @Nullable ComponentName overlayService,
- ComponentName dreamService) {
- if (overlayService == null) {
- return;
+ private final Callback<IDreamOverlay> mCallback = new Callback<IDreamOverlay>() {
+ @Override
+ public void onConnected(ObservableServiceConnection<IDreamOverlay> connection,
+ IDreamOverlay service) {
+ mOverlay = service;
+ for (Consumer<IDreamOverlay> consumer : mConsumers) {
+ consumer.accept(mOverlay);
+ }
}
- final ServiceInfo serviceInfo = fetchServiceInfo(context, dreamService);
-
- final Intent overlayIntent = new Intent();
- overlayIntent.setComponent(overlayService);
- overlayIntent.putExtra(EXTRA_SHOW_COMPLICATIONS,
- fetchShouldShowComplications(context, serviceInfo));
- overlayIntent.putExtra(EXTRA_DREAM_COMPONENT, dreamService);
-
- context.bindService(overlayIntent,
- this, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE);
- mBound = true;
- }
-
- public void unbind(Context context) {
- if (!mBound) {
- return;
+ @Override
+ public void onDisconnected(ObservableServiceConnection<IDreamOverlay> connection,
+ int reason) {
+ mOverlay = null;
}
+ };
- context.unbindService(this);
- mBound = false;
- }
-
- public void request(Consumer<IDreamOverlay> request) {
- mRequests.push(request);
- evaluate();
- }
-
- private void evaluate() {
- if (mOverlay == null) {
- return;
- }
-
- // Any new requests that arrive during this loop will be processed synchronously after
- // the loop exits.
- while (!mRequests.isEmpty()) {
- final Consumer<IDreamOverlay> request = mRequests.pop();
- request.accept(mOverlay);
- }
+ OverlayConnection(Context context,
+ Executor executor,
+ Handler handler,
+ ServiceTransformer<IDreamOverlay> transformer,
+ Intent serviceIntent,
+ int flags,
+ int minConnectionDurationMs,
+ int maxReconnectAttempts,
+ int baseReconnectDelayMs) {
+ super(context, executor, handler, transformer, serviceIntent, flags,
+ minConnectionDurationMs,
+ maxReconnectAttempts, baseReconnectDelayMs);
}
@Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- // Store Overlay and execute pending requests.
- mOverlay = IDreamOverlay.Stub.asInterface(service);
- evaluate();
+ public boolean bind() {
+ addCallback(mCallback);
+ return super.bind();
}
@Override
- public void onServiceDisconnected(ComponentName name) {
- // Clear Overlay binder to prevent further request processing.
- mOverlay = null;
+ public void unbind() {
+ removeCallback(mCallback);
+ super.unbind();
+ }
+
+ public void addConsumer(Consumer<IDreamOverlay> consumer) {
+ mConsumers.add(consumer);
+ if (mOverlay != null) {
+ consumer.accept(mOverlay);
+ }
+ }
+
+ public void removeConsumer(Consumer<IDreamOverlay> consumer) {
+ mConsumers.remove(consumer);
}
}
@@ -336,7 +319,6 @@
public DreamService() {
mDreamManager = IDreamManager.Stub.asInterface(ServiceManager.getService(DREAM_SERVICE));
- mOverlayConnection = new OverlayConnection();
}
/**
@@ -532,7 +514,7 @@
return mWindow;
}
- /**
+ /**
* Inflates a layout resource and set it to be the content view for this Dream.
* Behaves similarly to {@link android.app.Activity#setContentView(int)}.
*
@@ -955,6 +937,11 @@
@Override
public void onCreate() {
if (mDebug) Slog.v(mTag, "onCreate()");
+
+ mDreamComponent = new ComponentName(this, getClass());
+ mShouldShowComplications = fetchShouldShowComplications(this /*context*/,
+ fetchServiceInfo(this /*context*/, mDreamComponent));
+
super.onCreate();
}
@@ -996,13 +983,26 @@
public final IBinder onBind(Intent intent) {
if (mDebug) Slog.v(mTag, "onBind() intent = " + intent);
mDreamServiceWrapper = new DreamServiceWrapper();
+ final ComponentName overlayComponent = intent.getParcelableExtra(
+ EXTRA_DREAM_OVERLAY_COMPONENT, ComponentName.class);
// Connect to the overlay service if present.
- if (!mWindowless) {
- mOverlayConnection.bind(
+ if (!mWindowless && overlayComponent != null) {
+ final Resources resources = getResources();
+ final Intent overlayIntent = new Intent().setComponent(overlayComponent);
+
+ mOverlayConnection = new OverlayConnection(
/* context= */ this,
- intent.getParcelableExtra(EXTRA_DREAM_OVERLAY_COMPONENT, android.content.ComponentName.class),
- new ComponentName(this, getClass()));
+ getMainExecutor(),
+ mHandler,
+ IDreamOverlay.Stub::asInterface,
+ overlayIntent,
+ /* flags= */ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+ resources.getInteger(R.integer.config_minDreamOverlayDurationMs),
+ resources.getInteger(R.integer.config_dreamOverlayMaxReconnectAttempts),
+ resources.getInteger(R.integer.config_dreamOverlayReconnectTimeoutMs));
+
+ mOverlayConnection.bind();
}
return mDreamServiceWrapper;
@@ -1011,7 +1011,9 @@
@Override
public boolean onUnbind(Intent intent) {
// We must unbind from any overlay connection if we are unbound before finishing.
- mOverlayConnection.unbind(this);
+ if (mOverlayConnection != null) {
+ mOverlayConnection.unbind();
+ }
return super.onUnbind(intent);
}
@@ -1040,7 +1042,9 @@
}
mFinished = true;
- mOverlayConnection.unbind(this);
+ if (mOverlayConnection != null) {
+ mOverlayConnection.unbind();
+ }
if (mDreamToken == null) {
Slog.w(mTag, "Finish was called before the dream was attached.");
@@ -1337,19 +1341,26 @@
mWindow.getDecorView().addOnAttachStateChangeListener(
new View.OnAttachStateChangeListener() {
+ private Consumer<IDreamOverlay> mDreamStartOverlayConsumer;
+
@Override
public void onViewAttachedToWindow(View v) {
mDispatchAfterOnAttachedToWindow.run();
- // Request the DreamOverlay be told to dream with dream's window parameters
- // once the window has been attached.
- mOverlayConnection.request(overlay -> {
- try {
- overlay.startDream(mWindow.getAttributes(), mOverlayCallback);
- } catch (RemoteException e) {
- Log.e(mTag, "could not send window attributes:" + e);
- }
- });
+ if (mOverlayConnection != null) {
+ // Request the DreamOverlay be told to dream with dream's window
+ // parameters once the window has been attached.
+ mDreamStartOverlayConsumer = overlay -> {
+ try {
+ overlay.startDream(mWindow.getAttributes(), mOverlayCallback,
+ mDreamComponent.flattenToString(),
+ mShouldShowComplications);
+ } catch (RemoteException e) {
+ Log.e(mTag, "could not send window attributes:" + e);
+ }
+ };
+ mOverlayConnection.addConsumer(mDreamStartOverlayConsumer);
+ }
}
@Override
@@ -1362,6 +1373,9 @@
mActivity = null;
finish();
}
+ if (mOverlayConnection != null && mDreamStartOverlayConsumer != null) {
+ mOverlayConnection.removeConsumer(mDreamStartOverlayConsumer);
+ }
}
});
}
diff --git a/core/java/android/service/dreams/IDreamOverlay.aidl b/core/java/android/service/dreams/IDreamOverlay.aidl
index 2b6633d..05ebbfe 100644
--- a/core/java/android/service/dreams/IDreamOverlay.aidl
+++ b/core/java/android/service/dreams/IDreamOverlay.aidl
@@ -31,7 +31,11 @@
* @param params The {@link LayoutParams} for the associated DreamWindow, including the window
token of the Dream Activity.
* @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the
- * dream.
+ * dream.
+ * @param dreamComponent The component name of the dream service requesting overlay.
+ * @param shouldShowComplications Whether the dream overlay should show complications, e.g. clock
+ * and weather.
*/
- void startDream(in LayoutParams params, in IDreamOverlayCallback callback);
+ void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
+ in String dreamComponent, in boolean shouldShowComplications);
}
diff --git a/core/java/android/service/voice/AbstractHotwordDetector.java b/core/java/android/service/voice/AbstractHotwordDetector.java
index a2ca5a3..5d3852b 100644
--- a/core/java/android/service/voice/AbstractHotwordDetector.java
+++ b/core/java/android/service/voice/AbstractHotwordDetector.java
@@ -126,21 +126,26 @@
Slog.d(TAG, "updateState()");
}
throwIfDetectorIsNoLongerActive();
- synchronized (mLock) {
- updateStateLocked(options, sharedMemory, null /* callback */, mDetectorType);
+ try {
+ mManagerService.updateState(options, sharedMemory);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
- protected void updateStateLocked(@Nullable PersistableBundle options,
- @Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback,
+ protected void initAndVerifyDetector(
+ @Nullable PersistableBundle options,
+ @Nullable SharedMemory sharedMemory,
+ @NonNull IHotwordRecognitionStatusCallback callback,
int detectorType) {
if (DEBUG) {
- Slog.d(TAG, "updateStateLocked()");
+ Slog.d(TAG, "initAndVerifyDetector()");
}
Identity identity = new Identity();
identity.packageName = ActivityThread.currentOpPackageName();
try {
- mManagerService.updateState(identity, options, sharedMemory, callback, detectorType);
+ mManagerService.initAndVerifyDetector(identity, options, sharedMemory, callback,
+ detectorType);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index d01e7fe..d58f6d3 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -817,10 +817,8 @@
@Override
void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {
- // TODO: transition to use an API that is not updateState to provide
- // onHotwordDetectionServiceInitialized status to external callback
if (mSupportHotwordDetectionService) {
- updateStateLocked(options, sharedMemory, mInternalCallback,
+ initAndVerifyDetector(options, sharedMemory, mInternalCallback,
DETECTOR_TYPE_TRUSTED_HOTWORD_DSP);
}
try {
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index 02561c94..11688df 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -69,9 +69,7 @@
@Override
void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {
- // TODO: transition to use an API that is not updateState to provide
- // onHotwordDetectionServiceInitialized status to external callback
- updateStateLocked(options, sharedMemory,
+ initAndVerifyDetector(options, sharedMemory,
new InitializationStateListener(mHandler, mCallback),
DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE);
}
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 9fd8ecb..9b1d867 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -80,6 +80,7 @@
private final boolean mHasButtonUnderPad;
private final boolean mHasSensor;
private final boolean mHasBattery;
+ private final boolean mSupportsUsi;
private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
@GuardedBy("mMotionRanges")
@@ -462,7 +463,7 @@
int productId, String descriptor, boolean isExternal, int sources, int keyboardType,
KeyCharacterMap keyCharacterMap, @InputDeviceCountryCode int countryCode,
boolean hasVibrator, boolean hasMicrophone, boolean hasButtonUnderPad,
- boolean hasSensor, boolean hasBattery) {
+ boolean hasSensor, boolean hasBattery, boolean supportsUsi) {
mId = id;
mGeneration = generation;
mControllerNumber = controllerNumber;
@@ -481,6 +482,7 @@
mHasSensor = hasSensor;
mHasBattery = hasBattery;
mIdentifier = new InputDeviceIdentifier(descriptor, vendorId, productId);
+ mSupportsUsi = supportsUsi;
}
private InputDevice(Parcel in) {
@@ -501,6 +503,7 @@
mHasButtonUnderPad = in.readInt() != 0;
mHasSensor = in.readInt() != 0;
mHasBattery = in.readInt() != 0;
+ mSupportsUsi = in.readInt() != 0;
mIdentifier = new InputDeviceIdentifier(mDescriptor, mVendorId, mProductId);
int numRanges = in.readInt();
@@ -538,6 +541,7 @@
private boolean mHasBattery = false;
@InputDeviceCountryCode
private int mCountryCode = InputDeviceCountryCode.INVALID;
+ private boolean mSupportsUsi = false;
/** @see InputDevice#getId() */
public Builder setId(int id) {
@@ -641,12 +645,18 @@
return this;
}
+ /** @see InputDevice#supportsUsi() () */
+ public Builder setSupportsUsi(boolean supportsUsi) {
+ mSupportsUsi = supportsUsi;
+ return this;
+ }
+
/** Build {@link InputDevice}. */
public InputDevice build() {
return new InputDevice(mId, mGeneration, mControllerNumber, mName, mVendorId,
mProductId, mDescriptor, mIsExternal, mSources, mKeyboardType, mKeyCharacterMap,
mCountryCode, mHasVibrator, mHasMicrophone, mHasButtonUnderPad, mHasSensor,
- mHasBattery);
+ mHasBattery, mSupportsUsi);
}
}
@@ -1179,6 +1189,15 @@
}
/**
+ * Reports whether the device supports the Universal Stylus Initiative (USI) protocol for
+ * styluses.
+ * @hide
+ */
+ public boolean supportsUsi() {
+ return mSupportsUsi;
+ }
+
+ /**
* Provides information about the range of values for a particular {@link MotionEvent} axis.
*
* @see InputDevice#getMotionRange(int)
@@ -1308,6 +1327,7 @@
out.writeInt(mHasButtonUnderPad ? 1 : 0);
out.writeInt(mHasSensor ? 1 : 0);
out.writeInt(mHasBattery ? 1 : 0);
+ out.writeInt(mSupportsUsi ? 1 : 0);
final int numRanges = mMotionRanges.size();
out.writeInt(numRanges);
@@ -1361,6 +1381,8 @@
description.append(" Has mic: ").append(mHasMicrophone).append("\n");
+ description.append(" Supports USI: ").append(mSupportsUsi).append("\n");
+
description.append(" Sources: 0x").append(Integer.toHexString(mSources)).append(" (");
appendSourceDescriptionIfApplicable(description, SOURCE_KEYBOARD, "keyboard");
appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad");
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index eb8687c..7275780 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -59,6 +59,7 @@
private static final int HAS_INSETS_SIZE_OVERRIDE = 2;
private static Rect sTmpRect = new Rect();
+ private static Rect sTmpRect2 = new Rect();
/**
* The type of insets to provide.
@@ -88,6 +89,18 @@
*/
public InsetsSizeOverride[] insetsSizeOverrides = null;
+ /**
+ * This field, if set, is indicating the insets needs to be at least the given size inside the
+ * display cutout safe area. This will be compared to the insets size calculated based on other
+ * attributes, and will be applied when this is larger. This is independent of the
+ * PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT in LayoutParams, as this is not going to change
+ * the layout of the window, but only change the insets frame. This can be applied to insets
+ * calculated based on all three source frames.
+ *
+ * Be cautious, this will not be in effect for the window types whose insets size is overridden.
+ */
+ public Insets minimalInsetsSizeInDisplayCutoutSafe = null;
+
public InsetsFrameProvider(int type) {
this(type, SOURCE_FRAME, null, null);
}
@@ -202,7 +215,8 @@
public static void calculateInsetsFrame(Rect displayFrame, Rect containerBounds,
Rect displayCutoutSafe, Rect inOutFrame, int source, Insets insetsSize,
- @WindowManager.LayoutParams.PrivateFlags int privateFlags) {
+ @WindowManager.LayoutParams.PrivateFlags int privateFlags,
+ Insets displayCutoutSafeInsetsSize) {
boolean extendByCutout = false;
if (source == InsetsFrameProvider.SOURCE_DISPLAY) {
inOutFrame.set(displayFrame);
@@ -214,6 +228,33 @@
if (insetsSize == null) {
return;
}
+ if (displayCutoutSafeInsetsSize != null) {
+ sTmpRect2.set(inOutFrame);
+ }
+ calculateInsetsFrame(inOutFrame, insetsSize);
+
+ if (extendByCutout) {
+ WindowLayout.extendFrameByCutout(displayCutoutSafe, displayFrame, inOutFrame, sTmpRect);
+ }
+
+ if (displayCutoutSafeInsetsSize != null) {
+ // The insets is at least with the given size within the display cutout safe area.
+ // Calculate the smallest size.
+ calculateInsetsFrame(sTmpRect2, displayCutoutSafeInsetsSize);
+ WindowLayout.extendFrameByCutout(displayCutoutSafe, displayFrame, sTmpRect2, sTmpRect);
+ // If it's larger than previous calculation, use it.
+ if (sTmpRect2.contains(inOutFrame)) {
+ inOutFrame.set(sTmpRect2);
+ }
+ }
+ }
+
+ /**
+ * Calculate the insets frame given the insets size and the source frame.
+ * @param inOutFrame the source frame.
+ * @param insetsSize the insets size. Only the first non-zero value will be taken.
+ */
+ private static void calculateInsetsFrame(Rect inOutFrame, Insets insetsSize) {
// Only one side of the provider shall be applied. Check in the order of left - top -
// right - bottom, only the first non-zero value will be applied.
if (insetsSize.left != 0) {
@@ -227,10 +268,6 @@
} else {
inOutFrame.setEmpty();
}
-
- if (extendByCutout) {
- WindowLayout.extendFrameByCutout(displayCutoutSafe, displayFrame, inOutFrame, sTmpRect);
- }
}
/**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5c899e4..7b6ebf7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -15905,15 +15905,16 @@
}
/**
- * Returns whether the device is currently in touch mode. Touch mode is entered
- * once the user begins interacting with the device by touch, and affects various
- * things like whether focus is always visible to the user.
+ * Returns the touch mode state associated with this view.
*
- * If this view has no {@link ViewRootImpl} or {@link Display} attached, then it will return
- * the default touch mode value defined in
+ * Attached views return the touch mode state from the associated window's display.
+ * Detached views just return the default touch mode value defined in
* {@code com.android.internal.R.bool.config_defaultInTouchMode}.
*
- * @return Whether the device is in touch mode.
+ * Touch mode is entered once the user begins interacting with the device by touch, and
+ * affects various things like whether focus highlight is always visible to the user.
+ *
+ * @return the touch mode state associated with this view
*/
@ViewDebug.ExportedProperty
public boolean isInTouchMode() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1e2b241..b2b5f13 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1101,7 +1101,7 @@
// Update the last resource config in case the resource configuration was changed while
// activity relaunched.
- mLastConfigurationFromResources.setTo(getConfiguration());
+ updateLastConfigurationFromResources(getConfiguration());
}
private Configuration getConfiguration() {
@@ -5424,13 +5424,7 @@
// Update the display with new DisplayAdjustments.
updateInternalDisplay(mDisplay.getDisplayId(), localResources);
- final int lastLayoutDirection = mLastConfigurationFromResources.getLayoutDirection();
- final int currentLayoutDirection = config.getLayoutDirection();
- mLastConfigurationFromResources.setTo(config);
- if (lastLayoutDirection != currentLayoutDirection
- && mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
- mView.setLayoutDirection(currentLayoutDirection);
- }
+ updateLastConfigurationFromResources(config);
mView.dispatchConfigurationChanged(config);
// We could have gotten this {@link Configuration} update after we called
@@ -5444,6 +5438,17 @@
updateForceDarkMode();
}
+ private void updateLastConfigurationFromResources(Configuration resConfig) {
+ final int lastLayoutDirection = mLastConfigurationFromResources.getLayoutDirection();
+ final int currentLayoutDirection = resConfig.getLayoutDirection();
+ mLastConfigurationFromResources.setTo(resConfig);
+ // Update layout direction in case the language or screen layout is changed.
+ if (lastLayoutDirection != currentLayoutDirection && mView != null
+ && mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
+ mView.setLayoutDirection(currentLayoutDirection);
+ }
+ }
+
/**
* Return true if child is an ancestor of parent, (or equal to the parent).
*/
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index dbefcfb..70cfc3e 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -543,7 +543,6 @@
* {@link com.android.server.autofill.PresentationStatsEventLogger#getNoPresentationEventReason(int)}
* as well.</p>
*
- * TODO(b/233833662): Expose this as a public API in U.
* @hide
*/
@IntDef(prefix = { "COMMIT_REASON_" }, value = {
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index bbcf982..9f23f24 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -245,6 +245,23 @@
/**
* Set configuration and pass read-only data to hotword detection service.
+ *
+ * @param options Application configuration data to provide to the
+ * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or
+ * other contents that can be used to communicate with other processes.
+ * @param sharedMemory The unrestricted data blob to provide to the
+ * {@link HotwordDetectionService}. Use this to provide the hotword models data or other
+ * such data to the trusted process.
+ */
+ @EnforcePermission("MANAGE_HOTWORD_DETECTION")
+ void updateState(
+ in PersistableBundle options,
+ in SharedMemory sharedMemory);
+
+ /**
+ * Set configuration and pass read-only data to hotword detection service when creating
+ * the detector.
+ *
* Caller must provide an identity, used for permission tracking purposes.
* The uid/pid elements of the identity will be ignored by the server and replaced with the ones
* provided by binder.
@@ -259,7 +276,7 @@
* @param detectorType Indicate which detector is used.
*/
@EnforcePermission("MANAGE_HOTWORD_DETECTION")
- void updateState(
+ void initAndVerifyDetector(
in Identity originatorIdentity,
in PersistableBundle options,
in SharedMemory sharedMemory,
diff --git a/core/java/com/android/internal/util/ObservableServiceConnection.java b/core/java/com/android/internal/util/ObservableServiceConnection.java
new file mode 100644
index 0000000..3165d29
--- /dev/null
+++ b/core/java/com/android/internal/util/ObservableServiceConnection.java
@@ -0,0 +1,258 @@
+/*
+ * 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.internal.util;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.CallbackRegistry.NotifierCallback;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * {@link ObservableServiceConnection} is a concrete implementation of {@link ServiceConnection}
+ * that enables monitoring the status of a binder connection. It also aides in automatically
+ * converting a proxy into an internal wrapper type.
+ *
+ * @param <T> The type of the wrapper over the resulting service.
+ */
+public class ObservableServiceConnection<T> implements ServiceConnection {
+ /**
+ * An interface for converting the service proxy into a given internal wrapper type.
+ *
+ * @param <T> The type of the wrapper over the resulting service.
+ */
+ public interface ServiceTransformer<T> {
+ /**
+ * Called to convert the service proxy to the wrapper type.
+ *
+ * @param service The service proxy to create the wrapper type from.
+ * @return The wrapper type.
+ */
+ T convert(IBinder service);
+ }
+
+ /**
+ * An interface for listening to the connection status.
+ *
+ * @param <T> The wrapper type.
+ */
+ public interface Callback<T> {
+ /**
+ * Invoked when the service has been successfully connected to.
+ *
+ * @param connection The {@link ObservableServiceConnection} instance that is now connected
+ * @param service The service proxy converted into the typed wrapper.
+ */
+ void onConnected(ObservableServiceConnection<T> connection, T service);
+
+ /**
+ * Invoked when the service has been disconnected.
+ *
+ * @param connection The {@link ObservableServiceConnection} that is now disconnected.
+ * @param reason The reason for the disconnection.
+ */
+ void onDisconnected(ObservableServiceConnection<T> connection,
+ @DisconnectReason int reason);
+ }
+
+ /**
+ * Default state, service has not yet disconnected.
+ */
+ public static final int DISCONNECT_REASON_NONE = 0;
+ /**
+ * Disconnection was due to the resulting binding being {@code null}.
+ */
+ public static final int DISCONNECT_REASON_NULL_BINDING = 1;
+ /**
+ * Disconnection was due to the remote end disconnecting.
+ */
+ public static final int DISCONNECT_REASON_DISCONNECTED = 2;
+ /**
+ * Disconnection due to the binder dying.
+ */
+ public static final int DISCONNECT_REASON_BINDING_DIED = 3;
+ /**
+ * Disconnection from an explicit unbinding.
+ */
+ public static final int DISCONNECT_REASON_UNBIND = 4;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ DISCONNECT_REASON_NONE,
+ DISCONNECT_REASON_NULL_BINDING,
+ DISCONNECT_REASON_DISCONNECTED,
+ DISCONNECT_REASON_BINDING_DIED,
+ DISCONNECT_REASON_UNBIND
+ })
+ public @interface DisconnectReason {
+ }
+
+ private final Object mLock = new Object();
+ private final Context mContext;
+ private final Executor mExecutor;
+ private final ServiceTransformer<T> mTransformer;
+ private final Intent mServiceIntent;
+ private final int mFlags;
+
+ @GuardedBy("mLock")
+ private T mService;
+ @GuardedBy("mLock")
+ private boolean mBoundCalled = false;
+ @GuardedBy("mLock")
+ private int mLastDisconnectReason = DISCONNECT_REASON_NONE;
+
+ private final CallbackRegistry<Callback<T>, ObservableServiceConnection<T>, T>
+ mCallbackRegistry = new CallbackRegistry<>(
+ new NotifierCallback<Callback<T>, ObservableServiceConnection<T>, T>() {
+ @Override
+ public void onNotifyCallback(Callback<T> callback,
+ ObservableServiceConnection<T> sender,
+ int disconnectReason, T service) {
+ mExecutor.execute(() -> {
+ synchronized (mLock) {
+ if (service != null) {
+ callback.onConnected(sender, service);
+ } else if (mLastDisconnectReason != DISCONNECT_REASON_NONE) {
+ callback.onDisconnected(sender, disconnectReason);
+ }
+ }
+ });
+ }
+ });
+
+ /**
+ * Default constructor for {@link ObservableServiceConnection}.
+ *
+ * @param context The context from which the service will be bound with.
+ * @param executor The executor for connection callbacks to be delivered on
+ * @param transformer A {@link ObservableServiceConnection.ServiceTransformer} for transforming
+ * the resulting service into a desired type.
+ */
+ public ObservableServiceConnection(@NonNull Context context,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull ServiceTransformer<T> transformer,
+ Intent serviceIntent,
+ int flags) {
+ mContext = context;
+ mExecutor = executor;
+ mTransformer = transformer;
+ mServiceIntent = serviceIntent;
+ mFlags = flags;
+ }
+
+ /**
+ * Initiate binding to the service.
+ *
+ * @return {@code true} if initiating binding succeed, {@code false} if the binding failed or
+ * if this service is already bound. Regardless of the return value, you should later call
+ * {@link #unbind()} to release the connection.
+ */
+ public boolean bind() {
+ synchronized (mLock) {
+ if (mBoundCalled) {
+ return false;
+ }
+ final boolean bindResult =
+ mContext.bindService(mServiceIntent, mFlags, mExecutor, this);
+ mBoundCalled = true;
+ return bindResult;
+ }
+ }
+
+ /**
+ * Disconnect from the service if bound.
+ */
+ public void unbind() {
+ onDisconnected(DISCONNECT_REASON_UNBIND);
+ }
+
+ /**
+ * Adds a callback for receiving connection updates.
+ *
+ * @param callback The {@link Callback} to receive future updates.
+ */
+ public void addCallback(Callback<T> callback) {
+ mCallbackRegistry.add(callback);
+ mExecutor.execute(() -> {
+ synchronized (mLock) {
+ if (mService != null) {
+ callback.onConnected(this, mService);
+ } else if (mLastDisconnectReason != DISCONNECT_REASON_NONE) {
+ callback.onDisconnected(this, mLastDisconnectReason);
+ }
+ }
+ });
+ }
+
+ /**
+ * Removes previously added callback from receiving future connection updates.
+ *
+ * @param callback The {@link Callback} to be removed.
+ */
+ public void removeCallback(Callback<T> callback) {
+ synchronized (mLock) {
+ mCallbackRegistry.remove(callback);
+ }
+ }
+
+ private void onDisconnected(@DisconnectReason int reason) {
+ synchronized (mLock) {
+ if (!mBoundCalled) {
+ return;
+ }
+ mBoundCalled = false;
+ mLastDisconnectReason = reason;
+ mContext.unbindService(this);
+ mService = null;
+ mCallbackRegistry.notifyCallbacks(this, reason, null);
+ }
+ }
+
+ @Override
+ public final void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (mLock) {
+ mService = mTransformer.convert(service);
+ mLastDisconnectReason = DISCONNECT_REASON_NONE;
+ mCallbackRegistry.notifyCallbacks(this, mLastDisconnectReason, mService);
+ }
+ }
+
+ @Override
+ public final void onServiceDisconnected(ComponentName name) {
+ onDisconnected(DISCONNECT_REASON_DISCONNECTED);
+ }
+
+ @Override
+ public final void onBindingDied(ComponentName name) {
+ onDisconnected(DISCONNECT_REASON_BINDING_DIED);
+ }
+
+ @Override
+ public final void onNullBinding(ComponentName name) {
+ onDisconnected(DISCONNECT_REASON_NULL_BINDING);
+ }
+}
diff --git a/core/java/com/android/internal/util/PersistentServiceConnection.java b/core/java/com/android/internal/util/PersistentServiceConnection.java
new file mode 100644
index 0000000..d201734
--- /dev/null
+++ b/core/java/com/android/internal/util/PersistentServiceConnection.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.SystemClock;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.concurrent.Executor;
+
+/**
+ * {@link PersistentServiceConnection} is a concrete implementation of {@link ServiceConnection}
+ * that maintains the binder connection by handling reconnection when a failure occurs.
+ *
+ * @param <T> The transformed connection type handled by the service.
+ *
+ * When the target process is killed (by OOM-killer, force-stopped, crash, etc..) then this class
+ * will trigger a reconnection to the target. This should be used carefully.
+ *
+ * NOTE: This class does *not* handle package-updates -- i.e. even if the binding dies due to
+ * the target package being updated, this class won't reconnect. This is because this class doesn't
+ * know what to do when the service component has gone missing, for example. If the user of this
+ * class wants to restore the connection, then it should call {@link #unbind()} and {@link #bind}
+ * explicitly.
+ */
+public class PersistentServiceConnection<T> extends ObservableServiceConnection<T> {
+ private final Callback<T> mConnectionCallback = new Callback<T>() {
+ private long mConnectedTime;
+
+ @Override
+ public void onConnected(ObservableServiceConnection<T> connection, T service) {
+ mConnectedTime = mInjector.uptimeMillis();
+ }
+
+ @Override
+ public void onDisconnected(ObservableServiceConnection<T> connection,
+ @DisconnectReason int reason) {
+ if (reason == DISCONNECT_REASON_UNBIND) return;
+ synchronized (mLock) {
+ if ((mInjector.uptimeMillis() - mConnectedTime) > mMinConnectionDurationMs) {
+ mReconnectAttempts = 0;
+ bindInternalLocked();
+ } else {
+ scheduleConnectionAttemptLocked();
+ }
+ }
+ }
+ };
+
+ private final Object mLock = new Object();
+ private final Injector mInjector;
+ private final Handler mHandler;
+ private final int mMinConnectionDurationMs;
+ private final int mMaxReconnectAttempts;
+ private final int mBaseReconnectDelayMs;
+ @GuardedBy("mLock")
+ private int mReconnectAttempts;
+ @GuardedBy("mLock")
+ private Object mCancelToken;
+
+ private final Runnable mConnectRunnable = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ mCancelToken = null;
+ bindInternalLocked();
+ }
+ }
+ };
+
+ /**
+ * Default constructor for {@link PersistentServiceConnection}.
+ *
+ * @param context The context from which the service will be bound with.
+ * @param executor The executor for connection callbacks to be delivered on
+ * @param transformer A {@link ServiceTransformer} for transforming
+ */
+ public PersistentServiceConnection(Context context,
+ Executor executor,
+ Handler handler,
+ ServiceTransformer<T> transformer,
+ Intent serviceIntent,
+ int flags,
+ int minConnectionDurationMs,
+ int maxReconnectAttempts,
+ int baseReconnectDelayMs) {
+ this(context,
+ executor,
+ handler,
+ transformer,
+ serviceIntent,
+ flags,
+ minConnectionDurationMs,
+ maxReconnectAttempts,
+ baseReconnectDelayMs,
+ new Injector());
+ }
+
+ @VisibleForTesting
+ public PersistentServiceConnection(
+ Context context,
+ Executor executor,
+ Handler handler,
+ ServiceTransformer<T> transformer,
+ Intent serviceIntent,
+ int flags,
+ int minConnectionDurationMs,
+ int maxReconnectAttempts,
+ int baseReconnectDelayMs,
+ Injector injector) {
+ super(context, executor, transformer, serviceIntent, flags);
+ mHandler = handler;
+ mMinConnectionDurationMs = minConnectionDurationMs;
+ mMaxReconnectAttempts = maxReconnectAttempts;
+ mBaseReconnectDelayMs = baseReconnectDelayMs;
+ mInjector = injector;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean bind() {
+ synchronized (mLock) {
+ addCallback(mConnectionCallback);
+ mReconnectAttempts = 0;
+ return bindInternalLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean bindInternalLocked() {
+ return super.bind();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void unbind() {
+ synchronized (mLock) {
+ removeCallback(mConnectionCallback);
+ cancelPendingConnectionAttemptLocked();
+ super.unbind();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void cancelPendingConnectionAttemptLocked() {
+ if (mCancelToken != null) {
+ mHandler.removeCallbacksAndMessages(mCancelToken);
+ mCancelToken = null;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void scheduleConnectionAttemptLocked() {
+ cancelPendingConnectionAttemptLocked();
+
+ if (mReconnectAttempts >= mMaxReconnectAttempts) {
+ return;
+ }
+
+ final long reconnectDelayMs =
+ (long) Math.scalb(mBaseReconnectDelayMs, mReconnectAttempts);
+
+ mCancelToken = new Object();
+ mHandler.postDelayed(mConnectRunnable, mCancelToken, reconnectDelayMs);
+ mReconnectAttempts++;
+ }
+
+ /**
+ * Injector for testing
+ */
+ @VisibleForTesting
+ public static class Injector {
+ /**
+ * Returns milliseconds since boot, not counting time spent in deep sleep. Can be overridden
+ * in tests with a fake clock.
+ */
+ public long uptimeMillis() {
+ return SystemClock.uptimeMillis();
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index af9c5a5..52ffc98 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -17,6 +17,9 @@
package com.android.internal.widget;
+import static android.content.res.Resources.ID_NULL;
+
+import android.annotation.IdRes;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -96,6 +99,8 @@
private int mTopOffset;
private boolean mShowAtTop;
+ @IdRes
+ private int mIgnoreOffsetTopLimitViewId = ID_NULL;
private boolean mIsDragging;
private boolean mOpenOnClick;
@@ -156,6 +161,10 @@
mIsMaxCollapsedHeightSmallExplicit =
a.hasValue(R.styleable.ResolverDrawerLayout_maxCollapsedHeightSmall);
mShowAtTop = a.getBoolean(R.styleable.ResolverDrawerLayout_showAtTop, false);
+ if (a.hasValue(R.styleable.ResolverDrawerLayout_ignoreOffsetTopLimit)) {
+ mIgnoreOffsetTopLimitViewId = a.getResourceId(
+ R.styleable.ResolverDrawerLayout_ignoreOffsetTopLimit, ID_NULL);
+ }
a.recycle();
mScrollIndicatorDrawable = mContext.getDrawable(R.drawable.scroll_indicator_material);
@@ -577,12 +586,32 @@
dy -= 1.0f;
}
+ boolean isIgnoreOffsetLimitSet = false;
+ int ignoreOffsetLimit = 0;
+ View ignoreOffsetLimitView = findIgnoreOffsetLimitView();
+ if (ignoreOffsetLimitView != null) {
+ LayoutParams lp = (LayoutParams) ignoreOffsetLimitView.getLayoutParams();
+ ignoreOffsetLimit = ignoreOffsetLimitView.getBottom() + lp.bottomMargin;
+ isIgnoreOffsetLimitSet = true;
+ }
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.ignoreOffset) {
child.offsetTopAndBottom((int) dy);
+ } else if (isIgnoreOffsetLimitSet) {
+ int top = child.getTop();
+ int targetTop = Math.max(
+ (int) (ignoreOffsetLimit + lp.topMargin + dy),
+ lp.mFixedTop);
+ if (top != targetTop) {
+ child.offsetTopAndBottom(targetTop - top);
+ }
+ ignoreOffsetLimit = child.getBottom() + lp.bottomMargin;
}
}
final boolean isCollapsedOld = mCollapseOffset != 0;
@@ -1024,6 +1053,8 @@
final int rightEdge = width - getPaddingRight();
final int widthAvailable = rightEdge - leftEdge;
+ boolean isIgnoreOffsetLimitSet = false;
+ int ignoreOffsetLimit = 0;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
@@ -1036,9 +1067,24 @@
continue;
}
+ if (mIgnoreOffsetTopLimitViewId != ID_NULL && !isIgnoreOffsetLimitSet) {
+ if (mIgnoreOffsetTopLimitViewId == child.getId()) {
+ ignoreOffsetLimit = child.getBottom() + lp.bottomMargin;
+ isIgnoreOffsetLimitSet = true;
+ }
+ }
+
int top = ypos + lp.topMargin;
if (lp.ignoreOffset) {
- top -= mCollapseOffset;
+ if (!isDragging()) {
+ lp.mFixedTop = (int) (top - mCollapseOffset);
+ }
+ if (isIgnoreOffsetLimitSet) {
+ top = Math.max(ignoreOffsetLimit + lp.topMargin, (int) (top - mCollapseOffset));
+ ignoreOffsetLimit = top + child.getMeasuredHeight() + lp.bottomMargin;
+ } else {
+ top -= mCollapseOffset;
+ }
}
final int bottom = top + child.getMeasuredHeight();
@@ -1102,11 +1148,23 @@
mCollapsibleHeightReserved = ss.mCollapsibleHeightReserved;
}
+ private View findIgnoreOffsetLimitView() {
+ if (mIgnoreOffsetTopLimitViewId == ID_NULL) {
+ return null;
+ }
+ View v = findViewById(mIgnoreOffsetTopLimitViewId);
+ if (v != null && v != this && v.getParent() == this && v.getVisibility() != View.GONE) {
+ return v;
+ }
+ return null;
+ }
+
public static class LayoutParams extends MarginLayoutParams {
public boolean alwaysShow;
public boolean ignoreOffset;
public boolean hasNestedScrollIndicator;
public int maxHeight;
+ int mFixedTop;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index bb4ab39..1f64df4 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -101,9 +101,18 @@
static void android_os_Parcel_markForBinder(JNIEnv* env, jclass clazz, jlong nativePtr,
jobject binder)
{
+ LOG_ALWAYS_FATAL_IF(binder == nullptr, "Null binder specified for markForBinder");
+
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel) {
- parcel->markForBinder(ibinderForJavaObject(env, binder));
+ sp<IBinder> nBinder = ibinderForJavaObject(env, binder);
+
+ if (nBinder == nullptr) {
+ ALOGE("Native binder in markForBinder is null for non-null jobject");
+ return;
+ }
+
+ parcel->markForBinder(nBinder);
}
}
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index f28e2f6..9f88f33 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -798,6 +798,12 @@
if (env->IsInstanceOf(obj, gBinderOffsets.mClass)) {
JavaBBinderHolder* jbh = (JavaBBinderHolder*)
env->GetLongField(obj, gBinderOffsets.mObject);
+
+ if (jbh == nullptr) {
+ ALOGE("JavaBBinderHolder null on binder");
+ return nullptr;
+ }
+
return jbh->get(env, obj);
}
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index aece8c3..39ec037 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -57,9 +57,6 @@
const InputDeviceIdentifier& ident = deviceInfo.getIdentifier();
- // Not sure why, but JNI is complaining when I pass this through directly.
- jboolean hasMic = deviceInfo.hasMic() ? JNI_TRUE : JNI_FALSE;
-
ScopedLocalRef<jobject>
inputDeviceObj(env,
env->NewObject(gInputDeviceClassInfo.clazz, gInputDeviceClassInfo.ctor,
@@ -70,8 +67,9 @@
deviceInfo.isExternal(), deviceInfo.getSources(),
deviceInfo.getKeyboardType(), kcmObj.get(),
deviceInfo.getCountryCode(), deviceInfo.hasVibrator(),
- hasMic, deviceInfo.hasButtonUnderPad(),
- deviceInfo.hasSensor(), deviceInfo.hasBattery()));
+ deviceInfo.hasMic(), deviceInfo.hasButtonUnderPad(),
+ deviceInfo.hasSensor(), deviceInfo.hasBattery(),
+ deviceInfo.supportsUsi()));
const std::vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
for (const InputDeviceInfo::MotionRange& range: ranges) {
@@ -94,7 +92,7 @@
gInputDeviceClassInfo.ctor =
GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>",
"(IIILjava/lang/String;IILjava/lang/"
- "String;ZIILandroid/view/KeyCharacterMap;IZZZZZ)V");
+ "String;ZIILandroid/view/KeyCharacterMap;IZZZZZZ)V");
gInputDeviceClassInfo.addMotionRange = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz,
"addMotionRange", "(IIFFFFF)V");
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 6a200d05..a06e8a4 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -23,6 +23,7 @@
android:maxWidth="@dimen/resolver_max_width"
android:maxCollapsedHeight="@dimen/resolver_max_collapsed_height"
android:maxCollapsedHeightSmall="56dp"
+ android:ignoreOffsetTopLimit="@id/title_container"
android:id="@id/contentPanel">
<RelativeLayout
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 69c5043..2d832bc 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -9620,6 +9620,12 @@
<attr name="maxCollapsedHeightSmall" format="dimension" />
<!-- Whether the Drawer should be positioned at the top rather than at the bottom. -->
<attr name="showAtTop" format="boolean" />
+ <!-- By default `ResolverDrawerLayout`’s children views with `layout_ignoreOffset` property
+ set to true have a fixed position in the layout that won’t be affected by the drawer’s
+ movements. This property alternates that behavior. It specifies a child view’s id that
+ will push all ignoreOffset siblings below it when the drawer is moved i.e. setting the
+ top limit the ignoreOffset elements. -->
+ <attr name="ignoreOffsetTopLimit" format="reference" />
</declare-styleable>
<declare-styleable name="MessagingLinearLayout">
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 47faf2a..3834478 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -553,6 +553,14 @@
<!-- If this is true, then keep dreaming when undocking. -->
<bool name="config_keepDreamingWhenUndocking">false</bool>
+ <!-- The timeout (in ms) to wait before attempting to reconnect to the dream overlay service if
+ it becomes disconnected -->
+ <integer name="config_dreamOverlayReconnectTimeoutMs">1000</integer> <!-- 1 second -->
+ <!-- The maximum number of times to attempt reconnecting to the dream overlay service -->
+ <integer name="config_dreamOverlayMaxReconnectAttempts">3</integer>
+ <!-- The duration after which the dream overlay connection should be considered stable -->
+ <integer name="config_minDreamOverlayDurationMs">10000</integer> <!-- 10 seconds -->
+
<!-- Auto-rotation behavior -->
<!-- If true, enables auto-rotation features using the accelerometer.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b42db13..530891f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1626,6 +1626,7 @@
<java-symbol type="xml" name="password_kbd_symbols_shift" />
<java-symbol type="xml" name="power_profile" />
<java-symbol type="xml" name="power_profile_test" />
+ <java-symbol type="xml" name="irq_device_map" />
<java-symbol type="xml" name="sms_short_codes" />
<java-symbol type="xml" name="audio_assets" />
<java-symbol type="xml" name="global_keys" />
@@ -2238,6 +2239,9 @@
<java-symbol type="array" name="config_supportedDreamComplications" />
<java-symbol type="array" name="config_disabledDreamComponents" />
<java-symbol type="bool" name="config_dismissDreamOnActivityStart" />
+ <java-symbol type="integer" name="config_dreamOverlayReconnectTimeoutMs" />
+ <java-symbol type="integer" name="config_dreamOverlayMaxReconnectAttempts" />
+ <java-symbol type="integer" name="config_minDreamOverlayDurationMs" />
<java-symbol type="string" name="config_loggable_dream_prefix" />
<java-symbol type="string" name="config_dozeComponent" />
<java-symbol type="string" name="enable_explore_by_touch_warning_title" />
diff --git a/core/res/res/xml/irq_device_map.xml b/core/res/res/xml/irq_device_map.xml
new file mode 100644
index 0000000..86a44d6
--- /dev/null
+++ b/core/res/res/xml/irq_device_map.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** 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.
+*/
+-->
+<irq-device-map>
+ <!-- This file maps devices (chips) that can send IRQs to the CPU (and bring it out of sleep) to
+ logical subsystems in userspace code. Since each Android device has its own uniquely
+ designed chipset, this mapping is expected to be empty by default and should be overridden
+ by device specific configs.
+ This mapping helps the system to meaningfully attribute CPU wakeups to logical work that
+ happened on the device. The devices are referred to by their names as defined in the kernel.
+ Currently defined subsystems are:
+ - Alarm: Use this to denote wakeup alarms requested by apps via the AlarmManager API.
+
+ The overlay should use tags <device> and <subsystem> to describe this mapping in the
+ following way:
+
+ <irq-device-map>
+ <device name="device_name_1">
+ <subsystem>Subsystem1</subsystem>
+ <subsystem>Subsystem2</subsystem>
+ :
+ :
+ </device>
+ <device name="device_name_2">
+ :
+ </device>
+ :
+ </irq-device-map>
+
+ The tag <device> should have a "name" attribute specifying the kernel name of the device.
+ Each <device> tag can then enclose multiple <subsystem> tags. Each <subsystem> tag should
+ enclose the name of the logical subsystems (one of the ones defined above) as text.
+ Undefined subsystem names will be ignored by the framework.
+ -->
+</irq-device-map>
\ No newline at end of file
diff --git a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
index 6bd498c..d633843 100644
--- a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
+++ b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
@@ -124,7 +124,7 @@
assertTransportBehavior(input, expected);
}
- public void testMutiplePingPing() {
+ public void testMultiplePingPing() {
final byte[] input = concat(
generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, "red"),
generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 2, "green"));
@@ -248,7 +248,7 @@
}
@Override
- public int read(byte b[], int off, int len) throws IOException {
+ public int read(byte[] b, int off, int len) throws IOException {
// Instead of hanging indefinitely, wait a bit and claim that
// nothing was read, without hitting EOF
SystemClock.sleep(100);
@@ -259,13 +259,13 @@
private static class DelayingInputStream extends FilterInputStream {
private final long mDelay;
- public DelayingInputStream(InputStream in, long delay) {
+ DelayingInputStream(InputStream in, long delay) {
super(in);
mDelay = delay;
}
@Override
- public int read(byte b[], int off, int len) throws IOException {
+ public int read(byte[] b, int off, int len) throws IOException {
SystemClock.sleep(mDelay);
return super.read(b, off, len);
}
@@ -274,25 +274,25 @@
private static class DelayingOutputStream extends FilterOutputStream {
private final long mDelay;
- public DelayingOutputStream(OutputStream out, long delay) {
+ DelayingOutputStream(OutputStream out, long delay) {
super(out);
mDelay = delay;
}
@Override
- public void write(byte b[], int off, int len) throws IOException {
+ public void write(byte[] b, int off, int len) throws IOException {
SystemClock.sleep(mDelay);
super.write(b, off, len);
}
}
private static class TrickleInputStream extends FilterInputStream {
- public TrickleInputStream(InputStream in) {
+ TrickleInputStream(InputStream in) {
super(in);
}
@Override
- public int read(byte b[], int off, int len) throws IOException {
+ public int read(byte[] b, int off, int len) throws IOException {
return super.read(b, off, 1);
}
}
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index e154586..f6216fa 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1654,6 +1654,12 @@
android:exported="true">
</activity>
+ <activity android:name="android.app.activity.ActivityTransitionDrawableTest$TestActivity"
+ android:exported="true"
+ android:enabled="true"
+ android:theme="@style/Theme">
+ </activity>
+
<activity
android:name="android.os.TestVrActivity"
android:enableVrMode="com.android.frameworks.coretests/android.os.TestVrActivity$TestVrListenerService">
diff --git a/core/tests/coretests/src/android/app/activity/ActivityTransitionDrawableTest.java b/core/tests/coretests/src/android/app/activity/ActivityTransitionDrawableTest.java
new file mode 100644
index 0000000..2c4e443
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/ActivityTransitionDrawableTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.activity;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
+import android.transition.Fade;
+import android.view.View;
+import android.view.Window;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test for verifying Activity Transitions Drawable behavior
+ */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+@Presubmit
+public class ActivityTransitionDrawableTest {
+ private static final String LAUNCH_ON_START = "launch on start";
+
+ @Rule
+ public final ActivityTestRule<TestActivity> mActivityTestRule =
+ new ActivityTestRule<>(TestActivity.class, true);
+
+ @Test
+ public void stopTransitionDrawableAlphaRestored() throws Throwable {
+ mActivityTestRule.runOnUiThread(() -> {
+ Activity activity = mActivityTestRule.getActivity();
+ Intent intent = new Intent(activity, TestActivity.class);
+ intent.putExtra(LAUNCH_ON_START, true);
+ Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(activity).toBundle();
+ activity.startActivity(intent, bundle);
+ });
+
+ assertThat(TestActivity.activityAdded.await(5, TimeUnit.SECONDS)).isTrue();
+ TestActivity topActivity = TestActivity.sInstances.get(2);
+ TestActivity middleActivity = TestActivity.sInstances.get(1);
+ assertThat(topActivity.startedLatch.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(middleActivity.stoppedLatch.await(5, TimeUnit.SECONDS)).isTrue();
+ mActivityTestRule.runOnUiThread(() -> {
+ assertThat(middleActivity.getWindow().getDecorView().getBackground().getAlpha())
+ .isEqualTo(255);
+ });
+ }
+
+ public static class TestActivity extends Activity {
+ public static final ArrayList<TestActivity> sInstances = new ArrayList<TestActivity>();
+ public static CountDownLatch activityAdded = new CountDownLatch(3);
+
+ private boolean mLaunchOnStart = false;
+ public CountDownLatch startedLatch = new CountDownLatch(1);
+ public CountDownLatch stoppedLatch = new CountDownLatch(1);
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ getWindow().requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);
+ getWindow().setAllowEnterTransitionOverlap(false);
+ setContentView(new View(this));
+ Fade longFade = new Fade();
+ longFade.setDuration(2000);
+ getWindow().setEnterTransition(longFade);
+ getWindow().setExitTransition(longFade);
+ super.onCreate(savedInstanceState);
+ mLaunchOnStart = getIntent().getBooleanExtra(LAUNCH_ON_START, false);
+ sInstances.add(this);
+ activityAdded.countDown();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ if (mLaunchOnStart) {
+ mLaunchOnStart = false;
+ Intent intent = new Intent(this, TestActivity.class);
+ startActivity(intent);
+ }
+ startedLatch.countDown();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ stoppedLatch.countDown();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ sInstances.remove(this);
+ }
+ }
+}
diff --git a/core/tests/utiltests/Android.bp b/core/tests/utiltests/Android.bp
index adc3676..3798da5 100644
--- a/core/tests/utiltests/Android.bp
+++ b/core/tests/utiltests/Android.bp
@@ -34,6 +34,7 @@
"mockito-target-minus-junit4",
"androidx.test.ext.junit",
"truth-prebuilt",
+ "servicestests-utils",
],
libs: [
diff --git a/core/tests/utiltests/src/com/android/internal/util/ObservableServiceConnectionTest.java b/core/tests/utiltests/src/com/android/internal/util/ObservableServiceConnectionTest.java
new file mode 100644
index 0000000..d124ad9
--- /dev/null
+++ b/core/tests/utiltests/src/com/android/internal/util/ObservableServiceConnectionTest.java
@@ -0,0 +1,226 @@
+/*
+ * 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.internal.util;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.ObservableServiceConnection.ServiceTransformer;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayDeque;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.concurrent.Executor;
+
+@SmallTest
+public class ObservableServiceConnectionTest {
+ private static final ComponentName COMPONENT_NAME =
+ new ComponentName("test.package", "component");
+
+ public static class Foo {
+ int mValue;
+
+ Foo(int value) {
+ mValue = value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Foo)) return false;
+ Foo foo = (Foo) o;
+ return mValue == foo.mValue;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mValue);
+ }
+ }
+
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private Intent mIntent;
+ @Mock
+ private Foo mResult;
+ @Mock
+ private IBinder mBinder;
+ @Mock
+ private ServiceTransformer<Foo> mTransformer;
+ @Mock
+ private ObservableServiceConnection.Callback<Foo> mCallback;
+ private final FakeExecutor mExecutor = new FakeExecutor();
+ private ObservableServiceConnection<Foo> mConnection;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mConnection = new ObservableServiceConnection<>(
+ mContext,
+ mExecutor,
+ mTransformer,
+ mIntent,
+ /* flags= */ Context.BIND_AUTO_CREATE);
+ }
+
+ @After
+ public void tearDown() {
+ mExecutor.clearAll();
+ }
+
+ @Test
+ public void testConnect() {
+ // Register twice to ensure only one callback occurs.
+ mConnection.addCallback(mCallback);
+ mConnection.addCallback(mCallback);
+
+ mExecutor.runAll();
+ mConnection.bind();
+
+ // Ensure that no callbacks happen before connection.
+ verify(mCallback, never()).onConnected(any(), any());
+ verify(mCallback, never()).onDisconnected(any(), anyInt());
+
+ when(mTransformer.convert(mBinder)).thenReturn(mResult);
+ mConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+
+ mExecutor.runAll();
+ verify(mCallback, times(1)).onConnected(mConnection, mResult);
+ }
+
+ @Test
+ public void testDisconnectBeforeBind() {
+ mConnection.addCallback(mCallback);
+ mExecutor.runAll();
+ mConnection.onServiceDisconnected(COMPONENT_NAME);
+ mExecutor.runAll();
+ // Disconnects before binds should be ignored.
+ verify(mCallback, never()).onDisconnected(eq(mConnection), anyInt());
+ }
+
+ @Test
+ public void testDisconnect() {
+ mConnection.addCallback(mCallback);
+ mExecutor.runAll();
+ mConnection.bind();
+ mConnection.onServiceDisconnected(COMPONENT_NAME);
+
+ // Ensure the callback doesn't get triggered until the executor runs.
+ verify(mCallback, never()).onDisconnected(eq(mConnection), anyInt());
+ mExecutor.runAll();
+ // Ensure proper disconnect reason reported.
+ verify(mCallback, times(1)).onDisconnected(mConnection,
+ ObservableServiceConnection.DISCONNECT_REASON_DISCONNECTED);
+ // Verify unbound from service.
+ verify(mContext, times(1)).unbindService(mConnection);
+
+ clearInvocations(mContext);
+ // Ensure unbind after disconnect has no effect on the connection
+ mConnection.unbind();
+ verify(mContext, never()).unbindService(mConnection);
+ }
+
+ @Test
+ public void testBindingDied() {
+ mConnection.addCallback(mCallback);
+ mExecutor.runAll();
+ mConnection.bind();
+ mConnection.onBindingDied(COMPONENT_NAME);
+
+ // Ensure the callback doesn't get triggered until the executor runs.
+ verify(mCallback, never()).onDisconnected(eq(mConnection), anyInt());
+ mExecutor.runAll();
+ // Ensure proper disconnect reason reported.
+ verify(mCallback, times(1)).onDisconnected(mConnection,
+ ObservableServiceConnection.DISCONNECT_REASON_BINDING_DIED);
+ // Verify unbound from service.
+ verify(mContext, times(1)).unbindService(mConnection);
+ }
+
+ @Test
+ public void testNullBinding() {
+ mConnection.addCallback(mCallback);
+ mExecutor.runAll();
+ mConnection.bind();
+ mConnection.onNullBinding(COMPONENT_NAME);
+
+ // Ensure the callback doesn't get triggered until the executor runs.
+ verify(mCallback, never()).onDisconnected(eq(mConnection), anyInt());
+ mExecutor.runAll();
+ // Ensure proper disconnect reason reported.
+ verify(mCallback, times(1)).onDisconnected(mConnection,
+ ObservableServiceConnection.DISCONNECT_REASON_NULL_BINDING);
+ // Verify unbound from service.
+ verify(mContext, times(1)).unbindService(mConnection);
+ }
+
+ @Test
+ public void testUnbind() {
+ mConnection.addCallback(mCallback);
+ mExecutor.runAll();
+ mConnection.bind();
+ mConnection.unbind();
+
+ // Ensure the callback doesn't get triggered until the executor runs.
+ verify(mCallback, never()).onDisconnected(eq(mConnection), anyInt());
+ mExecutor.runAll();
+ verify(mCallback).onDisconnected(mConnection,
+ ObservableServiceConnection.DISCONNECT_REASON_UNBIND);
+ }
+
+ static class FakeExecutor implements Executor {
+ private final Queue<Runnable> mQueue = new ArrayDeque<>();
+
+ @Override
+ public void execute(Runnable command) {
+ mQueue.add(command);
+ }
+
+ public void runAll() {
+ while (!mQueue.isEmpty()) {
+ mQueue.remove().run();
+ }
+ }
+
+ public void clearAll() {
+ while (!mQueue.isEmpty()) {
+ mQueue.remove();
+ }
+ }
+ }
+}
diff --git a/core/tests/utiltests/src/com/android/internal/util/PersistentServiceConnectionTest.java b/core/tests/utiltests/src/com/android/internal/util/PersistentServiceConnectionTest.java
new file mode 100644
index 0000000..fee4654
--- /dev/null
+++ b/core/tests/utiltests/src/com/android/internal/util/PersistentServiceConnectionTest.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+
+import com.android.internal.util.ObservableServiceConnection.ServiceTransformer;
+import com.android.server.testutils.OffsettableClock;
+import com.android.server.testutils.TestHandler;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+import java.util.concurrent.Executor;
+
+public class PersistentServiceConnectionTest {
+ private static final ComponentName COMPONENT_NAME =
+ new ComponentName("test.package", "component");
+ private static final int MAX_RETRIES = 2;
+ private static final int RETRY_DELAY_MS = 1000;
+ private static final int CONNECTION_MIN_DURATION_MS = 5000;
+ private PersistentServiceConnection<Proxy> mConnection;
+
+ public static class Proxy {
+ }
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private Intent mIntent;
+ @Mock
+ private Proxy mResult;
+ @Mock
+ private IBinder mBinder;
+ @Mock
+ private ServiceTransformer<Proxy> mTransformer;
+ @Mock
+ private ObservableServiceConnection.Callback<Proxy> mCallback;
+ private TestHandler mHandler;
+ private final FakeExecutor mFakeExecutor = new FakeExecutor();
+ private OffsettableClock mClock;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mClock = new OffsettableClock.Stopped();
+ mHandler = spy(new TestHandler(null, mClock));
+
+ mConnection = new PersistentServiceConnection<>(
+ mContext,
+ mFakeExecutor,
+ mHandler,
+ mTransformer,
+ mIntent,
+ /* flags= */ Context.BIND_AUTO_CREATE,
+ CONNECTION_MIN_DURATION_MS,
+ MAX_RETRIES,
+ RETRY_DELAY_MS,
+ new TestInjector(mClock));
+
+ mClock.fastForward(1000);
+ mConnection.addCallback(mCallback);
+ when(mTransformer.convert(mBinder)).thenReturn(mResult);
+ }
+
+ @After
+ public void tearDown() {
+ mFakeExecutor.clearAll();
+ }
+
+ @Test
+ public void testConnect() {
+ mConnection.bind();
+ mConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+ mFakeExecutor.runAll();
+ // Ensure that we did not schedule a retry
+ verify(mHandler, never()).postDelayed(any(), anyLong());
+ }
+
+ @Test
+ public void testRetryOnBindFailure() {
+ mConnection.bind();
+
+ verify(mContext, times(1)).bindService(
+ eq(mIntent),
+ anyInt(),
+ eq(mFakeExecutor),
+ eq(mConnection));
+
+ // After disconnect, a reconnection should be attempted after the RETRY_DELAY_MS
+ mConnection.onServiceDisconnected(COMPONENT_NAME);
+ mFakeExecutor.runAll();
+ advanceTime(RETRY_DELAY_MS);
+ verify(mContext, times(2)).bindService(
+ eq(mIntent),
+ anyInt(),
+ eq(mFakeExecutor),
+ eq(mConnection));
+
+ // Reconnect attempt #2
+ mConnection.onServiceDisconnected(COMPONENT_NAME);
+ mFakeExecutor.runAll();
+ advanceTime(RETRY_DELAY_MS * 2);
+ verify(mContext, times(3)).bindService(
+ eq(mIntent),
+ anyInt(),
+ eq(mFakeExecutor),
+ eq(mConnection));
+
+ // There should be no more reconnect attempts, since the maximum is 2
+ mConnection.onServiceDisconnected(COMPONENT_NAME);
+ mFakeExecutor.runAll();
+ advanceTime(RETRY_DELAY_MS * 4);
+ verify(mContext, times(3)).bindService(
+ eq(mIntent),
+ anyInt(),
+ eq(mFakeExecutor),
+ eq(mConnection));
+ }
+
+ @Test
+ public void testManualUnbindDoesNotReconnect() {
+ mConnection.bind();
+
+ verify(mContext, times(1)).bindService(
+ eq(mIntent),
+ anyInt(),
+ eq(mFakeExecutor),
+ eq(mConnection));
+
+ mConnection.unbind();
+ // Ensure that disconnection after unbind does not reconnect.
+ mConnection.onServiceDisconnected(COMPONENT_NAME);
+ mFakeExecutor.runAll();
+ advanceTime(RETRY_DELAY_MS);
+
+ verify(mContext, times(1)).bindService(
+ eq(mIntent),
+ anyInt(),
+ eq(mFakeExecutor),
+ eq(mConnection));
+ }
+
+ private void advanceTime(long millis) {
+ mClock.fastForward(millis);
+ mHandler.timeAdvance();
+ }
+
+ static class TestInjector extends PersistentServiceConnection.Injector {
+ private final OffsettableClock mClock;
+
+ TestInjector(OffsettableClock clock) {
+ mClock = clock;
+ }
+
+ @Override
+ public long uptimeMillis() {
+ return mClock.now();
+ }
+ }
+
+ static class FakeExecutor implements Executor {
+ private final Queue<Runnable> mQueue = new ArrayDeque<>();
+
+ @Override
+ public void execute(Runnable command) {
+ mQueue.add(command);
+ }
+
+ public void runAll() {
+ while (!mQueue.isEmpty()) {
+ mQueue.remove().run();
+ }
+ }
+
+ public void clearAll() {
+ while (!mQueue.isEmpty()) {
+ mQueue.remove();
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index 0fb6ff8..b516e140 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -117,9 +117,7 @@
if (mWindowLayoutChangeListeners.containsKey(context)
// In theory this method can be called on the same consumer with different context.
|| mWindowLayoutChangeListeners.containsValue(consumer)) {
- throw new IllegalArgumentException(
- "Context or Consumer has already been registered for WindowLayoutInfo"
- + " callback.");
+ return;
}
if (!context.isUiContext()) {
throw new IllegalArgumentException("Context must be a UI Context, which should be"
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 82573b2..f615ad6 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -45,6 +45,9 @@
"src/com/android/wm/shell/util/**/*.java",
"src/com/android/wm/shell/common/split/SplitScreenConstants.java",
"src/com/android/wm/shell/sysui/ShellSharedConstants.java",
+ "src/com/android/wm/shell/common/TransactionPool.java",
+ "src/com/android/wm/shell/animation/Interpolators.java",
+ "src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
],
path: "src",
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
index 014f02b..8bba4404 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
@@ -15,38 +15,20 @@
*/
package com.android.wm.shell.startingsurface;
-import static android.view.Choreographer.CALLBACK_COMMIT;
import static android.view.View.GONE;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLASHSCREEN_EXIT_ANIM;
import android.animation.Animator;
-import android.animation.ValueAnimator;
import android.content.Context;
-import android.graphics.BlendMode;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.RadialGradient;
import android.graphics.Rect;
-import android.graphics.Shader;
-import android.util.MathUtils;
import android.util.Slog;
-import android.view.Choreographer;
import android.view.SurfaceControl;
-import android.view.SyncRtSurfaceTransactionApplier;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
import android.window.SplashScreenView;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.R;
-import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.TransactionPool;
/**
@@ -55,14 +37,8 @@
*/
public class SplashScreenExitAnimation implements Animator.AnimatorListener {
private static final boolean DEBUG_EXIT_ANIMATION = false;
- private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false;
private static final String TAG = StartingWindowController.TAG;
- private static final Interpolator ICON_INTERPOLATOR = new PathInterpolator(0.15f, 0f, 1f, 1f);
- private static final Interpolator MASK_RADIUS_INTERPOLATOR =
- new PathInterpolator(0f, 0f, 0.4f, 1f);
- private static final Interpolator SHIFT_UP_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f);
-
private final SurfaceControl mFirstWindowSurface;
private final Rect mFirstWindowFrame = new Rect();
private final SplashScreenView mSplashScreenView;
@@ -75,9 +51,6 @@
private final float mBrandingStartAlpha;
private final TransactionPool mTransactionPool;
- private ValueAnimator mMainAnimator;
- private ShiftUpAnimation mShiftUpAnimation;
- private RadialVanishAnimation mRadialVanishAnimation;
private Runnable mFinishCallback;
SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash,
@@ -121,187 +94,10 @@
}
void startAnimations() {
- mMainAnimator = createAnimator();
- mMainAnimator.start();
- }
-
- // fade out icon, reveal app, shift up main window
- private ValueAnimator createAnimator() {
- // reveal app
- final float transparentRatio = 0.8f;
- final int globalHeight = mSplashScreenView.getHeight();
- final int verticalCircleCenter = 0;
- final int finalVerticalLength = globalHeight - verticalCircleCenter;
- final int halfWidth = mSplashScreenView.getWidth() / 2;
- final int endRadius = (int) (0.5 + (1f / transparentRatio * (int)
- Math.sqrt(finalVerticalLength * finalVerticalLength + halfWidth * halfWidth)));
- final int[] colors = {Color.WHITE, Color.WHITE, Color.TRANSPARENT};
- final float[] stops = {0f, transparentRatio, 1f};
-
- mRadialVanishAnimation = new RadialVanishAnimation(mSplashScreenView);
- mRadialVanishAnimation.setCircleCenter(halfWidth, verticalCircleCenter);
- mRadialVanishAnimation.setRadius(0 /* initRadius */, endRadius);
- mRadialVanishAnimation.setRadialPaintParam(colors, stops);
-
- if (mFirstWindowSurface != null && mFirstWindowSurface.isValid()) {
- // shift up main window
- View occludeHoleView = new View(mSplashScreenView.getContext());
- if (DEBUG_EXIT_ANIMATION_BLEND) {
- occludeHoleView.setBackgroundColor(Color.BLUE);
- } else {
- occludeHoleView.setBackgroundColor(mSplashScreenView.getInitBackgroundColor());
- }
- final ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
- WindowManager.LayoutParams.MATCH_PARENT, mMainWindowShiftLength);
- mSplashScreenView.addView(occludeHoleView, params);
-
- mShiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView);
- }
-
- ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
- animator.setDuration(mAnimationDuration);
- animator.setInterpolator(Interpolators.LINEAR);
- animator.addListener(this);
- animator.addUpdateListener(a -> onAnimationProgress((float) a.getAnimatedValue()));
- return animator;
- }
-
- private static class RadialVanishAnimation extends View {
- private final SplashScreenView mView;
- private int mInitRadius;
- private int mFinishRadius;
-
- private final Point mCircleCenter = new Point();
- private final Matrix mVanishMatrix = new Matrix();
- private final Paint mVanishPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-
- RadialVanishAnimation(SplashScreenView target) {
- super(target.getContext());
- mView = target;
- mView.addView(this);
- mVanishPaint.setAlpha(0);
- }
-
- void onAnimationProgress(float linearProgress) {
- if (mVanishPaint.getShader() == null) {
- return;
- }
-
- final float radiusProgress = MASK_RADIUS_INTERPOLATOR.getInterpolation(linearProgress);
- final float alphaProgress = Interpolators.ALPHA_OUT.getInterpolation(linearProgress);
- final float scale = mInitRadius + (mFinishRadius - mInitRadius) * radiusProgress;
-
- mVanishMatrix.setScale(scale, scale);
- mVanishMatrix.postTranslate(mCircleCenter.x, mCircleCenter.y);
- mVanishPaint.getShader().setLocalMatrix(mVanishMatrix);
- mVanishPaint.setAlpha(Math.round(0xFF * alphaProgress));
-
- postInvalidate();
- }
-
- void setRadius(int initRadius, int finishRadius) {
- if (DEBUG_EXIT_ANIMATION) {
- Slog.v(TAG, "RadialVanishAnimation setRadius init: " + initRadius
- + " final " + finishRadius);
- }
- mInitRadius = initRadius;
- mFinishRadius = finishRadius;
- }
-
- void setCircleCenter(int x, int y) {
- if (DEBUG_EXIT_ANIMATION) {
- Slog.v(TAG, "RadialVanishAnimation setCircleCenter x: " + x + " y " + y);
- }
- mCircleCenter.set(x, y);
- }
-
- void setRadialPaintParam(int[] colors, float[] stops) {
- // setup gradient shader
- final RadialGradient rShader =
- new RadialGradient(0, 0, 1, colors, stops, Shader.TileMode.CLAMP);
- mVanishPaint.setShader(rShader);
- if (!DEBUG_EXIT_ANIMATION_BLEND) {
- // We blend the reveal gradient with the splash screen using DST_OUT so that the
- // splash screen is fully visible when radius = 0 (or gradient opacity is 0) and
- // fully invisible when radius = finishRadius AND gradient opacity is 1.
- mVanishPaint.setBlendMode(BlendMode.DST_OUT);
- }
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- canvas.drawRect(0, 0, mView.getWidth(), mView.getHeight(), mVanishPaint);
- }
- }
-
- private final class ShiftUpAnimation {
- private final float mFromYDelta;
- private final float mToYDelta;
- private final View mOccludeHoleView;
- private final SyncRtSurfaceTransactionApplier mApplier;
- private final Matrix mTmpTransform = new Matrix();
-
- ShiftUpAnimation(float fromYDelta, float toYDelta, View occludeHoleView) {
- mFromYDelta = fromYDelta;
- mToYDelta = toYDelta;
- mOccludeHoleView = occludeHoleView;
- mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
- }
-
- void onAnimationProgress(float linearProgress) {
- if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()
- || !mSplashScreenView.isAttachedToWindow()) {
- return;
- }
-
- final float progress = SHIFT_UP_INTERPOLATOR.getInterpolation(linearProgress);
- final float dy = mFromYDelta + (mToYDelta - mFromYDelta) * progress;
-
- mOccludeHoleView.setTranslationY(dy);
- mTmpTransform.setTranslate(0 /* dx */, dy);
-
- // set the vsyncId to ensure the transaction doesn't get applied too early.
- final SurfaceControl.Transaction tx = mTransactionPool.acquire();
- tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
- mTmpTransform.postTranslate(mFirstWindowFrame.left,
- mFirstWindowFrame.top + mMainWindowShiftLength);
-
- SyncRtSurfaceTransactionApplier.SurfaceParams
- params = new SyncRtSurfaceTransactionApplier.SurfaceParams
- .Builder(mFirstWindowSurface)
- .withMatrix(mTmpTransform)
- .withMergeTransaction(tx)
- .build();
- mApplier.scheduleApply(params);
-
- mTransactionPool.release(tx);
- }
-
- void finish() {
- if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()) {
- return;
- }
- final SurfaceControl.Transaction tx = mTransactionPool.acquire();
- if (mSplashScreenView.isAttachedToWindow()) {
- tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
-
- SyncRtSurfaceTransactionApplier.SurfaceParams
- params = new SyncRtSurfaceTransactionApplier.SurfaceParams
- .Builder(mFirstWindowSurface)
- .withWindowCrop(null)
- .withMergeTransaction(tx)
- .build();
- mApplier.scheduleApply(params);
- } else {
- tx.setWindowCrop(mFirstWindowSurface, null);
- tx.apply();
- }
- mTransactionPool.release(tx);
-
- Choreographer.getSfInstance().postCallback(CALLBACK_COMMIT,
- mFirstWindowSurface::release, null);
- }
+ SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface,
+ mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration,
+ mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay,
+ mAppRevealDuration, this);
}
private void reset() {
@@ -316,9 +112,6 @@
mFinishCallback = null;
}
}
- if (mShiftUpAnimation != null) {
- mShiftUpAnimation.finish();
- }
}
@Override
@@ -342,40 +135,4 @@
public void onAnimationRepeat(Animator animation) {
// ignore
}
-
- private void onFadeOutProgress(float linearProgress) {
- final float iconProgress = ICON_INTERPOLATOR.getInterpolation(
- getProgress(linearProgress, 0 /* delay */, mIconFadeOutDuration));
- final View iconView = mSplashScreenView.getIconView();
- final View brandingView = mSplashScreenView.getBrandingView();
- if (iconView != null) {
- iconView.setAlpha(mIconStartAlpha * (1 - iconProgress));
- }
- if (brandingView != null) {
- brandingView.setAlpha(mBrandingStartAlpha * (1 - iconProgress));
- }
- }
-
- private void onAnimationProgress(float linearProgress) {
- onFadeOutProgress(linearProgress);
-
- final float revealLinearProgress = getProgress(linearProgress, mAppRevealDelay,
- mAppRevealDuration);
-
- if (mRadialVanishAnimation != null) {
- mRadialVanishAnimation.onAnimationProgress(revealLinearProgress);
- }
-
- if (mShiftUpAnimation != null) {
- mShiftUpAnimation.onAnimationProgress(revealLinearProgress);
- }
- }
-
- private float getProgress(float linearProgress, long delay, long duration) {
- return MathUtils.constrain(
- (linearProgress * (mAnimationDuration) - delay) / duration,
- 0.0f,
- 1.0f
- );
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
new file mode 100644
index 0000000..3098e55
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -0,0 +1,358 @@
+/*
+ * 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.startingsurface;
+
+import static android.view.Choreographer.CALLBACK_COMMIT;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.BlendMode;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.util.MathUtils;
+import android.util.Slog;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+import android.view.SyncRtSurfaceTransactionApplier;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.window.SplashScreenView;
+
+import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.TransactionPool;
+
+/**
+ * Utilities for creating the splash screen window animations.
+ * @hide
+ */
+public class SplashScreenExitAnimationUtils {
+ private static final boolean DEBUG_EXIT_ANIMATION = false;
+ private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false;
+ private static final String TAG = "SplashScreenExitAnimationUtils";
+
+ private static final Interpolator ICON_INTERPOLATOR = new PathInterpolator(0.15f, 0f, 1f, 1f);
+ private static final Interpolator MASK_RADIUS_INTERPOLATOR =
+ new PathInterpolator(0f, 0f, 0.4f, 1f);
+ private static final Interpolator SHIFT_UP_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f);
+
+ /**
+ * Creates and starts the animator to fade out the icon, reveal the app, and shift up main
+ * window.
+ * @hide
+ */
+ public static void startAnimations(ViewGroup splashScreenView,
+ SurfaceControl firstWindowSurface, int mainWindowShiftLength,
+ TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
+ int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
+ int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
+ ValueAnimator animator =
+ createAnimator(splashScreenView, firstWindowSurface, mainWindowShiftLength,
+ transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
+ iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
+ animatorListener);
+ animator.start();
+ }
+
+ /**
+ * Creates the animator to fade out the icon, reveal the app, and shift up main window.
+ * @hide
+ */
+ private static ValueAnimator createAnimator(ViewGroup splashScreenView,
+ SurfaceControl firstWindowSurface, int mMainWindowShiftLength,
+ TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
+ int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
+ int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
+ // reveal app
+ final float transparentRatio = 0.8f;
+ final int globalHeight = splashScreenView.getHeight();
+ final int verticalCircleCenter = 0;
+ final int finalVerticalLength = globalHeight - verticalCircleCenter;
+ final int halfWidth = splashScreenView.getWidth() / 2;
+ final int endRadius = (int) (0.5 + (1f / transparentRatio * (int)
+ Math.sqrt(finalVerticalLength * finalVerticalLength + halfWidth * halfWidth)));
+ final int[] colors = {Color.WHITE, Color.WHITE, Color.TRANSPARENT};
+ final float[] stops = {0f, transparentRatio, 1f};
+
+ RadialVanishAnimation radialVanishAnimation = new RadialVanishAnimation(splashScreenView);
+ radialVanishAnimation.setCircleCenter(halfWidth, verticalCircleCenter);
+ radialVanishAnimation.setRadius(0 /* initRadius */, endRadius);
+ radialVanishAnimation.setRadialPaintParam(colors, stops);
+
+ View occludeHoleView = null;
+ ShiftUpAnimation shiftUpAnimation = null;
+ if (firstWindowSurface != null && firstWindowSurface.isValid()) {
+ // shift up main window
+ occludeHoleView = new View(splashScreenView.getContext());
+ if (DEBUG_EXIT_ANIMATION_BLEND) {
+ occludeHoleView.setBackgroundColor(Color.BLUE);
+ } else if (splashScreenView instanceof SplashScreenView) {
+ occludeHoleView.setBackgroundColor(
+ ((SplashScreenView) splashScreenView).getInitBackgroundColor());
+ } else {
+ occludeHoleView.setBackgroundColor(
+ isDarkTheme(splashScreenView.getContext()) ? Color.BLACK : Color.WHITE);
+ }
+ final ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT, mMainWindowShiftLength);
+ splashScreenView.addView(occludeHoleView, params);
+
+ shiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView,
+ firstWindowSurface, splashScreenView, transactionPool, firstWindowFrame,
+ mMainWindowShiftLength);
+ }
+
+ ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+ animator.setDuration(animationDuration);
+ animator.setInterpolator(Interpolators.LINEAR);
+ if (animatorListener != null) {
+ animator.addListener(animatorListener);
+ }
+ View finalOccludeHoleView = occludeHoleView;
+ ShiftUpAnimation finalShiftUpAnimation = shiftUpAnimation;
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (finalShiftUpAnimation != null) {
+ finalShiftUpAnimation.finish();
+ }
+ splashScreenView.removeView(radialVanishAnimation);
+ splashScreenView.removeView(finalOccludeHoleView);
+ }
+ });
+ animator.addUpdateListener(animation -> {
+ float linearProgress = (float) animation.getAnimatedValue();
+
+ // Fade out progress
+ final float iconProgress =
+ ICON_INTERPOLATOR.getInterpolation(getProgress(
+ linearProgress, 0 /* delay */, iconFadeOutDuration, animationDuration));
+ View iconView = null;
+ View brandingView = null;
+ if (splashScreenView instanceof SplashScreenView) {
+ iconView = ((SplashScreenView) splashScreenView).getIconView();
+ brandingView = ((SplashScreenView) splashScreenView).getBrandingView();
+ }
+ if (iconView != null) {
+ iconView.setAlpha(iconStartAlpha * (1 - iconProgress));
+ }
+ if (brandingView != null) {
+ brandingView.setAlpha(brandingStartAlpha * (1 - iconProgress));
+ }
+
+ final float revealLinearProgress = getProgress(linearProgress, appRevealDelay,
+ appRevealDuration, animationDuration);
+
+ radialVanishAnimation.onAnimationProgress(revealLinearProgress);
+
+ if (finalShiftUpAnimation != null) {
+ finalShiftUpAnimation.onAnimationProgress(revealLinearProgress);
+ }
+ });
+ return animator;
+ }
+
+ private static float getProgress(float linearProgress, long delay, long duration,
+ int animationDuration) {
+ return MathUtils.constrain(
+ (linearProgress * (animationDuration) - delay) / duration,
+ 0.0f,
+ 1.0f
+ );
+ }
+
+ private static boolean isDarkTheme(Context context) {
+ Configuration configuration = context.getResources().getConfiguration();
+ int nightMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ return nightMode == Configuration.UI_MODE_NIGHT_YES;
+ }
+
+ /**
+ * View which creates a circular reveal of the underlying view.
+ * @hide
+ */
+ @SuppressLint("ViewConstructor")
+ public static class RadialVanishAnimation extends View {
+ private final ViewGroup mView;
+ private int mInitRadius;
+ private int mFinishRadius;
+
+ private final Point mCircleCenter = new Point();
+ private final Matrix mVanishMatrix = new Matrix();
+ private final Paint mVanishPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+ public RadialVanishAnimation(ViewGroup target) {
+ super(target.getContext());
+ mView = target;
+ mView.addView(this);
+ if (getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
+ ((ViewGroup.MarginLayoutParams) getLayoutParams()).setMargins(0, 0, 0, 0);
+ }
+ mVanishPaint.setAlpha(0);
+ }
+
+ void onAnimationProgress(float linearProgress) {
+ if (mVanishPaint.getShader() == null) {
+ return;
+ }
+
+ final float radiusProgress = MASK_RADIUS_INTERPOLATOR.getInterpolation(linearProgress);
+ final float alphaProgress = Interpolators.ALPHA_OUT.getInterpolation(linearProgress);
+ final float scale = mInitRadius + (mFinishRadius - mInitRadius) * radiusProgress;
+
+ mVanishMatrix.setScale(scale, scale);
+ mVanishMatrix.postTranslate(mCircleCenter.x, mCircleCenter.y);
+ mVanishPaint.getShader().setLocalMatrix(mVanishMatrix);
+ mVanishPaint.setAlpha(Math.round(0xFF * alphaProgress));
+
+ postInvalidate();
+ }
+
+ void setRadius(int initRadius, int finishRadius) {
+ if (DEBUG_EXIT_ANIMATION) {
+ Slog.v(TAG, "RadialVanishAnimation setRadius init: " + initRadius
+ + " final " + finishRadius);
+ }
+ mInitRadius = initRadius;
+ mFinishRadius = finishRadius;
+ }
+
+ void setCircleCenter(int x, int y) {
+ if (DEBUG_EXIT_ANIMATION) {
+ Slog.v(TAG, "RadialVanishAnimation setCircleCenter x: " + x + " y " + y);
+ }
+ mCircleCenter.set(x, y);
+ }
+
+ void setRadialPaintParam(int[] colors, float[] stops) {
+ // setup gradient shader
+ final RadialGradient rShader =
+ new RadialGradient(0, 0, 1, colors, stops, Shader.TileMode.CLAMP);
+ mVanishPaint.setShader(rShader);
+ if (!DEBUG_EXIT_ANIMATION_BLEND) {
+ // We blend the reveal gradient with the splash screen using DST_OUT so that the
+ // splash screen is fully visible when radius = 0 (or gradient opacity is 0) and
+ // fully invisible when radius = finishRadius AND gradient opacity is 1.
+ mVanishPaint.setBlendMode(BlendMode.DST_OUT);
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawRect(0, 0, mView.getWidth(), mView.getHeight(), mVanishPaint);
+ }
+ }
+
+ /**
+ * Shifts up the main window.
+ * @hide
+ */
+ public static final class ShiftUpAnimation {
+ private final float mFromYDelta;
+ private final float mToYDelta;
+ private final View mOccludeHoleView;
+ private final SyncRtSurfaceTransactionApplier mApplier;
+ private final Matrix mTmpTransform = new Matrix();
+ private final SurfaceControl mFirstWindowSurface;
+ private final ViewGroup mSplashScreenView;
+ private final TransactionPool mTransactionPool;
+ private final Rect mFirstWindowFrame;
+ private final int mMainWindowShiftLength;
+
+ public ShiftUpAnimation(float fromYDelta, float toYDelta, View occludeHoleView,
+ SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
+ TransactionPool transactionPool, Rect firstWindowFrame,
+ int mainWindowShiftLength) {
+ mFromYDelta = fromYDelta;
+ mToYDelta = toYDelta;
+ mOccludeHoleView = occludeHoleView;
+ mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
+ mFirstWindowSurface = firstWindowSurface;
+ mSplashScreenView = splashScreenView;
+ mTransactionPool = transactionPool;
+ mFirstWindowFrame = firstWindowFrame;
+ mMainWindowShiftLength = mainWindowShiftLength;
+ }
+
+ void onAnimationProgress(float linearProgress) {
+ if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()
+ || !mSplashScreenView.isAttachedToWindow()) {
+ return;
+ }
+
+ final float progress = SHIFT_UP_INTERPOLATOR.getInterpolation(linearProgress);
+ final float dy = mFromYDelta + (mToYDelta - mFromYDelta) * progress;
+
+ mOccludeHoleView.setTranslationY(dy);
+ mTmpTransform.setTranslate(0 /* dx */, dy);
+
+ // set the vsyncId to ensure the transaction doesn't get applied too early.
+ final SurfaceControl.Transaction tx = mTransactionPool.acquire();
+ tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
+ mTmpTransform.postTranslate(mFirstWindowFrame.left,
+ mFirstWindowFrame.top + mMainWindowShiftLength);
+
+ SyncRtSurfaceTransactionApplier.SurfaceParams
+ params = new SyncRtSurfaceTransactionApplier.SurfaceParams
+ .Builder(mFirstWindowSurface)
+ .withMatrix(mTmpTransform)
+ .withMergeTransaction(tx)
+ .build();
+ mApplier.scheduleApply(params);
+
+ mTransactionPool.release(tx);
+ }
+
+ void finish() {
+ if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()) {
+ return;
+ }
+ final SurfaceControl.Transaction tx = mTransactionPool.acquire();
+ if (mSplashScreenView.isAttachedToWindow()) {
+ tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
+
+ SyncRtSurfaceTransactionApplier.SurfaceParams
+ params = new SyncRtSurfaceTransactionApplier.SurfaceParams
+ .Builder(mFirstWindowSurface)
+ .withWindowCrop(null)
+ .withMergeTransaction(tx)
+ .build();
+ mApplier.scheduleApply(params);
+ } else {
+ tx.setWindowCrop(mFirstWindowSurface, null);
+ tx.apply();
+ }
+ mTransactionPool.release(tx);
+
+ Choreographer.getSfInstance().postCallback(CALLBACK_COMMIT,
+ mFirstWindowSurface::release, null);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
index 3d8525b..1dc03b9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
@@ -18,12 +18,12 @@
import android.platform.test.annotations.Presubmit
import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
import org.junit.Test
/** Base class for pip expand tests */
abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
- protected val testApp = FixedOrientationAppHelper(instrumentation)
+ protected val testApp = SimpleAppHelper(instrumentation)
/**
* Checks that the pip app window remains inside the display bounds throughout the whole
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 5e8a623..46fbe7c 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -1020,6 +1020,40 @@
return base::unexpected(std::nullopt);
}
+template <typename TChar, typename SP>
+base::expected<size_t, NullOrIOError> ResStringPool::stringIndex(
+ SP sp, std::unordered_map<SP, size_t>& map) const
+{
+ AutoMutex lock(mStringIndexLock);
+
+ if (map.empty()) {
+ // build string index on the first call
+ for (size_t i = 0; i < mHeader->stringCount; i++) {
+ base::expected<SP, NullOrIOError> s;
+ if constexpr(std::is_same_v<TChar, char16_t>) {
+ s = stringAt(i);
+ } else {
+ s = string8At(i);
+ }
+ if (s.has_value()) {
+ const auto r = map.insert({*s, i});
+ if (!r.second) {
+ ALOGE("failed to build string index, string id=%zu\n", i);
+ }
+ } else {
+ return base::unexpected(s.error());
+ }
+ }
+ }
+
+ if (!map.empty()) {
+ const auto result = map.find(sp);
+ if (result != map.end())
+ return result->second;
+ }
+ return base::unexpected(std::nullopt);
+}
+
base::expected<size_t, NullOrIOError> ResStringPool::indexOfString(const char16_t* str,
size_t strLen) const
{
@@ -1027,134 +1061,28 @@
return base::unexpected(std::nullopt);
}
- if ((mHeader->flags&ResStringPool_header::UTF8_FLAG) != 0) {
- if (kDebugStringPoolNoisy) {
- ALOGI("indexOfString UTF-8: %s", String8(str, strLen).string());
- }
+ if (kDebugStringPoolNoisy) {
+ ALOGI("indexOfString (%s): %s", isUTF8() ? "UTF-8" : "UTF-16",
+ String8(str, strLen).string());
+ }
- // The string pool contains UTF 8 strings; we don't want to cause
- // temporary UTF-16 strings to be created as we search.
- if (mHeader->flags&ResStringPool_header::SORTED_FLAG) {
- // Do a binary search for the string... this is a little tricky,
- // because the strings are sorted with strzcmp16(). So to match
- // the ordering, we need to convert strings in the pool to UTF-16.
- // But we don't want to hit the cache, so instead we will have a
- // local temporary allocation for the conversions.
- size_t convBufferLen = strLen + 4;
- std::vector<char16_t> convBuffer(convBufferLen);
- ssize_t l = 0;
- ssize_t h = mHeader->stringCount-1;
-
- ssize_t mid;
- while (l <= h) {
- mid = l + (h - l)/2;
- int c = -1;
- const base::expected<StringPiece, NullOrIOError> s = string8At(mid);
- if (UNLIKELY(IsIOError(s))) {
- return base::unexpected(s.error());
- }
- if (s.has_value()) {
- char16_t* end = utf8_to_utf16(reinterpret_cast<const uint8_t*>(s->data()),
- s->size(), convBuffer.data(), convBufferLen);
- c = strzcmp16(convBuffer.data(), end-convBuffer.data(), str, strLen);
- }
- if (kDebugStringPoolNoisy) {
- ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n",
- s->data(), c, (int)l, (int)mid, (int)h);
- }
- if (c == 0) {
- if (kDebugStringPoolNoisy) {
- ALOGI("MATCH!");
- }
- return mid;
- } else if (c < 0) {
- l = mid + 1;
- } else {
- h = mid - 1;
- }
- }
- } else {
- // It is unusual to get the ID from an unsorted string block...
- // most often this happens because we want to get IDs for style
- // span tags; since those always appear at the end of the string
- // block, start searching at the back.
- String8 str8(str, strLen);
- const size_t str8Len = str8.size();
- for (int i=mHeader->stringCount-1; i>=0; i--) {
- const base::expected<StringPiece, NullOrIOError> s = string8At(i);
- if (UNLIKELY(IsIOError(s))) {
- return base::unexpected(s.error());
- }
- if (s.has_value()) {
- if (kDebugStringPoolNoisy) {
- ALOGI("Looking at %s, i=%d\n", s->data(), i);
- }
- if (str8Len == s->size()
- && memcmp(s->data(), str8.string(), str8Len) == 0) {
- if (kDebugStringPoolNoisy) {
- ALOGI("MATCH!");
- }
- return i;
- }
- }
- }
- }
-
+ base::expected<size_t, NullOrIOError> idx;
+ if (isUTF8()) {
+ auto str8 = String8(str, strLen);
+ idx = stringIndex<char>(StringPiece(str8.c_str(), str8.size()), mStringIndex8);
} else {
+ idx = stringIndex<char16_t>(StringPiece16(str, strLen), mStringIndex16);
+ }
+
+ if (UNLIKELY(!idx.has_value())) {
+ return base::unexpected(idx.error());
+ }
+
+ if (*idx < mHeader->stringCount) {
if (kDebugStringPoolNoisy) {
- ALOGI("indexOfString UTF-16: %s", String8(str, strLen).string());
+ ALOGI("MATCH! (idx=%zu)", *idx);
}
-
- if (mHeader->flags&ResStringPool_header::SORTED_FLAG) {
- // Do a binary search for the string...
- ssize_t l = 0;
- ssize_t h = mHeader->stringCount-1;
-
- ssize_t mid;
- while (l <= h) {
- mid = l + (h - l)/2;
- const base::expected<StringPiece16, NullOrIOError> s = stringAt(mid);
- if (UNLIKELY(IsIOError(s))) {
- return base::unexpected(s.error());
- }
- int c = s.has_value() ? strzcmp16(s->data(), s->size(), str, strLen) : -1;
- if (kDebugStringPoolNoisy) {
- ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n",
- String8(s->data(), s->size()).string(), c, (int)l, (int)mid, (int)h);
- }
- if (c == 0) {
- if (kDebugStringPoolNoisy) {
- ALOGI("MATCH!");
- }
- return mid;
- } else if (c < 0) {
- l = mid + 1;
- } else {
- h = mid - 1;
- }
- }
- } else {
- // It is unusual to get the ID from an unsorted string block...
- // most often this happens because we want to get IDs for style
- // span tags; since those always appear at the end of the string
- // block, start searching at the back.
- for (int i=mHeader->stringCount-1; i>=0; i--) {
- const base::expected<StringPiece16, NullOrIOError> s = stringAt(i);
- if (UNLIKELY(IsIOError(s))) {
- return base::unexpected(s.error());
- }
- if (kDebugStringPoolNoisy) {
- ALOGI("Looking at %s, i=%d\n", String8(s->data(), s->size()).string(), i);
- }
- if (s.has_value() && strLen == s->size() &&
- strzcmp16(s->data(), s->size(), str, strLen) == 0) {
- if (kDebugStringPoolNoisy) {
- ALOGI("MATCH!");
- }
- return i;
- }
- }
- }
+ return *idx;
}
return base::unexpected(std::nullopt);
}
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 9309091..24628cd 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -41,6 +41,7 @@
#include <array>
#include <map>
#include <memory>
+#include <unordered_map>
namespace android {
@@ -562,8 +563,17 @@
incfs::map_ptr<uint32_t> mStyles;
uint32_t mStylePoolSize; // number of uint32_t
+ // mStringIndex is used to quickly map a string to its ID
+ mutable Mutex mStringIndexLock;
+ mutable std::unordered_map<StringPiece, size_t> mStringIndex8;
+ mutable std::unordered_map<StringPiece16, size_t> mStringIndex16;
+
base::expected<StringPiece, NullOrIOError> stringDecodeAt(
size_t idx, incfs::map_ptr<uint8_t> str, size_t encLen) const;
+
+ template <typename TChar, typename SP=BasicStringPiece<TChar>>
+ base::expected<size_t, NullOrIOError> stringIndex(
+ SP str, std::unordered_map<SP, size_t>& map) const;
};
/**
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index 50aa6fe..6ae7dfb 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -44,6 +44,7 @@
void onRequestStreamVolume(int seq);
void onRequestTrackInfoList(int seq);
void onRequestCurrentTvInputId(int seq);
+ void onRequestStartRecording(in Uri programUri, int seq);
void onRequestSigning(
in String id, in String algorithm, in String alias, in byte[] data, int seq);
void onAdRequest(in AdRequest request, int Seq);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index c8c695f..84b9c9e 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -63,6 +63,7 @@
void notifyContentAllowed(in IBinder sessionToken, int userId);
void notifyContentBlocked(in IBinder sessionToken, in String rating, int userId);
void notifySignalStrength(in IBinder sessionToken, int stength, int userId);
+ void notifyRecordingStarted(in IBinder sessionToken, in String recordingId, int userId);
void setSurface(in IBinder sessionToken, in Surface surface, int userId);
void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 818c287..95b4ffa 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -53,6 +53,7 @@
void notifyContentAllowed();
void notifyContentBlocked(in String rating);
void notifySignalStrength(int strength);
+ void notifyRecordingStarted(in String recordingId);
void setSurface(in Surface surface);
void dispatchSurfaceChanged(int format, int width, int height);
void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index 32b08b7..6478057 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -43,6 +43,7 @@
void onRequestStreamVolume();
void onRequestTrackInfoList();
void onRequestCurrentTvInputId();
+ void onRequestStartRecording(in Uri programUri);
void onRequestSigning(in String id, in String algorithm, in String alias, in byte[] data);
void onAdRequest(in AdRequest request);
}
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index a72f34c..042cb15 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -81,6 +81,7 @@
private static final int DO_CREATE_MEDIA_VIEW = 27;
private static final int DO_RELAYOUT_MEDIA_VIEW = 28;
private static final int DO_REMOVE_MEDIA_VIEW = 29;
+ private static final int DO_NOTIFY_RECORDING_STARTED = 30;
private final HandlerCaller mCaller;
private Session mSessionImpl;
@@ -164,6 +165,10 @@
mSessionImpl.sendCurrentTvInputId((String) msg.obj);
break;
}
+ case DO_NOTIFY_RECORDING_STARTED: {
+ mSessionImpl.notifyRecordingStarted((String) msg.obj);
+ break;
+ }
case DO_SEND_SIGNING_RESULT: {
SomeArgs args = (SomeArgs) msg.obj;
mSessionImpl.sendSigningResult((String) args.arg1, (byte[]) args.arg2);
@@ -381,6 +386,12 @@
}
@Override
+ public void notifyRecordingStarted(String recordingId) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(
+ DO_NOTIFY_RECORDING_STARTED, recordingId));
+ }
+
+ @Override
public void setSurface(Surface surface) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface));
}
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index c0261f2..a27fd10 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -487,6 +487,18 @@
}
@Override
+ public void onRequestStartRecording(Uri programUri, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestStartRecording(programUri);
+ }
+ }
+
+ @Override
public void onRequestSigning(
String id, String algorithm, String alias, byte[] data, int seq) {
synchronized (mSessionCallbackRecordMap) {
@@ -1035,6 +1047,18 @@
}
}
+ void notifyRecordingStarted(@Nullable String recordingId) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.notifyRecordingStarted(mToken, recordingId, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
if (mToken == null) {
Log.w(TAG, "The session has been already released");
@@ -1696,6 +1720,15 @@
});
}
+ void postRequestStartRecording(Uri programUri) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestStartRecording(mSession, programUri);
+ }
+ });
+ }
+
void postRequestSigning(String id, String algorithm, String alias, byte[] data) {
mHandler.post(new Runnable() {
@Override
@@ -1847,6 +1880,15 @@
}
/**
+ * This is called when {@link TvInteractiveAppService.Session#RequestStartRecording} is
+ * called.
+ *
+ * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
+ */
+ public void onRequestStartRecording(Session session, Uri programUri) {
+ }
+
+ /**
* This is called when
* {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])} is
* called.
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index f5b4322..3d65effa 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -455,6 +455,13 @@
}
/**
+ * Receives started recording's ID.
+ * @hide
+ */
+ public void onRecordingStarted(@Nullable String recordingId) {
+ }
+
+ /**
* Receives signing result.
* @param signingId the ID to identify the request. It's the same as the corresponding ID in
* {@link Session#requestSigning(String, String, String, byte[])}
@@ -905,6 +912,27 @@
}
/**
+ * Requests starting of recording
+ *
+ * @hide
+ */
+ @CallSuper
+ public void requestStartRecording(@Nullable Uri programUri) {
+ executeOrPostRunnableOnMainThread(() -> {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestStartRecording");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestStartRecording(programUri);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestStartRecording", e);
+ }
+ });
+ }
+
+ /**
* Requests signing of the given data.
*
* <p>This is used when the corresponding server of the broadcast-independent interactive
@@ -1114,6 +1142,10 @@
onAdResponse(response);
}
+ void notifyRecordingStarted(String recordingId) {
+ onRecordingStarted(recordingId);
+ }
+
/**
* Notifies when the session state is changed.
*
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 53b0324..76ba69c 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -581,6 +581,22 @@
}
/**
+ * Alerts the TV interactive app that a recording has been started with recordingId
+ *
+ * @param recordingId The Id of the recording started
+ *
+ * @hide
+ */
+ public void notifyRecordingStarted(@Nullable String recordingId) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyRecordingStarted");
+ }
+ if (mSession != null) {
+ mSession.notifyRecordingStarted(recordingId);
+ }
+ }
+
+ /**
* Sends signing result to related TV interactive app.
*
* <p>This is used when the corresponding server of the broadcast-independent interactive
@@ -841,6 +857,19 @@
}
/**
+ * This is called when {@link TvInteractiveAppService.Session#requestStartRecording(Uri)}
+ * is called.
+ * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+ * @param programUri The program URI to record
+ *
+ * @hide
+ */
+ public void onRequestStartRecording(
+ @NonNull String iAppServiceId,
+ @Nullable Uri programUri) {
+ }
+
+ /**
* This is called when
* {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])} is
* called.
@@ -1164,6 +1193,20 @@
}
@Override
+ public void onRequestStartRecording(Session session, Uri programUri) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestStartRecording");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestStartRecording - session not created");
+ return;
+ }
+ if (mCallback != null) {
+ mCallback.onRequestStartRecording(mIAppServiceId, programUri);
+ }
+ }
+
+ @Override
public void onRequestSigning(
Session session, String id, String algorithm, String alias, byte[] data) {
if (DEBUG) {
diff --git a/media/java/android/media/tv/tuner/TunerVersionChecker.java b/media/java/android/media/tv/tuner/TunerVersionChecker.java
index 3e13bed..f29a93c 100644
--- a/media/java/android/media/tv/tuner/TunerVersionChecker.java
+++ b/media/java/android/media/tv/tuner/TunerVersionChecker.java
@@ -59,6 +59,10 @@
* Tuner version 2.0.
*/
public static final int TUNER_VERSION_2_0 = (2 << 16);
+ /**
+ * Tuner version 3.0.
+ */
+ public static final int TUNER_VERSION_3_0 = (3 << 16);
/**
* Get the current running Tuner version.
diff --git a/media/java/android/media/tv/tuner/dvr/DvrPlayback.java b/media/java/android/media/tv/tuner/dvr/DvrPlayback.java
index 11e6999..0680240 100644
--- a/media/java/android/media/tv/tuner/dvr/DvrPlayback.java
+++ b/media/java/android/media/tv/tuner/dvr/DvrPlayback.java
@@ -23,6 +23,7 @@
import android.media.tv.tuner.Tuner;
import android.media.tv.tuner.Tuner.Result;
import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.TunerVersionChecker;
import android.media.tv.tuner.filter.Filter;
import android.os.ParcelFileDescriptor;
import android.os.Process;
@@ -91,6 +92,7 @@
private native int nativeAttachFilter(Filter filter);
private native int nativeDetachFilter(Filter filter);
private native int nativeConfigureDvr(DvrSettings settings);
+ private native int nativeSetStatusCheckIntervalHint(long durationInMs);
private native int nativeStartDvr();
private native int nativeStopDvr();
private native int nativeFlushDvr();
@@ -177,6 +179,35 @@
}
/**
+ * Set playback buffer status check time interval.
+ *
+ * This status check time interval will be used by the Dvr to decide how often to evaluate
+ * data. The default value will be decided by HAL if it’s not set.
+ *
+ * <p>This functionality is only available in Tuner version 3.0 and higher and will otherwise
+ * return a {@link Tuner#RESULT_UNAVAILABLE}. Use {@link TunerVersionChecker#getTunerVersion()}
+ * to get the version information.
+ *
+ * @param durationInMs specifies the duration of the delay in milliseconds.
+ *
+ * @return one of the following results:
+ * {@link Tuner#RESULT_SUCCESS} if succeed,
+ * {@link Tuner#RESULT_UNAVAILABLE} if Dvr is unavailable or unsupported HAL versions,
+ * {@link Tuner#RESULT_NOT_INITIALIZED} if Dvr is not initialized,
+ * {@link Tuner#RESULT_INVALID_STATE} if Dvr is in a wrong state,
+ * {@link Tuner#RESULT_INVALID_ARGUMENT} if the input parameter is invalid.
+ */
+ @Result
+ public int setPlaybackBufferStatusCheckIntervalHint(long durationInMs) {
+ if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_3_0, "Set status check interval hint")) {
+ // no-op
+ return Tuner.RESULT_UNAVAILABLE;
+ }
+ return nativeSetStatusCheckIntervalHint(durationInMs);
+ }
+
+ /**
* Starts DVR.
*
* <p>Starts consuming playback data or producing data for recording.
diff --git a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
index e72026a..1a65832 100644
--- a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
+++ b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
@@ -22,6 +22,7 @@
import android.media.tv.tuner.Tuner;
import android.media.tv.tuner.Tuner.Result;
import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.TunerVersionChecker;
import android.media.tv.tuner.filter.Filter;
import android.os.ParcelFileDescriptor;
import android.os.Process;
@@ -53,6 +54,7 @@
private native int nativeAttachFilter(Filter filter);
private native int nativeDetachFilter(Filter filter);
private native int nativeConfigureDvr(DvrSettings settings);
+ private native int nativeSetStatusCheckIntervalHint(long durationInMs);
private native int nativeStartDvr();
private native int nativeStopDvr();
private native int nativeFlushDvr();
@@ -131,6 +133,35 @@
}
/**
+ * Set record buffer status check time interval.
+ *
+ * This status check time interval will be used by the Dvr to decide how often to evaluate
+ * data. The default value will be decided by HAL if it’s not set.
+ *
+ * <p>This functionality is only available in Tuner version 3.0 and higher and will otherwise
+ * return a {@link Tuner#RESULT_UNAVAILABLE}. Use {@link TunerVersionChecker#getTunerVersion()}
+ * to get the version information.
+ *
+ * @param durationInMs specifies the duration of the delay in milliseconds.
+ *
+ * @return one of the following results:
+ * {@link Tuner#RESULT_SUCCESS} if succeed,
+ * {@link Tuner#RESULT_UNAVAILABLE} if Dvr is unavailable or unsupported HAL versions,
+ * {@link Tuner#RESULT_NOT_INITIALIZED} if Dvr is not initialized,
+ * {@link Tuner#RESULT_INVALID_STATE} if Dvr is in a wrong state,
+ * {@link Tuner#RESULT_INVALID_ARGUMENT} if the input parameter is invalid.
+ */
+ @Result
+ public int setRecordBufferStatusCheckIntervalHint(long durationInMs) {
+ if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_3_0, "Set status check interval hint")) {
+ // no-op
+ return Tuner.RESULT_UNAVAILABLE;
+ }
+ return nativeSetStatusCheckIntervalHint(durationInMs);
+ }
+
+ /**
* Starts DVR.
*
* <p>Starts consuming playback data or producing data for recording.
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 96a3781..a0304bb 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -175,7 +175,7 @@
shared_libs: [
"android.hardware.graphics.bufferqueue@2.0",
- "android.hardware.tv.tuner-V1-ndk",
+ "android.hardware.tv.tuner-V2-ndk",
"libbinder_ndk",
"libandroid_runtime",
"libcutils",
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 244730b..c18edcd 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -4485,6 +4485,17 @@
return (jint)result;
}
+static jint android_media_tv_Tuner_set_status_check_interval_hint(JNIEnv *env, jobject dvr,
+ jlong durationInMs) {
+ sp<DvrClient> dvrClient = getDvrClient(env, dvr);
+ if (dvrClient == nullptr) {
+ ALOGD("Failed to set status check interval hint: dvr client not found");
+ return (int)Result::NOT_INITIALIZED;
+ }
+ Result result = dvrClient->setStatusCheckIntervalHint(durationInMs);
+ return (jint)result;
+}
+
static jint android_media_tv_Tuner_start_dvr(JNIEnv *env, jobject dvr) {
sp<DvrClient> dvrClient = getDvrClient(env, dvr);
if (dvrClient == nullptr) {
@@ -4828,6 +4839,8 @@
(void *)android_media_tv_Tuner_detach_filter },
{ "nativeConfigureDvr", "(Landroid/media/tv/tuner/dvr/DvrSettings;)I",
(void *)android_media_tv_Tuner_configure_dvr },
+ { "nativeSetStatusCheckIntervalHint", "(J)I",
+ (void *)android_media_tv_Tuner_set_status_check_interval_hint},
{ "nativeStartDvr", "()I", (void *)android_media_tv_Tuner_start_dvr },
{ "nativeStopDvr", "()I", (void *)android_media_tv_Tuner_stop_dvr },
{ "nativeFlushDvr", "()I", (void *)android_media_tv_Tuner_flush_dvr },
@@ -4844,6 +4857,8 @@
(void *)android_media_tv_Tuner_detach_filter},
{ "nativeConfigureDvr", "(Landroid/media/tv/tuner/dvr/DvrSettings;)I",
(void *)android_media_tv_Tuner_configure_dvr},
+ { "nativeSetStatusCheckIntervalHint", "(J)I",
+ (void *)android_media_tv_Tuner_set_status_check_interval_hint},
{ "nativeStartDvr", "()I", (void *)android_media_tv_Tuner_start_dvr},
{ "nativeStopDvr", "()I", (void *)android_media_tv_Tuner_stop_dvr},
{ "nativeFlushDvr", "()I", (void *)android_media_tv_Tuner_flush_dvr},
diff --git a/media/jni/tuner/DvrClient.cpp b/media/jni/tuner/DvrClient.cpp
index 05683b6..6e47052 100644
--- a/media/jni/tuner/DvrClient.cpp
+++ b/media/jni/tuner/DvrClient.cpp
@@ -303,7 +303,17 @@
return Result::INVALID_STATE;
}
+Result DvrClient::setStatusCheckIntervalHint(int64_t durationInMs) {
+ if (mTunerDvr == nullptr) {
+ return Result::INVALID_STATE;
+ }
+ if (durationInMs < 0) {
+ return Result::INVALID_ARGUMENT;
+ }
+ Status s = mTunerDvr->setStatusCheckIntervalHint(durationInMs);
+ return ClientHelper::getServiceSpecificErrorCode(s);
+}
/////////////// TunerDvrCallback ///////////////////////
TunerDvrCallback::TunerDvrCallback(sp<DvrClientCallback> dvrClientCallback)
: mDvrClientCallback(dvrClientCallback) {}
diff --git a/media/jni/tuner/DvrClient.h b/media/jni/tuner/DvrClient.h
index 61c0325..40ed75b 100644
--- a/media/jni/tuner/DvrClient.h
+++ b/media/jni/tuner/DvrClient.h
@@ -126,6 +126,11 @@
*/
Result close();
+ /**
+ * Set status check time interval.
+ */
+ Result setStatusCheckIntervalHint(int64_t durationInMs);
+
private:
/**
* An AIDL Tuner Dvr Singleton assigned at the first time the Tuner Client
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 21d4d80..8913799 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -139,8 +139,18 @@
}
ASurfaceControl* ASurfaceControl_fromSurfaceControl(JNIEnv* env, jobject surfaceControlObj) {
- return reinterpret_cast<ASurfaceControl*>(
- android_view_SurfaceControl_getNativeSurfaceControl(env, surfaceControlObj));
+ LOG_ALWAYS_FATAL_IF(!env,
+ "nullptr passed to ASurfaceControl_fromSurfaceControl as env argument");
+ LOG_ALWAYS_FATAL_IF(!surfaceControlObj,
+ "nullptr passed to ASurfaceControl_fromSurfaceControl as surfaceControlObj "
+ "argument");
+ SurfaceControl* surfaceControl =
+ android_view_SurfaceControl_getNativeSurfaceControl(env, surfaceControlObj);
+ LOG_ALWAYS_FATAL_IF(!surfaceControl,
+ "surfaceControlObj passed to ASurfaceControl_fromSurfaceControl is not "
+ "valid");
+ SurfaceControl_acquire(surfaceControl);
+ return reinterpret_cast<ASurfaceControl*>(surfaceControl);
}
struct ASurfaceControlStats {
@@ -200,8 +210,17 @@
}
ASurfaceTransaction* ASurfaceTransaction_fromTransaction(JNIEnv* env, jobject transactionObj) {
- return reinterpret_cast<ASurfaceTransaction*>(
- android_view_SurfaceTransaction_getNativeSurfaceTransaction(env, transactionObj));
+ LOG_ALWAYS_FATAL_IF(!env,
+ "nullptr passed to ASurfaceTransaction_fromTransaction as env argument");
+ LOG_ALWAYS_FATAL_IF(!transactionObj,
+ "nullptr passed to ASurfaceTransaction_fromTransaction as transactionObj "
+ "argument");
+ Transaction* transaction =
+ android_view_SurfaceTransaction_getNativeSurfaceTransaction(env, transactionObj);
+ LOG_ALWAYS_FATAL_IF(!transaction,
+ "surfaceControlObj passed to ASurfaceTransaction_fromTransaction is not "
+ "valid");
+ return reinterpret_cast<ASurfaceTransaction*>(transaction);
}
void ASurfaceTransaction_apply(ASurfaceTransaction* aSurfaceTransaction) {
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 9818ee7..56715b4 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -46,6 +46,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
+import android.companion.AssociatedDevice;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
@@ -331,6 +332,7 @@
private void onUserSelectedDevice(@NonNull DeviceFilterPair<?> selectedDevice) {
final MacAddress macAddress = selectedDevice.getMacAddress();
mRequest.setDisplayName(selectedDevice.getDisplayName());
+ mRequest.setAssociatedDevice(new AssociatedDevice(selectedDevice.getDevice()));
onAssociationApproved(macAddress);
}
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 2901705..2c24bf1 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -3,6 +3,7 @@
<string name="string_cancel">Cancel</string>
<string name="string_continue">Continue</string>
<string name="string_more_options">More options</string>
+ <string name="string_create_at_another_place">Create at another place</string>
<string name="string_no_thanks">No thanks</string>
<string name="passkey_creation_intro_title">A simple way to sign in safely</string>
<string name="passkey_creation_intro_body">Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more</string>
@@ -10,4 +11,5 @@
<string name="choose_provider_body">This provider will store passkeys and passwords for you and help you easily autofill and sign in. Learn more</string>
<string name="choose_create_option_title">Create a passkey at</string>
<string name="choose_sign_in_title">Use saved sign in</string>
+ <string name="create_passkey_at">Create passkey at</string>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 5918633..46bf19c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -1,6 +1,14 @@
package com.android.credentialmanager
+import android.app.slice.Slice
+import android.app.slice.SliceSpec
import android.content.Context
+import android.content.Intent
+import android.credentials.ui.Entry
+import android.credentials.ui.ProviderData
+import android.credentials.ui.RequestInfo
+import android.graphics.drawable.Icon
+import android.os.Binder
import com.android.credentialmanager.createflow.CreateOptionInfo
import com.android.credentialmanager.createflow.CreatePasskeyUiState
import com.android.credentialmanager.createflow.CreateScreenState
@@ -11,8 +19,82 @@
// Consider repo per screen, similar to view model?
class CredentialManagerRepo(
- private val context: Context
+ private val context: Context,
+ intent: Intent,
) {
+ private val requestInfo: RequestInfo
+ private val providerList: List<ProviderData>
+
+ init {
+ requestInfo = intent.extras?.getParcelable(
+ RequestInfo.EXTRA_REQUEST_INFO,
+ RequestInfo::class.java
+ ) ?: RequestInfo(
+ Binder(),
+ RequestInfo.TYPE_CREATE,
+ /*isFirstUsage=*/false
+ )
+
+ providerList = intent.extras?.getParcelableArrayList(
+ ProviderData.EXTRA_PROVIDER_DATA_LIST,
+ ProviderData::class.java
+ ) ?: testProviderList()
+ }
+
+ private fun testProviderList(): List<ProviderData> {
+ return listOf(
+ ProviderData(
+ "com.google",
+ listOf<Entry>(
+ newEntry(1, "elisa.beckett@gmail.com", "Elisa Backett"),
+ newEntry(2, "elisa.work@google.com", "Elisa Backett Work"),
+ ),
+ listOf<Entry>(
+ newEntry(3, "Go to Settings", ""),
+ newEntry(4, "Switch Account", ""),
+ ),
+ null
+ ),
+ ProviderData(
+ "com.dashlane",
+ listOf<Entry>(
+ newEntry(5, "elisa.beckett@dashlane.com", "Elisa Backett"),
+ newEntry(6, "elisa.work@dashlane.com", "Elisa Backett Work"),
+ ),
+ listOf<Entry>(
+ newEntry(7, "Manage Accounts", "Manage your accounts in the dashlane app"),
+ ),
+ null
+ ),
+ ProviderData(
+ "com.lastpass",
+ listOf<Entry>(
+ newEntry(8, "elisa.beckett@lastpass.com", "Elisa Backett"),
+ ),
+ listOf<Entry>(),
+ null
+ )
+
+ )
+ }
+
+ private fun newEntry(id: Int, title: String, subtitle: String): Entry {
+ val slice = Slice.Builder(
+ Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1)
+ )
+ .addText(title, null, listOf(Entry.HINT_TITLE))
+ .addText(subtitle, null, listOf(Entry.HINT_SUBTITLE))
+ .addIcon(
+ Icon.createWithResource(context, R.drawable.ic_passkey),
+ null,
+ listOf(Entry.HINT_ICON))
+ .build()
+ return Entry(
+ id,
+ slice
+ )
+ }
+
private fun getCredentialProviderList():
List<com.android.credentialmanager.getflow.ProviderInfo> {
return listOf(
@@ -140,8 +222,11 @@
companion object {
lateinit var repo: CredentialManagerRepo
- fun setup(context: Context) {
- repo = CredentialManagerRepo(context)
+ fun setup(
+ context: Context,
+ intent: Intent,
+ ) {
+ repo = CredentialManagerRepo(context, intent)
}
fun getInstance(): CredentialManagerRepo {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 5cd6a13..98c824c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -15,7 +15,7 @@
class CredentialSelectorActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- CredentialManagerRepo.setup(this)
+ CredentialManagerRepo.setup(this, intent)
val startDestination = intent.extras?.getString(
"start_destination",
"CREATE_PASSKEY"
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 5aa1e9b..044688b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -22,4 +22,5 @@
PASSKEY_INTRO,
PROVIDER_SELECTION,
CREATION_OPTION_SELECTION,
+ MORE_OPTIONS_SELECTION,
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
index fbec1bc..997519d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -19,9 +19,13 @@
import androidx.compose.material.Divider
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
import androidx.compose.material.ModalBottomSheetLayout
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -71,8 +75,13 @@
onOptionSelected = {viewModel.onCreateOptionSelected(it)},
onCancel = cancelActivity,
multiProvider = uiState.providers.size > 1,
- onMoreOptionSelected = {viewModel.onMoreOptionSelected()}
+ onMoreOptionSelected = {viewModel.onMoreOptionSelected(it)}
)
+ CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionSelectionCard(
+ providerInfo = uiState.selectedProvider!!,
+ onCancel = cancelActivity,
+ onBackButtonSelected = {viewModel.onBackButtonSelected(it)}
+ )
}
},
scrimColor = Color.Transparent,
@@ -202,6 +211,67 @@
}
}
+@Composable
+fun MoreOptionSelectionCard(
+ providerInfo: ProviderInfo,
+ onCancel: () -> Unit,
+ onBackButtonSelected: (String) -> Unit
+) {
+ Card(
+ backgroundColor = lightBackgroundColor,
+ ) {
+ Column() {
+ TopAppBar(
+ title = { Text(text = stringResource(R.string.string_more_options), style = Typography.subtitle1) },
+ backgroundColor = lightBackgroundColor,
+ elevation = 0.dp,
+ navigationIcon =
+ {
+ IconButton(onClick = { onBackButtonSelected(providerInfo.name) }) {
+ Icon(Icons.Filled.ArrowBack, "backIcon"
+ )
+ }
+ }
+ )
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ Text(
+ text = stringResource(R.string.create_passkey_at),
+ style = Typography.body1,
+ modifier = Modifier.padding(horizontal = 28.dp)
+ )
+ Card(
+ shape = Shapes.medium,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally)
+ ) {
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(2.dp)
+ ) {
+ }
+ }
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ Row(
+ horizontalArrangement = Arrangement.Start,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ CancelButton(stringResource(R.string.string_cancel), onCancel)
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ }
+}
+
@ExperimentalMaterialApi
@Composable
fun ProviderRow(providerInfo: ProviderInfo, onProviderSelected: (String) -> Unit) {
@@ -276,7 +346,7 @@
onOptionSelected: (String) -> Unit,
onCancel: () -> Unit,
multiProvider: Boolean,
- onMoreOptionSelected: () -> Unit,
+ onMoreOptionSelected: (String) -> Unit,
) {
Card(
backgroundColor = lightBackgroundColor,
@@ -318,7 +388,7 @@
}
if (multiProvider) {
item {
- MoreOptionRow(onSelect = onMoreOptionSelected)
+ MoreOptionRow(onSelect = { onMoreOptionSelected(providerInfo.name) })
}
}
}
@@ -389,7 +459,7 @@
shape = Shapes.large
) {
Text(
- text = stringResource(R.string.string_more_options),
+ text = stringResource(R.string.string_create_at_another_place),
style = Typography.h6,
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
index e42016d..15300de 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
@@ -52,7 +52,17 @@
}
}
- fun onMoreOptionSelected() {
- Log.d("Account Selector", "On more option selected")
+ fun onMoreOptionSelected(providerName: String) {
+ uiState = uiState.copy(
+ currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION,
+ selectedProvider = getProviderInfoByName(providerName)
+ )
+ }
+
+ fun onBackButtonSelected(providerName: String) {
+ uiState = uiState.copy(
+ currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
+ selectedProvider = getProviderInfoByName(providerName)
+ )
}
}
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 6669d6b..696ea4a 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -59,6 +59,10 @@
<action android:name="android.content.pm.action.CONFIRM_INSTALL" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
+ <intent-filter android:priority="1">
+ <action android:name="android.content.pm.action.CONFIRM_PRE_APPROVAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
</activity>
<activity android:name=".InstallStaging"
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index ac1a574f..bfab9be 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -59,7 +59,8 @@
String callingAttributionTag = null;
final boolean isSessionInstall =
- PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());
+ PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(intent.getAction())
+ || PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());
// If the activity was started via a PackageInstaller session, we retrieve the calling
// package from that session
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 20dc2cb..de76632 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -39,9 +39,11 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
+import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Process;
@@ -151,7 +153,7 @@
}
/**
- * Replace any dialog shown by the dialog with the one for the given {@link #createDialog id}.
+ * Replace any dialog shown by the dialog with the one for the given {@link #createDialog(int)}.
*
* @param id The dialog type to add
*/
@@ -296,6 +298,14 @@
? RESULT_OK : RESULT_FIRST_USER, result);
}
+ private static PackageInfo generateStubPackageInfo(String packageName) {
+ final PackageInfo info = new PackageInfo();
+ final ApplicationInfo aInfo = new ApplicationInfo();
+ info.applicationInfo = aInfo;
+ info.packageName = info.applicationInfo.packageName = packageName;
+ return info;
+ }
+
@Override
protected void onCreate(Bundle icicle) {
if (mLocalLOGV) Log.i(TAG, "creating for user " + getUserId());
@@ -315,6 +325,7 @@
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
final Intent intent = getIntent();
+ final String action = intent.getAction();
mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE);
mCallingAttributionTag = intent.getStringExtra(EXTRA_CALLING_ATTRIBUTION_TAG);
@@ -324,11 +335,11 @@
mOriginatingPackage = (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN)
? getPackageNameForUid(mOriginatingUid) : null;
- final Uri packageUri;
-
- if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction())) {
- final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
- final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
+ final Object packageSource;
+ if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(action)) {
+ final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
+ -1 /* defaultValue */);
+ final SessionInfo info = mInstaller.getSessionInfo(sessionId);
if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
finish();
@@ -336,18 +347,32 @@
}
mSessionId = sessionId;
- packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
+ packageSource = Uri.fromFile(new File(info.resolvedBaseCodePath));
+ mOriginatingURI = null;
+ mReferrerURI = null;
+ } else if (PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(action)) {
+ final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
+ -1 /* defaultValue */);
+ final SessionInfo info = mInstaller.getSessionInfo(sessionId);
+ if (info == null || !info.isPreapprovalRequested) {
+ Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
+ finish();
+ return;
+ }
+
+ mSessionId = sessionId;
+ packageSource = info;
mOriginatingURI = null;
mReferrerURI = null;
} else {
mSessionId = -1;
- packageUri = intent.getData();
+ packageSource = intent.getData();
mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
}
// if there's nothing to do, quietly slip into the ether
- if (packageUri == null) {
+ if (packageSource == null) {
Log.w(TAG, "Unspecified source");
setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
finish();
@@ -359,7 +384,7 @@
return;
}
- boolean wasSetUp = processPackageUri(packageUri);
+ final boolean wasSetUp = processAppSnippet(packageSource);
if (mLocalLOGV) Log.i(TAG, "wasSetUp: " + wasSetUp);
if (!wasSetUp) {
@@ -619,6 +644,39 @@
return true;
}
+ /**
+ * Use the SessionInfo and set up the installer for pre-commit install session.
+ *
+ * @param info The SessionInfo to compose
+ *
+ * @return {@code true} iff the installer could be set up
+ */
+ private boolean processSessionInfo(@NonNull SessionInfo info) {
+ mPkgInfo = generateStubPackageInfo(info.appPackageName);
+ mAppSnippet = new PackageUtil.AppSnippet(info.appLabel,
+ info.appIcon != null ? new BitmapDrawable(getResources(), info.appIcon)
+ : getPackageManager().getDefaultActivityIcon());
+ return true;
+ }
+
+ /**
+ * Parse the Uri (post-commit install session) or use the SessionInfo (pre-commit install
+ * session) to set up the installer for this install.
+ *
+ * @param source The source of package URI or SessionInfo
+ *
+ * @return {@code true} iff the installer could be set up
+ */
+ private boolean processAppSnippet(@NonNull Object source) {
+ if (source instanceof Uri) {
+ return processPackageUri((Uri) source);
+ } else if (source instanceof SessionInfo) {
+ return processSessionInfo((SessionInfo) source);
+ }
+
+ return false;
+ }
+
@Override
public void onBackPressed() {
if (mSessionId != -1) {
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
index e7e37e4..8edd5f7 100644
--- a/packages/SettingsLib/SpaPrivileged/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -30,7 +30,7 @@
],
kotlincflags: [
"-Xjvm-default=all",
- "-Xopt-in=kotlin.RequiresOptIn",
+ "-opt-in=kotlin.RequiresOptIn",
],
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
index 7796549..1dc52cb 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
@@ -1,6 +1,7 @@
package com.android.settingslib.spaprivileged.framework.common
import android.app.admin.DevicePolicyManager
+import android.app.usage.StorageStatsManager
import android.content.Context
import android.os.UserManager
@@ -9,3 +10,6 @@
/** The [DevicePolicyManager] instance. */
val Context.devicePolicyManager get() = getSystemService(DevicePolicyManager::class.java)!!
+
+/** The [StorageStatsManager] instance. */
+val Context.storageStatsManager get() = getSystemService(StorageStatsManager::class.java)!!
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
new file mode 100644
index 0000000..037b737
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.text.format.Formatter
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.produceState
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.framework.common.storageStatsManager
+import com.android.settingslib.spaprivileged.model.app.userHandle
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+@Composable
+fun ApplicationInfo.getStorageSize(): State<String> {
+ val context = LocalContext.current
+ return produceState(initialValue = stringResource(R.string.summary_placeholder)) {
+ withContext(Dispatchers.IO) {
+ value = Formatter.formatFileSize(context, calculateSizeBytes(context))
+ }
+ }
+}
+
+private fun ApplicationInfo.calculateSizeBytes(context: Context): Long {
+ val storageStatsManager = context.storageStatsManager
+ val stats = storageStatsManager.queryStatsForPackage(storageUuid, packageName, userHandle)
+ return stats.codeBytes + stats.dataBytes + stats.cacheBytes
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
new file mode 100644
index 0000000..cec6d7d
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import android.app.usage.StorageStats
+import android.app.usage.StorageStatsManager
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spaprivileged.framework.common.storageStatsManager
+import com.android.settingslib.spaprivileged.model.app.userHandle
+import com.google.common.truth.Truth.assertThat
+import java.util.UUID
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class AppStorageSizeTest {
+ @JvmField
+ @Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Spy
+ private var context: Context = ApplicationProvider.getApplicationContext()
+
+ @Mock
+ private lateinit var storageStatsManager: StorageStatsManager
+
+ private val app = ApplicationInfo().apply {
+ storageUuid = UUID.randomUUID()
+ }
+
+ @Before
+ fun setUp() {
+ whenever(context.storageStatsManager).thenReturn(storageStatsManager)
+ whenever(storageStatsManager.queryStatsForPackage(
+ app.storageUuid, app.packageName, app.userHandle
+ )).thenReturn(STATS)
+ }
+
+ @Test
+ fun getStorageSize() {
+ var storageSize = stateOf("")
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ storageSize = app.getStorageSize()
+ }
+ }
+
+ assertThat(storageSize.value).isEqualTo("123 B")
+ }
+
+ companion object {
+ private val STATS = StorageStats().apply {
+ codeBytes = 100
+ dataBytes = 20
+ cacheBytes = 3
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java
index c2034f8..0c0f5a8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java
@@ -18,12 +18,9 @@
import static java.util.Objects.requireNonNull;
-import android.app.admin.DevicePolicyManager;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.os.UserManager;
-import android.text.TextUtils;
import androidx.annotation.Nullable;
@@ -69,50 +66,11 @@
mResolveActivityChecker = requireNonNull(resolveActivityChecker);
}
+ /**
+ * We don't show Learn More button in Admin-Support Dialog anymore.
+ */
@Override
public void setupLearnMoreButton(Context context) {
- assertInitialized();
-
- String url = mStringProvider.getLearnMoreHelpPageUrl();
-
- if (!TextUtils.isEmpty(url)
- && canLaunchHelpPageInPreferredOrCurrentUser(context, url, mPreferredUserHandle)) {
- setupLearnMoreButtonToLaunchHelpPage(context, url, mPreferredUserHandle);
- } else {
- mLauncher.setupLearnMoreButtonToShowAdminPolicies(context, mEnforcementAdminUserId,
- mEnforcedAdmin);
- }
- }
-
- private boolean canLaunchHelpPageInPreferredOrCurrentUser(
- Context context, String url, UserHandle preferredUserHandle) {
- PackageManager packageManager = context.getPackageManager();
- if (mLauncher.canLaunchHelpPage(
- packageManager, url, preferredUserHandle, mResolveActivityChecker)
- && mForegroundUserChecker.isUserForeground(context, preferredUserHandle)) {
- return true;
- }
- return mLauncher.canLaunchHelpPage(
- packageManager, url, context.getUser(), mResolveActivityChecker);
- }
-
- /**
- * Sets up the "Learn more" button to launch the web help page in the {@code
- * preferredUserHandle} user. If not possible to launch it there, it sets up the button to
- * launch it in the current user instead.
- */
- private void setupLearnMoreButtonToLaunchHelpPage(
- Context context, String url, UserHandle preferredUserHandle) {
- PackageManager packageManager = context.getPackageManager();
- if (mLauncher.canLaunchHelpPage(
- packageManager, url, preferredUserHandle, mResolveActivityChecker)
- && mForegroundUserChecker.isUserForeground(context, preferredUserHandle)) {
- mLauncher.setupLearnMoreButtonToLaunchHelpPage(context, url, preferredUserHandle);
- }
- if (mLauncher.canLaunchHelpPage(
- packageManager, url, context.getUser(), mResolveActivityChecker)) {
- mLauncher.setupLearnMoreButtonToLaunchHelpPage(context, url, context.getUser());
- }
}
private static boolean isUserForeground(Context context, UserHandle userHandle) {
@@ -122,25 +80,7 @@
@Override
public String getAdminSupportTitle(@Nullable String restriction) {
- if (restriction == null) {
- return mStringProvider.getDefaultDisabledByPolicyTitle();
- }
- switch (restriction) {
- case UserManager.DISALLOW_ADJUST_VOLUME:
- return mStringProvider.getDisallowAdjustVolumeTitle();
- case UserManager.DISALLOW_OUTGOING_CALLS:
- return mStringProvider.getDisallowOutgoingCallsTitle();
- case UserManager.DISALLOW_SMS:
- return mStringProvider.getDisallowSmsTitle();
- case DevicePolicyManager.POLICY_DISABLE_CAMERA:
- return mStringProvider.getDisableCameraTitle();
- case DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE:
- return mStringProvider.getDisableScreenCaptureTitle();
- case DevicePolicyManager.POLICY_SUSPEND_PACKAGES:
- return mStringProvider.getSuspendPackagesTitle();
- default:
- return mStringProvider.getDefaultDisabledByPolicyTitle();
- }
+ return mStringProvider.getDefaultDisabledByPolicyTitle();
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
index 3e7481a..f5dddc3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
@@ -43,6 +43,7 @@
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.GlifLayout;
import com.google.android.setupdesign.util.ThemeHelper;
+import com.google.android.setupdesign.util.ThemeResolver;
import java.util.ArrayList;
import java.util.Arrays;
@@ -81,7 +82,14 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setTheme(R.style.SudThemeGlifV3_DayNight);
+ boolean dayNightEnabled = ThemeHelper.isSetupWizardDayNightEnabled(this);
+ ThemeResolver themeResolver =
+ new ThemeResolver.Builder(ThemeResolver.getDefault())
+ .setDefaultTheme(ThemeHelper.getSuwDefaultTheme(this))
+ .setUseDayNight(true)
+ .build();
+ int themeResId = themeResolver.resolve("", /* suppressDayNight= */ !dayNightEnabled);
+ setTheme(themeResId);
ThemeHelper.trySetDynamicColor(this);
setContentView(R.layout.avatar_picker);
setUpButtons();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java
index bc9bdec..7b88566 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java
@@ -18,10 +18,7 @@
import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCED_ADMIN;
import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCEMENT_ADMIN_USER_ID;
-import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.LEARN_MORE_ACTION_LAUNCH_HELP_PAGE;
-import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES;
import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.SUPPORT_MESSAGE;
-import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.URL;
import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DEFAULT_DISABLED_BY_POLICY_CONTENT;
import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DEFAULT_DISABLED_BY_POLICY_TITLE;
import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DISALLOW_ADJUST_VOLUME_TITLE;
@@ -64,67 +61,6 @@
}
@Test
- public void setupLearnMoreButton_noUrl_negativeButtonSet() {
- ManagedDeviceActionDisabledByAdminController controller = createController(EMPTY_URL);
-
- controller.setupLearnMoreButton(mContext);
-
- mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES);
- }
-
- @Test
- public void setupLearnMoreButton_validUrl_foregroundUser_launchesHelpPage() {
- ManagedDeviceActionDisabledByAdminController controller = createController(
- URL,
- /* isUserForeground= */ true,
- /* preferredUserHandle= */ MANAGED_USER,
- /* userContainingBrowser= */ MANAGED_USER);
-
- controller.setupLearnMoreButton(mContext);
-
- mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_LAUNCH_HELP_PAGE);
- }
-
- @Test
- public void setupLearnMoreButton_validUrl_browserInPreferredUser_notForeground_showsAdminPolicies() {
- ManagedDeviceActionDisabledByAdminController controller = createController(
- URL,
- /* isUserForeground= */ false,
- /* preferredUserHandle= */ MANAGED_USER,
- /* userContainingBrowser= */ MANAGED_USER);
-
- controller.setupLearnMoreButton(mContext);
-
- mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES);
- }
-
- @Test
- public void setupLearnMoreButton_validUrl_browserInCurrentUser_launchesHelpPage() {
- ManagedDeviceActionDisabledByAdminController controller = createController(
- URL,
- /* isUserForeground= */ false,
- /* preferredUserHandle= */ MANAGED_USER,
- /* userContainingBrowser= */ mContext.getUser());
-
- controller.setupLearnMoreButton(mContext);
-
- mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_LAUNCH_HELP_PAGE);
- }
-
- @Test
- public void setupLearnMoreButton_validUrl_browserNotOnAnyUser_showsAdminPolicies() {
- ManagedDeviceActionDisabledByAdminController controller = createController(
- URL,
- /* isUserForeground= */ false,
- /* preferredUserHandle= */ MANAGED_USER,
- /* userContainingBrowser= */ null);
-
- controller.setupLearnMoreButton(mContext);
-
- mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES);
- }
-
- @Test
public void getAdminSupportTitleResource_noRestriction_works() {
ManagedDeviceActionDisabledByAdminController controller = createController();
@@ -137,7 +73,7 @@
ManagedDeviceActionDisabledByAdminController controller = createController();
assertThat(controller.getAdminSupportTitle(RESTRICTION))
- .isEqualTo(SUPPORT_TITLE_FOR_RESTRICTION);
+ .isEqualTo(DEFAULT_DISABLED_BY_POLICY_TITLE);
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
index fe337d267..8e33ca3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
@@ -106,13 +106,20 @@
+ "license content #1\n"
+ "</pre><!-- license-text -->\n"
+ "</td></tr><!-- same-license -->\n"
- + "</table></body></html>\n";
+ + "</table>\n"
+ + "<div class=\"path-counts\"><table>\n"
+ + " <tr><th>Path prefix</th><th>Count</th></tr>\n\n"
+ + " <tr><td>file0</td><td>1</td></tr>\n"
+ + " <tr><td>file1</td><td>1</td></tr>\n"
+ + "</table></div>\n\n"
+ + "</body></html>\n";
private static final String HTML_NEW_BODY_STRING =
"<strong>Libraries</strong>\n"
+ "<ul class=\"libraries\">\n"
+ "<li><a href=\"#id0\">libA</a></li>\n"
+ "<li><a href=\"#id1\">libB</a></li>\n"
+ + "<li><a href=\"#id0\">libC</a></li>\n"
+ "</ul>\n"
+ "<strong>Files</strong>\n"
+ "<ul class=\"files\">\n"
@@ -146,7 +153,14 @@
+ "license content #1\n"
+ "</pre><!-- license-text -->\n"
+ "</td></tr><!-- same-license -->\n"
- + "</table></body></html>\n";
+ + "</table>\n"
+ + "<div class=\"path-counts\"><table>\n"
+ + " <tr><th>Path prefix</th><th>Count</th></tr>\n\n"
+ + " <tr><td>file0</td><td>1</td></tr>\n"
+ + " <tr><td>file1</td><td>1</td></tr>\n"
+ + " <tr><td>file2</td><td>1</td></tr>\n"
+ + "</table></div>\n\n"
+ + "</body></html>\n";
private static final String EXPECTED_OLD_HTML_STRING = HTML_HEAD_STRING + HTML_OLD_BODY_STRING;
@@ -263,7 +277,7 @@
Map<String, Set<String>> toOne = new HashMap<>();
toBoth.put("", new HashSet<String>(Arrays.asList("0", "1")));
- toOne.put("", new HashSet<String>(Arrays.asList("0", "1")));
+ toOne.put("", new HashSet<String>(Arrays.asList("0")));
fileNameToLibraryToContentIdMap.put("/file0", toBoth);
fileNameToLibraryToContentIdMap.put("/file1", toOne);
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 3325eec..6d61fd8 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -5,20 +5,32 @@
dsandler@android.com
aaliomer@google.com
+aaronjli@google.com
+acul@google.com
adamcohen@google.com
+aioana@google.com
alexflo@google.com
+andonian@google.com
+aroederer@google.com
arteiro@google.com
asc@google.com
awickham@google.com
+ayepin@google.com
+bbade@google.com
beverlyt@google.com
-brzezinski@google.com
+bhinegardner@google.com
+bhnm@google.com
brycelee@google.com
+brzezinski@google.com
caitlinshk@google.com
+chandruis@google.com
chrisgollner@google.com
+cinek@google.com
dupin@google.com
ethibodeau@google.com
evanlaird@google.com
florenceyang@google.com
+gallmann@google.com
gwasserman@google.com
hwwang@google.com
hyunyoungs@google.com
@@ -35,31 +47,41 @@
juliacr@google.com
juliatuttle@google.com
justinkoh@google.com
+justinweir@google.com
kozynski@google.com
kprevas@google.com
+lusilva@google.com
lynhan@google.com
madym@google.com
mankoff@google.com
+mateuszc@google.com
+michaelmikhil@google.com
+michschn@google.com
mkephart@google.com
mpietal@google.com
mrcasey@google.com
mrenouf@google.com
nickchameyev@google.com
nicomazz@google.com
+nijamkin@google.com
ogunwale@google.com
+omarmt@google.com
+patmanning@google.com
peanutbutter@google.com
peskal@google.com
pinyaoting@google.com
pixel@google.com
pomini@google.com
rahulbanerjee@google.com
+rasheedlewis@google.com
roosa@google.com
+saff@google.com
santie@google.com
shanh@google.com
snoeberger@google.com
steell@google.com
-sfufa@google.com
stwu@google.com
+syeonlee@google.com
sunnygoyal@google.com
susikp@google.com
thiruram@google.com
@@ -70,10 +92,13 @@
victortulias@google.com
winsonc@google.com
wleshner@google.com
+xilei@google.com
xuqiu@google.com
+yeinj@google.com
yuandizhou@google.com
yurilin@google.com
zakcohen@google.com
+zoepage@google.com
#Android TV
rgl@google.com
diff --git a/packages/SystemUI/animation/res/values/ids.xml b/packages/SystemUI/animation/res/values/ids.xml
index f7150ab..2d82307a 100644
--- a/packages/SystemUI/animation/res/values/ids.xml
+++ b/packages/SystemUI/animation/res/values/ids.xml
@@ -16,7 +16,6 @@
-->
<resources>
<!-- DialogLaunchAnimator -->
- <item type="id" name="tag_launch_animation_running"/>
<item type="id" name="tag_dialog_background"/>
<!-- ViewBoundsAnimator -->
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 9656b8a..23cee4d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -29,12 +29,12 @@
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewRootImpl
import android.view.WindowInsets
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
import android.widget.FrameLayout
import com.android.internal.jank.InteractionJankMonitor
-import com.android.internal.jank.InteractionJankMonitor.Configuration
import com.android.internal.jank.InteractionJankMonitor.CujType
import kotlin.math.roundToInt
@@ -46,6 +46,7 @@
*
* This animator also allows to easily animate a dialog into an activity.
*
+ * @see show
* @see showFromView
* @see showFromDialog
* @see createActivityLaunchController
@@ -67,8 +68,81 @@
ActivityLaunchAnimator.INTERPOLATORS.copy(
positionXInterpolator = ActivityLaunchAnimator.INTERPOLATORS.positionInterpolator
)
+ }
- private val TAG_LAUNCH_ANIMATION_RUNNING = R.id.tag_launch_animation_running
+ /**
+ * A controller that takes care of applying the dialog launch and exit animations to the source
+ * that triggered the animation.
+ */
+ interface Controller {
+ /** The [ViewRootImpl] of this controller. */
+ val viewRoot: ViewRootImpl
+
+ /**
+ * The identity object of the source animated by this controller. This animator will ensure
+ * that 2 animations with the same source identity are not going to run at the same time, to
+ * avoid flickers when a dialog is shown from the same source more or less at the same time
+ * (for instance if the user clicks an expandable button twice).
+ */
+ val sourceIdentity: Any
+
+ /**
+ * Move the drawing of the source in the overlay of [viewGroup].
+ *
+ * Once this method is called, and until [stopDrawingInOverlay] is called, the source
+ * controlled by this Controller should be drawn in the overlay of [viewGroup] so that it is
+ * drawn above all other elements in the same [viewRoot].
+ */
+ fun startDrawingInOverlayOf(viewGroup: ViewGroup)
+
+ /**
+ * Move the drawing of the source back in its original location.
+ *
+ * @see startDrawingInOverlayOf
+ */
+ fun stopDrawingInOverlay()
+
+ /**
+ * Create the [LaunchAnimator.Controller] that will be called to animate the source
+ * controlled by this [Controller] during the dialog launch animation.
+ *
+ * At the end of this animation, the source should *not* be visible anymore (until the
+ * dialog is closed and is animated back into the source).
+ */
+ fun createLaunchController(): LaunchAnimator.Controller
+
+ /**
+ * Create the [LaunchAnimator.Controller] that will be called to animate the source
+ * controlled by this [Controller] during the dialog exit animation.
+ *
+ * At the end of this animation, the source should be visible again.
+ */
+ fun createExitController(): LaunchAnimator.Controller
+
+ /**
+ * Whether we should animate the dialog back into the source when it is dismissed. If this
+ * methods returns `false`, then the dialog will simply fade out and
+ * [onExitAnimationCancelled] will be called.
+ *
+ * Note that even when this returns `true`, the exit animation might still be cancelled (in
+ * which case [onExitAnimationCancelled] will also be called).
+ */
+ fun shouldAnimateExit(): Boolean
+
+ /**
+ * Called if we decided to *not* animate the dialog into the source for some reason. This
+ * means that [createExitController] will *not* be called and this implementation should
+ * make sure that the source is back in its original state, before it was animated into the
+ * dialog. In particular, the source should be visible again.
+ */
+ fun onExitAnimationCancelled()
+
+ /**
+ * Return the [InteractionJankMonitor.Configuration.Builder] to be used for animations
+ * controlled by this controller.
+ */
+ // TODO(b/252723237): Make this non-nullable
+ fun jankConfigurationBuilder(cuj: Int): InteractionJankMonitor.Configuration.Builder?
}
/**
@@ -96,7 +170,28 @@
dialog: Dialog,
view: View,
cuj: DialogCuj? = null,
- animateBackgroundBoundsChange: Boolean = false,
+ animateBackgroundBoundsChange: Boolean = false
+ ) {
+ show(dialog, createController(view), cuj, animateBackgroundBoundsChange)
+ }
+
+ /**
+ * Show [dialog] by expanding it from a source controlled by [controller].
+ *
+ * If [animateBackgroundBoundsChange] is true, then the background of the dialog will be
+ * animated when the dialog bounds change.
+ *
+ * Note: The background of [view] should be a (rounded) rectangle so that it can be properly
+ * animated.
+ *
+ * Caveats: When calling this function and [dialog] is not a fullscreen dialog, then it will be
+ * made fullscreen and 2 views will be inserted between the dialog DecorView and its children.
+ */
+ fun show(
+ dialog: Dialog,
+ controller: Controller,
+ cuj: DialogCuj? = null,
+ animateBackgroundBoundsChange: Boolean = false
) {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw IllegalStateException(
@@ -109,9 +204,10 @@
// intent is to launch a dialog from another dialog.
val animatedParent =
openedDialogs.firstOrNull {
- it.dialog.window.decorView.viewRootImpl == view.viewRootImpl
+ it.dialog.window.decorView.viewRootImpl == controller.viewRoot
}
- val animateFrom = animatedParent?.dialogContentWithBackground ?: view
+ val animateFrom =
+ animatedParent?.dialogContentWithBackground?.let { createController(it) } ?: controller
if (animatedParent == null && animateFrom !is LaunchableView) {
// Make sure the View we launch from implements LaunchableView to avoid visibility
@@ -126,15 +222,17 @@
)
}
- // Make sure we don't run the launch animation from the same view twice at the same time.
- if (animateFrom.getTag(TAG_LAUNCH_ANIMATION_RUNNING) != null) {
- Log.e(TAG, "Not running dialog launch animation as there is already one running")
+ // Make sure we don't run the launch animation from the same source twice at the same time.
+ if (openedDialogs.any { it.controller.sourceIdentity == controller.sourceIdentity }) {
+ Log.e(
+ TAG,
+ "Not running dialog launch animation from source as it is already expanded into a" +
+ " dialog"
+ )
dialog.show()
return
}
- animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true)
-
val animatedDialog =
AnimatedDialog(
launchAnimator,
@@ -146,16 +244,99 @@
animateBackgroundBoundsChange,
animatedParent,
isForTesting,
- cuj
+ cuj,
)
openedDialogs.add(animatedDialog)
animatedDialog.start()
}
+ /** Create a [Controller] that can animate [source] to & from a dialog. */
+ private fun createController(source: View): Controller {
+ return object : Controller {
+ override val viewRoot: ViewRootImpl
+ get() = source.viewRootImpl
+
+ override val sourceIdentity: Any = source
+
+ override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
+ // Create a temporary ghost of the source (which will make it invisible) and add it
+ // to the host dialog.
+ GhostView.addGhost(source, viewGroup)
+
+ // The ghost of the source was just created, so the source is currently invisible.
+ // We need to make sure that it stays invisible as long as the dialog is shown or
+ // animating.
+ (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
+ }
+
+ override fun stopDrawingInOverlay() {
+ // Note: here we should remove the ghost from the overlay, but in practice this is
+ // already done by the launch controllers created below.
+
+ // Make sure we allow the source to change its visibility again.
+ (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
+ source.visibility = View.VISIBLE
+ }
+
+ override fun createLaunchController(): LaunchAnimator.Controller {
+ val delegate = GhostedViewLaunchAnimatorController(source)
+ return object : LaunchAnimator.Controller by delegate {
+ override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ // Remove the temporary ghost added by [startDrawingInOverlayOf]. Another
+ // ghost (that ghosts only the source content, and not its background) will
+ // be added right after this by the delegate and will be animated.
+ GhostView.removeGhost(source)
+ delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+ }
+
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+
+ // We hide the source when the dialog is showing. We will make this view
+ // visible again when dismissing the dialog. This does nothing if the source
+ // implements [LaunchableView], as it's already INVISIBLE in that case.
+ source.visibility = View.INVISIBLE
+ }
+ }
+ }
+
+ override fun createExitController(): LaunchAnimator.Controller {
+ return GhostedViewLaunchAnimatorController(source)
+ }
+
+ override fun shouldAnimateExit(): Boolean {
+ // The source should be invisible by now, if it's not then something else changed
+ // its visibility and we probably don't want to run the animation.
+ if (source.visibility != View.INVISIBLE) {
+ return false
+ }
+
+ return source.isAttachedToWindow && ((source.parent as? View)?.isShown ?: true)
+ }
+
+ override fun onExitAnimationCancelled() {
+ // Make sure we allow the source to change its visibility again.
+ (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
+
+ // If the view is invisible it's probably because of us, so we make it visible
+ // again.
+ if (source.visibility == View.INVISIBLE) {
+ source.visibility = View.VISIBLE
+ }
+ }
+
+ override fun jankConfigurationBuilder(
+ cuj: Int
+ ): InteractionJankMonitor.Configuration.Builder? {
+ return InteractionJankMonitor.Configuration.Builder.withView(cuj, source)
+ }
+ }
+ }
+
/**
- * Launch [dialog] from [another dialog][animateFrom] that was shown using [showFromView]. This
- * will allow for dismissing the whole stack.
+ * Launch [dialog] from [another dialog][animateFrom] that was shown using [show]. This will
+ * allow for dismissing the whole stack.
*
* @see dismissStack
*/
@@ -181,32 +362,55 @@
/**
* Create an [ActivityLaunchAnimator.Controller] that can be used to launch an activity from the
- * dialog that contains [View]. Note that the dialog must have been show using [showFromView]
- * and be currently showing, otherwise this will return null.
+ * dialog that contains [View]. Note that the dialog must have been shown using this animator,
+ * otherwise this method will return null.
*
* The returned controller will take care of dismissing the dialog at the right time after the
* activity started, when the dialog to app animation is done (or when it is cancelled). If this
* method returns null, then the dialog won't be dismissed.
*
- * Note: The background of [view] should be a (rounded) rectangle so that it can be properly
- * animated.
- *
* @param view any view inside the dialog to animate.
*/
@JvmOverloads
fun createActivityLaunchController(
view: View,
- cujType: Int? = null
+ cujType: Int? = null,
): ActivityLaunchAnimator.Controller? {
val animatedDialog =
openedDialogs.firstOrNull {
it.dialog.window.decorView.viewRootImpl == view.viewRootImpl
}
?: return null
+ return createActivityLaunchController(animatedDialog, cujType)
+ }
+ /**
+ * Create an [ActivityLaunchAnimator.Controller] that can be used to launch an activity from
+ * [dialog]. Note that the dialog must have been shown using this animator, otherwise this
+ * method will return null.
+ *
+ * The returned controller will take care of dismissing the dialog at the right time after the
+ * activity started, when the dialog to app animation is done (or when it is cancelled). If this
+ * method returns null, then the dialog won't be dismissed.
+ *
+ * @param dialog the dialog to animate.
+ */
+ @JvmOverloads
+ fun createActivityLaunchController(
+ dialog: Dialog,
+ cujType: Int? = null,
+ ): ActivityLaunchAnimator.Controller? {
+ val animatedDialog = openedDialogs.firstOrNull { it.dialog == dialog } ?: return null
+ return createActivityLaunchController(animatedDialog, cujType)
+ }
+
+ private fun createActivityLaunchController(
+ animatedDialog: AnimatedDialog,
+ cujType: Int? = null
+ ): ActivityLaunchAnimator.Controller? {
// At this point, we know that the intent of the caller is to dismiss the dialog to show
- // an app, so we disable the exit animation into the touch surface because we will never
- // want to run it anyways.
+ // an app, so we disable the exit animation into the source because we will never want to
+ // run it anyways.
animatedDialog.exitAnimationDisabled = true
val dialog = animatedDialog.dialog
@@ -252,7 +456,7 @@
// If this dialog was shown from a cascade of other dialogs, make sure those ones
// are dismissed too.
- animatedDialog.touchSurface = animatedDialog.prepareForStackDismiss()
+ animatedDialog.prepareForStackDismiss()
// Remove the dim.
dialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
@@ -283,12 +487,11 @@
}
/**
- * Ensure that all dialogs currently shown won't animate into their touch surface when
- * dismissed.
+ * Ensure that all dialogs currently shown won't animate into their source when dismissed.
*
* This is a temporary API meant to be called right before we both dismiss a dialog and start an
- * activity, which currently does not look good if we animate the dialog into the touch surface
- * at the same time as the activity starts.
+ * activity, which currently does not look good if we animate the dialog into their source at
+ * the same time as the activity starts.
*
* TODO(b/193634619): Remove this function and animate dialog into opening activity instead.
*/
@@ -297,13 +500,11 @@
}
/**
- * Dismiss [dialog]. If it was launched from another dialog using [showFromView], also dismiss
- * the stack of dialogs, animating back to the original touchSurface.
+ * Dismiss [dialog]. If it was launched from another dialog using this animator, also dismiss
+ * the stack of dialogs and simply fade out [dialog].
*/
fun dismissStack(dialog: Dialog) {
- openedDialogs
- .firstOrNull { it.dialog == dialog }
- ?.let { it.touchSurface = it.prepareForStackDismiss() }
+ openedDialogs.firstOrNull { it.dialog == dialog }?.prepareForStackDismiss()
dialog.dismiss()
}
@@ -337,8 +538,11 @@
private val callback: DialogLaunchAnimator.Callback,
private val interactionJankMonitor: InteractionJankMonitor,
- /** The view that triggered the dialog after being tapped. */
- var touchSurface: View,
+ /**
+ * The controller of the source that triggered the dialog and that will animate into/from the
+ * dialog.
+ */
+ val controller: DialogLaunchAnimator.Controller,
/**
* A callback that will be called with this [AnimatedDialog] after the dialog was dismissed and
@@ -383,17 +587,18 @@
private var originalDialogBackgroundColor = Color.BLACK
/**
- * Whether we are currently launching/showing the dialog by animating it from [touchSurface].
+ * Whether we are currently launching/showing the dialog by animating it from its source
+ * controlled by [controller].
*/
private var isLaunching = true
- /** Whether we are currently dismissing/hiding the dialog by animating into [touchSurface]. */
+ /** Whether we are currently dismissing/hiding the dialog by animating into its source. */
private var isDismissing = false
private var dismissRequested = false
var exitAnimationDisabled = false
- private var isTouchSurfaceGhostDrawn = false
+ private var isSourceDrawnInDialog = false
private var isOriginalDialogViewLaidOut = false
/** A layout listener to animate the dialog height change. */
@@ -410,13 +615,19 @@
*/
private var decorViewLayoutListener: View.OnLayoutChangeListener? = null
+ private var hasInstrumentedJank = false
+
fun start() {
if (cuj != null) {
- val config = Configuration.Builder.withView(cuj.cujType, touchSurface)
- if (cuj.tag != null) {
- config.setTag(cuj.tag)
+ val config = controller.jankConfigurationBuilder(cuj.cujType)
+ if (config != null) {
+ if (cuj.tag != null) {
+ config.setTag(cuj.tag)
+ }
+
+ interactionJankMonitor.begin(config)
+ hasInstrumentedJank = true
}
- interactionJankMonitor.begin(config)
}
// Create the dialog so that its onCreate() method is called, which usually sets the dialog
@@ -618,47 +829,45 @@
// Show the dialog.
dialog.show()
- addTouchSurfaceGhost()
+ moveSourceDrawingToDialog()
}
- private fun addTouchSurfaceGhost() {
+ private fun moveSourceDrawingToDialog() {
if (decorView.viewRootImpl == null) {
- // Make sure that we have access to the dialog view root to synchronize the creation of
- // the ghost.
- decorView.post(::addTouchSurfaceGhost)
+ // Make sure that we have access to the dialog view root to move the drawing to the
+ // dialog overlay.
+ decorView.post(::moveSourceDrawingToDialog)
return
}
- // Create a ghost of the touch surface (which will make the touch surface invisible) and add
- // it to the host dialog. We trigger a one off synchronization to make sure that this is
- // done in sync between the two different windows.
+ // Move the drawing of the source in the overlay of this dialog, then animate. We trigger a
+ // one-off synchronization to make sure that this is done in sync between the two different
+ // windows.
synchronizeNextDraw(
then = {
- isTouchSurfaceGhostDrawn = true
+ isSourceDrawnInDialog = true
maybeStartLaunchAnimation()
}
)
- GhostView.addGhost(touchSurface, decorView)
-
- // The ghost of the touch surface was just created, so the touch surface is currently
- // invisible. We need to make sure that it stays invisible as long as the dialog is shown or
- // animating.
- (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
+ controller.startDrawingInOverlayOf(decorView)
}
/**
- * Synchronize the next draw of the touch surface and dialog view roots so that they are
- * performed at the same time, in the same transaction. This is necessary to make sure that the
- * ghost of the touch surface is drawn at the same time as the touch surface is made invisible
- * (or inversely, removed from the UI when the touch surface is made visible).
+ * Synchronize the next draw of the source and dialog view roots so that they are performed at
+ * the same time, in the same transaction. This is necessary to make sure that the source is
+ * drawn in the overlay at the same time as it is removed from its original position (or
+ * inversely, removed from the overlay when the source is moved back to its original position).
*/
private fun synchronizeNextDraw(then: () -> Unit) {
if (forceDisableSynchronization) {
+ // Don't synchronize when inside an automated test.
then()
return
}
- ViewRootSync.synchronizeNextDraw(touchSurface, decorView, then)
+ ViewRootSync.synchronizeNextDraw(decorView, controller.viewRoot.view, then)
+ decorView.invalidate()
+ controller.viewRoot.view.invalidate()
}
private fun findFirstViewGroupWithBackground(view: View): ViewGroup? {
@@ -681,7 +890,7 @@
}
private fun maybeStartLaunchAnimation() {
- if (!isTouchSurfaceGhostDrawn || !isOriginalDialogViewLaidOut) {
+ if (!isSourceDrawnInDialog || !isOriginalDialogViewLaidOut) {
return
}
@@ -690,19 +899,7 @@
startAnimation(
isLaunching = true,
- onLaunchAnimationStart = {
- // Remove the temporary ghost. Another ghost (that ghosts only the touch surface
- // content, and not its background) will be added right after this and will be
- // animated.
- GhostView.removeGhost(touchSurface)
- },
onLaunchAnimationEnd = {
- touchSurface.setTag(R.id.tag_launch_animation_running, null)
-
- // We hide the touch surface when the dialog is showing. We will make this view
- // visible again when dismissing the dialog.
- touchSurface.visibility = View.INVISIBLE
-
isLaunching = false
// dismiss was called during the animation, dismiss again now to actually dismiss.
@@ -718,7 +915,10 @@
backgroundLayoutListener
)
}
- cuj?.run { interactionJankMonitor.end(cujType) }
+
+ if (hasInstrumentedJank) {
+ interactionJankMonitor.end(cuj!!.cujType)
+ }
}
)
}
@@ -753,8 +953,8 @@
}
/**
- * Hide the dialog into the touch surface and call [onAnimationFinished] when the animation is
- * done (passing animationRan=true) or if it's skipped (passing animationRan=false) to actually
+ * Hide the dialog into the source and call [onAnimationFinished] when the animation is done
+ * (passing animationRan=true) or if it's skipped (passing animationRan=false) to actually
* dismiss the dialog.
*/
private fun hideDialogIntoView(onAnimationFinished: (Boolean) -> Unit) {
@@ -763,17 +963,9 @@
decorView.removeOnLayoutChangeListener(decorViewLayoutListener)
}
- if (!shouldAnimateDialogIntoView()) {
- Log.i(TAG, "Skipping animation of dialog into the touch surface")
-
- // Make sure we allow the touch surface to change its visibility again.
- (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
-
- // If the view is invisible it's probably because of us, so we make it visible again.
- if (touchSurface.visibility == View.INVISIBLE) {
- touchSurface.visibility = View.VISIBLE
- }
-
+ if (!shouldAnimateDialogIntoSource()) {
+ Log.i(TAG, "Skipping animation of dialog into the source")
+ controller.onExitAnimationCancelled()
onAnimationFinished(false /* instantDismiss */)
onDialogDismissed(this@AnimatedDialog)
return
@@ -786,10 +978,6 @@
dialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
},
onLaunchAnimationEnd = {
- // Make sure we allow the touch surface to change its visibility again.
- (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
-
- touchSurface.visibility = View.VISIBLE
val dialogContentWithBackground = this.dialogContentWithBackground!!
dialogContentWithBackground.visibility = View.INVISIBLE
@@ -799,14 +987,11 @@
)
}
- // Make sure that the removal of the ghost and making the touch surface visible is
- // done at the same time.
- synchronizeNextDraw(
- then = {
- onAnimationFinished(true /* instantDismiss */)
- onDialogDismissed(this@AnimatedDialog)
- }
- )
+ controller.stopDrawingInOverlay()
+ synchronizeNextDraw {
+ onAnimationFinished(true /* instantDismiss */)
+ onDialogDismissed(this@AnimatedDialog)
+ }
}
)
}
@@ -816,27 +1001,34 @@
onLaunchAnimationStart: () -> Unit = {},
onLaunchAnimationEnd: () -> Unit = {}
) {
- // Create 2 ghost controllers to animate both the dialog and the touch surface in the
- // dialog.
- val startView = if (isLaunching) touchSurface else dialogContentWithBackground!!
- val endView = if (isLaunching) dialogContentWithBackground!! else touchSurface
- val startViewController = GhostedViewLaunchAnimatorController(startView)
- val endViewController = GhostedViewLaunchAnimatorController(endView)
- startViewController.launchContainer = decorView
- endViewController.launchContainer = decorView
+ // Create 2 controllers to animate both the dialog and the source.
+ val startController =
+ if (isLaunching) {
+ controller.createLaunchController()
+ } else {
+ GhostedViewLaunchAnimatorController(dialogContentWithBackground!!)
+ }
+ val endController =
+ if (isLaunching) {
+ GhostedViewLaunchAnimatorController(dialogContentWithBackground!!)
+ } else {
+ controller.createExitController()
+ }
+ startController.launchContainer = decorView
+ endController.launchContainer = decorView
- val endState = endViewController.createAnimatorState()
+ val endState = endController.createAnimatorState()
val controller =
object : LaunchAnimator.Controller {
override var launchContainer: ViewGroup
- get() = startViewController.launchContainer
+ get() = startController.launchContainer
set(value) {
- startViewController.launchContainer = value
- endViewController.launchContainer = value
+ startController.launchContainer = value
+ endController.launchContainer = value
}
override fun createAnimatorState(): LaunchAnimator.State {
- return startViewController.createAnimatorState()
+ return startController.createAnimatorState()
}
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
@@ -845,15 +1037,29 @@
// onLaunchAnimationStart on the controller (which will create its own ghost).
onLaunchAnimationStart()
- startViewController.onLaunchAnimationStart(isExpandingFullyAbove)
- endViewController.onLaunchAnimationStart(isExpandingFullyAbove)
+ startController.onLaunchAnimationStart(isExpandingFullyAbove)
+ endController.onLaunchAnimationStart(isExpandingFullyAbove)
}
override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
- startViewController.onLaunchAnimationEnd(isExpandingFullyAbove)
- endViewController.onLaunchAnimationEnd(isExpandingFullyAbove)
+ // onLaunchAnimationEnd is called by an Animator at the end of the animation,
+ // on a Choreographer animation tick. The following calls will move the animated
+ // content from the dialog overlay back to its original position, and this
+ // change must be reflected in the next frame given that we then sync the next
+ // frame of both the content and dialog ViewRoots. However, in case that content
+ // is rendered by Compose, whose compositions are also scheduled on a
+ // Choreographer frame, any state change made *right now* won't be reflected in
+ // the next frame given that a Choreographer frame can't schedule another and
+ // have it happen in the same frame. So we post the forwarded calls to
+ // [Controller.onLaunchAnimationEnd], leaving this Choreographer frame, ensuring
+ // that the move of the content back to its original window will be reflected in
+ // the next frame right after [onLaunchAnimationEnd] is called.
+ dialog.context.mainExecutor.execute {
+ startController.onLaunchAnimationEnd(isExpandingFullyAbove)
+ endController.onLaunchAnimationEnd(isExpandingFullyAbove)
- onLaunchAnimationEnd()
+ onLaunchAnimationEnd()
+ }
}
override fun onLaunchAnimationProgress(
@@ -861,11 +1067,11 @@
progress: Float,
linearProgress: Float
) {
- startViewController.onLaunchAnimationProgress(state, progress, linearProgress)
+ startController.onLaunchAnimationProgress(state, progress, linearProgress)
// The end view is visible only iff the starting view is not visible.
state.visible = !state.visible
- endViewController.onLaunchAnimationProgress(state, progress, linearProgress)
+ endController.onLaunchAnimationProgress(state, progress, linearProgress)
// If the dialog content is complex, its dimension might change during the
// launch animation. The animation end position might also change during the
@@ -873,14 +1079,16 @@
// Therefore we update the end state to the new position/size. Usually the
// dialog dimension or position will change in the early frames, so changing the
// end state shouldn't really be noticeable.
- endViewController.fillGhostedViewState(endState)
+ if (endController is GhostedViewLaunchAnimatorController) {
+ endController.fillGhostedViewState(endState)
+ }
}
}
launchAnimator.startAnimation(controller, endState, originalDialogBackgroundColor)
}
- private fun shouldAnimateDialogIntoView(): Boolean {
+ private fun shouldAnimateDialogIntoSource(): Boolean {
// Don't animate if the dialog was previously hidden using hide() or if we disabled the exit
// animation.
if (exitAnimationDisabled || !dialog.isShowing) {
@@ -888,24 +1096,12 @@
}
// If we are dreaming, the dialog was probably closed because of that so we don't animate
- // into the touchSurface.
+ // into the source.
if (callback.isDreaming()) {
return false
}
- // The touch surface should be invisible by now, if it's not then something else changed its
- // visibility and we probably don't want to run the animation.
- if (touchSurface.visibility != View.INVISIBLE) {
- return false
- }
-
- // If the touch surface is not attached or one of its ancestors is not visible, then we
- // don't run the animation either.
- if (!touchSurface.isAttachedToWindow) {
- return false
- }
-
- return (touchSurface.parent as? View)?.isShown ?: true
+ return controller.shouldAnimateExit()
}
/** A layout listener to animate the change of bounds of the dialog background. */
@@ -988,17 +1184,13 @@
}
}
- fun prepareForStackDismiss(): View {
+ fun prepareForStackDismiss() {
if (parentAnimatedDialog == null) {
- return touchSurface
+ return
}
parentAnimatedDialog.exitAnimationDisabled = true
parentAnimatedDialog.dialog.hide()
- val view = parentAnimatedDialog.prepareForStackDismiss()
+ parentAnimatedDialog.prepareForStackDismiss()
parentAnimatedDialog.dialog.dismiss()
- // Make the touch surface invisible, so we end up animating to it when we actually
- // dismiss the stack
- view.visibility = View.INVISIBLE
- return view
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index eb000ad..0028d13 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -199,6 +199,10 @@
// the content before fading out the background.
ghostView = GhostView.addGhost(ghostedView, launchContainer)
+ // The ghost was just created, so ghostedView is currently invisible. We need to make sure
+ // that it stays invisible as long as we are animating.
+ (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
+
val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
matrix.getValues(initialGhostViewMatrixValues)
@@ -293,6 +297,7 @@
backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
GhostView.removeGhost(ghostedView)
+ (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
launchContainerOverlay.remove(backgroundView)
// Make sure that the view is considered VISIBLE by accessibility by first making it
diff --git a/packages/SystemUI/compose/core/Android.bp b/packages/SystemUI/compose/core/Android.bp
index 4cfe392..fbdb526 100644
--- a/packages/SystemUI/compose/core/Android.bp
+++ b/packages/SystemUI/compose/core/Android.bp
@@ -30,8 +30,11 @@
],
static_libs: [
+ "SystemUIAnimationLib",
+
"androidx.compose.runtime_runtime",
"androidx.compose.material3_material3",
+ "androidx.savedstate_savedstate",
],
kotlincflags: ["-Xjvm-default=all"],
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/Expandable.kt
new file mode 100644
index 0000000..edbd684
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/Expandable.kt
@@ -0,0 +1,363 @@
+/*
+ * 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.compose.animation
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroupOverlay
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.contentColorFor
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCompositionContext
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.drawOutline
+import androidx.compose.ui.graphics.drawscope.scale
+import androidx.compose.ui.layout.boundsInRoot
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.Density
+import androidx.lifecycle.ViewTreeLifecycleOwner
+import androidx.lifecycle.ViewTreeViewModelStoreOwner
+import androidx.savedstate.findViewTreeSavedStateRegistryOwner
+import androidx.savedstate.setViewTreeSavedStateRegistryOwner
+import com.android.systemui.animation.LaunchAnimator
+import kotlin.math.min
+
+/**
+ * Create an expandable shape that can launch into an Activity or a Dialog.
+ *
+ * Example:
+ * ```
+ * Expandable(
+ * color = MaterialTheme.colorScheme.primary,
+ * shape = RoundedCornerShape(16.dp),
+ * ) { controller ->
+ * Row(
+ * Modifier
+ * // For activities:
+ * .clickable { activityStarter.startActivity(intent, controller.forActivity()) }
+ *
+ * // For dialogs:
+ * .clickable { dialogLaunchAnimator.show(dialog, controller.forDialog()) }
+ * ) { ... }
+ * }
+ * ```
+ *
+ * @sample com.android.systemui.compose.gallery.ActivityLaunchScreen
+ * @sample com.android.systemui.compose.gallery.DialogLaunchScreen
+ */
+@Composable
+fun Expandable(
+ color: Color,
+ shape: Shape,
+ modifier: Modifier = Modifier,
+ contentColor: Color = contentColorFor(color),
+ content: @Composable (ExpandableController) -> Unit,
+) {
+ Expandable(
+ rememberExpandableController(color, shape, contentColor),
+ modifier,
+ content,
+ )
+}
+
+/**
+ * Create an expandable shape that can launch into an Activity or a Dialog.
+ *
+ * This overload can be used in cases where you need to create the [ExpandableController] before
+ * composing this [Expandable], for instance if something outside of this Expandable can trigger a
+ * launch animation
+ *
+ * Example:
+ * ```
+ * // The controller that you can use to trigger the animations from anywhere.
+ * val controller =
+ * rememberExpandableController(
+ * color = MaterialTheme.colorScheme.primary,
+ * shape = RoundedCornerShape(16.dp),
+ * )
+ *
+ * Expandable(controller) {
+ * ...
+ * }
+ * ```
+ *
+ * @sample com.android.systemui.compose.gallery.ActivityLaunchScreen
+ * @sample com.android.systemui.compose.gallery.DialogLaunchScreen
+ */
+@Composable
+fun Expandable(
+ controller: ExpandableController,
+ modifier: Modifier = Modifier,
+ content: @Composable (ExpandableController) -> Unit,
+) {
+ val controller = controller as ExpandableControllerImpl
+ val color = controller.color
+ val contentColor = controller.contentColor
+ val shape = controller.shape
+
+ // TODO(b/230830644): Use movableContentOf to preserve the content state instead once the
+ // Compose libraries have been updated and include aosp/2163631.
+ val wrappedContent =
+ @Composable { controller: ExpandableController ->
+ CompositionLocalProvider(
+ LocalContentColor provides contentColor,
+ ) {
+ content(controller)
+ }
+ }
+
+ val thisExpandableSize by remember {
+ derivedStateOf { controller.boundsInComposeViewRoot.value.size }
+ }
+
+ // Make sure we don't read animatorState directly here to avoid recomposition every time the
+ // state changes (i.e. every frame of the animation).
+ val isAnimating by remember {
+ derivedStateOf {
+ controller.animatorState.value != null && controller.overlay.value != null
+ }
+ }
+
+ when {
+ isAnimating -> {
+ // Don't compose the movable content during the animation, as it should be composed only
+ // once at all times. We make this spacer exactly the same size as this Expandable when
+ // it is visible.
+ Spacer(
+ modifier
+ .clip(shape)
+ .requiredSize(with(controller.density) { thisExpandableSize.toDpSize() })
+ )
+
+ // The content and its animated background in the overlay. We draw it only when we are
+ // animating.
+ AnimatedContentInOverlay(
+ color,
+ thisExpandableSize,
+ controller.animatorState,
+ controller.overlay.value
+ ?: error("AnimatedContentInOverlay shouldn't be composed with null overlay."),
+ controller,
+ wrappedContent,
+ controller.composeViewRoot,
+ { controller.currentComposeViewInOverlay.value = it },
+ controller.density,
+ )
+ }
+ controller.isDialogShowing.value -> {
+ Box(
+ modifier
+ .drawWithContent { /* Don't draw anything when the dialog is shown. */}
+ .onGloballyPositioned {
+ controller.boundsInComposeViewRoot.value = it.boundsInRoot()
+ }
+ ) { wrappedContent(controller) }
+ }
+ else -> {
+ Box(
+ modifier.clip(shape).background(color, shape).onGloballyPositioned {
+ controller.boundsInComposeViewRoot.value = it.boundsInRoot()
+ }
+ ) { wrappedContent(controller) }
+ }
+ }
+}
+
+/** Draw [content] in [overlay] while respecting its screen position given by [animatorState]. */
+@Composable
+private fun AnimatedContentInOverlay(
+ color: Color,
+ sizeInOriginalLayout: Size,
+ animatorState: State<LaunchAnimator.State?>,
+ overlay: ViewGroupOverlay,
+ controller: ExpandableController,
+ content: @Composable (ExpandableController) -> Unit,
+ composeViewRoot: View,
+ onOverlayComposeViewChanged: (View?) -> Unit,
+ density: Density,
+) {
+ val compositionContext = rememberCompositionContext()
+ val context = LocalContext.current
+
+ // Create the ComposeView and force its content composition so that the movableContent is
+ // composed exactly once when we start animating.
+ val composeViewInOverlay =
+ remember(context, density) {
+ val startWidth = sizeInOriginalLayout.width
+ val startHeight = sizeInOriginalLayout.height
+ val contentModifier =
+ Modifier
+ // Draw the content with the same size as it was at the start of the animation
+ // so that its content is laid out exactly the same way.
+ .requiredSize(with(density) { sizeInOriginalLayout.toDpSize() })
+ .drawWithContent {
+ val animatorState = animatorState.value ?: return@drawWithContent
+
+ // Scale the content with the background while keeping its aspect ratio.
+ val widthRatio =
+ if (startWidth != 0f) {
+ animatorState.width.toFloat() / startWidth
+ } else {
+ 1f
+ }
+ val heightRatio =
+ if (startHeight != 0f) {
+ animatorState.height.toFloat() / startHeight
+ } else {
+ 1f
+ }
+ val scale = min(widthRatio, heightRatio)
+ scale(scale) { this@drawWithContent.drawContent() }
+ }
+
+ val composeView =
+ ComposeView(context).apply {
+ setContent {
+ Box(
+ Modifier.fillMaxSize().drawWithContent {
+ val animatorState = animatorState.value ?: return@drawWithContent
+ if (!animatorState.visible) {
+ return@drawWithContent
+ }
+
+ val topRadius = animatorState.topCornerRadius
+ val bottomRadius = animatorState.bottomCornerRadius
+ if (topRadius == bottomRadius) {
+ // Shortcut to avoid Outline calculation and allocation.
+ val cornerRadius = CornerRadius(topRadius)
+ drawRoundRect(color, cornerRadius = cornerRadius)
+ } else {
+ val shape =
+ RoundedCornerShape(
+ topStart = topRadius,
+ topEnd = topRadius,
+ bottomStart = bottomRadius,
+ bottomEnd = bottomRadius,
+ )
+ val outline = shape.createOutline(size, layoutDirection, this)
+ drawOutline(outline, color = color)
+ }
+
+ drawContent()
+ },
+ // We center the content in the expanding container.
+ contentAlignment = Alignment.Center,
+ ) {
+ Box(contentModifier) { content(controller) }
+ }
+ }
+ }
+
+ // Set the owners.
+ val overlayViewGroup =
+ getOverlayViewGroup(
+ context,
+ overlay,
+ )
+ ViewTreeLifecycleOwner.set(
+ overlayViewGroup,
+ ViewTreeLifecycleOwner.get(composeViewRoot),
+ )
+ ViewTreeViewModelStoreOwner.set(
+ overlayViewGroup,
+ ViewTreeViewModelStoreOwner.get(composeViewRoot),
+ )
+ overlayViewGroup.setViewTreeSavedStateRegistryOwner(
+ composeViewRoot.findViewTreeSavedStateRegistryOwner()
+ )
+
+ composeView.setParentCompositionContext(compositionContext)
+
+ composeView
+ }
+
+ DisposableEffect(overlay, composeViewInOverlay) {
+ // Add the ComposeView to the overlay.
+ overlay.add(composeViewInOverlay)
+
+ val startState =
+ animatorState.value
+ ?: throw IllegalStateException(
+ "AnimatedContentInOverlay shouldn't be composed with null animatorState."
+ )
+ measureAndLayoutComposeViewInOverlay(composeViewInOverlay, startState)
+ onOverlayComposeViewChanged(composeViewInOverlay)
+
+ onDispose {
+ composeViewInOverlay.disposeComposition()
+ overlay.remove(composeViewInOverlay)
+ onOverlayComposeViewChanged(null)
+ }
+ }
+}
+
+internal fun measureAndLayoutComposeViewInOverlay(
+ view: View,
+ state: LaunchAnimator.State,
+) {
+ val exactWidth = state.width
+ val exactHeight = state.height
+ view.measure(
+ View.MeasureSpec.makeSafeMeasureSpec(exactWidth, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeSafeMeasureSpec(exactHeight, View.MeasureSpec.EXACTLY),
+ )
+
+ val parent = view.parent as ViewGroup
+ val parentLocation = parent.locationOnScreen
+ val offsetX = parentLocation[0]
+ val offsetY = parentLocation[1]
+ view.layout(
+ state.left - offsetX,
+ state.top - offsetY,
+ state.right - offsetX,
+ state.bottom - offsetY,
+ )
+}
+
+// TODO(b/230830644): Add hidden API to ViewGroupOverlay to access this ViewGroup directly?
+private fun getOverlayViewGroup(context: Context, overlay: ViewGroupOverlay): ViewGroup {
+ val view = View(context)
+ overlay.add(view)
+ var current = view.parent
+ while (current.parent != null) {
+ current = current.parent
+ }
+ overlay.remove(view)
+ return current as ViewGroup
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
new file mode 100644
index 0000000..065c314
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
@@ -0,0 +1,306 @@
+/*
+ * 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.compose.animation
+
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroupOverlay
+import android.view.ViewRootImpl
+import androidx.compose.material3.contentColorFor
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.LaunchAnimator
+import kotlin.math.roundToInt
+
+/** A controller that can control animated launches. */
+interface ExpandableController {
+ /** Create an [ActivityLaunchAnimator.Controller] to animate into an Activity. */
+ fun forActivity(): ActivityLaunchAnimator.Controller
+
+ /** Create a [DialogLaunchAnimator.Controller] to animate into a Dialog. */
+ fun forDialog(): DialogLaunchAnimator.Controller
+}
+
+/**
+ * Create an [ExpandableController] to control an [Expandable]. This is useful if you need to create
+ * the controller before the [Expandable], for instance to handle clicks outside of the Expandable
+ * that would still trigger a dialog/activity launch animation.
+ */
+@Composable
+fun rememberExpandableController(
+ color: Color,
+ shape: Shape,
+ contentColor: Color = contentColorFor(color),
+): ExpandableController {
+ val composeViewRoot = LocalView.current
+ val density = LocalDensity.current
+ val layoutDirection = LocalLayoutDirection.current
+
+ // The current animation state, if we are currently animating a dialog or activity.
+ val animatorState = remember { mutableStateOf<LaunchAnimator.State?>(null) }
+
+ // Whether a dialog controlled by this ExpandableController is currently showing.
+ val isDialogShowing = remember { mutableStateOf(false) }
+
+ // The overlay in which we should animate the launch.
+ val overlay = remember { mutableStateOf<ViewGroupOverlay?>(null) }
+
+ // The current [ComposeView] being animated in the [overlay], if any.
+ val currentComposeViewInOverlay = remember { mutableStateOf<View?>(null) }
+
+ // The bounds in [composeViewRoot] of the expandable controlled by this controller.
+ val boundsInComposeViewRoot = remember { mutableStateOf(Rect.Zero) }
+
+ // Whether this composable is still composed. We only do the dialog exit animation if this is
+ // true.
+ val isComposed = remember { mutableStateOf(true) }
+ DisposableEffect(Unit) { onDispose { isComposed.value = false } }
+
+ return remember(color, contentColor, shape, composeViewRoot, density, layoutDirection) {
+ ExpandableControllerImpl(
+ color,
+ contentColor,
+ shape,
+ composeViewRoot,
+ density,
+ animatorState,
+ isDialogShowing,
+ overlay,
+ currentComposeViewInOverlay,
+ boundsInComposeViewRoot,
+ layoutDirection,
+ isComposed,
+ )
+ }
+}
+
+internal class ExpandableControllerImpl(
+ internal val color: Color,
+ internal val contentColor: Color,
+ internal val shape: Shape,
+ internal val composeViewRoot: View,
+ internal val density: Density,
+ internal val animatorState: MutableState<LaunchAnimator.State?>,
+ internal val isDialogShowing: MutableState<Boolean>,
+ internal val overlay: MutableState<ViewGroupOverlay?>,
+ internal val currentComposeViewInOverlay: MutableState<View?>,
+ internal val boundsInComposeViewRoot: MutableState<Rect>,
+ private val layoutDirection: LayoutDirection,
+ private val isComposed: State<Boolean>,
+) : ExpandableController {
+ override fun forActivity(): ActivityLaunchAnimator.Controller {
+ return activityController()
+ }
+
+ override fun forDialog(): DialogLaunchAnimator.Controller {
+ return dialogController()
+ }
+
+ /**
+ * Create a [LaunchAnimator.Controller] that is going to be used to drive an activity or dialog
+ * animation. This controller will:
+ * 1. Compute the start/end animation state using [boundsInComposeViewRoot] and the location of
+ * composeViewRoot on the screen.
+ * 2. Update [animatorState] with the current animation state if we are animating, or null
+ * otherwise.
+ */
+ private fun launchController(): LaunchAnimator.Controller {
+ return object : LaunchAnimator.Controller {
+ private val rootLocationOnScreen = intArrayOf(0, 0)
+
+ override var launchContainer: ViewGroup = composeViewRoot.rootView as ViewGroup
+
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ animatorState.value = null
+ }
+
+ override fun onLaunchAnimationProgress(
+ state: LaunchAnimator.State,
+ progress: Float,
+ linearProgress: Float
+ ) {
+ // We copy state given that it's always the same object that is mutated by
+ // ActivityLaunchAnimator.
+ animatorState.value =
+ LaunchAnimator.State(
+ state.top,
+ state.bottom,
+ state.left,
+ state.right,
+ state.topCornerRadius,
+ state.bottomCornerRadius,
+ )
+ .apply { visible = state.visible }
+
+ // Force measure and layout the ComposeView in the overlay whenever the animation
+ // state changes.
+ currentComposeViewInOverlay.value?.let {
+ measureAndLayoutComposeViewInOverlay(it, state)
+ }
+ }
+
+ override fun createAnimatorState(): LaunchAnimator.State {
+ val boundsInRoot = boundsInComposeViewRoot.value
+ val outline =
+ shape.createOutline(
+ Size(boundsInRoot.width, boundsInRoot.height),
+ layoutDirection,
+ density,
+ )
+
+ val (topCornerRadius, bottomCornerRadius) =
+ when (outline) {
+ is Outline.Rectangle -> 0f to 0f
+ is Outline.Rounded -> {
+ val roundRect = outline.roundRect
+
+ // TODO(b/230830644): Add better support different corner radii.
+ val topCornerRadius =
+ maxOf(
+ roundRect.topLeftCornerRadius.x,
+ roundRect.topLeftCornerRadius.y,
+ roundRect.topRightCornerRadius.x,
+ roundRect.topRightCornerRadius.y,
+ )
+ val bottomCornerRadius =
+ maxOf(
+ roundRect.bottomLeftCornerRadius.x,
+ roundRect.bottomLeftCornerRadius.y,
+ roundRect.bottomRightCornerRadius.x,
+ roundRect.bottomRightCornerRadius.y,
+ )
+
+ topCornerRadius to bottomCornerRadius
+ }
+ else ->
+ error(
+ "ExpandableState only supports (rounded) rectangles at the " +
+ "moment."
+ )
+ }
+
+ val rootLocation = rootLocationOnScreen()
+ return LaunchAnimator.State(
+ top = rootLocation.y.roundToInt(),
+ bottom = (rootLocation.y + boundsInRoot.height).roundToInt(),
+ left = rootLocation.x.roundToInt(),
+ right = (rootLocation.x + boundsInRoot.width).roundToInt(),
+ topCornerRadius = topCornerRadius,
+ bottomCornerRadius = bottomCornerRadius,
+ )
+ }
+
+ private fun rootLocationOnScreen(): Offset {
+ composeViewRoot.getLocationOnScreen(rootLocationOnScreen)
+ val boundsInRoot = boundsInComposeViewRoot.value
+ val x = rootLocationOnScreen[0] + boundsInRoot.left
+ val y = rootLocationOnScreen[1] + boundsInRoot.top
+ return Offset(x, y)
+ }
+ }
+ }
+
+ /** Create an [ActivityLaunchAnimator.Controller] that can be used to animate activities. */
+ private fun activityController(): ActivityLaunchAnimator.Controller {
+ val delegate = launchController()
+ return object : ActivityLaunchAnimator.Controller, LaunchAnimator.Controller by delegate {
+ override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+ overlay.value = composeViewRoot.rootView.overlay as ViewGroupOverlay
+ }
+
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+ overlay.value = null
+ }
+ }
+ }
+
+ private fun dialogController(): DialogLaunchAnimator.Controller {
+ return object : DialogLaunchAnimator.Controller {
+ override val viewRoot: ViewRootImpl = composeViewRoot.viewRootImpl
+ override val sourceIdentity: Any = this@ExpandableControllerImpl
+
+ override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
+ val newOverlay = viewGroup.overlay as ViewGroupOverlay
+ if (newOverlay != overlay.value) {
+ overlay.value = newOverlay
+ }
+ }
+
+ override fun stopDrawingInOverlay() {
+ if (overlay.value != null) {
+ overlay.value = null
+ }
+ }
+
+ override fun createLaunchController(): LaunchAnimator.Controller {
+ val delegate = launchController()
+ return object : LaunchAnimator.Controller by delegate {
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+
+ // Make sure we don't draw this expandable when the dialog is showing.
+ isDialogShowing.value = true
+ }
+ }
+ }
+
+ override fun createExitController(): LaunchAnimator.Controller {
+ val delegate = launchController()
+ return object : LaunchAnimator.Controller by delegate {
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+ isDialogShowing.value = false
+ }
+ }
+ }
+
+ override fun shouldAnimateExit(): Boolean = isComposed.value
+
+ override fun onExitAnimationCancelled() {
+ isDialogShowing.value = false
+ }
+
+ override fun jankConfigurationBuilder(
+ cuj: Int
+ ): InteractionJankMonitor.Configuration.Builder? {
+ // TODO(b/252723237): Add support for jank monitoring when animating from a
+ // Composable.
+ return null
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 5486adb..6ec65ce 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -24,7 +24,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
androidprv:layout_maxWidth="@dimen/keyguard_security_width"
- androidprv:layout_maxHeight="@dimen/keyguard_security_height"
android:layout_gravity="center_horizontal|bottom"
android:gravity="bottom"
>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index fa3ed21..01c9ac1 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1582,4 +1582,9 @@
<dimen name="dream_overlay_status_bar_ambient_text_shadow_dx">0.5dp</dimen>
<dimen name="dream_overlay_status_bar_ambient_text_shadow_dy">0.5dp</dimen>
<dimen name="dream_overlay_status_bar_ambient_text_shadow_radius">2dp</dimen>
+
+ <!-- Default device corner radius, used for assist UI -->
+ <dimen name="config_rounded_mask_size">0px</dimen>
+ <dimen name="config_rounded_mask_size_top">0px</dimen>
+ <dimen name="config_rounded_mask_size_bottom">0px</dimen>
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 2111df5..647dd47 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -27,6 +27,8 @@
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Intent;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -233,17 +235,14 @@
@ViewDebug.ExportedProperty(category="recents")
public boolean isLocked;
+ public Point positionInParent;
+
+ public Rect appBounds;
+
// Last snapshot data, only used for recent tasks
public ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
new ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData();
- /**
- * Indicates that this task for the desktop tile in recents.
- *
- * Used when desktop mode feature is enabled.
- */
- public boolean desktopTile;
-
public Task() {
// Do nothing
}
@@ -274,7 +273,8 @@
this(other.key, other.colorPrimary, other.colorBackground, other.isDockable,
other.isLocked, other.taskDescription, other.topActivity);
lastSnapshotData.set(other.lastSnapshotData);
- desktopTile = other.desktopTile;
+ positionInParent = other.positionInParent;
+ appBounds = other.appBounds;
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
index 00f1c01..207f344 100644
--- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
@@ -15,6 +15,12 @@
*/
package com.android.keyguard;
+import static androidx.constraintlayout.widget.ConstraintSet.BOTTOM;
+import static androidx.constraintlayout.widget.ConstraintSet.END;
+import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
+import static androidx.constraintlayout.widget.ConstraintSet.START;
+import static androidx.constraintlayout.widget.ConstraintSet.TOP;
+
import android.annotation.Nullable;
import android.app.admin.IKeyguardCallback;
import android.app.admin.IKeyguardClient;
@@ -30,7 +36,10 @@
import android.view.SurfaceControlViewHost;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
-import android.view.ViewGroup;
+import android.view.View;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
@@ -49,7 +58,7 @@
private static final int REMOTE_CONTENT_READY_TIMEOUT_MILLIS = 500;
private final KeyguardUpdateMonitor mUpdateMonitor;
private final Context mContext;
- private final ViewGroup mParent;
+ private final ConstraintLayout mParent;
private AdminSecurityView mView;
private Handler mHandler;
private IKeyguardClient mClient;
@@ -156,6 +165,7 @@
mUpdateMonitor = updateMonitor;
mKeyguardCallback = callback;
mView = new AdminSecurityView(mContext, mSurfaceHolderCallback);
+ mView.setId(View.generateViewId());
}
/**
@@ -167,6 +177,15 @@
}
if (!mView.isAttachedToWindow()) {
mParent.addView(mView);
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.clone(mParent);
+ constraintSet.connect(mView.getId(), TOP, PARENT_ID, TOP);
+ constraintSet.connect(mView.getId(), START, PARENT_ID, START);
+ constraintSet.connect(mView.getId(), END, PARENT_ID, END);
+ constraintSet.connect(mView.getId(), BOTTOM, PARENT_ID, BOTTOM);
+ constraintSet.constrainHeight(mView.getId(), ConstraintSet.MATCH_CONSTRAINT);
+ constraintSet.constrainWidth(mView.getId(), ConstraintSet.MATCH_CONSTRAINT);
+ constraintSet.applyTo(mParent);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 353c369..c34db15 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -21,15 +21,23 @@
import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
+import static androidx.constraintlayout.widget.ConstraintSet.BOTTOM;
+import static androidx.constraintlayout.widget.ConstraintSet.CHAIN_SPREAD;
+import static androidx.constraintlayout.widget.ConstraintSet.END;
+import static androidx.constraintlayout.widget.ConstraintSet.LEFT;
+import static androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT;
+import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
+import static androidx.constraintlayout.widget.ConstraintSet.RIGHT;
+import static androidx.constraintlayout.widget.ConstraintSet.START;
+import static androidx.constraintlayout.widget.ConstraintSet.TOP;
+import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
+
import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
import static java.lang.Integer.max;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.admin.DevicePolicyManager;
@@ -44,12 +52,12 @@
import android.graphics.drawable.LayerDrawable;
import android.os.UserManager;
import android.provider.Settings;
+import android.transition.TransitionManager;
import android.util.AttributeSet;
import android.util.Log;
import android.util.MathUtils;
import android.util.TypedValue;
import android.view.GestureDetector;
-import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -59,8 +67,6 @@
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
import android.view.WindowManager;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.ImageView;
@@ -68,8 +74,9 @@
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
@@ -92,9 +99,9 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Consumer;
-public class KeyguardSecurityContainer extends FrameLayout {
+/** Determines how the bouncer is displayed to the user. */
+public class KeyguardSecurityContainer extends ConstraintLayout {
static final int USER_TYPE_PRIMARY = 1;
static final int USER_TYPE_WORK_PROFILE = 2;
static final int USER_TYPE_SECONDARY_USER = 3;
@@ -125,15 +132,6 @@
// How much to scale the default slop by, to avoid accidental drags.
private static final float SLOP_SCALE = 4f;
- private static final long IME_DISAPPEAR_DURATION_MS = 125;
-
- // The duration of the animation to switch security sides.
- private static final long SECURITY_SHIFT_ANIMATION_DURATION_MS = 500;
-
- // How much of the switch sides animation should be dedicated to fading the security out. The
- // remainder will fade it back in again.
- private static final float SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION = 0.2f;
-
@VisibleForTesting
KeyguardSecurityViewFlipper mSecurityViewFlipper;
private GlobalSettings mGlobalSettings;
@@ -649,47 +647,8 @@
}
@Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int maxHeight = 0;
- int maxWidth = 0;
- int childState = 0;
-
- for (int i = 0; i < getChildCount(); i++) {
- final View view = getChildAt(i);
- if (view.getVisibility() != GONE) {
- int updatedWidthMeasureSpec = mViewMode.getChildWidthMeasureSpec(widthMeasureSpec);
- final LayoutParams lp = (LayoutParams) view.getLayoutParams();
-
- // When using EXACTLY spec, measure will use the layout width if > 0. Set before
- // measuring the child
- lp.width = MeasureSpec.getSize(updatedWidthMeasureSpec);
- measureChildWithMargins(view, updatedWidthMeasureSpec, 0,
- heightMeasureSpec, 0);
-
- maxWidth = Math.max(maxWidth,
- view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
- maxHeight = Math.max(maxHeight,
- view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
- childState = combineMeasuredStates(childState, view.getMeasuredState());
- }
- }
-
- maxWidth += getPaddingLeft() + getPaddingRight();
- maxHeight += getPaddingTop() + getPaddingBottom();
-
- // Check against our minimum height and width
- maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
- maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
-
- setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
- resolveSizeAndState(maxHeight, heightMeasureSpec,
- childState << MEASURED_HEIGHT_STATE_SHIFT));
- }
-
- @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
-
int width = right - left;
if (changed && mWidth != width) {
mWidth = width;
@@ -761,7 +720,7 @@
* Enscapsulates the differences between bouncer modes for the container.
*/
interface ViewMode {
- default void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
+ default void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@NonNull FalsingManager falsingManager,
@NonNull UserSwitcherController userSwitcherController) {};
@@ -787,11 +746,6 @@
/** On notif tap, this animation will run */
default void startAppearAnimation(SecurityMode securityMode) {};
- /** Override to alter the width measure spec to perhaps limit the ViewFlipper size */
- default int getChildWidthMeasureSpec(int parentWidthMeasureSpec) {
- return parentWidthMeasureSpec;
- }
-
/** Called when we are setting a new ViewMode */
default void onDestroy() {};
}
@@ -801,13 +755,12 @@
* screen devices
*/
abstract static class SidedSecurityMode implements ViewMode {
- @Nullable private ValueAnimator mRunningSecurityShiftAnimator;
private KeyguardSecurityViewFlipper mViewFlipper;
- private ViewGroup mView;
+ private ConstraintLayout mView;
private GlobalSettings mGlobalSettings;
private int mDefaultSideSetting;
- public void init(ViewGroup v, KeyguardSecurityViewFlipper viewFlipper,
+ public void init(ConstraintLayout v, KeyguardSecurityViewFlipper viewFlipper,
GlobalSettings globalSettings, boolean leftAlignedByDefault) {
mView = v;
mViewFlipper = viewFlipper;
@@ -850,127 +803,6 @@
protected abstract void updateSecurityViewLocation(boolean leftAlign, boolean animate);
- protected void translateSecurityViewLocation(boolean leftAlign, boolean animate) {
- translateSecurityViewLocation(leftAlign, animate, i -> {});
- }
-
- /**
- * Moves the inner security view to the correct location with animation. This is triggered
- * when the user double taps on the side of the screen that is not currently occupied by
- * the security view.
- */
- protected void translateSecurityViewLocation(boolean leftAlign, boolean animate,
- Consumer<Float> securityAlphaListener) {
- if (mRunningSecurityShiftAnimator != null) {
- mRunningSecurityShiftAnimator.cancel();
- mRunningSecurityShiftAnimator = null;
- }
-
- int targetTranslation = leftAlign
- ? 0 : mView.getMeasuredWidth() - mViewFlipper.getWidth();
-
- if (animate) {
- // This animation is a bit fun to implement. The bouncer needs to move, and fade
- // in/out at the same time. The issue is, the bouncer should only move a short
- // amount (120dp or so), but obviously needs to go from one side of the screen to
- // the other. This needs a pretty custom animation.
- //
- // This works as follows. It uses a ValueAnimation to simply drive the animation
- // progress. This animator is responsible for both the translation of the bouncer,
- // and the current fade. It will fade the bouncer out while also moving it along the
- // 120dp path. Once the bouncer is fully faded out though, it will "snap" the
- // bouncer closer to its destination, then fade it back in again. The effect is that
- // the bouncer will move from 0 -> X while fading out, then
- // (destination - X) -> destination while fading back in again.
- // TODO(b/208250221): Make this animation properly abortable.
- Interpolator positionInterpolator = AnimationUtils.loadInterpolator(
- mView.getContext(), android.R.interpolator.fast_out_extra_slow_in);
- Interpolator fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN;
- Interpolator fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
-
- mRunningSecurityShiftAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
- mRunningSecurityShiftAnimator.setDuration(SECURITY_SHIFT_ANIMATION_DURATION_MS);
- mRunningSecurityShiftAnimator.setInterpolator(Interpolators.LINEAR);
-
- int initialTranslation = (int) mViewFlipper.getTranslationX();
- int totalTranslation = (int) mView.getResources().getDimension(
- R.dimen.security_shift_animation_translation);
-
- final boolean shouldRestoreLayerType = mViewFlipper.hasOverlappingRendering()
- && mViewFlipper.getLayerType() != View.LAYER_TYPE_HARDWARE;
- if (shouldRestoreLayerType) {
- mViewFlipper.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */null);
- }
-
- float initialAlpha = mViewFlipper.getAlpha();
-
- mRunningSecurityShiftAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mRunningSecurityShiftAnimator = null;
- }
- });
- mRunningSecurityShiftAnimator.addUpdateListener(animation -> {
- float switchPoint = SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION;
- boolean isFadingOut = animation.getAnimatedFraction() < switchPoint;
-
- int currentTranslation = (int) (positionInterpolator.getInterpolation(
- animation.getAnimatedFraction()) * totalTranslation);
- int translationRemaining = totalTranslation - currentTranslation;
-
- // Flip the sign if we're going from right to left.
- if (leftAlign) {
- currentTranslation = -currentTranslation;
- translationRemaining = -translationRemaining;
- }
-
- float opacity;
- if (isFadingOut) {
- // The bouncer fades out over the first X%.
- float fadeOutFraction = MathUtils.constrainedMap(
- /* rangeMin= */1.0f,
- /* rangeMax= */0.0f,
- /* valueMin= */0.0f,
- /* valueMax= */switchPoint,
- animation.getAnimatedFraction());
- opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction);
-
- // When fading out, the alpha needs to start from the initial opacity of the
- // view flipper, otherwise we get a weird bit of jank as it ramps back to
- // 100%.
- mViewFlipper.setAlpha(opacity * initialAlpha);
-
- // Animate away from the source.
- mViewFlipper.setTranslationX(initialTranslation + currentTranslation);
- } else {
- // And in again over the remaining (100-X)%.
- float fadeInFraction = MathUtils.constrainedMap(
- /* rangeMin= */0.0f,
- /* rangeMax= */1.0f,
- /* valueMin= */switchPoint,
- /* valueMax= */1.0f,
- animation.getAnimatedFraction());
-
- opacity = fadeInInterpolator.getInterpolation(fadeInFraction);
- mViewFlipper.setAlpha(opacity);
-
- // Fading back in, animate towards the destination.
- mViewFlipper.setTranslationX(targetTranslation - translationRemaining);
- }
- securityAlphaListener.accept(opacity);
-
- if (animation.getAnimatedFraction() == 1.0f && shouldRestoreLayerType) {
- mViewFlipper.setLayerType(View.LAYER_TYPE_NONE, /* paint= */null);
- }
- });
-
- mRunningSecurityShiftAnimator.start();
- } else {
- mViewFlipper.setTranslationX(targetTranslation);
- }
- }
-
-
boolean isLeftAligned() {
return mGlobalSettings.getInt(Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
mDefaultSideSetting)
@@ -989,11 +821,11 @@
* Default bouncer is centered within the space
*/
static class DefaultViewMode implements ViewMode {
- private ViewGroup mView;
+ private ConstraintLayout mView;
private KeyguardSecurityViewFlipper mViewFlipper;
@Override
- public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
+ public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@NonNull FalsingManager falsingManager,
@NonNull UserSwitcherController userSwitcherController) {
@@ -1005,11 +837,14 @@
}
private void updateSecurityViewGroup() {
- FrameLayout.LayoutParams lp =
- (FrameLayout.LayoutParams) mViewFlipper.getLayoutParams();
- lp.gravity = Gravity.CENTER_HORIZONTAL;
- mViewFlipper.setLayoutParams(lp);
- mViewFlipper.setTranslationX(0);
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.connect(mViewFlipper.getId(), START, PARENT_ID, START);
+ constraintSet.connect(mViewFlipper.getId(), END, PARENT_ID, END);
+ constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
+ constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
+ constraintSet.constrainHeight(mViewFlipper.getId(), MATCH_CONSTRAINT);
+ constraintSet.constrainWidth(mViewFlipper.getId(), MATCH_CONSTRAINT);
+ constraintSet.applyTo(mView);
}
}
@@ -1018,7 +853,7 @@
* a user switcher, in both portrait and landscape modes.
*/
static class UserSwitcherViewMode extends SidedSecurityMode {
- private ViewGroup mView;
+ private ConstraintLayout mView;
private ViewGroup mUserSwitcherViewGroup;
private KeyguardSecurityViewFlipper mViewFlipper;
private TextView mUserSwitcher;
@@ -1029,11 +864,8 @@
private UserSwitcherController.UserSwitchCallback mUserSwitchCallback =
this::setupUserSwitcher;
- private float mAnimationLastAlpha = 1f;
- private boolean mAnimationWaitsToShift = true;
-
@Override
- public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
+ public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@NonNull FalsingManager falsingManager,
@NonNull UserSwitcherController userSwitcherController) {
@@ -1240,88 +1072,55 @@
});
}
- /**
- * Each view will get half the width. Yes, it would be easier to use something other than
- * FrameLayout but it was too disruptive to downstream projects to change.
- */
- @Override
- public int getChildWidthMeasureSpec(int parentWidthMeasureSpec) {
- return MeasureSpec.makeMeasureSpec(
- MeasureSpec.getSize(parentWidthMeasureSpec) / 2,
- MeasureSpec.EXACTLY);
- }
-
@Override
public void updateSecurityViewLocation() {
updateSecurityViewLocation(isLeftAligned(), /* animate= */false);
}
public void updateSecurityViewLocation(boolean leftAlign, boolean animate) {
- setYTranslation();
- setGravity();
- setXTranslation(leftAlign, animate);
- }
-
- private void setXTranslation(boolean leftAlign, boolean animate) {
- if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
- mUserSwitcherViewGroup.setTranslationX(0);
- mViewFlipper.setTranslationX(0);
- } else {
- int switcherTargetTranslation = leftAlign
- ? mView.getMeasuredWidth() - mViewFlipper.getWidth() : 0;
- if (animate) {
- mAnimationWaitsToShift = true;
- mAnimationLastAlpha = 1f;
- translateSecurityViewLocation(leftAlign, animate, securityAlpha -> {
- // During the animation security view fades out - alpha goes from 1 to
- // (almost) 0 - and then fades in - alpha grows back to 1.
- // If new alpha is bigger than previous one it means we're at inflection
- // point and alpha is zero or almost zero. That's when we want to do
- // translation of user switcher, so that it's not visible to the user.
- boolean fullyFadeOut = securityAlpha == 0.0f
- || securityAlpha > mAnimationLastAlpha;
- if (fullyFadeOut && mAnimationWaitsToShift) {
- mUserSwitcherViewGroup.setTranslationX(switcherTargetTranslation);
- mAnimationWaitsToShift = false;
- }
- mUserSwitcherViewGroup.setAlpha(securityAlpha);
- mAnimationLastAlpha = securityAlpha;
- });
- } else {
- translateSecurityViewLocation(leftAlign, animate);
- mUserSwitcherViewGroup.setTranslationX(switcherTargetTranslation);
- }
+ if (animate) {
+ TransitionManager.beginDelayedTransition(mView,
+ new KeyguardSecurityViewTransition());
}
-
- }
-
- private void setGravity() {
- if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
- updateViewGravity(mUserSwitcherViewGroup, Gravity.CENTER_HORIZONTAL);
- updateViewGravity(mViewFlipper, Gravity.CENTER_HORIZONTAL);
- } else {
- // horizontal gravity is the same because we translate these views anyway
- updateViewGravity(mViewFlipper, Gravity.LEFT | Gravity.BOTTOM);
- updateViewGravity(mUserSwitcherViewGroup, Gravity.LEFT | Gravity.CENTER_VERTICAL);
- }
- }
-
- private void setYTranslation() {
int yTrans = mResources.getDimensionPixelSize(R.dimen.bouncer_user_switcher_y_trans);
if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
- mUserSwitcherViewGroup.setTranslationY(yTrans);
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP, yTrans);
+ constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
+ constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
+ constraintSet.centerHorizontally(mViewFlipper.getId(), PARENT_ID);
+ constraintSet.centerHorizontally(mUserSwitcherViewGroup.getId(), PARENT_ID);
+ constraintSet.setVerticalChainStyle(mViewFlipper.getId(), CHAIN_SPREAD);
+ constraintSet.setVerticalChainStyle(mUserSwitcherViewGroup.getId(), CHAIN_SPREAD);
+ constraintSet.constrainHeight(mUserSwitcherViewGroup.getId(), WRAP_CONTENT);
+ constraintSet.constrainWidth(mUserSwitcherViewGroup.getId(), WRAP_CONTENT);
+ constraintSet.constrainHeight(mViewFlipper.getId(), MATCH_CONSTRAINT);
+ constraintSet.applyTo(mView);
} else {
- // Attempt to reposition a bit higher to make up for this frame being a bit lower
- // on the device
- mUserSwitcherViewGroup.setTranslationY(-yTrans);
- mViewFlipper.setTranslationY(0);
- }
- }
+ int leftElement = leftAlign ? mViewFlipper.getId() : mUserSwitcherViewGroup.getId();
+ int rightElement =
+ leftAlign ? mUserSwitcherViewGroup.getId() : mViewFlipper.getId();
- private void updateViewGravity(View v, int gravity) {
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
- lp.gravity = gravity;
- v.setLayoutParams(lp);
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.connect(leftElement, LEFT, PARENT_ID, LEFT);
+ constraintSet.connect(leftElement, RIGHT, rightElement, LEFT);
+ constraintSet.connect(rightElement, LEFT, leftElement, RIGHT);
+ constraintSet.connect(rightElement, RIGHT, PARENT_ID, RIGHT);
+ constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP);
+ constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, PARENT_ID, BOTTOM,
+ yTrans);
+ constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
+ constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
+ constraintSet.setHorizontalChainStyle(mUserSwitcherViewGroup.getId(), CHAIN_SPREAD);
+ constraintSet.setHorizontalChainStyle(mViewFlipper.getId(), CHAIN_SPREAD);
+ constraintSet.constrainHeight(mUserSwitcherViewGroup.getId(),
+ MATCH_CONSTRAINT);
+ constraintSet.constrainWidth(mUserSwitcherViewGroup.getId(),
+ MATCH_CONSTRAINT);
+ constraintSet.constrainWidth(mViewFlipper.getId(), MATCH_CONSTRAINT);
+ constraintSet.constrainHeight(mViewFlipper.getId(), MATCH_CONSTRAINT);
+ constraintSet.applyTo(mView);
+ }
}
}
@@ -1330,11 +1129,11 @@
* between alternate sides of the display.
*/
static class OneHandedViewMode extends SidedSecurityMode {
- private ViewGroup mView;
+ private ConstraintLayout mView;
private KeyguardSecurityViewFlipper mViewFlipper;
@Override
- public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
+ public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@NonNull FalsingManager falsingManager,
@NonNull UserSwitcherController userSwitcherController) {
@@ -1342,28 +1141,10 @@
mView = v;
mViewFlipper = viewFlipper;
- updateSecurityViewGravity();
updateSecurityViewLocation(isLeftAligned(), /* animate= */false);
}
/**
- * One-handed mode contains the child to half of the available space.
- */
- @Override
- public int getChildWidthMeasureSpec(int parentWidthMeasureSpec) {
- return MeasureSpec.makeMeasureSpec(
- MeasureSpec.getSize(parentWidthMeasureSpec) / 2,
- MeasureSpec.EXACTLY);
- }
-
- private void updateSecurityViewGravity() {
- FrameLayout.LayoutParams lp =
- (FrameLayout.LayoutParams) mViewFlipper.getLayoutParams();
- lp.gravity = Gravity.LEFT | Gravity.BOTTOM;
- mViewFlipper.setLayoutParams(lp);
- }
-
- /**
* Moves the bouncer to align with a tap (most likely in the shade), so the bouncer
* appears on the same side as a touch.
*/
@@ -1380,7 +1161,20 @@
}
protected void updateSecurityViewLocation(boolean leftAlign, boolean animate) {
- translateSecurityViewLocation(leftAlign, animate);
+ if (animate) {
+ TransitionManager.beginDelayedTransition(mView,
+ new KeyguardSecurityViewTransition());
+ }
+ ConstraintSet constraintSet = new ConstraintSet();
+ if (leftAlign) {
+ constraintSet.connect(mViewFlipper.getId(), LEFT, PARENT_ID, LEFT);
+ } else {
+ constraintSet.connect(mViewFlipper.getId(), RIGHT, PARENT_ID, RIGHT);
+ }
+ constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
+ constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
+ constraintSet.constrainPercentWidth(mViewFlipper.getId(), 0.5f);
+ constraintSet.applyTo(mView);
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
new file mode 100644
index 0000000..9eb2c11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
@@ -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.keyguard
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.Rect
+import android.transition.Transition
+import android.transition.TransitionValues
+import android.util.MathUtils
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.AnimationUtils
+import com.android.internal.R.interpolator.fast_out_extra_slow_in
+import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
+
+/** Animates constraint layout changes for the security view. */
+class KeyguardSecurityViewTransition : Transition() {
+
+ companion object {
+ const val PROP_BOUNDS = "securityViewLocation:bounds"
+
+ // The duration of the animation to switch security sides.
+ const val SECURITY_SHIFT_ANIMATION_DURATION_MS = 500L
+
+ // How much of the switch sides animation should be dedicated to fading the security out.
+ // The remainder will fade it back in again.
+ const val SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION = 0.2f
+ }
+
+ private fun captureValues(values: TransitionValues) {
+ val boundsRect = Rect()
+ boundsRect.left = values.view.left
+ boundsRect.top = values.view.top
+ boundsRect.right = values.view.right
+ boundsRect.bottom = values.view.bottom
+ values.values[PROP_BOUNDS] = boundsRect
+ }
+
+ override fun getTransitionProperties(): Array<String>? {
+ return arrayOf(PROP_BOUNDS)
+ }
+
+ override fun captureEndValues(transitionValues: TransitionValues?) {
+ transitionValues?.let { captureValues(it) }
+ }
+
+ override fun captureStartValues(transitionValues: TransitionValues?) {
+ transitionValues?.let { captureValues(it) }
+ }
+
+ override fun createAnimator(
+ sceneRoot: ViewGroup?,
+ startValues: TransitionValues?,
+ endValues: TransitionValues?
+ ): Animator? {
+ if (sceneRoot == null || startValues == null || endValues == null) {
+ return null
+ }
+
+ // This animation is a bit fun to implement. The bouncer needs to move, and fade
+ // in/out at the same time. The issue is, the bouncer should only move a short
+ // amount (120dp or so), but obviously needs to go from one side of the screen to
+ // the other. This needs a pretty custom animation.
+ //
+ // This works as follows. It uses a ValueAnimation to simply drive the animation
+ // progress. This animator is responsible for both the translation of the bouncer,
+ // and the current fade. It will fade the bouncer out while also moving it along the
+ // 120dp path. Once the bouncer is fully faded out though, it will "snap" the
+ // bouncer closer to its destination, then fade it back in again. The effect is that
+ // the bouncer will move from 0 -> X while fading out, then
+ // (destination - X) -> destination while fading back in again.
+ // TODO(b/208250221): Make this animation properly abortable.
+ val positionInterpolator =
+ AnimationUtils.loadInterpolator(sceneRoot.context, fast_out_extra_slow_in)
+ val fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN
+ val fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN
+ var runningSecurityShiftAnimator = ValueAnimator.ofFloat(0.0f, 1.0f)
+ runningSecurityShiftAnimator.duration = SECURITY_SHIFT_ANIMATION_DURATION_MS
+ runningSecurityShiftAnimator.interpolator = Interpolators.LINEAR
+ val startRect = startValues.values[PROP_BOUNDS] as Rect
+ val endRect = endValues.values[PROP_BOUNDS] as Rect
+ val v = startValues.view
+ val totalTranslation: Int =
+ sceneRoot.resources.getDimension(R.dimen.security_shift_animation_translation).toInt()
+ val shouldRestoreLayerType =
+ (v.hasOverlappingRendering() && v.layerType != View.LAYER_TYPE_HARDWARE)
+ if (shouldRestoreLayerType) {
+ v.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */ null)
+ }
+ val initialAlpha: Float = v.alpha
+ runningSecurityShiftAnimator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ runningSecurityShiftAnimator = null
+ }
+ }
+ )
+
+ var finishedFadingOutNonSecurityView = false
+
+ runningSecurityShiftAnimator.addUpdateListener { animation: ValueAnimator ->
+ val switchPoint = SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION
+ val isFadingOut = animation.animatedFraction < switchPoint
+ val opacity: Float
+ var currentTranslation =
+ (positionInterpolator.getInterpolation(animation.animatedFraction) *
+ totalTranslation)
+ .toInt()
+ var translationRemaining = totalTranslation - currentTranslation
+ val leftAlign = endRect.left < startRect.left
+ if (leftAlign) {
+ currentTranslation = -currentTranslation
+ translationRemaining = -translationRemaining
+ }
+
+ if (isFadingOut) {
+ // The bouncer fades out over the first X%.
+ val fadeOutFraction =
+ MathUtils.constrainedMap(
+ /* rangeMin= */ 1.0f,
+ /* rangeMax= */ 0.0f,
+ /* valueMin= */ 0.0f,
+ /* valueMax= */ switchPoint,
+ animation.animatedFraction
+ )
+ opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction)
+
+ // When fading out, the alpha needs to start from the initial opacity of the
+ // view flipper, otherwise we get a weird bit of jank as it ramps back to
+ // 100%.
+ v.alpha = opacity * initialAlpha
+ if (v is KeyguardSecurityViewFlipper) {
+ v.setLeftTopRightBottom(
+ startRect.left + currentTranslation,
+ startRect.top,
+ startRect.right + currentTranslation,
+ startRect.bottom
+ )
+ }
+ } else {
+ // And in again over the remaining (100-X)%.
+ val fadeInFraction =
+ MathUtils.constrainedMap(
+ /* rangeMin= */ 0.0f,
+ /* rangeMax= */ 1.0f,
+ /* valueMin= */ switchPoint,
+ /* valueMax= */ 1.0f,
+ animation.animatedFraction
+ )
+ opacity = fadeInInterpolator.getInterpolation(fadeInFraction)
+ v.alpha = opacity
+
+ // Fading back in, animate towards the destination.
+ if (v is KeyguardSecurityViewFlipper) {
+ v.setLeftTopRightBottom(
+ endRect.left - translationRemaining,
+ endRect.top,
+ endRect.right - translationRemaining,
+ endRect.bottom
+ )
+ }
+ }
+ if (animation.animatedFraction == 1.0f && shouldRestoreLayerType) {
+ v.setLayerType(View.LAYER_TYPE_NONE, /* paint= */ null)
+ }
+
+ // For views that are not the security view flipper, we do not want to apply
+ // an x translation animation. Instead, we want to fade out, move to final position and
+ // then fade in.
+ if (v !is KeyguardSecurityViewFlipper) {
+ // Opacity goes close to 0 but does not fully get to 0.
+ if (opacity - 0.001f < 0f) {
+ v.setLeftTopRightBottom(
+ endRect.left,
+ endRect.top,
+ endRect.right,
+ endRect.bottom
+ )
+ finishedFadingOutNonSecurityView = true
+ } else if (!finishedFadingOutNonSecurityView) {
+ v.setLeftTopRightBottom(
+ startRect.left,
+ startRect.top,
+ startRect.right,
+ startRect.bottom
+ )
+ }
+ }
+ }
+ runningSecurityShiftAnimator.start()
+ return runningSecurityShiftAnimator
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 761aa7e9..80120a9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2333,7 +2333,7 @@
*/
public void requestFaceAuth(boolean userInitiatedRequest,
@FaceAuthApiRequestReason String reason) {
- mLogger.logFaceAuthRequested(userInitiatedRequest);
+ mLogger.logFaceAuthRequested(userInitiatedRequest, reason);
updateFaceListeningState(BIOMETRIC_ACTION_START, apiRequestReasonToUiEvent(reason));
}
@@ -3164,14 +3164,7 @@
* Whether the keyguard is showing and not occluded.
*/
public boolean isKeyguardVisible() {
- return isKeyguardShowing() && !mKeyguardOccluded;
- }
-
- /**
- * Whether the keyguard is showing. It may still be occluded and not visible.
- */
- public boolean isKeyguardShowing() {
- return mKeyguardShowing;
+ return mKeyguardShowing && !mKeyguardOccluded;
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index 3ea8826..90f0446 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -94,11 +94,6 @@
void setOccluded(boolean occluded, boolean animate);
/**
- * @return Whether the keyguard is showing
- */
- boolean isShowing();
-
- /**
* Dismisses the keyguard by going to the next screen or making it gone.
*/
void dismissAndCollapse();
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 54cec71..2eee957 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -108,10 +108,11 @@
}, { "Face help received, msgId: $int1 msg: $str1" })
}
- fun logFaceAuthRequested(userInitiatedRequest: Boolean) {
- logBuffer.log(TAG, DEBUG,
- { bool1 = userInitiatedRequest },
- { "requestFaceAuth() userInitiated=$bool1" })
+ fun logFaceAuthRequested(userInitiatedRequest: Boolean, reason: String) {
+ logBuffer.log(TAG, DEBUG, {
+ bool1 = userInitiatedRequest
+ str1 = reason
+ }, { "requestFaceAuth() userInitiated=$bool1 reason=$str1" })
}
fun logFaceAuthSuccess(userId: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
index 109be40..37829f2 100644
--- a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
+++ b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
@@ -3,6 +3,7 @@
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
+import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -55,7 +56,11 @@
} else {
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
}
- packageManager.setComponentEnabledSetting(chooserComponent, newState, /* flags = */ 0)
+ try {
+ packageManager.setComponentEnabledSetting(chooserComponent, newState, /* flags = */ 0)
+ } catch (e: IllegalArgumentException) {
+ Log.w("ChooserSelector", "Unable to set IntentResolver enabled=" + enabled, e)
+ }
}
suspend inline fun awaitCancellation(): Nothing = suspendCancellableCoroutine { }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java
index 33e6ca4..9b441ad 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java
@@ -21,6 +21,8 @@
import android.view.Display;
import android.view.Surface;
+import com.android.systemui.R;
+
/**
* Utility class for determining screen and corner dimensions.
*/
@@ -82,17 +84,13 @@
* where the curve ends), in pixels.
*/
public static int getCornerRadiusBottom(Context context) {
- int radius = 0;
-
- int resourceId = context.getResources().getIdentifier("config_rounded_mask_size_bottom",
- "dimen", "com.android.systemui");
- if (resourceId > 0) {
- radius = context.getResources().getDimensionPixelSize(resourceId);
- }
+ int radius = context.getResources().getDimensionPixelSize(
+ R.dimen.config_rounded_mask_size_bottom);
if (radius == 0) {
radius = getCornerRadiusDefault(context);
}
+
return radius;
}
@@ -101,28 +99,17 @@
* the curve ends), in pixels.
*/
public static int getCornerRadiusTop(Context context) {
- int radius = 0;
-
- int resourceId = context.getResources().getIdentifier("config_rounded_mask_size_top",
- "dimen", "com.android.systemui");
- if (resourceId > 0) {
- radius = context.getResources().getDimensionPixelSize(resourceId);
- }
+ int radius = context.getResources().getDimensionPixelSize(
+ R.dimen.config_rounded_mask_size_top);
if (radius == 0) {
radius = getCornerRadiusDefault(context);
}
+
return radius;
}
private static int getCornerRadiusDefault(Context context) {
- int radius = 0;
-
- int resourceId = context.getResources().getIdentifier("config_rounded_mask_size",
- "dimen", "com.android.systemui");
- if (resourceId > 0) {
- radius = context.getResources().getDimensionPixelSize(resourceId);
- }
- return radius;
+ return context.getResources().getDimensionPixelSize(R.dimen.config_rounded_mask_size);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 2578df3..0f5a99c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -245,7 +245,7 @@
mAcquiredReceived = true;
final UdfpsView view = mOverlay.getOverlayView();
if (view != null) {
- view.unconfigureDisplay();
+ unconfigureDisplay(view);
}
if (acquiredGood) {
mOverlay.onAcquiredGood();
@@ -737,6 +737,19 @@
mOverlay = null;
mOrientationListener.disable();
+
+ }
+
+ private void unconfigureDisplay(@NonNull UdfpsView view) {
+ if (view.isDisplayConfigured()) {
+ view.unconfigureDisplay();
+
+ if (mCancelAodTimeoutAction != null) {
+ mCancelAodTimeoutAction.run();
+ mCancelAodTimeoutAction = null;
+ }
+ mIsAodInterruptActive = false;
+ }
}
/**
@@ -812,12 +825,12 @@
* sensors, this can result in illumination persisting for longer than necessary.
*/
void onCancelUdfps() {
- if (mOverlay != null && mOverlay.getOverlayView() != null) {
- onFingerUp(mOverlay.getRequestId(), mOverlay.getOverlayView());
- }
if (!mIsAodInterruptActive) {
return;
}
+ if (mOverlay != null && mOverlay.getOverlayView() != null) {
+ onFingerUp(mOverlay.getRequestId(), mOverlay.getOverlayView());
+ }
if (mCancelAodTimeoutAction != null) {
mCancelAodTimeoutAction.run();
mCancelAodTimeoutAction = null;
@@ -911,15 +924,8 @@
}
}
mOnFingerDown = false;
- if (view.isDisplayConfigured()) {
- view.unconfigureDisplay();
- }
+ unconfigureDisplay(view);
- if (mCancelAodTimeoutAction != null) {
- mCancelAodTimeoutAction.run();
- mCancelAodTimeoutAction = null;
- }
- mIsAodInterruptActive = false;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 934aedf..4d7f89d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -219,7 +219,7 @@
mView.animateInUdfpsBouncer(null);
}
- if (mKeyguardViewManager.isOccluded()) {
+ if (mKeyguardStateController.isOccluded()) {
mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 0d06c51..c2dffe8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -27,9 +27,6 @@
import com.android.systemui.keyguard.KeyguardSliceProvider;
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
-import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
-import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
-import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
import com.android.systemui.people.PeopleProvider;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.FoldStateLogger;
@@ -133,9 +130,6 @@
});
getNaturalRotationUnfoldProgressProvider().ifPresent(o -> o.init());
// No init method needed, just needs to be gotten so that it's created.
- getMediaTttChipControllerSender();
- getMediaTttChipControllerReceiver();
- getMediaTttCommandLineHelper();
getMediaMuteAwaitConnectionCli();
getNearbyMediaDevicesManager();
getUnfoldLatencyTracker().init();
@@ -206,15 +200,6 @@
Optional<NaturalRotationUnfoldProgressProvider> getNaturalRotationUnfoldProgressProvider();
/** */
- Optional<MediaTttChipControllerSender> getMediaTttChipControllerSender();
-
- /** */
- Optional<MediaTttChipControllerReceiver> getMediaTttChipControllerReceiver();
-
- /** */
- Optional<MediaTttCommandLineHelper> getMediaTttCommandLineHelper();
-
- /** */
Optional<MediaMuteAwaitConnectionCli> getMediaMuteAwaitConnectionCli();
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 8bb27a7..55eda0a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -32,6 +32,9 @@
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.log.SessionTracker
import com.android.systemui.media.RingtonePlayer
+import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper
+import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
+import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender
import com.android.systemui.power.PowerUI
import com.android.systemui.recents.Recents
import com.android.systemui.settings.dagger.MultiUserUtilsModule
@@ -213,4 +216,26 @@
@IntoMap
@ClassKey(KeyguardLiftController::class)
abstract fun bindKeyguardLiftController(sysui: KeyguardLiftController): CoreStartable
+
+ /** Inject into MediaTttChipControllerReceiver. */
+ @Binds
+ @IntoMap
+ @ClassKey(MediaTttChipControllerReceiver::class)
+ abstract fun bindMediaTttChipControllerReceiver(
+ sysui: MediaTttChipControllerReceiver
+ ): CoreStartable
+
+ /** Inject into MediaTttChipControllerSender. */
+ @Binds
+ @IntoMap
+ @ClassKey(MediaTttChipControllerSender::class)
+ abstract fun bindMediaTttChipControllerSender(
+ sysui: MediaTttChipControllerSender
+ ): CoreStartable
+
+ /** Inject into MediaTttCommandLineHelper. */
+ @Binds
+ @IntoMap
+ @ClassKey(MediaTttCommandLineHelper::class)
+ abstract fun bindMediaTttCommandLineHelper(sysui: MediaTttCommandLineHelper): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 696fc72..d1b7368 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -64,29 +64,26 @@
private final Executor mExecutor;
// A controller for the dream overlay container view (which contains both the status bar and the
// content area).
- private final DreamOverlayContainerViewController mDreamOverlayContainerViewController;
+ private DreamOverlayContainerViewController mDreamOverlayContainerViewController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Nullable
private final ComponentName mLowLightDreamComponent;
private final UiEventLogger mUiEventLogger;
+ private final WindowManager mWindowManager;
// A reference to the {@link Window} used to hold the dream overlay.
private Window mWindow;
- // True if the service has been destroyed.
- private boolean mDestroyed;
+ // True if a dream has bound to the service and dream overlay service has started.
+ private boolean mStarted = false;
- private final Complication.Host mHost = new Complication.Host() {
- @Override
- public void requestExitDream() {
- mExecutor.execute(DreamOverlayService.this::requestExit);
- }
- };
+ // True if the service has been destroyed.
+ private boolean mDestroyed = false;
+
+ private final DreamOverlayComponent mDreamOverlayComponent;
private final LifecycleRegistry mLifecycleRegistry;
- private ViewModelStore mViewModelStore = new ViewModelStore();
-
private DreamOverlayTouchMonitor mDreamOverlayTouchMonitor;
private final KeyguardUpdateMonitorCallback mKeyguardCallback =
@@ -103,7 +100,7 @@
}
};
- private DreamOverlayStateController mStateController;
+ private final DreamOverlayStateController mStateController;
@VisibleForTesting
public enum DreamOverlayEvent implements UiEventLogger.UiEventEnum {
@@ -128,6 +125,7 @@
public DreamOverlayService(
Context context,
@Main Executor executor,
+ WindowManager windowManager,
DreamOverlayComponent.Factory dreamOverlayComponentFactory,
DreamOverlayStateController stateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -136,19 +134,19 @@
ComponentName lowLightDreamComponent) {
mContext = context;
mExecutor = executor;
+ mWindowManager = windowManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLowLightDreamComponent = lowLightDreamComponent;
mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
mStateController = stateController;
mUiEventLogger = uiEventLogger;
- final DreamOverlayComponent component =
- dreamOverlayComponentFactory.create(mViewModelStore, mHost);
- mDreamOverlayContainerViewController = component.getDreamOverlayContainerViewController();
+ final ViewModelStore viewModelStore = new ViewModelStore();
+ final Complication.Host host =
+ () -> mExecutor.execute(DreamOverlayService.this::requestExit);
+ mDreamOverlayComponent = dreamOverlayComponentFactory.create(viewModelStore, host);
+ mLifecycleRegistry = mDreamOverlayComponent.getLifecycleRegistry();
setCurrentState(Lifecycle.State.CREATED);
- mLifecycleRegistry = component.getLifecycleRegistry();
- mDreamOverlayTouchMonitor = component.getDreamOverlayTouchMonitor();
- mDreamOverlayTouchMonitor.init();
}
private void setCurrentState(Lifecycle.State state) {
@@ -159,34 +157,48 @@
public void onDestroy() {
mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback);
setCurrentState(Lifecycle.State.DESTROYED);
- final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
- if (mWindow != null) {
- windowManager.removeView(mWindow.getDecorView());
- }
- mStateController.setOverlayActive(false);
- mStateController.setLowLightActive(false);
+
+ resetCurrentDreamOverlay();
+
mDestroyed = true;
super.onDestroy();
}
@Override
public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
- mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
setCurrentState(Lifecycle.State.STARTED);
- final ComponentName dreamComponent = getDreamComponent();
- mStateController.setLowLightActive(
- dreamComponent != null && dreamComponent.equals(mLowLightDreamComponent));
+
mExecutor.execute(() -> {
+ mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
+
if (mDestroyed) {
// The task could still be executed after the service has been destroyed. Bail if
// that is the case.
return;
}
+
+ if (mStarted) {
+ // Reset the current dream overlay before starting a new one. This can happen
+ // when two dreams overlap (briefly, for a smoother dream transition) and both
+ // dreams are bound to the dream overlay service.
+ resetCurrentDreamOverlay();
+ }
+
+ mDreamOverlayContainerViewController =
+ mDreamOverlayComponent.getDreamOverlayContainerViewController();
+ mDreamOverlayTouchMonitor = mDreamOverlayComponent.getDreamOverlayTouchMonitor();
+ mDreamOverlayTouchMonitor.init();
+
mStateController.setShouldShowComplications(shouldShowComplications());
addOverlayWindowLocked(layoutParams);
setCurrentState(Lifecycle.State.RESUMED);
mStateController.setOverlayActive(true);
+ final ComponentName dreamComponent = getDreamComponent();
+ mStateController.setLowLightActive(
+ dreamComponent != null && dreamComponent.equals(mLowLightDreamComponent));
mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START);
+
+ mStarted = true;
});
}
@@ -222,8 +234,7 @@
removeContainerViewFromParent();
mWindow.setContentView(mDreamOverlayContainerViewController.getContainerView());
- final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
- windowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
+ mWindowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
}
private void removeContainerViewFromParent() {
@@ -238,4 +249,18 @@
Log.w(TAG, "Removing dream overlay container view parent!");
parentView.removeView(containerView);
}
+
+ private void resetCurrentDreamOverlay() {
+ if (mStarted && mWindow != null) {
+ mWindowManager.removeView(mWindow.getDecorView());
+ }
+
+ mStateController.setOverlayActive(false);
+ mStateController.setLowLightActive(false);
+
+ mDreamOverlayContainerViewController = null;
+ mDreamOverlayTouchMonitor = null;
+
+ mStarted = false;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index fcf11ef..3dbadb0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -116,7 +116,7 @@
* the framework APIs.
*/
public static final UnreleasedFlag USER_INTERACTOR_AND_REPO_USE_CONTROLLER =
- new UnreleasedFlag(210, true);
+ new UnreleasedFlag(210);
/**
* Whether `UserSwitcherController` should use the user interactor.
@@ -127,8 +127,7 @@
* <p>Note: do not set this to true if {@link #USER_INTERACTOR_AND_REPO_USE_CONTROLLER} is
* {@code true} as it would created a cycle between controller -> interactor -> controller.
*/
- public static final UnreleasedFlag USER_CONTROLLER_USES_INTERACTOR =
- new UnreleasedFlag(211, false);
+ public static final ReleasedFlag USER_CONTROLLER_USES_INTERACTOR = new ReleasedFlag(211);
/***************************************/
// 300 - power menu
@@ -300,8 +299,8 @@
public static final UnreleasedFlag SCREENSHOT_REQUEST_PROCESSOR = new UnreleasedFlag(1300);
public static final UnreleasedFlag SCREENSHOT_WORK_PROFILE_POLICY = new UnreleasedFlag(1301);
- // 1400 - columbus, b/242800729
- public static final UnreleasedFlag QUICK_TAP_IN_PCC = new UnreleasedFlag(1400);
+ // 1400 - columbus
+ public static final ReleasedFlag QUICK_TAP_IN_PCC = new ReleasedFlag(1400);
// 1500 - chooser
public static final UnreleasedFlag CHOOSER_UNBUNDLED = new UnreleasedFlag(1500);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index ad8c688..c4eac1c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -662,7 +662,7 @@
}
override fun onKeyguardDismissAmountChanged() {
- if (keyguardViewController.isShowing && !playingCannedUnlockAnimation) {
+ if (keyguardStateController.isShowing && !playingCannedUnlockAnimation) {
showOrHideSurfaceIfDismissAmountThresholdsReached()
// If the surface is visible or it's about to be, start updating its appearance to
@@ -721,7 +721,7 @@
*/
private fun finishKeyguardExitRemoteAnimationIfReachThreshold() {
// no-op if keyguard is not showing or animation is not enabled.
- if (!keyguardViewController.isShowing) {
+ if (!keyguardStateController.isShowing) {
return
}
@@ -844,7 +844,7 @@
* animation.
*/
fun hideKeyguardViewAfterRemoteAnimation() {
- if (keyguardViewController.isShowing) {
+ if (keyguardStateController.isShowing) {
// Hide the keyguard, with no fade out since we animated it away during the unlock.
keyguardViewController.hide(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 7155acf..aee70ee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1875,7 +1875,7 @@
// if the keyguard is already showing, don't bother. check flags in both files
// to account for the hiding animation which results in a delay and discrepancy
// between flags
- if (mShowing && mKeyguardViewControllerLazy.get().isShowing()) {
+ if (mShowing && mKeyguardStateController.isShowing()) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
resetStateLocked();
return;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 0c5564b..28aa19e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -322,7 +322,7 @@
@SysUISingleton
@KeyguardUpdateMonitorLog
public static LogBuffer provideKeyguardUpdateMonitorLogBuffer(LogBufferFactory factory) {
- return factory.create("KeyguardUpdateMonitorLog", 200);
+ return factory.create("KeyguardUpdateMonitorLog", 400);
}
/**
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 00b0ff9..f5caefb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -22,6 +22,7 @@
import android.media.MediaRoute2Info
import android.util.Log
import androidx.annotation.VisibleForTesting
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver
@@ -39,14 +40,10 @@
*/
@SysUISingleton
class MediaTttCommandLineHelper @Inject constructor(
- commandRegistry: CommandRegistry,
+ private val commandRegistry: CommandRegistry,
private val context: Context,
@Main private val mainExecutor: Executor
-) {
- init {
- commandRegistry.registerCommand(SENDER_COMMAND) { SenderCommand() }
- commandRegistry.registerCommand(RECEIVER_COMMAND) { ReceiverCommand() }
- }
+) : CoreStartable(context) {
/** All commands for the sender device. */
inner class SenderCommand : Command {
@@ -56,7 +53,7 @@
val displayState: Int?
try {
displayState = ChipStateSender.getSenderStateIdFromName(commandName)
- } catch (ex: IllegalArgumentException) {
+ } catch (ex: IllegalArgumentException) {
pw.println("Invalid command name $commandName")
return
}
@@ -150,6 +147,11 @@
"<chipState> useAppIcon=[true|false]")
}
}
+
+ override fun start() {
+ commandRegistry.registerCommand(SENDER_COMMAND) { SenderCommand() }
+ commandRegistry.registerCommand(RECEIVER_COMMAND) { ReceiverCommand() }
+ }
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 511c4bf..1461293 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -55,7 +55,7 @@
*/
@SysUISingleton
class MediaTttChipControllerReceiver @Inject constructor(
- commandQueue: CommandQueue,
+ private val commandQueue: CommandQueue,
context: Context,
@MediaTttReceiverLogger logger: MediaTttLogger,
windowManager: WindowManager,
@@ -101,10 +101,6 @@
}
}
- init {
- commandQueue.addCallback(commandQueueCallbacks)
- }
-
private fun updateMediaTapToTransferReceiverDisplay(
@StatusBarManager.MediaTransferReceiverState displayState: Int,
routeInfo: MediaRoute2Info,
@@ -141,6 +137,10 @@
)
}
+ override fun start() {
+ commandQueue.addCallback(commandQueueCallbacks)
+ }
+
override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
context, newInfo.routeInfo.clientPackageName, logger
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 a98158e..5d63145 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
@@ -48,7 +48,6 @@
import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
-import dagger.Lazy
import javax.inject.Inject
/**
@@ -57,7 +56,7 @@
*/
@SysUISingleton
open class MediaTttChipControllerSender @Inject constructor(
- commandQueue: CommandQueue,
+ private val commandQueue: CommandQueue,
context: Context,
@MediaTttSenderLogger logger: MediaTttLogger,
windowManager: WindowManager,
@@ -66,10 +65,8 @@
configurationController: ConfigurationController,
powerManager: PowerManager,
private val uiEventLogger: MediaTttSenderUiEventLogger,
- // Added Lazy<> to delay the time we create Falsing instances.
- // And overcome performance issue, check [b/247817628] for details.
- private val falsingManager: Lazy<FalsingManager>,
- private val falsingCollector: Lazy<FalsingCollector>,
+ private val falsingManager: FalsingManager,
+ private val falsingCollector: FalsingCollector,
private val viewUtil: ViewUtil,
) : TemporaryViewDisplayController<ChipSenderInfo, MediaTttLogger>(
context,
@@ -102,10 +99,6 @@
}
}
- init {
- commandQueue.addCallback(commandQueueCallbacks)
- }
-
private fun updateMediaTapToTransferSenderDisplay(
@StatusBarManager.MediaTransferSenderState displayState: Int,
routeInfo: MediaRoute2Info,
@@ -128,6 +121,10 @@
}
}
+ override fun start() {
+ commandQueue.addCallback(commandQueueCallbacks)
+ }
+
override fun updateView(
newInfo: ChipSenderInfo,
currentView: ViewGroup
@@ -138,7 +135,7 @@
parent = currentView.requireViewById(R.id.media_ttt_sender_chip)
parent.touchHandler = object : Gefingerpoken {
override fun onTouchEvent(ev: MotionEvent?): Boolean {
- falsingCollector.get().onTouchEvent(ev)
+ falsingCollector.onTouchEvent(ev)
return false
}
}
@@ -167,7 +164,7 @@
newInfo.routeInfo,
newInfo.undoCallback,
uiEventLogger,
- falsingManager.get(),
+ falsingManager,
)
undoView.setOnClickListener(undoClickListener)
undoView.visibility = (undoClickListener != null).visibleIfTrue()
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index da9fefa..33021e3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -48,7 +48,6 @@
import androidx.annotation.NonNull;
-import com.android.keyguard.KeyguardViewController;
import com.android.systemui.Dumpable;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -61,6 +60,7 @@
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -90,7 +90,7 @@
private final AccessibilityManager mAccessibilityManager;
private final Lazy<AssistManager> mAssistManagerLazy;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
- private final KeyguardViewController mKeyguardViewController;
+ private final KeyguardStateController mKeyguardStateController;
private final UserTracker mUserTracker;
private final SystemActions mSystemActions;
private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
@@ -125,7 +125,7 @@
OverviewProxyService overviewProxyService,
Lazy<AssistManager> assistManagerLazy,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
- KeyguardViewController keyguardViewController,
+ KeyguardStateController keyguardStateController,
NavigationModeController navigationModeController,
UserTracker userTracker,
DumpManager dumpManager) {
@@ -134,7 +134,7 @@
mAccessibilityManager = accessibilityManager;
mAssistManagerLazy = assistManagerLazy;
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
- mKeyguardViewController = keyguardViewController;
+ mKeyguardStateController = keyguardStateController;
mUserTracker = userTracker;
mSystemActions = systemActions;
accessibilityManager.addAccessibilityServicesStateChangeListener(this);
@@ -326,7 +326,7 @@
shadeWindowView =
mCentralSurfacesOptionalLazy.get().get().getNotificationShadeWindowView();
}
- boolean isKeyguardShowing = mKeyguardViewController.isShowing();
+ boolean isKeyguardShowing = mKeyguardStateController.isShowing();
boolean imeVisibleOnShade = shadeWindowView != null && shadeWindowView.isAttachedToWindow()
&& shadeWindowView.getRootWindowInsets().isVisible(WindowInsets.Type.ime());
return imeVisibleOnShade
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index c337dea..6d76c17 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -4287,7 +4287,7 @@
}
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyExpanded()
- && mStatusBarKeyguardViewManager.isShowing()) {
+ && mKeyguardStateController.isShowing()) {
mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX());
}
@@ -4405,7 +4405,7 @@
}
mSysUiState.setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
isFullyExpanded() && !isInSettings())
- .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED, isInSettings())
+ .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED, isFullyExpanded() && isInSettings())
.commitUpdate(mDisplayId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
index 7dc9dc7..d71fbf6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
@@ -1,3 +1,6 @@
+justinweir@google.com
+syeonlee@google.com
+
per-file *Notification* = set noparent
per-file *Notification* = file:../statusbar/notification/OWNERS
@@ -11,4 +14,4 @@
per-file NotificationPanelUnfoldAnimationController.kt = alexflo@google.com, jeffdq@google.com, juliacr@google.com
per-file NotificationPanelView.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com
-per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com
\ No newline at end of file
+per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
index 1be4c04..b5c7ef5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification;
-import android.annotation.Nullable;
import android.util.ArraySet;
import androidx.annotation.VisibleForTesting;
@@ -25,7 +24,6 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import javax.inject.Inject;
@@ -43,7 +41,6 @@
private boolean mLastDynamicUnlocked;
private boolean mCacheInvalid;
- @Nullable private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Inject
DynamicPrivacyController(NotificationLockscreenUserManager notificationLockscreenUserManager,
@@ -100,7 +97,7 @@
* contents aren't revealed yet?
*/
public boolean isInLockedDownShade() {
- if (!isStatusBarKeyguardShowing() || !mKeyguardStateController.isMethodSecure()) {
+ if (!mKeyguardStateController.isShowing() || !mKeyguardStateController.isMethodSecure()) {
return false;
}
int state = mStateController.getState();
@@ -113,16 +110,7 @@
return true;
}
- private boolean isStatusBarKeyguardShowing() {
- return mStatusBarKeyguardViewManager != null && mStatusBarKeyguardViewManager.isShowing();
- }
-
- public void setStatusBarKeyguardViewManager(
- StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
- mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
- }
-
public interface Listener {
void onDynamicPrivacyChanged();
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
index a7719d3..e71d80c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
@@ -18,6 +18,8 @@
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+
import android.os.SystemClock;
import android.service.notification.NotificationStats;
@@ -70,6 +72,8 @@
dismissalSurface = NotificationStats.DISMISSAL_PEEK;
} else if (mStatusBarStateController.isDozing()) {
dismissalSurface = NotificationStats.DISMISSAL_AOD;
+ } else if (mStatusBarStateController.getState() == KEYGUARD) {
+ dismissalSurface = NotificationStats.DISMISSAL_LOCKSCREEN;
}
return new DismissedByUserStats(
dismissalSurface,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index 3292a8f..6cf4bf3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -37,6 +37,19 @@
boolean shouldHeadsUp(NotificationEntry entry);
/**
+ * Returns the value of whether this entry should peek (from shouldHeadsUp(entry)), but only
+ * optionally logs the status.
+ *
+ * This method should be used in cases where the caller needs to check whether a notification
+ * qualifies for a heads up, but is not necessarily guaranteed to make the heads-up happen.
+ *
+ * @param entry the entry to check
+ * @param log whether or not to log the results of this check
+ * @return true if the entry should heads up, false otherwise
+ */
+ boolean checkHeadsUp(NotificationEntry entry, boolean log);
+
+ /**
* Whether the notification should appear as a bubble with a fly-out on top of the screen.
*
* @param entry the entry to check
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 558fd62..c5a6921 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -137,11 +137,11 @@
public boolean shouldBubbleUp(NotificationEntry entry) {
final StatusBarNotification sbn = entry.getSbn();
- if (!canAlertCommon(entry)) {
+ if (!canAlertCommon(entry, true)) {
return false;
}
- if (!canAlertAwakeCommon(entry)) {
+ if (!canAlertAwakeCommon(entry, true)) {
return false;
}
@@ -163,10 +163,15 @@
@Override
public boolean shouldHeadsUp(NotificationEntry entry) {
+ return checkHeadsUp(entry, true);
+ }
+
+ @Override
+ public boolean checkHeadsUp(NotificationEntry entry, boolean log) {
if (mStatusBarStateController.isDozing()) {
- return shouldHeadsUpWhenDozing(entry);
+ return shouldHeadsUpWhenDozing(entry, log);
} else {
- return shouldHeadsUpWhenAwake(entry);
+ return shouldHeadsUpWhenAwake(entry, log);
}
}
@@ -263,61 +268,61 @@
}
}
- private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) {
+ private boolean shouldHeadsUpWhenAwake(NotificationEntry entry, boolean log) {
StatusBarNotification sbn = entry.getSbn();
if (!mUseHeadsUp) {
- mLogger.logNoHeadsUpFeatureDisabled();
+ if (log) mLogger.logNoHeadsUpFeatureDisabled();
return false;
}
- if (!canAlertCommon(entry)) {
+ if (!canAlertCommon(entry, log)) {
return false;
}
- if (!canAlertHeadsUpCommon(entry)) {
+ if (!canAlertHeadsUpCommon(entry, log)) {
return false;
}
- if (!canAlertAwakeCommon(entry)) {
+ if (!canAlertAwakeCommon(entry, log)) {
return false;
}
if (isSnoozedPackage(sbn)) {
- mLogger.logNoHeadsUpPackageSnoozed(entry);
+ if (log) mLogger.logNoHeadsUpPackageSnoozed(entry);
return false;
}
boolean inShade = mStatusBarStateController.getState() == SHADE;
if (entry.isBubble() && inShade) {
- mLogger.logNoHeadsUpAlreadyBubbled(entry);
+ if (log) mLogger.logNoHeadsUpAlreadyBubbled(entry);
return false;
}
if (entry.shouldSuppressPeek()) {
- mLogger.logNoHeadsUpSuppressedByDnd(entry);
+ if (log) mLogger.logNoHeadsUpSuppressedByDnd(entry);
return false;
}
if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
- mLogger.logNoHeadsUpNotImportant(entry);
+ if (log) mLogger.logNoHeadsUpNotImportant(entry);
return false;
}
boolean inUse = mPowerManager.isScreenOn() && !isDreaming();
if (!inUse) {
- mLogger.logNoHeadsUpNotInUse(entry);
+ if (log) mLogger.logNoHeadsUpNotInUse(entry);
return false;
}
for (int i = 0; i < mSuppressors.size(); i++) {
if (mSuppressors.get(i).suppressAwakeHeadsUp(entry)) {
- mLogger.logNoHeadsUpSuppressedBy(entry, mSuppressors.get(i));
+ if (log) mLogger.logNoHeadsUpSuppressedBy(entry, mSuppressors.get(i));
return false;
}
}
- mLogger.logHeadsUp(entry);
+ if (log) mLogger.logHeadsUp(entry);
return true;
}
@@ -328,37 +333,37 @@
* @param entry the entry to check
* @return true if the entry should ambient pulse, false otherwise
*/
- private boolean shouldHeadsUpWhenDozing(NotificationEntry entry) {
+ private boolean shouldHeadsUpWhenDozing(NotificationEntry entry, boolean log) {
if (!mAmbientDisplayConfiguration.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) {
- mLogger.logNoPulsingSettingDisabled(entry);
+ if (log) mLogger.logNoPulsingSettingDisabled(entry);
return false;
}
if (mBatteryController.isAodPowerSave()) {
- mLogger.logNoPulsingBatteryDisabled(entry);
+ if (log) mLogger.logNoPulsingBatteryDisabled(entry);
return false;
}
- if (!canAlertCommon(entry)) {
- mLogger.logNoPulsingNoAlert(entry);
+ if (!canAlertCommon(entry, log)) {
+ if (log) mLogger.logNoPulsingNoAlert(entry);
return false;
}
- if (!canAlertHeadsUpCommon(entry)) {
- mLogger.logNoPulsingNoAlert(entry);
+ if (!canAlertHeadsUpCommon(entry, log)) {
+ if (log) mLogger.logNoPulsingNoAlert(entry);
return false;
}
if (entry.shouldSuppressAmbient()) {
- mLogger.logNoPulsingNoAmbientEffect(entry);
+ if (log) mLogger.logNoPulsingNoAmbientEffect(entry);
return false;
}
if (entry.getImportance() < NotificationManager.IMPORTANCE_DEFAULT) {
- mLogger.logNoPulsingNotImportant(entry);
+ if (log) mLogger.logNoPulsingNotImportant(entry);
return false;
}
- mLogger.logPulsing(entry);
+ if (log) mLogger.logPulsing(entry);
return true;
}
@@ -366,18 +371,22 @@
* Common checks between regular & AOD heads up and bubbles.
*
* @param entry the entry to check
+ * @param log whether or not to log the results of these checks
* @return true if these checks pass, false if the notification should not alert
*/
- private boolean canAlertCommon(NotificationEntry entry) {
+ private boolean canAlertCommon(NotificationEntry entry, boolean log) {
for (int i = 0; i < mSuppressors.size(); i++) {
if (mSuppressors.get(i).suppressInterruptions(entry)) {
- mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), /* awake */ false);
+ if (log) {
+ mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i),
+ /* awake */ false);
+ }
return false;
}
}
if (mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry)) {
- mLogger.keyguardHideNotification(entry);
+ if (log) mLogger.keyguardHideNotification(entry);
return false;
}
@@ -388,19 +397,20 @@
* Common checks for heads up notifications on regular and AOD displays.
*
* @param entry the entry to check
+ * @param log whether or not to log the results of these checks
* @return true if these checks pass, false if the notification should not alert
*/
- private boolean canAlertHeadsUpCommon(NotificationEntry entry) {
+ private boolean canAlertHeadsUpCommon(NotificationEntry entry, boolean log) {
StatusBarNotification sbn = entry.getSbn();
// Don't alert notifications that are suppressed due to group alert behavior
if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
- mLogger.logNoAlertingGroupAlertBehavior(entry);
+ if (log) mLogger.logNoAlertingGroupAlertBehavior(entry);
return false;
}
if (entry.hasJustLaunchedFullScreenIntent()) {
- mLogger.logNoAlertingRecentFullscreen(entry);
+ if (log) mLogger.logNoAlertingRecentFullscreen(entry);
return false;
}
@@ -413,12 +423,14 @@
* @param entry the entry to check
* @return true if these checks pass, false if the notification should not alert
*/
- private boolean canAlertAwakeCommon(NotificationEntry entry) {
+ private boolean canAlertAwakeCommon(NotificationEntry entry, boolean log) {
StatusBarNotification sbn = entry.getSbn();
for (int i = 0; i < mSuppressors.size(); i++) {
if (mSuppressors.get(i).suppressAwakeInterruptions(entry)) {
- mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), /* awake */ true);
+ if (log) {
+ mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), /* awake */ true);
+ }
return false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index ce465bc..2719dd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -44,7 +44,8 @@
import javax.inject.Inject;
/**
- * A global state to track all input states for the algorithm.
+ * Global state to track all input states for
+ * {@link com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm}.
*/
@SysUISingleton
public class AmbientState implements Dumpable {
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 4576a64..9900e41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -150,7 +150,6 @@
private final KeyguardBypassController mKeyguardBypassController;
private PowerManager.WakeLock mWakeLock;
private final KeyguardUpdateMonitor mUpdateMonitor;
- private final DozeParameters mDozeParameters;
private final KeyguardStateController mKeyguardStateController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final SessionTracker mSessionTracker;
@@ -262,7 +261,7 @@
KeyguardStateController keyguardStateController, Handler handler,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@Main Resources resources,
- KeyguardBypassController keyguardBypassController, DozeParameters dozeParameters,
+ KeyguardBypassController keyguardBypassController,
MetricsLogger metricsLogger, DumpManager dumpManager,
PowerManager powerManager,
NotificationMediaManager notificationMediaManager,
@@ -276,7 +275,6 @@
VibratorHelper vibrator) {
mPowerManager = powerManager;
mUpdateMonitor = keyguardUpdateMonitor;
- mDozeParameters = dozeParameters;
mUpdateMonitor.registerCallback(this);
mMediaManager = notificationMediaManager;
mLatencyTracker = latencyTracker;
@@ -522,7 +520,7 @@
boolean deviceDreaming = mUpdateMonitor.isDreaming();
if (!mUpdateMonitor.isDeviceInteractive()) {
- if (!mKeyguardViewController.isShowing()
+ if (!mKeyguardStateController.isShowing()
&& !mScreenOffAnimationController.isKeyguardShowDelayed()) {
if (mKeyguardStateController.isUnlocked()) {
return MODE_WAKE_AND_UNLOCK;
@@ -539,7 +537,7 @@
if (unlockingAllowed && deviceDreaming) {
return MODE_WAKE_AND_UNLOCK_FROM_DREAM;
}
- if (mKeyguardViewController.isShowing()) {
+ if (mKeyguardStateController.isShowing()) {
if (mKeyguardViewController.bouncerIsOrWillBeShowing() && unlockingAllowed) {
return MODE_DISMISS_BOUNCER;
} else if (unlockingAllowed) {
@@ -558,7 +556,7 @@
boolean bypass = mKeyguardBypassController.getBypassEnabled()
|| mAuthController.isUdfpsFingerDown();
if (!mUpdateMonitor.isDeviceInteractive()) {
- if (!mKeyguardViewController.isShowing()) {
+ if (!mKeyguardStateController.isShowing()) {
return bypass ? MODE_WAKE_AND_UNLOCK : MODE_ONLY_WAKE;
} else if (!unlockingAllowed) {
return bypass ? MODE_SHOW_BOUNCER : MODE_NONE;
@@ -582,7 +580,7 @@
if (unlockingAllowed && mKeyguardStateController.isOccluded()) {
return MODE_UNLOCK_COLLAPSING;
}
- if (mKeyguardViewController.isShowing()) {
+ if (mKeyguardStateController.isShowing()) {
if ((mKeyguardViewController.bouncerIsOrWillBeShowing()
|| mKeyguardBypassController.getAltBouncerShowing()) && unlockingAllowed) {
return MODE_DISMISS_BOUNCER;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index e30bc68..d6fadca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -363,7 +363,7 @@
mKeyguardUpdateMonitor.onCameraLaunched();
}
- if (!mStatusBarKeyguardViewManager.isShowing()) {
+ if (!mKeyguardStateController.isShowing()) {
final Intent cameraIntent = CameraIntents.getInsecureCameraIntent(mContext);
mCentralSurfaces.startActivityDismissingKeyguard(cameraIntent,
false /* onlyProvisioned */, true /* dismissShade */,
@@ -420,7 +420,7 @@
// TODO(b/169087248) Possibly add haptics here for emergency action. Currently disabled for
// app-side haptic experimentation.
- if (!mStatusBarKeyguardViewManager.isShowing()) {
+ if (!mKeyguardStateController.isShowing()) {
mCentralSurfaces.startActivityDismissingKeyguard(emergencyIntent,
false /* onlyProvisioned */, true /* dismissShade */,
true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index cc15c82..b6e658f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -459,7 +459,6 @@
private final KeyguardStateController mKeyguardStateController;
private final HeadsUpManagerPhone mHeadsUpManager;
private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
- private final DynamicPrivacyController mDynamicPrivacyController;
private final FalsingCollector mFalsingCollector;
private final FalsingManager mFalsingManager;
private final BroadcastDispatcher mBroadcastDispatcher;
@@ -762,7 +761,6 @@
mHeadsUpManager = headsUpManagerPhone;
mKeyguardIndicationController = keyguardIndicationController;
mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
- mDynamicPrivacyController = dynamicPrivacyController;
mFalsingCollector = falsingCollector;
mFalsingManager = falsingManager;
mBroadcastDispatcher = broadcastDispatcher;
@@ -1553,7 +1551,6 @@
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
mRemoteInputManager.addControllerCallback(mStatusBarKeyguardViewManager);
- mDynamicPrivacyController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mLightBarController.setBiometricUnlockController(mBiometricUnlockController);
mMediaManager.setBiometricUnlockController(mBiometricUnlockController);
@@ -2062,7 +2059,7 @@
// Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
// the bouncer appear animation.
- if (!mStatusBarKeyguardViewManager.isShowing()) {
+ if (!mKeyguardStateController.isShowing()) {
WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
}
}
@@ -2499,8 +2496,8 @@
};
// Do not deferKeyguard when occluded because, when keyguard is occluded,
// we do not launch the activity until keyguard is done.
- boolean occluded = mStatusBarKeyguardViewManager.isShowing()
- && mStatusBarKeyguardViewManager.isOccluded();
+ boolean occluded = mKeyguardStateController.isShowing()
+ && mKeyguardStateController.isOccluded();
boolean deferred = !occluded;
executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShadeDirectly,
willLaunchResolverActivity, deferred /* deferred */, animate);
@@ -2570,8 +2567,8 @@
@Override
public boolean onDismiss() {
if (runnable != null) {
- if (mStatusBarKeyguardViewManager.isShowing()
- && mStatusBarKeyguardViewManager.isOccluded()) {
+ if (mKeyguardStateController.isShowing()
+ && mKeyguardStateController.isOccluded()) {
mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
} else {
mMainExecutor.execute(runnable);
@@ -2665,7 +2662,7 @@
private void executeWhenUnlocked(OnDismissAction action, boolean requiresShadeOpen,
boolean afterKeyguardGone) {
- if (mStatusBarKeyguardViewManager.isShowing() && requiresShadeOpen) {
+ if (mKeyguardStateController.isShowing() && requiresShadeOpen) {
mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
}
dismissKeyguardThenExecute(action, null /* cancelAction */,
@@ -2689,7 +2686,7 @@
mBiometricUnlockController.startWakeAndUnlock(
BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING);
}
- if (mStatusBarKeyguardViewManager.isShowing()) {
+ if (mKeyguardStateController.isShowing()) {
mStatusBarKeyguardViewManager.dismissWithAction(action, cancelAction,
afterKeyguardGone);
} else {
@@ -2825,8 +2822,8 @@
}
private void logStateToEventlog() {
- boolean isShowing = mStatusBarKeyguardViewManager.isShowing();
- boolean isOccluded = mStatusBarKeyguardViewManager.isOccluded();
+ boolean isShowing = mKeyguardStateController.isShowing();
+ boolean isOccluded = mKeyguardStateController.isOccluded();
boolean isBouncerShowing = mStatusBarKeyguardViewManager.isBouncerShowing();
boolean isSecure = mKeyguardStateController.isMethodSecure();
boolean unlocked = mKeyguardStateController.canDismissLockScreen();
@@ -3222,18 +3219,17 @@
Trace.traceCounter(Trace.TRACE_TAG_APP, "dozing", mDozing ? 1 : 0);
Trace.beginSection("CentralSurfaces#updateDozingState");
- boolean visibleNotOccluded = mStatusBarKeyguardViewManager.isShowing()
- && !mStatusBarKeyguardViewManager.isOccluded();
+ boolean keyguardVisible = mKeyguardStateController.isVisible();
// If we're dozing and we'll be animating the screen off, the keyguard isn't currently
// visible but will be shortly for the animation, so we should proceed as if it's visible.
- boolean visibleNotOccludedOrWillBe =
- visibleNotOccluded || (mDozing && mDozeParameters.shouldDelayKeyguardShow());
+ boolean keyguardVisibleOrWillBe =
+ keyguardVisible || (mDozing && mDozeParameters.shouldDelayKeyguardShow());
boolean wakeAndUnlock = mBiometricUnlockController.getMode()
== BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup() && !wakeAndUnlock)
|| (mDozing && mDozeParameters.shouldControlScreenOff()
- && visibleNotOccludedOrWillBe);
+ && keyguardVisibleOrWillBe);
mNotificationPanelViewController.setDozing(mDozing, animate);
updateQsExpansionEnabled();
@@ -3915,11 +3911,7 @@
@Override
public boolean isKeyguardShowing() {
- if (mStatusBarKeyguardViewManager == null) {
- Slog.i(TAG, "isKeyguardShowing() called before startKeyguard(), returning true");
- return true;
- }
- return mStatusBarKeyguardViewManager.isShowing();
+ return mKeyguardStateController.isShowing();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index d4d510f..5f5ec68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -230,8 +230,6 @@
private View mNotificationContainer;
@Nullable protected KeyguardBouncer mBouncer;
- protected boolean mShowing;
- protected boolean mOccluded;
protected boolean mRemoteInputActive;
private boolean mGlobalActionsVisible = false;
private boolean mLastGlobalActionsVisible = false;
@@ -278,10 +276,9 @@
new KeyguardUpdateMonitorCallback() {
@Override
public void onEmergencyCallAction() {
-
// Since we won't get a setOccluded call we have to reset the view manually such that
// the bouncer goes away.
- if (mOccluded) {
+ if (mKeyguardStateController.isOccluded()) {
reset(true /* hideBouncerWhenShowing */);
}
}
@@ -481,7 +478,7 @@
} else {
mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
}
- } else if (mShowing && !hideBouncerOverDream) {
+ } else if (mKeyguardStateController.isShowing() && !hideBouncerOverDream) {
if (!isWakeAndUnlocking()
&& !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
&& !mCentralSurfaces.isInLaunchTransition()
@@ -502,7 +499,7 @@
mBouncerInteractor.show(/* isScrimmed= */false);
}
}
- } else if (!mShowing && isBouncerInTransit()) {
+ } else if (!mKeyguardStateController.isShowing() && isBouncerInTransit()) {
// Keyguard is not visible anymore, but expansion animation was still running.
// We need to hide the bouncer, otherwise it will be stuck in transit.
if (mBouncer != null) {
@@ -535,9 +532,8 @@
@Override
public void show(Bundle options) {
Trace.beginSection("StatusBarKeyguardViewManager#show");
- mShowing = true;
mNotificationShadeWindowController.setKeyguardShowing(true);
- mKeyguardStateController.notifyKeyguardState(mShowing,
+ mKeyguardStateController.notifyKeyguardState(true,
mKeyguardStateController.isOccluded());
reset(true /* hideBouncerWhenShowing */);
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
@@ -601,7 +597,7 @@
} else {
mBouncerInteractor.hide();
}
- if (mShowing) {
+ if (mKeyguardStateController.isShowing()) {
// If we were showing the bouncer and then aborting, we need to also clear out any
// potential actions unless we actually unlocked.
cancelPostAuthActions();
@@ -618,7 +614,7 @@
public void showBouncer(boolean scrimmed) {
resetAlternateAuth(false);
- if (mShowing && !isBouncerShowing()) {
+ if (mKeyguardStateController.isShowing() && !isBouncerShowing()) {
if (mBouncer != null) {
mBouncer.show(false /* resetSecuritySelection */, scrimmed);
} else {
@@ -635,7 +631,7 @@
public void dismissWithAction(OnDismissAction r, Runnable cancelAction,
boolean afterKeyguardGone, String message) {
- if (mShowing) {
+ if (mKeyguardStateController.isShowing()) {
try {
Trace.beginSection("StatusBarKeyguardViewManager#dismissWithAction");
cancelPendingWakeupAction();
@@ -721,11 +717,12 @@
@Override
public void reset(boolean hideBouncerWhenShowing) {
- if (mShowing) {
+ if (mKeyguardStateController.isShowing()) {
+ final boolean isOccluded = mKeyguardStateController.isOccluded();
// Hide quick settings.
- mNotificationPanelViewController.resetViews(/* animate= */ !mOccluded);
+ mNotificationPanelViewController.resetViews(/* animate= */ !isOccluded);
// Hide bouncer and quick-quick settings.
- if (mOccluded && !mDozing) {
+ if (isOccluded && !mDozing) {
mCentralSurfaces.hideKeyguard();
if (hideBouncerWhenShowing || needsFullscreenBouncer()) {
hideBouncer(false /* destroyView */);
@@ -807,7 +804,8 @@
private void setDozing(boolean dozing) {
if (mDozing != dozing) {
mDozing = dozing;
- if (dozing || mBouncer.needsFullscreenBouncer() || mOccluded) {
+ if (dozing || mBouncer.needsFullscreenBouncer()
+ || mKeyguardStateController.isOccluded()) {
reset(dozing /* hideBouncerWhenShowing */);
}
updateStates();
@@ -840,18 +838,23 @@
@Override
public void setOccluded(boolean occluded, boolean animate) {
- final boolean isOccluding = !mOccluded && occluded;
- final boolean isUnOccluding = mOccluded && !occluded;
- setOccludedAndUpdateStates(occluded);
+ final boolean wasOccluded = mKeyguardStateController.isOccluded();
+ final boolean isOccluding = !wasOccluded && occluded;
+ final boolean isUnOccluding = wasOccluded && !occluded;
+ mKeyguardStateController.notifyKeyguardState(
+ mKeyguardStateController.isShowing(), occluded);
+ updateStates();
+ final boolean isShowing = mKeyguardStateController.isShowing();
+ final boolean isOccluded = mKeyguardStateController.isOccluded();
- if (mShowing && isOccluding) {
+ if (isShowing && isOccluding) {
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__OCCLUDED);
if (mCentralSurfaces.isInLaunchTransition()) {
final Runnable endRunnable = new Runnable() {
@Override
public void run() {
- mNotificationShadeWindowController.setKeyguardOccluded(mOccluded);
+ mNotificationShadeWindowController.setKeyguardOccluded(isOccluded);
reset(true /* hideBouncerWhenShowing */);
}
};
@@ -866,19 +869,19 @@
// When isLaunchingActivityOverLockscreen() is true, we know for sure that the post
// collapse runnables will be run.
mShadeController.get().addPostCollapseAction(() -> {
- mNotificationShadeWindowController.setKeyguardOccluded(mOccluded);
+ mNotificationShadeWindowController.setKeyguardOccluded(isOccluded);
reset(true /* hideBouncerWhenShowing */);
});
return;
}
- } else if (mShowing && isUnOccluding) {
+ } else if (isShowing && isUnOccluding) {
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
}
- if (mShowing) {
- mMediaManager.updateMediaMetaData(false, animate && !mOccluded);
+ if (isShowing) {
+ mMediaManager.updateMediaMetaData(false, animate && !isOccluded);
}
- mNotificationShadeWindowController.setKeyguardOccluded(mOccluded);
+ mNotificationShadeWindowController.setKeyguardOccluded(isOccluded);
// setDozing(false) will call reset once we stop dozing. Also, if we're going away, there's
// no need to reset the keyguard views as we'll be gone shortly. Resetting now could cause
@@ -888,20 +891,11 @@
// by a FLAG_DISMISS_KEYGUARD_ACTIVITY.
reset(isOccluding /* hideBouncerWhenShowing*/);
}
- if (animate && !mOccluded && mShowing && !bouncerIsShowing()) {
+ if (animate && !isOccluded && isShowing && !bouncerIsShowing()) {
mCentralSurfaces.animateKeyguardUnoccluding();
}
}
- private void setOccludedAndUpdateStates(boolean occluded) {
- mOccluded = occluded;
- updateStates();
- }
-
- public boolean isOccluded() {
- return mOccluded;
- }
-
@Override
public void startPreHideAnimation(Runnable finishRunnable) {
if (bouncerIsShowing()) {
@@ -932,8 +926,7 @@
@Override
public void hide(long startTime, long fadeoutDuration) {
Trace.beginSection("StatusBarKeyguardViewManager#hide");
- mShowing = false;
- mKeyguardStateController.notifyKeyguardState(mShowing,
+ mKeyguardStateController.notifyKeyguardState(false,
mKeyguardStateController.isOccluded());
launchPendingWakeupAction();
@@ -1081,11 +1074,6 @@
KeyguardUpdateMonitor.getCurrentUser()) != KeyguardSecurityModel.SecurityMode.None;
}
- @Override
- public boolean isShowing() {
- return mShowing;
- }
-
/**
* Returns whether a back invocation can be handled, which depends on whether the keyguard
* is currently showing (which itself is derived from multiple states).
@@ -1184,8 +1172,8 @@
if (!mCentralSurfacesRegistered) {
return;
}
- boolean showing = mShowing;
- boolean occluded = mOccluded;
+ boolean showing = mKeyguardStateController.isShowing();
+ boolean occluded = mKeyguardStateController.isOccluded();
boolean bouncerShowing = bouncerIsShowing();
boolean bouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing();
boolean bouncerDismissible = !isFullscreenBouncer();
@@ -1219,13 +1207,6 @@
mNotificationShadeWindowController.setBouncerShowing(bouncerShowing);
mCentralSurfaces.setBouncerShowing(bouncerShowing);
}
-
- if (occluded != mLastOccluded || mFirstUpdate) {
- mKeyguardStateController.notifyKeyguardState(showing, occluded);
- }
- if (occluded != mLastOccluded || mShowing != showing || mFirstUpdate) {
- mKeyguardUpdateManager.setKeyguardShowing(showing, occluded);
- }
if (bouncerIsOrWillBeShowing != mLastBouncerIsOrWillBeShowing || mFirstUpdate
|| bouncerShowing != mLastBouncerShowing) {
mKeyguardUpdateManager.sendKeyguardBouncerChanged(bouncerIsOrWillBeShowing,
@@ -1276,12 +1257,12 @@
public boolean isNavBarVisible() {
boolean isWakeAndUnlockPulsing = mBiometricUnlockController != null
&& mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK_PULSING;
- boolean keyguardShowing = mShowing && !mOccluded;
+ boolean keyguardVisible = mKeyguardStateController.isVisible();
boolean hideWhileDozing = mDozing && !isWakeAndUnlockPulsing;
- boolean keyguardWithGestureNav = (keyguardShowing && !mDozing && !mScreenOffAnimationPlaying
+ boolean keyguardWithGestureNav = (keyguardVisible && !mDozing && !mScreenOffAnimationPlaying
|| mPulsing && !mIsDocked)
&& mGesturalNav;
- return (!keyguardShowing && !hideWhileDozing && !mScreenOffAnimationPlaying
+ return (!keyguardVisible && !hideWhileDozing && !mScreenOffAnimationPlaying
|| bouncerIsShowing()
|| mRemoteInputActive
|| keyguardWithGestureNav
@@ -1413,7 +1394,7 @@
DismissWithActionRequest request = mPendingWakeupAction;
mPendingWakeupAction = null;
if (request != null) {
- if (mShowing) {
+ if (mKeyguardStateController.isShowing()) {
dismissWithAction(request.dismissAction, request.cancelAction,
request.afterKeyguardGone, request.message);
} else if (request.dismissAction != null) {
@@ -1432,10 +1413,10 @@
public boolean bouncerNeedsScrimming() {
// When a dream overlay is active, scrimming will cause any expansion to immediately expand.
- return (mOccluded && !mDreamOverlayStateController.isOverlayActive())
+ return (mKeyguardStateController.isOccluded()
+ && !mDreamOverlayStateController.isOverlayActive())
|| bouncerWillDismissWithAction()
- || (bouncerIsShowing()
- && bouncerIsScrimmed())
+ || (bouncerIsShowing() && bouncerIsScrimmed())
|| isFullscreenBouncer();
}
@@ -1454,8 +1435,6 @@
public void dump(PrintWriter pw) {
pw.println("StatusBarKeyguardViewManager:");
- pw.println(" mShowing: " + mShowing);
- pw.println(" mOccluded: " + mOccluded);
pw.println(" mRemoteInputActive: " + mRemoteInputActive);
pw.println(" mDozing: " + mDozing);
pw.println(" mAfterKeyguardGoneAction: " + mAfterKeyguardGoneAction);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
index c96faab..062c3d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
@@ -21,7 +21,9 @@
/** Provides information about the current wifi network. */
sealed class WifiNetworkModel {
/** A model representing that we have no active wifi network. */
- object Inactive : WifiNetworkModel()
+ object Inactive : WifiNetworkModel() {
+ override fun toString() = "WifiNetwork.Inactive"
+ }
/**
* A model representing that our wifi network is actually a carrier merged network, meaning it's
@@ -29,7 +31,9 @@
*
* See [android.net.wifi.WifiInfo.isCarrierMerged] for more information.
*/
- object CarrierMerged : WifiNetworkModel()
+ object CarrierMerged : WifiNetworkModel() {
+ override fun toString() = "WifiNetwork.CarrierMerged"
+ }
/** Provides information about an active wifi network. */
data class Active(
@@ -72,6 +76,24 @@
}
}
+ override fun toString(): String {
+ // Only include the passpoint-related values in the string if we have them. (Most
+ // networks won't have them so they'll be mostly clutter.)
+ val passpointString =
+ if (isPasspointAccessPoint ||
+ isOnlineSignUpForPasspointAccessPoint ||
+ passpointProviderFriendlyName != null) {
+ ", isPasspointAp=$isPasspointAccessPoint, " +
+ "isOnlineSignUpForPasspointAp=$isOnlineSignUpForPasspointAccessPoint, " +
+ "passpointName=$passpointProviderFriendlyName"
+ } else {
+ ""
+ }
+
+ return "WifiNetworkModel.Active(networkId=$networkId, isValidated=$isValidated, " +
+ "level=$level, ssid=$ssid$passpointString)"
+ }
+
companion object {
@VisibleForTesting
internal const val MIN_VALID_LEVEL = 0
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 250d9d4..1ae1eae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -35,9 +35,16 @@
}
/**
- * If the lock screen is visible.
- * The keyguard is also visible when the device is asleep or in always on mode, except when
- * the screen timed out and the user can unlock by quickly pressing power.
+ * If the keyguard is visible. This is unrelated to being locked or not.
+ */
+ default boolean isVisible() {
+ return isShowing() && !isOccluded();
+ }
+
+ /**
+ * If the keyguard is showing. This includes when it's occluded by an activity, and when
+ * the device is asleep or in always on mode, except when the screen timed out and the user
+ * can unlock by quickly pressing power.
*
* This is unrelated to being locked or not.
*
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 437d4d4..cc6fdcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -181,6 +181,7 @@
if (mShowing == showing && mOccluded == occluded) return;
mShowing = showing;
mOccluded = occluded;
+ mKeyguardUpdateMonitor.setKeyguardShowing(showing, occluded);
Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events",
"Keyguard showing: " + showing + " occluded: " + occluded);
notifyKeyguardChanged();
@@ -387,6 +388,8 @@
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardStateController:");
+ pw.println(" mShowing: " + mShowing);
+ pw.println(" mOccluded: " + mOccluded);
pw.println(" mSecure: " + mSecure);
pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen);
pw.println(" mTrustManaged: " + mTrustManaged);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index e0d780a..afcf7a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.window;
+import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
+import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
@@ -27,6 +30,7 @@
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Binder;
@@ -36,6 +40,7 @@
import android.view.DisplayCutout;
import android.view.Gravity;
import android.view.IWindowManager;
+import android.view.InsetsFrameProvider;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
@@ -221,6 +226,19 @@
lp.setTitle("StatusBar");
lp.packageName = mContext.getPackageName();
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ final InsetsFrameProvider gestureInsetsProvider =
+ new InsetsFrameProvider(ITYPE_TOP_MANDATORY_GESTURES);
+ final int safeTouchRegionHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.display_cutout_touchable_region_size);
+ if (safeTouchRegionHeight > 0) {
+ gestureInsetsProvider.minimalInsetsSizeInDisplayCutoutSafe =
+ Insets.of(0, safeTouchRegionHeight, 0, 0);
+ }
+ lp.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(ITYPE_STATUS_BAR),
+ new InsetsFrameProvider(ITYPE_TOP_TAPPABLE_ELEMENT),
+ gestureInsetsProvider
+ };
return lp;
}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index 93650b0..4450b76 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -32,6 +32,7 @@
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -61,7 +62,7 @@
@LayoutRes private val viewLayoutRes: Int,
private val windowTitle: String,
private val wakeReason: String,
-) {
+) : CoreStartable(context) {
/**
* Window layout params that will be used as a starting point for the [windowLayoutParams] of
* all subclasses.
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
index 80385e6..f5cd0ca 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
@@ -41,6 +41,7 @@
import android.testing.ViewUtils;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceView;
+import android.view.View;
import androidx.test.filters.SmallTest;
@@ -84,6 +85,7 @@
MockitoAnnotations.initMocks(this);
mKeyguardSecurityContainer = spy(new KeyguardSecurityContainer(mContext));
+ mKeyguardSecurityContainer.setId(View.generateViewId());
ViewUtils.attachView(mKeyguardSecurityContainer);
mTestableLooper = TestableLooper.get(this);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index c1036e3..52f8825 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -21,10 +21,14 @@
import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE;
import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT;
import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.systemBars;
+import static androidx.constraintlayout.widget.ConstraintSet.CHAIN_SPREAD;
+import static androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT;
+import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
+import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
+
import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT;
import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED;
@@ -32,9 +36,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -43,19 +44,17 @@
import android.graphics.Insets;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
-import android.widget.FrameLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.user.data.source.UserRecord;
import com.android.systemui.util.settings.GlobalSettings;
@@ -64,8 +63,6 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -78,14 +75,11 @@
public class KeyguardSecurityContainerTest extends SysuiTestCase {
private static final int VIEW_WIDTH = 1600;
-
- private int mScreenWidth;
- private int mFakeMeasureSpec;
+ private static final int VIEW_HEIGHT = 900;
@Rule
public MockitoRule mRule = MockitoJUnit.rule();
- @Mock
private KeyguardSecurityViewFlipper mSecurityViewFlipper;
@Mock
private GlobalSettings mGlobalSettings;
@@ -93,59 +87,32 @@
private FalsingManager mFalsingManager;
@Mock
private UserSwitcherController mUserSwitcherController;
- @Mock
- private KeyguardStateController mKeyguardStateController;
- @Captor
- private ArgumentCaptor<FrameLayout.LayoutParams> mLayoutCaptor;
private KeyguardSecurityContainer mKeyguardSecurityContainer;
- private FrameLayout.LayoutParams mSecurityViewFlipperLayoutParams;
@Before
public void setup() {
// Needed here, otherwise when mKeyguardSecurityContainer is created below, it'll cache
// the real references (rather than the TestableResources that this call creates).
mContext.ensureTestableResources();
- mSecurityViewFlipperLayoutParams = new FrameLayout.LayoutParams(
- MATCH_PARENT, MATCH_PARENT);
- when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
+ mSecurityViewFlipper = new KeyguardSecurityViewFlipper(getContext());
+ mSecurityViewFlipper.setId(View.generateViewId());
mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext());
+ mKeyguardSecurityContainer.setRight(VIEW_WIDTH);
+ mKeyguardSecurityContainer.setLeft(0);
+ mKeyguardSecurityContainer.setTop(0);
+ mKeyguardSecurityContainer.setBottom(VIEW_HEIGHT);
+ mKeyguardSecurityContainer.setId(View.generateViewId());
mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper;
mKeyguardSecurityContainer.addView(mSecurityViewFlipper, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
when(mUserSwitcherController.getCurrentUserName()).thenReturn("Test User");
when(mUserSwitcherController.isKeyguardShowing()).thenReturn(true);
-
- mScreenWidth = getUiDevice().getDisplayWidth();
- mFakeMeasureSpec = View
- .MeasureSpec.makeMeasureSpec(mScreenWidth, View.MeasureSpec.EXACTLY);
}
-
@Test
- public void onMeasure_usesHalfWidthWithOneHandedModeEnabled() {
- mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager,
- mUserSwitcherController);
-
- int halfWidthMeasureSpec =
- View.MeasureSpec.makeMeasureSpec(mScreenWidth / 2, View.MeasureSpec.EXACTLY);
- mKeyguardSecurityContainer.onMeasure(mFakeMeasureSpec, mFakeMeasureSpec);
-
- verify(mSecurityViewFlipper).measure(halfWidthMeasureSpec, mFakeMeasureSpec);
- }
-
- @Test
- public void onMeasure_usesFullWidthWithOneHandedModeDisabled() {
- mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
- mUserSwitcherController);
-
- mKeyguardSecurityContainer.measure(mFakeMeasureSpec, mFakeMeasureSpec);
- verify(mSecurityViewFlipper).measure(mFakeMeasureSpec, mFakeMeasureSpec);
- }
-
- @Test
- public void onMeasure_respectsViewInsets() {
+ public void testOnApplyWindowInsets() {
int paddingBottom = getContext().getResources()
.getDimensionPixelSize(R.dimen.keyguard_security_view_bottom_margin);
int imeInsetAmount = paddingBottom + 1;
@@ -162,17 +129,12 @@
.setInsetsIgnoringVisibility(systemBars(), systemBarInset)
.build();
- // It's reduced by the max of the systembar and IME, so just subtract IME inset.
- int expectedHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
- mScreenWidth - imeInsetAmount, View.MeasureSpec.EXACTLY);
-
mKeyguardSecurityContainer.onApplyWindowInsets(insets);
- mKeyguardSecurityContainer.measure(mFakeMeasureSpec, mFakeMeasureSpec);
- verify(mSecurityViewFlipper).measure(mFakeMeasureSpec, expectedHeightMeasureSpec);
+ assertThat(mKeyguardSecurityContainer.getPaddingBottom()).isEqualTo(imeInsetAmount);
}
@Test
- public void onMeasure_respectsViewInsets_largerSystembar() {
+ public void testOnApplyWindowInsets_largerSystembar() {
int imeInsetAmount = 0;
int paddingBottom = getContext().getResources()
.getDimensionPixelSize(R.dimen.keyguard_security_view_bottom_margin);
@@ -189,25 +151,22 @@
.setInsetsIgnoringVisibility(systemBars(), systemBarInset)
.build();
- int expectedHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
- mScreenWidth - systemBarInsetAmount, View.MeasureSpec.EXACTLY);
-
mKeyguardSecurityContainer.onApplyWindowInsets(insets);
- mKeyguardSecurityContainer.measure(mFakeMeasureSpec, mFakeMeasureSpec);
- verify(mSecurityViewFlipper).measure(mFakeMeasureSpec, expectedHeightMeasureSpec);
+ assertThat(mKeyguardSecurityContainer.getPaddingBottom()).isEqualTo(systemBarInsetAmount);
}
- private void setupForUpdateKeyguardPosition(boolean oneHandedMode) {
- int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT;
- mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager,
+ @Test
+ public void testDefaultViewMode() {
+ mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager,
mUserSwitcherController);
-
- mKeyguardSecurityContainer.measure(mFakeMeasureSpec, mFakeMeasureSpec);
- mKeyguardSecurityContainer.layout(0, 0, mScreenWidth, mScreenWidth);
-
- // Clear any interactions with the mock so we know the interactions definitely come from the
- // below testing.
- reset(mSecurityViewFlipper);
+ mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
+ mUserSwitcherController);
+ ConstraintSet.Constraint viewFlipperConstraint =
+ getViewConstraint(mSecurityViewFlipper.getId());
+ assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.startToStart).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.endToEnd).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
}
@Test
@@ -217,13 +176,22 @@
mKeyguardSecurityContainer.getWidth() - 1f);
verify(mGlobalSettings).putInt(ONE_HANDED_KEYGUARD_SIDE, ONE_HANDED_KEYGUARD_SIDE_RIGHT);
- assertSecurityTranslationX(
- mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth());
+ ConstraintSet.Constraint viewFlipperConstraint =
+ getViewConstraint(mSecurityViewFlipper.getId());
+ assertThat(viewFlipperConstraint.layout.widthPercent).isEqualTo(0.5f);
+ assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(-1);
mKeyguardSecurityContainer.updatePositionByTouchX(1f);
verify(mGlobalSettings).putInt(ONE_HANDED_KEYGUARD_SIDE, ONE_HANDED_KEYGUARD_SIDE_LEFT);
- verify(mSecurityViewFlipper).setTranslationX(0.0f);
+ viewFlipperConstraint =
+ getViewConstraint(mSecurityViewFlipper.getId());
+ assertThat(viewFlipperConstraint.layout.widthPercent).isEqualTo(0.5f);
+ assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(-1);
}
@Test
@@ -232,10 +200,16 @@
mKeyguardSecurityContainer.updatePositionByTouchX(
mKeyguardSecurityContainer.getWidth() - 1f);
- verify(mSecurityViewFlipper, never()).setTranslationX(anyInt());
+ ConstraintSet.Constraint viewFlipperConstraint =
+ getViewConstraint(mSecurityViewFlipper.getId());
+ assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(-1);
+ assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(-1);
mKeyguardSecurityContainer.updatePositionByTouchX(1f);
- verify(mSecurityViewFlipper, never()).setTranslationX(anyInt());
+ viewFlipperConstraint =
+ getViewConstraint(mSecurityViewFlipper.getId());
+ assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(-1);
+ assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(-1);
}
@Test
@@ -249,17 +223,31 @@
setupUserSwitcher();
mKeyguardSecurityContainer.onConfigurationChanged(landscapeConfig);
- // THEN views are oriented side by side
- assertSecurityGravity(Gravity.LEFT | Gravity.BOTTOM);
- assertUserSwitcherGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
- assertSecurityTranslationX(
- mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth());
- assertUserSwitcherTranslationX(0f);
-
+ ConstraintSet.Constraint viewFlipperConstraint =
+ getViewConstraint(mSecurityViewFlipper.getId());
+ ConstraintSet.Constraint userSwitcherConstraint =
+ getViewConstraint(R.id.keyguard_bouncer_user_switcher);
+ assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.leftToRight).isEqualTo(
+ R.id.keyguard_bouncer_user_switcher);
+ assertThat(userSwitcherConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
+ assertThat(userSwitcherConstraint.layout.rightToLeft).isEqualTo(
+ mSecurityViewFlipper.getId());
+ assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
+ assertThat(userSwitcherConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+ assertThat(userSwitcherConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
+ assertThat(userSwitcherConstraint.layout.bottomMargin).isEqualTo(
+ getContext().getResources().getDimensionPixelSize(
+ R.dimen.bouncer_user_switcher_y_trans));
+ assertThat(viewFlipperConstraint.layout.horizontalChainStyle).isEqualTo(CHAIN_SPREAD);
+ assertThat(userSwitcherConstraint.layout.horizontalChainStyle).isEqualTo(CHAIN_SPREAD);
+ assertThat(viewFlipperConstraint.layout.mHeight).isEqualTo(MATCH_CONSTRAINT);
+ assertThat(userSwitcherConstraint.layout.mHeight).isEqualTo(MATCH_CONSTRAINT);
}
@Test
- public void testUserSwitcherModeViewGravityPortrait() {
+ public void testUserSwitcherModeViewPositionPortrait() {
// GIVEN one user has been setup and in landscape
when(mUserSwitcherController.getUsers()).thenReturn(buildUserRecords(1));
Configuration portraitConfig = configuration(ORIENTATION_PORTRAIT);
@@ -267,15 +255,28 @@
// WHEN UserSwitcherViewMode is initialized and config has changed
setupUserSwitcher();
- reset(mSecurityViewFlipper);
- when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
mKeyguardSecurityContainer.onConfigurationChanged(portraitConfig);
- // THEN views are both centered horizontally
- assertSecurityGravity(Gravity.CENTER_HORIZONTAL);
- assertUserSwitcherGravity(Gravity.CENTER_HORIZONTAL);
- assertSecurityTranslationX(0);
- assertUserSwitcherTranslationX(0);
+ ConstraintSet.Constraint viewFlipperConstraint =
+ getViewConstraint(mSecurityViewFlipper.getId());
+ ConstraintSet.Constraint userSwitcherConstraint =
+ getViewConstraint(R.id.keyguard_bouncer_user_switcher);
+
+ assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
+ assertThat(userSwitcherConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+ assertThat(userSwitcherConstraint.layout.topMargin).isEqualTo(
+ getContext().getResources().getDimensionPixelSize(
+ R.dimen.bouncer_user_switcher_y_trans));
+ assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(PARENT_ID);
+ assertThat(userSwitcherConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
+ assertThat(userSwitcherConstraint.layout.rightToRight).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.verticalChainStyle).isEqualTo(CHAIN_SPREAD);
+ assertThat(userSwitcherConstraint.layout.verticalChainStyle).isEqualTo(CHAIN_SPREAD);
+ assertThat(viewFlipperConstraint.layout.mHeight).isEqualTo(MATCH_CONSTRAINT);
+ assertThat(userSwitcherConstraint.layout.mHeight).isEqualTo(WRAP_CONTENT);
+ assertThat(userSwitcherConstraint.layout.mWidth).isEqualTo(WRAP_CONTENT);
}
@Test
@@ -337,9 +338,9 @@
when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_LEFT);
mKeyguardSecurityContainer.onConfigurationChanged(new Configuration());
- assertSecurityTranslationX(0);
- assertUserSwitcherTranslationX(
- mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth());
+ ConstraintSet.Constraint viewFlipperConstraint = getViewConstraint(
+ mSecurityViewFlipper.getId());
+ assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
}
private Configuration configuration(@Configuration.Orientation int orientation) {
@@ -348,28 +349,6 @@
return config;
}
- private void assertSecurityTranslationX(float translation) {
- verify(mSecurityViewFlipper).setTranslationX(translation);
- }
-
- private void assertUserSwitcherTranslationX(float translation) {
- ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById(
- R.id.keyguard_bouncer_user_switcher);
- assertThat(userSwitcher.getTranslationX()).isEqualTo(translation);
- }
-
- private void assertUserSwitcherGravity(@Gravity.GravityFlags int gravity) {
- ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById(
- R.id.keyguard_bouncer_user_switcher);
- assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity)
- .isEqualTo(gravity);
- }
-
- private void assertSecurityGravity(@Gravity.GravityFlags int gravity) {
- verify(mSecurityViewFlipper, atLeastOnce()).setLayoutParams(mLayoutCaptor.capture());
- assertThat(mLayoutCaptor.getValue().gravity).isEqualTo(gravity);
- }
-
private void setViewWidth(int width) {
mKeyguardSecurityContainer.setRight(width);
mKeyguardSecurityContainer.setLeft(0);
@@ -399,9 +378,6 @@
when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_RIGHT);
mKeyguardSecurityContainer.initMode(KeyguardSecurityContainer.MODE_USER_SWITCHER,
mGlobalSettings, mFalsingManager, mUserSwitcherController);
- // reset mSecurityViewFlipper so setup doesn't influence test verifications
- reset(mSecurityViewFlipper);
- when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
}
private ArrayList<UserRecord> buildUserRecords(int count) {
@@ -415,4 +391,17 @@
}
return users;
}
+
+ private void setupForUpdateKeyguardPosition(boolean oneHandedMode) {
+ int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT;
+ mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager,
+ mUserSwitcherController);
+ }
+
+ /** Get the ConstraintLayout constraint of the view. */
+ private ConstraintSet.Constraint getViewConstraint(int viewId) {
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.clone(mKeyguardSecurityContainer);
+ return constraintSet.getConstraint(viewId);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java
new file mode 100644
index 0000000..dd9683f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.assist.ui;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DisplayUtilsTest extends SysuiTestCase {
+
+ @Mock
+ Resources mResources;
+ @Mock
+ Context mMockContext;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testGetCornerRadii_noOverlay() {
+ assertEquals(0, DisplayUtils.getCornerRadiusBottom(mContext));
+ assertEquals(0, DisplayUtils.getCornerRadiusTop(mContext));
+ }
+
+ @Test
+ public void testGetCornerRadii_onlyDefaultOverridden() {
+ when(mResources.getDimensionPixelSize(R.dimen.config_rounded_mask_size)).thenReturn(100);
+ when(mMockContext.getResources()).thenReturn(mResources);
+
+ assertEquals(100, DisplayUtils.getCornerRadiusBottom(mMockContext));
+ assertEquals(100, DisplayUtils.getCornerRadiusTop(mMockContext));
+ }
+
+ @Test
+ public void testGetCornerRadii_allOverridden() {
+ when(mResources.getDimensionPixelSize(R.dimen.config_rounded_mask_size)).thenReturn(100);
+ when(mResources.getDimensionPixelSize(R.dimen.config_rounded_mask_size_top)).thenReturn(
+ 150);
+ when(mResources.getDimensionPixelSize(R.dimen.config_rounded_mask_size_bottom)).thenReturn(
+ 200);
+ when(mMockContext.getResources()).thenReturn(mResources);
+
+ assertEquals(200, DisplayUtils.getCornerRadiusBottom(mMockContext));
+ assertEquals(150, DisplayUtils.getCornerRadiusTop(mMockContext));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 11e5880..f210708 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -38,6 +38,7 @@
import static org.mockito.Mockito.when;
import android.graphics.Rect;
+import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.SensorProperties;
@@ -688,7 +689,7 @@
}
@Test
- public void aodInterruptCancelTimeoutActionWhenFingerUp() throws RemoteException {
+ public void aodInterruptCancelTimeoutActionOnFingerUp() throws RemoteException {
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
@@ -740,6 +741,56 @@
}
@Test
+ public void aodInterruptCancelTimeoutActionOnAcquired() throws RemoteException {
+ when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+
+ // GIVEN AOD interrupt
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mScreenObserver.onScreenTurnedOn();
+ mFgExecutor.runAllReady();
+ mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
+ mFgExecutor.runAllReady();
+
+ // Configure UdfpsView to accept the acquired event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+
+ // WHEN acquired is received
+ mOverlayController.onAcquired(TEST_UDFPS_SENSOR_ID,
+ BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD);
+
+ // Configure UdfpsView to accept the ACTION_DOWN event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+
+ // WHEN ACTION_DOWN is received
+ verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+ MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+ mBiometricsExecutor.runAllReady();
+ downEvent.recycle();
+
+ // WHEN ACTION_MOVE is received
+ MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
+ mBiometricsExecutor.runAllReady();
+ moveEvent.recycle();
+ mFgExecutor.runAllReady();
+
+ // Configure UdfpsView to accept the finger up event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+
+ // WHEN it times out
+ mFgExecutor.advanceClockToNext();
+ mFgExecutor.runAllReady();
+
+ // THEN the display should be unconfigured once. If the timeout action is not
+ // cancelled, the display would be unconfigured twice which would cause two
+ // FP attempts.
+ verify(mUdfpsView, times(1)).unconfigureDisplay();
+ }
+
+ @Test
public void aodInterruptScreenOff() throws RemoteException {
// GIVEN screen off
mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
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 eec33ca..f370be1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -27,10 +28,10 @@
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
-import android.service.dreams.DreamService;
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;
@@ -53,6 +54,8 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -61,6 +64,7 @@
public class DreamOverlayServiceTest extends SysuiTestCase {
private static final ComponentName LOW_LIGHT_COMPONENT = new ComponentName("package",
"lowlight");
+ private static final String DREAM_COMPONENT = "package/dream";
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
@@ -108,12 +112,14 @@
@Mock
UiEventLogger mUiEventLogger;
+ @Captor
+ ArgumentCaptor<View> mViewCaptor;
+
DreamOverlayService mService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mContext.addMockSystemService(WindowManager.class, mWindowManager);
when(mDreamOverlayComponent.getDreamOverlayContainerViewController())
.thenReturn(mDreamOverlayContainerViewController);
@@ -129,7 +135,7 @@
when(mDreamOverlayContainerViewController.getContainerView())
.thenReturn(mDreamOverlayContainerView);
- mService = new DreamOverlayService(mContext, mMainExecutor,
+ mService = new DreamOverlayService(mContext, mMainExecutor, mWindowManager,
mDreamOverlayComponentFactory,
mStateController,
mKeyguardUpdateMonitor,
@@ -143,7 +149,8 @@
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback);
+ overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
verify(mUiEventLogger).log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
@@ -157,7 +164,8 @@
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback);
+ overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
verify(mWindowManager).addView(any(), any());
@@ -169,7 +177,8 @@
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback);
+ overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
verify(mDreamOverlayContainerViewController).init();
@@ -186,49 +195,76 @@
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback);
+ overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
verify(mDreamOverlayContainerViewParent).removeView(mDreamOverlayContainerView);
}
@Test
- public void testShouldShowComplicationsFalseByDefault() {
- mService.onBind(new Intent());
+ public void testShouldShowComplicationsSetByStartDream() throws RemoteException {
+ final IBinder proxy = mService.onBind(new Intent());
+ final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
- assertThat(mService.shouldShowComplications()).isFalse();
- }
-
- @Test
- public void testShouldShowComplicationsSetByIntentExtra() {
- final Intent intent = new Intent();
- intent.putExtra(DreamService.EXTRA_SHOW_COMPLICATIONS, true);
- mService.onBind(intent);
+ // Inform the overlay service of dream starting.
+ overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ true /*shouldShowComplication*/);
assertThat(mService.shouldShowComplications()).isTrue();
}
@Test
- public void testLowLightSetByIntentExtra() throws RemoteException {
- final Intent intent = new Intent();
- intent.putExtra(DreamService.EXTRA_DREAM_COMPONENT, LOW_LIGHT_COMPONENT);
-
- final IBinder proxy = mService.onBind(intent);
+ public void testLowLightSetByStartDream() throws RemoteException {
+ final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
- assertThat(mService.getDreamComponent()).isEqualTo(LOW_LIGHT_COMPONENT);
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback);
+ overlay.startDream(mWindowParams, mDreamOverlayCallback,
+ LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
+ assertThat(mService.getDreamComponent()).isEqualTo(LOW_LIGHT_COMPONENT);
verify(mStateController).setLowLightActive(true);
}
@Test
- public void testDestroy() {
+ public void testDestroy() throws RemoteException {
+ final IBinder proxy = mService.onBind(new Intent());
+ final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+ // Inform the overlay service of dream starting.
+ overlay.startDream(mWindowParams, mDreamOverlayCallback,
+ LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
+ mMainExecutor.runAllReady();
+
+ // Verify view added.
+ verify(mWindowManager).addView(mViewCaptor.capture(), any());
+
+ // Service destroyed.
mService.onDestroy();
mMainExecutor.runAllReady();
+ // Verify view removed.
+ verify(mWindowManager).removeView(mViewCaptor.getValue());
+
+ // Verify state correctly set.
+ verify(mKeyguardUpdateMonitor).removeCallback(any());
+ verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
+ verify(mStateController).setOverlayActive(false);
+ verify(mStateController).setLowLightActive(false);
+ }
+
+ @Test
+ public void testDoNotRemoveViewOnDestroyIfOverlayNotStarted() {
+ // Service destroyed without ever starting dream.
+ mService.onDestroy();
+ mMainExecutor.runAllReady();
+
+ // Verify no view is removed.
+ verify(mWindowManager, never()).removeView(any());
+
+ // Verify state still correctly set.
verify(mKeyguardUpdateMonitor).removeCallback(any());
verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
verify(mStateController).setOverlayActive(false);
@@ -245,7 +281,8 @@
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback);
+ overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ false /*shouldShowComplication*/);
// Destroy the service.
mService.onDestroy();
@@ -255,4 +292,44 @@
verify(mWindowManager, never()).addView(any(), any());
}
+
+ @Test
+ public void testResetCurrentOverlayWhenConnectedToNewDream() throws RemoteException {
+ final IBinder proxy = mService.onBind(new Intent());
+ final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+ // Inform the overlay service of dream starting. Do not show dream complications.
+ overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ false /*shouldShowComplication*/);
+ mMainExecutor.runAllReady();
+
+ // Verify that a new window is added.
+ verify(mWindowManager).addView(mViewCaptor.capture(), any());
+ final View windowDecorView = mViewCaptor.getValue();
+
+ // Assert that the overlay is not showing complications.
+ assertThat(mService.shouldShowComplications()).isFalse();
+
+ clearInvocations(mDreamOverlayComponent);
+ clearInvocations(mWindowManager);
+
+ // New dream starting with dream complications showing. Note that when a new dream is
+ // binding to the dream overlay service, it receives the same instance of IBinder as the
+ // first one.
+ overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ true /*shouldShowComplication*/);
+ mMainExecutor.runAllReady();
+
+ // Assert that the overlay is showing complications.
+ assertThat(mService.shouldShowComplications()).isTrue();
+
+ // Verify that the old overlay window has been removed, and a new one created.
+ verify(mWindowManager).removeView(windowDecorView);
+ verify(mWindowManager).addView(any(), any());
+
+ // Verify that new instances of overlay container view controller and overlay touch monitor
+ // are created.
+ verify(mDreamOverlayComponent).getDreamOverlayContainerViewController();
+ verify(mDreamOverlayComponent).getDreamOverlayTouchMonitor();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 21c018a..39f3c96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -176,7 +176,7 @@
// and the keyguard goes away
mViewMediator.setShowingLocked(false);
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
mViewMediator.mUpdateCallback.onKeyguardVisibilityChanged(false);
TestableLooper.get(this).processAllMessages();
@@ -201,7 +201,7 @@
// and the keyguard goes away
mViewMediator.setShowingLocked(false);
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
mViewMediator.mUpdateCallback.onKeyguardVisibilityChanged(false);
TestableLooper.get(this).processAllMessages();
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 2a13053..d828193 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
@@ -64,6 +64,7 @@
context,
FakeExecutor(FakeSystemClock()),
)
+ mediaTttCommandLineHelper.start()
}
@Test(expected = IllegalStateException::class)
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 f7b3091..9577274 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
@@ -110,6 +110,7 @@
receiverUiEventLogger,
viewUtil,
)
+ controllerReceiver.start()
val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
verify(commandQueue).addCallback(callbackCaptor.capture())
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 213b74a..3a8a51d 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
@@ -49,7 +49,6 @@
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
-import dagger.Lazy
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -84,12 +83,8 @@
@Mock
private lateinit var commandQueue: CommandQueue
@Mock
- private lateinit var lazyFalsingManager: Lazy<FalsingManager>
- @Mock
private lateinit var falsingManager: FalsingManager
@Mock
- private lateinit var lazyFalsingCollector: Lazy<FalsingCollector>
- @Mock
private lateinit var falsingCollector: FalsingCollector
@Mock
private lateinit var viewUtil: ViewUtil
@@ -119,8 +114,6 @@
senderUiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
- whenever(lazyFalsingManager.get()).thenReturn(falsingManager)
- whenever(lazyFalsingCollector.get()).thenReturn(falsingCollector)
controllerSender = TestMediaTttChipControllerSender(
commandQueue,
@@ -132,10 +125,11 @@
configurationController,
powerManager,
senderUiEventLogger,
- lazyFalsingManager,
- lazyFalsingCollector,
+ falsingManager,
+ falsingCollector,
viewUtil,
)
+ controllerSender.start()
val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
verify(commandQueue).addCallback(callbackCaptor.capture())
@@ -441,7 +435,7 @@
@Test
fun transferToReceiverSucceeded_withUndoRunnable_falseTap_callbackNotRun() {
- whenever(lazyFalsingManager.get().isFalseTap(anyInt())).thenReturn(true)
+ whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true)
var undoCallbackCalled = false
val undoCallback = object : IUndoMediaTransferCallback.Stub() {
override fun onUndoTriggered() {
@@ -457,7 +451,7 @@
@Test
fun transferToReceiverSucceeded_withUndoRunnable_realTap_callbackRun() {
- whenever(lazyFalsingManager.get().isFalseTap(anyInt())).thenReturn(false)
+ whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false)
var undoCallbackCalled = false
val undoCallback = object : IUndoMediaTransferCallback.Stub() {
override fun onUndoTriggered() {
@@ -839,8 +833,8 @@
configurationController: ConfigurationController,
powerManager: PowerManager,
uiEventLogger: MediaTttSenderUiEventLogger,
- falsingManager: Lazy<FalsingManager>,
- falsingCollector: Lazy<FalsingCollector>,
+ falsingManager: FalsingManager,
+ falsingCollector: FalsingCollector,
viewUtil: ViewUtil,
) : MediaTttChipControllerSender(
commandQueue,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index 8073103..6c03730 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -39,7 +39,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.keyguard.KeyguardViewController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -49,6 +48,7 @@
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Test;
@@ -113,7 +113,7 @@
mNavBarHelper = new NavBarHelper(mContext, mAccessibilityManager,
mAccessibilityButtonModeObserver, mAccessibilityButtonTargetObserver,
mSystemActions, mOverviewProxyService, mAssistManagerLazy,
- () -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardViewController.class),
+ () -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class),
mNavigationModeController, mUserTracker, mDumpManager);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index b0cf061..9bf27a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -19,6 +19,8 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -30,20 +32,25 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.content.res.Configuration;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.util.SparseArray;
import androidx.test.filters.SmallTest;
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.LightBarController;
@@ -72,11 +79,14 @@
private NavigationBarController mNavigationBarController;
private NavigationBar mDefaultNavBar;
private NavigationBar mSecondaryNavBar;
+ private StaticMockitoSession mMockitoSession;
@Mock
private CommandQueue mCommandQueue;
@Mock
private NavigationBarComponent.Factory mNavigationBarFactory;
+ @Mock
+ TaskbarDelegate mTaskbarDelegate;
@Before
public void setUp() {
@@ -90,7 +100,7 @@
Dependency.get(Dependency.MAIN_HANDLER),
mock(ConfigurationController.class),
mock(NavBarHelper.class),
- mock(TaskbarDelegate.class),
+ mTaskbarDelegate,
mNavigationBarFactory,
mock(StatusBarKeyguardViewManager.class),
mock(DumpManager.class),
@@ -100,6 +110,7 @@
Optional.of(mock(BackAnimation.class)),
mock(FeatureFlags.class)));
initializeNavigationBars();
+ mMockitoSession = mockitoSession().mockStatic(Utilities.class).startMocking();
}
private void initializeNavigationBars() {
@@ -120,6 +131,7 @@
mNavigationBarController = null;
mDefaultNavBar = null;
mSecondaryNavBar = null;
+ mMockitoSession.finishMocking();
}
@Test
@@ -268,4 +280,22 @@
public void test3ButtonTaskbarFlagDisabledNoRegister() {
verify(mCommandQueue, never()).addCallback(any(TaskbarDelegate.class));
}
+
+
+ @Test
+ public void testConfigurationChange_taskbarNotInitialized() {
+ Configuration configuration = mContext.getResources().getConfiguration();
+ when(Utilities.isTablet(any())).thenReturn(true);
+ mNavigationBarController.onConfigChanged(configuration);
+ verify(mTaskbarDelegate, never()).onConfigurationChanged(configuration);
+ }
+
+ @Test
+ public void testConfigurationChange_taskbarInitialized() {
+ Configuration configuration = mContext.getResources().getConfiguration();
+ when(Utilities.isTablet(any())).thenReturn(true);
+ when(mTaskbarDelegate.isInitialized()).thenReturn(true);
+ mNavigationBarController.onConfigChanged(configuration);
+ verify(mTaskbarDelegate, times(1)).onConfigurationChanged(configuration);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 51f0953..0e9d279 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -72,7 +72,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
-import com.android.keyguard.KeyguardViewController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
@@ -194,7 +193,7 @@
@Mock
private CentralSurfaces mCentralSurfaces;
@Mock
- private KeyguardViewController mKeyguardViewController;
+ private KeyguardStateController mKeyguardStateController;
@Mock
private UserContextProvider mUserContextProvider;
@Mock
@@ -240,7 +239,7 @@
mock(AccessibilityButtonTargetsObserver.class),
mSystemActions, mOverviewProxyService,
() -> mock(AssistManager.class), () -> Optional.of(mCentralSurfaces),
- mKeyguardViewController, mock(NavigationModeController.class),
+ mKeyguardStateController, mock(NavigationModeController.class),
mock(UserTracker.class), mock(DumpManager.class)));
mNavigationBar = createNavBar(mContext);
mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
@@ -380,7 +379,7 @@
// Verify navbar didn't alter and showing back icon when the keyguard is showing without
// requesting IME insets visible.
- doReturn(true).when(mKeyguardViewController).isShowing();
+ doReturn(true).when(mKeyguardStateController).isShowing();
mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE,
BACK_DISPOSITION_DEFAULT, true);
assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
index b719c7f..a6381d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
@@ -32,7 +32,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Assert;
@@ -58,8 +57,6 @@
mDynamicPrivacyController = new DynamicPrivacyController(
mLockScreenUserManager, mKeyguardStateController,
mock(StatusBarStateController.class));
- mDynamicPrivacyController.setStatusBarKeyguardViewManager(
- mock(StatusBarKeyguardViewManager.class));
mDynamicPrivacyController.addListener(mListener);
// Disable dynamic privacy by default
allowNotificationsInPublic(false);
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 365e608..4dea6be 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
@@ -95,8 +95,6 @@
@Mock
private AuthController mAuthController;
@Mock
- private DozeParameters mDozeParameters;
- @Mock
private MetricsLogger mMetricsLogger;
@Mock
private NotificationMediaManager mNotificationMediaManager;
@@ -120,7 +118,7 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
TestableResources res = getContext().getOrCreateTestableResources();
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
when(mKeyguardStateController.isUnlocked()).thenReturn(false);
@@ -132,7 +130,7 @@
mBiometricUnlockController = new BiometricUnlockController(mDozeScrimController,
mKeyguardViewMediator, mScrimController,
mNotificationShadeWindowController, mKeyguardStateController, mHandler,
- mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters,
+ mUpdateMonitor, res.getResources(), mKeyguardBypassController,
mMetricsLogger, mDumpManager, mPowerManager,
mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
mAuthController, mStatusBarStateController,
@@ -170,7 +168,7 @@
public void onBiometricAuthenticated_whenFingerprintAndNotInteractive_wakeAndUnlock() {
reset(mUpdateMonitor);
reset(mStatusBarKeyguardViewManager);
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
when(mDozeScrimController.isPulsing()).thenReturn(true);
// the value of isStrongBiometric doesn't matter here since we only care about the returned
@@ -187,7 +185,7 @@
public void onBiometricAuthenticated_whenDeviceIsAlreadyUnlocked_wakeAndUnlock() {
reset(mUpdateMonitor);
reset(mStatusBarKeyguardViewManager);
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
when(mKeyguardStateController.isUnlocked()).thenReturn(true);
when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
when(mDozeScrimController.isPulsing()).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 05f8760..ad497a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -516,32 +516,32 @@
@Test
public void executeRunnableDismissingKeyguard_nullRunnable_showingAndOccluded() {
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
mCentralSurfaces.executeRunnableDismissingKeyguard(null, null, false, false, false);
}
@Test
public void executeRunnableDismissingKeyguard_nullRunnable_showing() {
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
mCentralSurfaces.executeRunnableDismissingKeyguard(null, null, false, false, false);
}
@Test
public void executeRunnableDismissingKeyguard_nullRunnable_notShowing() {
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
mCentralSurfaces.executeRunnableDismissingKeyguard(null, null, false, false, false);
}
@Test
public void executeRunnableDismissingKeyguard_dreaming_notShowing() throws RemoteException {
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
when(mKeyguardUpdateMonitor.isDreaming()).thenReturn(true);
mCentralSurfaces.executeRunnableDismissingKeyguard(() -> {},
@@ -555,8 +555,8 @@
@Test
public void executeRunnableDismissingKeyguard_notDreaming_notShowing() throws RemoteException {
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
when(mKeyguardUpdateMonitor.isDreaming()).thenReturn(false);
mCentralSurfaces.executeRunnableDismissingKeyguard(() -> {},
@@ -571,10 +571,10 @@
@Test
public void lockscreenStateMetrics_notShowing() {
// uninteresting state, except that fingerprint must be non-zero
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
// interesting state
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
when(mKeyguardStateController.isMethodSecure()).thenReturn(false);
mCentralSurfaces.onKeyguardViewManagerStatesUpdated();
@@ -589,10 +589,10 @@
@Test
public void lockscreenStateMetrics_notShowing_secure() {
// uninteresting state, except that fingerprint must be non-zero
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
// interesting state
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
@@ -608,10 +608,10 @@
@Test
public void lockscreenStateMetrics_isShowing() {
// uninteresting state, except that fingerprint must be non-zero
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
// interesting state
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
when(mKeyguardStateController.isMethodSecure()).thenReturn(false);
@@ -627,10 +627,10 @@
@Test
public void lockscreenStateMetrics_isShowing_secure() {
// uninteresting state, except that fingerprint must be non-zero
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
// interesting state
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
@@ -646,10 +646,10 @@
@Test
public void lockscreenStateMetrics_isShowingBouncer() {
// uninteresting state, except that fingerprint must be non-zero
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
// interesting state
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
@@ -1053,9 +1053,9 @@
}
@Test
- public void startActivityDismissingKeyguard_isShowingandIsOccluded() {
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(true);
+ public void startActivityDismissingKeyguard_isShowingAndIsOccluded() {
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
mCentralSurfaces.startActivityDismissingKeyguard(
new Intent(),
/* onlyProvisioned = */false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
new file mode 100644
index 0000000..a986777
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+/**
+ * Mock implementation of KeyguardStateController which tracks showing and occluded states
+ * based on {@link #notifyKeyguardState(boolean showing, boolean occluded)}}.
+ */
+public class FakeKeyguardStateController implements KeyguardStateController {
+ private boolean mShowing;
+ private boolean mOccluded;
+ private boolean mCanDismissLockScreen;
+
+ @Override
+ public void notifyKeyguardState(boolean showing, boolean occluded) {
+ mShowing = showing;
+ mOccluded = occluded;
+ }
+
+ @Override
+ public boolean isShowing() {
+ return mShowing;
+ }
+
+ @Override
+ public boolean isOccluded() {
+ return mOccluded;
+ }
+
+ public void setCanDismissLockScreen(boolean canDismissLockScreen) {
+ mCanDismissLockScreen = canDismissLockScreen;
+ }
+
+ @Override
+ public boolean canDismissLockScreen() {
+ return mCanDismissLockScreen;
+ }
+
+ @Override
+ public boolean isBouncerShowing() {
+ return false;
+ }
+
+ @Override
+ public boolean isKeyguardScreenRotationAllowed() {
+ return false;
+ }
+
+ @Override
+ public boolean isMethodSecure() {
+ return true;
+ }
+
+ @Override
+ public boolean isTrusted() {
+ return false;
+ }
+
+ @Override
+ public boolean isKeyguardGoingAway() {
+ return false;
+ }
+
+ @Override
+ public boolean isKeyguardFadingAway() {
+ return false;
+ }
+
+ @Override
+ public boolean isLaunchTransitionFadingAway() {
+ return false;
+ }
+
+ @Override
+ public long getKeyguardFadingAwayDuration() {
+ return 0;
+ }
+
+ @Override
+ public long getKeyguardFadingAwayDelay() {
+ return 0;
+ }
+
+ @Override
+ public long calculateGoingToFullShadeDelay() {
+ return 0;
+ }
+
+ @Override
+ public float getDismissAmount() {
+ return 0f;
+ }
+
+ @Override
+ public boolean isDismissingFromSwipe() {
+ return false;
+ }
+
+ @Override
+ public boolean isFlingingToDismissKeyguard() {
+ return false;
+ }
+
+ @Override
+ public boolean isFlingingToDismissKeyguardDuringSwipeGesture() {
+ return false;
+ }
+
+ @Override
+ public boolean isSnappingKeyguardBackAfterSwipe() {
+ return false;
+ }
+
+ @Override
+ public void notifyPanelFlingStart(boolean dismiss) {
+ }
+
+ @Override
+ public void notifyPanelFlingEnd() {
+ }
+
+ @Override
+ public void addCallback(Callback listener) {
+ }
+
+ @Override
+ public void removeCallback(Callback listener) {
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index ecbb7e5b..8da8d04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -28,6 +28,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -68,7 +69,6 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.google.common.truth.Truth;
@@ -94,7 +94,6 @@
@Mock private ViewMediatorCallback mViewMediatorCallback;
@Mock private LockPatternUtils mLockPatternUtils;
- @Mock private KeyguardStateController mKeyguardStateController;
@Mock private CentralSurfaces mCentralSurfaces;
@Mock private ViewGroup mContainer;
@Mock private NotificationPanelViewController mNotificationPanelView;
@@ -123,6 +122,8 @@
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback;
+ private FakeKeyguardStateController mKeyguardStateController =
+ spy(new FakeKeyguardStateController());
@Mock private ViewRootImpl mViewRootImpl;
@Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
@@ -180,7 +181,6 @@
mBiometricUnlockController,
mNotificationContainer,
mBypassController);
- when(mKeyguardStateController.isOccluded()).thenReturn(false);
mStatusBarKeyguardViewManager.show(null);
ArgumentCaptor<KeyguardBouncer.BouncerExpansionCallback> callbackArgumentCaptor =
ArgumentCaptor.forClass(KeyguardBouncer.BouncerExpansionCallback.class);
@@ -253,7 +253,7 @@
@Test
public void onPanelExpansionChanged_showsBouncerWhenSwiping() {
- when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
+ mKeyguardStateController.setCanDismissLockScreen(false);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
verify(mBouncer).show(eq(false), eq(false));
@@ -340,13 +340,12 @@
}
@Test
- public void setOccluded_onKeyguardOccludedChangedCalledCorrectly() {
+ public void setOccluded_onKeyguardOccludedChangedCalled() {
clearInvocations(mKeyguardStateController);
clearInvocations(mKeyguardUpdateMonitor);
- // Should be false to start, so no invocations
mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */);
- verify(mKeyguardStateController, never()).notifyKeyguardState(anyBoolean(), anyBoolean());
+ verify(mKeyguardStateController).notifyKeyguardState(true, false);
clearInvocations(mKeyguardUpdateMonitor);
clearInvocations(mKeyguardStateController);
@@ -357,8 +356,8 @@
clearInvocations(mKeyguardUpdateMonitor);
clearInvocations(mKeyguardStateController);
- mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
- verify(mKeyguardStateController, never()).notifyKeyguardState(anyBoolean(), anyBoolean());
+ mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */);
+ verify(mKeyguardStateController).notifyKeyguardState(true, false);
}
@Test
@@ -426,7 +425,7 @@
when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true);
assertTrue(
"Is showing not accurate when alternative auth showing",
- mStatusBarKeyguardViewManager.isShowing());
+ mStatusBarKeyguardViewManager.isBouncerShowing());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index 929e529..a3ad028 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -23,9 +23,9 @@
import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
-import com.android.systemui.statusbar.connectivity.WifiIcons
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
+import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
@@ -144,7 +144,12 @@
/** A function that, given a context, calculates the correct content description string. */
val contentDescription: (Context) -> String,
- )
+
+ /** A human-readable description used for the test names. */
+ val description: String,
+ ) {
+ override fun toString() = description
+ }
// Note: We use default values for the boolean parameters to reflect a "typical configuration"
// for wifi. This allows each TestCase to only define the parameter values that are critical
@@ -158,12 +163,21 @@
/** The expected output. Null if we expect the output to be null. */
val expected: Expected?
- )
+ ) {
+ override fun toString(): String {
+ return "when INPUT(enabled=$enabled, " +
+ "forceHidden=$forceHidden, " +
+ "showWhenEnabled=$alwaysShowIconWhenEnabled, " +
+ "hasDataCaps=$hasDataCapabilities, " +
+ "network=$network) then " +
+ "EXPECTED($expected)"
+ }
+ }
companion object {
- @Parameters(name = "{0}")
- @JvmStatic
- fun data(): Collection<TestCase> =
+ @Parameters(name = "{0}") @JvmStatic fun data(): Collection<TestCase> = testData
+
+ private val testData: List<TestCase> =
listOf(
// Enabled = false => no networks shown
TestCase(
@@ -215,11 +229,12 @@
network = WifiNetworkModel.Inactive,
expected =
Expected(
- iconResource = WifiIcons.WIFI_NO_NETWORK,
+ iconResource = WIFI_NO_NETWORK,
contentDescription = { context ->
"${context.getString(WIFI_NO_CONNECTION)}," +
context.getString(NO_INTERNET)
- }
+ },
+ description = "No network icon",
),
),
TestCase(
@@ -231,7 +246,8 @@
contentDescription = { context ->
"${context.getString(WIFI_CONNECTION_STRENGTH[4])}," +
context.getString(NO_INTERNET)
- }
+ },
+ description = "No internet level 4 icon",
),
),
TestCase(
@@ -242,7 +258,8 @@
iconResource = WIFI_FULL_ICONS[2],
contentDescription = { context ->
context.getString(WIFI_CONNECTION_STRENGTH[2])
- }
+ },
+ description = "Full internet level 2 icon",
),
),
@@ -252,11 +269,12 @@
network = WifiNetworkModel.Inactive,
expected =
Expected(
- iconResource = WifiIcons.WIFI_NO_NETWORK,
+ iconResource = WIFI_NO_NETWORK,
contentDescription = { context ->
"${context.getString(WIFI_NO_CONNECTION)}," +
context.getString(NO_INTERNET)
- }
+ },
+ description = "No network icon",
),
),
TestCase(
@@ -268,7 +286,8 @@
contentDescription = { context ->
"${context.getString(WIFI_CONNECTION_STRENGTH[2])}," +
context.getString(NO_INTERNET)
- }
+ },
+ description = "No internet level 2 icon",
),
),
TestCase(
@@ -279,7 +298,8 @@
iconResource = WIFI_FULL_ICONS[0],
contentDescription = { context ->
context.getString(WIFI_CONNECTION_STRENGTH[0])
- }
+ },
+ description = "Full internet level 0 icon",
),
),
@@ -309,7 +329,8 @@
iconResource = WIFI_FULL_ICONS[4],
contentDescription = { context ->
context.getString(WIFI_CONNECTION_STRENGTH[4])
- }
+ },
+ description = "Full internet level 4 icon",
),
),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index e1c1b46..c4abedd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -260,6 +260,9 @@
var mostRecentViewInfo: ViewInfo? = null
override val windowLayoutParams = commonWindowLayoutParams
+
+ override fun start() {}
+
override fun updateView(newInfo: ViewInfo, currentView: ViewGroup) {
mostRecentViewInfo = newInfo
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
index 15ba672..4ca1fd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
@@ -26,6 +26,7 @@
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.junit.Assert.assertTrue
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,6 +36,7 @@
private val serializer = IpcSerializer()
+ @Ignore("b/253046405")
@Test
fun serializeManyIncomingIpcs(): Unit = runBlocking(Dispatchers.Main.immediate) {
val processor = launch(start = CoroutineStart.LAZY) { serializer.process() }
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index 0ab5a8a..80d9d97 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -270,7 +270,8 @@
final long callingIdentity = Binder.clearCallingIdentity();
try {
association = mService.createAssociation(userId, packageName, macAddress,
- request.getDisplayName(), request.getDeviceProfile(), request.isSelfManaged());
+ request.getDisplayName(), request.getDeviceProfile(),
+ request.getAssociatedDevice(), request.isSelfManaged());
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 0426c79..f2cb602 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -55,6 +55,7 @@
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.companion.AssociatedDevice;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.DeviceNotAssociatedException;
@@ -872,23 +873,25 @@
/**
* @deprecated use
- * {@link #createAssociation(int, String, MacAddress, CharSequence, String, boolean)}
+ * {@link #createAssociation(int, String, MacAddress, CharSequence, String, AssociatedDevice,
+ * boolean)}
*/
@Deprecated
void legacyCreateAssociation(@UserIdInt int userId, @NonNull String deviceMacAddress,
@NonNull String packageName, @Nullable String deviceProfile) {
final MacAddress macAddress = MacAddress.fromString(deviceMacAddress);
- createAssociation(userId, packageName, macAddress, null, deviceProfile, false);
+ createAssociation(userId, packageName, macAddress, null, deviceProfile, null, false);
}
AssociationInfo createAssociation(@UserIdInt int userId, @NonNull String packageName,
@Nullable MacAddress macAddress, @Nullable CharSequence displayName,
- @Nullable String deviceProfile, boolean selfManaged) {
+ @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
+ boolean selfManaged) {
final int id = getNewAssociationIdForPackage(userId, packageName);
final long timestamp = System.currentTimeMillis();
final AssociationInfo association = new AssociationInfo(id, userId, packageName,
- macAddress, displayName, deviceProfile, selfManaged,
+ macAddress, displayName, deviceProfile, associatedDevice, selfManaged,
/* notifyOnDeviceNearby */ false, /* revoked */ false, timestamp, Long.MAX_VALUE);
Slog.i(TAG, "New CDM association created=" + association);
mAssociationStore.addAssociation(association);
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index 4b56c1b..c4f5766 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -229,6 +229,11 @@
/**
* Reads previously persisted data for the given user "into" the provided containers.
*
+ * Note that {@link AssociationInfo#getAssociatedDevice()} will always be {@code null} after
+ * retrieval from this datastore because it is not persisted (by design). This means that
+ * persisted data is not guaranteed to be identical to the initial data that was stored at the
+ * time of association.
+ *
* @param userId Android UserID
* @param associationsOut a container to read the {@link AssociationInfo}s "into".
* @param previouslyUsedIdsPerPackageOut a container to read the used IDs "into".
@@ -293,6 +298,9 @@
/**
* Persisted data to the disk.
*
+ * Note that associatedDevice field in {@link AssociationInfo} is not persisted by this
+ * datastore implementation.
+ *
* @param userId Android UserID
* @param associations a set of user's associations.
* @param previouslyUsedIdsPerPackage a set previously used Association IDs for the user.
@@ -419,7 +427,7 @@
final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
out.add(new AssociationInfo(associationId, userId, appPackage,
- MacAddress.fromString(deviceAddress), null, profile,
+ MacAddress.fromString(deviceAddress), null, profile, null,
/* managedByCompanionApp */ false, notify, /* revoked */ false, timeApproved,
Long.MAX_VALUE));
}
@@ -556,9 +564,11 @@
boolean notify, boolean revoked, long timeApproved, long lastTimeConnected) {
AssociationInfo associationInfo = null;
try {
+ // We do not persist AssociatedDevice, which means that AssociationInfo retrieved from
+ // datastore is not guaranteed to be identical to the one from initial association.
associationInfo = new AssociationInfo(associationId, userId, appPackage, macAddress,
- displayName, profile, selfManaged, notify, revoked, timeApproved,
- lastTimeConnected);
+ displayName, profile, null, selfManaged, notify, revoked,
+ timeApproved, lastTimeConnected);
} catch (Exception e) {
if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e);
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 4018be1..0f101b0 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -42,6 +42,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.ActivityThread;
+import android.app.assist.ActivityId;
import android.content.ComponentName;
import android.content.ContentCaptureOptions;
import android.content.ContentResolver;
@@ -916,13 +917,16 @@
return mGlobalContentCaptureOptions.getOptions(userId, packageName);
}
+ // ErrorProne says ContentCaptureManagerService.this.mLock needs to be guarded by
+ // 'service.mLock', which is the same as mLock.
+ @SuppressWarnings("GuardedBy")
@Override
public void notifyActivityEvent(int userId, @NonNull ComponentName activityComponent,
- @ActivityEventType int eventType) {
+ @ActivityEventType int eventType, @NonNull ActivityId activityId) {
synchronized (mLock) {
final ContentCapturePerUserService service = peekServiceForUserLocked(userId);
if (service != null) {
- service.onActivityEventLocked(activityComponent, eventType);
+ service.onActivityEventLocked(activityId, activityComponent, eventType);
}
}
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index a08687f..cee78e0 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -548,12 +548,13 @@
}
@GuardedBy("mLock")
- void onActivityEventLocked(@NonNull ComponentName componentName, @ActivityEventType int type) {
+ void onActivityEventLocked(@NonNull ActivityId activityId,
+ @NonNull ComponentName componentName, @ActivityEventType int type) {
if (mRemoteService == null) {
if (mMaster.debug) Slog.d(mTag, "onActivityEvent(): no remote service");
return;
}
- final ActivityEvent event = new ActivityEvent(componentName, type);
+ final ActivityEvent event = new ActivityEvent(activityId, componentName, type);
if (mMaster.verbose) Slog.v(mTag, "onActivityEvent(): " + event);
diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java
index 285c406..41044bf 100644
--- a/services/core/java/android/os/BatteryStatsInternal.java
+++ b/services/core/java/android/os/BatteryStatsInternal.java
@@ -16,12 +16,18 @@
package android.os;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
import com.android.internal.os.BinderCallsStats;
import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Collection;
import java.util.List;
+
/**
* Battery stats local system service interface. This is used to pass internal data out of
* BatteryStatsImpl, as well as make unchecked calls into BatteryStatsImpl.
@@ -29,6 +35,19 @@
* @hide Only for use within Android OS.
*/
public abstract class BatteryStatsInternal {
+
+ public static final int CPU_WAKEUP_SUBSYSTEM_UNKNOWN = -1;
+ public static final int CPU_WAKEUP_SUBSYSTEM_ALARM = 1;
+
+ /** @hide */
+ @IntDef(prefix = {"CPU_WAKEUP_SUBSYSTEM_"}, value = {
+ CPU_WAKEUP_SUBSYSTEM_UNKNOWN,
+ CPU_WAKEUP_SUBSYSTEM_ALARM,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface CpuWakeupSubsystem {
+ }
+
/**
* Returns the wifi interfaces.
*/
@@ -56,9 +75,10 @@
/**
* Inform battery stats how many deferred jobs existed when the app got launched and how
* long ago was the last job execution for the app.
- * @param uid the uid of the app.
+ *
+ * @param uid the uid of the app.
* @param numDeferred number of deferred jobs.
- * @param sinceLast how long in millis has it been since a job was run
+ * @param sinceLast how long in millis has it been since a job was run
*/
public abstract void noteJobsDeferred(int uid, int numDeferred, long sinceLast);
@@ -72,4 +92,11 @@
* Informs battery stats of native thread IDs of threads taking incoming binder calls.
*/
public abstract void noteBinderThreadNativeIds(int[] binderThreadNativeTids);
+
+ /**
+ * Reports any activity that could potentially have caused the CPU to wake up.
+ * Accepts a timestamp to allow the reporter to report it before or after the event.
+ */
+ public abstract void noteCpuWakingActivity(@CpuWakeupSubsystem int subsystem,
+ long elapsedMillis, @NonNull int... uids);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b34fe69..c837051 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -204,6 +204,7 @@
import android.app.ProfilerInfo;
import android.app.SyncNotedAppOp;
import android.app.WaitResult;
+import android.app.assist.ActivityId;
import android.app.backup.BackupManager.OperationType;
import android.app.backup.IBackupManager;
import android.app.compat.CompatChanges;
@@ -2915,7 +2916,7 @@
* @param taskRoot Task's root
*/
public void updateActivityUsageStats(ComponentName activity, int userId, int event,
- IBinder appToken, ComponentName taskRoot) {
+ IBinder appToken, ComponentName taskRoot, ActivityId activityId) {
if (DEBUG_SWITCH) {
Slog.d(TAG_SWITCH, "updateActivityUsageStats: comp="
+ activity + " hash=" + appToken.hashCode() + " event=" + event);
@@ -2932,7 +2933,7 @@
if (contentCaptureService != null && (event == Event.ACTIVITY_PAUSED
|| event == Event.ACTIVITY_RESUMED || event == Event.ACTIVITY_STOPPED
|| event == Event.ACTIVITY_DESTROYED)) {
- contentCaptureService.notifyActivityEvent(userId, activity, event);
+ contentCaptureService.notifyActivityEvent(userId, activity, event, activityId);
}
// Currently we have move most of logic to the client side. When the activity lifecycle
// event changed, the client side will notify the VoiceInteractionManagerService. But
@@ -5285,7 +5286,7 @@
// Tell anyone interested that we are done booting!
SystemProperties.set("sys.boot_completed", "1");
SystemProperties.set("dev.bootcomplete", "1");
- mUserController.sendBootCompleted(
+ mUserController.onBootComplete(
new IIntentReceiver.Stub() {
@Override
public void performReceive(Intent intent, int resultCode,
@@ -17106,9 +17107,9 @@
@Override
public void updateActivityUsageStats(ComponentName activity, int userId, int event,
- IBinder appToken, ComponentName taskRoot) {
+ IBinder appToken, ComponentName taskRoot, ActivityId activityId) {
ActivityManagerService.this.updateActivityUsageStats(activity, userId, event,
- appToken, taskRoot);
+ appToken, taskRoot, activityId);
}
@Override
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index d9d29d65..75d1f68 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -81,9 +81,11 @@
import android.telephony.ModemActivityInfo;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.StatsEvent;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IBatteryStats;
import com.android.internal.os.BackgroundThread;
@@ -104,6 +106,7 @@
import com.android.server.power.stats.BatteryStatsImpl;
import com.android.server.power.stats.BatteryUsageStatsProvider;
import com.android.server.power.stats.BatteryUsageStatsStore;
+import com.android.server.power.stats.CpuWakeupStats;
import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import java.io.File;
@@ -120,6 +123,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@@ -142,6 +146,8 @@
private final PowerProfile mPowerProfile;
final BatteryStatsImpl mStats;
+ @GuardedBy("mWakeupStats")
+ final CpuWakeupStats mCpuWakeupStats;
private final BatteryUsageStatsStore mBatteryUsageStatsStore;
private final BatteryStatsImpl.UserInfoProvider mUserManagerUserInfoProvider;
private final Context mContext;
@@ -373,6 +379,7 @@
}
mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats,
mBatteryUsageStatsStore);
+ mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map);
}
public void publish() {
@@ -464,6 +471,12 @@
mStats.noteBinderThreadNativeIds(binderThreadNativeTids);
}
}
+
+ @Override
+ public void noteCpuWakingActivity(int subsystem, long elapsedMillis, int... uids) {
+ Objects.requireNonNull(uids);
+ mCpuWakeupStats.noteWakingActivity(subsystem, elapsedMillis, uids);
+ }
}
@Override
@@ -2251,12 +2264,13 @@
try {
String reason;
while ((reason = waitWakeup()) != null) {
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ final long nowUptime = SystemClock.uptimeMillis();
// Wait for the completion of pending works if there is any
awaitCompletion();
-
+ mCpuWakeupStats.noteWakeupTimeAndReason(nowElapsed, nowUptime, reason);
synchronized (mStats) {
- mStats.noteWakeupReasonLocked(reason,
- SystemClock.elapsedRealtime(), SystemClock.uptimeMillis());
+ mStats.noteWakeupReasonLocked(reason, nowElapsed, nowUptime);
}
}
} catch (RuntimeException e) {
@@ -2312,6 +2326,7 @@
pw.println(" --read-daily: read-load last written daily stats.");
pw.println(" --settings: dump the settings key/values related to batterystats");
pw.println(" --cpu: dump cpu stats for debugging purpose");
+ pw.println(" --wakeups: dump CPU wakeup history and attribution.");
pw.println(" --power-profile: dump the power profile constants");
pw.println(" --usage: write battery usage stats. Optional arguments:");
pw.println(" --proto: output as a binary protobuffer");
@@ -2567,6 +2582,10 @@
}
dumpUsageStatsToProto(fd, pw, model, proto);
return;
+ } else if ("--wakeups".equals(arg)) {
+ mCpuWakeupStats.dump(new IndentingPrintWriter(pw, " "),
+ SystemClock.elapsedRealtime());
+ return;
} else if ("-a".equals(arg)) {
flags |= BatteryStats.DUMP_VERBOSE;
} else if (arg.length() > 0 && arg.charAt(0) == '-'){
@@ -2697,12 +2716,16 @@
} else {
if (DBG) Slog.d(TAG, "begin dumpLocked from UID " + Binder.getCallingUid());
awaitCompletion();
+
synchronized (mStats) {
mStats.dumpLocked(mContext, pw, flags, reqUid, historyStart);
if (writeData) {
mStats.writeAsyncLocked();
}
}
+ pw.println();
+ mCpuWakeupStats.dump(new IndentingPrintWriter(pw, " "), SystemClock.elapsedRealtime());
+
if (DBG) Slog.d(TAG, "end dumpLocked");
}
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 226c638..216a48e 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -439,11 +439,6 @@
mUserLru.add(UserHandle.USER_SYSTEM);
mLockPatternUtils = mInjector.getLockPatternUtils();
updateStartedUserArrayLU();
-
- // TODO(b/232452368): currently mAllowUserUnlocking is only used on devices with HSUM
- // (Headless System User Mode), but on master it will be used by all devices (and hence this
- // initial assignment should be removed).
- mAllowUserUnlocking = !UserManager.isHeadlessSystemUserMode();
}
void setInitialConfig(boolean userSwitchUiEnabled, int maxRunningUsers,
@@ -602,8 +597,11 @@
if (!mInjector.getUserManager().isPreCreated(userId)) {
mHandler.sendMessage(mHandler.obtainMessage(REPORT_LOCKED_BOOT_COMPLETE_MSG,
userId, 0));
- // In case of headless system user mode, do not send boot complete broadcast for
- // system user as it is sent by sendBootCompleted call.
+ // The "locked boot complete" broadcast for the system user is supposed be sent when
+ // the device has finished booting. Normally, that is the same time that the system
+ // user transitions to RUNNING_LOCKED. However, in "headless system user mode", the
+ // system user is explicitly started before the device has finished booting. In
+ // that case, we need to wait until onBootComplete() to send the broadcast.
if (!(UserManager.isHeadlessSystemUserMode() && uss.mHandle.isSystem())) {
// ACTION_LOCKED_BOOT_COMPLETED
sendLockedBootCompletedBroadcast(resultTo, userId);
@@ -1808,15 +1806,13 @@
*/
private boolean maybeUnlockUser(@UserIdInt int userId, @Nullable IProgressListener listener) {
- // Delay user unlocking for headless system user mode until the system boot
- // completes. When the system boot completes, the {@link #onBootCompleted()}
- // method unlocks all started users for headless system user mode. This is done
- // to prevent unlocking the users too early during the system boot up.
- // Otherwise, emulated volumes are mounted too early during the system
- // boot up. When vold is reset on boot complete, vold kills all apps/services
- // (that use these emulated volumes) before unmounting the volumes(b/241929666).
- // In the past, these killings have caused the system to become too unstable on
- // some occasions.
+ // We cannot allow users to be unlocked before PHASE_BOOT_COMPLETED, for two reasons.
+ // First, emulated volumes aren't supposed to be used until then; StorageManagerService
+ // assumes it can reset everything upon reaching PHASE_BOOT_COMPLETED. Second, on some
+ // devices the Weaver HAL needed to unlock the user's storage isn't available until sometime
+ // shortly before PHASE_BOOT_COMPLETED. The below logic enforces a consistent flow across
+ // all devices, regardless of their Weaver implementation.
+ //
// Any unlocks that get delayed by this will be done by onBootComplete() instead.
if (!mAllowUserUnlocking) {
Slogf.i(TAG, "Not unlocking user %d yet because boot hasn't completed", userId);
@@ -2424,11 +2420,8 @@
}
}
- /**
- * @deprecated TODO(b/232452368): this logic will be merged into sendBootCompleted
- */
- @Deprecated
- private void onBootCompletedOnHeadlessSystemUserModeDevices() {
+ void onBootComplete(IIntentReceiver resultTo) {
+ // Now that PHASE_BOOT_COMPLETED has been reached, user unlocking is allowed.
setAllowUserUnlocking(true);
// Get a copy of mStartedUsers to use outside of lock.
@@ -2436,37 +2429,30 @@
synchronized (mLock) {
startedUsers = mStartedUsers.clone();
}
+ // In non-headless system user mode, call finishUserBoot() to transition the system user
+ // from the BOOTING state to RUNNING_LOCKED, then to RUNNING_UNLOCKED if possible.
+ //
+ // In headless system user mode, additional users may have been started, and all users
+ // (including the system user) that ever get started are started explicitly. In this case,
+ // we should *not* transition users out of the BOOTING state using finishUserBoot(), as that
+ // doesn't handle issuing the needed onUserStarting() call, and it would just race with an
+ // explicit start anyway. We do, however, need to send the "locked boot complete" broadcast
+ // for the system user, as that got skipped earlier due to the *device* boot not being
+ // complete yet. We also need to try to unlock all started users, since until now explicit
+ // user starts didn't proceed to unlocking, due to it being too early in the device boot.
+ //
// USER_SYSTEM must be processed first. It will be first in the array, as its ID is lowest.
Preconditions.checkArgument(startedUsers.keyAt(0) == UserHandle.USER_SYSTEM);
for (int i = 0; i < startedUsers.size(); i++) {
- UserState uss = startedUsers.valueAt(i);
- int userId = uss.mHandle.getIdentifier();
- Slogf.i(TAG, "Attempting to unlock user %d on boot complete", userId);
- maybeUnlockUser(userId);
- }
- }
-
- void sendBootCompleted(IIntentReceiver resultTo) {
- if (UserManager.isHeadlessSystemUserMode()) {
- // Unlocking users is delayed until boot complete for headless system user mode.
- onBootCompletedOnHeadlessSystemUserModeDevices();
- }
-
- // Get a copy of mStartedUsers to use outside of lock
- SparseArray<UserState> startedUsers;
- synchronized (mLock) {
- startedUsers = mStartedUsers.clone();
- }
- for (int i = 0; i < startedUsers.size(); i++) {
+ int userId = startedUsers.keyAt(i);
UserState uss = startedUsers.valueAt(i);
if (!UserManager.isHeadlessSystemUserMode()) {
finishUserBoot(uss, resultTo);
- } else if (uss.mHandle.isSystem()) {
- // In case of headless system user mode, send only locked boot complete broadcast
- // for system user since finishUserBoot call will be made using other code path;
- // for non-system user, do nothing since finishUserBoot will be called elsewhere.
- sendLockedBootCompletedBroadcast(resultTo, uss.mHandle.getIdentifier());
- return;
+ } else {
+ if (userId == UserHandle.USER_SYSTEM) {
+ sendLockedBootCompletedBroadcast(resultTo, userId);
+ }
+ maybeUnlockUser(userId);
}
}
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 6a53978..6795b6b 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -21,6 +21,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.RouteInfo.RTN_THROW;
import static android.net.RouteInfo.RTN_UNREACHABLE;
import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
@@ -51,6 +52,7 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
+import android.net.ConnectivityDiagnosticsManager;
import android.net.ConnectivityManager;
import android.net.DnsResolver;
import android.net.INetd;
@@ -234,6 +236,7 @@
private final Context mContext;
private final ConnectivityManager mConnectivityManager;
private final AppOpsManager mAppOpsManager;
+ private final ConnectivityDiagnosticsManager mConnectivityDiagnosticsManager;
// The context is for specific user which is created from mUserId
private final Context mUserIdContext;
@VisibleForTesting final Dependencies mDeps;
@@ -549,6 +552,8 @@
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
mUserIdContext = context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
+ mConnectivityDiagnosticsManager =
+ mContext.getSystemService(ConnectivityDiagnosticsManager.class);
mDeps = deps;
mNms = netService;
mNetd = netd;
@@ -2739,6 +2744,7 @@
@Nullable private IkeSessionWrapper mSession;
@Nullable private IkeSessionConnectionInfo mIkeConnectionInfo;
+ @Nullable private VpnConnectivityDiagnosticsCallback mDiagnosticsCallback;
// mMobikeEnabled can only be updated after IKE AUTH is finished.
private boolean mMobikeEnabled = false;
@@ -2797,6 +2803,15 @@
mConnectivityManager.registerSystemDefaultNetworkCallback(mNetworkCallback,
new Handler(mLooper));
}
+
+ // DiagnosticsCallback may return more than one alive VPNs, but VPN will filter based on
+ // Network object.
+ final NetworkRequest diagRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_VPN)
+ .removeCapability(NET_CAPABILITY_NOT_VPN).build();
+ mDiagnosticsCallback = new VpnConnectivityDiagnosticsCallback();
+ mConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback(
+ diagRequest, mExecutor, mDiagnosticsCallback);
}
private boolean isActiveNetwork(@Nullable Network network) {
@@ -3066,22 +3081,28 @@
return;
}
+ if (maybeMigrateIkeSession(underlyingNetwork)) return;
+
+ startIkeSession(underlyingNetwork);
+ }
+
+ boolean maybeMigrateIkeSession(@NonNull Network underlyingNetwork) {
+ if (mSession == null || !mMobikeEnabled) return false;
+
+ // IKE session can schedule a migration event only when IKE AUTH is finished
+ // and mMobikeEnabled is true.
+ Log.d(TAG, "Migrate IKE Session with token "
+ + mCurrentToken
+ + " to network "
+ + underlyingNetwork);
+ mSession.setNetwork(underlyingNetwork);
+ return true;
+ }
+
+ private void startIkeSession(@NonNull Network underlyingNetwork) {
+ Log.d(TAG, "Start new IKE session on network " + underlyingNetwork);
+
try {
- if (mSession != null && mMobikeEnabled) {
- // IKE session can schedule a migration event only when IKE AUTH is finished
- // and mMobikeEnabled is true.
- Log.d(
- TAG,
- "Migrate IKE Session with token "
- + mCurrentToken
- + " to network "
- + underlyingNetwork);
- mSession.setNetwork(underlyingNetwork);
- return;
- }
-
- Log.d(TAG, "Start new IKE session on network " + underlyingNetwork);
-
// Clear mInterface to prevent Ikev2VpnRunner being cleared when
// interfaceRemoved() is called.
synchronized (Vpn.this) {
@@ -3169,6 +3190,28 @@
mUnderlyingLinkProperties = lp;
}
+ class VpnConnectivityDiagnosticsCallback
+ extends ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback {
+ // The callback runs in the executor thread.
+ @Override
+ public void onDataStallSuspected(
+ ConnectivityDiagnosticsManager.DataStallReport report) {
+ synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != Vpn.IkeV2VpnRunner.this) return;
+
+ // Handle the report only for current VPN network.
+ if (mNetworkAgent != null
+ && mNetworkAgent.getNetwork().equals(report.getNetwork())) {
+ Log.d(TAG, "Data stall suspected");
+
+ // Trigger MOBIKE.
+ maybeMigrateIkeSession(mActiveNetwork);
+ }
+ }
+ }
+ }
+
/**
* Handles loss of the default underlying network
*
@@ -3463,6 +3506,8 @@
resetIkeState();
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+ mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback(
+ mDiagnosticsCallback);
mExecutor.shutdown();
}
diff --git a/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java b/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java
index c33b5f1..0812fd9 100644
--- a/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java
+++ b/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.assist.ActivityId;
import android.content.ComponentName;
import android.content.ContentCaptureOptions;
import android.os.Bundle;
@@ -61,5 +62,6 @@
* Notifies the intelligence service of a high-level activity event for the given user.
*/
public abstract void notifyActivityEvent(@UserIdInt int userId,
- @NonNull ComponentName activityComponent, @ActivityEventType int eventType);
+ @NonNull ComponentName activityComponent, @ActivityEventType int eventType,
+ @NonNull ActivityId activityId);
}
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
new file mode 100644
index 0000000..87cbbfe
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -0,0 +1,173 @@
+/*
+ * 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.display;
+
+import com.android.server.display.brightness.BrightnessReason;
+
+import java.util.Objects;
+
+/**
+ * A state class representing a set of brightness related entities that are decided at runtime by
+ * the DisplayBrightnessModeStrategies when updating the brightness.
+ */
+public final class DisplayBrightnessState {
+ private final float mBrightness;
+ private final float mSdrBrightness;
+ private final BrightnessReason mBrightnessReason;
+
+ private DisplayBrightnessState(Builder builder) {
+ this.mBrightness = builder.getBrightness();
+ this.mSdrBrightness = builder.getSdrBrightness();
+ this.mBrightnessReason = builder.getBrightnessReason();
+ }
+
+ /**
+ * Gets the brightness
+ */
+ public float getBrightness() {
+ return mBrightness;
+ }
+
+ /**
+ * Gets the sdr brightness
+ */
+ public float getSdrBrightness() {
+ return mSdrBrightness;
+ }
+
+ /**
+ * Gets the {@link BrightnessReason}
+ */
+ public BrightnessReason getBrightnessReason() {
+ return mBrightnessReason;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:");
+ stringBuilder.append("\n brightness:");
+ stringBuilder.append(getBrightness());
+ stringBuilder.append("\n sdrBrightness:");
+ stringBuilder.append(getSdrBrightness());
+ stringBuilder.append("\n brightnessReason:");
+ stringBuilder.append(getBrightnessReason());
+ return stringBuilder.toString();
+ }
+
+ /**
+ * Checks whether the two objects have the same values.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof DisplayBrightnessState)) {
+ return false;
+ }
+
+ DisplayBrightnessState
+ displayBrightnessState = (DisplayBrightnessState) other;
+
+ if (mBrightness != displayBrightnessState.getBrightness()) {
+ return false;
+ }
+ if (mSdrBrightness != displayBrightnessState.getSdrBrightness()) {
+ return false;
+ }
+ if (!mBrightnessReason.equals(displayBrightnessState.getBrightnessReason())) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason);
+ }
+
+ /**
+ * A DisplayBrightnessState's builder class.
+ */
+ public static class Builder {
+ private float mBrightness;
+ private float mSdrBrightness;
+ private BrightnessReason mBrightnessReason = new BrightnessReason();
+
+ /**
+ * Gets the brightness
+ */
+ public float getBrightness() {
+ return mBrightness;
+ }
+
+ /**
+ * Sets the brightness
+ *
+ * @param brightness The brightness to be associated with DisplayBrightnessState's
+ * builder
+ */
+ public Builder setBrightness(float brightness) {
+ this.mBrightness = brightness;
+ return this;
+ }
+
+ /**
+ * Gets the sdr brightness
+ */
+ public float getSdrBrightness() {
+ return mSdrBrightness;
+ }
+
+ /**
+ * Sets the sdr brightness
+ *
+ * @param sdrBrightness The sdr brightness to be associated with DisplayBrightnessState's
+ * builder
+ */
+ public Builder setSdrBrightness(float sdrBrightness) {
+ this.mSdrBrightness = sdrBrightness;
+ return this;
+ }
+
+ /**
+ * Gets the {@link BrightnessReason}
+ */
+ public BrightnessReason getBrightnessReason() {
+ return mBrightnessReason;
+ }
+
+ /**
+ * Sets the {@link BrightnessReason}
+ *
+ * @param brightnessReason The brightness reason {@link BrightnessReason} to be
+ * associated with the builder
+ */
+ public Builder setBrightnessReason(BrightnessReason brightnessReason) {
+ this.mBrightnessReason = brightnessReason;
+ return this;
+ }
+
+ /**
+ * This is used to construct an immutable DisplayBrightnessState object from its builder
+ */
+ public DisplayBrightnessState build() {
+ return new DisplayBrightnessState(this);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 81219ba..bf981be 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -150,7 +150,7 @@
* <quirk>canSetBrightnessViaHwc</quirk>
* </quirks>
*
- * <autoBrightness>
+ * <autoBrightness enable="true">
* <brighteningLightDebounceMillis>
* 2000
* </brighteningLightDebounceMillis>
@@ -507,6 +507,11 @@
private long mAutoBrightnessDarkeningLightDebounce =
INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE;
+ // This setting allows non-default displays to have autobrightness enabled.
+ private boolean mAutoBrightnessAvailable = false;
+ // This stores the raw value loaded from the config file - true if not written.
+ private boolean mDdcAutoBrightnessAvailable = true;
+
// Brightness Throttling data may be updated via the DeviceConfig. Here we store the original
// data, which comes from the ddc, and the current one, which may be the DeviceConfig
// overwritten value.
@@ -1119,6 +1124,10 @@
return mProximitySensor;
}
+ boolean isAutoBrightnessAvailable() {
+ return mAutoBrightnessAvailable;
+ }
+
/**
* @param quirkValue The quirk to test.
* @return {@code true} if the specified quirk is present in this configuration, {@code false}
@@ -1271,6 +1280,8 @@
+ mAutoBrightnessDarkeningLightDebounce
+ ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux)
+ ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
+ + ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
+ + ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
+ "}";
}
@@ -1349,6 +1360,7 @@
loadBrightnessChangeThresholdsFromXml();
setProxSensorUnspecified();
loadAutoBrightnessConfigsFromConfigXml();
+ loadAutoBrightnessAvailableFromConfigXml();
mLoadedFrom = "<config.xml>";
}
@@ -1367,6 +1379,7 @@
setSimpleMappingStrategyValues();
loadAmbientLightSensorFromConfigXml();
setProxSensorUnspecified();
+ loadAutoBrightnessAvailableFromConfigXml();
}
private void copyUninitializedValuesFromSecondaryConfig(DisplayConfiguration defaultConfig) {
@@ -1559,9 +1572,11 @@
}
private void loadAutoBrightnessConfigValues(DisplayConfiguration config) {
- loadAutoBrightnessBrighteningLightDebounce(config.getAutoBrightness());
- loadAutoBrightnessDarkeningLightDebounce(config.getAutoBrightness());
- loadAutoBrightnessDisplayBrightnessMapping(config.getAutoBrightness());
+ final AutoBrightness autoBrightness = config.getAutoBrightness();
+ loadAutoBrightnessBrighteningLightDebounce(autoBrightness);
+ loadAutoBrightnessDarkeningLightDebounce(autoBrightness);
+ loadAutoBrightnessDisplayBrightnessMapping(autoBrightness);
+ loadEnableAutoBrightness(autoBrightness);
}
/**
@@ -1623,6 +1638,11 @@
}
}
+ private void loadAutoBrightnessAvailableFromConfigXml() {
+ mAutoBrightnessAvailable = mContext.getResources().getBoolean(
+ R.bool.config_automatic_brightness_available);
+ }
+
private void loadBrightnessMapFromConfigXml() {
// Use the config.xml mapping
final Resources res = mContext.getResources();
@@ -2262,6 +2282,20 @@
return levels;
}
+ private void loadEnableAutoBrightness(AutoBrightness autobrightness) {
+ // mDdcAutoBrightnessAvailable is initialised to true, so that we fallback to using the
+ // config.xml values if the autobrightness tag is not defined in the ddc file.
+ // Autobrightness can still be turned off globally via config_automatic_brightness_available
+ mDdcAutoBrightnessAvailable = true;
+ if (autobrightness != null) {
+ mDdcAutoBrightnessAvailable = autobrightness.getEnabled();
+ }
+
+ mAutoBrightnessAvailable = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_automatic_brightness_available)
+ && mDdcAutoBrightnessAvailable;
+ }
+
static class SensorData {
public String type;
public String name;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 4752044..422e98f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -566,13 +566,6 @@
mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness(
pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR));
- // Check the setting, but also verify that it is the default display. Only the default
- // display has an automatic brightness controller running.
- // TODO: b/179021925 - Fix to work with multiple displays
- mUseSoftwareAutoBrightnessConfig = resources.getBoolean(
- com.android.internal.R.bool.config_automatic_brightness_available)
- && mDisplayId == Display.DEFAULT_DISPLAY;
-
mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing);
@@ -952,6 +945,8 @@
}
private void setUpAutoBrightness(Resources resources, Handler handler) {
+ mUseSoftwareAutoBrightnessConfig = mDisplayDeviceConfig.isAutoBrightnessAvailable();
+
if (!mUseSoftwareAutoBrightnessConfig) {
return;
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 172b4be..23c020e 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -542,13 +542,6 @@
mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness(
pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR));
- // Check the setting, but also verify that it is the default display. Only the default
- // display has an automatic brightness controller running.
- // TODO: b/179021925 - Fix to work with multiple displays
- mUseSoftwareAutoBrightnessConfig = resources.getBoolean(
- R.bool.config_automatic_brightness_available)
- && mDisplayId == Display.DEFAULT_DISPLAY;
-
mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
R.bool.config_allowAutoBrightnessWhileDozing);
@@ -928,6 +921,8 @@
}
private void setUpAutoBrightness(Resources resources, Handler handler) {
+ mUseSoftwareAutoBrightnessConfig = mDisplayDeviceConfig.isAutoBrightnessAvailable();
+
if (!mUseSoftwareAutoBrightnessConfig) {
return;
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessModeStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessModeStrategy.java
new file mode 100644
index 0000000..3be5933
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessModeStrategy.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.server.display.brightness.strategy;
+
+import android.hardware.display.DisplayManagerInternal;
+
+import com.android.server.display.DisplayBrightnessState;
+
+import java.io.PrintWriter;
+
+/**
+ * An interface to define the general skeleton of how a BrightnessModeStrategy should look like
+ * This is responsible for deciding the DisplayBrightnessState that the display should change to,
+ * not taking into account clamping that might be needed
+ */
+public interface DisplayBrightnessModeStrategy {
+ /**
+ * Decides the DisplayBrightnessState that the system should change to.
+ *
+ * @param displayPowerRequest The request to evaluate the updated brightness
+ * @param displayState The target displayState to which the system should
+ * change to after processing the request
+ * @param displayBrightnessStateBuilder The DisplayBrightnessStateBuilder, consisting of
+ * DisplayBrightnessState that have been constructed so far
+ */
+ DisplayBrightnessState.Builder updateBrightness(
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest, int displayState,
+ DisplayBrightnessState.Builder displayBrightnessStateBuilder);
+
+ /**
+ * Used to dump the state.
+ *
+ * @param writer The PrintWriter used to dump the state.
+ */
+ void dump(PrintWriter writer);
+}
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index 696b604..324eefc 100644
--- a/services/core/java/com/android/server/input/BatteryController.java
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -38,7 +38,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.input.BatteryController.UEventManager.UEventListener;
+import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -438,7 +438,7 @@
private State mState;
@Nullable
- private UEventListener mUEventListener;
+ private UEventBatteryListener mUEventBatteryListener;
DeviceMonitor(int deviceId) {
mState = new State(deviceId);
@@ -473,20 +473,26 @@
return;
}
final int deviceId = mState.deviceId;
- mUEventListener = new UEventListener() {
+ mUEventBatteryListener = new UEventBatteryListener() {
@Override
- public void onUEvent(long eventTime) {
+ public void onBatteryUEvent(long eventTime) {
handleUEventNotification(deviceId, eventTime);
}
};
- mUEventManager.addListener(mUEventListener, "DEVPATH=" + batteryPath);
+ mUEventManager.addListener(
+ mUEventBatteryListener, "DEVPATH=" + formatDevPath(batteryPath));
+ }
+
+ private String formatDevPath(String path) {
+ // Remove the "/sys" prefix if it has one.
+ return path.startsWith("/sys") ? path.substring(4) : path;
}
// This must be called when the device is no longer being monitored.
public void stopMonitoring() {
- if (mUEventListener != null) {
- mUEventManager.removeListener(mUEventListener);
- mUEventListener = null;
+ if (mUEventBatteryListener != null) {
+ mUEventManager.removeListener(mUEventBatteryListener);
+ mUEventBatteryListener = null;
}
}
@@ -498,7 +504,7 @@
@Override
public String toString() {
return "state=" + mState
- + ", uEventListener=" + (mUEventListener != null ? "added" : "none");
+ + ", uEventListener=" + (mUEventBatteryListener != null ? "added" : "none");
}
}
@@ -507,7 +513,7 @@
interface UEventManager {
@VisibleForTesting
- abstract class UEventListener {
+ abstract class UEventBatteryListener {
private final UEventObserver mObserver = new UEventObserver() {
@Override
public void onUEvent(UEvent event) {
@@ -517,18 +523,23 @@
"UEventListener: Received UEvent: "
+ event + " eventTime: " + eventTime);
}
- UEventListener.this.onUEvent(eventTime);
+ if (!"CHANGE".equalsIgnoreCase(event.get("ACTION"))
+ || !"POWER_SUPPLY".equalsIgnoreCase(event.get("SUBSYSTEM"))) {
+ // Disregard any UEvents that do not correspond to battery changes.
+ return;
+ }
+ UEventBatteryListener.this.onBatteryUEvent(eventTime);
}
};
- public abstract void onUEvent(long eventTime);
+ public abstract void onBatteryUEvent(long eventTime);
}
- default void addListener(UEventListener listener, String match) {
+ default void addListener(UEventBatteryListener listener, String match) {
listener.mObserver.startObserving(match);
}
- default void removeListener(UEventListener listener) {
+ default void removeListener(UEventBatteryListener listener) {
listener.mObserver.stopObserving();
}
}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 66bdadb..ab69c34 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -333,16 +333,25 @@
byte scryptLogP;
public int credentialType;
byte[] salt;
- // If Weaver is available, then this field is empty. Otherwise, it is the Gatekeeper
- // password handle that resulted from enrolling the hashed LSKF.
+ // This is the Gatekeeper password handle that resulted from enrolling the stretched LSKF,
+ // when applicable. This field isn't used if Weaver is available, or in new protectors when
+ // the LSKF is empty.
public byte[] passwordHandle;
- public static PasswordData create(int passwordType) {
+ public static PasswordData create(int credentialType) {
PasswordData result = new PasswordData();
- result.scryptLogN = PASSWORD_SCRYPT_LOG_N;
- result.scryptLogR = PASSWORD_SCRYPT_LOG_R;
- result.scryptLogP = PASSWORD_SCRYPT_LOG_P;
- result.credentialType = passwordType;
+ if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ // When the LSKF is empty, scrypt provides no security benefit, so just use the
+ // minimum parameters (N=2, r=1, p=1).
+ result.scryptLogN = 1;
+ result.scryptLogR = 0;
+ result.scryptLogP = 0;
+ } else {
+ result.scryptLogN = PASSWORD_SCRYPT_LOG_N;
+ result.scryptLogR = PASSWORD_SCRYPT_LOG_R;
+ result.scryptLogP = PASSWORD_SCRYPT_LOG_P;
+ }
+ result.credentialType = credentialType;
result.salt = secureRandom(PASSWORD_SALT_LENGTH);
return result;
}
@@ -776,11 +785,12 @@
long protectorId = generateProtectorId();
PasswordData pwd = PasswordData.create(credential.getType());
byte[] stretchedLskf = stretchLskf(credential, pwd);
- final long sid;
+ long sid = GateKeeper.INVALID_SECURE_USER_ID;
final byte[] protectorSecret;
if (isWeaverAvailable()) {
- // Protector uses Weaver to verify the LSKF
+ // Weaver is available, so make the protector use it to verify the LSKF. Do this even
+ // if the LSKF is empty, as that gives us support for securely deleting the protector.
int weaverSlot = getNextAvailableWeaverSlot();
Slog.i(TAG, "Weaver enroll password to slot " + weaverSlot + " for user " + userId);
byte[] weaverSecret = weaverEnroll(weaverSlot, stretchedLskfToWeaverKey(stretchedLskf),
@@ -794,33 +804,34 @@
// No need to pass in quality since the credential type already encodes sufficient info
synchronizeWeaverFrpPassword(pwd, 0, userId, weaverSlot);
- pwd.passwordHandle = null;
- sid = GateKeeper.INVALID_SECURE_USER_ID;
protectorSecret = transformUnderWeaverSecret(stretchedLskf, weaverSecret);
} else {
- // Protector uses Gatekeeper to verify the LSKF
-
- // In case GK enrollment leaves persistent state around (in RPMB), this will nuke them
- // to prevent them from accumulating and causing problems.
- try {
- gatekeeper.clearSecureUserId(fakeUserId(userId));
- } catch (RemoteException ignore) {
- Slog.w(TAG, "Failed to clear SID from gatekeeper");
+ // Weaver is unavailable, so make the protector use Gatekeeper to verify the LSKF
+ // instead. However, skip Gatekeeper when the LSKF is empty, since it wouldn't give any
+ // benefit in that case as Gatekeeper isn't expected to provide secure deletion.
+ if (!credential.isNone()) {
+ // In case GK enrollment leaves persistent state around (in RPMB), this will nuke
+ // them to prevent them from accumulating and causing problems.
+ try {
+ gatekeeper.clearSecureUserId(fakeUserId(userId));
+ } catch (RemoteException ignore) {
+ Slog.w(TAG, "Failed to clear SID from gatekeeper");
+ }
+ GateKeeperResponse response;
+ try {
+ response = gatekeeper.enroll(fakeUserId(userId), null, null,
+ stretchedLskfToGkPassword(stretchedLskf));
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Failed to enroll LSKF for new SP protector"
+ + " for user " + userId, e);
+ }
+ if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
+ throw new IllegalStateException("Failed to enroll LSKF for new SP protector"
+ + " for user " + userId);
+ }
+ pwd.passwordHandle = response.getPayload();
+ sid = sidFromPasswordHandle(pwd.passwordHandle);
}
- GateKeeperResponse response;
- try {
- response = gatekeeper.enroll(fakeUserId(userId), null, null,
- stretchedLskfToGkPassword(stretchedLskf));
- } catch (RemoteException e) {
- throw new IllegalStateException("Failed to enroll LSKF for new SP protector for "
- + "user " + userId, e);
- }
- if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
- throw new IllegalStateException("Failed to enroll LSKF for new SP protector for "
- + "user " + userId);
- }
- pwd.passwordHandle = response.getPayload();
- sid = sidFromPasswordHandle(pwd.passwordHandle);
protectorSecret = transformUnderSecdiscardable(stretchedLskf,
createSecdiscardable(protectorId, userId));
// No need to pass in quality since the credential type already encodes sufficient info
@@ -1049,7 +1060,7 @@
byte[] stretchedLskf = stretchLskf(credential, pwd);
final byte[] protectorSecret;
- final long sid;
+ long sid = GateKeeper.INVALID_SECURE_USER_ID;
int weaverSlot = loadWeaverSlot(protectorId, userId);
if (weaverSlot != INVALID_WEAVER_SLOT) {
// Protector uses Weaver to verify the LSKF
@@ -1062,54 +1073,62 @@
if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
return result;
}
- sid = GateKeeper.INVALID_SECURE_USER_ID;
protectorSecret = transformUnderWeaverSecret(stretchedLskf,
result.gkResponse.getGatekeeperHAT());
} else {
- // Protector uses Gatekeeper to verify the LSKF
- byte[] gkPassword = stretchedLskfToGkPassword(stretchedLskf);
- GateKeeperResponse response;
- try {
- response = gatekeeper.verifyChallenge(fakeUserId(userId), 0L,
- pwd.passwordHandle, gkPassword);
- } catch (RemoteException e) {
- Slog.e(TAG, "gatekeeper verify failed", e);
- result.gkResponse = VerifyCredentialResponse.ERROR;
- return result;
- }
- int responseCode = response.getResponseCode();
- if (responseCode == GateKeeperResponse.RESPONSE_OK) {
- result.gkResponse = VerifyCredentialResponse.OK;
- if (response.getShouldReEnroll()) {
- GateKeeperResponse reenrollResponse;
- try {
- reenrollResponse = gatekeeper.enroll(fakeUserId(userId),
- pwd.passwordHandle, gkPassword, gkPassword);
- } catch (RemoteException e) {
- Slog.w(TAG, "Fail to invoke gatekeeper.enroll", e);
- reenrollResponse = GateKeeperResponse.ERROR;
- // continue the flow anyway
- }
- if (reenrollResponse.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
- pwd.passwordHandle = reenrollResponse.getPayload();
- // Use the reenrollment opportunity to update credential type
- // (getting rid of CREDENTIAL_TYPE_PASSWORD_OR_PIN)
- pwd.credentialType = credential.getType();
- saveState(PASSWORD_DATA_NAME, pwd.toBytes(), protectorId, userId);
- synchronizeFrpPassword(pwd, 0, userId);
- } else {
- Slog.w(TAG, "Fail to re-enroll user password for user " + userId);
- // continue the flow anyway
- }
+ // Weaver is unavailable, so the protector uses Gatekeeper to verify the LSKF, unless
+ // the LSKF is empty in which case Gatekeeper might not have been used at all.
+ if (pwd.passwordHandle == null) {
+ if (!credential.isNone()) {
+ Slog.e(TAG, "Missing Gatekeeper password handle for nonempty LSKF");
+ result.gkResponse = VerifyCredentialResponse.ERROR;
+ return result;
}
- } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
- result.gkResponse = VerifyCredentialResponse.fromTimeout(response.getTimeout());
- return result;
- } else {
- result.gkResponse = VerifyCredentialResponse.ERROR;
- return result;
+ } else {
+ byte[] gkPassword = stretchedLskfToGkPassword(stretchedLskf);
+ GateKeeperResponse response;
+ try {
+ response = gatekeeper.verifyChallenge(fakeUserId(userId), 0L,
+ pwd.passwordHandle, gkPassword);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "gatekeeper verify failed", e);
+ result.gkResponse = VerifyCredentialResponse.ERROR;
+ return result;
+ }
+ int responseCode = response.getResponseCode();
+ if (responseCode == GateKeeperResponse.RESPONSE_OK) {
+ result.gkResponse = VerifyCredentialResponse.OK;
+ if (response.getShouldReEnroll()) {
+ GateKeeperResponse reenrollResponse;
+ try {
+ reenrollResponse = gatekeeper.enroll(fakeUserId(userId),
+ pwd.passwordHandle, gkPassword, gkPassword);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Fail to invoke gatekeeper.enroll", e);
+ reenrollResponse = GateKeeperResponse.ERROR;
+ // continue the flow anyway
+ }
+ if (reenrollResponse.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
+ pwd.passwordHandle = reenrollResponse.getPayload();
+ // Use the reenrollment opportunity to update credential type
+ // (getting rid of CREDENTIAL_TYPE_PASSWORD_OR_PIN)
+ pwd.credentialType = credential.getType();
+ saveState(PASSWORD_DATA_NAME, pwd.toBytes(), protectorId, userId);
+ synchronizeFrpPassword(pwd, 0, userId);
+ } else {
+ Slog.w(TAG, "Fail to re-enroll user password for user " + userId);
+ // continue the flow anyway
+ }
+ }
+ } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
+ result.gkResponse = VerifyCredentialResponse.fromTimeout(response.getTimeout());
+ return result;
+ } else {
+ result.gkResponse = VerifyCredentialResponse.ERROR;
+ return result;
+ }
+ sid = sidFromPasswordHandle(pwd.passwordHandle);
}
- sid = sidFromPasswordHandle(pwd.passwordHandle);
protectorSecret = transformUnderSecdiscardable(stretchedLskf,
loadSecdiscardable(protectorId, userId));
}
@@ -1463,7 +1482,8 @@
return result;
}
- private int fakeUserId(int userId) {
+ @VisibleForTesting
+ static int fakeUserId(int userId) {
return 100000 + userId;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4b18add..77fea09 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4872,6 +4872,13 @@
}
@Override
+ public int getHintsFromListenerNoToken() {
+ synchronized (mNotificationLock) {
+ return mListenerHints;
+ }
+ }
+
+ @Override
public void requestInterruptionFilterFromListener(INotificationListener token,
int interruptionFilter) throws RemoteException {
final long identity = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/pm/DumpHelper.java b/services/core/java/com/android/server/pm/DumpHelper.java
index d2a5e32..3385a09 100644
--- a/services/core/java/com/android/server/pm/DumpHelper.java
+++ b/services/core/java/com/android/server/pm/DumpHelper.java
@@ -26,6 +26,7 @@
import android.content.pm.FeatureInfo;
import android.content.pm.PackageManager;
import android.os.Binder;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.incremental.PerUidReadTimeouts;
import android.service.pm.PackageServiceDumpProto;
@@ -60,6 +61,7 @@
private final ArrayMap<String, FeatureInfo> mAvailableFeatures;
private final ArraySet<String> mProtectedBroadcasts;
private final PerUidReadTimeouts[] mPerUidReadTimeouts;
+ private final SnapshotStatistics mSnapshotStatistics;
DumpHelper(
PermissionManagerServiceInternal permissionManager,
@@ -70,7 +72,8 @@
ChangedPackagesTracker changedPackagesTracker,
ArrayMap<String, FeatureInfo> availableFeatures,
ArraySet<String> protectedBroadcasts,
- PerUidReadTimeouts[] perUidReadTimeouts) {
+ PerUidReadTimeouts[] perUidReadTimeouts,
+ SnapshotStatistics snapshotStatistics) {
mPermissionManager = permissionManager;
mStorageEventHelper = storageEventHelper;
mDomainVerificationManager = domainVerificationManager;
@@ -81,6 +84,7 @@
mAvailableFeatures = availableFeatures;
mProtectedBroadcasts = protectedBroadcasts;
mPerUidReadTimeouts = perUidReadTimeouts;
+ mSnapshotStatistics = snapshotStatistics;
}
@NeverCompile // Avoid size overhead of debugging code.
@@ -585,6 +589,9 @@
if (dumpState.onTitlePrinted()) {
pw.println();
}
+ pw.println("Snapshot statistics:");
+ mSnapshotStatistics.dump(pw, " " /* indent */, SystemClock.currentTimeMicro(),
+ snapshot.getUsed(), dumpState.isBrief());
}
if (!checkin
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index e9f26e9..022bf3c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -81,9 +81,11 @@
import android.content.pm.InstallationFileParcel;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.PreapprovalDetails;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.PackageInfoFlags;
import android.content.pm.PackageManagerInternal;
import android.content.pm.SigningDetails;
import android.content.pm.dex.DexMetadataHelper;
@@ -92,8 +94,13 @@
import android.content.pm.parsing.PackageLite;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.res.ApkAssets;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.icu.util.ULocale;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -193,6 +200,7 @@
private static final int MSG_INSTALL = 3;
private static final int MSG_ON_PACKAGE_INSTALLED = 4;
private static final int MSG_SESSION_VALIDATION_FAILURE = 5;
+ private static final int MSG_PRE_APPROVAL_REQUEST = 6;
/** XML constants used for persisting a session */
static final String TAG_SESSION = "session";
@@ -275,7 +283,7 @@
* #mValidatedTargetSdk} is compared with {@link Build.VERSION_CODES#R} before getting the
* target sdk version from a validated apk in {@link #validateApkInstallLocked()}, the compared
* result will not trigger any user action in
- * {@link #checkUserActionRequirement(PackageInstallerSession)}.
+ * {@link #checkUserActionRequirement(PackageInstallerSession, IntentSender)}.
*/
private static final int INVALID_TARGET_SDK_VERSION = Integer.MAX_VALUE;
@@ -360,6 +368,7 @@
@GuardedBy("mLock")
private boolean mShouldBeSealed = false;
+ private final AtomicBoolean mPreapprovalRequested = new AtomicBoolean(false);
private final AtomicBoolean mCommitted = new AtomicBoolean(false);
/**
@@ -387,6 +396,9 @@
@GuardedBy("mLock")
private IntentSender mRemoteStatusReceiver;
+ @GuardedBy("mLock")
+ private PreapprovalDetails mPreapprovalDetails;
+
/** Fields derived from commit parsing */
@GuardedBy("mLock")
private String mPackageName;
@@ -740,11 +752,12 @@
final Bundle extras = (Bundle) args.arg3;
final IntentSender statusReceiver = (IntentSender) args.arg4;
final int returnCode = args.argi1;
+ final boolean isPreapproval = args.argi2 == 1;
args.recycle();
sendOnPackageInstalled(mContext, statusReceiver, sessionId,
isInstallerDeviceOwnerOrAffiliatedProfileOwner(), userId,
- packageName, returnCode, message, extras);
+ packageName, returnCode, isPreapproval, message, extras);
break;
case MSG_SESSION_VALIDATION_FAILURE:
@@ -752,6 +765,9 @@
final String detailMessage = (String) msg.obj;
onSessionValidationFailure(error, detailMessage);
break;
+ case MSG_PRE_APPROVAL_REQUEST:
+ handlePreapprovalRequest();
+ break;
}
return true;
@@ -779,10 +795,7 @@
*/
private boolean isInstallerDeviceOwnerOrAffiliatedProfileOwner() {
assertNotLocked("isInstallerDeviceOwnerOrAffiliatedProfileOwner");
- // It is safe to access mInstallerUid and mInstallSource without lock
- // because they are immutable after sealing.
- assertSealed("isInstallerDeviceOwnerOrAffiliatedProfileOwner");
- if (userId != UserHandle.getUserId(mInstallerUid)) {
+ if (userId != UserHandle.getUserId(getInstallerUid())) {
return false;
}
DevicePolicyManagerInternal dpmi =
@@ -874,7 +887,7 @@
if (snapshot.isInstallDisabledForPackage(getInstallerPackageName(), mInstallerUid,
userId)) {
- // show the installer to account for device poslicy or unknown sources use cases
+ // show the installer to account for device policy or unknown sources use cases
return USER_ACTION_REQUIRED;
}
@@ -1031,18 +1044,22 @@
mResolvedBaseFile.getAbsolutePath() : null;
info.progress = progress;
info.sealed = mSealed;
- info.isCommitted = mCommitted.get();
+ info.isCommitted = isCommitted();
+ info.isPreapprovalRequested = isPreapprovalRequested();
info.active = mActiveCount.get() > 0;
info.mode = params.mode;
info.installReason = params.installReason;
info.installScenario = params.installScenario;
info.sizeBytes = params.sizeBytes;
- info.appPackageName = mPackageName != null ? mPackageName : params.appPackageName;
+ info.appPackageName = mPreapprovalDetails != null ? mPreapprovalDetails.getPackageName()
+ : mPackageName != null ? mPackageName : params.appPackageName;
if (includeIcon) {
- info.appIcon = params.appIcon;
+ info.appIcon = mPreapprovalDetails != null && mPreapprovalDetails.getIcon() != null
+ ? mPreapprovalDetails.getIcon() : params.appIcon;
}
- info.appLabel = params.appLabel;
+ info.appLabel =
+ mPreapprovalDetails != null ? mPreapprovalDetails.getLabel() : params.appLabel;
info.installLocation = params.installLocation;
if (!scrubData) {
@@ -1086,6 +1103,11 @@
}
}
+ /** @hide */
+ boolean isPreapprovalRequested() {
+ return mPreapprovalRequested.get();
+ }
+
/** {@hide} */
boolean isCommitted() {
return mCommitted.get();
@@ -1122,6 +1144,14 @@
}
@GuardedBy("mLock")
+ private void assertPreparedAndNotPreapprovalRequestedLocked(String cookie) {
+ assertPreparedAndNotSealedLocked(cookie);
+ if (isPreapprovalRequested()) {
+ throw new IllegalStateException(cookie + " not allowed after requesting");
+ }
+ }
+
+ @GuardedBy("mLock")
private void assertPreparedAndNotSealedLocked(String cookie) {
assertPreparedAndNotCommittedOrDestroyedLocked(cookie);
if (mSealed) {
@@ -1132,7 +1162,7 @@
@GuardedBy("mLock")
private void assertPreparedAndNotCommittedOrDestroyedLocked(String cookie) {
assertPreparedAndNotDestroyedLocked(cookie);
- if (mCommitted.get()) {
+ if (isCommitted()) {
throw new SecurityException(cookie + " not allowed after commit");
}
}
@@ -1173,7 +1203,7 @@
@GuardedBy("mProgressLock")
private void computeProgressLocked(boolean forcePublish) {
- if (!isIncrementalInstallation() || !mCommitted.get()) {
+ if (!isIncrementalInstallation() || !isCommitted()) {
mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f)
+ MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f);
} else {
@@ -1200,7 +1230,7 @@
assertCallerIsOwnerRootOrVerifier();
synchronized (mLock) {
assertPreparedAndNotDestroyedLocked("getNames");
- if (!mCommitted.get()) {
+ if (!isCommitted()) {
return getNamesLocked();
} else {
return getStageDirContentsLocked();
@@ -1640,11 +1670,7 @@
@Override
public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
- if (hasParentSessionId()) {
- throw new IllegalStateException(
- "Session " + sessionId + " is a child of multi-package session "
- + getParentSessionId() + " and may not be committed directly.");
- }
+ assertNotChild("commit");
if (!markAsSealed(statusReceiver, forTransfer)) {
return;
@@ -1708,6 +1734,20 @@
}
}
+ @WorkerThread
+ private void handlePreapprovalRequest() {
+ /**
+ * Stops the process if the session needs user action. When the user answers the yes,
+ * {@link #setPermissionsResult(boolean)} is called and then
+ * {@link #MSG_PRE_APPROVAL_REQUEST} is handled to come back here to check again.
+ */
+ if (sendPendingUserActionIntentIfNeeded()) {
+ return;
+ }
+
+ dispatchSessionPreappoved();
+ }
+
private final class FileSystemConnector extends
IPackageInstallerSessionFileSystemConnector.Stub {
final Set<String> mAddedFiles = new ArraySet<>();
@@ -1835,7 +1875,7 @@
// single / child sessions.
try {
synchronized (mLock) {
- if (mCommitted.get()) {
+ if (isCommitted()) {
return true;
}
// Read transfers from the original owner stay open, but as the session's data
@@ -2115,7 +2155,11 @@
*/
@WorkerThread
private boolean sendPendingUserActionIntentIfNeeded() {
- assertNotChild("PackageInstallerSession#sendPendingUserActionIntentIfNeeded");
+ // To support pre-approval request of atomic install, we allow child session to handle
+ // the result by itself since it has the status receiver.
+ if (isCommitted()) {
+ assertNotChild("PackageInstallerSession#sendPendingUserActionIntentIfNeeded");
+ }
final IntentSender statusReceiver = getRemoteStatusReceiver();
return sessionContains(s -> checkUserActionRequirement(s, statusReceiver));
@@ -2420,7 +2464,10 @@
// User needs to confirm installation;
// give installer an intent they can use to involve
// user.
- final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_INSTALL);
+ final boolean isPreapproval = isPreapprovalRequested() && !isCommitted();
+ final Intent intent = new Intent(
+ isPreapproval ? PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL
+ : PackageInstaller.ACTION_CONFIRM_INSTALL);
intent.setPackage(mPm.getPackageInstallerPackageName());
intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
@@ -3009,6 +3056,9 @@
}
}
}
+
+ assertPreapprovalDetailsConsistentIfNeededLocked(packageLite, pkgInfo);
+
if (packageLite.isUseEmbeddedDex()) {
for (File file : mResolvedStagedFiles) {
if (file.getName().endsWith(".apk")
@@ -3253,6 +3303,78 @@
}
}
+ @GuardedBy("mLock")
+ private void assertPreapprovalDetailsConsistentIfNeededLocked(@NonNull PackageLite packageLite,
+ @Nullable PackageInfo info) throws PackageManagerException {
+ if (mPreapprovalDetails == null || !isPreapprovalRequested()) {
+ return;
+ }
+
+ if (!TextUtils.equals(mPackageName, mPreapprovalDetails.getPackageName())) {
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+ mPreapprovalDetails + " inconsistent with " + mPackageName);
+ }
+
+ // In case the app label in PreapprovalDetails from different locale in split APK,
+ // we check all APK files to find the app label.
+ final PackageInfo packageInfo =
+ info != null ? info : mContext.getPackageManager().getPackageArchiveInfo(
+ packageLite.getPath(), PackageInfoFlags.of(0));
+ if (packageInfo == null) {
+ throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+ "Failure to obtain package info.");
+ }
+ final List<String> filePaths = packageLite.getAllApkPaths();
+ final String appLabel = mPreapprovalDetails.getLabel();
+ final ULocale appLocale = mPreapprovalDetails.getLocale();
+ final ApplicationInfo appInfo = packageInfo.applicationInfo;
+ boolean appLabelMatched = false;
+ for (int i = filePaths.size() - 1; i >= 0 && !appLabelMatched; i--) {
+ appLabelMatched |= TextUtils.equals(getAppLabel(filePaths.get(i), appLocale, appInfo),
+ appLabel);
+ }
+ if (!appLabelMatched) {
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+ mPreapprovalDetails + " inconsistent with app label");
+ }
+ }
+
+ private CharSequence getAppLabel(String path, ULocale locale, ApplicationInfo appInfo)
+ throws PackageManagerException {
+ final Resources pRes = mContext.getResources();
+ final AssetManager assetManager = new AssetManager();
+ final Configuration config = new Configuration(pRes.getConfiguration());
+ final ApkAssets apkAssets;
+ try {
+ apkAssets = ApkAssets.loadFromPath(path);
+ } catch (IOException e) {
+ throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+ "Failure to get resources from package archive " + path);
+ }
+ assetManager.setApkAssets(new ApkAssets[]{apkAssets}, false /* invalidateCaches */);
+ config.setLocale(locale.toLocale());
+ final Resources res = new Resources(assetManager, pRes.getDisplayMetrics(), config);
+ return tryLoadingAppLabel(res, appInfo);
+ }
+
+ private CharSequence tryLoadingAppLabel(@NonNull Resources res, @NonNull ApplicationInfo info) {
+ CharSequence label = null;
+ // Try to load the label from the package's resources. If an app has not explicitly
+ // specified any label, just use the package name.
+ if (info.labelRes != 0) {
+ try {
+ label = res.getText(info.labelRes);
+ } catch (Resources.NotFoundException ignore) {
+ }
+ }
+ if (label == null) {
+ label = (info.nonLocalizedLabel != null)
+ ? info.nonLocalizedLabel : info.packageName;
+ }
+
+ return label;
+ }
+
private SigningDetails unsafeGetCertsWithoutVerification(String path)
throws PackageManagerException {
final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
@@ -3469,11 +3591,13 @@
}
void setPermissionsResult(boolean accepted) {
- if (!isSealed()) {
+ if (!isSealed() && !isPreapprovalRequested()) {
throw new SecurityException("Must be sealed to accept permissions");
}
- PackageInstallerSession root = hasParentSessionId()
+ // To support pre-approval request of atomic install, we allow child session to handle
+ // the result by itself since it has the status receiver.
+ final PackageInstallerSession root = hasParentSessionId() && isCommitted()
? mSessionProvider.getSession(getParentSessionId()) : this;
if (accepted) {
@@ -3481,7 +3605,8 @@
synchronized (mLock) {
mPermissionsManuallyAccepted = true;
}
- root.mHandler.obtainMessage(MSG_INSTALL).sendToTarget();
+ root.mHandler.obtainMessage(
+ isCommitted() ? MSG_INSTALL : MSG_PRE_APPROVAL_REQUEST).sendToTarget();
} else {
root.destroy();
root.dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
@@ -3593,7 +3718,7 @@
mDestroyed = true;
r = () -> {
assertNotLocked("abandonStaged");
- if (isStaged() && mCommitted.get()) {
+ if (isStaged() && isCommitted()) {
mStagingManager.abortCommittedSession(mStagedSession);
}
destroy();
@@ -3937,7 +4062,7 @@
private boolean canBeAddedAsChild(int parentCandidate) {
synchronized (mLock) {
return (!hasParentSessionId() || mParentSessionId == parentCandidate)
- && !mCommitted.get()
+ && !isCommitted()
&& !mDestroyed;
}
}
@@ -4096,10 +4221,68 @@
args.arg3 = extras;
args.arg4 = statusReceiver;
args.argi1 = returnCode;
+ args.argi2 = isPreapprovalRequested() && !isCommitted() ? 1 : 0;
mHandler.obtainMessage(MSG_ON_PACKAGE_INSTALLED, args).sendToTarget();
}
}
+ private void dispatchSessionPreappoved() {
+ final IntentSender target = getRemoteStatusReceiver();
+ final Intent intent = new Intent();
+ intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
+ intent.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_SUCCESS);
+ intent.putExtra(PackageInstaller.EXTRA_PRE_APPROVAL, true);
+ try {
+ target.sendIntent(mContext, 0 /* code */, intent, null /* onFinished */,
+ null /* handler */);
+ } catch (IntentSender.SendIntentException ignored) {
+ }
+ }
+
+ @Override
+ public void requestUserPreapproval(@NonNull PreapprovalDetails details,
+ @NonNull IntentSender statusReceiver) {
+ validatePreapprovalRequest(details, statusReceiver);
+ dispatchPreapprovalRequest();
+ }
+
+ /**
+ * Validates whether the necessary information (e.g., PreapprovalDetails) are provided.
+ */
+ private void validatePreapprovalRequest(@NonNull PreapprovalDetails details,
+ @NonNull IntentSender statusReceiver) {
+ assertCallerIsOwnerOrRoot();
+ if (isMultiPackage()) {
+ throw new IllegalStateException(
+ "Session " + sessionId + " is a parent of multi-package session and "
+ + "requestUserPreapproval on the parent session isn't supported.");
+ }
+
+ synchronized (mLock) {
+ assertPreparedAndNotSealedLocked("request of session " + sessionId);
+ mPreapprovalDetails = details;
+ setRemoteStatusReceiver(statusReceiver);
+ }
+ }
+
+ private void dispatchPreapprovalRequest() {
+ synchronized (mLock) {
+ assertPreparedAndNotPreapprovalRequestedLocked("dispatchPreapprovalRequest");
+ }
+
+ // Mark this session are pre-approval requested, and ready to progress to the next phase.
+ markAsPreapprovalRequested();
+
+ mHandler.obtainMessage(MSG_PRE_APPROVAL_REQUEST).sendToTarget();
+ }
+
+ /**
+ * Marks this session as pre-approval requested, and prevents further related modification.
+ */
+ private void markAsPreapprovalRequested() {
+ mPreapprovalRequested.set(true);
+ }
+
void setSessionReady() {
synchronized (mLock) {
// Do not allow destroyed/failed session to change state
@@ -4265,6 +4448,7 @@
pw.printPair("mClientProgress", clientProgress);
pw.printPair("mProgress", progress);
pw.printPair("mCommitted", mCommitted);
+ pw.printPair("mPreapprovalRequested", mPreapprovalRequested);
pw.printPair("mSealed", mSealed);
pw.printPair("mPermissionsManuallyAccepted", mPermissionsManuallyAccepted);
pw.printPair("mStageDirInUse", mStageDirInUse);
@@ -4282,6 +4466,7 @@
pw.printPair("mSessionReady", mSessionReady);
pw.printPair("mSessionErrorCode", mSessionErrorCode);
pw.printPair("mSessionErrorMessage", mSessionErrorMessage);
+ pw.printPair("mPreapprovalDetails", mPreapprovalDetails);
pw.println();
pw.decreaseIndent();
@@ -4295,6 +4480,8 @@
final Intent fillIn = new Intent();
fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_PENDING_USER_ACTION);
+ fillIn.putExtra(PackageInstaller.EXTRA_PRE_APPROVAL,
+ PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(intent.getAction()));
fillIn.putExtra(Intent.EXTRA_INTENT, intent);
try {
target.sendIntent(context, 0, fillIn, null, null);
@@ -4307,7 +4494,7 @@
*/
private static void sendOnPackageInstalled(Context context, IntentSender target, int sessionId,
boolean showNotification, int userId, String basePackageName, int returnCode,
- String msg, Bundle extras) {
+ boolean isPreapproval, String msg, Bundle extras) {
if (INSTALL_SUCCEEDED == returnCode && showNotification) {
boolean update = (extras != null) && extras.getBoolean(Intent.EXTRA_REPLACING);
Notification notification = PackageInstallerService.buildSuccessNotification(context,
@@ -4330,6 +4517,7 @@
fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
PackageManager.installStatusToString(returnCode, msg));
fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode);
+ fillIn.putExtra(PackageInstaller.EXTRA_PRE_APPROVAL, isPreapproval);
if (extras != null) {
final String existing = extras.getString(
PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE);
@@ -4448,7 +4636,7 @@
writeStringAttribute(out, ATTR_SESSION_STAGE_CID, stageCid);
}
writeBooleanAttribute(out, ATTR_PREPARED, mPrepared);
- writeBooleanAttribute(out, ATTR_COMMITTED, mCommitted.get());
+ writeBooleanAttribute(out, ATTR_COMMITTED, isCommitted());
writeBooleanAttribute(out, ATTR_DESTROYED, mDestroyed);
writeBooleanAttribute(out, ATTR_SEALED, mSealed);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d939ca6..8fed153 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6031,7 +6031,7 @@
new DumpHelper(mPermissionManager, mStorageEventHelper,
mDomainVerificationManager, mInstallerService, mRequiredVerifierPackages,
knownPackages, mChangedPackagesTracker, availableFeatures, protectedBroadcasts,
- getPerUidReadTimeouts(snapshot)
+ getPerUidReadTimeouts(snapshot), mSnapshotStatistics
).doDump(snapshot, fd, pw, args);
}
}
diff --git a/services/core/java/com/android/server/pm/SnapshotStatistics.java b/services/core/java/com/android/server/pm/SnapshotStatistics.java
index 95f80de..2cfc894 100644
--- a/services/core/java/com/android/server/pm/SnapshotStatistics.java
+++ b/services/core/java/com/android/server/pm/SnapshotStatistics.java
@@ -240,11 +240,6 @@
public int mTotalUsed = 0;
/**
- * The total number of times a snapshot was bypassed because corking was in effect.
- */
- public int mTotalCorked = 0;
-
- /**
* The total number of builds that count as big, which means they took longer than
* SNAPSHOT_BIG_BUILD_TIME_NS.
*/
@@ -297,13 +292,6 @@
}
}
- /**
- * Record a cork.
- */
- private void corked() {
- mTotalCorked++;
- }
-
private Stats(long now) {
mStartTimeUs = now;
mTimes = new int[mTimeBins.count()];
@@ -321,7 +309,6 @@
mUsed = Arrays.copyOf(orig.mUsed, orig.mUsed.length);
mTotalBuilds = orig.mTotalBuilds;
mTotalUsed = orig.mTotalUsed;
- mTotalCorked = orig.mTotalCorked;
mBigBuilds = orig.mBigBuilds;
mShortLived = orig.mShortLived;
mTotalTimeUs = orig.mTotalTimeUs;
@@ -379,7 +366,6 @@
* Dump the summary statistics record. Choose the header or the data.
* number of builds
* number of uses
- * number of corks
* number of big builds
* number of short lifetimes
* cumulative build time, in seconds
@@ -388,13 +374,13 @@
private void dumpStats(PrintWriter pw, String indent, long now, boolean header) {
dumpPrefix(pw, indent, now, header, "Summary stats");
if (header) {
- pw.format(Locale.US, " %10s %10s %10s %10s %10s %10s %10s",
- "TotBlds", "TotUsed", "TotCork", "BigBlds", "ShortLvd",
+ pw.format(Locale.US, " %10s %10s %10s %10s %10s %10s",
+ "TotBlds", "TotUsed", "BigBlds", "ShortLvd",
"TotTime", "MaxTime");
} else {
pw.format(Locale.US,
- " %10d %10d %10d %10d %10d %10d %10d",
- mTotalBuilds, mTotalUsed, mTotalCorked, mBigBuilds, mShortLived,
+ " %10d %10d %10d %10d %10d %10d",
+ mTotalBuilds, mTotalUsed, mBigBuilds, mShortLived,
mTotalTimeUs / 1000, mMaxBuildTimeUs / 1000);
}
pw.println();
@@ -559,16 +545,6 @@
}
/**
- * Record a corked snapshot request.
- */
- public final void corked() {
- synchronized (mLock) {
- mShort[0].corked();
- mLong[0].corked();
- }
- }
-
- /**
* Roll a stats array. Shift the elements up an index and create a new element at
* index zero. The old element zero is completed with the specified time.
*/
@@ -624,8 +600,7 @@
* Dump the statistics. The format is compatible with the PackageManager dumpsys
* output.
*/
- public void dump(PrintWriter pw, String indent, long now, int unrecorded,
- int corkLevel, boolean brief) {
+ public void dump(PrintWriter pw, String indent, long now, int unrecorded, boolean brief) {
// Grab the raw statistics under lock, but print them outside of the lock.
Stats[] l;
Stats[] s;
@@ -635,8 +610,7 @@
s = Arrays.copyOf(mShort, mShort.length);
s[0] = new Stats(s[0]);
}
- pw.format(Locale.US, "%s Unrecorded-hits: %d Cork-level: %d", indent,
- unrecorded, corkLevel);
+ pw.format(Locale.US, "%s Unrecorded-hits: %d", indent, unrecorded);
pw.println();
dump(pw, indent, now, l, s, "stats");
if (brief) {
diff --git a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
new file mode 100644
index 0000000..5f76fbc
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
@@ -0,0 +1,455 @@
+/*
+ * 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.power.stats;
+
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM;
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.util.IndentingPrintWriter;
+import android.util.LongSparseArray;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseLongArray;
+import android.util.TimeSparseArray;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IntPair;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Stores stats about CPU wakeups and tries to attribute them to subsystems and uids.
+ */
+public class CpuWakeupStats {
+ private static final String SUBSYSTEM_ALARM_STRING = "Alarm";
+ @VisibleForTesting
+ static final long WAKEUP_RETENTION_MS = 3 * 24 * 60 * 60_000; // 3 days.
+ @VisibleForTesting
+ static final long WAKEUP_REASON_HALF_WINDOW_MS = 500;
+
+ private final IrqDeviceMap mIrqDeviceMap;
+ private final WakingActivityHistory mRecentWakingActivity = new WakingActivityHistory();
+
+ @VisibleForTesting
+ final TimeSparseArray<Wakeup> mWakeupEvents = new TimeSparseArray<>();
+ @VisibleForTesting
+ final TimeSparseArray<SparseArray<SparseBooleanArray>> mWakeupAttribution =
+ new TimeSparseArray<>();
+
+ public CpuWakeupStats(Context context, int mapRes) {
+ mIrqDeviceMap = IrqDeviceMap.getInstance(context, mapRes);
+ }
+
+ /** Notes a wakeup reason as reported by SuspendControlService to battery stats. */
+ public synchronized void noteWakeupTimeAndReason(long elapsedRealtime, long uptime,
+ String rawReason) {
+ final Wakeup parsedWakeup = new Wakeup(rawReason, elapsedRealtime, uptime);
+ mWakeupEvents.put(elapsedRealtime, parsedWakeup);
+ attemptAttributionFor(parsedWakeup);
+ // Assuming that wakeups always arrive in monotonically increasing elapsedRealtime order,
+ // we can delete all history that will not be useful in attributing future wakeups.
+ mRecentWakingActivity.clearAllBefore(elapsedRealtime - WAKEUP_REASON_HALF_WINDOW_MS);
+
+ // Limit history of wakeups and their attribution to the last WAKEUP_RETENTION_MS. Note that
+ // the last wakeup and its attribution (if computed) is always stored, even if that wakeup
+ // had occurred before WAKEUP_RETENTION_MS.
+ int lastIdx = mWakeupEvents.closestIndexOnOrBefore(elapsedRealtime - WAKEUP_RETENTION_MS);
+ for (int i = lastIdx; i >= 0; i--) {
+ mWakeupEvents.removeAt(i);
+ }
+ lastIdx = mWakeupAttribution.closestIndexOnOrBefore(elapsedRealtime - WAKEUP_RETENTION_MS);
+ for (int i = lastIdx; i >= 0; i--) {
+ mWakeupAttribution.removeAt(i);
+ }
+ }
+
+ /** Notes a waking activity that could have potentially woken up the CPU. */
+ public synchronized void noteWakingActivity(int subsystem, long elapsedRealtime, int... uids) {
+ if (!attemptAttributionWith(subsystem, elapsedRealtime, uids)) {
+ mRecentWakingActivity.recordActivity(subsystem, elapsedRealtime, uids);
+ }
+ }
+
+ private synchronized void attemptAttributionFor(Wakeup wakeup) {
+ final SparseBooleanArray subsystems = getResponsibleSubsystemsForWakeup(wakeup);
+ if (subsystems == null) {
+ // We don't support attribution for this kind of wakeup yet.
+ return;
+ }
+
+ SparseArray<SparseBooleanArray> attribution = mWakeupAttribution.get(wakeup.mElapsedMillis);
+ if (attribution == null) {
+ attribution = new SparseArray<>();
+ mWakeupAttribution.put(wakeup.mElapsedMillis, attribution);
+ }
+
+ for (int subsystemIdx = 0; subsystemIdx < subsystems.size(); subsystemIdx++) {
+ final int subsystem = subsystems.keyAt(subsystemIdx);
+
+ // Blame all activity that happened WAKEUP_REASON_HALF_WINDOW_MS before or after
+ // the wakeup from each responsible subsystem.
+ final long startTime = wakeup.mElapsedMillis - WAKEUP_REASON_HALF_WINDOW_MS;
+ final long endTime = wakeup.mElapsedMillis + WAKEUP_REASON_HALF_WINDOW_MS;
+
+ final SparseBooleanArray uidsToBlame = mRecentWakingActivity.removeBetween(subsystem,
+ startTime, endTime);
+ attribution.put(subsystem, uidsToBlame);
+ }
+ }
+
+ private synchronized boolean attemptAttributionWith(int subsystem, long activityElapsed,
+ int... uids) {
+ final int startIdx = mWakeupEvents.closestIndexOnOrAfter(
+ activityElapsed - WAKEUP_REASON_HALF_WINDOW_MS);
+ final int endIdx = mWakeupEvents.closestIndexOnOrBefore(
+ activityElapsed + WAKEUP_REASON_HALF_WINDOW_MS);
+
+ for (int wakeupIdx = startIdx; wakeupIdx <= endIdx; wakeupIdx++) {
+ final Wakeup wakeup = mWakeupEvents.valueAt(wakeupIdx);
+ final SparseBooleanArray subsystems = getResponsibleSubsystemsForWakeup(wakeup);
+ if (subsystems == null) {
+ // Unsupported for attribution
+ continue;
+ }
+ if (subsystems.get(subsystem)) {
+ // We don't expect more than one wakeup to be found within such a short window, so
+ // just attribute this one and exit
+ SparseArray<SparseBooleanArray> attribution = mWakeupAttribution.get(
+ wakeup.mElapsedMillis);
+ if (attribution == null) {
+ attribution = new SparseArray<>();
+ mWakeupAttribution.put(wakeup.mElapsedMillis, attribution);
+ }
+ SparseBooleanArray uidsToBlame = attribution.get(subsystem);
+ if (uidsToBlame == null) {
+ uidsToBlame = new SparseBooleanArray(uids.length);
+ attribution.put(subsystem, uidsToBlame);
+ }
+ for (final int uid : uids) {
+ uidsToBlame.put(uid, true);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Dumps the relevant stats for cpu wakeups and their attribution to subsystem and uids */
+ public synchronized void dump(IndentingPrintWriter pw, long nowElapsed) {
+ pw.println("CPU wakeup stats:");
+ pw.increaseIndent();
+
+ mIrqDeviceMap.dump(pw);
+ pw.println();
+
+ mRecentWakingActivity.dump(pw, nowElapsed);
+ pw.println();
+
+ final SparseLongArray attributionStats = new SparseLongArray();
+ pw.println("Wakeup events:");
+ pw.increaseIndent();
+ for (int i = mWakeupEvents.size() - 1; i >= 0; i--) {
+ TimeUtils.formatDuration(mWakeupEvents.keyAt(i), nowElapsed, pw);
+ pw.println(":");
+
+ pw.increaseIndent();
+ final Wakeup wakeup = mWakeupEvents.valueAt(i);
+ pw.println(wakeup);
+ pw.print("Attribution: ");
+ final SparseArray<SparseBooleanArray> attribution = mWakeupAttribution.get(
+ wakeup.mElapsedMillis);
+ if (attribution == null) {
+ pw.println("N/A");
+ } else {
+ for (int subsystemIdx = 0; subsystemIdx < attribution.size(); subsystemIdx++) {
+ if (subsystemIdx > 0) {
+ pw.print(", ");
+ }
+ final long counters = attributionStats.get(attribution.keyAt(subsystemIdx),
+ IntPair.of(0, 0));
+ int attributed = IntPair.first(counters);
+ final int total = IntPair.second(counters) + 1;
+
+ pw.print("subsystem: " + subsystemToString(attribution.keyAt(subsystemIdx)));
+ pw.print(", uids: [");
+ final SparseBooleanArray uids = attribution.valueAt(subsystemIdx);
+ if (uids != null) {
+ for (int uidIdx = 0; uidIdx < uids.size(); uidIdx++) {
+ if (uidIdx > 0) {
+ pw.print(", ");
+ }
+ UserHandle.formatUid(pw, uids.keyAt(uidIdx));
+ }
+ attributed++;
+ }
+ pw.print("]");
+
+ attributionStats.put(attribution.keyAt(subsystemIdx),
+ IntPair.of(attributed, total));
+ }
+ pw.println();
+ }
+ pw.decreaseIndent();
+ }
+ pw.decreaseIndent();
+
+ pw.println("Attribution stats:");
+ pw.increaseIndent();
+ for (int i = 0; i < attributionStats.size(); i++) {
+ pw.print("Subsystem " + subsystemToString(attributionStats.keyAt(i)));
+ pw.print(": ");
+ final long ratio = attributionStats.valueAt(i);
+ pw.println(IntPair.first(ratio) + "/" + IntPair.second(ratio));
+ }
+ pw.println("Total: " + mWakeupEvents.size());
+ pw.decreaseIndent();
+
+ pw.decreaseIndent();
+ pw.println();
+ }
+
+ private static final class WakingActivityHistory {
+ private static final long WAKING_ACTIVITY_RETENTION_MS = 3 * 60 * 60_000; // 3 hours.
+ private SparseArray<TimeSparseArray<SparseBooleanArray>> mWakingActivity =
+ new SparseArray<>();
+
+ void recordActivity(int subsystem, long elapsedRealtime, int... uids) {
+ if (uids == null) {
+ return;
+ }
+ TimeSparseArray<SparseBooleanArray> wakingActivity = mWakingActivity.get(subsystem);
+ if (wakingActivity == null) {
+ wakingActivity = new TimeSparseArray<>();
+ mWakingActivity.put(subsystem, wakingActivity);
+ }
+ SparseBooleanArray uidsToBlame = wakingActivity.get(elapsedRealtime);
+ if (uidsToBlame == null) {
+ uidsToBlame = new SparseBooleanArray(uids.length);
+ wakingActivity.put(elapsedRealtime, uidsToBlame);
+ }
+ for (int i = 0; i < uids.length; i++) {
+ uidsToBlame.put(uids[i], true);
+ }
+ // Limit activity history per subsystem to the last WAKING_ACTIVITY_RETENTION_MS.
+ // Note that the last activity is always present, even if it occurred before
+ // WAKING_ACTIVITY_RETENTION_MS.
+ final int endIdx = wakingActivity.closestIndexOnOrBefore(
+ elapsedRealtime - WAKING_ACTIVITY_RETENTION_MS);
+ for (int i = endIdx; i >= 0; i--) {
+ wakingActivity.removeAt(endIdx);
+ }
+ }
+
+ void clearAllBefore(long elapsedRealtime) {
+ for (int subsystemIdx = mWakingActivity.size() - 1; subsystemIdx >= 0; subsystemIdx--) {
+ final TimeSparseArray<SparseBooleanArray> activityPerSubsystem =
+ mWakingActivity.valueAt(subsystemIdx);
+ final int endIdx = activityPerSubsystem.closestIndexOnOrBefore(elapsedRealtime);
+ for (int removeIdx = endIdx; removeIdx >= 0; removeIdx--) {
+ activityPerSubsystem.removeAt(removeIdx);
+ }
+ // Generally waking activity is a high frequency occurrence for any subsystem, so we
+ // don't delete the TimeSparseArray even if it is now empty, to avoid object churn.
+ // This will leave one TimeSparseArray per subsystem, which are few right now.
+ }
+ }
+
+ SparseBooleanArray removeBetween(int subsystem, long startElapsed, long endElapsed) {
+ final SparseBooleanArray uidsToReturn = new SparseBooleanArray();
+
+ final TimeSparseArray<SparseBooleanArray> activityForSubsystem =
+ mWakingActivity.get(subsystem);
+ if (activityForSubsystem != null) {
+ final int startIdx = activityForSubsystem.closestIndexOnOrAfter(startElapsed);
+ final int endIdx = activityForSubsystem.closestIndexOnOrBefore(endElapsed);
+ for (int i = endIdx; i >= startIdx; i--) {
+ final SparseBooleanArray uidsForTime = activityForSubsystem.valueAt(i);
+ for (int j = 0; j < uidsForTime.size(); j++) {
+ if (uidsForTime.valueAt(j)) {
+ uidsToReturn.put(uidsForTime.keyAt(j), true);
+ }
+ }
+ }
+ // More efficient to remove in a separate loop as it avoids repeatedly calling gc().
+ for (int i = endIdx; i >= startIdx; i--) {
+ activityForSubsystem.removeAt(i);
+ }
+ // Generally waking activity is a high frequency occurrence for any subsystem, so we
+ // don't delete the TimeSparseArray even if it is now empty, to avoid object churn.
+ // This will leave one TimeSparseArray per subsystem, which are few right now.
+ }
+ return uidsToReturn.size() > 0 ? uidsToReturn : null;
+ }
+
+ void dump(IndentingPrintWriter pw, long nowElapsed) {
+ pw.println("Recent waking activity:");
+ pw.increaseIndent();
+ for (int i = 0; i < mWakingActivity.size(); i++) {
+ pw.println("Subsystem " + subsystemToString(mWakingActivity.keyAt(i)) + ":");
+ final LongSparseArray<SparseBooleanArray> wakingActivity =
+ mWakingActivity.valueAt(i);
+ if (wakingActivity == null) {
+ continue;
+ }
+ pw.increaseIndent();
+ for (int j = wakingActivity.size() - 1; j >= 0; j--) {
+ TimeUtils.formatDuration(wakingActivity.keyAt(j), nowElapsed, pw);
+ final SparseBooleanArray uidsToBlame = wakingActivity.valueAt(j);
+ if (uidsToBlame == null) {
+ pw.println();
+ continue;
+ }
+ pw.print(": ");
+ for (int k = 0; k < uidsToBlame.size(); k++) {
+ if (uidsToBlame.valueAt(k)) {
+ UserHandle.formatUid(pw, uidsToBlame.keyAt(k));
+ pw.print(", ");
+ }
+ }
+ pw.println();
+ }
+ pw.decreaseIndent();
+ }
+ pw.decreaseIndent();
+ }
+ }
+
+ private SparseBooleanArray getResponsibleSubsystemsForWakeup(Wakeup wakeup) {
+ if (ArrayUtils.isEmpty(wakeup.mDevices)) {
+ return null;
+ }
+ final SparseBooleanArray result = new SparseBooleanArray();
+ for (final Wakeup.IrqDevice device : wakeup.mDevices) {
+ final List<String> rawSubsystems = mIrqDeviceMap.getSubsystemsForDevice(device.mDevice);
+
+ boolean anyKnownSubsystem = false;
+ if (rawSubsystems != null) {
+ for (int i = 0; i < rawSubsystems.size(); i++) {
+ final int subsystem = stringToKnownSubsystem(rawSubsystems.get(i));
+ if (subsystem != CPU_WAKEUP_SUBSYSTEM_UNKNOWN) {
+ // Just in case the xml had arbitrary subsystem names, we want to make sure
+ // that we only put the known ones into our attribution map.
+ result.put(subsystem, true);
+ anyKnownSubsystem = true;
+ }
+ }
+ }
+ if (!anyKnownSubsystem) {
+ result.put(CPU_WAKEUP_SUBSYSTEM_UNKNOWN, true);
+ }
+ }
+ return result;
+ }
+
+ static int stringToKnownSubsystem(String rawSubsystem) {
+ switch (rawSubsystem) {
+ case SUBSYSTEM_ALARM_STRING:
+ return CPU_WAKEUP_SUBSYSTEM_ALARM;
+ }
+ return CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
+ }
+
+ static String subsystemToString(int subsystem) {
+ switch (subsystem) {
+ case CPU_WAKEUP_SUBSYSTEM_ALARM:
+ return SUBSYSTEM_ALARM_STRING;
+ case CPU_WAKEUP_SUBSYSTEM_UNKNOWN:
+ return "Unknown";
+ }
+ return "N/A";
+ }
+
+ private static final class Wakeup {
+ private static final String PARSER_TAG = "CpuWakeupStats.Wakeup";
+ private static final String ABORT_REASON_PREFIX = "Abort";
+ private static final Pattern sIrqPattern = Pattern.compile("(\\d+)\\s+(\\S+)");
+
+ String mRawReason;
+ long mElapsedMillis;
+ long mUptimeMillis;
+ IrqDevice[] mDevices;
+
+ Wakeup(String rawReason, long elapsedMillis, long uptimeMillis) {
+ mRawReason = rawReason;
+ mElapsedMillis = elapsedMillis;
+ mUptimeMillis = uptimeMillis;
+ mDevices = parseIrqDevices(rawReason);
+ }
+
+ private static IrqDevice[] parseIrqDevices(String rawReason) {
+ final String[] components = rawReason.split(":");
+ if (ArrayUtils.isEmpty(components) || components[0].startsWith(ABORT_REASON_PREFIX)) {
+ // We don't support parsing aborts yet.
+ return null;
+ }
+
+ int parsedDeviceCount = 0;
+ IrqDevice[] parsedDevices = new IrqDevice[components.length];
+
+ for (String component : components) {
+ final Matcher matcher = sIrqPattern.matcher(component);
+ if (matcher.find()) {
+ final int line;
+ final String device;
+ try {
+ line = Integer.parseInt(matcher.group(1));
+ device = matcher.group(2);
+ } catch (NumberFormatException e) {
+ Slog.e(PARSER_TAG,
+ "Exception while parsing device names from part: " + component, e);
+ continue;
+ }
+ parsedDevices[parsedDeviceCount++] = new IrqDevice(line, device);
+ }
+ }
+ return (parsedDeviceCount > 0) ? Arrays.copyOf(parsedDevices, parsedDeviceCount) : null;
+ }
+
+ @Override
+ public String toString() {
+ return "Wakeup{"
+ + "mRawReason='" + mRawReason + '\''
+ + ", mElapsedMillis=" + mElapsedMillis
+ + ", mUptimeMillis=" + TimeUtils.formatDuration(mUptimeMillis)
+ + ", mDevices=" + Arrays.toString(mDevices)
+ + '}';
+ }
+
+ static final class IrqDevice {
+ int mLine;
+ String mDevice;
+
+ IrqDevice(int line, String device) {
+ mLine = line;
+ mDevice = device;
+ }
+
+ @Override
+ public String toString() {
+ return "IrqDevice{" + "mLine=" + mLine + ", mDevice='" + mDevice + '\'' + '}';
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/IrqDeviceMap.java b/services/core/java/com/android/server/power/stats/IrqDeviceMap.java
new file mode 100644
index 0000000..091d18e
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/IrqDeviceMap.java
@@ -0,0 +1,137 @@
+/*
+ * 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.power.stats;
+
+import android.annotation.XmlRes;
+import android.content.Context;
+import android.content.res.XmlResourceParser;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.LongSparseArray;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Parses irq_device_map.xml to store a mapping of devices that can send IRQs to the CPU to
+ * subsystems that represent some logical work happening on the device that could need an IRQ.
+ */
+public class IrqDeviceMap {
+ private static final String TAG_IRQ_DEVICE_MAP = "irq-device-map";
+ private static final String TAG_DEVICE = "device";
+ private static final String TAG_SUBSYSTEM = "subsystem";
+ private static final String ATTR_NAME = "name";
+
+ private static LongSparseArray<IrqDeviceMap> sInstanceMap = new LongSparseArray<>(1);
+
+ private final ArrayMap<String, List<String>> mSubsystemsForDevice = new ArrayMap();
+
+ private IrqDeviceMap(XmlResourceParser parser) {
+ try {
+ XmlUtils.beginDocument(parser, TAG_IRQ_DEVICE_MAP);
+
+ int type;
+ String currentDevice = null;
+ final ArraySet<String> subsystems = new ArraySet<>();
+
+ while ((type = parser.getEventType()) != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_DEVICE)) {
+ currentDevice = parser.getAttributeValue(null, ATTR_NAME);
+ }
+ if (currentDevice != null && type == XmlPullParser.END_TAG
+ && parser.getName().equals(TAG_DEVICE)) {
+ final int n = subsystems.size();
+ if (n > 0) {
+ mSubsystemsForDevice.put(currentDevice,
+ Collections.unmodifiableList(new ArrayList<>(subsystems)));
+ }
+ subsystems.clear();
+ currentDevice = null;
+ }
+ if (currentDevice != null && type == XmlPullParser.START_TAG
+ && parser.getName().equals(TAG_SUBSYSTEM)) {
+ parser.next();
+ if (parser.getEventType() == XmlPullParser.TEXT) {
+ subsystems.add(parser.getText());
+ }
+ }
+ parser.next();
+ }
+ } catch (XmlPullParserException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ parser.close();
+ }
+ }
+
+ /**
+ * Returns an instance of IrqDeviceMap initialzed with the given context and xml resource.
+ * The xml resource should describe the mapping in a way similar to
+ * core/res/res/xml/irq_device_map.xml.
+ */
+ public static IrqDeviceMap getInstance(Context context, @XmlRes int resId) {
+ synchronized (IrqDeviceMap.class) {
+ final int idx = sInstanceMap.indexOfKey(resId);
+ if (idx >= 0) {
+ return sInstanceMap.valueAt(idx);
+ }
+ }
+ final XmlResourceParser parser = context.getResources().getXml(resId);
+ final IrqDeviceMap irqDeviceMap = new IrqDeviceMap(parser);
+ synchronized (IrqDeviceMap.class) {
+ sInstanceMap.put(resId, irqDeviceMap);
+ }
+ return irqDeviceMap;
+ }
+
+ List<String> getSubsystemsForDevice(String device) {
+ return mSubsystemsForDevice.get(device);
+ }
+
+ void dump(IndentingPrintWriter pw) {
+ pw.println("Irq device map:");
+ pw.increaseIndent();
+
+ final LongSparseArray<IrqDeviceMap> instanceMap;
+ synchronized (IrqDeviceMap.class) {
+ instanceMap = sInstanceMap;
+ }
+ final int idx = instanceMap.indexOfValue(this);
+ final String res = (idx >= 0) ? ("0x" + Long.toHexString(instanceMap.keyAt(idx))) : null;
+ pw.println("Loaded from xml resource: " + res);
+
+ pw.println("Map:");
+ pw.increaseIndent();
+ for (int i = 0; i < mSubsystemsForDevice.size(); i++) {
+ pw.print(mSubsystemsForDevice.keyAt(i) + ": ");
+ pw.println(mSubsystemsForDevice.valueAt(i));
+ }
+ pw.decreaseIndent();
+
+ pw.decreaseIndent();
+ }
+}
diff --git a/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java b/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
index fd6ec06..f744d00 100644
--- a/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
+++ b/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
@@ -46,6 +46,8 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedListener,
SensorEventListener {
@@ -275,7 +277,7 @@
public void onSensorChanged(SensorEvent event) {
// Using log space to represent human sensation (Fechner's Law) instead of lux
// because lux values causes bright flashes to skew the average very high.
- addElement(event.timestamp, Math.max(0,
+ addElement(TimeUnit.NANOSECONDS.toMillis(event.timestamp), Math.max(0,
(int) (Math.log(event.values[0]) * LIGHT_VALUE_MULTIPLIER)));
updateLightSession();
mHandler.removeCallbacksAndMessages(mDelayedUpdateToken);
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index c524bc4..f52f0b7 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -1047,6 +1047,29 @@
}
@Override
+ public void notifyRecordingStarted(IBinder sessionToken, String recordingId, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+ "notifyRecordingStarted");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).notifyRecordingStarted(recordingId);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in notifyRecordingStarted", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ }
+
+ @Override
public void startInteractiveApp(IBinder sessionToken, int userId) {
if (DEBUG) {
Slogf.d(TAG, "BinderService#start(userId=%d)", userId);
@@ -2213,6 +2236,23 @@
}
@Override
+ public void onRequestStartRecording(Uri programUri) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestStartRecording: " + programUri);
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestStartRecording(programUri, mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestStartRecording", e);
+ }
+ }
+ }
+
+ @Override
public void onRequestSigning(String id, String algorithm, String alias, byte[] data) {
synchronized (mLock) {
if (DEBUG) {
diff --git a/services/core/java/com/android/server/utils/EventLogger.java b/services/core/java/com/android/server/utils/EventLogger.java
index 321edc5..45b4ff8 100644
--- a/services/core/java/com/android/server/utils/EventLogger.java
+++ b/services/core/java/com/android/server/utils/EventLogger.java
@@ -25,20 +25,78 @@
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
+import java.util.Locale;
+/**
+ * Logs human-readable events for debugging purposes.
+ */
public class EventLogger {
- // ring buffer of events to log.
+ /** Identifies the source of events. */
+ private final String mTag;
+
+ /** Stores the events using a ring buffer. */
private final LinkedList<Event> mEvents;
- private final String mTitle;
-
- // the maximum number of events to keep in log
+ /**
+ * The maximum number of events to keep in {@code mEvents}.
+ *
+ * <p>Calling {@link #log} when the size of {@link #mEvents} matches the threshold will
+ * cause the oldest event to be evicted.
+ */
private final int mMemSize;
- public static abstract class Event {
- // formatter for timestamps
- private final static SimpleDateFormat sFormat = new SimpleDateFormat("MM-dd HH:mm:ss:SSS");
+ /**
+ * Constructor for logger.
+ * @param size the maximum number of events to keep in log
+ * @param tag the string displayed before the recorded log
+ */
+ public EventLogger(int size, String tag) {
+ mEvents = new LinkedList<Event>();
+ mMemSize = size;
+ mTag = tag;
+ }
+
+ public synchronized void log(Event evt) {
+ if (mEvents.size() >= mMemSize) {
+ mEvents.removeFirst();
+ }
+ mEvents.add(evt);
+ }
+
+ /**
+ * Add a string-based event to the log, and print it to logcat as info.
+ * @param msg the message for the logs
+ * @param tag the logcat tag to use
+ */
+ public synchronized void loglogi(String msg, String tag) {
+ final Event event = new StringEvent(msg);
+ log(event.printLog(tag));
+ }
+
+ /**
+ * Same as {@link #loglogi(String, String)} but specifying the logcat type
+ * @param msg the message for the logs
+ * @param logType the type of logcat entry
+ * @param tag the logcat tag to use
+ */
+ public synchronized void loglog(String msg, @Event.LogType int logType, String tag) {
+ final Event event = new StringEvent(msg);
+ log(event.printLog(logType, tag));
+ }
+
+ public synchronized void dump(PrintWriter pw) {
+ pw.println("Events log: " + mTag);
+ for (Event evt : mEvents) {
+ pw.println(evt.toString());
+ }
+ }
+
+ public abstract static class Event {
+
+ /** Timestamps formatter. */
+ private static final SimpleDateFormat sFormat =
+ new SimpleDateFormat("MM-dd HH:mm:ss:SSS", Locale.US);
private final long mTimestamp;
@@ -114,7 +172,7 @@
* Timestamp information will be automatically added, do not include it.
* @return a string representation of the event that occurred.
*/
- abstract public String eventToString();
+ public abstract String eventToString();
}
public static class StringEvent extends Event {
@@ -129,50 +187,4 @@
return mMsg;
}
}
-
- /**
- * Constructor for logger.
- * @param size the maximum number of events to keep in log
- * @param title the string displayed before the recorded log
- */
- public EventLogger(int size, String title) {
- mEvents = new LinkedList<Event>();
- mMemSize = size;
- mTitle = title;
- }
-
- public synchronized void log(Event evt) {
- if (mEvents.size() >= mMemSize) {
- mEvents.removeFirst();
- }
- mEvents.add(evt);
- }
-
- /**
- * Add a string-based event to the log, and print it to logcat as info.
- * @param msg the message for the logs
- * @param tag the logcat tag to use
- */
- public synchronized void loglogi(String msg, String tag) {
- final Event event = new StringEvent(msg);
- log(event.printLog(tag));
- }
-
- /**
- * Same as {@link #loglogi(String, String)} but specifying the logcat type
- * @param msg the message for the logs
- * @param logType the type of logcat entry
- * @param tag the logcat tag to use
- */
- public synchronized void loglog(String msg, @Event.LogType int logType, String tag) {
- final Event event = new StringEvent(msg);
- log(event.printLog(logType, tag));
- }
-
- public synchronized void dump(PrintWriter pw) {
- pw.println("Audio event log: " + mTitle);
- for (Event evt : mEvents) {
- pw.println(evt.toString());
- }
- }
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ce7794d..78b657b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -256,6 +256,7 @@
import android.app.WaitResult;
import android.app.WindowConfiguration;
import android.app.admin.DevicePolicyManager;
+import android.app.assist.ActivityId;
import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ActivityRelaunchItem;
@@ -5585,7 +5586,9 @@
LocalServices.getService(ContentCaptureManagerInternal.class);
if (contentCaptureService != null) {
contentCaptureService.notifyActivityEvent(mUserId, mActivityComponent,
- ActivityEvent.TYPE_ACTIVITY_STARTED);
+ ActivityEvent.TYPE_ACTIVITY_STARTED,
+ new ActivityId(getTask() != null ? getTask().mTaskId : INVALID_TASK_ID,
+ shareableActivityToken));
}
break;
case PAUSED:
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 7d23cca..416d546 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -152,6 +152,7 @@
import android.app.ProfilerInfo;
import android.app.WaitResult;
import android.app.admin.DevicePolicyCache;
+import android.app.assist.ActivityId;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
import android.app.compat.CompatChanges;
@@ -4859,17 +4860,19 @@
void updateActivityUsageStats(ActivityRecord activity, int event) {
ComponentName taskRoot = null;
+ int taskId = INVALID_TASK_ID;
final Task task = activity.getTask();
if (task != null) {
final ActivityRecord rootActivity = task.getRootActivity();
if (rootActivity != null) {
taskRoot = rootActivity.mActivityComponent;
}
+ taskId = task.mTaskId;
}
-
final Message m = PooledLambda.obtainMessage(
ActivityManagerInternal::updateActivityUsageStats, mAmInternal,
- activity.mActivityComponent, activity.mUserId, event, activity.token, taskRoot);
+ activity.mActivityComponent, activity.mUserId, event, activity.token, taskRoot,
+ new ActivityId(taskId, activity.shareableActivityToken));
mH.sendMessage(m);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index b473700..214a2c1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1481,7 +1481,7 @@
handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED,
mRootWindowContainer.getDefaultTaskDisplayArea(), currentRootTask,
forceNonResizeable);
- if (r != null) {
+ if (r != null && (options == null || !options.getDisableStartingWindow())) {
// Use a starting window to reduce the transition latency for reshowing the task.
// Note that with shell transition, this should be executed before requesting
// transition to avoid delaying the starting window.
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
new file mode 100644
index 0000000..5e44d6c
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.util.DisplayMetrics.DENSITY_DEFAULT;
+
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.annotation.Nullable;
+import android.app.ActivityOptions;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
+
+/**
+ * The class that defines default launch params for tasks in desktop mode
+ */
+public class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
+
+ private static final String TAG =
+ TAG_WITH_CLASS_NAME ? "DesktopModeLaunchParamsModifier" : TAG_ATM;
+ private static final boolean DEBUG = false;
+
+ // Desktop mode feature flag.
+ static final boolean DESKTOP_MODE_SUPPORTED = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_mode", false);
+ // Override default freeform task width when desktop mode is enabled. In dips.
+ private static final int DESKTOP_MODE_DEFAULT_WIDTH_DP = SystemProperties.getInt(
+ "persist.wm.debug.desktop_mode.default_width", 840);
+ // Override default freeform task height when desktop mode is enabled. In dips.
+ private static final int DESKTOP_MODE_DEFAULT_HEIGHT_DP = SystemProperties.getInt(
+ "persist.wm.debug.desktop_mode.default_height", 630);
+
+ private StringBuilder mLogBuilder;
+
+ @Override
+ public int onCalculate(@Nullable Task task, @Nullable ActivityInfo.WindowLayout layout,
+ @Nullable ActivityRecord activity, @Nullable ActivityRecord source,
+ @Nullable ActivityOptions options, @Nullable ActivityStarter.Request request, int phase,
+ LaunchParamsController.LaunchParams currentParams,
+ LaunchParamsController.LaunchParams outParams) {
+
+ initLogBuilder(task, activity);
+ int result = calculate(task, layout, activity, source, options, request, phase,
+ currentParams, outParams);
+ outputLog();
+ return result;
+ }
+
+ private int calculate(@Nullable Task task, @Nullable ActivityInfo.WindowLayout layout,
+ @Nullable ActivityRecord activity, @Nullable ActivityRecord source,
+ @Nullable ActivityOptions options, @Nullable ActivityStarter.Request request, int phase,
+ LaunchParamsController.LaunchParams currentParams,
+ LaunchParamsController.LaunchParams outParams) {
+
+ if (task == null) {
+ appendLog("task null, skipping");
+ return RESULT_SKIP;
+ }
+ if (phase != PHASE_BOUNDS) {
+ appendLog("not in bounds phase, skipping");
+ return RESULT_SKIP;
+ }
+ if (!task.inFreeformWindowingMode()) {
+ appendLog("not a freeform task, skipping");
+ return RESULT_SKIP;
+ }
+ if (!currentParams.mBounds.isEmpty()) {
+ appendLog("currentParams has bounds set, not overriding");
+ return RESULT_SKIP;
+ }
+
+ // Copy over any values
+ outParams.set(currentParams);
+
+ // Update width and height with default desktop mode values
+ float density = (float) task.getConfiguration().densityDpi / DENSITY_DEFAULT;
+ final int width = (int) (DESKTOP_MODE_DEFAULT_WIDTH_DP * density + 0.5f);
+ final int height = (int) (DESKTOP_MODE_DEFAULT_HEIGHT_DP * density + 0.5f);
+ outParams.mBounds.right = width;
+ outParams.mBounds.bottom = height;
+
+ // Center the task in window bounds
+ Rect windowBounds = task.getWindowConfiguration().getBounds();
+ outParams.mBounds.offset(windowBounds.centerX() - outParams.mBounds.centerX(),
+ windowBounds.centerY() - outParams.mBounds.centerY());
+
+ appendLog("setting desktop mode task bounds to %s", outParams.mBounds);
+
+ return RESULT_DONE;
+ }
+
+ private void initLogBuilder(Task task, ActivityRecord activity) {
+ if (DEBUG) {
+ mLogBuilder = new StringBuilder(
+ "DesktopModeLaunchParamsModifier: task=" + task + " activity=" + activity);
+ }
+ }
+
+ private void appendLog(String format, Object... args) {
+ if (DEBUG) mLogBuilder.append(" ").append(String.format(format, args));
+ }
+
+ private void outputLog() {
+ if (DEBUG) Slog.d(TAG, mLogBuilder.toString());
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index b91d943..442777a 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -28,8 +28,6 @@
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
-import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -118,7 +116,6 @@
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
-import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.InsetsFlags;
@@ -1150,24 +1147,6 @@
break;
case TYPE_STATUS_BAR:
mStatusBar = win;
- final TriConsumer<DisplayFrames, WindowContainer, Rect> gestureFrameProvider =
- (displayFrames, windowContainer, rect) -> {
- rect.bottom = rect.top + getStatusBarHeight(displayFrames);
- final DisplayCutout cutout =
- displayFrames.mInsetsState.getDisplayCutout();
- if (cutout != null) {
- final Rect top = cutout.getBoundingRectTop();
- if (!top.isEmpty()) {
- rect.bottom = Math.max(rect.bottom,
- top.bottom + mDisplayCutoutTouchableRegionSize);
- }
- }
- };
- mDisplayContent.setInsetProvider(ITYPE_STATUS_BAR, win, null);
- mDisplayContent.setInsetProvider(
- ITYPE_TOP_MANDATORY_GESTURES, win, gestureFrameProvider);
- mDisplayContent.setInsetProvider(ITYPE_TOP_TAPPABLE_ELEMENT, win, null);
- mInsetsSourceWindowsExceptIme.add(win);
break;
case TYPE_NAVIGATION_BAR:
mNavigationBar = win;
@@ -1185,7 +1164,8 @@
displayFrames.mUnrestricted,
win.getBounds(), displayFrames.mDisplayCutoutSafe,
inOutFrame, provider.source,
- provider.insetsSize, lp.privateFlags);
+ provider.insetsSize, lp.privateFlags,
+ provider.minimalInsetsSizeInDisplayCutoutSafe);
}
}
inOutFrame.inset(win.mGivenContentInsets);
@@ -1231,85 +1211,91 @@
mInsetsSourceWindowsExceptIme.add(win);
if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar);
break;
- default:
- if (attrs.providedInsets != null) {
- for (int i = attrs.providedInsets.length - 1; i >= 0; i--) {
- final InsetsFrameProvider provider = attrs.providedInsets[i];
- switch (provider.type) {
- case ITYPE_STATUS_BAR:
- mStatusBarAlt = win;
- mStatusBarAltPosition = getAltBarPosition(attrs);
- break;
- case ITYPE_NAVIGATION_BAR:
- mNavigationBarAlt = win;
- mNavigationBarAltPosition = getAltBarPosition(attrs);
- break;
- case ITYPE_CLIMATE_BAR:
- mClimateBarAlt = win;
- mClimateBarAltPosition = getAltBarPosition(attrs);
- break;
- case ITYPE_EXTRA_NAVIGATION_BAR:
- mExtraNavBarAlt = win;
- mExtraNavBarAltPosition = getAltBarPosition(attrs);
- break;
+ }
+ // TODO(b/239145252): Temporarily skip the navigation bar as it is still with the hard-coded
+ // logic.
+ if (attrs.providedInsets != null && attrs.type != TYPE_NAVIGATION_BAR) {
+ for (int i = attrs.providedInsets.length - 1; i >= 0; i--) {
+ final InsetsFrameProvider provider = attrs.providedInsets[i];
+ switch (provider.type) {
+ case ITYPE_STATUS_BAR:
+ if (attrs.type != TYPE_STATUS_BAR) {
+ mStatusBarAlt = win;
+ mStatusBarAltPosition = getAltBarPosition(attrs);
}
- // The index of the provider and corresponding insets types cannot change at
- // runtime as ensured in WMS. Make use of the index in the provider directly
- // to access the latest provided size at runtime.
- final int index = i;
- final TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider =
- provider.insetsSize != null
- ? (displayFrames, windowContainer, inOutFrame) -> {
- inOutFrame.inset(win.mGivenContentInsets);
+ break;
+ case ITYPE_NAVIGATION_BAR:
+ if (attrs.type != TYPE_NAVIGATION_BAR) {
+ mNavigationBarAlt = win;
+ mNavigationBarAltPosition = getAltBarPosition(attrs);
+ }
+ break;
+ case ITYPE_CLIMATE_BAR:
+ mClimateBarAlt = win;
+ mClimateBarAltPosition = getAltBarPosition(attrs);
+ break;
+ case ITYPE_EXTRA_NAVIGATION_BAR:
+ mExtraNavBarAlt = win;
+ mExtraNavBarAltPosition = getAltBarPosition(attrs);
+ break;
+ }
+ // The index of the provider and corresponding insets types cannot change at
+ // runtime as ensured in WMS. Make use of the index in the provider directly
+ // to access the latest provided size at runtime.
+ final int index = i;
+ final TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider =
+ provider.insetsSize != null
+ ? (displayFrames, windowContainer, inOutFrame) -> {
+ inOutFrame.inset(win.mGivenContentInsets);
+ final LayoutParams lp =
+ win.mAttrs.forRotation(displayFrames.mRotation);
+ final InsetsFrameProvider ifp =
+ win.mAttrs.forRotation(displayFrames.mRotation)
+ .providedInsets[index];
+ InsetsFrameProvider.calculateInsetsFrame(
+ displayFrames.mUnrestricted,
+ windowContainer.getBounds(),
+ displayFrames.mDisplayCutoutSafe,
+ inOutFrame, ifp.source,
+ ifp.insetsSize, lp.privateFlags,
+ ifp.minimalInsetsSizeInDisplayCutoutSafe);
+ } : null;
+ final InsetsFrameProvider.InsetsSizeOverride[] overrides =
+ provider.insetsSizeOverrides;
+ final SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>>
+ overrideProviders;
+ if (overrides != null) {
+ overrideProviders = new SparseArray<>();
+ for (int j = overrides.length - 1; j >= 0; j--) {
+ final int overrideIndex = j;
+ final TriConsumer<DisplayFrames, WindowContainer, Rect>
+ overrideFrameProvider =
+ (displayFrames, windowContainer, inOutFrame) -> {
final LayoutParams lp =
- win.mAttrs.forRotation(displayFrames.mRotation);
+ win.mAttrs.forRotation(
+ displayFrames.mRotation);
final InsetsFrameProvider ifp =
- win.mAttrs.forRotation(displayFrames.mRotation)
- .providedInsets[index];
+ win.mAttrs.providedInsets[index];
InsetsFrameProvider.calculateInsetsFrame(
displayFrames.mUnrestricted,
windowContainer.getBounds(),
displayFrames.mDisplayCutoutSafe,
inOutFrame, ifp.source,
- ifp.insetsSize, lp.privateFlags);
- } : null;
- final InsetsFrameProvider.InsetsSizeOverride[] overrides =
- provider.insetsSizeOverrides;
- final SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>>
- overrideProviders;
- if (overrides != null) {
- overrideProviders = new SparseArray<>();
- for (int j = overrides.length - 1; j >= 0; j--) {
- final int overrideIndex = j;
- final TriConsumer<DisplayFrames, WindowContainer, Rect>
- overrideFrameProvider =
- (displayFrames, windowContainer, inOutFrame) -> {
- final LayoutParams lp =
- win.mAttrs.forRotation(
- displayFrames.mRotation);
- final InsetsFrameProvider ifp =
- win.mAttrs.providedInsets[index];
- InsetsFrameProvider.calculateInsetsFrame(
- displayFrames.mUnrestricted,
- windowContainer.getBounds(),
- displayFrames.mDisplayCutoutSafe,
- inOutFrame, ifp.source,
- ifp.insetsSizeOverrides[
- overrideIndex].insetsSize,
- lp.privateFlags);
- };
- overrideProviders.put(overrides[j].windowType,
- overrideFrameProvider);
- }
- } else {
- overrideProviders = null;
- }
- mDisplayContent.setInsetProvider(provider.type, win, frameProvider,
- overrideProviders);
- mInsetsSourceWindowsExceptIme.add(win);
+ ifp.insetsSizeOverrides[
+ overrideIndex].insetsSize,
+ lp.privateFlags,
+ null);
+ };
+ overrideProviders.put(overrides[j].windowType,
+ overrideFrameProvider);
}
+ } else {
+ overrideProviders = null;
}
- break;
+ mDisplayContent.setInsetProvider(provider.type, win, frameProvider,
+ overrideProviders);
+ mInsetsSourceWindowsExceptIme.add(win);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index 7bd2a4a..e74e5787 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -64,6 +64,10 @@
void registerDefaultModifiers(ActivityTaskSupervisor supervisor) {
// {@link TaskLaunchParamsModifier} handles window layout preferences.
registerModifier(new TaskLaunchParamsModifier(supervisor));
+ if (DesktopModeLaunchParamsModifier.DESKTOP_MODE_SUPPORTED) {
+ // {@link DesktopModeLaunchParamsModifier} handles default task size changes
+ registerModifier(new DesktopModeLaunchParamsModifier());
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index a638784..92f7efd 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -265,7 +265,7 @@
ActivityOptions options, int callingPid, int callingUid) {
// If a launch task id is specified, then ensure that the caller is the recents
// component or has the START_TASKS_FROM_RECENTS permission
- if (options.getLaunchTaskId() != INVALID_TASK_ID
+ if ((options.getLaunchTaskId() != INVALID_TASK_ID || options.getDisableStartingWindow())
&& !supervisor.mRecentTasks.isCallerRecents(callingUid)) {
final int startInTaskPerm = ActivityTaskManagerService.checkPermission(
START_TASKS_FROM_RECENTS, callingPid, callingUid);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 155cf28..885968f 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5058,6 +5058,9 @@
== ActivityOptions.ANIM_SCENE_TRANSITION) {
doShow = false;
}
+ if (options != null && options.getDisableStartingWindow()) {
+ doShow = false;
+ }
if (r.mLaunchTaskBehind) {
// Don't do a starting window for mLaunchTaskBehind. More importantly make sure we
// tell WindowManager that r is visible even though it is at the back of the root
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 0f5184a..36389ea 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3868,8 +3868,8 @@
// configuration update when the window has requested to be hidden. Doing so can lead to
// the client erroneously accepting a configuration that would have otherwise caused an
// activity restart. We instead hand back the last reported {@link MergedConfiguration}.
- if (useLatestConfig || (relayoutVisible && (!shouldCheckTokenVisibleRequested()
- || mToken.isVisibleRequested()))) {
+ if (useLatestConfig || (relayoutVisible && (mActivityRecord == null
+ || mActivityRecord.mVisibleRequested))) {
final Configuration globalConfig = getProcessGlobalConfiguration();
final Configuration overrideConfig = getMergedOverrideConfiguration();
outMergedConfiguration.setConfiguration(globalConfig, overrideConfig);
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 98e5f1d..b914051 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -383,6 +383,7 @@
</xs:complexType>
<xs:complexType name="autoBrightness">
+ <xs:attribute name="enabled" type="xs:boolean" use="optional" default="true"/>
<xs:sequence>
<!-- Sets the debounce for autoBrightness brightening in millis-->
<xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 748ef4b..d89bd7c 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -6,9 +6,11 @@
method public final java.math.BigInteger getBrighteningLightDebounceMillis();
method public final java.math.BigInteger getDarkeningLightDebounceMillis();
method public final com.android.server.display.config.DisplayBrightnessMapping getDisplayBrightnessMapping();
+ method public boolean getEnabled();
method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
method public final void setDisplayBrightnessMapping(com.android.server.display.config.DisplayBrightnessMapping);
+ method public void setEnabled(boolean);
}
public class BrightnessThresholds {
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java
new file mode 100644
index 0000000..50996d7
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.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.server.display;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DisplayBrightnessStateTest {
+ private static final float FLOAT_DELTA = 0.001f;
+
+ private DisplayBrightnessState.Builder mDisplayBrightnessStateBuilder;
+
+ @Before
+ public void before() {
+ mDisplayBrightnessStateBuilder = new DisplayBrightnessState.Builder();
+ }
+
+ @Test
+ public void validateAllDisplayBrightnessStateFieldsAreSetAsExpected() {
+ float brightness = 0.3f;
+ float sdrBrightness = 0.2f;
+ BrightnessReason brightnessReason = new BrightnessReason();
+ brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC);
+ brightnessReason.setModifier(BrightnessReason.MODIFIER_DIMMED);
+ DisplayBrightnessState displayBrightnessState =
+ mDisplayBrightnessStateBuilder.setBrightness(brightness).setSdrBrightness(
+ sdrBrightness).setBrightnessReason(brightnessReason).build();
+
+ assertEquals(displayBrightnessState.getBrightness(), brightness, FLOAT_DELTA);
+ assertEquals(displayBrightnessState.getSdrBrightness(), sdrBrightness, FLOAT_DELTA);
+ assertEquals(displayBrightnessState.getBrightnessReason(), brightnessReason);
+ assertEquals(displayBrightnessState.toString(), getString(displayBrightnessState));
+ }
+
+ private String getString(DisplayBrightnessState displayBrightnessState) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("DisplayBrightnessState:");
+ sb.append("\n brightness:" + displayBrightnessState.getBrightness());
+ sb.append("\n sdrBrightness:" + displayBrightnessState.getSdrBrightness());
+ sb.append("\n brightnessReason:" + displayBrightnessState.getBrightnessReason());
+ return sb.toString();
+ }
+}
diff --git a/services/tests/servicestests/res/xml/irq_device_map_1.xml b/services/tests/servicestests/res/xml/irq_device_map_1.xml
new file mode 100644
index 0000000..1f1a77b
--- /dev/null
+++ b/services/tests/servicestests/res/xml/irq_device_map_1.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** 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.
+*/
+-->
+<irq-device-map>
+ <device name="test.device.1">
+ <subsystem>test.subsystem.1</subsystem>
+ <subsystem>test.subsystem.2</subsystem>
+ </device>
+ <device name="test.device.2">
+ <subsystem>test.subsystem.3</subsystem>
+ <subsystem>test.subsystem.2</subsystem>
+ </device>
+</irq-device-map>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/irq_device_map_2.xml b/services/tests/servicestests/res/xml/irq_device_map_2.xml
new file mode 100644
index 0000000..508c98d
--- /dev/null
+++ b/services/tests/servicestests/res/xml/irq_device_map_2.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** 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.
+*/
+-->
+
+<!-- Long comment describing anything that may be needed
+for this file and its maintenance -->
+<irq-device-map>
+ <!-- Small comment specific to this device -->
+ <invalid name="test.device.1">
+ <!-- These valid subsystem definitions should be ignored because of invalid parent tag -->
+ <subsystem>test.subsystem.1</subsystem>
+ <subsystem>test.subsystem.2</subsystem>
+ </invalid>
+ <device name="test.device.2">
+ <!-- Multiline comment to describe nuances
+ about why these subsystems
+ rely on this hardware device
+ to wake the CPU up from sleep
+ -->
+ <subsystem>test.subsystem.3</subsystem>
+ <!-- Small comment specific to test.subsystem.4 -->
+ <subsystem>test.subsystem.4</subsystem>
+ <subsystem>test.subsystem.5</subsystem>
+ <!-- Duplicates should be ignored -->
+ <subsystem>test.subsystem.4</subsystem>
+ <subsystem>test.subsystem.3</subsystem>
+ <subsystem>test.subsystem.5</subsystem>
+ </device>
+
+ <device name="test.device.3">
+ <!-- All child tags are invalid, mapping should be empty / non-existent for this device -->
+ <subsys>ignored</subsys>
+ <system>redundant</system>
+ </device>
+
+ <device name="test.device.4">
+ <!-- Invalid child tags should be skipped but others should be mapped -->
+ <invalid>unused</invalid>
+ <random>skipped</random>
+ <subsystem>test.subsystem.1</subsystem>
+ <subsystem>test.subsystem.4</subsystem>
+ </device>
+
+</irq-device-map>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/irq_device_map_3.xml b/services/tests/servicestests/res/xml/irq_device_map_3.xml
new file mode 100644
index 0000000..498b676
--- /dev/null
+++ b/services/tests/servicestests/res/xml/irq_device_map_3.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** 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.
+*/
+-->
+<irq-device-map>
+ <device name="test.alarm.device">
+ <subsystem>Alarm</subsystem>
+ </device>
+ <device name="test.wifi.device">
+ <subsystem>undefined</subsystem>
+ </device>
+</irq-device-map>
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 2d2c76c..0b776a3 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -181,11 +181,8 @@
doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt());
mockIsUsersOnSecondaryDisplaysEnabled(false);
// All UserController params are set to default.
- mUserController = new UserController(mInjector);
- // TODO(b/232452368): need to explicitly call setAllowUserUnlocking(), otherwise most
- // tests would fail. But we might need to disable it for the onBootComplete() test (i.e,
- // to make sure the users are unlocked at the right time)
+ mUserController = new UserController(mInjector);
mUserController.setAllowUserUnlocking(true);
setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
setUpUser(TEST_PRE_CREATED_USER_ID, NO_USERINFO_FLAGS, /* preCreated= */ true, null);
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 6388c7d..9c5d1a5 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
@@ -226,7 +226,7 @@
mContext.getSystemService(WindowManager.class), threadVerifier);
mAssociationInfo = new AssociationInfo(1, 0, null,
- MacAddress.BROADCAST_ADDRESS, "", null, true, false, false, 0, 0);
+ MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0);
mVdms = new VirtualDeviceManagerService(mContext);
mLocalService = mVdms.getLocalServiceInstance();
diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
index 5f3f3d7..65076a3 100644
--- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -33,6 +33,7 @@
import android.view.InputDevice
import androidx.test.InstrumentationRegistry
import com.android.server.input.BatteryController.UEventManager
+import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.MatcherAssert.assertThat
@@ -260,25 +261,28 @@
@Test
fun testListenersNotifiedOnUEventNotification() {
- `when`(native.getBatteryDevicePath(DEVICE_ID)).thenReturn("/test/device1")
+ `when`(native.getBatteryDevicePath(DEVICE_ID)).thenReturn("/sys/dev/test/device1")
`when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING)
`when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
val listener = createMockListener()
- val uEventListener = ArgumentCaptor.forClass(UEventManager.UEventListener::class.java)
+ val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
- verify(uEventManager).addListener(uEventListener.capture(), eq("DEVPATH=/test/device1"))
+ // The device paths for UEvent notifications do not include the "/sys" prefix, so verify
+ // that the added listener is configured to match the path without that prefix.
+ verify(uEventManager)
+ .addListener(uEventListener.capture(), eq("DEVPATH=/dev/test/device1"))
listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)
// If the battery state has changed when an UEvent is sent, the listeners are notified.
`when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80)
- uEventListener.value!!.onUEvent(TIMESTAMP)
+ uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.80f,
eventTime = TIMESTAMP)
// If the battery state has not changed when an UEvent is sent, the listeners are not
// notified.
clearInvocations(listener)
- uEventListener.value!!.onUEvent(TIMESTAMP + 1)
+ uEventListener.value!!.onBatteryUEvent(TIMESTAMP + 1)
verifyNoMoreInteractions(listener)
batteryController.unregisterBatteryListener(DEVICE_ID, listener, PID)
@@ -293,7 +297,7 @@
`when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING)
`when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
val listener = createMockListener()
- val uEventListener = ArgumentCaptor.forClass(UEventManager.UEventListener::class.java)
+ val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
verify(uEventManager).addListener(uEventListener.capture(), eq("DEVPATH=/test/device1"))
listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 5e6cccc..1e67c12 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -75,15 +75,34 @@
}
@Test
- public void testLskfBasedProtector() throws RemoteException {
+ public void testNoneLskfBasedProtector() throws RemoteException {
+ final int USER_ID = 10;
+ MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mContext, mStorage,
+ mGateKeeperService, mUserManager, mPasswordSlotManager);
+ SyntheticPassword sp = manager.newSyntheticPassword(USER_ID);
+ assertFalse(lskfGatekeeperHandleExists(USER_ID));
+ long protectorId = manager.createLskfBasedProtector(mGateKeeperService,
+ LockscreenCredential.createNone(), sp, USER_ID);
+ assertFalse(lskfGatekeeperHandleExists(USER_ID));
+
+ AuthenticationResult result = manager.unlockLskfBasedProtector(mGateKeeperService,
+ protectorId, LockscreenCredential.createNone(), USER_ID, null);
+ assertArrayEquals(result.syntheticPassword.deriveKeyStorePassword(),
+ sp.deriveKeyStorePassword());
+ }
+
+ @Test
+ public void testNonNoneLskfBasedProtector() throws RemoteException {
final int USER_ID = 10;
final LockscreenCredential password = newPassword("user-password");
final LockscreenCredential badPassword = newPassword("bad-password");
MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mContext, mStorage,
mGateKeeperService, mUserManager, mPasswordSlotManager);
SyntheticPassword sp = manager.newSyntheticPassword(USER_ID);
+ assertFalse(lskfGatekeeperHandleExists(USER_ID));
long protectorId = manager.createLskfBasedProtector(mGateKeeperService, password, sp,
USER_ID);
+ assertTrue(lskfGatekeeperHandleExists(USER_ID));
AuthenticationResult result = manager.unlockLskfBasedProtector(mGateKeeperService,
protectorId, password, USER_ID, null);
@@ -95,6 +114,10 @@
assertNull(result.syntheticPassword);
}
+ private boolean lskfGatekeeperHandleExists(int userId) throws RemoteException {
+ return mGateKeeperService.getSecureUserId(SyntheticPasswordManager.fakeUserId(userId)) != 0;
+ }
+
private boolean hasSyntheticPassword(int userId) throws RemoteException {
return mService.getLong(CURRENT_LSKF_BASED_PROTECTOR_ID_KEY, 0, userId) != 0;
}
@@ -430,6 +453,21 @@
}
@Test
+ public void testPasswordData_scryptParams() {
+ // CREDENTIAL_TYPE_NONE should result in the minimum scrypt params being used.
+ PasswordData data = PasswordData.create(CREDENTIAL_TYPE_NONE);
+ assertEquals(1, data.scryptLogN);
+ assertEquals(0, data.scryptLogR);
+ assertEquals(0, data.scryptLogP);
+
+ // Any other credential type should result in the real scrypt params being used.
+ data = PasswordData.create(CREDENTIAL_TYPE_PASSWORD);
+ assertTrue(data.scryptLogN > 1);
+ assertTrue(data.scryptLogR > 0);
+ assertTrue(data.scryptLogP > 0);
+ }
+
+ @Test
public void testPasswordData_serializeDeserialize() {
PasswordData data = new PasswordData();
data.scryptLogN = 11;
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index 27c3ca4..a545b1f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -325,6 +325,7 @@
actual.getSessionErrorMessage());
assertEquals(expected.isPrepared(), actual.isPrepared());
assertEquals(expected.isCommitted(), actual.isCommitted());
+ assertEquals(expected.isPreapprovalRequested(), actual.isPreapprovalRequested());
assertEquals(expected.createdMillis, actual.createdMillis);
assertEquals(expected.isSealed(), actual.isSealed());
assertEquals(expected.isMultiPackage(), actual.isMultiPackage());
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
new file mode 100644
index 0000000..7731a32
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.power.stats;
+
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM;
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
+
+import static com.android.server.power.stats.CpuWakeupStats.WAKEUP_REASON_HALF_WINDOW_MS;
+import static com.android.server.power.stats.CpuWakeupStats.WAKEUP_RETENTION_MS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.frameworks.servicestests.R;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ThreadLocalRandom;
+
+@RunWith(AndroidJUnit4.class)
+public class CpuWakeupStatsTest {
+ private static final String KERNEL_REASON_ALARM_IRQ = "120 test.alarm.device";
+ private static final String KERNEL_REASON_UNKNOWN_IRQ = "140 test.unknown.device";
+ private static final String KERNEL_REASON_UNKNOWN = "unsupported-free-form-reason";
+
+ private static final int TEST_UID_1 = 13239823;
+ private static final int TEST_UID_2 = 25268423;
+ private static final int TEST_UID_3 = 92261423;
+ private static final int TEST_UID_4 = 56926423;
+ private static final int TEST_UID_5 = 76421423;
+
+ private static final Context sContext = InstrumentationRegistry.getTargetContext();
+ private final ThreadLocalRandom mRandom = ThreadLocalRandom.current();
+
+ @Test
+ public void removesOldWakeups() {
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_1);
+
+ final Set<Long> timestamps = new HashSet<>();
+ final long firstWakeup = 453192;
+
+ obj.noteWakeupTimeAndReason(firstWakeup, 32, "unused");
+ timestamps.add(firstWakeup);
+ for (int i = 1; i < 1000; i++) {
+ final long delta = mRandom.nextLong(WAKEUP_RETENTION_MS);
+ if (timestamps.add(firstWakeup + delta)) {
+ obj.noteWakeupTimeAndReason(firstWakeup + delta, i, "unused");
+ }
+ }
+ assertThat(obj.mWakeupEvents.size()).isEqualTo(timestamps.size());
+
+ obj.noteWakeupTimeAndReason(firstWakeup + WAKEUP_RETENTION_MS + 1242, 231, "unused");
+ assertThat(obj.mWakeupEvents.size()).isEqualTo(timestamps.size());
+
+ for (int i = 0; i < 100; i++) {
+ final long now = mRandom.nextLong(WAKEUP_RETENTION_MS + 1, 100 * WAKEUP_RETENTION_MS);
+ obj.noteWakeupTimeAndReason(now, i, "unused");
+ assertThat(obj.mWakeupEvents.closestIndexOnOrBefore(now - WAKEUP_RETENTION_MS))
+ .isLessThan(0);
+ }
+ }
+
+ @Test
+ public void alarmIrqAttributionSolo() {
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+ final long wakeupTime = 12423121;
+
+ obj.noteWakeupTimeAndReason(wakeupTime, 1, KERNEL_REASON_ALARM_IRQ);
+
+ // Outside the window, so should be ignored.
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM,
+ wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_1);
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM,
+ wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_2);
+ // Should be attributed
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3, TEST_UID_5);
+
+ final SparseArray<SparseBooleanArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+ assertThat(attribution).isNotNull();
+ assertThat(attribution.size()).isEqualTo(1);
+ assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_ALARM)).isTrue();
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_1)).isEqualTo(false);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_2)).isEqualTo(false);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_3)).isEqualTo(true);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_5)).isEqualTo(true);
+ }
+
+ @Test
+ public void alarmIrqAttributionCombined() {
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+ final long wakeupTime = 92123210;
+
+ obj.noteWakeupTimeAndReason(wakeupTime, 4,
+ KERNEL_REASON_UNKNOWN_IRQ + ":" + KERNEL_REASON_ALARM_IRQ);
+
+ // Outside the window, so should be ignored.
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM,
+ wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_1);
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM,
+ wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_2);
+ // Should be attributed
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4,
+ TEST_UID_5);
+
+ final SparseArray<SparseBooleanArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+ assertThat(attribution).isNotNull();
+ assertThat(attribution.size()).isEqualTo(2);
+ assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_ALARM)).isTrue();
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_1)).isEqualTo(false);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_2)).isEqualTo(false);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_3)).isEqualTo(true);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_4)).isEqualTo(true);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_5)).isEqualTo(true);
+ assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isTrue();
+ }
+
+ @Test
+ public void unknownIrqAttribution() {
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+ final long wakeupTime = 92123410;
+
+ obj.noteWakeupTimeAndReason(wakeupTime, 24, KERNEL_REASON_UNKNOWN_IRQ);
+
+ // Unrelated subsystems, should not be attributed
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4,
+ TEST_UID_5);
+
+ final SparseArray<SparseBooleanArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+ assertThat(attribution).isNotNull();
+ assertThat(attribution.size()).isEqualTo(1);
+ assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isTrue();
+ final SparseBooleanArray uids = attribution.get(CPU_WAKEUP_SUBSYSTEM_UNKNOWN);
+ assertThat(uids == null || uids.size() == 0).isTrue();
+ }
+
+ @Test
+ public void unknownAttribution() {
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+ final long wakeupTime = 72123210;
+
+ obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNKNOWN);
+
+ // Unrelated subsystems, should be ignored.
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4);
+
+ // There should be nothing in the attribution map.
+ assertThat(obj.mWakeupAttribution.size()).isEqualTo(0);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/IrqDeviceMapTest.java b/services/tests/servicestests/src/com/android/server/power/stats/IrqDeviceMapTest.java
new file mode 100644
index 0000000..43d9e60
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/stats/IrqDeviceMapTest.java
@@ -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.
+ */
+
+package com.android.server.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.frameworks.servicestests.R;
+import com.android.internal.util.CollectionUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class IrqDeviceMapTest {
+ private static final String TEST_DEVICE_1 = "test.device.1";
+ private static final String TEST_DEVICE_2 = "test.device.2";
+ private static final String TEST_DEVICE_3 = "test.device.3";
+ private static final String TEST_DEVICE_4 = "test.device.4";
+
+ private static final String TEST_SUBSYSTEM_1 = "test.subsystem.1";
+ private static final String TEST_SUBSYSTEM_2 = "test.subsystem.2";
+ private static final String TEST_SUBSYSTEM_3 = "test.subsystem.3";
+ private static final String TEST_SUBSYSTEM_4 = "test.subsystem.4";
+ private static final String TEST_SUBSYSTEM_5 = "test.subsystem.5";
+
+ private static final Context sContext = InstrumentationRegistry.getTargetContext();
+
+ @Test
+ public void cachesInstancesPerXml() {
+ IrqDeviceMap irqDeviceMap1 = IrqDeviceMap.getInstance(sContext, R.xml.irq_device_map_1);
+ IrqDeviceMap irqDeviceMap2 = IrqDeviceMap.getInstance(sContext, R.xml.irq_device_map_1);
+ assertThat(irqDeviceMap1).isSameInstanceAs(irqDeviceMap2);
+
+ irqDeviceMap2 = IrqDeviceMap.getInstance(sContext, R.xml.irq_device_map_2);
+ assertThat(irqDeviceMap1).isNotSameInstanceAs(irqDeviceMap2);
+
+ irqDeviceMap1 = IrqDeviceMap.getInstance(sContext, R.xml.irq_device_map_2);
+ assertThat(irqDeviceMap1).isSameInstanceAs(irqDeviceMap2);
+ }
+
+ @Test
+ public void simpleXml() {
+ IrqDeviceMap deviceMap = IrqDeviceMap.getInstance(sContext, R.xml.irq_device_map_1);
+
+ List<String> subsystems = deviceMap.getSubsystemsForDevice(TEST_DEVICE_1);
+ assertThat(subsystems).hasSize(2);
+ // No specific order is required.
+ assertThat(subsystems).containsExactly(TEST_SUBSYSTEM_2, TEST_SUBSYSTEM_1);
+
+ subsystems = deviceMap.getSubsystemsForDevice(TEST_DEVICE_2);
+ assertThat(subsystems).hasSize(2);
+ // No specific order is required.
+ assertThat(subsystems).containsExactly(TEST_SUBSYSTEM_2, TEST_SUBSYSTEM_3);
+ }
+
+ @Test
+ public void complexXml() {
+ IrqDeviceMap deviceMap = IrqDeviceMap.getInstance(sContext, R.xml.irq_device_map_2);
+
+ List<String> subsystems = deviceMap.getSubsystemsForDevice(TEST_DEVICE_1);
+ assertThat(CollectionUtils.isEmpty(subsystems)).isTrue();
+
+ subsystems = deviceMap.getSubsystemsForDevice(TEST_DEVICE_2);
+ assertThat(subsystems).hasSize(3);
+ // No specific order is required.
+ assertThat(subsystems).containsExactly(TEST_SUBSYSTEM_3, TEST_SUBSYSTEM_4,
+ TEST_SUBSYSTEM_5);
+
+ subsystems = deviceMap.getSubsystemsForDevice(TEST_DEVICE_3);
+ assertThat(CollectionUtils.isEmpty(subsystems)).isTrue();
+
+ subsystems = deviceMap.getSubsystemsForDevice(TEST_DEVICE_4);
+ assertThat(subsystems).hasSize(2);
+ // No specific order is required.
+ assertThat(subsystems).containsExactly(TEST_SUBSYSTEM_4, TEST_SUBSYSTEM_1);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
new file mode 100644
index 0000000..7830e90
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.util.DisplayMetrics.DENSITY_DEFAULT;
+
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wm.LaunchParamsController.LaunchParamsModifier.Result;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for desktop mode task bounds.
+ *
+ * Build/Install/Run:
+ * atest WmTests:DesktopModeLaunchParamsModifierTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase {
+
+ private ActivityRecord mActivity;
+
+ private DesktopModeLaunchParamsModifier mTarget;
+
+ private LaunchParamsController.LaunchParams mCurrent;
+ private LaunchParamsController.LaunchParams mResult;
+
+ @Before
+ public void setUp() throws Exception {
+ mActivity = new ActivityBuilder(mAtm).build();
+ mTarget = new DesktopModeLaunchParamsModifier();
+ mCurrent = new LaunchParamsController.LaunchParams();
+ mCurrent.reset();
+ mResult = new LaunchParamsController.LaunchParams();
+ mResult.reset();
+ }
+
+ @Test
+ public void testReturnsSkipIfTaskIsNull() {
+ assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(null).calculate());
+ }
+
+ @Test
+ public void testReturnsSkipIfNotBoundsPhase() {
+ final Task task = new TaskBuilder(mSupervisor).build();
+ assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(task).setPhase(
+ PHASE_DISPLAY).calculate());
+ }
+
+ @Test
+ public void testReturnsSkipIfTaskNotInFreeform() {
+ final Task task = new TaskBuilder(mSupervisor).setWindowingMode(
+ WINDOWING_MODE_FULLSCREEN).build();
+ assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(task).calculate());
+ }
+
+ @Test
+ public void testReturnsSkipIfCurrentParamsHasBounds() {
+ final Task task = new TaskBuilder(mSupervisor).setWindowingMode(
+ WINDOWING_MODE_FREEFORM).build();
+ mCurrent.mBounds.set(/* left */ 0, /* top */ 0, /* right */ 100, /* bottom */ 100);
+ assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(task).calculate());
+ }
+
+ @Test
+ public void testUsesDefaultBounds() {
+ final Task task = new TaskBuilder(mSupervisor).setWindowingMode(
+ WINDOWING_MODE_FREEFORM).build();
+ assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(dpiToPx(task, 840), mResult.mBounds.width());
+ assertEquals(dpiToPx(task, 630), mResult.mBounds.height());
+ }
+
+ @Test
+ public void testUsesDisplayAreaAndWindowingModeFromSource() {
+ final Task task = new TaskBuilder(mSupervisor).setWindowingMode(
+ WINDOWING_MODE_FREEFORM).build();
+ TaskDisplayArea mockTaskDisplayArea = mock(TaskDisplayArea.class);
+ mCurrent.mPreferredTaskDisplayArea = mockTaskDisplayArea;
+ mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
+
+ assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(mockTaskDisplayArea, mResult.mPreferredTaskDisplayArea);
+ assertEquals(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode);
+ }
+
+ private int dpiToPx(Task task, int dpi) {
+ float density = (float) task.getConfiguration().densityDpi / DENSITY_DEFAULT;
+ return (int) (dpi * density + 0.5f);
+ }
+
+ private class CalculateRequestBuilder {
+ private Task mTask;
+ private int mPhase = PHASE_BOUNDS;
+ private final ActivityRecord mActivity =
+ DesktopModeLaunchParamsModifierTests.this.mActivity;
+ private final LaunchParamsController.LaunchParams mCurrentParams = mCurrent;
+ private final LaunchParamsController.LaunchParams mOutParams = mResult;
+
+ private CalculateRequestBuilder setTask(Task task) {
+ mTask = task;
+ return this;
+ }
+
+ private CalculateRequestBuilder setPhase(int phase) {
+ mPhase = phase;
+ return this;
+ }
+
+ @Result
+ private int calculate() {
+ return mTarget.onCalculate(mTask, /* layout*/ null, mActivity, /* source */
+ null, /* options */ null, /* request */ null, mPhase, mCurrentParams,
+ mOutParams);
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index 041f298..f2bc47d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -21,6 +21,8 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
+import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
@@ -45,6 +47,7 @@
import android.app.StatusBarManager;
import android.platform.test.annotations.Presubmit;
+import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
@@ -70,7 +73,7 @@
@Test
public void testControlsForDispatch_regular() {
- addWindow(TYPE_STATUS_BAR, "statusBar");
+ addStatusBar();
addWindow(TYPE_NAVIGATION_BAR, "navBar");
final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -82,7 +85,7 @@
@Test
public void testControlsForDispatch_multiWindowTaskVisible() {
- addWindow(TYPE_STATUS_BAR, "statusBar");
+ addStatusBar();
addWindow(TYPE_NAVIGATION_BAR, "navBar");
final WindowState win = createWindow(null, WINDOWING_MODE_MULTI_WINDOW,
@@ -95,7 +98,7 @@
@Test
public void testControlsForDispatch_freeformTaskVisible() {
- addWindow(TYPE_STATUS_BAR, "statusBar");
+ addStatusBar();
addWindow(TYPE_NAVIGATION_BAR, "navBar");
final WindowState win = createWindow(null, WINDOWING_MODE_FREEFORM,
@@ -108,8 +111,7 @@
@Test
public void testControlsForDispatch_forceStatusBarVisible() {
- addWindow(TYPE_STATUS_BAR, "statusBar").mAttrs.privateFlags |=
- PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
+ addStatusBar().mAttrs.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
addWindow(TYPE_NAVIGATION_BAR, "navBar");
final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -123,7 +125,7 @@
public void testControlsForDispatch_statusBarForceShowNavigation() {
addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade").mAttrs.privateFlags |=
PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
- addWindow(TYPE_STATUS_BAR, "statusBar");
+ addStatusBar();
addWindow(TYPE_NAVIGATION_BAR, "navBar");
final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -152,7 +154,7 @@
public void testControlsForDispatch_remoteInsetsControllerControlsBars_appHasNoControl() {
mDisplayContent.setRemoteInsetsController(createDisplayWindowInsetsController());
mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(true);
- addWindow(TYPE_STATUS_BAR, "statusBar");
+ addStatusBar();
addWindow(TYPE_NAVIGATION_BAR, "navBar");
final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -163,7 +165,7 @@
@Test
public void testControlsForDispatch_topAppHidesStatusBar() {
- addWindow(TYPE_STATUS_BAR, "statusBar");
+ addStatusBar();
addWindow(TYPE_NAVIGATION_BAR, "navBar");
// Add a fullscreen (MATCH_PARENT x MATCH_PARENT) app window which hides status bar.
@@ -256,7 +258,7 @@
@SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testShowTransientBars_bothCanBeTransient_appGetsBothFakeControls() {
- final WindowState statusBar = addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar");
+ final WindowState statusBar = addStatusBar();
statusBar.setHasSurface(true);
statusBar.getControllableInsetProvider().setServerVisible(true);
final WindowState navBar = addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar");
@@ -298,8 +300,7 @@
@SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testShowTransientBars_statusBarCanBeTransient_appGetsStatusBarFakeControl() {
- addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
- .getControllableInsetProvider().getSource().setVisible(false);
+ addStatusBar().getControllableInsetProvider().getSource().setVisible(false);
addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar")
.getControllableInsetProvider().setServerVisible(true);
@@ -328,8 +329,8 @@
@SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testAbortTransientBars_bothCanBeAborted_appGetsBothRealControls() {
- final InsetsSource statusBarSource = addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
- .getControllableInsetProvider().getSource();
+ final InsetsSource statusBarSource =
+ addStatusBar().getControllableInsetProvider().getSource();
final InsetsSource navBarSource = addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar")
.getControllableInsetProvider().getSource();
statusBarSource.setVisible(false);
@@ -381,8 +382,7 @@
@Test
public void testShowTransientBars_abortsWhenControlTargetChanges() {
- addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
- .getControllableInsetProvider().getSource().setVisible(false);
+ addStatusBar().getControllableInsetProvider().getSource().setVisible(false);
addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar")
.getControllableInsetProvider().getSource().setVisible(false);
final WindowState app = addWindow(TYPE_APPLICATION, "app");
@@ -406,6 +406,18 @@
return win;
}
+ private WindowState addStatusBar() {
+ final WindowState win = createWindow(null, TYPE_STATUS_BAR, "statusBar");
+ win.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
+ win.mAttrs.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(ITYPE_STATUS_BAR),
+ new InsetsFrameProvider(ITYPE_TOP_TAPPABLE_ELEMENT),
+ new InsetsFrameProvider(ITYPE_TOP_MANDATORY_GESTURES)
+ };
+ mDisplayContent.getDisplayPolicy().addWindowLw(win, win.mAttrs);
+ return win;
+ }
+
private WindowState addWindow(int type, String name) {
final WindowState win = createWindow(null, type, name);
mDisplayContent.getDisplayPolicy().addWindowLw(win, win.mAttrs);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 54b33e97..d59fce0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -26,6 +26,8 @@
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
+import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
@@ -84,6 +86,7 @@
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
+import android.view.InsetsFrameProvider;
import android.view.InsetsVisibilities;
import android.view.WindowManager;
@@ -3079,6 +3082,11 @@
attrs.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
attrs.setFitInsetsTypes(0 /* types */);
+ attrs.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(ITYPE_STATUS_BAR),
+ new InsetsFrameProvider(ITYPE_TOP_TAPPABLE_ELEMENT),
+ new InsetsFrameProvider(ITYPE_TOP_MANDATORY_GESTURES)
+ };
final TestWindowState statusBar = new TestWindowState(
displayContent.mWmService, mock(Session.class), new TestIWindow(), attrs, token);
token.addWindow(statusBar);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 8370691..cf24ff2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -217,6 +217,12 @@
mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0,
outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
assertEquals(0, outConfig.getMergedConfiguration().densityDpi);
+ // Non activity window can still get the last config.
+ win.mActivityRecord = null;
+ win.fillClientWindowFramesAndConfiguration(outFrames, outConfig,
+ false /* useLatestConfig */, true /* relayoutVisible */);
+ assertEquals(win.getConfiguration().densityDpi,
+ outConfig.getMergedConfiguration().densityDpi);
}
@Test
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 c597b46..40326e9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -27,6 +27,9 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.os.Process.SYSTEM_UID;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
+import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
@@ -324,6 +327,11 @@
mStatusBarWindow.mAttrs.layoutInDisplayCutoutMode =
LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
mStatusBarWindow.mAttrs.setFitInsetsTypes(0);
+ mStatusBarWindow.mAttrs.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(ITYPE_STATUS_BAR),
+ new InsetsFrameProvider(ITYPE_TOP_TAPPABLE_ELEMENT),
+ new InsetsFrameProvider(ITYPE_TOP_MANDATORY_GESTURES)
+ };
}
if (addAll || ArrayUtils.contains(requestedWindows, W_NOTIFICATION_SHADE)) {
mNotificationShadeWindow = createCommonWindow(null, TYPE_NOTIFICATION_SHADE,
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 79546b8..151ff80 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1242,6 +1242,19 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION)
@Override
public void updateState(
+ @Nullable PersistableBundle options,
+ @Nullable SharedMemory sharedMemory) {
+ synchronized (this) {
+ enforceIsCurrentVoiceInteractionService();
+
+ Binder.withCleanCallingIdentity(
+ () -> mImpl.updateStateLocked(options, sharedMemory));
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION)
+ @Override
+ public void initAndVerifyDetector(
@NonNull Identity voiceInteractorIdentity,
@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
@@ -1250,21 +1263,12 @@
synchronized (this) {
enforceIsCurrentVoiceInteractionService();
- if (mImpl == null) {
- Slog.w(TAG, "updateState without running voice interaction service");
- return;
- }
-
voiceInteractorIdentity.uid = Binder.getCallingUid();
voiceInteractorIdentity.pid = Binder.getCallingPid();
- final long caller = Binder.clearCallingIdentity();
- try {
- mImpl.updateStateLocked(
- voiceInteractorIdentity, options, sharedMemory, callback, detectorType);
- } finally {
- Binder.restoreCallingIdentity(caller);
- }
+ Binder.withCleanCallingIdentity(
+ () -> mImpl.initAndVerifyDetectorLocked(voiceInteractorIdentity, options,
+ sharedMemory, callback, detectorType));
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index dcf7b78..5d1901d 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -551,12 +551,31 @@
}
public void updateStateLocked(
+ @Nullable PersistableBundle options,
+ @Nullable SharedMemory sharedMemory) {
+ Slog.v(TAG, "updateStateLocked");
+
+ if (sharedMemory != null && !sharedMemory.setProtect(OsConstants.PROT_READ)) {
+ Slog.w(TAG, "Can't set sharedMemory to be read-only");
+ throw new IllegalStateException("Can't set sharedMemory to be read-only");
+ }
+
+ if (mHotwordDetectionConnection == null) {
+ Slog.w(TAG, "update State, but no hotword detection connection");
+ throw new IllegalStateException("Hotword detection connection not found");
+ }
+ synchronized (mHotwordDetectionConnection.mLock) {
+ mHotwordDetectionConnection.updateStateLocked(options, sharedMemory);
+ }
+ }
+
+ public void initAndVerifyDetectorLocked(
@NonNull Identity voiceInteractorIdentity,
@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
IHotwordRecognitionStatusCallback callback,
int detectorType) {
- Slog.v(TAG, "updateStateLocked");
+ Slog.v(TAG, "initAndVerifyDetectorLocked");
int voiceInteractionServiceUid = mInfo.getServiceInfo().applicationInfo.uid;
if (mHotwordDetectionComponentName == null) {
Slog.w(TAG, "Hotword detection service name not found");
@@ -614,8 +633,6 @@
mInfo.getServiceInfo().applicationInfo.uid, voiceInteractorIdentity,
mHotwordDetectionComponentName, mUser, /* bindInstantServiceAllowed= */ false,
options, sharedMemory, callback, detectorType);
- } else {
- mHotwordDetectionConnection.updateStateLocked(options, sharedMemory);
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index a27b2da..f810fbb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -16,7 +16,7 @@
package com.android.server.wm.flicker.ime
-import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
@@ -47,6 +47,7 @@
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup {
+ tapl.workspace.switchToOverview().dismissAllTasks()
testApp.launchViaIntent(wmHelper)
testApp.openIME(wmHelper)
this.setRotation(testSpec.startRotation)
@@ -61,7 +62,7 @@
}
/** {@inheritDoc} */
- @FlakyTest(bugId = 251214932)
+ @Presubmit
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
// depends on how much of the animation transactions are sent to SF at once
@@ -79,23 +80,23 @@
}
/** {@inheritDoc} */
- @FlakyTest(bugId = 251214932)
+ @Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
val component = ComponentNameMatcher("", "RecentTaskScreenshotSurface")
testSpec.assertWm {
this.visibleWindowsShownMoreThanOneConsecutiveEntry(
ignoreWindows =
- listOf(
- ComponentNameMatcher.SPLASH_SCREEN,
- ComponentNameMatcher.SNAPSHOT,
- component
- )
+ listOf(
+ ComponentNameMatcher.SPLASH_SCREEN,
+ ComponentNameMatcher.SNAPSHOT,
+ component
+ )
)
}
}
- @FlakyTest(bugId = 251214932)
+ @Presubmit
@Test
fun launcherWindowBecomesInvisible() {
testSpec.assertWm {
@@ -105,11 +106,11 @@
}
}
- @FlakyTest(bugId = 251214932)
+ @Presubmit
@Test
fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible()
- @FlakyTest(bugId = 251214932)
+ @Presubmit
@Test
fun imeAppWindowIsAlwaysVisible() {
// the app starts visible in live tile, and stays visible for the duration of entering
@@ -119,13 +120,13 @@
testSpec.assertWm { this.isAppWindowVisible(testApp) }
}
- @FlakyTest(bugId = 251214932)
+ @Presubmit
@Test
fun imeLayerBecomesVisible() {
testSpec.assertLayers { this.isVisible(ComponentNameMatcher.IME) }
}
- @FlakyTest(bugId = 251214932)
+ @Presubmit
@Test
fun appLayerReplacesLauncher() {
testSpec.assertLayers {
@@ -137,60 +138,6 @@
}
}
- @FlakyTest(bugId = 251214932)
- @Test
- override fun navBarLayerPositionAtStartAndEnd() {
- super.navBarLayerPositionAtStartAndEnd()
- }
-
- @FlakyTest(bugId = 251214932)
- @Test
- override fun navBarWindowIsAlwaysVisible() {
- super.navBarWindowIsAlwaysVisible()
- }
-
- @FlakyTest(bugId = 251214932)
- @Test
- override fun statusBarLayerIsVisibleAtStartAndEnd() {
- super.statusBarLayerIsVisibleAtStartAndEnd()
- }
-
- @FlakyTest(bugId = 251214932)
- @Test
- override fun entireScreenCovered() {
- super.entireScreenCovered()
- }
-
- @FlakyTest(bugId = 251214932)
- @Test
- override fun navBarLayerIsVisibleAtStartAndEnd() {
- super.navBarLayerIsVisibleAtStartAndEnd()
- }
-
- @FlakyTest(bugId = 251214932)
- @Test
- override fun statusBarLayerPositionAtStartAndEnd() {
- super.statusBarLayerPositionAtStartAndEnd()
- }
-
- @FlakyTest(bugId = 251214932)
- @Test
- override fun statusBarWindowIsAlwaysVisible() {
- super.statusBarWindowIsAlwaysVisible()
- }
-
- @FlakyTest(bugId = 251214932)
- @Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() {
- super.taskBarLayerIsVisibleAtStartAndEnd()
- }
-
- @FlakyTest(bugId = 251214932)
- @Test
- override fun taskBarWindowIsAlwaysVisible() {
- super.taskBarWindowIsAlwaysVisible()
- }
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
index 7ccfeb7..73e6d22 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
@@ -16,7 +16,7 @@
package com.android.server.wm.flicker.launch
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.RequiresDevice
import android.view.Surface
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -81,87 +81,101 @@
}
/** {@inheritDoc} */
- @Postsubmit @Test override fun appWindowAsTopWindowAtEnd() = super.appWindowAsTopWindowAtEnd()
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowAsTopWindowAtEnd() = super.appWindowAsTopWindowAtEnd()
/** {@inheritDoc} */
- @Postsubmit
+ @FlakyTest(bugId = 240916028)
@Test
override fun appWindowReplacesLauncherAsTopWindow() =
super.appWindowReplacesLauncherAsTopWindow()
/** {@inheritDoc} */
- @Postsubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
/** {@inheritDoc} */
- @Postsubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
/** {@inheritDoc} */
- @Postsubmit @Test override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
/** {@inheritDoc} */
- @Postsubmit @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
/** {@inheritDoc} */
- @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
- @Postsubmit @Test override fun focusChanges() = super.focusChanges()
+ @FlakyTest(bugId = 240916028) @Test override fun focusChanges() = super.focusChanges()
/** {@inheritDoc} */
- @Postsubmit
+ @FlakyTest(bugId = 240916028)
@Test
override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
- @Postsubmit
+ @FlakyTest(bugId = 240916028)
@Test
override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
- @Postsubmit
+ @FlakyTest(bugId = 240916028)
@Test
override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
- @Postsubmit
+ @FlakyTest(bugId = 240916028)
@Test
override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
- @Postsubmit
+ @FlakyTest(bugId = 240916028)
@Test
override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
- @Postsubmit
+ @FlakyTest(bugId = 240916028)
@Test
override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
- @Postsubmit
+ @FlakyTest(bugId = 240916028)
@Test
override fun statusBarLayerIsVisibleAtStartAndEnd() =
super.statusBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
- @Postsubmit
+ @FlakyTest(bugId = 240916028)
@Test
override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
- @Postsubmit
+ @FlakyTest(bugId = 240916028)
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
super.visibleLayersShownMoreThanOneConsecutiveEntry()
/** {@inheritDoc} */
- @Postsubmit
+ @FlakyTest(bugId = 240916028)
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
/** {@inheritDoc} */
- @Postsubmit @Test override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd()
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd()
companion object {
/**
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index 46186bc..bc1f0d1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -16,7 +16,6 @@
package com.android.server.wm.flicker.quickswitch
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
import android.view.Surface
@@ -104,7 +103,7 @@
* Checks that the transition starts with [testApp2]'s layers filling/covering exactly the
* entirety of the display.
*/
- @FlakyTest(bugId = 250520840)
+ @Presubmit
@Test
open fun startsWithApp2LayersCoverFullScreen() {
testSpec.assertLayersStart {
@@ -236,15 +235,6 @@
}
}
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
-
- @FlakyTest(bugId = 250518877)
- @Test
- override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
companion object {
private var startDisplayBounds = Rect.EMPTY
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
index 7a1350e..f988bb2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
@@ -23,6 +23,7 @@
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
+import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -61,8 +62,8 @@
override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/**
- * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the
- * start and end of the WM trace
+ * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at
+ * the start and end of the WM trace
*/
@Presubmit
@Test
@@ -71,8 +72,18 @@
testSpec.navBarWindowIsVisibleAtStartAndEnd()
}
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 250520840)
+ @Test
+ override fun startsWithApp2LayersCoverFullScreen() =
+ super.startsWithApp2LayersCoverFullScreen()
+
@FlakyTest(bugId = 246284708)
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @FlakyTest(bugId = 250518877)
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 0a21044..7e4504b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -16,7 +16,6 @@
package com.android.server.wm.flicker.quickswitch
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
import android.view.Surface
@@ -116,7 +115,7 @@
* Checks that the transition starts with [testApp1]'s layers filling/covering exactly the
* entirety of the display.
*/
- @FlakyTest(bugId = 250522691)
+ @Presubmit
@Test
open fun startsWithApp1LayersCoverFullScreen() {
testSpec.assertLayersStart {
@@ -255,10 +254,6 @@
@Test
override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
- @FlakyTest(bugId = 250518877)
- @Test
- override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
companion object {
private var startDisplayBounds = Rect.EMPTY
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
index 03647c9..cc954ab 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
@@ -23,6 +23,7 @@
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
+import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -62,8 +63,8 @@
override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/**
- * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the
- * start and end of the WM trace
+ * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at
+ * the start and end of the WM trace
*/
@Presubmit
@Test
@@ -76,4 +77,13 @@
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @FlakyTest(bugId = 250518877)
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+ @FlakyTest(bugId = 250522691)
+ @Test
+ override fun startsWithApp1LayersCoverFullScreen() =
+ super.startsWithApp1LayersCoverFullScreen()
}
diff --git a/tests/Input/src/com/android/test/input/InputDeviceTest.java b/tests/Input/src/com/android/test/input/InputDeviceTest.java
index 06a96df..8aaf18a 100644
--- a/tests/Input/src/com/android/test/input/InputDeviceTest.java
+++ b/tests/Input/src/com/android/test/input/InputDeviceTest.java
@@ -87,6 +87,7 @@
.setHasSensor(true)
.setHasBattery(true)
.setCountryCode(InputDeviceCountryCode.INTERNATIONAL)
+ .setSupportsUsi(true)
.build();
Parcel parcel = Parcel.obtain();
diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
index 0fb062f..0b61948 100644
--- a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
+++ b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
@@ -39,7 +39,7 @@
kotlin.system.exitProcess(2)
}
- val ancestorCollector = AncestorCollector(Opcodes.ASM7, null)
+ val ancestorCollector = AncestorCollector(Opcodes.ASM9, null)
for (entry in zipFile.entries()) {
if (entry.name.endsWith(".class")) {
diff --git a/tools/traceinjection/src/com/android/traceinjection/TraceInjectionClassVisitor.java b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionClassVisitor.java
index 863f976..67c5561 100644
--- a/tools/traceinjection/src/com/android/traceinjection/TraceInjectionClassVisitor.java
+++ b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionClassVisitor.java
@@ -28,7 +28,7 @@
private final TraceInjectionConfiguration mParams;
public TraceInjectionClassVisitor(ClassVisitor classVisitor,
TraceInjectionConfiguration params) {
- super(Opcodes.ASM7, classVisitor);
+ super(Opcodes.ASM9, classVisitor);
mParams = params;
}
diff --git a/tools/traceinjection/src/com/android/traceinjection/TraceInjectionMethodAdapter.java b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionMethodAdapter.java
index c2bbddc..91e987d 100644
--- a/tools/traceinjection/src/com/android/traceinjection/TraceInjectionMethodAdapter.java
+++ b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionMethodAdapter.java
@@ -61,7 +61,7 @@
public TraceInjectionMethodAdapter(MethodVisitor methodVisitor, int access,
String name, String descriptor, TraceInjectionConfiguration params) {
- super(Opcodes.ASM7, methodVisitor, access, name, descriptor);
+ super(Opcodes.ASM9, methodVisitor, access, name, descriptor);
mParams = params;
mIsConstructor = "<init>".equals(name);
}
@@ -157,7 +157,7 @@
class TracingAnnotationVisitor extends AnnotationVisitor {
TracingAnnotationVisitor(AnnotationVisitor annotationVisitor) {
- super(Opcodes.ASM7, annotationVisitor);
+ super(Opcodes.ASM9, annotationVisitor);
}
@Override