Merge "Binary Transparency: Log sepolicy hash" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index ce3e985..b54022b 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -64,6 +64,7 @@
":android.hardware.usb.flags-aconfig-java{.generated_srcjars}",
":android.tracing.flags-aconfig-java{.generated_srcjars}",
":android.appwidget.flags-aconfig-java{.generated_srcjars}",
+ ":android.webkit.flags-aconfig-java{.generated_srcjars}",
]
filegroup {
@@ -762,3 +763,19 @@
aconfig_declarations: "android.appwidget.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// WebView
+aconfig_declarations {
+ name: "android.webkit.flags-aconfig",
+ package: "android.webkit",
+ srcs: [
+ "core/java/android/webkit/*.aconfig",
+ "services/core/java/com/android/server/webkit/*.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "android.webkit.flags-aconfig-java",
+ aconfig_declarations: "android.webkit.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 03a23ba..ca73378 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -32,7 +32,6 @@
cmd: "$(location hoststubgen) " +
"@$(location ravenwood/ravenwood-standard-options.txt) " +
- "--out-stub-jar $(location ravenwood_stub.jar) " +
"--out-impl-jar $(location ravenwood.jar) " +
"--gen-keep-all-file $(location hoststubgen_keep_all.txt) " +
@@ -49,7 +48,6 @@
],
out: [
"ravenwood.jar",
- "ravenwood_stub.jar", // It's not used. TODO: Update hoststubgen to make it optional.
// Following files are created just as FYI.
"hoststubgen_keep_all.txt",
diff --git a/apct-tests/perftests/core/Android.bp b/apct-tests/perftests/core/Android.bp
index 9366ff2d..e1b3241 100644
--- a/apct-tests/perftests/core/Android.bp
+++ b/apct-tests/perftests/core/Android.bp
@@ -66,6 +66,7 @@
errorprone: {
javacflags: [
"-Xep:ReturnValueIgnored:WARN",
+ "-Xep:UnnecessaryStringBuilder:OFF",
],
},
}
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
index c42c7ca..2ace602 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
@@ -128,6 +128,7 @@
final MergedConfiguration mOutMergedConfiguration = new MergedConfiguration();
final InsetsState mOutInsetsState = new InsetsState();
final InsetsSourceControl.Array mOutControls = new InsetsSourceControl.Array();
+ final Bundle mOutBundle = new Bundle();
final IWindow mWindow;
final View mView;
final WindowManager.LayoutParams mParams;
@@ -136,7 +137,7 @@
final SurfaceControl mOutSurfaceControl;
final IntSupplier mViewVisibility;
-
+ int mRelayoutSeq;
int mFlags;
RelayoutRunner(Activity activity, IWindow window, IntSupplier visibilitySupplier) {
@@ -152,10 +153,11 @@
void runBenchmark(BenchmarkState state) throws RemoteException {
final IWindowSession session = WindowManagerGlobal.getWindowSession();
while (state.keepRunning()) {
+ mRelayoutSeq++;
session.relayout(mWindow, mParams, mWidth, mHeight,
- mViewVisibility.getAsInt(), mFlags, 0 /* seq */, 0 /* lastSyncSeqId */,
+ mViewVisibility.getAsInt(), mFlags, mRelayoutSeq, 0 /* lastSyncSeqId */,
mOutFrames, mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState,
- mOutControls, new Bundle());
+ mOutControls, mOutBundle);
}
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index b828f39..13bea6b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -598,7 +598,6 @@
long lastSuccessfulRunTime, long lastFailedRunTime, long cumulativeExecutionTimeMs,
int internalFlags,
int dynamicConstraints) {
- this.job = job;
this.callingUid = callingUid;
this.standbyBucket = standbyBucket;
mNamespace = namespace;
@@ -626,6 +625,22 @@
this.sourceTag = tag;
}
+ // This needs to be done before setting the field variable.
+ if (job.getRequiredNetwork() != null) {
+ // Later, when we check if a given network satisfies the required
+ // network, we need to know the UID that is requesting it, so push
+ // the source UID into place.
+ final JobInfo.Builder builder = new JobInfo.Builder(job);
+ builder.setRequiredNetwork(new NetworkRequest.Builder(job.getRequiredNetwork())
+ .setUids(Collections.singleton(new Range<>(this.sourceUid, this.sourceUid)))
+ .build());
+ // Don't perform validation checks at this point since we've already passed the
+ // initial validation check.
+ job = builder.build(false, false);
+ }
+
+ this.job = job;
+
final String bnNamespace = namespace == null ? "" : "@" + namespace + "@";
this.batteryName = this.sourceTag != null
? bnNamespace + this.sourceTag + ":" + job.getService().getPackageName()
@@ -708,21 +723,6 @@
updateNetworkBytesLocked();
- if (job.getRequiredNetwork() != null) {
- // Later, when we check if a given network satisfies the required
- // network, we need to know the UID that is requesting it, so push
- // our source UID into place.
- final JobInfo.Builder builder = new JobInfo.Builder(job);
- final NetworkRequest.Builder requestBuilder =
- new NetworkRequest.Builder(job.getRequiredNetwork());
- requestBuilder.setUids(
- Collections.singleton(new Range<Integer>(this.sourceUid, this.sourceUid)));
- builder.setRequiredNetwork(requestBuilder.build());
- // Don't perform validation checks at this point since we've already passed the
- // initial validation check.
- job = builder.build(false, false);
- }
-
updateMediaBackupExemptionStatus();
}
diff --git a/api/Android.bp b/api/Android.bp
index d6c14fb..2b1cfcb 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -404,3 +404,49 @@
"ApiDocs.bp",
"StubLibraries.bp",
]
+
+genrule_defaults {
+ name: "flag-api-mapping-generation-defaults",
+ cmd: "$(location extract-flagged-apis) $(in) $(out)",
+ tools: ["extract-flagged-apis"],
+}
+
+genrule {
+ name: "flag-api-mapping-PublicApi",
+ defaults: ["flag-api-mapping-generation-defaults"],
+ srcs: [":frameworks-base-api-current.txt"],
+ out: ["flag_api_map.textproto"],
+ dist: {
+ targets: ["droid"],
+ },
+}
+
+genrule {
+ name: "flag-api-mapping-SystemApi",
+ defaults: ["flag-api-mapping-generation-defaults"],
+ srcs: [":frameworks-base-api-system-current.txt"],
+ out: ["system_flag_api_map.textproto"],
+ dist: {
+ targets: ["droid"],
+ },
+}
+
+genrule {
+ name: "flag-api-mapping-ModuleLibApi",
+ defaults: ["flag-api-mapping-generation-defaults"],
+ srcs: [":frameworks-base-api-module-lib-current.txt"],
+ out: ["module_lib_flag_api_map.textproto"],
+ dist: {
+ targets: ["droid"],
+ },
+}
+
+genrule {
+ name: "flag-api-mapping-SystemServerApi",
+ defaults: ["flag-api-mapping-generation-defaults"],
+ srcs: [":frameworks-base-api-system-server-current.txt"],
+ out: ["system_server_flag_api_map.textproto"],
+ dist: {
+ targets: ["droid"],
+ },
+}
diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt
index 948e64f..9ffb704 100644
--- a/api/coverage/tools/ExtractFlaggedApis.kt
+++ b/api/coverage/tools/ExtractFlaggedApis.kt
@@ -25,7 +25,7 @@
var cb = ApiFile.parseApi(listOf(File(args[0])))
val flagToApi = mutableMapOf<String, MutableList<String>>()
cb.getPackages()
- .allTopLevelClasses()
+ .allClasses()
.filter { it.methods().size > 0 }
.forEach {
for (method in it.methods()) {
diff --git a/core/api/current.txt b/core/api/current.txt
index f09036b..812d4cd6 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12781,6 +12781,7 @@
method public boolean isPackageSuspended();
method @CheckResult public abstract boolean isPermissionRevokedByPolicy(@NonNull String, @NonNull String);
method public abstract boolean isSafeMode();
+ method @FlaggedApi("android.content.pm.get_package_info") @WorkerThread public <T> T parseAndroidManifest(@NonNull String, @NonNull java.util.function.Function<android.content.res.XmlResourceParser,T>) throws java.io.IOException;
method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryActivityProperty(@NonNull String);
method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryApplicationProperty(@NonNull String);
method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, int);
@@ -33157,7 +33158,6 @@
public static class PerformanceHintManager.Session implements java.io.Closeable {
method public void close();
method public void reportActualWorkDuration(long);
- method @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public void reportActualWorkDuration(@NonNull android.os.WorkDuration);
method @FlaggedApi("android.os.adpf_prefer_power_efficiency") public void setPreferPowerEfficiency(boolean);
method public void setThreads(@NonNull int[]);
method public void updateTargetWorkDuration(long);
@@ -33500,7 +33500,6 @@
method public static boolean setCurrentTimeMillis(long);
method public static void sleep(long);
method public static long uptimeMillis();
- method @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public static long uptimeNanos();
}
public class TestLooperManager {
@@ -33766,22 +33765,6 @@
method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibration, @Nullable android.os.VibrationAttributes);
}
- @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public final class WorkDuration implements android.os.Parcelable {
- ctor public WorkDuration();
- ctor public WorkDuration(long, long, long, long);
- method public int describeContents();
- method public long getActualCpuDurationNanos();
- method public long getActualGpuDurationNanos();
- method public long getActualTotalDurationNanos();
- method public long getWorkPeriodStartTimestampNanos();
- method public void setActualCpuDurationNanos(long);
- method public void setActualGpuDurationNanos(long);
- method public void setActualTotalDurationNanos(long);
- method public void setWorkPeriodStartTimestampNanos(long);
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.os.WorkDuration> CREATOR;
- }
-
public class WorkSource implements android.os.Parcelable {
ctor public WorkSource();
ctor public WorkSource(android.os.WorkSource);
@@ -36777,6 +36760,7 @@
field public static final String ACTION_APP_OPEN_BY_DEFAULT_SETTINGS = "android.settings.APP_OPEN_BY_DEFAULT_SETTINGS";
field public static final String ACTION_APP_SEARCH_SETTINGS = "android.settings.APP_SEARCH_SETTINGS";
field public static final String ACTION_APP_USAGE_SETTINGS = "android.settings.action.APP_USAGE_SETTINGS";
+ field @FlaggedApi("android.app.modes_api") public static final String ACTION_AUTOMATIC_ZEN_RULE_SETTINGS = "android.settings.AUTOMATIC_ZEN_RULE_SETTINGS";
field public static final String ACTION_AUTO_ROTATE_SETTINGS = "android.settings.AUTO_ROTATE_SETTINGS";
field public static final String ACTION_BATTERY_SAVER_SETTINGS = "android.settings.BATTERY_SAVER_SETTINGS";
field public static final String ACTION_BIOMETRIC_ENROLL = "android.settings.BIOMETRIC_ENROLL";
@@ -36864,6 +36848,7 @@
field public static final String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled";
field public static final String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
field public static final String EXTRA_AUTHORITIES = "authorities";
+ field @FlaggedApi("android.app.modes_api") public static final String EXTRA_AUTOMATIC_ZEN_RULE_ID = "android.provider.extra.AUTOMATIC_ZEN_RULE_ID";
field public static final String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
field public static final String EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED = "android.provider.extra.BIOMETRIC_AUTHENTICATORS_ALLOWED";
field public static final String EXTRA_CHANNEL_FILTER_LIST = "android.provider.extra.CHANNEL_FILTER_LIST";
@@ -45177,6 +45162,7 @@
method public void addOnSubscriptionsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void addSubscriptionsIntoGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid);
method public boolean canManageSubscription(android.telephony.SubscriptionInfo);
+ method @FlaggedApi("com.android.internal.telephony.flags.work_profile_api_split") @NonNull public android.telephony.SubscriptionManager createForAllUserProfiles();
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.os.ParcelUuid createSubscriptionGroup(@NonNull java.util.List<java.lang.Integer>);
method @Deprecated public static android.telephony.SubscriptionManager from(android.content.Context);
method public java.util.List<android.telephony.SubscriptionInfo> getAccessibleSubscriptionInfoList();
@@ -54201,7 +54187,7 @@
method public boolean isEnabled();
method public boolean isFocusable();
method public boolean isFocused();
- method public boolean isGranularScrollingSupported();
+ method @FlaggedApi("android.view.accessibility.granular_scrolling") public boolean isGranularScrollingSupported();
method public boolean isHeading();
method public boolean isImportantForAccessibility();
method public boolean isLongClickable();
@@ -54251,7 +54237,7 @@
method public void setError(CharSequence);
method public void setFocusable(boolean);
method public void setFocused(boolean);
- method public void setGranularScrollingSupported(boolean);
+ method @FlaggedApi("android.view.accessibility.granular_scrolling") public void setGranularScrollingSupported(boolean);
method public void setHeading(boolean);
method public void setHintText(CharSequence);
method public void setImportantForAccessibility(boolean);
@@ -54306,7 +54292,7 @@
field public static final String ACTION_ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT = "android.view.accessibility.action.ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT";
field public static final String ACTION_ARGUMENT_PROGRESS_VALUE = "android.view.accessibility.action.ARGUMENT_PROGRESS_VALUE";
field public static final String ACTION_ARGUMENT_ROW_INT = "android.view.accessibility.action.ARGUMENT_ROW_INT";
- field public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT = "android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT";
+ field @FlaggedApi("android.view.accessibility.granular_scrolling") public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT = "android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT";
field public static final String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT";
field public static final String ACTION_ARGUMENT_SELECTION_START_INT = "ACTION_ARGUMENT_SELECTION_START_INT";
field public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE = "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE";
@@ -54409,10 +54395,9 @@
public static final class AccessibilityNodeInfo.CollectionInfo {
ctor public AccessibilityNodeInfo.CollectionInfo(int, int, boolean);
ctor public AccessibilityNodeInfo.CollectionInfo(int, int, boolean, int);
- ctor public AccessibilityNodeInfo.CollectionInfo(int, int, boolean, int, int, int);
method public int getColumnCount();
- method public int getImportantForAccessibilityItemCount();
- method public int getItemCount();
+ method @FlaggedApi("android.view.accessibility.collection_info_item_counts") public int getImportantForAccessibilityItemCount();
+ method @FlaggedApi("android.view.accessibility.collection_info_item_counts") public int getItemCount();
method public int getRowCount();
method public int getSelectionMode();
method public boolean isHierarchical();
@@ -54421,18 +54406,18 @@
field public static final int SELECTION_MODE_MULTIPLE = 2; // 0x2
field public static final int SELECTION_MODE_NONE = 0; // 0x0
field public static final int SELECTION_MODE_SINGLE = 1; // 0x1
- field public static final int UNDEFINED = -1; // 0xffffffff
+ field @FlaggedApi("android.view.accessibility.collection_info_item_counts") public static final int UNDEFINED = -1; // 0xffffffff
}
- public static final class AccessibilityNodeInfo.CollectionInfo.Builder {
- ctor public AccessibilityNodeInfo.CollectionInfo.Builder();
- method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo build();
- method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setColumnCount(int);
- method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setHierarchical(boolean);
- method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setImportantForAccessibilityItemCount(int);
- method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setItemCount(int);
- method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setRowCount(int);
- method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setSelectionMode(int);
+ @FlaggedApi("android.view.accessibility.collection_info_item_counts") public static final class AccessibilityNodeInfo.CollectionInfo.Builder {
+ ctor @FlaggedApi("android.view.accessibility.collection_info_item_counts") public AccessibilityNodeInfo.CollectionInfo.Builder();
+ method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo build();
+ method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setColumnCount(int);
+ method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setHierarchical(boolean);
+ method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setImportantForAccessibilityItemCount(int);
+ method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setItemCount(int);
+ method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setRowCount(int);
+ method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setSelectionMode(int);
}
public static final class AccessibilityNodeInfo.CollectionItemInfo {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 32d252e..275fe77 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3868,8 +3868,9 @@
public class PackageInstaller {
method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException;
+ method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalStatus(int, int, long, @Nullable android.app.PendingIntent) throws android.content.pm.PackageManager.NameNotFoundException;
method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
- method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
+ method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL";
@@ -3883,12 +3884,20 @@
field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS";
field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ID = "android.content.pm.extra.UNARCHIVE_ID";
field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
+ field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS";
field public static final int LOCATION_DATA_APP = 0; // 0x0
field public static final int LOCATION_MEDIA_DATA = 2; // 0x2
field public static final int LOCATION_MEDIA_OBB = 1; // 0x1
field public static final int REASON_CONFIRM_PACKAGE_CHANGE = 0; // 0x0
field public static final int REASON_OWNERSHIP_CHANGED = 1; // 0x1
field public static final int REASON_REMIND_OWNERSHIP = 2; // 0x2
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4; // 0x4
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5; // 0x5
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2; // 0x2
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3; // 0x3
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1; // 0x1
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_GENERIC_ERROR = 100; // 0x64
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_OK = 0; // 0x0
}
public static class PackageInstaller.InstallInfo {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index bb335fa..b5f7f23 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -20,6 +20,7 @@
import static android.Manifest.permission.DETECT_SCREEN_CAPTURE;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.inMultiWindowMode;
import static android.os.Process.myUid;
@@ -9439,6 +9440,15 @@
ActivityClient.getInstance().enableTaskLocaleOverride(mToken);
}
+ /**
+ * Request ActivityRecordInputSink to enable or disable blocking input events.
+ * @hide
+ */
+ @RequiresPermission(INTERNAL_SYSTEM_WINDOW)
+ public void setActivityRecordInputSinkEnabled(boolean enabled) {
+ ActivityClient.getInstance().setActivityRecordInputSinkEnabled(mToken, enabled);
+ }
+
class HostCallbacks extends FragmentHostCallback<Activity> {
public HostCallbacks() {
super(Activity.this /*activity*/);
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index b35e87b..b8bd030 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -16,6 +16,8 @@
package android.app;
+import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.content.ComponentName;
@@ -614,6 +616,15 @@
}
}
+ @RequiresPermission(INTERNAL_SYSTEM_WINDOW)
+ void setActivityRecordInputSinkEnabled(IBinder activityToken, boolean enabled) {
+ try {
+ getActivityClientController().setActivityRecordInputSinkEnabled(activityToken, enabled);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Shows or hides a Camera app compat toggle for stretched issues with the requested state.
*
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 02eaf0b..8af1216 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -232,6 +232,7 @@
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.org.conscrypt.TrustedCertificateStore;
import com.android.server.am.MemInfoDumpProto;
+import com.android.window.flags.Flags;
import dalvik.annotation.optimization.NeverCompile;
import dalvik.system.AppSpecializationHooks;
@@ -3713,7 +3714,13 @@
final ArrayList<ResultInfo> list = new ArrayList<>();
list.add(new ResultInfo(id, requestCode, resultCode, data));
final ClientTransaction clientTransaction = ClientTransaction.obtain(mAppThread);
- clientTransaction.addCallback(ActivityResultItem.obtain(activityToken, list));
+ final ActivityResultItem activityResultItem = ActivityResultItem.obtain(
+ activityToken, list);
+ if (Flags.bundleClientTransactionFlag()) {
+ clientTransaction.addTransactionItem(activityResultItem);
+ } else {
+ clientTransaction.addCallback(activityResultItem);
+ }
try {
mAppThread.scheduleTransaction(clientTransaction);
} catch (RemoteException e) {
@@ -4492,16 +4499,26 @@
private void schedulePauseWithUserLeavingHint(ActivityClientRecord r) {
final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
- transaction.setLifecycleStateRequest(PauseActivityItem.obtain(r.token,
+ final PauseActivityItem pauseActivityItem = PauseActivityItem.obtain(r.token,
r.activity.isFinishing(), /* userLeaving */ true, r.activity.mConfigChangeFlags,
- /* dontReport */ false, /* autoEnteringPip */ false));
+ /* dontReport */ false, /* autoEnteringPip */ false);
+ if (Flags.bundleClientTransactionFlag()) {
+ transaction.addTransactionItem(pauseActivityItem);
+ } else {
+ transaction.setLifecycleStateRequest(pauseActivityItem);
+ }
executeTransaction(transaction);
}
private void scheduleResume(ActivityClientRecord r) {
final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
- transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(r.token,
- /* isForward */ false, /* shouldSendCompatFakeFocus */ false));
+ final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(r.token,
+ /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
+ if (Flags.bundleClientTransactionFlag()) {
+ transaction.addTransactionItem(resumeActivityItem);
+ } else {
+ transaction.setLifecycleStateRequest(resumeActivityItem);
+ }
executeTransaction(transaction);
}
@@ -6092,8 +6109,13 @@
TransactionExecutorHelper.getLifecycleRequestForCurrentState(r);
// Schedule the transaction.
final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
- transaction.addCallback(activityRelaunchItem);
- transaction.setLifecycleStateRequest(lifecycleRequest);
+ if (Flags.bundleClientTransactionFlag()) {
+ transaction.addTransactionItem(activityRelaunchItem);
+ transaction.addTransactionItem(lifecycleRequest);
+ } else {
+ transaction.addCallback(activityRelaunchItem);
+ transaction.setLifecycleStateRequest(lifecycleRequest);
+ }
executeTransaction(transaction);
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index ca6d8df..a4c3bb8 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -80,6 +80,8 @@
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VersionedPackage;
import android.content.pm.dex.ArtManager;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.res.ApkAssets;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
@@ -144,6 +146,7 @@
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
+import java.util.function.Function;
/** @hide */
public class ApplicationPackageManager extends PackageManager {
@@ -4024,4 +4027,34 @@
throw e.rethrowFromSystemServer();
}
}
+
+ @Override
+ public <T> T parseAndroidManifest(@NonNull String apkFilePath,
+ @NonNull Function<XmlResourceParser, T> parserFunction) throws IOException {
+ Objects.requireNonNull(apkFilePath, "apkFilePath cannot be null");
+ Objects.requireNonNull(parserFunction, "parserFunction cannot be null");
+ try (XmlResourceParser xmlResourceParser = getAndroidManifestParser(apkFilePath)) {
+ return parserFunction.apply(xmlResourceParser);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to get the android manifest parser", e);
+ throw e;
+ }
+ }
+
+ private static XmlResourceParser getAndroidManifestParser(@NonNull String apkFilePath)
+ throws IOException {
+ ApkAssets apkAssets = null;
+ try {
+ apkAssets = ApkAssets.loadFromPath(apkFilePath);
+ return apkAssets.openXml(ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME);
+ } finally {
+ if (apkAssets != null) {
+ try {
+ apkAssets.close();
+ } catch (Throwable ignored) {
+ Log.w(TAG, "Failed to close apkAssets", ignored);
+ }
+ }
+ }
+ }
}
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index a3c5e1c..7370fc3 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -191,4 +191,14 @@
*/
boolean isRequestedToLaunchInTaskFragment(in IBinder activityToken,
in IBinder taskFragmentToken);
+
+ /**
+ * Enable or disable ActivityRecordInputSink to block input events.
+ *
+ * @param token The token for the activity that requests to toggle.
+ * @param enabled Whether the input evens are blocked by ActivityRecordInputSink.
+ */
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.INTERNAL_SYSTEM_WINDOW)")
+ oneway void setActivityRecordInputSinkEnabled(in IBinder activityToken, boolean enabled);
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 56d0d1f..6c9c14f 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -51,6 +51,7 @@
import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.UserHandle;
+import android.provider.Settings;
import android.provider.Settings.Global;
import android.service.notification.Adjustment;
import android.service.notification.Condition;
@@ -1253,7 +1254,8 @@
* <p>
* If this method returns true, calls to
* {@link #updateAutomaticZenRule(String, AutomaticZenRule)} may fail and apps should defer
- * rule management to system settings/uis.
+ * rule management to system settings/uis via
+ * {@link Settings#ACTION_AUTOMATIC_ZEN_RULE_SETTINGS}.
*/
@FlaggedApi(Flags.FLAG_MODES_API)
public boolean areAutomaticZenRulesUserManaged() {
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index cc56a1c..d8448dc 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -72,6 +72,7 @@
per-file *Zen* = file:/packages/SystemUI/OWNERS
per-file *StatusBar* = file:/packages/SystemUI/OWNERS
per-file *UiModeManager* = file:/packages/SystemUI/OWNERS
+per-file notification.aconfig = file:/packages/SystemUI/OWNERS
# PackageManager
per-file ApplicationPackageManager.java = file:/services/core/java/com/android/server/pm/OWNERS
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index bbd07b8..35ce102 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -48,3 +48,10 @@
description: "Update DumpSys to include information about migrated APIs in DPE"
bug: "304999634"
}
+
+flag {
+ name: "quiet_mode_credential_bug_fix"
+ namespace: "enterprise"
+ description: "Guards a bugfix that ends the credential input flow if the managed user has not stopped."
+ bug: "293441361"
+}
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 7c34cde..5e55268 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -75,7 +75,6 @@
/**
* Adds a message to the end of the sequence of transaction items.
* @param item A single message that can contain a client activity/window request/callback.
- * TODO(b/260873529): replace both {@link #addCallback} and {@link #setLifecycleStateRequest}.
*/
public void addTransactionItem(@NonNull ClientTransactionItem item) {
if (mTransactionItems == null) {
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index dfbccb4..475c6fb 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -235,7 +235,9 @@
* Configuration - ActivityResult - Configuration - ActivityResult
* index 1 will be returned, because ActivityResult request on position 1 will be the last
* request that moves activity to the RESUMED state where it will eventually end.
+ * @deprecated to be removed with {@link TransactionExecutor#executeCallbacks}.
*/
+ @Deprecated
static int lastCallbackRequestingState(@NonNull ClientTransaction transaction) {
final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
if (callbacks == null || callbacks.isEmpty()
diff --git a/core/java/android/app/time/TEST_MAPPING b/core/java/android/app/time/TEST_MAPPING
index 49a4467..47a152a 100644
--- a/core/java/android/app/time/TEST_MAPPING
+++ b/core/java/android/app/time/TEST_MAPPING
@@ -1,6 +1,5 @@
{
- // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
- "postsubmit": [
+ "presubmit": [
{
"name": "FrameworksTimeCoreTests",
"options": [
@@ -8,7 +7,10 @@
"include-filter": "android.app."
}
]
- },
+ }
+ ],
+ // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+ "postsubmit": [
{
"name": "FrameworksServicesTests",
"options": [
diff --git a/core/java/android/app/timedetector/TEST_MAPPING b/core/java/android/app/timedetector/TEST_MAPPING
index c050a55..9517fb9 100644
--- a/core/java/android/app/timedetector/TEST_MAPPING
+++ b/core/java/android/app/timedetector/TEST_MAPPING
@@ -1,6 +1,5 @@
{
- // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
- "postsubmit": [
+ "presubmit": [
{
"name": "FrameworksTimeCoreTests",
"options": [
@@ -8,7 +7,10 @@
"include-filter": "android.app."
}
]
- },
+ }
+ ],
+ // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+ "postsubmit": [
{
"name": "FrameworksServicesTests",
"options": [
diff --git a/core/java/android/app/timezonedetector/TEST_MAPPING b/core/java/android/app/timezonedetector/TEST_MAPPING
index 46656d1..fd41b86 100644
--- a/core/java/android/app/timezonedetector/TEST_MAPPING
+++ b/core/java/android/app/timezonedetector/TEST_MAPPING
@@ -1,6 +1,5 @@
{
- // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
- "postsubmit": [
+ "presubmit": [
{
"name": "FrameworksTimeCoreTests",
"options": [
@@ -8,7 +7,10 @@
"include-filter": "android.app."
}
]
- },
+ }
+ ],
+ // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+ "postsubmit": [
{
"name": "FrameworksServicesTests",
"options": [
diff --git a/core/java/android/app/usage/ParcelableUsageEventList.java b/core/java/android/app/usage/ParcelableUsageEventList.java
index 016d97f..aa32392 100644
--- a/core/java/android/app/usage/ParcelableUsageEventList.java
+++ b/core/java/android/app/usage/ParcelableUsageEventList.java
@@ -48,13 +48,16 @@
private List<Event> mList;
- public ParcelableUsageEventList(List<Event> list) {
+ public ParcelableUsageEventList(@NonNull List<Event> list) {
+ if (list == null) {
+ throw new IllegalArgumentException("Empty list");
+ }
mList = list;
}
private ParcelableUsageEventList(Parcel in) {
final int N = in.readInt();
- mList = new ArrayList<>();
+ mList = new ArrayList<>(N);
if (DEBUG) Log.d(TAG, "Retrieving " + N + " items");
if (N <= 0) {
return;
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 1eb452c..6c7eba0 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -24,9 +24,11 @@
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -35,6 +37,7 @@
* from which to read {@link android.app.usage.UsageEvents.Event} objects.
*/
public final class UsageEvents implements Parcelable {
+ private static final String TAG = "UsageEvents";
/** @hide */
public static final String INSTANT_APP_PACKAGE_NAME = "android.instant_app";
@@ -786,10 +789,20 @@
}
private void readUsageEventsFromParcelWithParceledList(Parcel in) {
+ mEventCount = in.readInt();
mIndex = in.readInt();
- mEventsToWrite = in.readParcelable(UsageEvents.class.getClassLoader(),
- ParcelableUsageEventList.class).getList();
- mEventCount = mEventsToWrite.size();
+ ParcelableUsageEventList slice = in.readParcelable(getClass().getClassLoader(),
+ ParcelableUsageEventList.class);
+ if (slice != null) {
+ mEventsToWrite = slice.getList();
+ } else {
+ mEventsToWrite = new ArrayList<>();
+ }
+
+ if (mEventCount != mEventsToWrite.size()) {
+ Log.w(TAG, "Partial usage event list received: " + mEventCount + " != "
+ + mEventsToWrite.size());
+ }
}
private void readUsageEventsFromParcelWithBlob(Parcel in) {
@@ -1065,6 +1078,7 @@
}
private void writeUsageEventsToParcelWithParceledList(Parcel dest, int flags) {
+ dest.writeInt(mEventCount);
dest.writeInt(mIndex);
dest.writeParcelable(new ParcelableUsageEventList(mEventsToWrite), flags);
}
diff --git a/core/java/android/companion/utils/FeatureUtils.java b/core/java/android/companion/utils/FeatureUtils.java
deleted file mode 100644
index a382e09..0000000
--- a/core/java/android/companion/utils/FeatureUtils.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.utils;
-
-import android.os.Binder;
-import android.os.Build;
-import android.provider.DeviceConfig;
-
-/**
- * Util class for feature flags
- *
- * @hide
- */
-public final class FeatureUtils {
-
- private static final String NAMESPACE_COMPANION = "companion";
-
- private static final String PROPERTY_PERM_SYNC_ENABLED = "perm_sync_enabled";
-
- public static boolean isPermSyncEnabled() {
- // Permissions sync is always enabled in debuggable mode.
- if (Build.isDebuggable()) {
- return true;
- }
-
- // Clear app identity to read the device config for feature flag.
- final long identity = Binder.clearCallingIdentity();
- try {
- return DeviceConfig.getBoolean(NAMESPACE_COMPANION,
- PROPERTY_PERM_SYNC_ENABLED, false);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- private FeatureUtils() {
- }
-}
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 59ed045..1f25fd0 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -16,6 +16,7 @@
package android.content.pm;
+import android.app.PendingIntent;
import android.content.pm.ArchivedPackageParcel;
import android.content.pm.IPackageDeleteObserver2;
import android.content.pm.IPackageInstallerCallback;
@@ -82,7 +83,7 @@
void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
- void requestUnarchive(String packageName, String callerPackageName, in UserHandle userHandle);
+ void requestUnarchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES)")
void installPackageArchived(in ArchivedPackageParcel archivedPackageParcel,
@@ -90,4 +91,6 @@
in IntentSender statusReceiver,
String installerPackageName, in UserHandle userHandle);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
+ void reportUnarchivalStatus(int unarchiveId, int status, long requiredStorageBytes, in PendingIntent userActionIntent, in UserHandle userHandle);
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index e9a2aaa..4f0bfc7 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -387,6 +387,24 @@
"android.content.pm.extra.UNARCHIVE_ALL_USERS";
/**
+ * Current status of an unarchive operation. Will be one of
+ * {@link #UNARCHIVAL_OK}, {@link #UNARCHIVAL_ERROR_USER_ACTION_NEEDED},
+ * {@link #UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE}, {@link #UNARCHIVAL_ERROR_NO_CONNECTIVITY},
+ * {@link #UNARCHIVAL_GENERIC_ERROR}, {@link #UNARCHIVAL_ERROR_INSTALLER_DISABLED} or
+ * {@link #UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED}.
+ *
+ * <p> If the status is not {@link #UNARCHIVAL_OK}, then {@link Intent#EXTRA_INTENT} will be set
+ * with an intent for a corresponding follow-up action (e.g. storage clearing dialog) or a
+ * failure dialog.
+ *
+ * @see #requestUnarchive
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS";
+
+ /**
* A list of warnings that occurred during installation.
*
* @hide
@@ -652,6 +670,102 @@
@Retention(RetentionPolicy.SOURCE)
public @interface UserActionReason {}
+ /**
+ * The unarchival is possible and will commence.
+ *
+ * <p> Note that this does not mean that the unarchival has completed. This status should be
+ * sent before any longer asynchronous action (e.g. app download) is started.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_OK = 0;
+
+ /**
+ * The user needs to interact with the installer to enable the installation.
+ *
+ * <p> An example use case for this could be that the user needs to login to allow the
+ * download for a paid app.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1;
+
+ /**
+ * Not enough storage to unarchive the application.
+ *
+ * <p> The installer can optionally provide a {@code userActionIntent} for a space-clearing
+ * dialog. If no action is provided, then a generic intent
+ * {@link android.os.storage.StorageManager#ACTION_MANAGE_STORAGE} is started instead.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2;
+
+ /**
+ * The device is not connected to the internet
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3;
+
+ /**
+ * The installer responsible for the unarchival is disabled.
+ *
+ * <p> Should only be used by the system.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4;
+
+ /**
+ * The installer responsible for the unarchival has been uninstalled
+ *
+ * <p> Should only be used by the system.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5;
+
+ /**
+ * Generic error: The app cannot be unarchived.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_GENERIC_ERROR = 100;
+
+ /**
+ * The set of error types that can be set for
+ * {@link #reportUnarchivalStatus(int, int, PendingIntent)}.
+ *
+ * @hide
+ */
+ @IntDef(value = {
+ UNARCHIVAL_OK,
+ UNARCHIVAL_ERROR_USER_ACTION_NEEDED,
+ UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE,
+ UNARCHIVAL_ERROR_NO_CONNECTIVITY,
+ UNARCHIVAL_ERROR_INSTALLER_DISABLED,
+ UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED,
+ UNARCHIVAL_GENERIC_ERROR,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UnarchivalStatus {}
+
+
/** Default set of checksums - includes all available checksums.
* @see Session#requestChecksums */
private static final int DEFAULT_CHECKSUMS =
@@ -2238,8 +2352,8 @@
* Requests to archive a package which is currently installed.
*
* <p> During the archival process, the apps APKs and cache are removed from the device while
- * the user data is kept. Through the {@link #requestUnarchive(String)} call, apps can be
- * restored again through their responsible installer.
+ * the user data is kept. Through the {@link #requestUnarchive} call, apps
+ * can be restored again through their responsible installer.
*
* <p> Archived apps are returned as displayable apps through the {@link LauncherApps} APIs and
* will be displayed to users with UI treatment to highlight that said apps are archived. If
@@ -2278,6 +2392,10 @@
* <p> The installation will happen asynchronously and can be observed through
* {@link android.content.Intent#ACTION_PACKAGE_ADDED}.
*
+ * @param statusReceiver Callback used to notify whether the installer has accepted the
+ * unarchival request or an error has occurred. The status update will be
+ * sent though {@link EXTRA_UNARCHIVE_STATUS}. Only one status will be
+ * sent.
* @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not
* visible to the caller or if the package has no
* installer on the device anymore to unarchive it.
@@ -2290,10 +2408,10 @@
Manifest.permission.REQUEST_INSTALL_PACKAGES})
@SystemApi
@FlaggedApi(Flags.FLAG_ARCHIVING)
- public void requestUnarchive(@NonNull String packageName)
+ public void requestUnarchive(@NonNull String packageName, @NonNull IntentSender statusReceiver)
throws IOException, PackageManager.NameNotFoundException {
try {
- mInstaller.requestUnarchive(packageName, mInstallerPackageName,
+ mInstaller.requestUnarchive(packageName, mInstallerPackageName, statusReceiver,
new UserHandle(mUserId));
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
@@ -2303,6 +2421,39 @@
}
}
+ /**
+ * Reports the status of an unarchival to the system.
+ *
+ * @param unarchiveId the ID provided by the system as part of the
+ * intent.action.UNARCHIVE broadcast with EXTRA_UNARCHIVE_ID.
+ * @param status is used for the system to provide the user with necessary
+ * follow-up steps or errors.
+ * @param requiredStorageBytes If the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this field
+ * should be set to specify how many additional bytes of storage
+ * are required to unarchive the app.
+ * @param userActionIntent Optional intent to start a follow up action required to
+ * facilitate the unarchival flow (e.g. user needs to log in).
+ * @throws PackageManager.NameNotFoundException if no unarchival with {@code unarchiveId} exists
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.REQUEST_INSTALL_PACKAGES})
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public void reportUnarchivalStatus(int unarchiveId, @UnarchivalStatus int status,
+ long requiredStorageBytes, @Nullable PendingIntent userActionIntent)
+ throws PackageManager.NameNotFoundException {
+ try {
+ mInstaller.reportUnarchivalStatus(unarchiveId, status, requiredStorageBytes,
+ userActionIntent, new UserHandle(mUserId));
+ } catch (ParcelableException e) {
+ e.maybeRethrow(PackageManager.NameNotFoundException.class);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
// (b/239722738) This class serves as a bridge between the PackageLite class, which
// is a hidden class, and the consumers of this class. (e.g. InstallInstalling.java)
// This is a part of an effort to remove dependency on hidden APIs and use SystemAPIs or
@@ -2566,6 +2717,8 @@
public int developmentInstallFlags = 0;
/** {@hide} */
public int unarchiveId = -1;
+ /** {@hide} */
+ public IntentSender unarchiveIntentSender;
private final ArrayMap<String, Integer> mPermissionStates;
@@ -2618,6 +2771,7 @@
applicationEnabledSettingPersistent = source.readBoolean();
developmentInstallFlags = source.readInt();
unarchiveId = source.readInt();
+ unarchiveIntentSender = source.readParcelable(null, IntentSender.class);
}
/** {@hide} */
@@ -2652,6 +2806,7 @@
ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
ret.developmentInstallFlags = developmentInstallFlags;
ret.unarchiveId = unarchiveId;
+ ret.unarchiveIntentSender = unarchiveIntentSender;
return ret;
}
@@ -3364,6 +3519,7 @@
applicationEnabledSettingPersistent);
pw.printHexPair("developmentInstallFlags", developmentInstallFlags);
pw.printPair("unarchiveId", unarchiveId);
+ pw.printPair("unarchiveIntentSender", unarchiveIntentSender);
pw.println();
}
@@ -3408,6 +3564,7 @@
dest.writeBoolean(applicationEnabledSettingPersistent);
dest.writeInt(developmentInstallFlags);
dest.writeInt(unarchiveId);
+ dest.writeParcelable(unarchiveIntentSender, flags);
}
public static final Parcelable.Creator<SessionParams>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c3b3423..fe31c9d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -34,6 +34,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
import android.annotation.XmlRes;
import android.app.ActivityManager;
import android.app.ActivityThread;
@@ -96,6 +97,7 @@
import dalvik.system.VMRuntime;
import java.io.File;
+import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.security.cert.Certificate;
@@ -108,6 +110,7 @@
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
+import java.util.function.Function;
/**
* Class for retrieving various kinds of information related to the application
@@ -11426,4 +11429,60 @@
throw new UnsupportedOperationException(
"unregisterPackageMonitorCallback not implemented in subclass");
}
+
+ /**
+ * Retrieve AndroidManifest.xml information for the given application apk path.
+ *
+ * <p>Example:
+ *
+ * <pre><code>
+ * Bundle result;
+ * try {
+ * result = getContext().getPackageManager().parseAndroidManifest(apkFilePath,
+ * xmlResourceParser -> {
+ * Bundle bundle = new Bundle();
+ * // Search the start tag
+ * int type;
+ * while ((type = xmlResourceParser.next()) != XmlPullParser.START_TAG
+ * && type != XmlPullParser.END_DOCUMENT) {
+ * }
+ * if (type != XmlPullParser.START_TAG) {
+ * return bundle;
+ * }
+ *
+ * // Start to read the tags and attributes from the xmlResourceParser
+ * if (!xmlResourceParser.getName().equals("manifest")) {
+ * return bundle;
+ * }
+ * String packageName = xmlResourceParser.getAttributeValue(null, "package");
+ * bundle.putString("package", packageName);
+ *
+ * // Continue to read the tags and attributes from the xmlResourceParser
+ *
+ * return bundle;
+ * });
+ * } catch (IOException e) {
+ * }
+ * </code></pre>
+ *
+ * Note: When the parserFunction is invoked, the client can read the AndroidManifest.xml
+ * information by the XmlResourceParser object. After leaving the parserFunction, the
+ * XmlResourceParser object will be closed.
+ *
+ * @param apkFilePath The path of an application apk file.
+ * @param parserFunction The parserFunction will be invoked with the XmlResourceParser object
+ * after getting the AndroidManifest.xml of an application package.
+ *
+ * @return Returns the result of the {@link Function#apply(Object)}.
+ *
+ * @throws IOException if the AndroidManifest.xml of an application package cannot be
+ * read or accessed.
+ */
+ @FlaggedApi(android.content.pm.Flags.FLAG_GET_PACKAGE_INFO)
+ @WorkerThread
+ public <T> T parseAndroidManifest(@NonNull String apkFilePath,
+ @NonNull Function<XmlResourceParser, T> parserFunction) throws IOException {
+ throw new UnsupportedOperationException(
+ "parseAndroidManifest not implemented in subclass");
+ }
}
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
index 7756b9c..c379188 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -22,9 +22,11 @@
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.Trace;
import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
@@ -45,6 +47,8 @@
@VisibleForTesting(visibility = Visibility.PACKAGE)
public final class DeviceStateManagerGlobal {
private static DeviceStateManagerGlobal sInstance;
+ private static final String TAG = "DeviceStateManagerGlobal";
+ private static final boolean DEBUG = Build.IS_DEBUGGABLE;
/**
* Returns an instance of {@link DeviceStateManagerGlobal}. May return {@code null} if a
@@ -400,11 +404,29 @@
}
void notifyBaseStateChanged(int newBaseState) {
- mExecutor.execute(() -> mDeviceStateCallback.onBaseStateChanged(newBaseState));
+ execute("notifyBaseStateChanged",
+ () -> mDeviceStateCallback.onBaseStateChanged(newBaseState));
}
void notifyStateChanged(int newDeviceState) {
- mExecutor.execute(() -> mDeviceStateCallback.onStateChanged(newDeviceState));
+ execute("notifyStateChanged",
+ () -> mDeviceStateCallback.onStateChanged(newDeviceState));
+ }
+
+ private void execute(String traceName, Runnable r) {
+ mExecutor.execute(() -> {
+ if (DEBUG) {
+ Trace.beginSection(
+ mDeviceStateCallback.getClass().getSimpleName() + "#" + traceName);
+ }
+ try {
+ r.run();
+ } finally {
+ if (DEBUG) {
+ Trace.endSection();
+ }
+ }
+ });
}
}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 4791a83..f71e853 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -721,8 +721,19 @@
public interface DisplayOffloadSession {
/** Provide the display state to use in place of state DOZE. */
void setDozeStateOverride(int displayState);
- /** Returns the associated DisplayOffloader. */
- DisplayOffloader getDisplayOffloader();
+
+ /** Whether the session is active. */
+ boolean isActive();
+
+ /**
+ * Update the brightness from the offload chip.
+ * @param brightness The brightness value between {@link PowerManager.BRIGHTNESS_MIN} and
+ * {@link PowerManager.BRIGHTNESS_MAX}, or
+ * {@link PowerManager.BRIGHTNESS_INVALID_FLOAT} which removes
+ * the brightness from offload. Other values will be ignored.
+ */
+ void updateBrightness(float brightness);
+
/** Returns whether displayoffload supports the given display state. */
static boolean isSupportedOffloadState(int displayState) {
return Display.isSuspendedState(displayState);
diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java
index 19ba6a1..dbb3127 100644
--- a/core/java/android/net/NetworkStack.java
+++ b/core/java/android/net/NetworkStack.java
@@ -23,7 +23,6 @@
import android.os.IBinder;
import android.os.ServiceManager;
-import com.android.net.flags.Flags;
import com.android.net.module.util.PermissionUtils;
/**
* Constants and utilities for client code communicating with the network stack service.
@@ -104,16 +103,4 @@
final @NonNull String... otherPermissions) {
PermissionUtils.enforceNetworkStackPermissionOr(context, otherPermissions);
}
-
- /**
- * Get setting of the "set_data_saver_via_cm" flag.
- *
- * @hide
- */
- // A workaround for aconfig. Currently, aconfig value read from platform and mainline code can
- // be inconsistent. To avoid the problem, CTS for mainline code can get the flag value by this
- // method.
- public static boolean getDataSaverViaCmFlag() {
- return Flags.setDataSaverViaCm();
- }
}
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index f817fb8..1100731 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -82,8 +82,8 @@
private static final int MAIN_INDEX_SIZE = 1 << LOG_MAIN_INDEX_SIZE;
private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1;
/**
- * Debuggable builds will throw an BinderProxyMapSizeException if the number of
- * map entries exceeds:
+ * We will throw a BinderProxyMapSizeException if the number of map entries
+ * exceeds:
*/
private static final int CRASH_AT_SIZE = 25_000;
diff --git a/core/java/android/os/IHintSession.aidl b/core/java/android/os/IHintSession.aidl
index fe85da2..6b43e73 100644
--- a/core/java/android/os/IHintSession.aidl
+++ b/core/java/android/os/IHintSession.aidl
@@ -17,8 +17,6 @@
package android.os;
-import android.os.WorkDuration;
-
/** {@hide} */
oneway interface IHintSession {
void updateTargetWorkDuration(long targetDurationNanos);
@@ -26,5 +24,4 @@
void close();
void sendHint(int hint);
void setMode(int mode, boolean enabled);
- void reportActualWorkDuration2(in WorkDuration[] workDurations);
}
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index e005910..11084b8 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -103,7 +103,7 @@
* Any call in this class will change its internal data, so you must do your own thread
* safety to protect from racing.
*
- * All timings should be in {@link SystemClock#uptimeNanos()}.
+ * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
*/
public static class Session implements Closeable {
private long mNativeSessionPtr;
@@ -269,40 +269,6 @@
public @Nullable int[] getThreadIds() {
return nativeGetThreadIds(mNativeSessionPtr);
}
-
- /**
- * Reports the work duration for the last cycle of work.
- *
- * The system will attempt to adjust the core placement of the threads within the thread
- * group and/or the frequency of the core on which they are run to bring the actual duration
- * close to the target duration.
- *
- * @param workDuration the work duration of each component.
- * @throws IllegalArgumentException if work period start timestamp is not positive, or
- * actual total duration is not positive, or actual CPU duration is not positive,
- * or actual GPU duration is negative.
- */
- @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
- public void reportActualWorkDuration(@NonNull WorkDuration workDuration) {
- if (workDuration.mWorkPeriodStartTimestampNanos <= 0) {
- throw new IllegalArgumentException(
- "the work period start timestamp should be positive.");
- }
- if (workDuration.mActualTotalDurationNanos <= 0) {
- throw new IllegalArgumentException("the actual total duration should be positive.");
- }
- if (workDuration.mActualCpuDurationNanos <= 0) {
- throw new IllegalArgumentException("the actual CPU duration should be positive.");
- }
- if (workDuration.mActualGpuDurationNanos < 0) {
- throw new IllegalArgumentException(
- "the actual GPU duration should be non negative.");
- }
- nativeReportActualWorkDuration(mNativeSessionPtr,
- workDuration.mWorkPeriodStartTimestampNanos,
- workDuration.mActualTotalDurationNanos,
- workDuration.mActualCpuDurationNanos, workDuration.mActualGpuDurationNanos);
- }
}
private static native long nativeAcquireManager();
@@ -319,7 +285,4 @@
private static native void nativeSetThreads(long nativeSessionPtr, int[] tids);
private static native void nativeSetPreferPowerEfficiency(long nativeSessionPtr,
boolean enabled);
- private static native void nativeReportActualWorkDuration(long nativeSessionPtr,
- long workPeriodStartTimestampNanos, long actualTotalDurationNanos,
- long actualCpuDurationNanos, long actualGpuDurationNanos);
}
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index e2a5833..49a0bd3 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -16,7 +16,6 @@
package android.os;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.app.IAlarmManager;
import android.app.time.UnixEpochTime;
@@ -203,8 +202,8 @@
* Returns nanoseconds since boot, not counting time spent in deep sleep.
*
* @return nanoseconds of non-sleep uptime since boot.
+ * @hide
*/
- @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
@CriticalNative
@android.ravenwood.annotation.RavenwoodReplace
public static native long uptimeNanos();
diff --git a/core/java/android/os/WorkDuration.java b/core/java/android/os/WorkDuration.java
deleted file mode 100644
index 4fdc34f..0000000
--- a/core/java/android/os/WorkDuration.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-
-import java.util.Objects;
-
-/**
- * WorkDuration contains the measured time in nano seconds of the workload
- * in each component, see
- * {@link PerformanceHintManager.Session#reportActualWorkDuration(WorkDuration)}.
- *
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
- */
-@FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
-public final class WorkDuration implements Parcelable {
- long mWorkPeriodStartTimestampNanos = 0;
- long mActualTotalDurationNanos = 0;
- long mActualCpuDurationNanos = 0;
- long mActualGpuDurationNanos = 0;
- long mTimestampNanos = 0;
-
- public static final @NonNull Creator<WorkDuration> CREATOR = new Creator<>() {
- @Override
- public WorkDuration createFromParcel(Parcel in) {
- return new WorkDuration(in);
- }
-
- @Override
- public WorkDuration[] newArray(int size) {
- return new WorkDuration[size];
- }
- };
-
- public WorkDuration() {}
-
- public WorkDuration(long workPeriodStartTimestampNanos,
- long actualTotalDurationNanos,
- long actualCpuDurationNanos,
- long actualGpuDurationNanos) {
- mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos;
- mActualTotalDurationNanos = actualTotalDurationNanos;
- mActualCpuDurationNanos = actualCpuDurationNanos;
- mActualGpuDurationNanos = actualGpuDurationNanos;
- }
-
- /**
- * @hide
- */
- public WorkDuration(long workPeriodStartTimestampNanos,
- long actualTotalDurationNanos,
- long actualCpuDurationNanos,
- long actualGpuDurationNanos,
- long timestampNanos) {
- mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos;
- mActualTotalDurationNanos = actualTotalDurationNanos;
- mActualCpuDurationNanos = actualCpuDurationNanos;
- mActualGpuDurationNanos = actualGpuDurationNanos;
- mTimestampNanos = timestampNanos;
- }
-
- WorkDuration(@NonNull Parcel in) {
- mWorkPeriodStartTimestampNanos = in.readLong();
- mActualTotalDurationNanos = in.readLong();
- mActualCpuDurationNanos = in.readLong();
- mActualGpuDurationNanos = in.readLong();
- mTimestampNanos = in.readLong();
- }
-
- /**
- * Sets the work period start timestamp in nanoseconds.
- *
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
- */
- public void setWorkPeriodStartTimestampNanos(long workPeriodStartTimestampNanos) {
- if (workPeriodStartTimestampNanos <= 0) {
- throw new IllegalArgumentException(
- "the work period start timestamp should be positive.");
- }
- mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos;
- }
-
- /**
- * Sets the actual total duration in nanoseconds.
- *
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
- */
- public void setActualTotalDurationNanos(long actualTotalDurationNanos) {
- if (actualTotalDurationNanos <= 0) {
- throw new IllegalArgumentException("the actual total duration should be positive.");
- }
- mActualTotalDurationNanos = actualTotalDurationNanos;
- }
-
- /**
- * Sets the actual CPU duration in nanoseconds.
- *
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
- */
- public void setActualCpuDurationNanos(long actualCpuDurationNanos) {
- if (actualCpuDurationNanos <= 0) {
- throw new IllegalArgumentException("the actual CPU duration should be positive.");
- }
- mActualCpuDurationNanos = actualCpuDurationNanos;
- }
-
- /**
- * Sets the actual GPU duration in nanoseconds.
- *
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
- */
- public void setActualGpuDurationNanos(long actualGpuDurationNanos) {
- if (actualGpuDurationNanos < 0) {
- throw new IllegalArgumentException("the actual GPU duration should be non negative.");
- }
- mActualGpuDurationNanos = actualGpuDurationNanos;
- }
-
- /**
- * Returns the work period start timestamp based in nanoseconds.
- *
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
- */
- public long getWorkPeriodStartTimestampNanos() {
- return mWorkPeriodStartTimestampNanos;
- }
-
- /**
- * Returns the actual total duration in nanoseconds.
- *
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
- */
- public long getActualTotalDurationNanos() {
- return mActualTotalDurationNanos;
- }
-
- /**
- * Returns the actual CPU duration in nanoseconds.
- *
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
- */
- public long getActualCpuDurationNanos() {
- return mActualCpuDurationNanos;
- }
-
- /**
- * Returns the actual GPU duration in nanoseconds.
- *
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
- */
- public long getActualGpuDurationNanos() {
- return mActualGpuDurationNanos;
- }
-
- /**
- * @hide
- */
- public long getTimestampNanos() {
- return mTimestampNanos;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeLong(mWorkPeriodStartTimestampNanos);
- dest.writeLong(mActualTotalDurationNanos);
- dest.writeLong(mActualCpuDurationNanos);
- dest.writeLong(mActualGpuDurationNanos);
- dest.writeLong(mTimestampNanos);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj == this) {
- return true;
- }
- if (!(obj instanceof WorkDuration)) {
- return false;
- }
- WorkDuration workDuration = (WorkDuration) obj;
- return workDuration.mTimestampNanos == this.mTimestampNanos
- && workDuration.mWorkPeriodStartTimestampNanos == this.mWorkPeriodStartTimestampNanos
- && workDuration.mActualTotalDurationNanos == this.mActualTotalDurationNanos
- && workDuration.mActualCpuDurationNanos == this.mActualCpuDurationNanos
- && workDuration.mActualGpuDurationNanos == this.mActualGpuDurationNanos;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mWorkPeriodStartTimestampNanos, mActualTotalDurationNanos,
- mActualCpuDurationNanos, mActualGpuDurationNanos, mTimestampNanos);
- }
-}
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index d405d1d..a78f221 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -61,11 +61,4 @@
namespace: "backstage_power"
description: "Guards a new API in PowerManager to check if battery saver is supported or not."
bug: "305067031"
-}
-
-flag {
- name: "adpf_gpu_report_actual_work_duration"
- namespace: "game"
- description: "Guards the ADPF GPU APIs."
- bug: "284324521"
-}
+}
\ No newline at end of file
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 33c15d77..ff6ec29 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -36,6 +37,7 @@
import android.app.AppOpsManager;
import android.app.Application;
import android.app.AutomaticZenRule;
+import android.app.Flags;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.SearchManager;
@@ -1904,6 +1906,36 @@
= "android.settings.ACTION_CONDITION_PROVIDER_SETTINGS";
/**
+ * Activity Action: Shows the settings page for an {@link AutomaticZenRule} mode.
+ * <p>
+ * Users can change the behavior of the mode when it's activated and access the owning app's
+ * additional configuration screen, where triggering criteria can be modified (see
+ * {@link AutomaticZenRule#setConfigurationActivity(ComponentName)}).
+ * <p>
+ * A matching Activity will only be found if
+ * {@link NotificationManager#areAutomaticZenRulesUserManaged()} is true.
+ * <p>
+ * Input: Intent's data URI set with an application name, using the "package" schema (like
+ * "package:com.my.app").
+ * Input: The id of the rule, provided in {@link #EXTRA_AUTOMATIC_ZEN_RULE_ID}.
+ * <p>
+ * Output: Nothing.
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_AUTOMATIC_ZEN_RULE_SETTINGS
+ = "android.settings.AUTOMATIC_ZEN_RULE_SETTINGS";
+
+ /**
+ * Activity Extra: The String id of the {@link AutomaticZenRule mode} settings to display.
+ * <p>
+ * This must be passed as an extra field to the {@link #ACTION_AUTOMATIC_ZEN_RULE_SETTINGS}.
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final String EXTRA_AUTOMATIC_ZEN_RULE_ID
+ = "android.provider.extra.AUTOMATIC_ZEN_RULE_ID";
+
+ /**
* Activity Action: Show settings for video captioning.
* <p>
* In some cases, a matching Activity may not exist, so ensure you safeguard
diff --git a/core/java/android/service/notification/OWNERS b/core/java/android/service/notification/OWNERS
index bb0e6ab..cb0b5fa 100644
--- a/core/java/android/service/notification/OWNERS
+++ b/core/java/android/service/notification/OWNERS
@@ -2,6 +2,7 @@
juliacr@google.com
yurilin@google.com
+matiashe@google.com
jeffdq@google.com
dsandler@android.com
dsandler@google.com
diff --git a/core/java/android/service/timezone/TEST_MAPPING b/core/java/android/service/timezone/TEST_MAPPING
index 21a8eab..e5910ea 100644
--- a/core/java/android/service/timezone/TEST_MAPPING
+++ b/core/java/android/service/timezone/TEST_MAPPING
@@ -1,6 +1,5 @@
{
- // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
- "postsubmit": [
+ "presubmit": [
{
"name": "FrameworksTimeCoreTests",
"options": [
@@ -8,7 +7,10 @@
"include-filter": "android.service."
}
]
- },
+ }
+ ],
+ // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+ "postsubmit": [
{
"name": "CtsLocationTimeZoneManagerHostTest"
}
diff --git a/core/java/android/util/DataUnit.java b/core/java/android/util/DataUnit.java
index cc33af3..10905e1 100644
--- a/core/java/android/util/DataUnit.java
+++ b/core/java/android/util/DataUnit.java
@@ -32,6 +32,7 @@
*
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public enum DataUnit {
KILOBYTES { @Override public long toBytes(long v) { return v * 1_000; } },
MEGABYTES { @Override public long toBytes(long v) { return v * 1_000_000; } },
diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java
index 4654dbf..d2c5975 100644
--- a/core/java/android/util/EventLog.java
+++ b/core/java/android/util/EventLog.java
@@ -48,6 +48,9 @@
* They carry a payload of one or more int, long, or String values. The
* event-log-tags file defines the payload contents for each type code.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
+ "com.android.hoststubgen.nativesubstitution.EventLog_host")
public class EventLog {
/** @hide */ public EventLog() {}
@@ -416,6 +419,7 @@
/**
* Read TAGS_FILE, populating sTagCodes and sTagNames, if not already done.
*/
+ @android.ravenwood.annotation.RavenwoodReplace
private static synchronized void readTagsFile() {
if (sTagCodes != null && sTagNames != null) return;
@@ -441,8 +445,7 @@
try {
int num = Integer.parseInt(m.group(1));
String name = m.group(2);
- sTagCodes.put(name, num);
- sTagNames.put(num, name);
+ registerTagLocked(num, name);
} catch (NumberFormatException e) {
Log.wtf(TAG, "Error in " + TAGS_FILE + ": " + line, e);
}
@@ -454,4 +457,20 @@
try { if (reader != null) reader.close(); } catch (IOException e) {}
}
}
+
+ private static void registerTagLocked(int num, String name) {
+ sTagCodes.put(name, num);
+ sTagNames.put(num, name);
+ }
+
+ private static synchronized void readTagsFile$ravenwood() {
+ // TODO: restore parsing logic once we carry into runtime
+ sTagCodes = new HashMap<String, Integer>();
+ sTagNames = new HashMap<Integer, String>();
+
+ // Hard-code a few common tags
+ registerTagLocked(524288, "sysui_action");
+ registerTagLocked(524290, "sysui_count");
+ registerTagLocked(524291, "sysui_histogram");
+ }
}
diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java
index c04a71c..413ae1f 100644
--- a/core/java/android/util/IntArray.java
+++ b/core/java/android/util/IntArray.java
@@ -26,6 +26,7 @@
*
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class IntArray implements Cloneable {
private static final int MIN_CAPACITY_INCREMENT = 12;
diff --git a/core/java/android/util/LongArray.java b/core/java/android/util/LongArray.java
index 3101c0d..4c7ef08 100644
--- a/core/java/android/util/LongArray.java
+++ b/core/java/android/util/LongArray.java
@@ -30,6 +30,7 @@
*
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class LongArray implements Cloneable {
private static final int MIN_CAPACITY_INCREMENT = 12;
diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java
index 3aeecca..c0ceb9ea 100644
--- a/core/java/android/util/Slog.java
+++ b/core/java/android/util/Slog.java
@@ -31,6 +31,7 @@
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class Slog {
private Slog() {
@@ -216,6 +217,7 @@
* @see Log#wtf(String, String)
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @android.ravenwood.annotation.RavenwoodThrow
public static int wtf(@Nullable String tag, @NonNull String msg) {
return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, false, true);
}
@@ -223,6 +225,7 @@
/**
* Similar to {@link #wtf(String, String)}, but does not output anything to the log.
*/
+ @android.ravenwood.annotation.RavenwoodThrow
public static void wtfQuiet(@Nullable String tag, @NonNull String msg) {
Log.wtfQuiet(Log.LOG_ID_SYSTEM, tag, msg, true);
}
@@ -241,6 +244,7 @@
* @see Log#wtfStack(String, String)
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ @android.ravenwood.annotation.RavenwoodThrow
public static int wtfStack(@Nullable String tag, @NonNull String msg) {
return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, true, true);
}
@@ -259,6 +263,7 @@
*
* @see Log#wtf(String, Throwable)
*/
+ @android.ravenwood.annotation.RavenwoodThrow
public static int wtf(@Nullable String tag, @Nullable Throwable tr) {
return Log.wtf(Log.LOG_ID_SYSTEM, tag, tr.getMessage(), tr, false, true);
}
@@ -279,6 +284,7 @@
* @see Log#wtf(String, String, Throwable)
*/
@UnsupportedAppUsage
+ @android.ravenwood.annotation.RavenwoodThrow
public static int wtf(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, tr, false, true);
}
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index d06b0ce..bff8db1 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -41,6 +41,8 @@
/**
* A class containing utility methods related to time zones.
*/
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
+@android.ravenwood.annotation.RavenwoodKeepStaticInitializer
public class TimeUtils {
/** @hide */ public TimeUtils() {}
/** {@hide} */
@@ -180,6 +182,7 @@
private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
private static char[] sTmpFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
+ @android.ravenwood.annotation.RavenwoodKeep
static private int accumField(int amt, int suffix, boolean always, int zeropad) {
if (amt > 999) {
int num = 0;
@@ -202,6 +205,7 @@
return 0;
}
+ @android.ravenwood.annotation.RavenwoodKeep
static private int printFieldLocked(char[] formatStr, int amt, char suffix, int pos,
boolean always, int zeropad) {
if (always || amt > 0) {
@@ -242,6 +246,7 @@
return pos;
}
+ @android.ravenwood.annotation.RavenwoodKeep
private static int formatDurationLocked(long duration, int fieldLen) {
if (sFormatStr.length < fieldLen) {
sFormatStr = new char[fieldLen];
@@ -314,6 +319,7 @@
}
/** @hide Just for debugging; not internationalized. */
+ @android.ravenwood.annotation.RavenwoodKeep
public static void formatDuration(long duration, StringBuilder builder) {
synchronized (sFormatSync) {
int len = formatDurationLocked(duration, 0);
@@ -322,6 +328,7 @@
}
/** @hide Just for debugging; not internationalized. */
+ @android.ravenwood.annotation.RavenwoodKeep
public static void formatDuration(long duration, StringBuilder builder, int fieldLen) {
synchronized (sFormatSync) {
int len = formatDurationLocked(duration, fieldLen);
@@ -331,6 +338,7 @@
/** @hide Just for debugging; not internationalized. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ @android.ravenwood.annotation.RavenwoodKeep
public static void formatDuration(long duration, PrintWriter pw, int fieldLen) {
synchronized (sFormatSync) {
int len = formatDurationLocked(duration, fieldLen);
@@ -340,6 +348,7 @@
/** @hide Just for debugging; not internationalized. */
@TestApi
+ @android.ravenwood.annotation.RavenwoodKeep
public static String formatDuration(long duration) {
synchronized (sFormatSync) {
int len = formatDurationLocked(duration, 0);
@@ -349,11 +358,13 @@
/** @hide Just for debugging; not internationalized. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ @android.ravenwood.annotation.RavenwoodKeep
public static void formatDuration(long duration, PrintWriter pw) {
formatDuration(duration, pw, 0);
}
/** @hide Just for debugging; not internationalized. */
+ @android.ravenwood.annotation.RavenwoodKeep
public static void formatDuration(long time, long now, StringBuilder sb) {
if (time == 0) {
sb.append("--");
@@ -363,6 +374,7 @@
}
/** @hide Just for debugging; not internationalized. */
+ @android.ravenwood.annotation.RavenwoodKeep
public static void formatDuration(long time, long now, PrintWriter pw) {
if (time == 0) {
pw.print("--");
@@ -372,16 +384,19 @@
}
/** @hide Just for debugging; not internationalized. */
+ @android.ravenwood.annotation.RavenwoodKeep
public static String formatUptime(long time) {
return formatTime(time, SystemClock.uptimeMillis());
}
/** @hide Just for debugging; not internationalized. */
+ @android.ravenwood.annotation.RavenwoodKeep
public static String formatRealtime(long time) {
return formatTime(time, SystemClock.elapsedRealtime());
}
/** @hide Just for debugging; not internationalized. */
+ @android.ravenwood.annotation.RavenwoodKeep
public static String formatTime(long time, long referenceTime) {
long diff = time - referenceTime;
if (diff > 0) {
@@ -402,6 +417,7 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @android.ravenwood.annotation.RavenwoodKeep
public static String logTimeOfDay(long millis) {
Calendar c = Calendar.getInstance();
if (millis >= 0) {
@@ -413,6 +429,7 @@
}
/** {@hide} */
+ @android.ravenwood.annotation.RavenwoodKeep
public static String formatForLogging(long millis) {
if (millis <= 0) {
return "unknown";
@@ -426,6 +443,7 @@
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static void dumpTime(PrintWriter pw, long time) {
pw.print(sDumpDateFormat.format(new Date(time)));
}
@@ -457,6 +475,7 @@
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static void dumpTimeWithDelta(PrintWriter pw, long time, long now) {
pw.print(sDumpDateFormat.format(new Date(time)));
if (time == now) {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 43bfe13..53aed49 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -23,7 +23,7 @@
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
-import android.annotation.Hide;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -752,6 +752,7 @@
* {@link #isGranularScrollingSupported()} to check if granular scrolling is supported.
* </p>
*/
+ @FlaggedApi(Flags.FLAG_GRANULAR_SCROLLING)
public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT =
"android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT";
@@ -2608,6 +2609,7 @@
* @return True if all scroll actions that could support
* {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT} have done so, false otherwise.
*/
+ @FlaggedApi(Flags.FLAG_GRANULAR_SCROLLING)
public boolean isGranularScrollingSupported() {
return getBooleanProperty(BOOLEAN_PROPERTY_SUPPORTS_GRANULAR_SCROLLING);
}
@@ -2626,6 +2628,7 @@
*
* @throws IllegalStateException If called from an AccessibilityService.
*/
+ @FlaggedApi(Flags.FLAG_GRANULAR_SCROLLING)
public void setGranularScrollingSupported(boolean granularScrollingSupported) {
setBooleanProperty(BOOLEAN_PROPERTY_SUPPORTS_GRANULAR_SCROLLING,
granularScrollingSupported);
@@ -6119,6 +6122,7 @@
* This should be used for {@code mItemCount} and
* {@code mImportantForAccessibilityItemCount} when values for those fields are not known.
*/
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public static final int UNDEFINED = -1;
private int mRowCount;
@@ -6229,8 +6233,8 @@
* the item count is not known.
* @param importantForAccessibilityItemCount The count of the collection's views considered
* important for accessibility.
+ * @hide
*/
- @Hide
public CollectionInfo(int rowCount, int columnCount, boolean hierarchical,
int selectionMode, int itemCount, int importantForAccessibilityItemCount) {
mRowCount = rowCount;
@@ -6287,6 +6291,7 @@
*
* @return The count of items, which may be {@code UNDEFINED} if the count is not known.
*/
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public int getItemCount() {
return mItemCount;
}
@@ -6297,6 +6302,7 @@
* @return The count of items important for accessibility, which may be {@code UNDEFINED}
* if the count is not known.
*/
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public int getImportantForAccessibilityItemCount() {
return mImportantForAccessibilityItemCount;
}
@@ -6323,6 +6329,7 @@
* The builder for CollectionInfo.
*/
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public static final class Builder {
private int mRowCount = 0;
private int mColumnCount = 0;
@@ -6334,6 +6341,7 @@
/**
* Creates a new Builder.
*/
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public Builder() {
}
@@ -6343,6 +6351,7 @@
* @return This builder.
*/
@NonNull
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public CollectionInfo.Builder setRowCount(int rowCount) {
mRowCount = rowCount;
return this;
@@ -6354,6 +6363,7 @@
* @return This builder.
*/
@NonNull
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public CollectionInfo.Builder setColumnCount(int columnCount) {
mColumnCount = columnCount;
return this;
@@ -6364,6 +6374,7 @@
* @return This builder.
*/
@NonNull
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public CollectionInfo.Builder setHierarchical(boolean hierarchical) {
mHierarchical = hierarchical;
return this;
@@ -6375,6 +6386,7 @@
* @return This builder.
*/
@NonNull
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public CollectionInfo.Builder setSelectionMode(int selectionMode) {
mSelectionMode = selectionMode;
return this;
@@ -6389,6 +6401,7 @@
* @return This builder.
*/
@NonNull
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public CollectionInfo.Builder setItemCount(int itemCount) {
mItemCount = itemCount;
return this;
@@ -6401,6 +6414,7 @@
* @return This builder.
*/
@NonNull
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public CollectionInfo.Builder setImportantForAccessibilityItemCount(
int importantForAccessibilityItemCount) {
mImportantForAccessibilityItemCount = importantForAccessibilityItemCount;
@@ -6411,6 +6425,7 @@
* Creates a new {@link CollectionInfo} instance.
*/
@NonNull
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public CollectionInfo build() {
CollectionInfo collectionInfo = new CollectionInfo(mRowCount, mColumnCount,
mHierarchical);
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 950fa4b..c337cb4 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -17,6 +17,13 @@
}
flag {
+ namespace: "accessibility"
+ name: "collection_info_item_counts"
+ description: "Fields for total items and the number of important for accessibility items in a collection"
+ bug: "302376158"
+}
+
+flag {
name: "deduplicate_accessibility_warning_dialog"
namespace: "accessibility"
description: "Removes duplicate definition of the accessibility warning dialog."
@@ -39,6 +46,13 @@
flag {
namespace: "accessibility"
+ name: "granular_scrolling"
+ description: "Allow the use of granular scrolling. This allows scrollable nodes to scroll by increments other than a full screen"
+ bug: "302376158"
+}
+
+flag {
+ namespace: "accessibility"
name: "update_always_on_a11y_service"
description: "Updates the Always-On A11yService state when the user changes the enablement of the shortcut."
bug: "298869916"
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 42a1f1e..0da03fb 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -37,5 +37,14 @@
namespace: "window_surfaces"
name: "remove_capture_display"
description: "Remove uses of ScreenCapture#captureDisplay"
+ is_fixed_read_only: true
bug: "293445881"
-}
\ No newline at end of file
+}
+
+flag {
+ namespace: "window_surfaces"
+ name: "allow_disable_activity_record_input_sink"
+ description: "Whether to allow system activity to disable ActivityRecordInputSink"
+ is_fixed_read_only: true
+ bug: "262477923"
+}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index b600b22..933cc49 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -9,6 +9,16 @@
bug: "260873529"
}
+# Using a fixed read only flag because there are ClientTransaction scheduling before
+# WindowManagerService creation.
+flag {
+ namespace: "windowing_sdk"
+ name: "bundle_client_transaction_flag"
+ description: "To bundle multiple ClientTransactionItems into one ClientTransaction"
+ bug: "260873529"
+ is_fixed_read_only: true
+}
+
flag {
namespace: "windowing_sdk"
name: "activity_embedding_overlay_presentation_flag"
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index df6c153..7be27be 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -54,22 +54,6 @@
*/
public static final class NotificationFlags {
- /**
- * FOR DEVELOPMENT / TESTING ONLY!!!
- * Forcibly demote *ALL* FSI notifications as if no apps have the app op permission.
- * NOTE: enabling this implies SHOW_STICKY_HUN_FOR_DENIED_FSI in SystemUI
- */
- public static final Flag FSI_FORCE_DEMOTE =
- devFlag("persist.sysui.notification.fsi_force_demote");
-
- /** Gating the feature which shows FSI-denied notifications as Sticky HUNs */
- public static final Flag SHOW_STICKY_HUN_FOR_DENIED_FSI =
- releasedFlag("persist.sysui.notification.show_sticky_hun_for_denied_fsi");
-
- /** Gating the redaction of OTP notifications on the lockscreen */
- public static final Flag OTP_REDACTION =
- devFlag("persist.sysui.notification.otp_redaction");
-
/** Gating the logging of DND state change events. */
public static final Flag LOG_DND_STATE_EVENTS =
releasedFlag("persist.sysui.notification.log_dnd_state_events");
diff --git a/core/jni/android_os_PerformanceHintManager.cpp b/core/jni/android_os_PerformanceHintManager.cpp
index aebe7ea..95bf49f 100644
--- a/core/jni/android_os_PerformanceHintManager.cpp
+++ b/core/jni/android_os_PerformanceHintManager.cpp
@@ -16,16 +16,15 @@
#define LOG_TAG "PerfHint-jni"
-#include <android/performance_hint.h>
+#include "jni.h"
+
#include <dlfcn.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <utils/Log.h>
-
#include <vector>
#include "core_jni_helpers.h"
-#include "jni.h"
namespace android {
@@ -45,11 +44,6 @@
typedef int (*APH_setThreads)(APerformanceHintSession*, const pid_t*, size_t);
typedef void (*APH_getThreadIds)(APerformanceHintSession*, int32_t* const, size_t* const);
typedef void (*APH_setPreferPowerEfficiency)(APerformanceHintSession*, bool);
-typedef void (*APH_reportActualWorkDuration2)(APerformanceHintSession*, AWorkDuration*);
-
-typedef AWorkDuration* (*AWD_create)();
-typedef void (*AWD_setTimeNanos)(AWorkDuration*, int64_t);
-typedef void (*AWD_release)(AWorkDuration*);
bool gAPerformanceHintBindingInitialized = false;
APH_getManager gAPH_getManagerFn = nullptr;
@@ -62,14 +56,6 @@
APH_setThreads gAPH_setThreadsFn = nullptr;
APH_getThreadIds gAPH_getThreadIdsFn = nullptr;
APH_setPreferPowerEfficiency gAPH_setPreferPowerEfficiencyFn = nullptr;
-APH_reportActualWorkDuration2 gAPH_reportActualWorkDuration2Fn = nullptr;
-
-AWD_create gAWD_createFn = nullptr;
-AWD_setTimeNanos gAWD_setWorkPeriodStartTimestampNanosFn = nullptr;
-AWD_setTimeNanos gAWD_setActualTotalDurationNanosFn = nullptr;
-AWD_setTimeNanos gAWD_setActualCpuDurationNanosFn = nullptr;
-AWD_setTimeNanos gAWD_setActualGpuDurationNanosFn = nullptr;
-AWD_release gAWD_releaseFn = nullptr;
void ensureAPerformanceHintBindingInitialized() {
if (gAPerformanceHintBindingInitialized) return;
@@ -126,46 +112,9 @@
(APH_setPreferPowerEfficiency)dlsym(handle_,
"APerformanceHint_setPreferPowerEfficiency");
LOG_ALWAYS_FATAL_IF(gAPH_setPreferPowerEfficiencyFn == nullptr,
- "Failed to find required symbol "
+ "Failed to find required symbol"
"APerformanceHint_setPreferPowerEfficiency!");
- gAPH_reportActualWorkDuration2Fn =
- (APH_reportActualWorkDuration2)dlsym(handle_,
- "APerformanceHint_reportActualWorkDuration2");
- LOG_ALWAYS_FATAL_IF(gAPH_reportActualWorkDuration2Fn == nullptr,
- "Failed to find required symbol "
- "APerformanceHint_reportActualWorkDuration2!");
-
- gAWD_createFn = (AWD_create)dlsym(handle_, "AWorkDuration_create");
- LOG_ALWAYS_FATAL_IF(gAWD_createFn == nullptr,
- "Failed to find required symbol AWorkDuration_create!");
-
- gAWD_setWorkPeriodStartTimestampNanosFn =
- (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setWorkPeriodStartTimestampNanos");
- LOG_ALWAYS_FATAL_IF(gAWD_setWorkPeriodStartTimestampNanosFn == nullptr,
- "Failed to find required symbol "
- "AWorkDuration_setWorkPeriodStartTimestampNanos!");
-
- gAWD_setActualTotalDurationNanosFn =
- (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualTotalDurationNanos");
- LOG_ALWAYS_FATAL_IF(gAWD_setActualTotalDurationNanosFn == nullptr,
- "Failed to find required symbol "
- "AWorkDuration_setActualTotalDurationNanos!");
-
- gAWD_setActualCpuDurationNanosFn =
- (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualCpuDurationNanos");
- LOG_ALWAYS_FATAL_IF(gAWD_setActualCpuDurationNanosFn == nullptr,
- "Failed to find required symbol AWorkDuration_setActualCpuDurationNanos!");
-
- gAWD_setActualGpuDurationNanosFn =
- (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualGpuDurationNanos");
- LOG_ALWAYS_FATAL_IF(gAWD_setActualGpuDurationNanosFn == nullptr,
- "Failed to find required symbol AWorkDuration_setActualGpuDurationNanos!");
-
- gAWD_releaseFn = (AWD_release)dlsym(handle_, "AWorkDuration_release");
- LOG_ALWAYS_FATAL_IF(gAWD_releaseFn == nullptr,
- "Failed to find required symbol AWorkDuration_release!");
-
gAPerformanceHintBindingInitialized = true;
}
@@ -289,25 +238,6 @@
enabled);
}
-static void nativeReportActualWorkDuration2(JNIEnv* env, jclass clazz, jlong nativeSessionPtr,
- jlong workPeriodStartTimestampNanos,
- jlong actualTotalDurationNanos,
- jlong actualCpuDurationNanos,
- jlong actualGpuDurationNanos) {
- ensureAPerformanceHintBindingInitialized();
-
- AWorkDuration* workDuration = gAWD_createFn();
- gAWD_setWorkPeriodStartTimestampNanosFn(workDuration, workPeriodStartTimestampNanos);
- gAWD_setActualTotalDurationNanosFn(workDuration, actualTotalDurationNanos);
- gAWD_setActualCpuDurationNanosFn(workDuration, actualCpuDurationNanos);
- gAWD_setActualGpuDurationNanosFn(workDuration, actualGpuDurationNanos);
-
- gAPH_reportActualWorkDuration2Fn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr),
- workDuration);
-
- gAWD_releaseFn(workDuration);
-}
-
static const JNINativeMethod gPerformanceHintMethods[] = {
{"nativeAcquireManager", "()J", (void*)nativeAcquireManager},
{"nativeGetPreferredUpdateRateNanos", "(J)J", (void*)nativeGetPreferredUpdateRateNanos},
@@ -319,7 +249,6 @@
{"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads},
{"nativeGetThreadIds", "(J)[I", (void*)nativeGetThreadIds},
{"nativeSetPreferPowerEfficiency", "(JZ)V", (void*)nativeSetPreferPowerEfficiency},
- {"nativeReportActualWorkDuration", "(JJJJJ)V", (void*)nativeReportActualWorkDuration2},
};
int register_android_os_PerformanceHintManager(JNIEnv* env) {
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 5c1d91f..5b68e8e 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -184,11 +184,11 @@
void NativeInputEventReceiver::setFdEvents(int events) {
if (mFdEvents != events) {
mFdEvents = events;
- int fd = mInputConsumer.getChannel()->getFd();
+ auto&& fd = mInputConsumer.getChannel()->getFd();
if (events) {
- mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr);
+ mMessageQueue->getLooper()->addFd(fd.get(), 0, events, this, nullptr);
} else {
- mMessageQueue->getLooper()->removeFd(fd);
+ mMessageQueue->getLooper()->removeFd(fd.get());
}
}
}
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 833952d..6bdf821 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -102,8 +102,8 @@
}
status_t NativeInputEventSender::initialize() {
- int receiveFd = mInputPublisher.getChannel()->getFd();
- mMessageQueue->getLooper()->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, this, NULL);
+ auto&& receiveFd = mInputPublisher.getChannel()->getFd();
+ mMessageQueue->getLooper()->addFd(receiveFd.get(), 0, ALOOPER_EVENT_INPUT, this, NULL);
return OK;
}
@@ -112,7 +112,7 @@
LOG(DEBUG) << "channel '" << getInputChannelName() << "' ~ Disposing input event sender.";
}
- mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd());
+ mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd().get());
}
status_t NativeInputEventSender::sendKeyEvent(uint32_t seq, const KeyEvent* event) {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 68bad45..6cd6eb4 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5465,6 +5465,7 @@
<item>1,1,1.0,.15,15</item>
<item>0,0,0.7,0,1</item>
<item>0,0,0.83333,0,1</item>
+ <item>0,0,1.1667,0,1</item>
</string-array>
<!-- The integer index of the selected option in config_udfps_touch_detection_options -->
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 4b02257..2327b20 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -42,6 +42,7 @@
import android.app.PictureInPictureParams;
import android.app.ResourcesManager;
import android.app.servertransaction.ActivityConfigurationChangeItem;
+import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
@@ -73,6 +74,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.content.ReferrerIntent;
+import com.android.window.flags.Flags;
import org.junit.After;
import org.junit.Before;
@@ -227,7 +229,8 @@
try {
// Send process level config change.
ClientTransaction transaction = newTransaction(activityThread);
- transaction.addCallback(ConfigurationChangeItem.obtain(newConfig, DEVICE_ID_INVALID));
+ addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
+ newConfig, DEVICE_ID_INVALID));
appThread.scheduleTransaction(transaction);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -243,7 +246,7 @@
newConfig.seq++;
newConfig.smallestScreenWidthDp++;
transaction = newTransaction(activityThread);
- transaction.addCallback(ActivityConfigurationChangeItem.obtain(
+ addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
activity.getActivityToken(), newConfig));
appThread.scheduleTransaction(transaction);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -444,16 +447,16 @@
activity.mTestLatch = new CountDownLatch(1);
ClientTransaction transaction = newTransaction(activityThread);
- transaction.addCallback(ConfigurationChangeItem.obtain(
+ addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
processConfigLandscape, DEVICE_ID_INVALID));
appThread.scheduleTransaction(transaction);
transaction = newTransaction(activityThread);
- transaction.addCallback(ActivityConfigurationChangeItem.obtain(
+ addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
activity.getActivityToken(), activityConfigLandscape));
- transaction.addCallback(ConfigurationChangeItem.obtain(
+ addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
processConfigPortrait, DEVICE_ID_INVALID));
- transaction.addCallback(ActivityConfigurationChangeItem.obtain(
+ addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
activity.getActivityToken(), activityConfigPortrait));
appThread.scheduleTransaction(transaction);
@@ -840,8 +843,8 @@
false /* shouldSendCompatFakeFocus*/);
final ClientTransaction transaction = newTransaction(activity);
- transaction.addCallback(callbackItem);
- transaction.setLifecycleStateRequest(resumeStateRequest);
+ addClientTransactionItem(transaction, callbackItem);
+ addClientTransactionItem(transaction, resumeStateRequest);
return transaction;
}
@@ -853,7 +856,7 @@
false /* shouldSendCompatFakeFocus */);
final ClientTransaction transaction = newTransaction(activity);
- transaction.setLifecycleStateRequest(resumeStateRequest);
+ addClientTransactionItem(transaction, resumeStateRequest);
return transaction;
}
@@ -864,7 +867,7 @@
activity.getActivityToken(), 0 /* configChanges */);
final ClientTransaction transaction = newTransaction(activity);
- transaction.setLifecycleStateRequest(stopStateRequest);
+ addClientTransactionItem(transaction, stopStateRequest);
return transaction;
}
@@ -876,7 +879,7 @@
activity.getActivityToken(), config);
final ClientTransaction transaction = newTransaction(activity);
- transaction.addCallback(item);
+ addClientTransactionItem(transaction, item);
return transaction;
}
@@ -888,7 +891,7 @@
resume);
final ClientTransaction transaction = newTransaction(activity);
- transaction.addCallback(item);
+ addClientTransactionItem(transaction, item);
return transaction;
}
@@ -903,6 +906,17 @@
return ClientTransaction.obtain(activityThread.getApplicationThread());
}
+ private static void addClientTransactionItem(@NonNull ClientTransaction transaction,
+ @NonNull ClientTransactionItem item) {
+ if (Flags.bundleClientTransactionFlag()) {
+ transaction.addTransactionItem(item);
+ } else if (item.isActivityLifecycleItem()) {
+ transaction.setLifecycleStateRequest((ActivityLifecycleItem) item);
+ } else {
+ transaction.addCallback(item);
+ }
+ }
+
// Test activity
public static class TestActivity extends Activity {
static final String PIP_REQUESTED_OVERRIDE_ENTER = "pip_requested_override_enter";
diff --git a/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
index 2ec58d4..5a202c5 100644
--- a/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
+++ b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
@@ -43,11 +44,29 @@
@LargeTest
public class ParcelableUsageEventListTest {
private static final int SMALL_TEST_EVENT_COUNT = 100;
- private static final int LARGE_TEST_EVENT_COUNT = 10000;
+ private static final int LARGE_TEST_EVENT_COUNT = 30000;
private Random mRandom = new Random();
@Test
+ public void testNullList() throws Exception {
+ Parcel parcel = Parcel.obtain();
+ try {
+ parcel.writeParcelable(new ParcelableUsageEventList(null), 0);
+ fail("Expected IllegalArgumentException with null list.");
+ } catch (IllegalArgumentException expected) {
+ // Expected.
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+ @Test
+ public void testEmptyList() throws Exception {
+ testParcelableUsageEventList(0);
+ }
+
+ @Test
public void testSmallList() throws Exception {
testParcelableUsageEventList(SMALL_TEST_EVENT_COUNT);
}
@@ -58,15 +77,15 @@
}
private void testParcelableUsageEventList(int eventCount) throws Exception {
- List<Event> smallList = new ArrayList<>();
+ List<Event> eventList = new ArrayList<>();
for (int i = 0; i < eventCount; i++) {
- smallList.add(generateUsageEvent());
+ eventList.add(generateUsageEvent());
}
ParcelableUsageEventList slice;
Parcel parcel = Parcel.obtain();
try {
- parcel.writeParcelable(new ParcelableUsageEventList(smallList), 0);
+ parcel.writeParcelable(new ParcelableUsageEventList(eventList), 0);
parcel.setDataPosition(0);
slice = parcel.readParcelable(getClass().getClassLoader(),
ParcelableUsageEventList.class);
@@ -79,7 +98,7 @@
assertEquals(eventCount, slice.getList().size());
for (int i = 0; i < eventCount; i++) {
- compareUsageEvent(smallList.get(i), slice.getList().get(i));
+ compareUsageEvent(eventList.get(i), slice.getList().get(i));
}
}
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index 9b4dec4..20ba427 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -182,42 +182,4 @@
s.setPreferPowerEfficiency(true);
s.setPreferPowerEfficiency(true);
}
-
- @Test
- public void testReportActualWorkDurationWithWorkDurationClass() {
- Session s = createSession();
- assumeNotNull(s);
- s.updateTargetWorkDuration(16);
- s.reportActualWorkDuration(new WorkDuration(1, 12, 8, 6));
- s.reportActualWorkDuration(new WorkDuration(1, 33, 14, 20));
- s.reportActualWorkDuration(new WorkDuration(1, 14, 10, 6));
- }
-
- @Test
- public void testReportActualWorkDurationWithWorkDurationClass_IllegalArgument() {
- Session s = createSession();
- assumeNotNull(s);
- s.updateTargetWorkDuration(16);
- assertThrows(IllegalArgumentException.class, () -> {
- s.reportActualWorkDuration(new WorkDuration(-1, 12, 8, 6));
- });
- assertThrows(IllegalArgumentException.class, () -> {
- s.reportActualWorkDuration(new WorkDuration(0, 12, 8, 6));
- });
- assertThrows(IllegalArgumentException.class, () -> {
- s.reportActualWorkDuration(new WorkDuration(1, -1, 8, 6));
- });
- assertThrows(IllegalArgumentException.class, () -> {
- s.reportActualWorkDuration(new WorkDuration(1, 0, 8, 6));
- });
- assertThrows(IllegalArgumentException.class, () -> {
- s.reportActualWorkDuration(new WorkDuration(1, 12, -1, 6));
- });
- assertThrows(IllegalArgumentException.class, () -> {
- s.reportActualWorkDuration(new WorkDuration(1, 12, 0, 6));
- });
- assertThrows(IllegalArgumentException.class, () -> {
- s.reportActualWorkDuration(new WorkDuration(1, 12, 8, -1));
- });
- }
}
diff --git a/core/tests/utiltests/Android.bp b/core/tests/utiltests/Android.bp
index 580e73c..06340a2 100644
--- a/core/tests/utiltests/Android.bp
+++ b/core/tests/utiltests/Android.bp
@@ -35,6 +35,7 @@
"androidx.test.ext.junit",
"truth",
"servicestests-utils",
+ "ravenwood-junit",
],
libs: [
@@ -50,3 +51,22 @@
test_suites: ["device-tests"],
}
+
+android_ravenwood_test {
+ name: "FrameworksUtilTestsRavenwood",
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.test.rules",
+ ],
+ srcs: [
+ "src/android/util/DataUnitTest.java",
+ "src/android/util/EventLogTest.java",
+ "src/android/util/IndentingPrintWriterTest.java",
+ "src/android/util/IntArrayTest.java",
+ "src/android/util/LocalLogTest.java",
+ "src/android/util/LongArrayTest.java",
+ "src/android/util/SlogTest.java",
+ "src/android/util/TimeUtilsTest.java",
+ ],
+ auto_gen_config: true,
+}
diff --git a/core/tests/coretests/src/android/util/DataUnitTest.java b/core/tests/utiltests/src/android/util/DataUnitTest.java
similarity index 85%
rename from core/tests/coretests/src/android/util/DataUnitTest.java
rename to core/tests/utiltests/src/android/util/DataUnitTest.java
index 034cbdd..af9ebc8 100644
--- a/core/tests/coretests/src/android/util/DataUnitTest.java
+++ b/core/tests/utiltests/src/android/util/DataUnitTest.java
@@ -16,12 +16,18 @@
package android.util;
-import androidx.test.filters.SmallTest;
+import static org.junit.Assert.assertEquals;
-import junit.framework.TestCase;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
@SmallTest
-public class DataUnitTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+public class DataUnitTest {
+ @Test
public void testSi() throws Exception {
assertEquals(12_000L, DataUnit.KILOBYTES.toBytes(12));
assertEquals(12_000_000L, DataUnit.MEGABYTES.toBytes(12));
@@ -29,6 +35,7 @@
assertEquals(12_000_000_000_000L, DataUnit.TERABYTES.toBytes(12));
}
+ @Test
public void testIec() throws Exception {
assertEquals(12_288L, DataUnit.KIBIBYTES.toBytes(12));
assertEquals(12_582_912L, DataUnit.MEBIBYTES.toBytes(12));
diff --git a/core/tests/coretests/src/android/util/EventLogTest.java b/core/tests/utiltests/src/android/util/EventLogTest.java
similarity index 78%
rename from core/tests/coretests/src/android/util/EventLogTest.java
rename to core/tests/utiltests/src/android/util/EventLogTest.java
index 94e72c4..0ebf2c1 100644
--- a/core/tests/coretests/src/android/util/EventLogTest.java
+++ b/core/tests/utiltests/src/android/util/EventLogTest.java
@@ -16,23 +16,42 @@
package android.util;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.util.EventLog.Event;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import junit.framework.AssertionFailedError;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
/** Unit tests for {@link android.util.EventLog} */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class EventLogTest {
+ @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule();
@Test
+ public void testSimple() throws Throwable {
+ EventLog.writeEvent(42, 42);
+ EventLog.writeEvent(42, 42L);
+ EventLog.writeEvent(42, 42f);
+ EventLog.writeEvent(42, "forty-two");
+ EventLog.writeEvent(42, 42, "forty-two", null, 42);
+ }
+
+ @Test
+ @IgnoreUnderRavenwood(reason = "Reading not yet supported")
public void testWithNewData() throws Throwable {
Event event = createEvent(() -> {
EventLog.writeEvent(314, 123);
diff --git a/core/tests/coretests/src/android/util/LocalLogTest.java b/core/tests/utiltests/src/android/util/LocalLogTest.java
similarity index 93%
rename from core/tests/coretests/src/android/util/LocalLogTest.java
rename to core/tests/utiltests/src/android/util/LocalLogTest.java
index d4861cd..5025a3d 100644
--- a/core/tests/coretests/src/android/util/LocalLogTest.java
+++ b/core/tests/utiltests/src/android/util/LocalLogTest.java
@@ -16,9 +16,13 @@
package android.util;
-import androidx.test.filters.LargeTest;
+import static org.junit.Assert.assertTrue;
-import junit.framework.TestCase;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -27,13 +31,16 @@
import java.util.List;
@LargeTest
-public class LocalLogTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+public class LocalLogTest {
+ @Test
public void testA_localTimestamps() {
boolean localTimestamps = true;
doTestA(localTimestamps);
}
+ @Test
public void testA_nonLocalTimestamps() {
boolean localTimestamps = false;
doTestA(localTimestamps);
@@ -49,6 +56,7 @@
testcase(new LocalLog(10, localTimestamps), lines, want);
}
+ @Test
public void testB() {
String[] lines = {
"foo",
@@ -59,6 +67,7 @@
testcase(new LocalLog(0), lines, want);
}
+ @Test
public void testC() {
String[] lines = {
"dropped",
diff --git a/core/tests/utiltests/src/android/util/SlogTest.java b/core/tests/utiltests/src/android/util/SlogTest.java
new file mode 100644
index 0000000..6f761e3
--- /dev/null
+++ b/core/tests/utiltests/src/android/util/SlogTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SlogTest {
+ private static final String TAG = "tag";
+ private static final String MSG = "msg";
+ private static final Throwable THROWABLE = new Throwable();
+
+ @Test
+ public void testSimple() {
+ Slog.v(TAG, MSG);
+ Slog.d(TAG, MSG);
+ Slog.i(TAG, MSG);
+ Slog.w(TAG, MSG);
+ Slog.e(TAG, MSG);
+ }
+
+ @Test
+ public void testThrowable() {
+ Slog.v(TAG, MSG, THROWABLE);
+ Slog.d(TAG, MSG, THROWABLE);
+ Slog.i(TAG, MSG, THROWABLE);
+ Slog.w(TAG, MSG, THROWABLE);
+ Slog.e(TAG, MSG, THROWABLE);
+ }
+}
diff --git a/core/tests/utiltests/src/android/util/TimeUtilsTest.java b/core/tests/utiltests/src/android/util/TimeUtilsTest.java
new file mode 100644
index 0000000..e8246c8
--- /dev/null
+++ b/core/tests/utiltests/src/android/util/TimeUtilsTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.function.Consumer;
+
+@RunWith(AndroidJUnit4.class)
+public class TimeUtilsTest {
+ public static final long SECOND_IN_MILLIS = 1000;
+ public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60;
+ public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60;
+ public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24;
+ public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7;
+
+ @Test
+ public void testFormatTime() {
+ assertEquals("1672556400000 (now)",
+ TimeUtils.formatTime(1672556400000L, 1672556400000L));
+ assertEquals("1672556400000 (in 10 ms)",
+ TimeUtils.formatTime(1672556400000L, 1672556400000L - 10));
+ assertEquals("1672556400000 (10 ms ago)",
+ TimeUtils.formatTime(1672556400000L, 1672556400000L + 10));
+
+ // Uses formatter above, so we just care that it doesn't crash
+ TimeUtils.formatRealtime(1672556400000L);
+ TimeUtils.formatUptime(1672556400000L);
+ }
+
+ @Test
+ public void testFormatDuration_Zero() {
+ assertEquals("0", TimeUtils.formatDuration(0));
+ }
+
+ @Test
+ public void testFormatDuration_Negative() {
+ assertEquals("-10ms", TimeUtils.formatDuration(-10));
+ }
+
+ @Test
+ public void testFormatDuration() {
+ long accum = 900;
+ assertEquals("+900ms", TimeUtils.formatDuration(accum));
+
+ accum += 59 * SECOND_IN_MILLIS;
+ assertEquals("+59s900ms", TimeUtils.formatDuration(accum));
+
+ accum += 59 * MINUTE_IN_MILLIS;
+ assertEquals("+59m59s900ms", TimeUtils.formatDuration(accum));
+
+ accum += 23 * HOUR_IN_MILLIS;
+ assertEquals("+23h59m59s900ms", TimeUtils.formatDuration(accum));
+
+ accum += 6 * DAY_IN_MILLIS;
+ assertEquals("+6d23h59m59s900ms", TimeUtils.formatDuration(accum));
+ }
+
+ @Test
+ public void testDumpTime() {
+ assertEquals("2023-01-01 00:00:00.000", runWithPrintWriter((pw) -> {
+ TimeUtils.dumpTime(pw, 1672556400000L);
+ }));
+ assertEquals("2023-01-01 00:00:00.000 (now)", runWithPrintWriter((pw) -> {
+ TimeUtils.dumpTimeWithDelta(pw, 1672556400000L, 1672556400000L);
+ }));
+ assertEquals("2023-01-01 00:00:00.000 (-10ms)", runWithPrintWriter((pw) -> {
+ TimeUtils.dumpTimeWithDelta(pw, 1672556400000L, 1672556400000L + 10);
+ }));
+ }
+
+ @Test
+ public void testFormatForLogging() {
+ assertEquals("unknown", TimeUtils.formatForLogging(0));
+ assertEquals("unknown", TimeUtils.formatForLogging(-1));
+ assertEquals("unknown", TimeUtils.formatForLogging(Long.MIN_VALUE));
+ assertEquals("2023-01-01 00:00:00", TimeUtils.formatForLogging(1672556400000L));
+ }
+
+ @Test
+ public void testLogTimeOfDay() {
+ assertEquals("01-01 00:00:00.000", TimeUtils.logTimeOfDay(1672556400000L));
+ }
+
+ public static String runWithPrintWriter(Consumer<PrintWriter> consumer) {
+ final StringWriter sw = new StringWriter();
+ consumer.accept(new PrintWriter(sw));
+ return sw.toString();
+ }
+
+ public static String runWithStringBuilder(Consumer<StringBuilder> consumer) {
+ final StringBuilder sb = new StringBuilder();
+ consumer.accept(sb);
+ return sb.toString();
+ }
+}
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/HideInCommentsChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/HideInCommentsChecker.java
index 07f1d4a..8dc9579 100644
--- a/errorprone/java/com/google/errorprone/bugpatterns/android/HideInCommentsChecker.java
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/HideInCommentsChecker.java
@@ -63,7 +63,7 @@
@Override
public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) {
- final Map<Integer, Tree> javadocableTrees = findJavadocableTrees(tree);
+ final Map<Integer, Tree> javadocableTrees = findJavadocableTrees(tree, state);
final String sourceCode = state.getSourceCode().toString();
for (ErrorProneToken token : ErrorProneTokens.getTokens(sourceCode, state.context)) {
for (Tokens.Comment comment : token.comments()) {
@@ -112,9 +112,9 @@
}
- private Map<Integer, Tree> findJavadocableTrees(CompilationUnitTree tree) {
+ private Map<Integer, Tree> findJavadocableTrees(CompilationUnitTree tree, VisitorState state) {
Map<Integer, Tree> javadoccableTrees = new HashMap<>();
- new SuppressibleTreePathScanner<Void, Void>() {
+ new SuppressibleTreePathScanner<Void, Void>(state) {
@Override
public Void visitClass(ClassTree classTree, Void unused) {
javadoccableTrees.put(getStartPosition(classTree), classTree);
diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java
index 2ec4524..d659ddd 100644
--- a/graphics/java/android/graphics/BaseRecordingCanvas.java
+++ b/graphics/java/android/graphics/BaseRecordingCanvas.java
@@ -402,8 +402,8 @@
}
@Override
- public final void drawDoubleRoundRect(@NonNull RectF outer, float[] outerRadii,
- @NonNull RectF inner, float[] innerRadii, @NonNull Paint paint) {
+ public final void drawDoubleRoundRect(@NonNull RectF outer, @NonNull float[] outerRadii,
+ @NonNull RectF inner, @NonNull float[] innerRadii, @NonNull Paint paint) {
nDrawDoubleRoundRect(mNativeCanvasWrapper,
outer.left, outer.top, outer.right, outer.bottom, outerRadii,
inner.left, inner.top, inner.right, inner.bottom, innerRadii,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 4d73c20..ca3d8d1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -375,7 +375,8 @@
return TaskFragmentAnimationParams.DEFAULT;
}
return new TaskFragmentAnimationParams.Builder()
- .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
+ // TODO(b/263047900): Update extensions API.
+ // .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
.build();
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index faf7c39..b5c32bb 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -854,7 +854,8 @@
return new SplitAttributes.Builder()
.setSplitType(splitTypeToUpdate)
.setLayoutDirection(splitAttributes.getLayoutDirection())
- .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
+ // TODO(b/263047900): Update extensions API.
+ // .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
.build();
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index 9607b78..60beb0b 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -17,6 +17,7 @@
package androidx.window.extensions;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
import static com.google.common.truth.Truth.assertThat;
import android.app.ActivityTaskManager;
@@ -69,6 +70,7 @@
.isEqualTo(SplitAttributes.LayoutDirection.LOCALE);
assertThat(splitAttributes.getSplitType())
.isEqualTo(new SplitAttributes.SplitType.RatioSplitType(0.5f));
- assertThat(splitAttributes.getAnimationBackgroundColor()).isEqualTo(0);
+ // TODO(b/263047900): Update extensions API.
+ // assertThat(splitAttributes.getAnimationBackgroundColor()).isEqualTo(0);
}
}
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index c366ccd..4d2d960 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -42,3 +42,11 @@
description: "Enables PiP UI state callback on entering"
bug: "303718131"
}
+
+flag {
+ name: "enable_pip2_implementation"
+ namespace: "multitasking"
+ description: "Enables the new implementation of PiP (PiP2)"
+ bug: "290220798"
+ is_fixed_read_only: true
+}
diff --git a/packages/SystemUI/communal/layout/AndroidManifest.xml b/libs/WindowManager/Shell/res/values/config_tv.xml
similarity index 62%
rename from packages/SystemUI/communal/layout/AndroidManifest.xml
rename to libs/WindowManager/Shell/res/values/config_tv.xml
index 141be07..3da5539 100644
--- a/packages/SystemUI/communal/layout/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/res/values/config_tv.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2023 The Android Open Source Project
+<!--
+ Copyright (C) 2023 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,5 +14,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<manifest package="com.android.systemui.communal.layout" />
+<resources>
+ <integer name="config_tvPipEnterFadeOutDuration">500</integer>
+ <integer name="config_tvPipEnterFadeInDuration">1500</integer>
+ <integer name="config_tvPipExitFadeOutDuration">500</integer>
+ <integer name="config_tvPipExitFadeInDuration">500</integer>
+</resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index c7ab6aa..f5b877a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -963,7 +963,11 @@
&& mTaskView.isAttachedToWindow()) {
// post this to the looper, because if the device orientation just changed, we need to
// let the current shell transition complete before updating the task view bounds.
- post(() -> mTaskView.onLocationChanged());
+ post(() -> {
+ if (mTaskView != null) {
+ mTaskView.onLocationChanged();
+ }
+ });
}
if (mIsOverflow) {
// post this to the looper so that the view has a chance to be laid out before it can
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
index d520ff7..8b6c7b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
@@ -258,7 +258,7 @@
ActivityTaskManager.getService().onPictureInPictureStateChanged(
new PictureInPictureUiState(stashedState != STASH_TYPE_NONE /* isStashed */)
);
- } catch (RemoteException e) {
+ } catch (RemoteException | IllegalStateException e) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Unable to set alert PiP state change.", TAG);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
index 108aa82..1e30d8f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
@@ -21,13 +21,13 @@
import android.content.ComponentName
import android.content.Context
import android.os.RemoteException
-import android.os.SystemProperties
import android.util.DisplayMetrics
import android.util.Log
import android.util.Pair
import android.util.TypedValue
import android.window.TaskSnapshot
import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.Flags
import com.android.wm.shell.protolog.ShellProtoLogGroup
import kotlin.math.abs
@@ -37,7 +37,6 @@
// Minimum difference between two floats (e.g. aspect ratios) to consider them not equal.
private const val EPSILON = 1e-7
- private const val ENABLE_PIP2_IMPLEMENTATION = "persist.wm.debug.enable_pip2_implementation"
/**
* @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack.
@@ -138,5 +137,5 @@
@JvmStatic
val isPip2ExperimentEnabled: Boolean
- get() = SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false)
+ get() = Flags.enablePip2Implementation()
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index a9675f9..1947097 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -20,6 +20,8 @@
import android.os.Handler;
import android.os.SystemClock;
+import androidx.annotation.NonNull;
+
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
@@ -41,7 +43,6 @@
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
-import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm;
import com.android.wm.shell.pip.tv.TvPipBoundsController;
@@ -78,11 +79,12 @@
PipDisplayLayoutState pipDisplayLayoutState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
+ PipTransitionState pipTransitionState,
PipAppOpsListener pipAppOpsListener,
PipTaskOrganizer pipTaskOrganizer,
TvPipMenuController tvPipMenuController,
PipMediaController pipMediaController,
- PipTransitionController pipTransitionController,
+ TvPipTransition tvPipTransition,
TvPipNotificationController tvPipNotificationController,
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
@@ -99,9 +101,10 @@
pipDisplayLayoutState,
tvPipBoundsAlgorithm,
tvPipBoundsController,
+ pipTransitionState,
pipAppOpsListener,
pipTaskOrganizer,
- pipTransitionController,
+ tvPipTransition,
tvPipMenuController,
pipMediaController,
tvPipNotificationController,
@@ -151,25 +154,23 @@
return new LegacySizeSpecSource(context, pipDisplayLayoutState);
}
- // Handler needed for loadDrawableAsync() in PipControlsViewController
@WMSingleton
@Provides
- static PipTransitionController provideTvPipTransition(
+ static TvPipTransition provideTvPipTransition(
Context context,
- ShellInit shellInit,
- ShellTaskOrganizer shellTaskOrganizer,
- Transitions transitions,
+ @NonNull ShellInit shellInit,
+ @NonNull ShellTaskOrganizer shellTaskOrganizer,
+ @NonNull Transitions transitions,
TvPipBoundsState tvPipBoundsState,
- PipDisplayLayoutState pipDisplayLayoutState,
- PipTransitionState pipTransitionState,
- TvPipMenuController pipMenuController,
+ TvPipMenuController tvPipMenuController,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+ PipTransitionState pipTransitionState,
PipAnimationController pipAnimationController,
- PipSurfaceTransactionHelper pipSurfaceTransactionHelper) {
+ PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+ PipDisplayLayoutState pipDisplayLayoutState) {
return new TvPipTransition(context, shellInit, shellTaskOrganizer, transitions,
- tvPipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController,
- tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
- Optional.empty());
+ tvPipBoundsState, tvPipMenuController, tvPipBoundsAlgorithm, pipTransitionState,
+ pipAnimationController, pipSurfaceTransactionHelper, pipDisplayLayoutState);
}
@WMSingleton
@@ -207,7 +208,7 @@
PipTransitionState pipTransitionState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipAnimationController pipAnimationController,
- PipTransitionController pipTransitionController,
+ TvPipTransition tvPipTransition,
PipParamsChangedForwarder pipParamsChangedForwarder,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<SplitScreenController> splitScreenControllerOptional,
@@ -217,7 +218,7 @@
return new TvPipTaskOrganizer(context,
syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipDisplayLayoutState,
tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController,
- pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
+ pipSurfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder,
splitScreenControllerOptional, displayController, pipUiEventLogger,
shellTaskOrganizer, mainExecutor);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index cbed4b5..a58d94e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -81,15 +81,35 @@
*/
public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
Rect sourceBounds, Rect destinationBounds) {
+ mTmpDestinationRectF.set(destinationBounds);
+ return scale(tx, leash, sourceBounds, mTmpDestinationRectF, 0 /* degrees */);
+ }
+
+ /**
+ * Operates the scale (setMatrix) on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect sourceBounds, RectF destinationBounds) {
return scale(tx, leash, sourceBounds, destinationBounds, 0 /* degrees */);
}
/**
+ * Operates the scale (setMatrix) on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect sourceBounds, Rect destinationBounds, float degrees) {
+ mTmpDestinationRectF.set(destinationBounds);
+ return scale(tx, leash, sourceBounds, mTmpDestinationRectF, degrees);
+ }
+
+ /**
* Operates the scale (setMatrix) on a given transaction and leash, along with a rotation.
* @return same {@link PipSurfaceTransactionHelper} instance for method chaining
*/
public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
- Rect sourceBounds, Rect destinationBounds, float degrees) {
+ Rect sourceBounds, RectF destinationBounds, float degrees) {
mTmpSourceRectF.set(sourceBounds);
// We want the matrix to position the surface relative to the screen coordinates so offset
// the source to 0,0
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index c05601b..b4067d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -297,9 +297,9 @@
// changed RunningTaskInfo when it finishes.
private ActivityManager.RunningTaskInfo mDeferredTaskInfo;
private WindowContainerToken mToken;
- private SurfaceControl mLeash;
+ protected SurfaceControl mLeash;
protected PipTransitionState mPipTransitionState;
- private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+ protected PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
protected PictureInPictureParams mPictureInPictureParams;
private IntConsumer mOnDisplayIdChangeCallback;
@@ -973,7 +973,7 @@
return;
}
- cancelCurrentAnimator();
+ cancelAnimationOnTaskVanished();
onExitPipFinished(info);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -981,6 +981,10 @@
}
}
+ protected void cancelAnimationOnTaskVanished() {
+ cancelCurrentAnimator();
+ }
+
@Override
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
Objects.requireNonNull(mToken, "onTaskInfoChanged requires valid existing mToken");
@@ -1100,7 +1104,7 @@
}
/** Called when exiting PIP transition is finished to do the state cleanup. */
- void onExitPipFinished(TaskInfo info) {
+ public void onExitPipFinished(TaskInfo info) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"onExitPipFinished: %s, state=%s leash=%s",
info.topActivity, mPipTransitionState, mLeash);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index a48e969f..72c0cd7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -44,6 +44,7 @@
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import java.util.Collections;
import java.util.Set;
/**
@@ -101,12 +102,29 @@
&& mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0
&& !mTvPipBoundsState.isTvPipManuallyCollapsed();
if (isPipExpanded) {
- updateGravityOnExpansionToggled(/* expanding= */ true);
+ updateGravityOnExpansionToggled(/* expanding= */ isPipExpanded);
}
mTvPipBoundsState.setTvPipExpanded(isPipExpanded);
return adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds());
}
+ @Override
+ public Rect getEntryDestinationBoundsIgnoringKeepClearAreas() {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: getEntryDestinationBoundsIgnoringKeepClearAreas()", TAG);
+
+ updateExpandedPipSize();
+ final boolean isPipExpanded = mTvPipBoundsState.isTvExpandedPipSupported()
+ && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0
+ && !mTvPipBoundsState.isTvPipManuallyCollapsed();
+ if (isPipExpanded) {
+ updateGravityOnExpansionToggled(/* expanding= */ isPipExpanded);
+ }
+ mTvPipBoundsState.setTvPipExpanded(isPipExpanded);
+ return adjustBoundsForTemporaryDecor(getTvPipPlacement(Collections.emptySet(),
+ Collections.emptySet()).getUnstashedBounds());
+ }
+
/** Returns the current bounds adjusted to the new aspect ratio, if valid. */
@Override
public Rect getAdjustedDestinationBounds(Rect currentBounds, float newAspectRatio) {
@@ -133,16 +151,25 @@
*/
@NonNull
public Placement getTvPipPlacement() {
+ final Set<Rect> restrictedKeepClearAreas = mTvPipBoundsState.getRestrictedKeepClearAreas();
+ final Set<Rect> unrestrictedKeepClearAreas =
+ mTvPipBoundsState.getUnrestrictedKeepClearAreas();
+
+ return getTvPipPlacement(restrictedKeepClearAreas, unrestrictedKeepClearAreas);
+ }
+
+ /**
+ * Calculates the PiP bounds.
+ */
+ @NonNull
+ private Placement getTvPipPlacement(Set<Rect> restrictedKeepClearAreas,
+ Set<Rect> unrestrictedKeepClearAreas) {
final Size pipSize = getPipSize();
final Rect displayBounds = mTvPipBoundsState.getDisplayBounds();
final Size screenSize = new Size(displayBounds.width(), displayBounds.height());
final Rect insetBounds = new Rect();
getInsetBounds(insetBounds);
- final Set<Rect> restrictedKeepClearAreas = mTvPipBoundsState.getRestrictedKeepClearAreas();
- final Set<Rect> unrestrictedKeepClearAreas =
- mTvPipBoundsState.getUnrestrictedKeepClearAreas();
-
mKeepClearAlgorithm.setGravity(mTvPipBoundsState.getTvPipGravity());
mKeepClearAlgorithm.setScreenSize(screenSize);
mKeepClearAlgorithm.setMovementBounds(insetBounds);
@@ -189,8 +216,11 @@
int updatedGravity;
if (expanding) {
- // Save collapsed gravity.
- mTvPipBoundsState.setTvPipPreviousCollapsedGravity(mTvPipBoundsState.getTvPipGravity());
+ if (!mTvPipBoundsState.isTvPipExpanded()) {
+ // Save collapsed gravity.
+ mTvPipBoundsState.setTvPipPreviousCollapsedGravity(
+ mTvPipBoundsState.getTvPipGravity());
+ }
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
updatedGravity = Gravity.CENTER_HORIZONTAL | currentY;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index 2b3a93e..5ee3734e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -131,6 +131,7 @@
mTvFixedPipOrientation = ORIENTATION_UNDETERMINED;
mTvPipGravity = mDefaultGravity;
mPreviousCollapsedGravity = mDefaultGravity;
+ mIsTvPipExpanded = false;
mTvPipManuallyCollapsed = false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 72115fd..cd3d38b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -56,6 +56,7 @@
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellController;
@@ -122,6 +123,7 @@
private final PipDisplayLayoutState mPipDisplayLayoutState;
private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
private final TvPipBoundsController mTvPipBoundsController;
+ private final PipTransitionState mPipTransitionState;
private final PipAppOpsListener mAppOpsListener;
private final PipTaskOrganizer mPipTaskOrganizer;
private final PipMediaController mPipMediaController;
@@ -157,6 +159,7 @@
PipDisplayLayoutState pipDisplayLayoutState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
+ PipTransitionState pipTransitionState,
PipAppOpsListener pipAppOpsListener,
PipTaskOrganizer pipTaskOrganizer,
PipTransitionController pipTransitionController,
@@ -177,6 +180,7 @@
pipDisplayLayoutState,
tvPipBoundsAlgorithm,
tvPipBoundsController,
+ pipTransitionState,
pipAppOpsListener,
pipTaskOrganizer,
pipTransitionController,
@@ -199,6 +203,7 @@
PipDisplayLayoutState pipDisplayLayoutState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
+ PipTransitionState pipTransitionState,
PipAppOpsListener pipAppOpsListener,
PipTaskOrganizer pipTaskOrganizer,
PipTransitionController pipTransitionController,
@@ -212,6 +217,7 @@
Handler mainHandler,
ShellExecutor mainExecutor) {
mContext = context;
+ mPipTransitionState = pipTransitionState;
mMainHandler = mainHandler;
mMainExecutor = mainExecutor;
mShellController = shellController;
@@ -365,7 +371,6 @@
"%s: movePipToFullscreen(), state=%s", TAG, stateToName(mState));
mPipTaskOrganizer.exitPip(mResizeAnimationDuration, false /* requestEnterSplit */);
- onPipDisappeared();
}
private void togglePipExpansion() {
@@ -420,6 +425,11 @@
@Override
public void onPipTargetBoundsChange(Rect targetBounds, int animationDuration) {
+ if (!mPipTransitionState.hasEnteredPip()) {
+ // Do not schedule a move animation while we're still transitioning into/out of PiP
+ return;
+ }
+
mPipTaskOrganizer.scheduleAnimateResizePip(targetBounds,
animationDuration, null);
mTvPipMenuController.onPipTransitionToTargetBoundsStarted(targetBounds);
@@ -447,7 +457,7 @@
return;
}
mPipTaskOrganizer.removePip();
- onPipDisappeared();
+ mTvPipMenuController.closeMenu();
}
@Override
@@ -477,7 +487,7 @@
mPipNotificationController.dismiss();
mActionBroadcastReceiver.unregister();
- mTvPipMenuController.closeMenu();
+ mTvPipMenuController.detach();
mTvPipActionsProvider.reset();
mTvPipBoundsState.resetTvPipState();
mTvPipBoundsController.reset();
@@ -501,8 +511,6 @@
public void onPipTransitionCanceled(int direction) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState));
- mTvPipMenuController.onPipTransitionFinished(
- PipAnimationController.isInPipDirection(direction));
mTvPipActionsProvider.updatePipExpansionState(mTvPipBoundsState.isTvPipExpanded());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index ee55211..c6803f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -262,8 +262,8 @@
@Override
public void detach() {
- closeMenu();
detachPipMenu();
+ switchToMenuMode(MODE_NO_MENU);
mLeash = null;
}
@@ -320,10 +320,21 @@
@Override
public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction pipTx,
Rect pipBounds, float alpha) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: movePipMenu: %s, alpha %s", TAG, pipBounds.toShortString(), alpha);
+ movePipMenu(pipTx, pipBounds, alpha);
+ }
- if (pipBounds.isEmpty()) {
+ /**
+ * Move the PiP menu with the given bounds and update its opacity.
+ * The PiP SurfaceControl is given if there is a need to synchronize the movements
+ * on the same frame as PiP.
+ */
+ public void movePipMenu(@Nullable SurfaceControl.Transaction pipTx, @Nullable Rect pipBounds,
+ float alpha) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: movePipMenu: %s, alpha %s", TAG,
+ pipBounds != null ? pipBounds.toShortString() : null, alpha);
+
+ if ((pipBounds == null || pipBounds.isEmpty()) && alpha == ALPHA_NO_CHANGE) {
if (pipTx == null) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: no transaction given", TAG);
@@ -334,28 +345,36 @@
return;
}
- final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
- final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView);
- final Rect menuDestBounds = calculateMenuSurfaceBounds(pipBounds);
if (pipTx == null) {
pipTx = new SurfaceControl.Transaction();
}
- pipTx.setPosition(frontSurface, menuDestBounds.left, menuDestBounds.top);
- pipTx.setPosition(backSurface, menuDestBounds.left, menuDestBounds.top);
+
+ final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
+ final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView);
+
+ if (pipBounds != null) {
+ final Rect menuDestBounds = calculateMenuSurfaceBounds(pipBounds);
+ pipTx.setPosition(frontSurface, menuDestBounds.left, menuDestBounds.top);
+ pipTx.setPosition(backSurface, menuDestBounds.left, menuDestBounds.top);
+ updateMenuBounds(pipBounds);
+ }
if (alpha != ALPHA_NO_CHANGE) {
pipTx.setAlpha(frontSurface, alpha);
pipTx.setAlpha(backSurface, alpha);
}
- // Synchronize drawing the content in the front and back surfaces together with the pip
- // transaction and the position change for the front and back surfaces
- final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup("TvPip");
- syncGroup.add(mPipMenuView.getRootSurfaceControl(), null);
- syncGroup.add(mPipBackgroundView.getRootSurfaceControl(), null);
- updateMenuBounds(pipBounds);
- syncGroup.addTransaction(pipTx);
- syncGroup.markSyncReady();
+ if (pipBounds != null) {
+ // Synchronize drawing the content in the front and back surfaces together with the pip
+ // transaction and the position change for the front and back surfaces
+ final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup("TvPip");
+ syncGroup.add(mPipMenuView.getRootSurfaceControl(), null);
+ syncGroup.add(mPipBackgroundView.getRootSurfaceControl(), null);
+ syncGroup.addTransaction(pipTx);
+ syncGroup.markSyncReady();
+ } else {
+ pipTx.apply();
+ }
}
private boolean isMenuAttached() {
@@ -388,14 +407,19 @@
final Rect menuBounds = calculateMenuSurfaceBounds(pipBounds);
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: updateMenuBounds: %s", TAG, menuBounds.toShortString());
- mSystemWindows.updateViewLayout(mPipBackgroundView,
- getPipMenuLayoutParams(mContext, BACKGROUND_WINDOW_TITLE, menuBounds.width(),
- menuBounds.height()));
- mSystemWindows.updateViewLayout(mPipMenuView,
- getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, menuBounds.width(),
- menuBounds.height()));
- if (mPipMenuView != null) {
- mPipMenuView.setPipBounds(pipBounds);
+
+ boolean needsRelayout = mPipBackgroundView.getLayoutParams().width != menuBounds.width()
+ || mPipBackgroundView.getLayoutParams().height != menuBounds.height();
+ if (needsRelayout) {
+ mSystemWindows.updateViewLayout(mPipBackgroundView,
+ getPipMenuLayoutParams(mContext, BACKGROUND_WINDOW_TITLE, menuBounds.width(),
+ menuBounds.height()));
+ mSystemWindows.updateViewLayout(mPipMenuView,
+ getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, menuBounds.width(),
+ menuBounds.height()));
+ if (mPipMenuView != null) {
+ mPipMenuView.setPipBounds(pipBounds);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java
index f86f987..202d36f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java
@@ -168,6 +168,9 @@
* that the edu text will be marqueed
*/
private boolean isEduTextMarqueed() {
+ if (mEduTextView.getLayout() == null) {
+ return false;
+ }
final int availableWidth = (int) mEduTextView.getWidth()
- mEduTextView.getCompoundPaddingLeft()
- mEduTextView.getCompoundPaddingRight();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index f315afb..21223c9a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -35,7 +35,6 @@
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
-import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -46,6 +45,7 @@
* TV specific changes to the PipTaskOrganizer.
*/
public class TvPipTaskOrganizer extends PipTaskOrganizer {
+ private final TvPipTransition mTvPipTransition;
public TvPipTaskOrganizer(Context context,
@NonNull SyncTransactionQueue syncTransactionQueue,
@@ -56,7 +56,7 @@
@NonNull PipMenuController pipMenuController,
@NonNull PipAnimationController pipAnimationController,
@NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
- @NonNull PipTransitionController pipTransitionController,
+ @NonNull TvPipTransition tvPipTransition,
@NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenOptional,
@NonNull DisplayController displayController,
@@ -65,9 +65,10 @@
ShellExecutor mainExecutor) {
super(context, syncTransactionQueue, pipTransitionState, pipBoundsState,
pipDisplayLayoutState, boundsHandler, pipMenuController, pipAnimationController,
- surfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
+ surfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder,
splitScreenOptional, displayController, pipUiEventLogger, shellTaskOrganizer,
mainExecutor);
+ mTvPipTransition = tvPipTransition;
}
@Override
@@ -105,4 +106,14 @@
// when the menu alpha is 0 (e.g. when a fade-in animation starts).
return true;
}
+
+ @Override
+ protected void cancelAnimationOnTaskVanished() {
+ mTvPipTransition.cancelAnimations();
+ if (mLeash != null) {
+ mSurfaceControlTransactionFactory.getTransaction()
+ .setAlpha(mLeash, 0f)
+ .apply();
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index f24b2b3..571c839 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -16,43 +16,822 @@
package com.android.wm.shell.pip.tv;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_PIP;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.transitTypeToString;
+
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+import static com.android.wm.shell.pip.PipMenuController.ALPHA_NO_CHANGE;
+import static com.android.wm.shell.pip.PipTransitionState.ENTERED_PIP;
+import static com.android.wm.shell.pip.PipTransitionState.ENTERING_PIP;
+import static com.android.wm.shell.pip.PipTransitionState.EXITING_PIP;
+import static com.android.wm.shell.pip.PipTransitionState.UNDEFINED;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
+
+import android.animation.AnimationHandler;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.app.ActivityManager;
+import android.app.TaskInfo;
import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.IBinder;
+import android.os.Trace;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+import androidx.annotation.FloatRange;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
-import com.android.wm.shell.pip.PipTransition;
+import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
-import java.util.Optional;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
/**
* PiP Transition for TV.
*/
-public class TvPipTransition extends PipTransition {
+public class TvPipTransition extends PipTransitionController {
+ private static final String TAG = "TvPipTransition";
+ private static final float ZOOM_ANIMATION_SCALE_FACTOR = 0.97f;
+
+ private final PipTransitionState mPipTransitionState;
+ private final PipAnimationController mPipAnimationController;
+ private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
+ private final TvPipMenuController mTvPipMenuController;
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
+ private final PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory
+ mTransactionFactory;
+
+ private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
+ ThreadLocal.withInitial(() -> {
+ AnimationHandler handler = new AnimationHandler();
+ handler.setProvider(new SfVsyncFrameCallbackProvider());
+ return handler;
+ });
+
+ private final long mEnterFadeOutDuration;
+ private final long mEnterFadeInDuration;
+ private final long mExitFadeOutDuration;
+ private final long mExitFadeInDuration;
+
+ @Nullable
+ private Animator mCurrentAnimator;
+
+ /**
+ * The Task window that is currently in PIP windowing mode.
+ */
+ @Nullable
+ private WindowContainerToken mCurrentPipTaskToken;
+
+ @Nullable
+ private IBinder mPendingExitTransition;
public TvPipTransition(Context context,
@NonNull ShellInit shellInit,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
TvPipBoundsState tvPipBoundsState,
- PipDisplayLayoutState pipDisplayLayoutState,
- PipTransitionState pipTransitionState,
TvPipMenuController tvPipMenuController,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+ PipTransitionState pipTransitionState,
PipAnimationController pipAnimationController,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
- Optional<SplitScreenController> splitScreenOptional) {
- super(context, shellInit, shellTaskOrganizer, transitions, tvPipBoundsState,
- pipDisplayLayoutState, pipTransitionState, tvPipMenuController,
- tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
- splitScreenOptional);
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ super(shellInit, shellTaskOrganizer, transitions, tvPipBoundsState, tvPipMenuController,
+ tvPipBoundsAlgorithm);
+ mPipTransitionState = pipTransitionState;
+ mPipAnimationController = pipAnimationController;
+ mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
+ mTvPipMenuController = tvPipMenuController;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
+ mTransactionFactory =
+ new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
+
+ mEnterFadeOutDuration = context.getResources().getInteger(
+ R.integer.config_tvPipEnterFadeOutDuration);
+ mEnterFadeInDuration = context.getResources().getInteger(
+ R.integer.config_tvPipEnterFadeInDuration);
+ mExitFadeOutDuration = context.getResources().getInteger(
+ R.integer.config_tvPipExitFadeOutDuration);
+ mExitFadeInDuration = context.getResources().getInteger(
+ R.integer.config_tvPipExitFadeInDuration);
}
+ @Override
+ public void startExitTransition(int type, WindowContainerTransaction out,
+ @Nullable Rect destinationBounds) {
+ cancelAnimations();
+ mPendingExitTransition = mTransitions.startTransition(type, out, this);
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+
+ if (isCloseTransition(info)) {
+ // PiP is closing (without reentering fullscreen activity)
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Starting close animation", TAG);
+ cancelAnimations();
+ startCloseAnimation(info, startTransaction, finishTransaction, finishCallback);
+ mCurrentPipTaskToken = null;
+ return true;
+
+ } else if (transition.equals(mPendingExitTransition)) {
+ // PiP is exiting (reentering fullscreen activity)
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Starting exit animation", TAG);
+
+ final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info);
+ mPendingExitTransition = null;
+ // PipTaskChange can be null if the PIP task has been detached, for example, when the
+ // task contains multiple activities, the PIP will be moved to a new PIP task when
+ // entering, and be moved back when exiting. In that case, the PIP task will be removed
+ // immediately.
+ final TaskInfo pipTaskInfo = currentPipTaskChange != null
+ ? currentPipTaskChange.getTaskInfo()
+ : mPipOrganizer.getTaskInfo();
+ if (pipTaskInfo == null) {
+ throw new RuntimeException("Cannot find the pip task for exit-pip transition.");
+ }
+
+ final int type = info.getType();
+ switch (type) {
+ case TRANSIT_EXIT_PIP -> {
+ TransitionInfo.Change pipChange = currentPipTaskChange;
+ SurfaceControl activitySc = null;
+ if (mCurrentPipTaskToken == null) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: There is no existing PiP Task for TRANSIT_EXIT_PIP", TAG);
+ } else if (pipChange == null) {
+ // The pipTaskChange is null, this can happen if we are reparenting the
+ // PIP activity back to its original Task. In that case, we should animate
+ // the activity leash instead, which should be the change whose last parent
+ // is the recorded PiP Task.
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (mCurrentPipTaskToken.equals(change.getLastParent())) {
+ // Find the activity that is exiting PiP.
+ pipChange = change;
+ activitySc = change.getLeash();
+ break;
+ }
+ }
+ }
+ if (pipChange == null) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: No window of exiting PIP is found. Can't play expand "
+ + "animation",
+ TAG);
+ removePipImmediately(info, pipTaskInfo, startTransaction, finishTransaction,
+ finishCallback);
+ return true;
+ }
+ final TransitionInfo.Root root = TransitionUtil.getRootFor(pipChange, info);
+ final SurfaceControl pipLeash;
+ if (activitySc != null) {
+ // Use a local leash to animate activity in case the activity has
+ // letterbox which may be broken by PiP animation, e.g. always end at 0,0
+ // in parent and unable to include letterbox area in crop bounds.
+ final SurfaceControl activitySurface = pipChange.getLeash();
+ pipLeash = new SurfaceControl.Builder()
+ .setName(activitySc + "_pip-leash")
+ .setContainerLayer()
+ .setHidden(false)
+ .setParent(root.getLeash())
+ .build();
+ startTransaction.reparent(activitySurface, pipLeash);
+ // Put the activity at local position with offset in case it is letterboxed.
+ final Point activityOffset = pipChange.getEndRelOffset();
+ startTransaction.setPosition(activitySc, activityOffset.x,
+ activityOffset.y);
+ } else {
+ pipLeash = pipChange.getLeash();
+ startTransaction.reparent(pipLeash, root.getLeash());
+ }
+ startTransaction.setLayer(pipLeash, Integer.MAX_VALUE);
+ final Rect currentBounds = mPipBoundsState.getBounds();
+ final Rect destinationBounds = new Rect(pipChange.getEndAbsBounds());
+ cancelAnimations();
+ startExitAnimation(pipTaskInfo, pipLeash, currentBounds, destinationBounds,
+ startTransaction,
+ finishTransaction, finishCallback);
+ }
+ // pass through here is intended
+ case TRANSIT_TO_BACK, TRANSIT_REMOVE_PIP -> removePipImmediately(info, pipTaskInfo,
+ startTransaction, finishTransaction,
+ finishCallback
+ );
+ default -> {
+ return false;
+ }
+ }
+ mCurrentPipTaskToken = null;
+ return true;
+
+ } else if (isEnteringPip(info)) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Starting enter animation", TAG);
+
+ // Search for an Enter PiP transition
+ TransitionInfo.Change enterPip = null;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) {
+ enterPip = change;
+ }
+ }
+ if (enterPip == null) {
+ throw new IllegalStateException("Trying to start PiP animation without a pip"
+ + "participant");
+ }
+
+ // Make sure other open changes are visible as entering PIP. Some may be hidden in
+ // Transitions#setupStartState because the transition type is OPEN (such as auto-enter).
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change == enterPip) continue;
+ if (TransitionUtil.isOpeningType(change.getMode())) {
+ final SurfaceControl leash = change.getLeash();
+ startTransaction.show(leash).setAlpha(leash, 1.f);
+ }
+ }
+
+ cancelAnimations();
+ startEnterAnimation(enterPip, startTransaction, finishTransaction, finishCallback);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * For {@link Transitions#TRANSIT_REMOVE_PIP}, we just immediately remove the PIP Task.
+ */
+ private void removePipImmediately(@NonNull TransitionInfo info,
+ @NonNull TaskInfo taskInfo, @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: removePipImmediately", TAG);
+ cancelAnimations();
+ startTransaction.apply();
+ finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(),
+ mPipDisplayLayoutState.getDisplayBounds());
+ mTvPipMenuController.detach();
+ mPipOrganizer.onExitPipFinished(taskInfo);
+ finishCallback.onTransitionFinished(/* wct= */ null);
+
+ mPipTransitionState.setTransitionState(UNDEFINED);
+ sendOnPipTransitionFinished(TRANSITION_DIRECTION_REMOVE_STACK);
+ }
+
+ private void startCloseAnimation(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ final TransitionInfo.Change pipTaskChange = findCurrentPipTaskChange(info);
+ final SurfaceControl pipLeash = pipTaskChange.getLeash();
+
+ final List<SurfaceControl> closeLeashes = new ArrayList<>();
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (TransitionUtil.isClosingType(change.getMode()) && change != pipTaskChange) {
+ closeLeashes.add(change.getLeash());
+ }
+ }
+
+ final Rect pipBounds = mPipBoundsState.getBounds();
+ mSurfaceTransactionHelper
+ .resetScale(startTransaction, pipLeash, pipBounds)
+ .crop(startTransaction, pipLeash, pipBounds)
+ .shadow(startTransaction, pipLeash, false);
+
+ final SurfaceControl.Transaction transaction = mTransactionFactory.getTransaction();
+ for (SurfaceControl leash : closeLeashes) {
+ startTransaction.setShadowRadius(leash, 0f);
+ }
+
+ ValueAnimator closeFadeOutAnimator = createAnimator();
+ closeFadeOutAnimator.setInterpolator(TvPipInterpolators.EXIT);
+ closeFadeOutAnimator.setDuration(mExitFadeOutDuration);
+ closeFadeOutAnimator.addUpdateListener(
+ animationUpdateListener(pipLeash).fadingOut().withMenu());
+ for (SurfaceControl leash : closeLeashes) {
+ closeFadeOutAnimator.addUpdateListener(animationUpdateListener(leash).fadingOut());
+ }
+
+ closeFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(@NonNull Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: close animation: start", TAG);
+ for (SurfaceControl leash : closeLeashes) {
+ startTransaction.setShadowRadius(leash, 0f);
+ }
+ startTransaction.apply();
+
+ mPipTransitionState.setTransitionState(EXITING_PIP);
+ sendOnPipTransitionStarted(TRANSITION_DIRECTION_REMOVE_STACK);
+ }
+
+ @Override
+ public void onAnimationCancel(@NonNull Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: close animation: cancel", TAG);
+ sendOnPipTransitionCancelled(TRANSITION_DIRECTION_REMOVE_STACK);
+ }
+
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: close animation: end", TAG);
+ mTvPipMenuController.detach();
+ finishCallback.onTransitionFinished(null /* wct */);
+ transaction.close();
+ mPipTransitionState.setTransitionState(UNDEFINED);
+ sendOnPipTransitionFinished(TRANSITION_DIRECTION_REMOVE_STACK);
+
+ mCurrentAnimator = null;
+ }
+ });
+
+ closeFadeOutAnimator.start();
+ mCurrentAnimator = closeFadeOutAnimator;
+ }
+
+ @Override
+ public void startEnterAnimation(@NonNull TransitionInfo.Change pipChange,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ // Keep track of the PIP task
+ mCurrentPipTaskToken = pipChange.getContainer();
+ final ActivityManager.RunningTaskInfo taskInfo = pipChange.getTaskInfo();
+ final SurfaceControl leash = pipChange.getLeash();
+
+ mTvPipMenuController.attach(leash);
+ setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
+ taskInfo.topActivityInfo);
+
+ final Rect pipBounds =
+ mPipBoundsAlgorithm.getEntryDestinationBoundsIgnoringKeepClearAreas();
+ mPipBoundsState.setBounds(pipBounds);
+ mTvPipMenuController.movePipMenu(null, pipBounds, 0f);
+
+ final WindowContainerTransaction resizePipWct = new WindowContainerTransaction();
+ resizePipWct.setWindowingMode(taskInfo.token, WINDOWING_MODE_PINNED);
+ resizePipWct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_PINNED);
+ resizePipWct.setBounds(taskInfo.token, pipBounds);
+
+ mSurfaceTransactionHelper
+ .resetScale(finishTransaction, leash, pipBounds)
+ .crop(finishTransaction, leash, pipBounds)
+ .shadow(finishTransaction, leash, false);
+
+ final Rect currentBounds = pipChange.getStartAbsBounds();
+ final Rect fadeOutCurrentBounds = scaledRect(currentBounds, ZOOM_ANIMATION_SCALE_FACTOR);
+
+ final ValueAnimator enterFadeOutAnimator = createAnimator();
+ enterFadeOutAnimator.setInterpolator(TvPipInterpolators.EXIT);
+ enterFadeOutAnimator.setDuration(mEnterFadeOutDuration);
+ enterFadeOutAnimator.addUpdateListener(
+ animationUpdateListener(leash)
+ .fadingOut()
+ .animateBounds(currentBounds, fadeOutCurrentBounds, currentBounds));
+
+ enterFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
+ @SuppressLint("MissingPermission")
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: enter fade out animation: end", TAG);
+ SurfaceControl.Transaction tx = mTransactionFactory.getTransaction();
+ mSurfaceTransactionHelper
+ .resetScale(tx, leash, pipBounds)
+ .crop(tx, leash, pipBounds)
+ .shadow(tx, leash, false);
+ mShellTaskOrganizer.applyTransaction(resizePipWct);
+ tx.apply();
+ }
+ });
+
+ final ValueAnimator enterFadeInAnimator = createAnimator();
+ enterFadeInAnimator.setInterpolator(TvPipInterpolators.ENTER);
+ enterFadeInAnimator.setDuration(mEnterFadeInDuration);
+ enterFadeInAnimator.addUpdateListener(
+ animationUpdateListener(leash)
+ .fadingIn()
+ .withMenu()
+ .atBounds(pipBounds));
+
+ final AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet
+ .play(enterFadeInAnimator)
+ .after(500)
+ .after(enterFadeOutAnimator);
+
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: enter animation: start", TAG);
+ startTransaction.apply();
+ mPipTransitionState.setTransitionState(ENTERING_PIP);
+ sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: enter animation: cancel", TAG);
+ enterFadeInAnimator.setCurrentFraction(1f);
+ sendOnPipTransitionCancelled(TRANSITION_DIRECTION_TO_PIP);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: enter animation: end", TAG);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+ wct.setBounds(taskInfo.token, pipBounds);
+ finishCallback.onTransitionFinished(wct);
+
+ mPipTransitionState.setTransitionState(ENTERED_PIP);
+ sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
+ mCurrentAnimator = null;
+ }
+ });
+
+ animatorSet.start();
+ mCurrentAnimator = animatorSet;
+ }
+
+ private void startExitAnimation(@NonNull TaskInfo taskInfo, SurfaceControl leash,
+ Rect currentBounds, Rect destinationBounds,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ final Rect fadeInStartBounds = scaledRect(destinationBounds, ZOOM_ANIMATION_SCALE_FACTOR);
+
+ final ValueAnimator exitFadeOutAnimator = createAnimator();
+ exitFadeOutAnimator.setInterpolator(TvPipInterpolators.EXIT);
+ exitFadeOutAnimator.setDuration(mExitFadeOutDuration);
+ exitFadeOutAnimator.addUpdateListener(
+ animationUpdateListener(leash)
+ .fadingOut()
+ .withMenu()
+ .atBounds(currentBounds));
+ exitFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: exit fade out animation: end", TAG);
+ startTransaction.apply();
+ mPipMenuController.detach();
+ }
+ });
+
+ final ValueAnimator exitFadeInAnimator = createAnimator();
+ exitFadeInAnimator.setInterpolator(TvPipInterpolators.ENTER);
+ exitFadeInAnimator.setDuration(mExitFadeInDuration);
+ exitFadeInAnimator.addUpdateListener(
+ animationUpdateListener(leash)
+ .fadingIn()
+ .animateBounds(fadeInStartBounds, destinationBounds, destinationBounds));
+
+ final AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playSequentially(
+ exitFadeOutAnimator,
+ exitFadeInAnimator
+ );
+
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: exit animation: start", TAG);
+ mPipTransitionState.setTransitionState(EXITING_PIP);
+ sendOnPipTransitionStarted(TRANSITION_DIRECTION_LEAVE_PIP);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: exit animation: cancel", TAG);
+ sendOnPipTransitionCancelled(TRANSITION_DIRECTION_LEAVE_PIP);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: exit animation: end", TAG);
+ mPipOrganizer.onExitPipFinished(taskInfo);
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+ wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+ wct.setBounds(taskInfo.token, destinationBounds);
+ finishCallback.onTransitionFinished(wct);
+
+ mPipTransitionState.setTransitionState(UNDEFINED);
+ sendOnPipTransitionFinished(TRANSITION_DIRECTION_LEAVE_PIP);
+
+ mCurrentAnimator = null;
+ }
+ });
+
+ animatorSet.start();
+ mCurrentAnimator = animatorSet;
+ }
+
+ @NonNull
+ private ValueAnimator createAnimator() {
+ final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+ animator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get());
+ return animator;
+ }
+
+ @NonNull
+ private TvPipTransitionAnimatorUpdateListener animationUpdateListener(
+ @NonNull SurfaceControl leash) {
+ return new TvPipTransitionAnimatorUpdateListener(leash, mTvPipMenuController,
+ mTransactionFactory.getTransaction(), mSurfaceTransactionHelper);
+ }
+
+ @NonNull
+ private static Rect scaledRect(@NonNull Rect rect, float scale) {
+ final Rect out = new Rect(rect);
+ out.inset((int) (rect.width() * (1 - scale) / 2), (int) (rect.height() * (1 - scale) / 2));
+ return out;
+ }
+
+ private boolean isCloseTransition(TransitionInfo info) {
+ final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info);
+ return currentPipTaskChange != null && info.getType() == TRANSIT_CLOSE;
+ }
+
+ @Nullable
+ private TransitionInfo.Change findCurrentPipTaskChange(@NonNull TransitionInfo info) {
+ if (mCurrentPipTaskToken == null) {
+ return null;
+ }
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (mCurrentPipTaskToken.equals(change.getContainer())) {
+ return change;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Whether we should handle the given {@link TransitionInfo} animation as entering PIP.
+ */
+ private boolean isEnteringPip(@NonNull TransitionInfo info) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (isEnteringPip(change, info.getType())) return true;
+ }
+ return false;
+ }
+
+ /**
+ * Whether a particular change is a window that is entering pip.
+ */
+ @Override
+ public boolean isEnteringPip(@NonNull TransitionInfo.Change change,
+ @WindowManager.TransitionType int transitType) {
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED
+ && !Objects.equals(change.getContainer(), mCurrentPipTaskToken)) {
+ if (transitType == TRANSIT_PIP || transitType == TRANSIT_OPEN
+ || transitType == TRANSIT_CHANGE) {
+ return true;
+ }
+ // Please file a bug to handle the unexpected transition type.
+ android.util.Slog.e(TAG, "Found new PIP in transition with mis-matched type="
+ + transitTypeToString(transitType), new Throwable());
+ }
+ return false;
+ }
+
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: merge animation", TAG);
+ if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
+ mCurrentAnimator.end();
+ }
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ if (requestHasPipEnter(request)) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: handle PiP enter request", TAG);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ augmentRequest(transition, request, wct);
+ return wct;
+ } else if (request.getType() == TRANSIT_TO_BACK && request.getTriggerTask() != null
+ && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_PINNED) {
+ // if we receive a TRANSIT_TO_BACK type of request while in PiP
+ mPendingExitTransition = transition;
+
+ // update the transition state to avoid {@link PipTaskOrganizer#onTaskVanished()} calls
+ mPipTransitionState.setTransitionState(EXITING_PIP);
+
+ // return an empty WindowContainerTransaction so that we don't check other handlers
+ return new WindowContainerTransaction();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void augmentRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request,
+ @NonNull WindowContainerTransaction outWCT) {
+ if (!requestHasPipEnter(request)) {
+ throw new IllegalStateException("Called PiP augmentRequest when request has no PiP");
+ }
+ outWCT.setActivityWindowingMode(request.getTriggerTask().token, WINDOWING_MODE_UNDEFINED);
+ }
+
+ /**
+ * Cancel any ongoing PiP transitions/animations.
+ */
+ public void cancelAnimations() {
+ if (mPipAnimationController.isAnimating()) {
+ mPipAnimationController.getCurrentAnimator().cancel();
+ mPipAnimationController.resetAnimatorState();
+ }
+ if (mCurrentAnimator != null) {
+ mCurrentAnimator.cancel();
+ }
+ }
+
+ @Override
+ public void end() {
+ if (mCurrentAnimator != null) {
+ mCurrentAnimator.end();
+ }
+ }
+
+ private static class TvPipTransitionAnimatorUpdateListener implements
+ ValueAnimator.AnimatorUpdateListener {
+ private final SurfaceControl mLeash;
+ private final TvPipMenuController mTvPipMenuController;
+ private final SurfaceControl.Transaction mTransaction;
+ private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
+ private final RectF mTmpRectF = new RectF();
+ private final Rect mTmpRect = new Rect();
+
+ private float mStartAlpha = ALPHA_NO_CHANGE;
+ private float mEndAlpha = ALPHA_NO_CHANGE;
+
+ @Nullable
+ private Rect mStartBounds;
+ @Nullable
+ private Rect mEndBounds;
+ private Rect mWindowContainerBounds;
+ private boolean mShowMenu;
+
+ TvPipTransitionAnimatorUpdateListener(@NonNull SurfaceControl leash,
+ @NonNull TvPipMenuController tvPipMenuController,
+ @NonNull SurfaceControl.Transaction transaction,
+ @NonNull PipSurfaceTransactionHelper pipSurfaceTransactionHelper) {
+ mLeash = leash;
+ mTvPipMenuController = tvPipMenuController;
+ mTransaction = transaction;
+ mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
+ }
+
+ public TvPipTransitionAnimatorUpdateListener animateAlpha(
+ @FloatRange(from = 0.0, to = 1.0) float startAlpha,
+ @FloatRange(from = 0.0, to = 1.0) float endAlpha) {
+ mStartAlpha = startAlpha;
+ mEndAlpha = endAlpha;
+ return this;
+ }
+
+ public TvPipTransitionAnimatorUpdateListener animateBounds(@NonNull Rect startBounds,
+ @NonNull Rect endBounds, @NonNull Rect windowContainerBounds) {
+ mStartBounds = startBounds;
+ mEndBounds = endBounds;
+ mWindowContainerBounds = windowContainerBounds;
+ return this;
+ }
+
+ public TvPipTransitionAnimatorUpdateListener atBounds(@NonNull Rect bounds) {
+ return animateBounds(bounds, bounds, bounds);
+ }
+
+ public TvPipTransitionAnimatorUpdateListener fadingOut() {
+ return animateAlpha(1f, 0f);
+ }
+
+ public TvPipTransitionAnimatorUpdateListener fadingIn() {
+ return animateAlpha(0f, 1f);
+ }
+
+ public TvPipTransitionAnimatorUpdateListener withMenu() {
+ mShowMenu = true;
+ return this;
+ }
+
+ @Override
+ public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+ final float fraction = animation.getAnimatedFraction();
+ final float alpha = lerp(mStartAlpha, mEndAlpha, fraction);
+ if (mStartBounds != null && mEndBounds != null) {
+ lerp(mStartBounds, mEndBounds, fraction, mTmpRectF);
+ applyAnimatedValue(alpha, mTmpRectF);
+ } else {
+ applyAnimatedValue(alpha, null);
+ }
+ }
+
+ private void applyAnimatedValue(float alpha, @Nullable RectF bounds) {
+ Trace.beginSection("applyAnimatedValue");
+ final SurfaceControl.Transaction tx = mTransaction;
+
+ Trace.beginSection("leash scale and alpha");
+ if (alpha != ALPHA_NO_CHANGE) {
+ mSurfaceTransactionHelper.alpha(tx, mLeash, alpha);
+ }
+ if (bounds != null) {
+ mSurfaceTransactionHelper.scale(tx, mLeash, mWindowContainerBounds, bounds);
+ }
+ mSurfaceTransactionHelper.shadow(tx, mLeash, false);
+ tx.show(mLeash);
+ Trace.endSection();
+
+ if (mShowMenu) {
+ Trace.beginSection("movePipMenu");
+ if (bounds != null) {
+ mTmpRect.set((int) bounds.left, (int) bounds.top, (int) bounds.right,
+ (int) bounds.bottom);
+ mTvPipMenuController.movePipMenu(tx, mTmpRect, alpha);
+ } else {
+ mTvPipMenuController.movePipMenu(tx, null, alpha);
+ }
+ Trace.endSection();
+ } else {
+ mTvPipMenuController.movePipMenu(tx, null, 0f);
+ }
+
+ tx.apply();
+ Trace.endSection();
+ }
+
+ private float lerp(float start, float end, float fraction) {
+ return start * (1 - fraction) + end * fraction;
+ }
+
+ private void lerp(@NonNull Rect start, @NonNull Rect end, float fraction,
+ @NonNull RectF out) {
+ out.set(
+ start.left * (1 - fraction) + end.left * fraction,
+ start.top * (1 - fraction) + end.top * fraction,
+ start.right * (1 - fraction) + end.right * fraction,
+ start.bottom * (1 - fraction) + end.bottom * fraction);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 4e2b7f6..800f9e4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -66,6 +66,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -283,7 +284,7 @@
doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
.round(any(), any(), anyBoolean());
doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
- .scale(any(), any(), any(), any(), anyFloat());
+ .scale(any(), any(), any(), ArgumentMatchers.<Rect>any(), anyFloat());
doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
.alpha(any(), any(), anyFloat());
doNothing().when(mMockPipSurfaceTransactionHelper).onDensityOrFontScaleChanged(any());
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index b63ee1b..a9d1a2a 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -25,8 +25,9 @@
#include "MinikinSkia.h"
#include "SkPaint.h"
-#include "SkStream.h" // Fot tests.
+#include "SkStream.h" // For tests.
#include "SkTypeface.h"
+#include "utils/TypefaceUtils.h"
#include <minikin/FontCollection.h>
#include <minikin/FontFamily.h>
@@ -186,7 +187,9 @@
LOG_ALWAYS_FATAL_IF(fstat(fd, &st) == -1, "Failed to stat file %s", kRobotoFont);
void* data = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(data, st.st_size));
- sk_sp<SkTypeface> typeface = SkTypeface::MakeFromStream(std::move(fontData));
+ sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
+ LOG_ALWAYS_FATAL_IF(fm == nullptr, "Could not load FreeType SkFontMgr");
+ sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(fontData));
LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont);
std::shared_ptr<minikin::MinikinFont> font =
diff --git a/media/java/android/media/MediaMetrics.java b/media/java/android/media/MediaMetrics.java
index 5509782..f522974 100644
--- a/media/java/android/media/MediaMetrics.java
+++ b/media/java/android/media/MediaMetrics.java
@@ -148,6 +148,7 @@
createKey("headTrackerEnabled", String.class); // spatializer
public static final Key<Integer> INDEX = createKey("index", Integer.class); // volume
+ public static final Key<Integer> OLD_INDEX = createKey("oldIndex", Integer.class); // volume
public static final Key<Integer> INPUT_PORT_COUNT =
createKey("inputPortCount", Integer.class); // MIDI
// Either "true" or "false"
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 9f2a9ac..fea6c5f 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -336,13 +336,6 @@
APerformanceHint_closeSession; # introduced=Tiramisu
APerformanceHint_setThreads; # introduced=UpsideDownCake
APerformanceHint_setPreferPowerEfficiency; # introduced=VanillaIceCream
- APerformanceHint_reportActualWorkDuration2; # introduced=VanillaIceCream
- AWorkDuration_create; # introduced=VanillaIceCream
- AWorkDuration_release; # introduced=VanillaIceCream
- AWorkDuration_setWorkPeriodStartTimestampNanos; # introduced=VanillaIceCream
- AWorkDuration_setActualTotalDurationNanos; # introduced=VanillaIceCream
- AWorkDuration_setActualCpuDurationNanos; # introduced=VanillaIceCream
- AWorkDuration_setActualGpuDurationNanos; # introduced=VanillaIceCream
local:
*;
};
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index c4c8128..c25df6e 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -18,14 +18,12 @@
#include <aidl/android/hardware/power/SessionHint.h>
#include <aidl/android/hardware/power/SessionMode.h>
-#include <android/WorkDuration.h>
#include <android/os/IHintManager.h>
#include <android/os/IHintSession.h>
#include <android/performance_hint.h>
#include <binder/Binder.h>
#include <binder/IBinder.h>
#include <binder/IServiceManager.h>
-#include <inttypes.h>
#include <performance_hint_private.h>
#include <utils/SystemClock.h>
@@ -77,13 +75,10 @@
int setThreads(const int32_t* threadIds, size_t size);
int getThreadIds(int32_t* const threadIds, size_t* size);
int setPreferPowerEfficiency(bool enabled);
- int reportActualWorkDuration(AWorkDuration* workDuration);
private:
friend struct APerformanceHintManager;
- int reportActualWorkDurationInternal(WorkDuration* workDuration);
-
sp<IHintManager> mHintManager;
sp<IHintSession> mHintSession;
// HAL preferred update rate
@@ -97,7 +92,8 @@
// Last hint reported from sendHint indexed by hint value
std::vector<int64_t> mLastHintSentTimestamp;
// Cached samples
- std::vector<WorkDuration> mActualWorkDurations;
+ std::vector<int64_t> mActualDurationsNanos;
+ std::vector<int64_t> mTimestampsNanos;
};
static IHintManager* gIHintManagerForTesting = nullptr;
@@ -199,7 +195,8 @@
* Most of the workload is target_duration dependent, so now clear the cached samples
* as they are most likely obsolete.
*/
- mActualWorkDurations.clear();
+ mActualDurationsNanos.clear();
+ mTimestampsNanos.clear();
mFirstTargetMetTimestamp = 0;
mLastTargetMetTimestamp = 0;
return 0;
@@ -210,10 +207,43 @@
ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__);
return EINVAL;
}
+ int64_t now = elapsedRealtimeNano();
+ mActualDurationsNanos.push_back(actualDurationNanos);
+ mTimestampsNanos.push_back(now);
- WorkDuration workDuration(0, actualDurationNanos, actualDurationNanos, 0);
+ if (actualDurationNanos >= mTargetDurationNanos) {
+ // Reset timestamps if we are equal or over the target.
+ mFirstTargetMetTimestamp = 0;
+ } else {
+ // Set mFirstTargetMetTimestamp for first time meeting target.
+ if (!mFirstTargetMetTimestamp || !mLastTargetMetTimestamp ||
+ (now - mLastTargetMetTimestamp > 2 * mPreferredRateNanos)) {
+ mFirstTargetMetTimestamp = now;
+ }
+ /**
+ * Rate limit the change if the update is over mPreferredRateNanos since first
+ * meeting target and less than mPreferredRateNanos since last meeting target.
+ */
+ if (now - mFirstTargetMetTimestamp > mPreferredRateNanos &&
+ now - mLastTargetMetTimestamp <= mPreferredRateNanos) {
+ return 0;
+ }
+ mLastTargetMetTimestamp = now;
+ }
- return reportActualWorkDurationInternal(&workDuration);
+ binder::Status ret =
+ mHintSession->reportActualWorkDuration(mActualDurationsNanos, mTimestampsNanos);
+ if (!ret.isOk()) {
+ ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__,
+ ret.exceptionMessage().c_str());
+ mFirstTargetMetTimestamp = 0;
+ mLastTargetMetTimestamp = 0;
+ return EPIPE;
+ }
+ mActualDurationsNanos.clear();
+ mTimestampsNanos.clear();
+
+ return 0;
}
int APerformanceHintSession::sendHint(SessionHint hint) {
@@ -292,67 +322,6 @@
return OK;
}
-int APerformanceHintSession::reportActualWorkDuration(AWorkDuration* aWorkDuration) {
- WorkDuration* workDuration = static_cast<WorkDuration*>(aWorkDuration);
- if (workDuration->workPeriodStartTimestampNanos <= 0) {
- ALOGE("%s: workPeriodStartTimestampNanos must be positive", __FUNCTION__);
- return EINVAL;
- }
- if (workDuration->actualTotalDurationNanos <= 0) {
- ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__);
- return EINVAL;
- }
- if (workDuration->actualCpuDurationNanos <= 0) {
- ALOGE("%s: cpuDurationNanos must be positive", __FUNCTION__);
- return EINVAL;
- }
- if (workDuration->actualGpuDurationNanos < 0) {
- ALOGE("%s: gpuDurationNanos must be non negative", __FUNCTION__);
- return EINVAL;
- }
-
- return reportActualWorkDurationInternal(workDuration);
-}
-
-int APerformanceHintSession::reportActualWorkDurationInternal(WorkDuration* workDuration) {
- int64_t actualTotalDurationNanos = workDuration->actualTotalDurationNanos;
- int64_t now = uptimeNanos();
- workDuration->timestampNanos = now;
- mActualWorkDurations.push_back(std::move(*workDuration));
-
- if (actualTotalDurationNanos >= mTargetDurationNanos) {
- // Reset timestamps if we are equal or over the target.
- mFirstTargetMetTimestamp = 0;
- } else {
- // Set mFirstTargetMetTimestamp for first time meeting target.
- if (!mFirstTargetMetTimestamp || !mLastTargetMetTimestamp ||
- (now - mLastTargetMetTimestamp > 2 * mPreferredRateNanos)) {
- mFirstTargetMetTimestamp = now;
- }
- /**
- * Rate limit the change if the update is over mPreferredRateNanos since first
- * meeting target and less than mPreferredRateNanos since last meeting target.
- */
- if (now - mFirstTargetMetTimestamp > mPreferredRateNanos &&
- now - mLastTargetMetTimestamp <= mPreferredRateNanos) {
- return 0;
- }
- mLastTargetMetTimestamp = now;
- }
-
- binder::Status ret = mHintSession->reportActualWorkDuration2(mActualWorkDurations);
- if (!ret.isOk()) {
- ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__,
- ret.exceptionMessage().c_str());
- mFirstTargetMetTimestamp = 0;
- mLastTargetMetTimestamp = 0;
- return ret.exceptionCode() == binder::Status::EX_ILLEGAL_ARGUMENT ? EINVAL : EPIPE;
- }
- mActualWorkDurations.clear();
-
- return 0;
-}
-
// ===================================== C API
APerformanceHintManager* APerformanceHint_getManager() {
return APerformanceHintManager::getInstance();
@@ -407,64 +376,6 @@
return session->setPreferPowerEfficiency(enabled);
}
-int APerformanceHint_reportActualWorkDuration2(APerformanceHintSession* session,
- AWorkDuration* workDuration) {
- if (session == nullptr || workDuration == nullptr) {
- ALOGE("Invalid value: (session %p, workDuration %p)", session, workDuration);
- return EINVAL;
- }
- return session->reportActualWorkDuration(workDuration);
-}
-
-AWorkDuration* AWorkDuration_create() {
- WorkDuration* workDuration = new WorkDuration();
- return static_cast<AWorkDuration*>(workDuration);
-}
-
-void AWorkDuration_release(AWorkDuration* aWorkDuration) {
- if (aWorkDuration == nullptr) {
- ALOGE("%s: aWorkDuration is nullptr", __FUNCTION__);
- }
- delete aWorkDuration;
-}
-
-void AWorkDuration_setWorkPeriodStartTimestampNanos(AWorkDuration* aWorkDuration,
- int64_t workPeriodStartTimestampNanos) {
- if (aWorkDuration == nullptr || workPeriodStartTimestampNanos <= 0) {
- ALOGE("%s: Invalid value. (AWorkDuration: %p, workPeriodStartTimestampNanos: %" PRIi64 ")",
- __FUNCTION__, aWorkDuration, workPeriodStartTimestampNanos);
- }
- static_cast<WorkDuration*>(aWorkDuration)->workPeriodStartTimestampNanos =
- workPeriodStartTimestampNanos;
-}
-
-void AWorkDuration_setActualTotalDurationNanos(AWorkDuration* aWorkDuration,
- int64_t actualTotalDurationNanos) {
- if (aWorkDuration == nullptr || actualTotalDurationNanos <= 0) {
- ALOGE("%s: Invalid value. (AWorkDuration: %p, actualTotalDurationNanos: %" PRIi64 ")",
- __FUNCTION__, aWorkDuration, actualTotalDurationNanos);
- }
- static_cast<WorkDuration*>(aWorkDuration)->actualTotalDurationNanos = actualTotalDurationNanos;
-}
-
-void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* aWorkDuration,
- int64_t actualCpuDurationNanos) {
- if (aWorkDuration == nullptr || actualCpuDurationNanos <= 0) {
- ALOGE("%s: Invalid value. (AWorkDuration: %p, actualCpuDurationNanos: %" PRIi64 ")",
- __FUNCTION__, aWorkDuration, actualCpuDurationNanos);
- }
- static_cast<WorkDuration*>(aWorkDuration)->actualCpuDurationNanos = actualCpuDurationNanos;
-}
-
-void AWorkDuration_setActualGpuDurationNanos(AWorkDuration* aWorkDuration,
- int64_t actualGpuDurationNanos) {
- if (aWorkDuration == nullptr || actualGpuDurationNanos < 0) {
- ALOGE("%s: Invalid value. (AWorkDuration: %p, actualGpuDurationNanos: %" PRIi64 ")",
- __FUNCTION__, aWorkDuration, actualGpuDurationNanos);
- }
- static_cast<WorkDuration*>(aWorkDuration)->actualGpuDurationNanos = actualGpuDurationNanos;
-}
-
void APerformanceHint_setIHintManagerForTesting(void* iManager) {
delete gHintManagerForTesting;
gHintManagerForTesting = nullptr;
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 4553b49..22d33b1 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -16,7 +16,6 @@
#define LOG_TAG "PerformanceHintNativeTest"
-#include <android/WorkDuration.h>
#include <android/os/IHintManager.h>
#include <android/os/IHintSession.h>
#include <android/performance_hint.h>
@@ -61,8 +60,6 @@
MOCK_METHOD(Status, setMode, (int32_t mode, bool enabled), (override));
MOCK_METHOD(Status, close, (), (override));
MOCK_METHOD(IBinder*, onAsBinder, (), (override));
- MOCK_METHOD(Status, reportActualWorkDuration2,
- (const ::std::vector<android::os::WorkDuration>& workDurations), (override));
};
class PerformanceHintTest : public Test {
@@ -123,7 +120,6 @@
std::vector<int64_t> actualDurations;
actualDurations.push_back(20);
EXPECT_CALL(*iSession, reportActualWorkDuration(Eq(actualDurations), _)).Times(Exactly(1));
- EXPECT_CALL(*iSession, reportActualWorkDuration2(_)).Times(Exactly(1));
result = APerformanceHint_reportActualWorkDuration(session, actualDurationNanos);
EXPECT_EQ(0, result);
@@ -242,125 +238,4 @@
APerformanceHintSession* session =
APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration);
ASSERT_TRUE(session);
-}
-
-MATCHER_P(WorkDurationEq, expected, "") {
- if (arg.size() != expected.size()) {
- *result_listener << "WorkDuration vectors are different sizes. Expected: "
- << expected.size() << ", Actual: " << arg.size();
- return false;
- }
- for (int i = 0; i < expected.size(); ++i) {
- android::os::WorkDuration expectedWorkDuration = expected[i];
- android::os::WorkDuration actualWorkDuration = arg[i];
- if (!expectedWorkDuration.equalsWithoutTimestamp(actualWorkDuration)) {
- *result_listener << "WorkDuration at [" << i << "] is different: "
- << "Expected: " << expectedWorkDuration
- << ", Actual: " << actualWorkDuration;
- return false;
- }
- }
- return true;
-}
-
-TEST_F(PerformanceHintTest, TestAPerformanceHint_reportActualWorkDuration2) {
- APerformanceHintManager* manager = createManager();
-
- std::vector<int32_t> tids;
- tids.push_back(1);
- tids.push_back(2);
- int64_t targetDuration = 56789L;
-
- StrictMock<MockIHintSession>* iSession = new StrictMock<MockIHintSession>();
- sp<IHintSession> session_sp(iSession);
-
- EXPECT_CALL(*mMockIHintManager, createHintSession(_, Eq(tids), Eq(targetDuration), _))
- .Times(Exactly(1))
- .WillRepeatedly(DoAll(SetArgPointee<3>(std::move(session_sp)), Return(Status())));
-
- APerformanceHintSession* session =
- APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration);
- ASSERT_TRUE(session);
-
- int64_t targetDurationNanos = 10;
- EXPECT_CALL(*iSession, updateTargetWorkDuration(Eq(targetDurationNanos))).Times(Exactly(1));
- int result = APerformanceHint_updateTargetWorkDuration(session, targetDurationNanos);
- EXPECT_EQ(0, result);
-
- usleep(2); // Sleep for longer than preferredUpdateRateNanos.
- {
- std::vector<android::os::WorkDuration> actualWorkDurations;
- android::os::WorkDuration workDuration(1, 20, 13, 8);
- actualWorkDurations.push_back(workDuration);
-
- EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations)))
- .Times(Exactly(1));
- result = APerformanceHint_reportActualWorkDuration2(session,
- static_cast<AWorkDuration*>(
- &workDuration));
- EXPECT_EQ(0, result);
- }
-
- {
- std::vector<android::os::WorkDuration> actualWorkDurations;
- android::os::WorkDuration workDuration(-1, 20, 13, 8);
- actualWorkDurations.push_back(workDuration);
-
- EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations)))
- .Times(Exactly(1));
- result = APerformanceHint_reportActualWorkDuration2(session,
- static_cast<AWorkDuration*>(
- &workDuration));
- EXPECT_EQ(22, result);
- }
- {
- std::vector<android::os::WorkDuration> actualWorkDurations;
- android::os::WorkDuration workDuration(1, -20, 13, 8);
- actualWorkDurations.push_back(workDuration);
-
- EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations)))
- .Times(Exactly(1));
- result = APerformanceHint_reportActualWorkDuration2(session,
- static_cast<AWorkDuration*>(
- &workDuration));
- EXPECT_EQ(22, result);
- }
- {
- std::vector<android::os::WorkDuration> actualWorkDurations;
- android::os::WorkDuration workDuration(1, 20, -13, 8);
- actualWorkDurations.push_back(workDuration);
-
- EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations)))
- .Times(Exactly(1));
- result = APerformanceHint_reportActualWorkDuration2(session,
- static_cast<AWorkDuration*>(
- &workDuration));
- EXPECT_EQ(EINVAL, result);
- }
- {
- std::vector<android::os::WorkDuration> actualWorkDurations;
- android::os::WorkDuration workDuration(1, 20, 13, -8);
- actualWorkDurations.push_back(workDuration);
-
- EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations)))
- .Times(Exactly(1));
- result = APerformanceHint_reportActualWorkDuration2(session,
- static_cast<AWorkDuration*>(
- &workDuration));
- EXPECT_EQ(EINVAL, result);
- }
-
- EXPECT_CALL(*iSession, close()).Times(Exactly(1));
- APerformanceHint_closeSession(session);
-}
-
-TEST_F(PerformanceHintTest, TestAWorkDuration) {
- AWorkDuration* aWorkDuration = AWorkDuration_create();
- ASSERT_NE(aWorkDuration, nullptr);
-
- AWorkDuration_setWorkPeriodStartTimestampNanos(aWorkDuration, 1);
- AWorkDuration_setActualTotalDurationNanos(aWorkDuration, 20);
- AWorkDuration_setActualCpuDurationNanos(aWorkDuration, 13);
- AWorkDuration_setActualGpuDurationNanos(aWorkDuration, 8);
- AWorkDuration_release(aWorkDuration);
-}
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
index 4533db6..3abdb6f 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
@@ -37,17 +37,17 @@
RequestInfo::class.java
)
-val Intent.getCredentialProviderDataList: List<ProviderData>
+val Intent.getCredentialProviderDataList: List<GetCredentialProviderData>
get() = this.extras?.getParcelableArrayList(
ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
GetCredentialProviderData::class.java
- ) ?: emptyList()
+ ) ?.filterIsInstance<GetCredentialProviderData>() ?: emptyList()
-val Intent.createCredentialProviderDataList: List<ProviderData>
+val Intent.createCredentialProviderDataList: List<CreateCredentialProviderData>
get() = this.extras?.getParcelableArrayList(
ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
CreateCredentialProviderData::class.java
- ) ?: emptyList()
+ ) ?.filterIsInstance<CreateCredentialProviderData>() ?: emptyList()
val Intent.resultReceiver: ResultReceiver?
get() = this.getParcelableExtra(
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
index ee45fbb..d4bca2a 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
@@ -18,7 +18,6 @@
import android.content.Intent
import android.credentials.ui.Entry
-import android.credentials.ui.GetCredentialProviderData
import androidx.credentials.provider.PasswordCredentialEntry
import com.android.credentialmanager.factory.fromSlice
import com.android.credentialmanager.ktx.getCredentialProviderDataList
@@ -32,12 +31,10 @@
fun Intent.toGet(): Request.Get {
val credentialEntries = mutableListOf<Pair<String, Entry>>()
for (providerData in getCredentialProviderDataList) {
- if (providerData is GetCredentialProviderData) {
- for (credentialEntry in providerData.credentialEntries) {
- credentialEntries.add(
- Pair(providerData.providerFlattenedComponentName, credentialEntry)
- )
- }
+ for (credentialEntry in providerData.credentialEntries) {
+ credentialEntries.add(
+ Pair(providerData.providerFlattenedComponentName, credentialEntry)
+ )
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index b1e1585..90c7d46 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -49,6 +49,7 @@
import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider
import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider
import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
+import com.android.settingslib.spa.gallery.ui.CopyablePageProvider
import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
import com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver
@@ -100,6 +101,7 @@
SettingsTextFieldPasswordPageProvider,
SearchScaffoldPageProvider,
CardPageProvider,
+ CopyablePageProvider,
),
rootPages = listOf(
HomePageProvider.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
index f52ceec..1d897f7 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
@@ -44,6 +44,7 @@
import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider
import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
+import com.android.settingslib.spa.gallery.ui.CopyablePageProvider
import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
import com.android.settingslib.spa.widget.scaffold.HomeScaffold
@@ -71,6 +72,7 @@
AlertDialogPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
EditorMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
CardPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ CopyablePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
)
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt
new file mode 100644
index 0000000..f897d8c
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.spa.gallery.ui
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.settingslib.spa.framework.common.EntrySearchData
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.CopyableBody
+
+private const val TITLE = "Sample Copyable"
+
+object CopyablePageProvider : SettingsPageProvider {
+ override val name = "Copyable"
+
+ private val owner = createSettingsPage()
+
+ fun buildInjectEntry(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(owner)
+ .setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
+ .setSearchDataFn { EntrySearchData(title = TITLE) }
+ }
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ RegularScaffold(title = TITLE) {
+ Box(modifier = Modifier.padding(SettingsDimension.itemPadding)) {
+ CopyableBody(body = "Copyable body")
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 905640f..b40e911 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
#
[versions]
-agp = "8.1.3"
+agp = "8.1.4"
compose-compiler = "1.5.1"
dexmaker-mockito = "2.28.3"
jvm = "17"
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/CopyableBody.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/CopyableBody.kt
new file mode 100644
index 0000000..930d0a1
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/CopyableBody.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.spa.widget.ui
+
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.MenuDefaults
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.unit.DpOffset
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+@Composable
+fun CopyableBody(body: String) {
+ var expanded by remember { mutableStateOf(false) }
+ var dpOffset by remember { mutableStateOf(DpOffset.Unspecified) }
+
+ Box(modifier = Modifier
+ .fillMaxWidth()
+ .pointerInput(Unit) {
+ detectTapGestures(
+ onLongPress = {
+ dpOffset = DpOffset(it.x.toDp(), it.y.toDp())
+ expanded = true
+ },
+ )
+ }
+ ) {
+ SettingsBody(body)
+
+ DropdownMenu(
+ expanded = expanded,
+ onDismissRequest = { expanded = false },
+ offset = dpOffset,
+ ) {
+ DropdownMenuTitle(body)
+ DropdownMenuCopy(body) { expanded = false }
+ }
+ }
+}
+
+@Composable
+private fun DropdownMenuTitle(text: String) {
+ Text(
+ text = text,
+ modifier = Modifier
+ .padding(MenuDefaults.DropdownMenuItemContentPadding)
+ .padding(
+ top = SettingsDimension.itemPaddingAround,
+ bottom = SettingsDimension.buttonPaddingVertical,
+ ),
+ color = SettingsTheme.colorScheme.categoryTitle,
+ style = MaterialTheme.typography.labelMedium,
+ )
+}
+
+@Composable
+private fun DropdownMenuCopy(body: String, onCopy: () -> Unit) {
+ val clipboardManager = LocalClipboardManager.current
+ DropdownMenuItem(
+ text = {
+ Text(
+ text = stringResource(android.R.string.copy),
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.bodyLarge,
+ )
+ },
+ onClick = {
+ onCopy()
+ clipboardManager.setText(AnnotatedString(body))
+ }
+ )
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt
new file mode 100644
index 0000000..71072a5
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.spa.widget.ui
+
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.Context
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.longClick
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performTouchInput
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class CopyableBodyTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun text_isDisplayed() {
+ composeTestRule.setContent {
+ CopyableBody(TEXT)
+ }
+
+ composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
+ }
+
+ @Test
+ fun onLongPress_contextMenuDisplayed() {
+ composeTestRule.setContent {
+ CopyableBody(TEXT)
+ }
+
+ composeTestRule.onNodeWithText(TEXT).performTouchInput {
+ longClick()
+ }
+
+ composeTestRule.onNodeWithText(context.getString(android.R.string.copy)).assertIsDisplayed()
+ }
+
+ @Test
+ fun onCopy_saveToClipboard() {
+ val clipboardManager = context.getSystemService(ClipboardManager::class.java)!!
+ clipboardManager.setPrimaryClip(ClipData.newPlainText("", ""))
+ composeTestRule.setContent {
+ CopyableBody(TEXT)
+ }
+
+ composeTestRule.onNodeWithText(TEXT).performTouchInput {
+ longClick()
+ }
+ composeTestRule.onNodeWithText(context.getString(android.R.string.copy)).performClick()
+
+ assertThat(clipboardManager.primaryClip!!.getItemAt(0).text.toString()).isEqualTo(TEXT)
+ }
+
+ private companion object {
+ const val TEXT = "Text"
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
index fc10a27..45295b0 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -36,10 +36,10 @@
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
import com.android.settingslib.development.DevelopmentSettingsEnabler
import com.android.settingslib.spa.framework.compose.rememberDrawablePainter
import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.ui.CopyableBody
import com.android.settingslib.spa.widget.ui.SettingsBody
import com.android.settingslib.spa.widget.ui.SettingsTitle
import com.android.settingslib.spaprivileged.R
@@ -71,26 +71,38 @@
@Composable
private fun InstallType(app: ApplicationInfo) {
if (!app.isInstantApp) return
- Spacer(modifier = Modifier.height(4.dp))
- SettingsBody(stringResource(com.android.settingslib.widget.preference.app.R.string.install_type_instant))
+ Spacer(modifier = Modifier.height(SettingsDimension.paddingSmall))
+ SettingsBody(
+ stringResource(
+ com.android.settingslib.widget.preference.app.R.string.install_type_instant
+ )
+ )
}
@Composable
private fun AppVersion() {
- if (packageInfo.versionName == null) return
- Spacer(modifier = Modifier.height(4.dp))
- SettingsBody(packageInfo.versionNameBidiWrapped)
+ val versionName = packageInfo.versionNameBidiWrapped ?: return
+ Spacer(modifier = Modifier.height(SettingsDimension.paddingSmall))
+ SettingsBody(versionName)
}
@Composable
fun FooterAppVersion(showPackageName: Boolean = rememberIsDevelopmentSettingsEnabled()) {
- if (packageInfo.versionName == null) return
+ val context = LocalContext.current
+ val footer = remember(showPackageName) {
+ val list = mutableListOf<String>()
+ packageInfo.versionNameBidiWrapped?.let {
+ list += context.getString(R.string.version_text, it)
+ }
+ if (showPackageName) {
+ list += packageInfo.packageName
+ }
+ list.joinToString(separator = System.lineSeparator())
+ }
+ if (footer.isBlank()) return
HorizontalDivider()
Column(modifier = Modifier.padding(SettingsDimension.itemPadding)) {
- SettingsBody(stringResource(R.string.version_text, packageInfo.versionNameBidiWrapped))
- if (showPackageName) {
- SettingsBody(packageInfo.packageName)
- }
+ CopyableBody(footer)
}
}
@@ -104,7 +116,7 @@
private companion object {
/** Wrapped the version name, so its directionality still keep same when RTL. */
- val PackageInfo.versionNameBidiWrapped: String
+ val PackageInfo.versionNameBidiWrapped: String?
get() = BidiFormatter.getInstance().unicodeWrap(versionName)
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 785f779..36c91f4 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -45,7 +45,7 @@
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
-import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
+import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreferenceModel
import kotlinx.coroutines.flow.Flow
private const val ENTRY_NAME = "AppList"
@@ -157,7 +157,7 @@
}
val restrictedMode by restrictionsProviderFactory.rememberRestrictedMode(restrictions)
val allowed = listModel.isAllowed(record)
- return RestrictedSwitchPreference.getSummary(
+ return RestrictedSwitchPreferenceModel.getSummary(
context = context,
restrictedModeSupplier = { restrictedMode },
summaryIfNoRestricted = { getSummaryIfNoRestricted(allowed()) },
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreference.kt
new file mode 100644
index 0000000..125f636
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreference.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.preference
+
+import androidx.annotation.VisibleForTesting
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.widget.preference.MainSwitchPreference
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreferenceModel.Companion.RestrictedSwitchWrapper
+
+@Composable
+fun RestrictedMainSwitchPreference(model: SwitchPreferenceModel, restrictions: Restrictions) {
+ RestrictedMainSwitchPreference(model, restrictions, ::RestrictionsProviderImpl)
+}
+
+@VisibleForTesting
+@Composable
+internal fun RestrictedMainSwitchPreference(
+ model: SwitchPreferenceModel,
+ restrictions: Restrictions,
+ restrictionsProviderFactory: RestrictionsProviderFactory,
+) {
+ if (restrictions.keys.isEmpty()) {
+ MainSwitchPreference(model)
+ return
+ }
+ restrictionsProviderFactory.RestrictedSwitchWrapper(model, restrictions) {
+ MainSwitchPreference(it)
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
index e41976f..d5c5574 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
@@ -16,29 +16,14 @@
package com.android.settingslib.spaprivileged.template.preference
-import android.content.Context
import androidx.annotation.VisibleForTesting
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.semantics.Role
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.semantics.toggleableState
-import androidx.compose.ui.state.ToggleableState
import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
-import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
-import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
-import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
-import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
-import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
+import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreferenceModel.Companion.RestrictedSwitchWrapper
@Composable
fun RestrictedSwitchPreference(
@@ -59,91 +44,7 @@
SwitchPreference(model)
return
}
- val context = LocalContext.current
- val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
- val restrictedSwitchModel = remember(restrictedMode) {
- RestrictedSwitchPreferenceModel(context, model, restrictedMode)
- }
- restrictedSwitchModel.RestrictionWrapper {
- SwitchPreference(restrictedSwitchModel)
- }
-}
-
-internal object RestrictedSwitchPreference {
- fun getSummary(
- context: Context,
- restrictedModeSupplier: () -> RestrictedMode?,
- summaryIfNoRestricted: () -> String,
- checked: () -> Boolean?,
- ): () -> String = {
- when (val restrictedMode = restrictedModeSupplier()) {
- is NoRestricted -> summaryIfNoRestricted()
- is BaseUserRestricted -> context.getString(com.android.settingslib.R.string.disabled)
- is BlockedByAdmin -> restrictedMode.getSummary(checked())
- null -> context.getPlaceholder()
- }
- }
-}
-
-private class RestrictedSwitchPreferenceModel(
- context: Context,
- model: SwitchPreferenceModel,
- private val restrictedMode: RestrictedMode?,
-) : SwitchPreferenceModel {
- override val title = model.title
-
- override val summary = RestrictedSwitchPreference.getSummary(
- context = context,
- restrictedModeSupplier = { restrictedMode },
- summaryIfNoRestricted = model.summary,
- checked = model.checked,
- )
-
- override val checked = when (restrictedMode) {
- null -> ({ null })
- is NoRestricted -> model.checked
- is BaseUserRestricted -> ({ false })
- is BlockedByAdmin -> model.checked
- }
-
- override val changeable = when (restrictedMode) {
- null -> ({ false })
- is NoRestricted -> model.changeable
- is BaseUserRestricted -> ({ false })
- is BlockedByAdmin -> ({ false })
- }
-
- override val onCheckedChange = when (restrictedMode) {
- null -> null
- is NoRestricted -> model.onCheckedChange
- // Need to passthrough onCheckedChange for toggleable semantics, although since changeable
- // is false so this will not be called.
- is BaseUserRestricted -> model.onCheckedChange
- // Pass null since semantics ToggleableState is provided in RestrictionWrapper.
- is BlockedByAdmin -> null
- }
-
- @Composable
- fun RestrictionWrapper(content: @Composable () -> Unit) {
- if (restrictedMode !is BlockedByAdmin) {
- content()
- return
- }
- Box(
- Modifier
- .clickable(
- role = Role.Switch,
- onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() },
- )
- .semantics {
- this.toggleableState = ToggleableState(checked())
- },
- ) { content() }
- }
-
- private fun ToggleableState(value: Boolean?) = when (value) {
- true -> ToggleableState.On
- false -> ToggleableState.Off
- null -> ToggleableState.Indeterminate
+ restrictionsProviderFactory.RestrictedSwitchWrapper(model, restrictions) {
+ SwitchPreference(it)
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
new file mode 100644
index 0000000..fa44ecb
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.preference
+
+import android.content.Context
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.toggleableState
+import androidx.compose.ui.state.ToggleableState
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
+
+internal class RestrictedSwitchPreferenceModel(
+ context: Context,
+ model: SwitchPreferenceModel,
+ private val restrictedMode: RestrictedMode?,
+) : SwitchPreferenceModel {
+ override val title = model.title
+
+ override val summary = getSummary(
+ context = context,
+ restrictedModeSupplier = { restrictedMode },
+ summaryIfNoRestricted = model.summary,
+ checked = model.checked,
+ )
+
+ override val checked = when (restrictedMode) {
+ null -> ({ null })
+ is NoRestricted -> model.checked
+ is BaseUserRestricted -> ({ false })
+ is BlockedByAdmin -> model.checked
+ }
+
+ override val changeable = if (restrictedMode is NoRestricted) model.changeable else ({ false })
+
+ override val onCheckedChange = when (restrictedMode) {
+ null -> null
+ is NoRestricted -> model.onCheckedChange
+ // Need to passthrough onCheckedChange for toggleable semantics, although since changeable
+ // is false so this will not be called.
+ is BaseUserRestricted -> model.onCheckedChange
+ // Pass null since semantics ToggleableState is provided in RestrictionWrapper.
+ is BlockedByAdmin -> null
+ }
+
+ @Composable
+ fun RestrictionWrapper(content: @Composable () -> Unit) {
+ if (restrictedMode !is BlockedByAdmin) {
+ content()
+ return
+ }
+ Box(
+ Modifier
+ .clickable(
+ role = Role.Switch,
+ onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() },
+ )
+ .semantics {
+ this.toggleableState = ToggleableState(checked())
+ },
+ ) { content() }
+ }
+
+ private fun ToggleableState(value: Boolean?) = when (value) {
+ true -> ToggleableState.On
+ false -> ToggleableState.Off
+ null -> ToggleableState.Indeterminate
+ }
+
+ companion object {
+ @Composable
+ fun RestrictionsProviderFactory.RestrictedSwitchWrapper(
+ model: SwitchPreferenceModel,
+ restrictions: Restrictions,
+ content: @Composable (SwitchPreferenceModel) -> Unit,
+ ) {
+ val context = LocalContext.current
+ val restrictedMode = rememberRestrictedMode(restrictions).value
+ val restrictedSwitchPreferenceModel = remember(restrictedMode) {
+ RestrictedSwitchPreferenceModel(context, model, restrictedMode)
+ }
+ restrictedSwitchPreferenceModel.RestrictionWrapper {
+ content(restrictedSwitchPreferenceModel)
+ }
+ }
+
+ fun getSummary(
+ context: Context,
+ restrictedModeSupplier: () -> RestrictedMode?,
+ summaryIfNoRestricted: () -> String,
+ checked: () -> Boolean?,
+ ): () -> String = {
+ when (val restrictedMode = restrictedModeSupplier()) {
+ is NoRestricted -> summaryIfNoRestricted()
+ is BaseUserRestricted ->
+ context.getString(com.android.settingslib.R.string.disabled)
+
+ is BlockedByAdmin -> restrictedMode.getSummary(checked())
+ null -> context.getPlaceholder()
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
index ab34f68..72a5bd7 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
@@ -105,7 +105,8 @@
}
}
- composeTestRule.onNodeWithText("version $VERSION_NAME").assertIsDisplayed()
+ composeTestRule.onNodeWithText(text = "version $VERSION_NAME", substring = true)
+ .assertIsDisplayed()
}
@Test
@@ -119,10 +120,10 @@
composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides context) {
- appInfoProvider.FooterAppVersion(true)
+ appInfoProvider.FooterAppVersion(showPackageName = true)
}
}
- composeTestRule.onNodeWithText(PACKAGE_NAME).assertIsDisplayed()
+ composeTestRule.onNodeWithText(text = PACKAGE_NAME, substring = true).assertIsDisplayed()
}
@@ -137,10 +138,10 @@
composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides context) {
- appInfoProvider.FooterAppVersion(false)
+ appInfoProvider.FooterAppVersion(showPackageName = false)
}
}
- composeTestRule.onNodeWithText(PACKAGE_NAME).assertDoesNotExist()
+ composeTestRule.onNodeWithText(text = PACKAGE_NAME, substring = true).assertDoesNotExist()
}
private companion object {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreferenceTest.kt
new file mode 100644
index 0000000..55c16bd
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreferenceTest.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.preference
+
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.isOff
+import androidx.compose.ui.test.isOn
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin
+import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RestrictedMainSwitchPreferenceTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val fakeBlockedByAdmin = FakeBlockedByAdmin()
+
+ private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
+ private val switchPreferenceModel = object : SwitchPreferenceModel {
+ override val title = TITLE
+ private val checkedState = mutableStateOf(true)
+ override val checked = { checkedState.value }
+ override val onCheckedChange: (Boolean) -> Unit = { checkedState.value = it }
+ }
+
+ @Test
+ fun whenRestrictionsKeysIsEmpty_enabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ composeTestRule.onNode(isOn()).assertIsDisplayed()
+ }
+
+ @Test
+ fun whenRestrictionsKeysIsEmpty_toggleable() {
+ val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+ setContent(restrictions)
+ composeTestRule.onRoot().performClick()
+
+ composeTestRule.onNode(isOff()).assertIsDisplayed()
+ }
+
+ @Test
+ fun whenNoRestricted_enabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ composeTestRule.onNode(isOn()).assertIsDisplayed()
+ }
+
+ @Test
+ fun whenNoRestricted_toggleable() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+ setContent(restrictions)
+ composeTestRule.onRoot().performClick()
+
+ composeTestRule.onNode(isOff()).assertIsDisplayed()
+ }
+
+ @Test
+ fun whenBaseUserRestricted_disabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsNotEnabled()
+ composeTestRule.onNode(isOff()).assertIsDisplayed()
+ }
+
+ @Test
+ fun whenBaseUserRestricted_notToggleable() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+ setContent(restrictions)
+ composeTestRule.onRoot().performClick()
+
+ composeTestRule.onNode(isOff()).assertIsDisplayed()
+ }
+
+ @Test
+ fun whenBlockedByAdmin_disabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ composeTestRule.onNodeWithText(FakeBlockedByAdmin.SUMMARY).assertDoesNotExist()
+ composeTestRule.onNode(isOn()).assertIsDisplayed()
+ }
+
+ @Test
+ fun whenBlockedByAdmin_click() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+ setContent(restrictions)
+ composeTestRule.onRoot().performClick()
+
+ assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
+ }
+
+ private fun setContent(restrictions: Restrictions) {
+ composeTestRule.setContent {
+ RestrictedMainSwitchPreference(switchPreferenceModel, restrictions) { _, _ ->
+ fakeRestrictionsProvider
+ }
+ }
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ const val USER_ID = 0
+ const val RESTRICTION_KEY = "restriction_key"
+ }
+}
diff --git a/packages/SettingsLib/res/drawable/ic_bt_untethered_earbuds.xml b/packages/SettingsLib/res/drawable/ic_bt_untethered_earbuds.xml
new file mode 100644
index 0000000..bcf5b91
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_bt_untethered_earbuds.xml
@@ -0,0 +1,29 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal" >
+ <path
+ android:fillColor="#4E4639"
+ android:pathData="M21.818,18.14V15.227C21.818,12.522 19.615,10.318 16.909,10.318C14.204,10.318 12,12.522 12,15.227V19.591C12,22.296 14.204,24.5 16.909,24.5C17.662,24.5 18.382,24.326 19.025,24.02C19.538,24.326 20.127,24.5 20.727,24.5C22.527,24.5 24,23.028 24,21.228C24,19.809 23.084,18.587 21.818,18.14ZM16.909,12.5C18.414,12.5 19.636,13.722 19.636,15.227C19.636,16.733 18.414,17.955 16.909,17.955C15.404,17.955 14.182,16.733 14.182,15.227C14.182,13.722 15.404,12.5 16.909,12.5ZM14.182,19.591V19.308C14.967,19.831 15.906,20.136 16.909,20.136C17.913,20.136 18.851,19.831 19.636,19.308V19.591C19.636,19.635 19.636,19.678 19.636,19.711C19.636,19.722 19.636,19.733 19.636,19.744C19.636,19.777 19.636,19.82 19.625,19.853C19.625,19.864 19.625,19.886 19.625,19.896C19.625,19.918 19.615,19.951 19.615,19.973C19.615,19.995 19.604,20.028 19.604,20.049C19.604,20.06 19.593,20.082 19.593,20.093C19.549,20.3 19.494,20.497 19.407,20.693C19.385,20.736 19.364,20.78 19.342,20.824C19.342,20.835 19.331,20.846 19.331,20.846C19.32,20.867 19.309,20.889 19.298,20.911C19.287,20.933 19.265,20.966 19.254,20.987C19.244,20.998 19.244,21.009 19.233,21.02C19.211,21.053 19.2,21.075 19.178,21.107C19.178,21.107 19.178,21.108 19.178,21.118C18.687,21.838 17.858,22.318 16.92,22.318C15.404,22.318 14.182,21.097 14.182,19.591Z" />
+ <path
+ android:fillColor="#4E4639"
+ android:pathData="M12,5.409C12,2.704 9.796,0.5 7.091,0.5C4.385,0.5 2.182,2.704 2.182,5.409V8.322C0.916,8.769 0,9.991 0,11.409C0,13.209 1.473,14.682 3.273,14.682C3.873,14.682 4.462,14.507 4.975,14.202C5.618,14.507 6.338,14.682 7.091,14.682C9.796,14.682 12,12.478 12,9.773V5.409ZM7.091,2.682C8.596,2.682 9.818,3.904 9.818,5.409C9.818,6.915 8.596,8.136 7.091,8.136C5.585,8.136 4.364,6.915 4.364,5.409C4.364,3.904 5.585,2.682 7.091,2.682ZM7.091,12.5C6.153,12.5 5.324,12.02 4.833,11.3C4.833,11.3 4.833,11.3 4.833,11.289C4.811,11.256 4.8,11.234 4.778,11.202C4.767,11.191 4.767,11.18 4.756,11.169C4.745,11.147 4.724,11.115 4.713,11.093C4.702,11.071 4.691,11.049 4.68,11.027C4.68,11.016 4.669,11.005 4.669,11.005C4.647,10.962 4.625,10.918 4.604,10.875C4.516,10.678 4.451,10.482 4.418,10.274C4.418,10.264 4.407,10.242 4.407,10.231C4.407,10.209 4.396,10.176 4.396,10.155C4.396,10.133 4.385,10.1 4.385,10.078C4.385,10.067 4.385,10.045 4.385,10.035C4.385,10.002 4.375,9.958 4.375,9.925C4.375,9.915 4.375,9.904 4.375,9.893C4.364,9.86 4.364,9.816 4.364,9.773V9.489C5.149,10.013 6.087,10.318 7.091,10.318C8.095,10.318 9.033,10.013 9.818,9.489V9.773C9.818,11.278 8.596,12.5 7.091,12.5Z" />
+</vector>
+
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 2c15fc6..8412cba 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -249,6 +249,8 @@
Settings.Secure.HUB_MODE_TUTORIAL_STATE,
Settings.Secure.STYLUS_BUTTONS_ENABLED,
Settings.Secure.STYLUS_HANDWRITING_ENABLED,
- Settings.Secure.DEFAULT_NOTE_TASK_PROFILE
+ Settings.Secure.DEFAULT_NOTE_TASK_PROFILE,
+ Settings.Secure.CREDENTIAL_SERVICE,
+ Settings.Secure.CREDENTIAL_SERVICE_PRIMARY
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 71c2ddc..9197554 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -19,11 +19,13 @@
import static android.provider.settings.validators.SettingsValidators.ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.AUTOFILL_SERVICE_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_COMPONENT_LIST_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_PACKAGE_LIST_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.COMMA_SEPARATED_COMPONENT_LIST_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.JSON_OBJECT_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.LOCALE_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.NONE_NEGATIVE_LONG_VALIDATOR;
@@ -62,7 +64,6 @@
VALIDATORS.put(Secure.ADAPTIVE_CHARGING_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ADAPTIVE_SLEEP, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.CAMERA_AUTOROTATE, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.AUTOFILL_SERVICE, NULLABLE_COMPONENT_NAME_VALIDATOR);
VALIDATORS.put(
Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
new InclusiveFloatRangeValidator(1.0f, Float.MAX_VALUE));
@@ -398,5 +399,8 @@
VALIDATORS.put(Secure.STYLUS_HANDWRITING_ENABLED,
new DiscreteValueValidator(new String[] {"-1", "0", "1"}));
VALIDATORS.put(Secure.DEFAULT_NOTE_TASK_PROFILE, NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.CREDENTIAL_SERVICE, CREDENTIAL_SERVICE_VALIDATOR);
+ VALIDATORS.put(Secure.CREDENTIAL_SERVICE_PRIMARY, NULLABLE_COMPONENT_NAME_VALIDATOR);
+ VALIDATORS.put(Secure.AUTOFILL_SERVICE, AUTOFILL_SERVICE_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
index 49012b0..a8a659e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
@@ -235,4 +235,30 @@
}
}
};
+
+ static final Validator CREDENTIAL_SERVICE_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ if (value == null || value.equals("")) {
+ return true;
+ }
+
+ return COLON_SEPARATED_COMPONENT_LIST_VALIDATOR.validate(value);
+ }
+ };
+
+ static final Validator AUTOFILL_SERVICE_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ if (value == null || value.equals("")) {
+ return true;
+ }
+
+ if (value.equals("credential-provider")) {
+ return true;
+ }
+
+ return NULLABLE_COMPONENT_NAME_VALIDATOR.validate(value);
+ }
+ };
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index f1b53ed..efed8c3 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -851,8 +851,6 @@
Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
Settings.Secure.UI_TRANSLATION_ENABLED,
- Settings.Secure.CREDENTIAL_SERVICE,
- Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED,
Settings.Secure.DND_CONFIGS_MIGRATED,
Settings.Secure.NAVIGATION_MODE_RESTORE);
diff --git a/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java b/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java
index 865f431..3b3bf8ca 100644
--- a/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java
@@ -340,6 +340,60 @@
failIfOffendersPresent(offenders, "Settings.Secure");
}
+ @Test
+ public void testCredentialServiceValidator_returnsTrueIfNull() {
+ assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate(null));
+ }
+
+ @Test
+ public void testCredentialServiceValidator_returnsTrueIfEmpty() {
+ assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate(""));
+ }
+
+ @Test
+ public void testCredentialServiceValidator_returnsTrueIfSingleComponentName() {
+ assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate(
+ "android.credentials/android.credentials.Test"));
+ }
+
+ @Test
+ public void testCredentialServiceValidator_returnsTrueIfMultipleComponentName() {
+ assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate(
+ "android.credentials/android.credentials.Test"
+ + ":android.credentials/.Test2"));
+ }
+
+ @Test
+ public void testCredentialServiceValidator_returnsFalseIfInvalidComponentName() {
+ assertFalse(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate("test"));
+ }
+
+ @Test
+ public void testAutofillServiceValidator_returnsTrueIfNull() {
+ assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate(null));
+ }
+
+ @Test
+ public void testAutofillServiceValidator_returnsTrueIfEmpty() {
+ assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate(""));
+ }
+
+ @Test
+ public void testAutofillServiceValidator_returnsTrueIfPlaceholder() {
+ assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate("credential-provider"));
+ }
+
+ @Test
+ public void testAutofillServiceValidator_returnsTrueIfSingleComponentName() {
+ assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate(
+ "android.credentials/android.credentials.Test"));
+ }
+
+ @Test
+ public void testAutofillServiceValidator_returnsFalseIfInvalidComponentName() {
+ assertFalse(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate("test"));
+ }
+
private void failIfOffendersPresent(String offenders, String settingsType) {
if (offenders.length() > 0) {
fail("All " + settingsType + " settings that are backed up have to have a non-null"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 68d4b63..a745ab5 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -140,4 +140,11 @@
namespace: "systemui"
description: "Move LightRevealScrim to recommended architecture"
bug: "281655028"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "media_in_scene_container"
+ namespace: "systemui"
+ description: "Enable media in the scene container framework"
+ bug: "296122467"
+}
diff --git a/packages/SystemUI/communal/layout/Android.bp b/packages/SystemUI/communal/layout/Android.bp
deleted file mode 100644
index 88dad66..0000000
--- a/packages/SystemUI/communal/layout/Android.bp
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
- default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
-}
-
-android_library {
- name: "CommunalLayoutLib",
- srcs: [
- "src/**/*.kt",
- ],
- static_libs: [
- "androidx.arch.core_core-runtime",
- "androidx.compose.animation_animation-graphics",
- "androidx.compose.runtime_runtime",
- "androidx.compose.material3_material3",
- "jsr330",
- "kotlinx-coroutines-android",
- "kotlinx-coroutines-core",
- ],
- manifest: "AndroidManifest.xml",
- kotlincflags: ["-Xjvm-default=all"],
-}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt
deleted file mode 100644
index 91fe33c..0000000
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.communal.layout
-
-import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
-
-/** Computes the arrangement of cards. */
-class CommunalLayoutEngine {
- companion object {
- /**
- * Determines the size that each card should be rendered in, and distributes the cards into
- * columns.
- *
- * Returns a nested list where the outer list contains columns, and the inner list contains
- * cards in each column.
- *
- * Currently treats the first supported size as the size to be rendered in, ignoring other
- * supported sizes.
- *
- * Cards are ordered by priority, from highest to lowest.
- */
- fun distributeCardsIntoColumns(
- cards: List<CommunalGridLayoutCard>,
- ): List<List<CommunalGridLayoutCardInfo>> {
- val result = ArrayList<ArrayList<CommunalGridLayoutCardInfo>>()
-
- var capacityOfLastColumn = 0
- val sorted = cards.sortedByDescending { it.priority }
- for (card in sorted) {
- val cardSize = card.supportedSizes.first()
- if (capacityOfLastColumn >= cardSize.value) {
- // Card fits in last column
- capacityOfLastColumn -= cardSize.value
- } else {
- // Create a new column
- result.add(arrayListOf())
- capacityOfLastColumn = CommunalGridLayoutCard.Size.FULL.value - cardSize.value
- }
-
- result.last().add(CommunalGridLayoutCardInfo(card, cardSize))
- }
-
- return result
- }
- }
-
- /**
- * A data class that wraps around a [CommunalGridLayoutCard] and also contains the size that the
- * card should be rendered in.
- */
- data class CommunalGridLayoutCardInfo(
- val card: CommunalGridLayoutCard,
- val size: CommunalGridLayoutCard.Size,
- )
-}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
deleted file mode 100644
index 33024f7..0000000
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.communal.layout.ui.compose
-
-import android.util.SizeF
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import com.android.systemui.communal.layout.CommunalLayoutEngine
-import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
-import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig
-
-/**
- * An arrangement of cards with a horizontal scroll, where each card is displayed in the right size
- * and follows a specific order based on its priority, ensuring a seamless layout without any gaps.
- */
-@Composable
-fun CommunalGridLayout(
- modifier: Modifier,
- layoutConfig: CommunalGridLayoutConfig,
- communalCards: List<CommunalGridLayoutCard>,
-) {
- val columns = CommunalLayoutEngine.distributeCardsIntoColumns(communalCards)
- LazyRow(
- modifier = modifier.height(layoutConfig.gridHeight),
- horizontalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter),
- ) {
- for (column in columns) {
- item {
- Column(
- modifier = Modifier.width(layoutConfig.cardWidth),
- verticalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter),
- ) {
- for (cardInfo in column) {
- Row(
- modifier = Modifier.height(layoutConfig.cardHeight(cardInfo.size)),
- ) {
- cardInfo.card.Content(
- modifier = Modifier.fillMaxSize(),
- size =
- SizeF(
- layoutConfig.cardWidth.value,
- layoutConfig.cardHeight(cardInfo.size).value,
- ),
- )
- }
- }
- }
- }
- }
- }
-}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
deleted file mode 100644
index 4b2a156..0000000
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.communal.layout.ui.compose.config
-
-import android.util.SizeF
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-
-/** A card that hosts content to be rendered in the communal grid layout. */
-abstract class CommunalGridLayoutCard {
- /**
- * Content to be hosted by the card.
- *
- * To host non-Compose views, see
- * https://developer.android.com/jetpack/compose/migrate/interoperability-apis/views-in-compose.
- *
- * @param size The size given to the card. Content of the card should fill all this space, given
- * that margins and paddings have been taken care of by the layout.
- */
- @Composable abstract fun Content(modifier: Modifier, size: SizeF)
-
- /**
- * Sizes supported by the card.
- *
- * If multiple sizes are available, they should be ranked in order of preference, from most to
- * least preferred.
- */
- abstract val supportedSizes: List<Size>
-
- /**
- * Priority of the content hosted by the card.
- *
- * The value of priority is relative to other cards. Cards with a higher priority are generally
- * ordered first.
- */
- open val priority: Int = 0
-
- /**
- * Size of the card.
- *
- * @param value A numeric value that represents the size. Must be less than or equal to
- * [Size.FULL].
- */
- enum class Size(val value: Int) {
- /** The card takes up full height of the grid layout. */
- FULL(value = 6),
-
- /** The card takes up half of the vertical space of the grid layout. */
- HALF(value = 3),
-
- /** The card takes up a third of the vertical space of the grid layout. */
- THIRD(value = 2),
- }
-}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt
deleted file mode 100644
index 143df83..0000000
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.communal.layout.ui.compose.config
-
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.times
-
-/**
- * Configurations of the communal grid layout.
- *
- * The communal grid layout follows Material Design's responsive layout grid (see
- * https://m2.material.io/design/layout/responsive-layout-grid.html), in which the layout is divided
- * up by columns and gutters, and each card occupies one or multiple columns.
- */
-data class CommunalGridLayoutConfig(
- /**
- * Size in dp of each grid column.
- *
- * Every card occupies one or more grid columns, which means that the width of each card is
- * influenced by the size of the grid columns.
- */
- val gridColumnSize: Dp,
-
- /**
- * Size in dp of each grid gutter.
- *
- * A gutter is the space between columns that helps separate content. This is, therefore, also
- * the size of the gaps between cards, both horizontally and vertically.
- */
- val gridGutter: Dp,
-
- /**
- * Height in dp of the grid layout.
- *
- * Cards with a full size take up the entire height of the grid layout.
- */
- val gridHeight: Dp,
-
- /**
- * Number of grid columns that each card occupies.
- *
- * It's important to note that all the cards take up the same number of grid columns, or in
- * simpler terms, they all have the same width.
- */
- val gridColumnsPerCard: Int,
-) {
- /**
- * Width in dp of each card.
- *
- * It's important to note that all the cards take up the same number of grid columns, or in
- * simpler terms, they all have the same width.
- */
- val cardWidth = gridColumnSize * gridColumnsPerCard + gridGutter * (gridColumnsPerCard - 1)
-
- /** Returns the height of a card in dp, based on its size. */
- fun cardHeight(cardSize: CommunalGridLayoutCard.Size): Dp {
- return when (cardSize) {
- CommunalGridLayoutCard.Size.FULL -> cardHeightBy(denominator = 1)
- CommunalGridLayoutCard.Size.HALF -> cardHeightBy(denominator = 2)
- CommunalGridLayoutCard.Size.THIRD -> cardHeightBy(denominator = 3)
- }
- }
-
- /** Returns the height of a card in dp when the layout is evenly divided by [denominator]. */
- private fun cardHeightBy(denominator: Int): Dp {
- return (gridHeight - (denominator - 1) * gridGutter) / denominator
- }
-}
diff --git a/packages/SystemUI/communal/layout/tests/Android.bp b/packages/SystemUI/communal/layout/tests/Android.bp
deleted file mode 100644
index 9a05504..0000000
--- a/packages/SystemUI/communal/layout/tests/Android.bp
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (C) 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
- default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
-}
-
-android_test {
- name: "CommunalLayoutLibTests",
- srcs: [
- "**/*.kt",
- ],
- static_libs: [
- "CommunalLayoutLib",
- "androidx.test.runner",
- "androidx.test.rules",
- "androidx.test.ext.junit",
- "frameworks-base-testutils",
- "junit",
- "kotlinx_coroutines_test",
- "mockito-target-extended-minus-junit4",
- "platform-test-annotations",
- "testables",
- "truth",
- ],
- libs: [
- "android.test.mock",
- "android.test.base",
- "android.test.runner",
- ],
- jni_libs: [
- "libdexmakerjvmtiagent",
- "libstaticjvmtiagent",
- ],
- manifest: "AndroidManifest.xml",
-}
diff --git a/packages/SystemUI/communal/layout/tests/AndroidManifest.xml b/packages/SystemUI/communal/layout/tests/AndroidManifest.xml
deleted file mode 100644
index b19007c..0000000
--- a/packages/SystemUI/communal/layout/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest
- xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.systemui.communal.layout.tests">
-
- <application android:debuggable="true" android:largeHeap="true">
- <uses-library android:name="android.test.mock" />
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation android:name="android.testing.TestableInstrumentation"
- android:targetPackage="com.android.systemui.communal.layout.tests"
- android:label="Tests for CommunalLayoutLib">
- </instrumentation>
-
-</manifest>
diff --git a/packages/SystemUI/communal/layout/tests/AndroidTest.xml b/packages/SystemUI/communal/layout/tests/AndroidTest.xml
deleted file mode 100644
index 1352b23..0000000
--- a/packages/SystemUI/communal/layout/tests/AndroidTest.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<configuration description="Runs tests for CommunalLayoutLib">
-
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="install-arg" value="-t" />
- <option name="test-file-name" value="CommunalLayoutLibTests.apk" />
- </target_preparer>
-
- <option name="test-suite-tag" value="apct" />
- <option name="test-suite-tag" value="framework-base-presubmit" />
- <option name="test-tag" value="CommunalLayoutLibTests" />
-
- <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="com.android.systemui.communal.layout.tests" />
- <option name="runner" value="android.testing.TestableInstrumentation" />
- <option name="hidden-api-checks" value="false"/>
- </test>
-
-</configuration>
diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
deleted file mode 100644
index 50b7c5f..0000000
--- a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
+++ /dev/null
@@ -1,145 +0,0 @@
-package com.android.systemui.communal.layout
-
-import android.util.SizeF
-import androidx.compose.material3.Card
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalLayoutEngineTest {
- @Test
- fun distribution_fullLayout() {
- val cards =
- listOf(
- generateCard(CommunalGridLayoutCard.Size.FULL),
- generateCard(CommunalGridLayoutCard.Size.HALF),
- generateCard(CommunalGridLayoutCard.Size.HALF),
- generateCard(CommunalGridLayoutCard.Size.THIRD),
- generateCard(CommunalGridLayoutCard.Size.THIRD),
- generateCard(CommunalGridLayoutCard.Size.THIRD),
- )
- val expected =
- listOf(
- listOf(
- CommunalGridLayoutCard.Size.FULL,
- ),
- listOf(
- CommunalGridLayoutCard.Size.HALF,
- CommunalGridLayoutCard.Size.HALF,
- ),
- listOf(
- CommunalGridLayoutCard.Size.THIRD,
- CommunalGridLayoutCard.Size.THIRD,
- CommunalGridLayoutCard.Size.THIRD,
- ),
- )
-
- assertDistributionBySize(cards, expected)
- }
-
- @Test
- fun distribution_layoutWithGaps() {
- val cards =
- listOf(
- generateCard(CommunalGridLayoutCard.Size.HALF),
- generateCard(CommunalGridLayoutCard.Size.THIRD),
- generateCard(CommunalGridLayoutCard.Size.HALF),
- generateCard(CommunalGridLayoutCard.Size.FULL),
- generateCard(CommunalGridLayoutCard.Size.THIRD),
- )
- val expected =
- listOf(
- listOf(
- CommunalGridLayoutCard.Size.HALF,
- CommunalGridLayoutCard.Size.THIRD,
- ),
- listOf(
- CommunalGridLayoutCard.Size.HALF,
- ),
- listOf(
- CommunalGridLayoutCard.Size.FULL,
- ),
- listOf(
- CommunalGridLayoutCard.Size.THIRD,
- ),
- )
-
- assertDistributionBySize(cards, expected)
- }
-
- @Test
- fun distribution_sortByPriority() {
- val cards =
- listOf(
- generateCard(priority = 2),
- generateCard(priority = 7),
- generateCard(priority = 10),
- generateCard(priority = 1),
- generateCard(priority = 5),
- )
- val expected =
- listOf(
- listOf(10, 7),
- listOf(5, 2),
- listOf(1),
- )
-
- assertDistributionByPriority(cards, expected)
- }
-
- private fun assertDistributionBySize(
- cards: List<CommunalGridLayoutCard>,
- expected: List<List<CommunalGridLayoutCard.Size>>,
- ) {
- val result = CommunalLayoutEngine.distributeCardsIntoColumns(cards)
-
- for (c in expected.indices) {
- for (r in expected[c].indices) {
- assertThat(result[c][r].size).isEqualTo(expected[c][r])
- }
- }
- }
-
- private fun assertDistributionByPriority(
- cards: List<CommunalGridLayoutCard>,
- expected: List<List<Int>>,
- ) {
- val result = CommunalLayoutEngine.distributeCardsIntoColumns(cards)
-
- for (c in expected.indices) {
- for (r in expected[c].indices) {
- assertThat(result[c][r].card.priority).isEqualTo(expected[c][r])
- }
- }
- }
-
- private fun generateCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard {
- return object : CommunalGridLayoutCard() {
- override val supportedSizes = listOf(size)
-
- @Composable
- override fun Content(modifier: Modifier, size: SizeF) {
- Card(modifier = modifier, content = {})
- }
- }
- }
-
- private fun generateCard(priority: Int): CommunalGridLayoutCard {
- return object : CommunalGridLayoutCard() {
- override val supportedSizes = listOf(Size.HALF)
- override val priority = priority
-
- @Composable
- override fun Content(modifier: Modifier, size: SizeF) {
- Card(modifier = modifier, content = {})
- }
- }
- }
-}
diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt
deleted file mode 100644
index 946eeec..0000000
--- a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.android.systemui.communal.layout.ui.compose.config
-
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalGridLayoutConfigTest {
- @Test
- fun cardWidth() {
- Truth.assertThat(
- CommunalGridLayoutConfig(
- gridColumnSize = 5.dp,
- gridGutter = 3.dp,
- gridHeight = 17.dp,
- gridColumnsPerCard = 1,
- )
- .cardWidth
- )
- .isEqualTo(5.dp)
-
- Truth.assertThat(
- CommunalGridLayoutConfig(
- gridColumnSize = 5.dp,
- gridGutter = 3.dp,
- gridHeight = 17.dp,
- gridColumnsPerCard = 2,
- )
- .cardWidth
- )
- .isEqualTo(13.dp)
-
- Truth.assertThat(
- CommunalGridLayoutConfig(
- gridColumnSize = 5.dp,
- gridGutter = 3.dp,
- gridHeight = 17.dp,
- gridColumnsPerCard = 3,
- )
- .cardWidth
- )
- .isEqualTo(21.dp)
- }
-
- @Test
- fun cardHeight() {
- val config =
- CommunalGridLayoutConfig(
- gridColumnSize = 5.dp,
- gridGutter = 2.dp,
- gridHeight = 10.dp,
- gridColumnsPerCard = 3,
- )
-
- Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.FULL)).isEqualTo(10.dp)
- Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.HALF)).isEqualTo(4.dp)
- Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.THIRD)).isEqualTo(2.dp)
- }
-}
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index 16c2437..796abf4b 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -31,7 +31,6 @@
],
static_libs: [
- "CommunalLayoutLib",
"SystemUI-core",
"PlatformComposeCore",
"PlatformComposeSceneTransitionLayout",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 7eb7dac..57af2ba 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -19,7 +19,6 @@
import android.app.AlertDialog
import android.app.Dialog
import android.content.DialogInterface
-import android.content.res.Configuration
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.snap
@@ -54,8 +53,6 @@
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
-import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
-import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -68,7 +65,6 @@
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.style.TextOverflow
@@ -83,8 +79,8 @@
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.transitions
import com.android.compose.modifiers.thenIf
-import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
@@ -93,8 +89,8 @@
import com.android.systemui.common.shared.model.Text.Companion.loadText
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.fold.ui.composable.FoldPosture
import com.android.systemui.fold.ui.composable.foldPosture
+import com.android.systemui.fold.ui.helper.FoldPosture
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
@@ -149,10 +145,7 @@
) {
val backgroundColor = MaterialTheme.colorScheme.surface
val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState()
- val layout =
- calculateLayout(
- isSideBySideSupported = isSideBySideSupported,
- )
+ val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported)
Box(modifier) {
Canvas(Modifier.element(Bouncer.Elements.Background).fillMaxSize()) {
@@ -163,27 +156,27 @@
val isFullScreenUserSwitcherEnabled = viewModel.isUserSwitcherVisible
when (layout) {
- Layout.STANDARD ->
+ BouncerSceneLayout.STANDARD ->
StandardLayout(
viewModel = viewModel,
dialogFactory = dialogFactory,
modifier = childModifier,
)
- Layout.SIDE_BY_SIDE ->
+ BouncerSceneLayout.SIDE_BY_SIDE ->
SideBySideLayout(
viewModel = viewModel,
dialogFactory = dialogFactory,
isUserSwitcherVisible = isFullScreenUserSwitcherEnabled,
modifier = childModifier,
)
- Layout.STACKED ->
+ BouncerSceneLayout.STACKED ->
StackedLayout(
viewModel = viewModel,
dialogFactory = dialogFactory,
isUserSwitcherVisible = isFullScreenUserSwitcherEnabled,
modifier = childModifier,
)
- Layout.SPLIT ->
+ BouncerSceneLayout.SPLIT ->
SplitLayout(
viewModel = viewModel,
dialogFactory = dialogFactory,
@@ -728,58 +721,10 @@
}
}
-@Composable
-private fun calculateLayout(
- isSideBySideSupported: Boolean,
-): Layout {
- val windowSizeClass = LocalWindowSizeClass.current
- val width = windowSizeClass.widthSizeClass
- val height = windowSizeClass.heightSizeClass
- val isLarge = width > WindowWidthSizeClass.Compact && height > WindowHeightSizeClass.Compact
- val isTall =
- when (height) {
- WindowHeightSizeClass.Expanded -> width < WindowWidthSizeClass.Expanded
- WindowHeightSizeClass.Medium -> width < WindowWidthSizeClass.Medium
- else -> false
- }
- val isSquare =
- when (width) {
- WindowWidthSizeClass.Compact -> height == WindowHeightSizeClass.Compact
- WindowWidthSizeClass.Medium -> height == WindowHeightSizeClass.Medium
- WindowWidthSizeClass.Expanded -> height == WindowHeightSizeClass.Expanded
- else -> false
- }
- val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
-
- return when {
- // Small and tall devices (i.e. phone/folded in portrait) or square device not in landscape
- // mode (unfolded with hinge along horizontal plane).
- (!isLarge && isTall) || (isSquare && !isLandscape) -> Layout.STANDARD
- // Small and wide devices (i.e. phone/folded in landscape).
- !isLarge -> Layout.SPLIT
- // Large and tall devices (i.e. tablet in portrait).
- isTall -> Layout.STACKED
- // Large and wide/square devices (i.e. tablet in landscape, unfolded).
- else -> if (isSideBySideSupported) Layout.SIDE_BY_SIDE else Layout.STANDARD
- }
-}
-
interface BouncerSceneDialogFactory {
operator fun invoke(): AlertDialog
}
-/** Enumerates all known adaptive layout configurations. */
-private enum class Layout {
- /** The default UI with the bouncer laid out normally. */
- STANDARD,
- /** The bouncer is displayed vertically stacked with the user switcher. */
- STACKED,
- /** The bouncer is displayed side-by-side with the user switcher or an empty space. */
- SIDE_BY_SIDE,
- /** The bouncer is split in two with both sides shown side-by-side. */
- SPLIT,
-}
-
/** Enumerates all supported user-input area visibilities. */
private enum class UserInputAreaVisibility {
/**
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt
new file mode 100644
index 0000000..08b7559
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.bouncer.ui.composable
+
+import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
+import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
+import androidx.compose.runtime.Composable
+import com.android.compose.windowsizeclass.LocalWindowSizeClass
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
+import com.android.systemui.bouncer.ui.helper.SizeClass
+import com.android.systemui.bouncer.ui.helper.calculateLayoutInternal
+
+/**
+ * Returns the [BouncerSceneLayout] that should be used by the bouncer scene. If
+ * [isSideBySideSupported] is `false`, then [BouncerSceneLayout.SIDE_BY_SIDE] is replaced by
+ * [BouncerSceneLayout.STANDARD].
+ */
+@Composable
+fun calculateLayout(
+ isSideBySideSupported: Boolean,
+): BouncerSceneLayout {
+ val windowSizeClass = LocalWindowSizeClass.current
+
+ return calculateLayoutInternal(
+ width = windowSizeClass.widthSizeClass.toEnum(),
+ height = windowSizeClass.heightSizeClass.toEnum(),
+ isSideBySideSupported = isSideBySideSupported,
+ )
+}
+
+private fun WindowWidthSizeClass.toEnum(): SizeClass {
+ return when (this) {
+ WindowWidthSizeClass.Compact -> SizeClass.COMPACT
+ WindowWidthSizeClass.Medium -> SizeClass.MEDIUM
+ WindowWidthSizeClass.Expanded -> SizeClass.EXPANDED
+ else -> error("Unsupported WindowWidthSizeClass \"$this\"")
+ }
+}
+
+private fun WindowHeightSizeClass.toEnum(): SizeClass {
+ return when (this) {
+ WindowHeightSizeClass.Compact -> SizeClass.COMPACT
+ WindowHeightSizeClass.Medium -> SizeClass.MEDIUM
+ WindowHeightSizeClass.Expanded -> SizeClass.EXPANDED
+ else -> error("Unsupported WindowHeightSizeClass \"$this\"")
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index ce84c19..2c4dc80 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -65,10 +65,13 @@
viewModel.currentScene
.transform<CommunalSceneKey, SceneKey> { value -> value.toTransitionSceneKey() }
.collectAsState(TransitionSceneKey.Blank)
+ // Don't show hub mode UI if keyguard is present. This is important since we're in the shade,
+ // which can be opened from many locations.
+ val isKeyguardShowing by viewModel.isKeyguardVisible.collectAsState(initial = false)
// Failsafe to hide the whole SceneTransitionLayout in case of bugginess.
var showSceneTransitionLayout by remember { mutableStateOf(true) }
- if (!showSceneTransitionLayout) {
+ if (!showSceneTransitionLayout || !isKeyguardShowing) {
return
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt
index 1c993cf..e77ade9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt
@@ -23,19 +23,9 @@
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
-import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowInfoTracker
-
-sealed interface FoldPosture {
- /** A foldable device that's fully closed/folded or a device that doesn't support folding. */
- data object Folded : FoldPosture
- /** A foldable that's halfway open with the hinge held vertically. */
- data object Book : FoldPosture
- /** A foldable that's halfway open with the hinge held horizontally. */
- data object Tabletop : FoldPosture
- /** A foldable that's fully unfolded / flat. */
- data object FullyUnfolded : FoldPosture
-}
+import com.android.systemui.fold.ui.helper.FoldPosture
+import com.android.systemui.fold.ui.helper.foldPostureInternal
/** Returns the [FoldPosture] of the device currently. */
@Composable
@@ -48,32 +38,6 @@
initialValue = FoldPosture.Folded,
key1 = layoutInfo,
) {
- value =
- layoutInfo
- ?.displayFeatures
- ?.firstNotNullOfOrNull { it as? FoldingFeature }
- .let { foldingFeature ->
- when (foldingFeature?.state) {
- null -> FoldPosture.Folded
- FoldingFeature.State.HALF_OPENED ->
- foldingFeature.orientation.toHalfwayPosture()
- FoldingFeature.State.FLAT ->
- if (foldingFeature.isSeparating) {
- // Dual screen device.
- foldingFeature.orientation.toHalfwayPosture()
- } else {
- FoldPosture.FullyUnfolded
- }
- else -> error("Unsupported state \"${foldingFeature.state}\"")
- }
- }
- }
-}
-
-private fun FoldingFeature.Orientation.toHalfwayPosture(): FoldPosture {
- return when (this) {
- FoldingFeature.Orientation.HORIZONTAL -> FoldPosture.Tabletop
- FoldingFeature.Orientation.VERTICAL -> FoldPosture.Book
- else -> error("Unsupported orientation \"$this\"")
+ value = foldPostureInternal(layoutInfo)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index 28a4801..7654683 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -25,9 +25,6 @@
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
@@ -36,7 +33,6 @@
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
@@ -76,9 +72,6 @@
.element(QuickSettings.Elements.Content)
.fillMaxWidth()
.defaultMinSize(minHeight = 300.dp)
- .clip(RoundedCornerShape(32.dp))
- .background(MaterialTheme.colorScheme.primary)
- .padding(1.dp),
) {
QuickSettingsContent(qsSceneAdapter = qsSceneAdapter, state)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 9dd7bfaf..871d9f9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -23,7 +23,7 @@
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
-import androidx.compose.foundation.clickable
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
@@ -31,6 +31,7 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
@@ -51,6 +52,7 @@
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
+import com.android.systemui.shade.ui.composable.Shade
import com.android.systemui.shade.ui.composable.ShadeHeader
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager
@@ -104,53 +106,59 @@
) {
// TODO(b/280887232): implement the real UI.
Box(modifier = modifier.fillMaxSize()) {
- val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
- val collapsedHeaderHeight =
- with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() }
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier =
- Modifier.fillMaxSize()
- .clickable(onClick = { viewModel.onContentClicked() })
- .padding(start = 16.dp, end = 16.dp, bottom = 48.dp)
- ) {
- when (LocalWindowSizeClass.current.widthSizeClass) {
- WindowWidthSizeClass.Compact ->
- AnimatedVisibility(
- visible = !isCustomizing,
- enter =
- expandVertically(
- animationSpec = tween(1000),
- initialHeight = { collapsedHeaderHeight },
- ) + fadeIn(tween(1000)),
- exit =
- shrinkVertically(
- animationSpec = tween(1000),
- targetHeight = { collapsedHeaderHeight },
- shrinkTowards = Alignment.Top,
- ) + fadeOut(tween(1000)),
- ) {
- ExpandedShadeHeader(
+ Box(modifier = Modifier.fillMaxSize()) {
+ val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
+ val collapsedHeaderHeight =
+ with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() }
+ Spacer(
+ modifier =
+ Modifier.element(Shade.Elements.ScrimBackground)
+ .fillMaxSize()
+ .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
+ )
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier =
+ Modifier.fillMaxSize().padding(start = 16.dp, end = 16.dp, bottom = 48.dp)
+ ) {
+ when (LocalWindowSizeClass.current.widthSizeClass) {
+ WindowWidthSizeClass.Compact ->
+ AnimatedVisibility(
+ visible = !isCustomizing,
+ enter =
+ expandVertically(
+ animationSpec = tween(1000),
+ initialHeight = { collapsedHeaderHeight },
+ ) + fadeIn(tween(1000)),
+ exit =
+ shrinkVertically(
+ animationSpec = tween(1000),
+ targetHeight = { collapsedHeaderHeight },
+ shrinkTowards = Alignment.Top,
+ ) + fadeOut(tween(1000)),
+ ) {
+ ExpandedShadeHeader(
+ viewModel = viewModel.shadeHeaderViewModel,
+ createTintedIconManager = createTintedIconManager,
+ createBatteryMeterViewController = createBatteryMeterViewController,
+ statusBarIconController = statusBarIconController,
+ )
+ }
+ else ->
+ CollapsedShadeHeader(
viewModel = viewModel.shadeHeaderViewModel,
createTintedIconManager = createTintedIconManager,
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
)
- }
- else ->
- CollapsedShadeHeader(
- viewModel = viewModel.shadeHeaderViewModel,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController = createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
- )
+ }
+ Spacer(modifier = Modifier.height(16.dp))
+ QuickSettings(
+ modifier = Modifier.fillMaxHeight(),
+ viewModel.qsSceneAdapter,
+ QSSceneAdapter.State.QS
+ )
}
- Spacer(modifier = Modifier.height(16.dp))
- QuickSettings(
- modifier = Modifier.fillMaxHeight(),
- viewModel.qsSceneAdapter,
- QSSceneAdapter.State.QS
- )
}
HeadsUpNotificationSpace(
viewModel = viewModel.notifications,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt
new file mode 100644
index 0000000..61b2057
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.fold.ui.helper
+
+import android.graphics.Rect
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.window.layout.FoldingFeature
+import androidx.window.layout.WindowLayoutInfo
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FoldPostureTest : SysuiTestCase() {
+
+ @Test
+ fun foldPosture_whenNull_returnsFolded() {
+ assertThat(foldPostureInternal(null)).isEqualTo(FoldPosture.Folded)
+ }
+
+ @Test
+ fun foldPosture_whenHalfOpenHorizontally_returnsTabletop() {
+ assertThat(
+ foldPostureInternal(
+ createWindowLayoutInfo(
+ state = FoldingFeature.State.HALF_OPENED,
+ orientation = FoldingFeature.Orientation.HORIZONTAL,
+ )
+ )
+ )
+ .isEqualTo(FoldPosture.Tabletop)
+ }
+
+ @Test
+ fun foldPosture_whenHalfOpenVertically_returnsBook() {
+ assertThat(
+ foldPostureInternal(
+ createWindowLayoutInfo(
+ state = FoldingFeature.State.HALF_OPENED,
+ orientation = FoldingFeature.Orientation.VERTICAL,
+ )
+ )
+ )
+ .isEqualTo(FoldPosture.Book)
+ }
+
+ @Test
+ fun foldPosture_whenFlatAndNotSeparating_returnsFullyUnfolded() {
+ assertThat(
+ foldPostureInternal(
+ createWindowLayoutInfo(
+ state = FoldingFeature.State.FLAT,
+ orientation = FoldingFeature.Orientation.HORIZONTAL,
+ isSeparating = false,
+ )
+ )
+ )
+ .isEqualTo(FoldPosture.FullyUnfolded)
+ }
+
+ @Test
+ fun foldPosture_whenFlatAndSeparatingHorizontally_returnsTabletop() {
+ assertThat(
+ foldPostureInternal(
+ createWindowLayoutInfo(
+ state = FoldingFeature.State.FLAT,
+ isSeparating = true,
+ orientation = FoldingFeature.Orientation.HORIZONTAL,
+ )
+ )
+ )
+ .isEqualTo(FoldPosture.Tabletop)
+ }
+
+ @Test
+ fun foldPosture_whenFlatAndSeparatingVertically_returnsBook() {
+ assertThat(
+ foldPostureInternal(
+ createWindowLayoutInfo(
+ state = FoldingFeature.State.FLAT,
+ isSeparating = true,
+ orientation = FoldingFeature.Orientation.VERTICAL,
+ )
+ )
+ )
+ .isEqualTo(FoldPosture.Book)
+ }
+
+ private fun createWindowLayoutInfo(
+ state: FoldingFeature.State,
+ orientation: FoldingFeature.Orientation = FoldingFeature.Orientation.VERTICAL,
+ isSeparating: Boolean = false,
+ occlusionType: FoldingFeature.OcclusionType = FoldingFeature.OcclusionType.NONE,
+ ): WindowLayoutInfo {
+ return WindowLayoutInfo(
+ listOf(
+ object : FoldingFeature {
+ override val bounds: Rect = Rect(0, 0, 100, 100)
+ override val isSeparating: Boolean = isSeparating
+ override val occlusionType: FoldingFeature.OcclusionType = occlusionType
+ override val orientation: FoldingFeature.Orientation = orientation
+ override val state: FoldingFeature.State = state
+ }
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml b/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml
index 952f056..cc99f5e 100644
--- a/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml
+++ b/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml
@@ -22,13 +22,15 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
- android:orientation="horizontal" >
+ android:orientation="horizontal"
+ android:theme="@style/Theme.SystemUI.QuickSettings.Header" >
<com.android.systemui.util.AutoMarqueeTextView
android:id="@+id/mobile_carrier_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
+ android:textAppearance="@style/TextAppearance.QS.Status.Carriers"
android:layout_marginEnd="@dimen/qs_carrier_margin_width"
android:visibility="gone"
android:textDirection="locale"
diff --git a/packages/SystemUI/res/drawable/notification_material_bg.xml b/packages/SystemUI/res/drawable/notification_material_bg.xml
index 355e75d..9c08f5e 100644
--- a/packages/SystemUI/res/drawable/notification_material_bg.xml
+++ b/packages/SystemUI/res/drawable/notification_material_bg.xml
@@ -18,7 +18,7 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:color="?android:attr/colorControlHighlight">
- <item>
+ <item android:id="@+id/notification_background_color_layer">
<shape>
<solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" />
</shape>
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index b8f4c0f..7ab44e7 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -53,6 +53,8 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
+ android:focusable="true"
+ android:importantForAccessibility="no"
android:tint="?attr/shadeActive"
android:visibility="gone" />
diff --git a/packages/SystemUI/res/layout/udfps_touch_overlay.xml b/packages/SystemUI/res/layout/udfps_touch_overlay.xml
new file mode 100644
index 0000000..ea92776
--- /dev/null
+++ b/packages/SystemUI/res/layout/udfps_touch_overlay.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/udfps_touch_overlay"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:contentDescription="@string/accessibility_fingerprint_label">
+</com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 74d435d..1838795 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -230,7 +230,6 @@
<!-- Communal mode -->
<item type="id" name="communal_hub" />
- <item type="id" name="communal_widget_wrapper" />
<!-- Values assigned to the views in Biometrics Prompt -->
<item type="id" name="pin_pad"/>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 1fa55f5..54cb501 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -384,6 +384,11 @@
}
}
+ @Nullable
+ View getAodNotifIconContainer() {
+ return mAodIconContainer;
+ }
+
@Override
protected void onViewDetached() {
mClockRegistry.unregisterClockChangeListener(mClockChangedListener);
@@ -639,6 +644,7 @@
}
} else {
mNotificationIconAreaController.setupAodIcons(nic);
+ mAodIconContainer = nic;
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 87d937b..4fbf077 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -84,6 +84,7 @@
Dumpable {
private static final boolean DEBUG = KeyguardConstants.DEBUG;
@VisibleForTesting static final String TAG = "KeyguardStatusViewController";
+ private static final long STATUS_AREA_HEIGHT_ANIMATION_MILLIS = 133;
/**
* Duration to use for the animator when the keyguard status view alignment changes, and a
@@ -104,6 +105,10 @@
private final KeyguardInteractor mKeyguardInteractor;
private final PowerInteractor mPowerInteractor;
private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ private final DozeParameters mDozeParameters;
+
+ private View mStatusArea = null;
+ private ValueAnimator mStatusAreaHeightAnimator = null;
private Boolean mSplitShadeEnabled = false;
private Boolean mStatusViewCentered = true;
@@ -123,6 +128,46 @@
}
};
+ private final View.OnLayoutChangeListener mStatusAreaLayoutChangeListener =
+ new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v,
+ int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom
+ ) {
+ if (!mDozeParameters.getAlwaysOn()) {
+ return;
+ }
+
+ int oldHeight = oldBottom - oldTop;
+ int diff = v.getHeight() - oldHeight;
+ if (diff == 0) {
+ return;
+ }
+
+ int startValue = -1 * diff;
+ long duration = STATUS_AREA_HEIGHT_ANIMATION_MILLIS;
+ if (mStatusAreaHeightAnimator != null
+ && mStatusAreaHeightAnimator.isRunning()) {
+ duration += mStatusAreaHeightAnimator.getDuration()
+ - mStatusAreaHeightAnimator.getCurrentPlayTime();
+ startValue += (int) mStatusAreaHeightAnimator.getAnimatedValue();
+ mStatusAreaHeightAnimator.cancel();
+ mStatusAreaHeightAnimator = null;
+ }
+
+ mStatusAreaHeightAnimator = ValueAnimator.ofInt(startValue, 0);
+ mStatusAreaHeightAnimator.setDuration(duration);
+ final View nic = mKeyguardClockSwitchController.getAodNotifIconContainer();
+ if (nic != null) {
+ mStatusAreaHeightAnimator.addUpdateListener(anim -> {
+ nic.setTranslationY((int) anim.getAnimatedValue());
+ });
+ }
+ mStatusAreaHeightAnimator.start();
+ }
+ };
+
@Inject
public KeyguardStatusViewController(
KeyguardStatusView keyguardStatusView,
@@ -144,6 +189,7 @@
mKeyguardClockSwitchController = keyguardClockSwitchController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mConfigurationController = configurationController;
+ mDozeParameters = dozeParameters;
mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
dozeParameters, screenOffAnimationController, /* animateYPos= */ true,
logger.getBuffer());
@@ -218,12 +264,15 @@
@Override
protected void onViewAttached() {
+ mStatusArea = mView.findViewById(R.id.keyguard_status_area);
+ mStatusArea.addOnLayoutChangeListener(mStatusAreaLayoutChangeListener);
mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
mConfigurationController.addCallback(mConfigurationListener);
}
@Override
protected void onViewDetached() {
+ mStatusArea.removeOnLayoutChangeListener(mStatusAreaLayoutChangeListener);
mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
mConfigurationController.removeCallback(mConfigurationListener);
}
@@ -293,9 +342,15 @@
/**
* Get the height of the keyguard status view without the notification icon area, as that's
* only visible on AOD.
+ *
+ * We internally animate height changes to the status area to prevent discontinuities in the
+ * doze animation introduced by the height suddenly changing due to smartpace.
*/
public int getLockscreenHeight() {
- return mView.getHeight() - mKeyguardClockSwitchController.getNotificationIconAreaHeight();
+ int heightAnimValue = mStatusAreaHeightAnimator == null ? 0 :
+ (int) mStatusAreaHeightAnimator.getAnimatedValue();
+ return mView.getHeight() + heightAnimValue
+ - mKeyguardClockSwitchController.getNotificationIconAreaHeight();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 57e252d..8fe42b5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -100,7 +100,6 @@
import javax.inject.Provider;
import kotlin.Unit;
-
import kotlinx.coroutines.CoroutineScope;
/**
@@ -1099,6 +1098,7 @@
// TODO(b/141025588): Create separate methods for handling hard and soft errors.
final boolean isSoftError = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED
|| error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT
+ || error == BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL
|| isCameraPrivacyEnabled);
if (mCurrentDialog != null) {
if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index b064391..e15538b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -52,6 +52,7 @@
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
@@ -78,9 +79,9 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -150,7 +151,6 @@
@NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
@NonNull private final Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
@NonNull private final VibratorHelper mVibrator;
- @NonNull private final FeatureFlags mFeatureFlags;
@NonNull private final FalsingManager mFalsingManager;
@NonNull private final PowerManager mPowerManager;
@NonNull private final AccessibilityManager mAccessibilityManager;
@@ -281,7 +281,6 @@
fromUdfpsView
),
mActivityLaunchAnimator,
- mFeatureFlags,
mPrimaryBouncerInteractor,
mAlternateBouncerInteractor,
mUdfpsKeyguardAccessibilityDelegate,
@@ -318,10 +317,8 @@
return;
}
mAcquiredReceived = true;
- final UdfpsView view = mOverlay.getOverlayView();
- if (view != null && isOptical()) {
- unconfigureDisplay(view);
- }
+ final View view = mOverlay.getTouchOverlay();
+ unconfigureDisplay(view);
tryAodSendFingerUp();
});
}
@@ -339,7 +336,9 @@
if (mOverlay == null || mOverlay.isHiding()) {
return;
}
- mOverlay.getOverlayView().setDebugMessage(message);
+ if (!DeviceEntryUdfpsRefactor.isEnabled()) {
+ ((UdfpsView) mOverlay.getTouchOverlay()).setDebugMessage(message);
+ }
});
}
@@ -506,6 +505,7 @@
if ((mLockscreenShadeTransitionController.getQSDragProgress() != 0f
&& !mAlternateBouncerInteractor.isVisibleState())
|| mPrimaryBouncerInteractor.isInTransit()) {
+ Log.w(TAG, "ignoring touch due to qsDragProcess or primaryBouncerInteractor");
return false;
}
if (event.getAction() == MotionEvent.ACTION_DOWN
@@ -563,7 +563,7 @@
}
mAttemptedToDismissKeyguard = false;
onFingerUp(requestId,
- mOverlay.getOverlayView(),
+ mOverlay.getTouchOverlay(),
data.getPointerId(),
data.getX(),
data.getY(),
@@ -597,7 +597,7 @@
if (shouldPilfer && !mPointerPilfered
&& getBiometricSessionType() != SESSION_BIOMETRIC_PROMPT) {
mInputManager.pilferPointers(
- mOverlay.getOverlayView().getViewRootImpl().getInputToken());
+ mOverlay.getTouchOverlay().getViewRootImpl().getInputToken());
mPointerPilfered = true;
}
@@ -605,9 +605,15 @@
}
private boolean shouldTryToDismissKeyguard() {
- return mOverlay != null
- && mOverlay.getAnimationViewController()
- instanceof UdfpsKeyguardViewControllerAdapter
+ boolean onKeyguard = false;
+ if (DeviceEntryUdfpsRefactor.isEnabled()) {
+ onKeyguard = mKeyguardStateController.isShowing();
+ } else {
+ onKeyguard = mOverlay != null
+ && mOverlay.getAnimationViewController()
+ instanceof UdfpsKeyguardViewControllerAdapter;
+ }
+ return onKeyguard
&& mKeyguardStateController.canDismissLockScreen()
&& !mAttemptedToDismissKeyguard;
}
@@ -623,7 +629,6 @@
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@NonNull DumpManager dumpManager,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
- @NonNull FeatureFlags featureFlags,
@NonNull FalsingManager falsingManager,
@NonNull PowerManager powerManager,
@NonNull AccessibilityManager accessibilityManager,
@@ -670,7 +675,6 @@
mDumpManager = dumpManager;
mDialogManager = dialogManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mFeatureFlags = featureFlags;
mFalsingManager = falsingManager;
mPowerManager = powerManager;
mAccessibilityManager = accessibilityManager;
@@ -737,9 +741,9 @@
@VisibleForTesting
public void playStartHaptic() {
if (mAccessibilityManager.isTouchExplorationEnabled()) {
- if (mOverlay != null && mOverlay.getOverlayView() != null) {
+ if (mOverlay != null && mOverlay.getTouchOverlay() != null) {
mVibrator.performHapticFeedback(
- mOverlay.getOverlayView(),
+ mOverlay.getTouchOverlay(),
HapticFeedbackConstants.CONTEXT_CLICK
);
} else {
@@ -751,10 +755,11 @@
@Override
public void dozeTimeTick() {
- if (mOverlay != null) {
- final UdfpsView view = mOverlay.getOverlayView();
+ if (mOverlay != null && mOverlay.getTouchOverlay() instanceof UdfpsView) {
+ DeviceEntryUdfpsRefactor.assertInLegacyMode();
+ final View view = mOverlay.getTouchOverlay();
if (view != null) {
- view.dozeTimeTick();
+ ((UdfpsView) view).dozeTimeTick();
}
}
}
@@ -797,7 +802,7 @@
if (mOverlay != null) {
// Reset the controller back to its starting state.
- final UdfpsView oldView = mOverlay.getOverlayView();
+ final View oldView = mOverlay.getTouchOverlay();
if (oldView != null) {
onFingerUp(mOverlay.getRequestId(), oldView);
}
@@ -813,9 +818,21 @@
}
- private void unconfigureDisplay(@NonNull UdfpsView view) {
- if (view.isDisplayConfigured()) {
- view.unconfigureDisplay();
+ private void unconfigureDisplay(View view) {
+ if (!isOptical()) {
+ return;
+ }
+ if (DeviceEntryUdfpsRefactor.isEnabled()) {
+ if (mUdfpsDisplayMode != null) {
+ mUdfpsDisplayMode.disable(null); // beverlt
+ }
+ } else {
+ if (view != null) {
+ UdfpsView udfpsView = (UdfpsView) view;
+ if (udfpsView.isDisplayConfigured()) {
+ udfpsView.unconfigureDisplay();
+ }
+ }
}
}
@@ -837,10 +854,10 @@
}
mKeyguardViewManager.showPrimaryBouncer(true);
- // play the same haptic as the LockIconViewController longpress
- if (mOverlay != null && mOverlay.getOverlayView() != null) {
+ // play the same haptic as the DeviceEntryIcon longpress
+ if (mOverlay != null && mOverlay.getTouchOverlay() != null) {
mVibrator.performHapticFeedback(
- mOverlay.getOverlayView(),
+ mOverlay.getTouchOverlay(),
UdfpsController.LONG_PRESS
);
} else {
@@ -909,8 +926,8 @@
return;
}
cancelAodSendFingerUpAction();
- if (mOverlay != null && mOverlay.getOverlayView() != null) {
- onFingerUp(mOverlay.getRequestId(), mOverlay.getOverlayView());
+ if (mOverlay != null && mOverlay.getTouchOverlay() != null) {
+ onFingerUp(mOverlay.getRequestId(), mOverlay.getTouchOverlay());
}
}
@@ -1004,12 +1021,17 @@
mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, pointerId, x, y,
minor, major, orientation, time, gestureStart, isAod);
Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0);
- final UdfpsView view = mOverlay.getOverlayView();
+
+ final View view = mOverlay.getTouchOverlay();
if (view != null && isOptical()) {
if (mIgnoreRefreshRate) {
dispatchOnUiReady(requestId);
} else {
- view.configureDisplay(() -> dispatchOnUiReady(requestId));
+ if (DeviceEntryUdfpsRefactor.isEnabled()) {
+ mUdfpsDisplayMode.enable(() -> dispatchOnUiReady(requestId));
+ } else {
+ ((UdfpsView) view).configureDisplay(() -> dispatchOnUiReady(requestId));
+ }
}
}
@@ -1018,7 +1040,7 @@
}
}
- private void onFingerUp(long requestId, @NonNull UdfpsView view) {
+ private void onFingerUp(long requestId, @NonNull View view) {
onFingerUp(
requestId,
view,
@@ -1035,7 +1057,7 @@
private void onFingerUp(
long requestId,
- @NonNull UdfpsView view,
+ View view,
int pointerId,
float x,
float y,
@@ -1056,9 +1078,7 @@
}
}
mOnFingerDown = false;
- if (isOptical()) {
- unconfigureDisplay(view);
- }
+ unconfigureDisplay(view);
cancelAodSendFingerUpAction();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index a5bd89a..2d54f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -46,11 +46,11 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
+import com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -96,7 +96,6 @@
private val controllerCallback: IUdfpsOverlayControllerCallback,
private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
private val activityLaunchAnimator: ActivityLaunchAnimator,
- private val featureFlags: FeatureFlags,
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
@@ -104,9 +103,22 @@
private val transitionInteractor: KeyguardTransitionInteractor,
private val selectedUserInteractor: SelectedUserInteractor,
) {
- /** The view, when [isShowing], or null. */
- var overlayView: UdfpsView? = null
+ private var overlayViewLegacy: UdfpsView? = null
private set
+ private var overlayTouchView: UdfpsTouchOverlay? = null
+
+ /**
+ * Get the current UDFPS overlay touch view which is a different View depending on whether
+ * the DeviceEntryUdfpsRefactor flag is enabled or not.
+ * @return The view, when [isShowing], else null
+ */
+ fun getTouchOverlay(): View? {
+ return if (DeviceEntryUdfpsRefactor.isEnabled) {
+ overlayTouchView
+ } else {
+ overlayViewLegacy
+ }
+ }
private var overlayParams: UdfpsOverlayParams = UdfpsOverlayParams()
private var sensorBounds: Rect = Rect()
@@ -132,15 +144,15 @@
/** If the overlay is currently showing. */
val isShowing: Boolean
- get() = overlayView != null
+ get() = getTouchOverlay() != null
/** Opposite of [isShowing]. */
val isHiding: Boolean
- get() = overlayView == null
+ get() = getTouchOverlay() == null
/** The animation controller if the overlay [isShowing]. */
val animationViewController: UdfpsAnimationViewController<*>?
- get() = overlayView?.animationViewController
+ get() = overlayViewLegacy?.animationViewController
private var touchExplorationEnabled = false
@@ -158,28 +170,48 @@
/** Show the overlay or return false and do nothing if it is already showing. */
@SuppressLint("ClickableViewAccessibility")
fun show(controller: UdfpsController, params: UdfpsOverlayParams): Boolean {
- if (overlayView == null) {
+ if (getTouchOverlay() == null) {
overlayParams = params
sensorBounds = Rect(params.sensorBounds)
try {
- overlayView = (inflater.inflate(
- R.layout.udfps_view, null, false
- ) as UdfpsView).apply {
- overlayParams = params
- setUdfpsDisplayModeProvider(udfpsDisplayModeProvider)
- val animation = inflateUdfpsAnimation(this, controller)
- if (animation != null) {
- animation.init()
- animationViewController = animation
+ if (DeviceEntryUdfpsRefactor.isEnabled) {
+ overlayTouchView = (inflater.inflate(
+ R.layout.udfps_touch_overlay, null, false
+ ) as UdfpsTouchOverlay).apply {
+ // This view overlaps the sensor area
+ // prevent it from being selectable during a11y
+ if (requestReason.isImportantForAccessibility()) {
+ importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+ }
+ windowManager.addView(this, coreLayoutParams.updateDimensions(null))
}
- // This view overlaps the sensor area
- // prevent it from being selectable during a11y
- if (requestReason.isImportantForAccessibility()) {
- importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
- }
+ // TODO (b/305234447): Bind view model to UdfpsTouchOverlay here to control
+ // the visibility (sometimes, even if UDFPS is running, the UDFPS UI can be
+ // obscured and we don't want to accept touches. ie: for enrollment don't accept
+ // touches when the shade is expanded and for keyguard: don't accept touches
+ // depending on the keyguard & shade state
+ } else {
+ overlayViewLegacy = (inflater.inflate(
+ R.layout.udfps_view, null, false
+ ) as UdfpsView).apply {
+ overlayParams = params
+ setUdfpsDisplayModeProvider(udfpsDisplayModeProvider)
+ val animation = inflateUdfpsAnimation(this, controller)
+ if (animation != null) {
+ animation.init()
+ animationViewController = animation
+ }
+ // This view overlaps the sensor area
+ // prevent it from being selectable during a11y
+ if (requestReason.isImportantForAccessibility()) {
+ importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+ }
- windowManager.addView(this, coreLayoutParams.updateDimensions(animation))
- sensorRect = sensorBounds
+ windowManager.addView(this, coreLayoutParams.updateDimensions(animation))
+ sensorRect = sensorBounds
+ }
+ }
+ getTouchOverlay()?.apply {
touchExplorationEnabled = accessibilityManager.isTouchExplorationEnabled
overlayTouchListener = TouchExplorationStateChangeListener {
if (accessibilityManager.isTouchExplorationEnabled) {
@@ -193,7 +225,7 @@
}
}
accessibilityManager.addTouchExplorationStateChangeListener(
- overlayTouchListener!!
+ overlayTouchListener!!
)
overlayTouchListener?.onTouchExplorationStateChanged(true)
}
@@ -211,6 +243,8 @@
view: UdfpsView,
controller: UdfpsController
): UdfpsAnimationViewController<*>? {
+ DeviceEntryUdfpsRefactor.assertInLegacyMode()
+
val isEnrollment = when (requestReason) {
REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> true
else -> false
@@ -237,39 +271,27 @@
)
}
REASON_AUTH_KEYGUARD -> {
- if (DeviceEntryUdfpsRefactor.isEnabled) {
- // note: empty controller, currently shows no visual affordance
- // instead SysUI will show the fingerprint icon in its DeviceEntryIconView
- UdfpsBpViewController(
- view.addUdfpsView(R.layout.udfps_bp_view),
- statusBarStateController,
- primaryBouncerInteractor,
- dialogManager,
- dumpManager
- )
- } else {
- UdfpsKeyguardViewControllerLegacy(
- view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) {
- updateSensorLocation(sensorBounds)
- },
- statusBarStateController,
- statusBarKeyguardViewManager,
- keyguardUpdateMonitor,
- dumpManager,
- transitionController,
- configurationController,
- keyguardStateController,
- unlockedScreenOffAnimationController,
- dialogManager,
- controller,
- activityLaunchAnimator,
- primaryBouncerInteractor,
- alternateBouncerInteractor,
- udfpsKeyguardAccessibilityDelegate,
- selectedUserInteractor,
- transitionInteractor,
- )
- }
+ UdfpsKeyguardViewControllerLegacy(
+ view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) {
+ updateSensorLocation(sensorBounds)
+ },
+ statusBarStateController,
+ statusBarKeyguardViewManager,
+ keyguardUpdateMonitor,
+ dumpManager,
+ transitionController,
+ configurationController,
+ keyguardStateController,
+ unlockedScreenOffAnimationController,
+ dialogManager,
+ controller,
+ activityLaunchAnimator,
+ primaryBouncerInteractor,
+ alternateBouncerInteractor,
+ udfpsKeyguardAccessibilityDelegate,
+ selectedUserInteractor,
+ transitionInteractor,
+ )
}
REASON_AUTH_BP -> {
// note: empty controller, currently shows no visual affordance
@@ -302,19 +324,26 @@
fun hide(): Boolean {
val wasShowing = isShowing
- overlayView?.apply {
+ overlayViewLegacy?.apply {
if (isDisplayConfigured) {
unconfigureDisplay()
}
+ animationViewController = null
+ }
+ if (DeviceEntryUdfpsRefactor.isEnabled) {
+ udfpsDisplayModeProvider.disable(null)
+ }
+ getTouchOverlay()?.apply {
windowManager.removeView(this)
setOnTouchListener(null)
setOnHoverListener(null)
- animationViewController = null
overlayTouchListener?.let {
accessibilityManager.removeTouchExplorationStateChangeListener(it)
}
}
- overlayView = null
+
+ overlayViewLegacy = null
+ overlayTouchView = null
overlayTouchListener = null
return wasShowing
@@ -392,7 +421,14 @@
}
private fun shouldRotate(animation: UdfpsAnimationViewController<*>?): Boolean {
- if (animation !is UdfpsKeyguardViewControllerAdapter) {
+ val keyguardNotShowing =
+ if (DeviceEntryUdfpsRefactor.isEnabled) {
+ !keyguardStateController.isShowing
+ } else {
+ animation !is UdfpsKeyguardViewControllerAdapter
+ }
+
+ if (keyguardNotShowing) {
// always rotate view if we're not on the keyguard
return true
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/CommunalWidgetWrapper.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/view/UdfpsTouchOverlay.kt
similarity index 65%
rename from packages/SystemUI/src/com/android/systemui/communal/ui/view/CommunalWidgetWrapper.kt
rename to packages/SystemUI/src/com/android/systemui/biometrics/ui/view/UdfpsTouchOverlay.kt
index 039078e..2484c33 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/CommunalWidgetWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/view/UdfpsTouchOverlay.kt
@@ -12,20 +12,15 @@
* WITHOUT 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.communal.ui.view
+package com.android.systemui.biometrics.ui.view
import android.content.Context
import android.util.AttributeSet
-import android.widget.LinearLayout
-import com.android.systemui.res.R
+import android.widget.FrameLayout
-/** Wraps around a widget rendered in communal mode. */
-class CommunalWidgetWrapper(context: Context, attrs: AttributeSet? = null) :
- LinearLayout(context, attrs) {
- init {
- id = R.id.communal_widget_wrapper
- }
-}
+/**
+ * A translucent (not visible to the user) view that receives touches to send to FingerprintManager
+ * for fingerprint authentication.
+ */
+class UdfpsTouchOverlay(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt
new file mode 100644
index 0000000..5385442
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.bouncer.ui.helper
+
+import androidx.annotation.VisibleForTesting
+
+/** Enumerates all known adaptive layout configurations. */
+enum class BouncerSceneLayout {
+ /** The default UI with the bouncer laid out normally. */
+ STANDARD,
+ /** The bouncer is displayed vertically stacked with the user switcher. */
+ STACKED,
+ /** The bouncer is displayed side-by-side with the user switcher or an empty space. */
+ SIDE_BY_SIDE,
+ /** The bouncer is split in two with both sides shown side-by-side. */
+ SPLIT,
+}
+
+/** Enumerates the supported window size classes. */
+enum class SizeClass {
+ COMPACT,
+ MEDIUM,
+ EXPANDED,
+}
+
+/**
+ * Internal version of `calculateLayout` in the System UI Compose library, extracted here to allow
+ * for testing that's not dependent on Compose.
+ */
+@VisibleForTesting
+fun calculateLayoutInternal(
+ width: SizeClass,
+ height: SizeClass,
+ isSideBySideSupported: Boolean,
+): BouncerSceneLayout {
+ return when (height) {
+ SizeClass.COMPACT -> BouncerSceneLayout.SPLIT
+ SizeClass.MEDIUM ->
+ when (width) {
+ SizeClass.COMPACT -> BouncerSceneLayout.STANDARD
+ SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD
+ SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE
+ }
+ SizeClass.EXPANDED ->
+ when (width) {
+ SizeClass.COMPACT -> BouncerSceneLayout.STANDARD
+ SizeClass.MEDIUM -> BouncerSceneLayout.STACKED
+ SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE
+ }
+ }.takeIf { it != BouncerSceneLayout.SIDE_BY_SIDE || isSideBySideSupported }
+ ?: BouncerSceneLayout.STANDARD
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/CommonModule.kt b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
similarity index 71%
copy from packages/SystemUI/src/com/android/systemui/common/CommonModule.kt
copy to packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
index 5e6caf0..27c9b3f 100644
--- a/packages/SystemUI/src/com/android/systemui/common/CommonModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
@@ -11,20 +11,17 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
-package com.android.systemui.common
-import com.android.systemui.common.domain.interactor.ConfigurationInteractor
-import com.android.systemui.common.domain.interactor.ConfigurationInteractorImpl
+package com.android.systemui.common.data
+
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
import dagger.Binds
import dagger.Module
@Module
-abstract class CommonModule {
+abstract class CommonDataLayerModule {
@Binds abstract fun bindRepository(impl: ConfigurationRepositoryImpl): ConfigurationRepository
-
- @Binds abstract fun bindInteractor(impl: ConfigurationInteractorImpl): ConfigurationInteractor
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/CommonModule.kt b/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt
similarity index 71%
rename from packages/SystemUI/src/com/android/systemui/common/CommonModule.kt
rename to packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt
index 5e6caf0..7be2eaf 100644
--- a/packages/SystemUI/src/com/android/systemui/common/CommonModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt
@@ -11,20 +11,17 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
-package com.android.systemui.common
+
+package com.android.systemui.common.domain
import com.android.systemui.common.domain.interactor.ConfigurationInteractor
import com.android.systemui.common.domain.interactor.ConfigurationInteractorImpl
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
-import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
import dagger.Binds
import dagger.Module
@Module
-abstract class CommonModule {
- @Binds abstract fun bindRepository(impl: ConfigurationRepositoryImpl): ConfigurationRepository
-
+abstract class CommonDomainLayerModule {
@Binds abstract fun bindInteractor(impl: ConfigurationInteractorImpl): ConfigurationInteractor
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
new file mode 100644
index 0000000..8d04e3d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.common.ui.view
+
+import android.view.View
+
+/**
+ * Set this view's [View#importantForAccessibility] to [View#IMPORTANT_FOR_ACCESSIBILITY_YES] or
+ * [View#IMPORTANT_FOR_ACCESSIBILITY_NO] based on [value].
+ */
+fun View.setImportantForAccessibilityYesNo(value: Boolean) {
+ importantForAccessibility =
+ if (value) View.IMPORTANT_FOR_ACCESSIBILITY_YES else View.IMPORTANT_FOR_ACCESSIBILITY_NO
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index fd7f641..e630fd4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -27,6 +27,7 @@
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -46,6 +47,7 @@
private val widgetRepository: CommunalWidgetRepository,
mediaRepository: CommunalMediaRepository,
smartspaceRepository: SmartspaceRepository,
+ keyguardInteractor: KeyguardInteractor,
private val appWidgetHost: AppWidgetHost,
private val editWidgetsActivityStarter: EditWidgetsActivityStarter
) {
@@ -67,6 +69,8 @@
val isCommunalShowing: Flow<Boolean> =
communalRepository.desiredScene.map { it == CommunalSceneKey.Communal }
+ val isKeyguardVisible: Flow<Boolean> = keyguardInteractor.isKeyguardVisible
+
/** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */
fun onSceneChanged(newScene: CommunalSceneKey) {
communalRepository.setDesiredScene(newScene)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
deleted file mode 100644
index 0daf7b5..0000000
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.communal.ui.adapter
-
-import android.appwidget.AppWidgetHost
-import android.appwidget.AppWidgetManager
-import android.content.Context
-import android.util.SizeF
-import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
-import com.android.systemui.communal.ui.view.CommunalWidgetWrapper
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.Logger
-import com.android.systemui.log.dagger.CommunalLog
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-
-/** Transforms a [CommunalAppWidgetInfo] to a view that renders the widget. */
-class CommunalWidgetViewAdapter
-@Inject
-constructor(
- @Application private val context: Context,
- private val appWidgetManager: AppWidgetManager,
- private val appWidgetHost: AppWidgetHost,
- @CommunalLog logBuffer: LogBuffer,
-) {
- companion object {
- private const val TAG = "CommunalWidgetViewAdapter"
- }
-
- private val logger = Logger(logBuffer, TAG)
-
- fun adapt(providerInfoFlow: Flow<CommunalAppWidgetInfo?>): Flow<CommunalWidgetWrapper?> =
- providerInfoFlow.map {
- if (it == null) {
- return@map null
- }
-
- val appWidgetId = it.appWidgetId
- val providerInfo = it.providerInfo
-
- if (appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, providerInfo.provider)) {
- logger.d("Success binding app widget id: $appWidgetId")
- return@map CommunalWidgetWrapper(context).apply {
- addView(
- appWidgetHost.createView(context, appWidgetId, providerInfo).apply {
- // Set the widget to minimum width and height
- updateAppWidgetSize(
- appWidgetManager.getAppWidgetOptions(appWidgetId),
- listOf(
- SizeF(
- providerInfo.minResizeWidth.toFloat(),
- providerInfo.minResizeHeight.toFloat()
- )
- )
- )
- }
- )
- }
- } else {
- logger.w("Failed binding app widget id")
- return@map null
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index b4ab5fb..4d8e893 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -28,6 +28,8 @@
private val communalInteractor: CommunalInteractor,
val mediaHost: MediaHost,
) {
+ val isKeyguardVisible: Flow<Boolean> = communalInteractor.isKeyguardVisible
+
val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene
fun onSceneChanged(scene: CommunalSceneKey) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index eaf2d57..11bde6b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -39,7 +39,6 @@
tutorialInteractor: CommunalTutorialInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
) : BaseCommunalViewModel(communalInteractor, mediaHost) {
-
@OptIn(ExperimentalCoroutinesApi::class)
override val communalContent: Flow<List<CommunalContentModel>> =
tutorialInteractor.isTutorialAvailable.flatMapLatest { isTutorialMode ->
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 482832b..0405ca4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -42,7 +42,8 @@
import com.android.systemui.bouncer.ui.BouncerViewModule;
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
-import com.android.systemui.common.CommonModule;
+import com.android.systemui.common.data.CommonDataLayerModule;
+import com.android.systemui.common.domain.CommonDomainLayerModule;
import com.android.systemui.communal.dagger.CommunalModule;
import com.android.systemui.complication.dagger.ComplicationComponent;
import com.android.systemui.controls.dagger.ControlsModule;
@@ -176,7 +177,8 @@
ClipboardOverlayModule.class,
ClockRegistryModule.class,
CommunalModule.class,
- CommonModule.class,
+ CommonDataLayerModule.class,
+ CommonDomainLayerModule.class,
ConnectivityModule.class,
ControlsModule.class,
CoroutinesModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 7d54107..98fda3e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -464,12 +464,6 @@
val WALLPAPER_MULTI_CROP =
sysPropBooleanFlag("persist.wm.debug.wallpaper_multi_crop", default = false)
- // TODO(b/290220798): Tracking Bug
- @Keep
- @JvmField
- val ENABLE_PIP2_IMPLEMENTATION =
- sysPropBooleanFlag("persist.wm.debug.enable_pip2_implementation", default = false)
-
// 1200 - predictive back
@Keep
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/fold/ui/helper/FoldPosture.kt b/packages/SystemUI/src/com/android/systemui/fold/ui/helper/FoldPosture.kt
new file mode 100644
index 0000000..bc1cc4f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/fold/ui/helper/FoldPosture.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.fold.ui.helper
+
+import androidx.annotation.VisibleForTesting
+import androidx.window.layout.FoldingFeature
+import androidx.window.layout.WindowLayoutInfo
+
+sealed interface FoldPosture {
+ /** A foldable device that's fully closed/folded or a device that doesn't support folding. */
+ data object Folded : FoldPosture
+ /** A foldable that's halfway open with the hinge held vertically. */
+ data object Book : FoldPosture
+ /** A foldable that's halfway open with the hinge held horizontally. */
+ data object Tabletop : FoldPosture
+ /** A foldable that's fully unfolded / flat. */
+ data object FullyUnfolded : FoldPosture
+}
+
+/**
+ * Internal version of `foldPosture` in the System UI Compose library, extracted here to allow for
+ * testing that's not dependent on Compose.
+ */
+@VisibleForTesting
+fun foldPostureInternal(layoutInfo: WindowLayoutInfo?): FoldPosture {
+ return layoutInfo
+ ?.displayFeatures
+ ?.firstNotNullOfOrNull { it as? FoldingFeature }
+ .let { foldingFeature ->
+ when (foldingFeature?.state) {
+ null -> FoldPosture.Folded
+ FoldingFeature.State.HALF_OPENED -> foldingFeature.orientation.toHalfwayPosture()
+ FoldingFeature.State.FLAT ->
+ if (foldingFeature.isSeparating) {
+ // Dual screen device.
+ foldingFeature.orientation.toHalfwayPosture()
+ } else {
+ FoldPosture.FullyUnfolded
+ }
+ else -> error("Unsupported state \"${foldingFeature.state}\"")
+ }
+ }
+}
+
+private fun FoldingFeature.Orientation.toHalfwayPosture(): FoldPosture {
+ return when (this) {
+ FoldingFeature.Orientation.HORIZONTAL -> FoldPosture.Tabletop
+ FoldingFeature.Orientation.VERTICAL -> FoldPosture.Book
+ else -> error("Unsupported orientation \"$this\"")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index 99529a1..9a50d83 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -31,6 +31,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -49,12 +50,22 @@
transitionInteractor.startedKeyguardState.map { keyguardState ->
keyguardState == KeyguardState.AOD
}
+
+ private fun getColor(usingBackgroundProtection: Boolean): Int {
+ return if (usingBackgroundProtection) {
+ Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
+ } else {
+ Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
+ }
+ }
+
private val color: Flow<Int> =
- configurationRepository.onAnyConfigurationChange
- .map { Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) }
- .onStart {
- emit(Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary))
- }
+ deviceEntryIconViewModel.useBackgroundProtection.flatMapLatest { useBgProtection ->
+ configurationRepository.onAnyConfigurationChange
+ .map { getColor(useBgProtection) }
+ .onStart { emit(getColor(useBgProtection)) }
+ }
+
private val useAodIconVariant: Flow<Boolean> =
combine(isShowingAod, deviceEntryUdfpsInteractor.isUdfpsSupported) {
isTransitionToAod,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index f2db088..44232ff 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -19,12 +19,18 @@
import android.app.StatusBarManager
import android.os.UserHandle
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
import javax.inject.Inject
@SysUISingleton
-class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) {
+class MediaFlags
+@Inject
+constructor(
+ private val featureFlags: FeatureFlagsClassic,
+ private val sceneContainerFlags: SceneContainerFlags
+) {
/**
* Check whether media control actions should be based on PlaybackState instead of notification
*/
@@ -48,4 +54,8 @@
/** Check whether we allow remote media to generate resume controls */
fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME)
+
+ /** Check whether to use flexiglass layout */
+ fun isFlexiglassEnabled() =
+ sceneContainerFlags.isEnabled() && MediaInSceneContainerFlag.isEnabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
new file mode 100644
index 0000000..898298c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.util
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the media_in_scene_container flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object MediaInSceneContainerFlag {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
+
+ /** Is the flag enabled? */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.mediaInSceneContainer()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index 58c4f0d..ef72967 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -131,6 +131,9 @@
+ "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})"
+ "?)*";
+ // Not all JDKs support emoji patterns, including the one errorprone runs under, which
+ // makes it think that this is an invalid pattern.
+ @SuppressWarnings("InvalidPatternSyntax")
static final Pattern EMOJI_PATTERN = Pattern.compile(UNICODE_EMOJI_REGEX);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index fa18b35b..052c0da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -12,6 +12,7 @@
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.AttributeSet;
+import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -22,6 +23,7 @@
import android.widget.Scroller;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
@@ -43,6 +45,7 @@
private static final int NO_PAGE = -1;
private static final int REVEAL_SCROLL_DURATION_MILLIS = 750;
+ private static final int SINGLE_PAGE_SCROLL_DURATION_MILLIS = 300;
private static final float BOUNCE_ANIMATION_TENSION = 1.3f;
private static final long BOUNCE_ANIMATION_DURATION = 450L;
private static final int TILE_ANIMATION_STAGGER_DELAY = 85;
@@ -63,8 +66,9 @@
private PageListener mPageListener;
private boolean mListening;
- private Scroller mScroller;
+ @VisibleForTesting Scroller mScroller;
+ /* set of animations used to indicate which tiles were just revealed */
@Nullable
private AnimatorSet mBounceAnimatorSet;
private float mLastExpansion;
@@ -306,6 +310,38 @@
mPageIndicator = indicator;
mPageIndicator.setNumPages(mPages.size());
mPageIndicator.setLocation(mPageIndicatorPosition);
+ mPageIndicator.setOnKeyListener((view, keyCode, keyEvent) -> {
+ if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
+ // only scroll on ACTION_UP as we don't handle longpressing for now. Still we need
+ // to intercept even ACTION_DOWN otherwise keyboard focus will be moved before we
+ // have a chance to intercept ACTION_UP.
+ if (keyEvent.getAction() == KeyEvent.ACTION_UP && mScroller.isFinished()) {
+ scrollByX(getDeltaXForKeyboardScrolling(keyCode),
+ SINGLE_PAGE_SCROLL_DURATION_MILLIS);
+ }
+ return true;
+ }
+ return false;
+ });
+ }
+
+ private int getDeltaXForKeyboardScrolling(int keyCode) {
+ if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && getCurrentItem() != 0) {
+ return -getWidth();
+ } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
+ && getCurrentItem() != mPages.size() - 1) {
+ return getWidth();
+ }
+ return 0;
+ }
+
+ private void scrollByX(int x, int durationMillis) {
+ if (x != 0) {
+ mScroller.startScroll(/* startX= */ getScrollX(), /* startY= */ getScrollY(),
+ /* dx= */ x, /* dy= */ 0, /* duration= */ durationMillis);
+ // scroller just sets its state, we need to invalidate view to actually start scrolling
+ postInvalidateOnAnimation();
+ }
}
@Override
@@ -596,9 +632,7 @@
});
setOffscreenPageLimit(lastPageNumber); // Ensure the page to reveal has been inflated.
int dx = getWidth() * lastPageNumber;
- mScroller.startScroll(getScrollX(), getScrollY(), isLayoutRtl() ? -dx : dx, 0,
- REVEAL_SCROLL_DURATION_MILLIS);
- postInvalidateOnAnimation();
+ scrollByX(isLayoutRtl() ? -dx : dx, REVEAL_SCROLL_DURATION_MILLIS);
}
private boolean shouldNotRunAnimation(Set<String> tilesToReveal) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 346d5c3..e5e1e84 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -17,7 +17,6 @@
package com.android.systemui.qs.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
@@ -33,14 +32,10 @@
class QuickSettingsSceneViewModel
@Inject
constructor(
- private val deviceEntryInteractor: DeviceEntryInteractor,
val shadeHeaderViewModel: ShadeHeaderViewModel,
val qsSceneAdapter: QSSceneAdapter,
val notifications: NotificationsPlaceholderViewModel,
) {
- /** Notifies that some content in quick settings was clicked. */
- fun onContentClicked() = deviceEntryInteractor.attemptDeviceEntry()
-
val destinationScenes =
qsSceneAdapter.isCustomizing.map { customizing ->
if (customizing) {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index b30bc56..bc5090f 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -30,12 +30,11 @@
import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.RestrictedLockUtils;
import com.android.systemui.Gefingerpoken;
+import com.android.systemui.res.R;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.haptics.slider.SeekableSliderEventProducer;
-import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.res.R;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.util.ViewController;
@@ -282,7 +281,6 @@
private final VibratorHelper mVibratorHelper;
private final SystemClock mSystemClock;
private final CoroutineDispatcher mMainDispatcher;
- private final ActivityStarter mActivityStarter;
@Inject
public Factory(
@@ -290,14 +288,13 @@
UiEventLogger uiEventLogger,
VibratorHelper vibratorHelper,
SystemClock clock,
- @Main CoroutineDispatcher mainDispatcher,
- ActivityStarter activityStarter) {
+ @Main CoroutineDispatcher mainDispatcher
+ ) {
mFalsingManager = falsingManager;
mUiEventLogger = uiEventLogger;
mVibratorHelper = vibratorHelper;
mSystemClock = clock;
mMainDispatcher = mainDispatcher;
- mActivityStarter = activityStarter;
}
/**
@@ -313,8 +310,6 @@
int layout = getLayout();
BrightnessSliderView root = (BrightnessSliderView) LayoutInflater.from(context)
.inflate(layout, viewRoot, false);
- root.setActivityStarter(mActivityStarter);
-
BrightnessSliderHapticPlugin plugin;
if (hapticBrightnessSlider()) {
plugin = new BrightnessSliderHapticPluginImpl(
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index 5ecf07f..c885492 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -33,7 +33,6 @@
import com.android.settingslib.RestrictedLockUtils;
import com.android.systemui.Gefingerpoken;
-import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.res.R;
/**
@@ -42,7 +41,6 @@
*/
public class BrightnessSliderView extends FrameLayout {
- private ActivityStarter mActivityStarter;
@NonNull
private ToggleSeekBar mSlider;
private DispatchTouchEventListener mListener;
@@ -59,10 +57,6 @@
super(context, attrs);
}
- public void setActivityStarter(@NonNull ActivityStarter activityStarter) {
- mActivityStarter = activityStarter;
- }
-
// Inflated from quick_settings_brightness_dialog
@Override
protected void onFinishInflate() {
@@ -71,7 +65,6 @@
mSlider = requireViewById(R.id.slider);
mSlider.setAccessibilityLabel(getContentDescription().toString());
- mSlider.setActivityStarter(mActivityStarter);
// Finds the progress drawable. Assumes brightness_progress_drawable.xml
try {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
index 6ec10da..a5a0ae7 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
@@ -23,9 +23,8 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.SeekBar;
-import androidx.annotation.NonNull;
-
import com.android.settingslib.RestrictedLockUtils;
+import com.android.systemui.Dependency;
import com.android.systemui.plugins.ActivityStarter;
public class ToggleSeekBar extends SeekBar {
@@ -33,8 +32,6 @@
private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin = null;
- private ActivityStarter mActivityStarter;
-
public ToggleSeekBar(Context context) {
super(context);
}
@@ -52,7 +49,7 @@
if (mEnforcedAdmin != null) {
Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
mContext, mEnforcedAdmin);
- mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
+ Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0);
return true;
}
if (!isEnabled()) {
@@ -77,8 +74,4 @@
public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
mEnforcedAdmin = admin;
}
-
- public void setActivityStarter(@NonNull ActivityStarter activityStarter) {
- mActivityStarter = activityStarter;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index a44b4b4..c810786 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -184,6 +184,8 @@
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -604,6 +606,7 @@
private final LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel;
private final PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
private final SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
+ private final ActiveNotificationsInteractor mActiveNotificationsInteractor;
private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
private final KeyguardInteractor mKeyguardInteractor;
private final PowerInteractor mPowerInteractor;
@@ -774,6 +777,7 @@
KeyguardInteractor keyguardInteractor,
ActivityStarter activityStarter,
SharedNotificationContainerInteractor sharedNotificationContainerInteractor,
+ ActiveNotificationsInteractor activeNotificationsInteractor,
KeyguardViewConfigurator keyguardViewConfigurator,
KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
SplitShadeStateController splitShadeStateController,
@@ -804,6 +808,7 @@
mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mSharedNotificationContainerInteractor = sharedNotificationContainerInteractor;
+ mActiveNotificationsInteractor = activeNotificationsInteractor;
mKeyguardInteractor = keyguardInteractor;
mPowerInteractor = powerInteractor;
mKeyguardViewConfigurator = keyguardViewConfigurator;
@@ -1795,9 +1800,14 @@
}
private boolean hasVisibleNotifications() {
- return mNotificationStackScrollLayoutController
- .getVisibleNotificationCount() != 0
- || mMediaDataManager.hasActiveMediaOrRecommendation();
+ if (FooterViewRefactor.isEnabled()) {
+ return mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()
+ || mMediaDataManager.hasActiveMediaOrRecommendation();
+ } else {
+ return mNotificationStackScrollLayoutController
+ .getVisibleNotificationCount() != 0
+ || mMediaDataManager.hasActiveMediaOrRecommendation();
+ }
}
/** Returns space between top of lock icon and bottom of NotificationStackScrollLayout. */
@@ -3004,7 +3014,9 @@
@Override
public void setBouncerShowing(boolean bouncerShowing) {
mBouncerShowing = bouncerShowing;
- mNotificationStackScrollLayoutController.updateShowEmptyShadeView();
+ if (!FooterViewRefactor.isEnabled()) {
+ mNotificationStackScrollLayoutController.updateShowEmptyShadeView();
+ }
updateVisibility();
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 868fbce..e84bfc5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -87,6 +87,8 @@
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.QsFrameTranslateController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -106,12 +108,12 @@
import dalvik.annotation.optimization.NeverCompile;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import javax.inject.Inject;
-import dagger.Lazy;
-
/** Handles QuickSettings touch handling, expansion and animation state
* TODO (b/264460656) make this dumpable
*/
@@ -157,6 +159,7 @@
private final InteractionJankMonitor mInteractionJankMonitor;
private final ShadeRepository mShadeRepository;
private final ShadeInteractor mShadeInteractor;
+ private final ActiveNotificationsInteractor mActiveNotificationsInteractor;
private final JavaAdapter mJavaAdapter;
private final FalsingManager mFalsingManager;
private final AccessibilityManager mAccessibilityManager;
@@ -339,6 +342,7 @@
KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
ShadeRepository shadeRepository,
ShadeInteractor shadeInteractor,
+ ActiveNotificationsInteractor activeNotificationsInteractor,
JavaAdapter javaAdapter,
CastController castController,
SplitShadeStateController splitShadeStateController
@@ -386,6 +390,7 @@
mInteractionJankMonitor = interactionJankMonitor;
mShadeRepository = shadeRepository;
mShadeInteractor = shadeInteractor;
+ mActiveNotificationsInteractor = activeNotificationsInteractor;
mJavaAdapter = javaAdapter;
mLockscreenShadeTransitionController.addCallback(new LockscreenShadeTransitionCallback());
@@ -983,7 +988,9 @@
void updateQsState() {
boolean qsFullScreen = getExpanded() && !mSplitShadeEnabled;
mShadeRepository.setLegacyQsFullscreen(qsFullScreen);
- mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen);
+ if (!FooterViewRefactor.isEnabled()) {
+ mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen);
+ }
mNotificationStackScrollLayoutController.setScrollingEnabled(
mBarState != KEYGUARD && (!qsFullScreen || mExpansionFromOverscroll));
@@ -2230,8 +2237,12 @@
mLockscreenShadeTransitionController.getQSDragProgress());
setExpansionHeight(qsHeight);
}
- if (mNotificationStackScrollLayoutController.getVisibleNotificationCount() == 0
- && !mMediaDataManager.hasActiveMediaOrRecommendation()) {
+
+ boolean hasNotifications = FooterViewRefactor.isEnabled()
+ ? mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()
+ : mNotificationStackScrollLayoutController.getVisibleNotificationCount()
+ != 0;
+ if (!hasNotifications && !mMediaDataManager.hasActiveMediaOrRecommendation()) {
// No notifications are visible, let's animate to the height of qs instead
if (isQsFragmentCreated()) {
// Let's interpolate to the header height instead of the top padding,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
index de334bb..2338be2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
@@ -85,7 +85,9 @@
public void setFooterVisibility(@Visibility int visibility) {
mFooterVisibility = visibility;
- setSecondaryVisible(visibility == View.VISIBLE, false);
+ setSecondaryVisible(/* visible = */ visibility == View.VISIBLE,
+ /* animate = */false,
+ /* onAnimationEnded = */ null);
}
public void setFooterText(@StringRes int text) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 64970e4..fa2748c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -16,17 +16,19 @@
package com.android.systemui.statusbar.notification.collection.coordinator
+import com.android.app.tracing.traceSection
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.phone.NotificationIconAreaController
-import com.android.app.tracing.traceSection
import javax.inject.Inject
/**
@@ -40,6 +42,7 @@
private val groupExpansionManagerImpl: GroupExpansionManagerImpl,
private val notificationIconAreaController: NotificationIconAreaController,
private val renderListInteractor: RenderNotificationListInteractor,
+ private val activeNotificationsInteractor: ActiveNotificationsInteractor,
) : Coordinator {
override fun attach(pipeline: NotifPipeline) {
@@ -49,8 +52,14 @@
fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) =
traceSection("StackCoordinator.onAfterRenderList") {
- controller.setNotifStats(calculateNotifStats(entries))
- if (NotificationIconContainerRefactor.isEnabled) {
+ val notifStats = calculateNotifStats(entries)
+ if (FooterViewRefactor.isEnabled) {
+ activeNotificationsInteractor.setNotifStats(notifStats)
+ }
+ // TODO(b/293167744): This shouldn't be done if the footer flag is on, once the footer
+ // visibility is handled in the new stack.
+ controller.setNotifStats(notifStats)
+ if (NotificationIconContainerRefactor.isEnabled || FooterViewRefactor.isEnabled) {
renderListInteractor.setRenderedList(entries)
} else {
notificationIconAreaController.updateNotificationIcons(entries)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
index fde4ecb..a37937a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
@@ -26,6 +26,7 @@
/** Data provided to the NotificationRootController whenever the pipeline runs */
data class NotifStats(
+ // TODO(b/293167744): The count can be removed from here when we remove the FooterView flag.
val numActiveNotifs: Int,
val hasNonClearableAlertingNotifs: Boolean,
val hasClearableAlertingNotifs: Boolean,
@@ -33,17 +34,16 @@
val hasClearableSilentNotifs: Boolean
) {
companion object {
- @JvmStatic
- val empty = NotifStats(0, false, false, false, false)
+ @JvmStatic val empty = NotifStats(0, false, false, false, false)
}
}
/**
* An implementation of NotifStackController which provides default, no-op implementations of each
- * method. This is used by ArcSystemUI so that that implementation can opt-in to overriding
- * methods, rather than forcing us to add no-op implementations in their implementation every time
- * a method is added.
+ * method. This is used by ArcSystemUI so that that implementation can opt-in to overriding methods,
+ * rather than forcing us to add no-op implementations in their implementation every time a method
+ * is added.
*/
open class DefaultNotifStackController @Inject constructor() : NotifStackController {
override fun setNotifStats(stats: NotifStats) {}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
index 12ee54d..5ed82cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.data.repository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.render.NotifStats
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore.Key
import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
@@ -37,6 +38,9 @@
/** Are any already-seen notifications currently filtered out of the active list? */
val hasFilteredOutSeenNotifications = MutableStateFlow(false)
+
+ /** Stats about the list of notifications attached to the shade */
+ val notifStats = MutableStateFlow(NotifStats.empty)
}
/** Represents the notification list, comprised of groups and individual notifications. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index 85ba205..31893b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -15,17 +15,19 @@
package com.android.systemui.statusbar.notification.domain.interactor
+import com.android.systemui.statusbar.notification.collection.render.NotifStats
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
class ActiveNotificationsInteractor
@Inject
constructor(
- repository: ActiveNotificationListRepository,
+ private val repository: ActiveNotificationListRepository,
) {
/** Notifications actively presented to the user in the notification stack, in order. */
val topLevelRepresentativeNotifications: Flow<List<ActiveNotificationModel>> =
@@ -40,4 +42,25 @@
}
}
}
+
+ /** Are any notifications being actively presented in the notification stack? */
+ val areAnyNotificationsPresent: Flow<Boolean> =
+ repository.activeNotifications.map { it.renderList.isNotEmpty() }.distinctUntilChanged()
+
+ /**
+ * The same as [areAnyNotificationsPresent], but without flows, for easy access in synchronous
+ * code.
+ */
+ val areAnyNotificationsPresentValue: Boolean
+ get() = repository.activeNotifications.value.renderList.isNotEmpty()
+
+ /** Are there are any notifications that can be cleared by the "Clear all" button? */
+ val hasClearableNotifications: Flow<Boolean> =
+ repository.notifStats
+ .map { it.hasClearableAlertingNotifs || it.hasClearableSilentNotifs }
+ .distinctUntilChanged()
+
+ fun setNotifStats(notifStats: NotifStats) {
+ repository.notifStats.value = notifStats
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 10a43d5..3184d5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -46,6 +46,7 @@
import com.android.systemui.util.DumpUtilsKt;
import java.io.PrintWriter;
+import java.util.function.Consumer;
public class FooterView extends StackScrollerDecorView {
private static final String TAG = "FooterView";
@@ -63,9 +64,13 @@
private String mSeenNotifsFilteredText;
private Drawable mSeenNotifsFilteredIcon;
+ private @StringRes int mClearAllButtonTextId;
+ private @StringRes int mClearAllButtonDescriptionId;
private @StringRes int mMessageStringId;
private @DrawableRes int mMessageIconId;
+ private OnClickListener mClearAllButtonClickListener;
+
public FooterView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -84,12 +89,18 @@
return isSecondaryVisible();
}
+ /** See {@link this#setClearAllButtonVisible(boolean, boolean, Consumer)}. */
+ public void setClearAllButtonVisible(boolean visible, boolean animate) {
+ setClearAllButtonVisible(visible, animate, /* onAnimationEnded = */ null);
+ }
+
/**
* Set the visibility of the "Clear all" button to {@code visible}. Animate the change if
* {@code animate} is true.
*/
- public void setClearAllButtonVisible(boolean visible, boolean animate) {
- setSecondaryVisible(visible, animate);
+ public void setClearAllButtonVisible(boolean visible, boolean animate,
+ Consumer<Boolean> onAnimationEnded) {
+ setSecondaryVisible(visible, animate, onAnimationEnded);
}
@Override
@@ -106,6 +117,42 @@
});
}
+ /** Set the text label for the "Clear all" button. */
+ public void setClearAllButtonText(@StringRes int textId) {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
+ if (mClearAllButtonTextId == textId) {
+ return; // nothing changed
+ }
+ mClearAllButtonTextId = textId;
+ updateClearAllButtonText();
+ }
+
+ private void updateClearAllButtonText() {
+ if (mClearAllButtonTextId == 0) {
+ return; // not initialized yet
+ }
+ mClearAllButton.setText(getContext().getString(mClearAllButtonTextId));
+ }
+
+ /** Set the accessibility content description for the "Clear all" button. */
+ public void setClearAllButtonDescription(@StringRes int contentDescriptionId) {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+ if (mClearAllButtonDescriptionId == contentDescriptionId) {
+ return; // nothing changed
+ }
+ mClearAllButtonDescriptionId = contentDescriptionId;
+ updateClearAllButtonDescription();
+ }
+
+ private void updateClearAllButtonDescription() {
+ if (mClearAllButtonDescriptionId == 0) {
+ return; // not initialized yet
+ }
+ mClearAllButton.setContentDescription(getContext().getString(mClearAllButtonDescriptionId));
+ }
+
/** Set the string for a message to be shown instead of the buttons. */
public void setMessageString(@StringRes int messageId) {
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
@@ -181,6 +228,10 @@
/** Set onClickListener for the clear all (end) button. */
public void setClearAllButtonClickListener(OnClickListener listener) {
+ if (FooterViewRefactor.isEnabled()) {
+ if (mClearAllButtonClickListener == listener) return;
+ mClearAllButtonClickListener = listener;
+ }
mClearAllButton.setOnClickListener(listener);
}
@@ -214,7 +265,28 @@
mManageButton.setText(mManageNotificationText);
mManageButton.setContentDescription(mManageNotificationText);
}
- if (!FooterViewRefactor.isEnabled()) {
+ if (FooterViewRefactor.isEnabled()) {
+ updateClearAllButtonText();
+ updateClearAllButtonDescription();
+
+ updateMessageString();
+ updateMessageIcon();
+ } else {
+ // NOTE: Prior to the refactor, `updateResources` set the class properties to the right
+ // string values. It was always being called together with `updateContent`, which
+ // deals with actually associating those string values with the correct views
+ // (buttons or text).
+ // In the new code, the resource IDs are being set in the view binder (through
+ // setMessageString and similar setters). The setters themselves now deal with
+ // updating both the resource IDs and the views where appropriate (as in, calling
+ // `updateMessageString` when the resource ID changes). This eliminates the need for
+ // `updateResources`, which will eventually be removed. There are, however, still
+ // situations in which we want to update the views even if the resource IDs didn't
+ // change, such as configuration changes.
+ mClearAllButton.setText(R.string.clear_all_notifications_text);
+ mClearAllButton.setContentDescription(
+ mContext.getString(R.string.accessibility_clear_all));
+
mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText);
mSeenNotifsFooterTextView
.setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null);
@@ -230,16 +302,8 @@
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateColors();
- mClearAllButton.setText(R.string.clear_all_notifications_text);
- mClearAllButton.setContentDescription(
- mContext.getString(R.string.accessibility_clear_all));
updateResources();
updateContent();
-
- if (FooterViewRefactor.isEnabled()) {
- updateMessageString();
- updateMessageIcon();
- }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
index 6d823437..0299114 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -16,10 +16,14 @@
package com.android.systemui.statusbar.notification.footer.ui.viewbinder
+import android.view.View
import androidx.lifecycle.lifecycleScope
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.stopAnimating
+import com.android.systemui.util.ui.value
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.launch
@@ -28,9 +32,31 @@
fun bind(
footer: FooterView,
viewModel: FooterViewModel,
+ clearAllNotifications: View.OnClickListener,
): DisposableHandle {
+ // Listen for changes when the view is attached.
return footer.repeatWhenAttached {
- // Listen for changes when the view is attached.
+ lifecycleScope.launch {
+ viewModel.clearAllButton.collect { button ->
+ if (button.isVisible.isAnimating) {
+ footer.setClearAllButtonVisible(
+ button.isVisible.value,
+ /* animate = */ true,
+ ) { _ ->
+ button.isVisible.stopAnimating()
+ }
+ } else {
+ footer.setClearAllButtonVisible(
+ button.isVisible.value,
+ /* animate = */ false,
+ )
+ }
+ footer.setClearAllButtonText(button.labelId)
+ footer.setClearAllButtonDescription(button.accessibilityDescriptionId)
+ footer.setClearAllButtonClickListener(clearAllNotifications)
+ }
+ }
+
lifecycleScope.launch {
viewModel.message.collect { message ->
footer.setFooterLabelVisible(message.visible)
diff --git a/core/java/android/os/WorkDuration.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
similarity index 65%
rename from core/java/android/os/WorkDuration.aidl
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
index 0f61204..ea5abef 100644
--- a/core/java/android/os/WorkDuration.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
@@ -14,6 +14,13 @@
* limitations under the License.
*/
-package android.os;
+package com.android.systemui.statusbar.notification.footer.ui.viewmodel
-parcelable WorkDuration cpp_header "android/WorkDuration.h";
\ No newline at end of file
+import android.annotation.StringRes
+import com.android.systemui.util.ui.AnimatedValue
+
+data class FooterButtonViewModel(
+ @StringRes val labelId: Int,
+ @StringRes val accessibilityDescriptionId: Int,
+ val isVisible: AnimatedValue<Boolean>,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
index 7390485..721bea1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -18,21 +18,50 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.ui.AnimatableEvent
+import com.android.systemui.util.ui.toAnimatedValueFlow
import dagger.Module
import dagger.Provides
import java.util.Optional
import javax.inject.Provider
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
/** ViewModel for [FooterView]. */
-class FooterViewModel(seenNotificationsInteractor: SeenNotificationsInteractor) {
- init {
- /* Check if */ FooterViewRefactor.isUnexpectedlyInLegacyMode()
- }
+class FooterViewModel(
+ activeNotificationsInteractor: ActiveNotificationsInteractor,
+ seenNotificationsInteractor: SeenNotificationsInteractor,
+ shadeInteractor: ShadeInteractor,
+) {
+ val clearAllButton: Flow<FooterButtonViewModel> =
+ activeNotificationsInteractor.hasClearableNotifications
+ .sample(
+ combine(
+ shadeInteractor.isShadeFullyExpanded,
+ shadeInteractor.isShadeTouchable,
+ ::Pair
+ )
+ .onStart { emit(Pair(false, false)) }
+ ) { hasClearableNotifications, (isShadeFullyExpanded, animationsEnabled) ->
+ val shouldAnimate = isShadeFullyExpanded && animationsEnabled
+ AnimatableEvent(hasClearableNotifications, shouldAnimate)
+ }
+ .toAnimatedValueFlow()
+ .map { visible ->
+ FooterButtonViewModel(
+ labelId = R.string.clear_all_notifications_text,
+ accessibilityDescriptionId = R.string.accessibility_clear_all,
+ isVisible = visible,
+ )
+ }
val message: Flow<FooterMessageViewModel> =
seenNotificationsInteractor.hasFilteredOutSeenNotifications.map { hasFilteredOutNotifs ->
@@ -49,10 +78,18 @@
@Provides
@SysUISingleton
fun provideOptional(
+ activeNotificationsInteractor: Provider<ActiveNotificationsInteractor>,
seenNotificationsInteractor: Provider<SeenNotificationsInteractor>,
+ shadeInteractor: Provider<ShadeInteractor>,
): Optional<FooterViewModel> {
return if (FooterViewRefactor.isEnabled) {
- Optional.of(FooterViewModel(seenNotificationsInteractor.get()))
+ Optional.of(
+ FooterViewModel(
+ activeNotificationsInteractor.get(),
+ seenNotificationsInteractor.get(),
+ shadeInteractor.get()
+ )
+ )
} else {
Optional.empty()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 9f2b0a6..8e82442 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -204,6 +204,11 @@
override fun shouldSuppress(entry: NotificationEntry) = !entry.canBubble()
}
+class BubbleAppSuspendedSuppressor :
+ VisualInterruptionFilter(types = setOf(BUBBLE), reason = "app is suspended") {
+ override fun shouldSuppress(entry: NotificationEntry) = entry.ranking.isSuspended
+}
+
class BubbleNoMetadataSuppressor() :
VisualInterruptionFilter(types = setOf(BUBBLE), reason = "has no or invalid bubble metadata") {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 2fffd37..334e08d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -56,6 +56,14 @@
}
}
+ fun logSuspendedAppBubble(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
+ }, {
+ "No bubble up: notification: app $str1 is suspended"
+ })
+ }
+
fun logNoBubbleNoMetadata(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
str1 = entry.logKey
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 4045380..510086d 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
@@ -204,6 +204,11 @@
return false;
}
+ if (entry.getRanking().isSuspended()) {
+ mLogger.logSuspendedAppBubble(entry);
+ return false;
+ }
+
if (entry.getBubbleMetadata() == null
|| (entry.getBubbleMetadata().getShortcutId() == null
&& entry.getBubbleMetadata().getIntent() == null)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 39a87de..73dd5f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -159,6 +159,7 @@
addFilter(PulseLockscreenVisibilityPrivateSuppressor())
addFilter(PulseLowImportanceSuppressor())
addFilter(BubbleNotAllowedSuppressor())
+ addFilter(BubbleAppSuspendedSuppressor())
addFilter(BubbleNoMetadataSuppressor())
addFilter(HunGroupAlertBehaviorSuppressor())
addFilter(HunJustLaunchedFsiSuppressor())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 11c65e5..6cb079a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -2221,8 +2221,8 @@
if (mMenuRow != null) {
mMenuRow.resetMenu();
}
- mTranslateAnim = null;
}
+ mTranslateAnim = null;
}
});
mTranslateAnim = translateAnim;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index 8eda96f..64f61d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -27,6 +27,7 @@
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
@@ -42,9 +43,11 @@
* A view that can be used for both the dimmed and normal background of an notification.
*/
public class NotificationBackgroundView extends View implements Dumpable {
+ private static final String TAG = "NotificationBackgroundView";
private final boolean mDontModifyCorners;
private Drawable mBackground;
+ private Drawable mBackgroundDrawableToTint;
private int mClipTopAmount;
private int mClipBottomAmount;
private int mTintColor;
@@ -131,6 +134,7 @@
unscheduleDrawable(mBackground);
}
mBackground = background;
+ mBackgroundDrawableToTint = findBackgroundDrawableToTint(mBackground);
mRippleColor = null;
mBackground.mutate();
if (mBackground != null) {
@@ -144,25 +148,46 @@
invalidate();
}
+ // setCustomBackground should be called from ActivatableNotificationView.initBackground
+ // with R.drawable.notification_material_bg, which is a layer-list with a lower layer
+ // for the background color (annotated with an ID so we can find it) and an upper layer
+ // to blend in the stateful @color/notification_overlay_color.
+ //
+ // If the notification is tinted, we want to set a tint list on *just that lower layer* that
+ // will replace the default materialColorSurfaceContainerHigh *without* wiping out the stateful
+ // tints in the upper layer that make the hovered and pressed states visible.
+ //
+ // This function fishes that lower layer out, or makes a fuss in logcat if it can't find it.
+ private @Nullable Drawable findBackgroundDrawableToTint(@Nullable Drawable background) {
+ if (background == null) {
+ return null;
+ }
+
+ if (!(background instanceof LayerDrawable)) {
+ Log.wtf(TAG, "background is not a LayerDrawable: " + background);
+ return background;
+ }
+
+ final Drawable backgroundColorLayer = ((LayerDrawable) background).findDrawableByLayerId(
+ R.id.notification_background_color_layer);
+
+ if (backgroundColorLayer == null) {
+ Log.wtf(TAG, "background is missing background color layer: " + background);
+ return background;
+ }
+
+ return backgroundColorLayer;
+ }
+
public void setCustomBackground(int drawableResId) {
final Drawable d = mContext.getDrawable(drawableResId);
setCustomBackground(d);
}
public void setTint(int tintColor) {
- if (tintColor != 0) {
- ColorStateList stateList = new ColorStateList(new int[][]{
- new int[]{com.android.internal.R.attr.state_pressed},
- new int[]{com.android.internal.R.attr.state_hovered},
- new int[]{}},
+ mBackgroundDrawableToTint.setTint(tintColor);
+ mBackgroundDrawableToTint.setTintMode(PorterDuff.Mode.SRC_ATOP);
- new int[]{tintColor, tintColor, tintColor}
- );
- mBackground.setTintMode(PorterDuff.Mode.SRC_ATOP);
- mBackground.setTintList(stateList);
- } else {
- mBackground.setTintList(null);
- }
mTintColor = tintColor;
invalidate();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index ec90a8d..162e8af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -54,7 +54,7 @@
mContent = findContentView();
mSecondaryView = findSecondaryView();
setVisible(false /* visible */, false /* animate */);
- setSecondaryVisible(false /* visible */, false /* animate */);
+ setSecondaryVisible(false /* visible */, false /* animate */, null /* onAnimationEnd */);
setOutlineProvider(null);
}
@@ -155,15 +155,23 @@
/**
* Set the secondary view of this layout to visible.
*
- * @param visible should the secondary view be visible
- * @param animate should the change be animated
+ * @param visible True if the contents should be visible.
+ * @param animate True if we should fade to new visibility.
+ * @param onAnimationEnded Callback to run after visibility updates, takes a boolean as a
+ * parameter that represents whether the animation was cancelled.
*/
- protected void setSecondaryVisible(boolean visible, boolean animate) {
+ protected void setSecondaryVisible(boolean visible, boolean animate,
+ Consumer<Boolean> onAnimationEnded) {
if (mIsSecondaryVisible != visible) {
mSecondaryAnimating = animate;
mIsSecondaryVisible = visible;
- setViewVisible(mSecondaryView, visible, animate,
- (cancelled) -> onSecondaryVisibilityAnimationEnd());
+ Consumer<Boolean> onAnimationEndedWrapper = (cancelled) -> {
+ onContentVisibilityAnimationEnd();
+ if (onAnimationEnded != null) {
+ onAnimationEnded.accept(cancelled);
+ }
+ };
+ setViewVisible(mSecondaryView, visible, animate, onAnimationEndedWrapper);
}
if (!mSecondaryAnimating) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 38d782b..6944453 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -565,6 +565,7 @@
private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
private final ScreenOffAnimationController mScreenOffAnimationController;
private boolean mShouldUseSplitNotificationShade;
+ private boolean mShouldSkipTopPaddingAnimationAfterFold = false;
private boolean mHasFilteredOutSeenNotifications;
@Nullable private SplitShadeStateController mSplitShadeStateController = null;
private boolean mIsSmallLandscapeLockscreenEnabled = false;
@@ -740,7 +741,9 @@
updateFooter();
}
- void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) {
+ /** Setter for filtered notifs, to be removed with the FooterViewRefactor flag. */
+ public void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) {
+ FooterViewRefactor.assertInLegacyMode();
mHasFilteredOutSeenNotifications = hasFilteredOutSeenNotifications;
}
@@ -749,7 +752,6 @@
if (mFooterView == null || mController == null) {
return;
}
- // TODO: move this logic to controller, which will invoke updateFooterView directly
final boolean showHistory = mController.isHistoryEnabled();
final boolean showDismissView = shouldShowDismissView();
@@ -773,13 +775,6 @@
&& !mIsRemoteInputActive;
}
- /**
- * Return whether there are any clearable notifications
- */
- boolean hasActiveClearableNotifications(@SelectedRows int selection) {
- return mController.hasActiveClearableNotifications(selection);
- }
-
public NotificationSwipeActionHelper getSwipeActionHelper() {
return mSwipeHelper;
}
@@ -1370,7 +1365,11 @@
mTopPadding = topPadding;
updateAlgorithmHeightAndPadding();
updateContentHeight();
- if (shouldAnimate && mAnimationsEnabled && mIsExpanded) {
+ if (mAmbientState.isOnKeyguard()
+ && !mShouldUseSplitNotificationShade
+ && mShouldSkipTopPaddingAnimationAfterFold) {
+ mShouldSkipTopPaddingAnimationAfterFold = false;
+ } else if (shouldAnimate && mAnimationsEnabled && mIsExpanded) {
mTopPaddingNeedsAnimation = true;
mNeedsAnimation = true;
}
@@ -1658,8 +1657,44 @@
/**
* @return the position from where the appear transition ends when expanding.
* Measured in absolute height.
+ *
+ * TODO(b/308591475): This entire logic can probably be improved as part of the empty shade
+ * refactor, but for now:
+ * - if the empty shade is visible, we can assume that the visible notif count is not 0;
+ * - if the shelf is visible, we can assume there are at least 2 notifications present (this
+ * is not true for AOD, but this logic refers to the expansion of the shade, where we never
+ * have the shelf on its own)
*/
private float getAppearEndPosition() {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+ return getAppearEndPositionLegacy();
+ }
+
+ int appearPosition = mAmbientState.getStackTopMargin();
+ if (mEmptyShadeView.getVisibility() == GONE) {
+ if (isHeadsUpTransition()
+ || (mInHeadsUpPinnedMode && !mAmbientState.isDozing())) {
+ if (mShelf.getVisibility() != GONE) {
+ appearPosition += mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
+ }
+ appearPosition += getTopHeadsUpPinnedHeight()
+ + getPositionInLinearLayout(mAmbientState.getTrackedHeadsUpRow());
+ } else if (mShelf.getVisibility() != GONE) {
+ appearPosition += mShelf.getIntrinsicHeight();
+ }
+ } else {
+ appearPosition = mEmptyShadeView.getHeight();
+ }
+ return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding);
+ }
+
+ /**
+ * The version of {@code getAppearEndPosition} that uses the notif count. The view shouldn't
+ * need to know about that, so we want to phase this out with the footer view refactor.
+ */
+ private float getAppearEndPositionLegacy() {
+ FooterViewRefactor.assertInLegacyMode();
+
int appearPosition = mAmbientState.getStackTopMargin();
int visibleNotifCount = mController.getVisibleNotificationCount();
if (mEmptyShadeView.getVisibility() == GONE && visibleNotifCount > 0) {
@@ -1698,7 +1733,8 @@
// This can't use expansion fraction as that goes only from 0 to 1. Also when
// appear fraction for HUN is 0, expansion fraction will be already around 0.2-0.3
// and that makes translation jump immediately.
- float appearEndPosition = getAppearEndPosition();
+ float appearEndPosition = FooterViewRefactor.isEnabled() ? getAppearEndPosition()
+ : getAppearEndPositionLegacy();
float appearStartPosition = getAppearStartPosition();
float hunAppearFraction = (height - appearStartPosition)
/ (appearEndPosition - appearStartPosition);
@@ -3720,6 +3756,11 @@
}
protected boolean isInsideQsHeader(MotionEvent ev) {
+ if (mQsHeader == null) {
+ Log.wtf(TAG, "qsHeader is null while NSSL is handling a touch");
+ return false;
+ }
+
mQsHeader.getBoundsOnScreen(mQsHeaderBound);
/**
* One-handed mode defines a feature FEATURE_ONE_HANDED of DisplayArea {@link DisplayArea}
@@ -4577,13 +4618,15 @@
if (mManageButtonClickListener != null) {
mFooterView.setManageButtonClickListener(mManageButtonClickListener);
}
- mFooterView.setClearAllButtonClickListener(v -> {
- if (mFooterClearAllListener != null) {
- mFooterClearAllListener.onClearAll();
- }
- clearNotifications(ROWS_ALL, true /* closeShade */);
- footerView.setClearAllButtonVisible(false /* visible */, true /* animate */);
- });
+ if (!FooterViewRefactor.isEnabled()) {
+ mFooterView.setClearAllButtonClickListener(v -> {
+ if (mFooterClearAllListener != null) {
+ mFooterClearAllListener.onClearAll();
+ }
+ clearNotifications(ROWS_ALL, true /* closeShade */);
+ footerView.setClearAllButtonVisible(false /* visible */, true /* animate */);
+ });
+ }
if (FooterViewRefactor.isEnabled()) {
updateFooter();
}
@@ -4599,12 +4642,21 @@
addView(mEmptyShadeView, index);
}
- void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) {
+ /** Legacy version, should be removed with the footer refactor flag. */
+ public void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) {
+ FooterViewRefactor.assertInLegacyMode();
+ updateEmptyShadeView(visible, areNotificationsHiddenInShade,
+ mHasFilteredOutSeenNotifications);
+ }
+
+ /** Trigger an update for the empty shade resources and visibility. */
+ public void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade,
+ boolean hasFilteredOutSeenNotifications) {
mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
if (areNotificationsHiddenInShade) {
updateEmptyShadeView(R.string.dnd_suppressing_shade_text, 0, 0);
- } else if (mHasFilteredOutSeenNotifications) {
+ } else if (hasFilteredOutSeenNotifications) {
updateEmptyShadeView(
R.string.no_unseen_notif_text,
R.string.unlock_to_see_notif_text,
@@ -4647,9 +4699,9 @@
}
boolean animate = mIsExpanded && mAnimationsEnabled;
mFooterView.setVisible(visible, animate);
- mFooterView.setClearAllButtonVisible(showDismissView, animate);
mFooterView.showHistory(showHistory);
if (!FooterViewRefactor.isEnabled()) {
+ mFooterView.setClearAllButtonVisible(showDismissView, animate);
mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
}
}
@@ -4781,10 +4833,13 @@
public void removeContainerView(View v) {
Assert.isMainThread();
removeView(v);
- if (v instanceof ExpandableNotificationRow && !mController.isShowingEmptyShadeView()) {
- mController.updateShowEmptyShadeView();
- updateFooter();
- mController.updateImportantForAccessibility();
+ if (!FooterViewRefactor.isEnabled()) {
+ // A notification was removed, and we're not currently showing the empty shade view.
+ if (v instanceof ExpandableNotificationRow && !mController.isShowingEmptyShadeView()) {
+ mController.updateShowEmptyShadeView();
+ updateFooter();
+ mController.updateImportantForAccessibility();
+ }
}
updateSpeedBumpIndex();
@@ -4793,10 +4848,13 @@
public void addContainerView(View v) {
Assert.isMainThread();
addView(v);
- if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) {
- mController.updateShowEmptyShadeView();
- updateFooter();
- mController.updateImportantForAccessibility();
+ if (!FooterViewRefactor.isEnabled()) {
+ // A notification was added, and we're currently showing the empty shade view.
+ if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) {
+ mController.updateShowEmptyShadeView();
+ updateFooter();
+ mController.updateImportantForAccessibility();
+ }
}
updateSpeedBumpIndex();
@@ -4806,7 +4864,9 @@
Assert.isMainThread();
ensureRemovedFromTransientContainer(v);
addView(v, index);
- if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) {
+ // A notification was added, and we're currently showing the empty shade view.
+ if (!FooterViewRefactor.isEnabled() && v instanceof ExpandableNotificationRow
+ && mController.isShowingEmptyShadeView()) {
mController.updateShowEmptyShadeView();
updateFooter();
mController.updateImportantForAccessibility();
@@ -5079,7 +5139,8 @@
if (mEmptyShadeView.getVisibility() == GONE) {
return getMinExpansionHeight();
} else {
- return getAppearEndPosition();
+ return FooterViewRefactor.isEnabled() ? getAppearEndPosition()
+ : getAppearEndPositionLegacy();
}
}
@@ -5306,11 +5367,15 @@
return viewsToRemove;
}
+ /** Clear all clearable notifications when the user requests it. */
+ public void clearAllNotifications() {
+ clearNotifications(ROWS_ALL, /* closeShade = */ true);
+ }
+
/**
* Collects a list of visible rows, and animates them away in a staggered fashion as if they
* were dismissed. Notifications are dismissed in the backend via onClearAllAnimationsEnd.
*/
- @VisibleForTesting
void clearNotifications(@SelectedRows int selection, boolean closeShade) {
// Animate-swipe all dismissable notifications, then animate the shade closed
final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection);
@@ -5610,6 +5675,7 @@
}
void setFooterClearAllListener(FooterClearAllListener listener) {
+ FooterViewRefactor.assertInLegacyMode();
mFooterClearAllListener = listener;
}
@@ -5680,6 +5746,7 @@
boolean split = mSplitShadeStateController.shouldUseSplitNotificationShade(getResources());
if (split != mShouldUseSplitNotificationShade) {
mShouldUseSplitNotificationShade = split;
+ mShouldSkipTopPaddingAnimationAfterFold = true;
mAmbientState.setUseSplitShade(split);
updateDismissBehavior();
updateUseRoundedRectClipping();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 3e140a4..e6315fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -20,7 +20,6 @@
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
import static com.android.app.animation.Interpolators.STANDARD;
-
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
@@ -108,7 +107,9 @@
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.dagger.SilentHeader;
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -223,7 +224,9 @@
@Override
public void onViewAttachedToWindow(View v) {
mConfigurationController.addCallback(mConfigurationListener);
- mZenModeController.addCallback(mZenModeControllerCallback);
+ if (!FooterViewRefactor.isEnabled()) {
+ mZenModeController.addCallback(mZenModeControllerCallback);
+ }
final int newBarState = mStatusBarStateController.getState();
if (newBarState != mBarState) {
mStateListener.onStateChanged(newBarState);
@@ -236,7 +239,9 @@
@Override
public void onViewDetachedFromWindow(View v) {
mConfigurationController.removeCallback(mConfigurationListener);
- mZenModeController.removeCallback(mZenModeControllerCallback);
+ if (!FooterViewRefactor.isEnabled()) {
+ mZenModeController.removeCallback(mZenModeControllerCallback);
+ }
mStatusBarStateController.removeCallback(mStateListener);
}
};
@@ -292,7 +297,9 @@
final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
@Override
public void onDensityOrFontScaleChanged() {
- updateShowEmptyShadeView();
+ if (!FooterViewRefactor.isEnabled()) {
+ updateShowEmptyShadeView();
+ }
mView.reinflateViews();
}
@@ -308,7 +315,9 @@
mView.updateBgColor();
mView.updateDecorViews();
mView.reinflateViews();
- updateShowEmptyShadeView();
+ if (!FooterViewRefactor.isEnabled()) {
+ updateShowEmptyShadeView();
+ }
updateFooter();
}
@@ -357,7 +366,9 @@
mView.updateSensitiveness(mStatusBarStateController.goingToFullShade(),
mLockscreenUserManager.isAnyProfilePublicMode());
mView.onStatePostChange(mStatusBarStateController.fromShadeLocked());
- updateImportantForAccessibility();
+ if (!FooterViewRefactor.isEnabled()) {
+ updateImportantForAccessibility();
+ }
}
};
@@ -459,7 +470,7 @@
@Override
public void onSnooze(StatusBarNotification sbn,
- NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
+ NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
mNotificationsController.setNotificationSnoozed(sbn, snoozeOption);
}
@@ -581,7 +592,7 @@
@Override
public boolean updateSwipeProgress(View animView, boolean dismissable,
- float swipeProgress) {
+ float swipeProgress) {
// Returning true prevents alpha fading.
return false;
}
@@ -673,6 +684,7 @@
UiEventLogger uiEventLogger,
NotificationRemoteInputManager remoteInputManager,
VisibilityLocationProviderDelegator visibilityLocationProviderDelegator,
+ ActiveNotificationsInteractor activeNotificationsInteractor,
SeenNotificationsInteractor seenNotificationsInteractor,
NotificationListViewBinder viewBinder,
ShadeController shadeController,
@@ -747,8 +759,10 @@
mView.setClearAllAnimationListener(this::onAnimationEnd);
mView.setClearAllListener((selection) -> mUiEventLogger.log(
NotificationPanelEvent.fromSelection(selection)));
- mView.setFooterClearAllListener(() ->
- mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES));
+ if (!FooterViewRefactor.isEnabled()) {
+ mView.setFooterClearAllListener(() ->
+ mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES));
+ }
mView.setIsRemoteInputActive(mRemoteInputManager.isRemoteInputActive());
mRemoteInputManager.addControllerCallback(new RemoteInputController.Callback() {
@Override
@@ -840,8 +854,10 @@
mViewBinder.bind(mView, this);
- collectFlow(mView, mKeyguardTransitionRepo.getTransitions(),
- this::onKeyguardTransitionChanged);
+ if (!FooterViewRefactor.isEnabled()) {
+ collectFlow(mView, mKeyguardTransitionRepo.getTransitions(),
+ this::onKeyguardTransitionChanged);
+ }
}
private boolean isInVisibleLocation(NotificationEntry entry) {
@@ -1031,6 +1047,8 @@
}
public int getVisibleNotificationCount() {
+ // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle footer
+ // visibility in the refactored code
return mNotifStats.getNumActiveNotifs();
}
@@ -1074,7 +1092,7 @@
}
public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
- boolean cancelAnimators) {
+ boolean cancelAnimators) {
mView.setOverScrollAmount(amount, onTop, animate, cancelAnimators);
}
@@ -1124,6 +1142,7 @@
}
public void setQsFullScreen(boolean fullScreen) {
+ FooterViewRefactor.assertInLegacyMode();
mView.setQsFullScreen(fullScreen);
updateShowEmptyShadeView();
}
@@ -1273,7 +1292,10 @@
public void updateVisibility(boolean visible) {
mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
- if (mView.getVisibility() == View.VISIBLE) {
+ // Refactor note: the empty shade's visibility doesn't seem to actually depend on the
+ // parent visibility (so this update seemingly doesn't do anything). Therefore, this is not
+ // modeled in the refactored code.
+ if (!FooterViewRefactor.isEnabled() && mView.getVisibility() == View.VISIBLE) {
// Synchronize EmptyShadeView visibility with the parent container.
updateShowEmptyShadeView();
updateImportantForAccessibility();
@@ -1288,6 +1310,8 @@
* are true.
*/
public void updateShowEmptyShadeView() {
+ FooterViewRefactor.assertInLegacyMode();
+
Trace.beginSection("NSSLC.updateShowEmptyShadeView");
final boolean shouldShow = getVisibleNotificationCount() == 0
@@ -1331,6 +1355,7 @@
* auto-scrolling in NSSL.
*/
public void updateImportantForAccessibility() {
+ FooterViewRefactor.assertInLegacyMode();
if (getVisibleNotificationCount() == 0 && mView.onKeyguard()) {
mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
} else {
@@ -1338,16 +1363,6 @@
}
}
- /**
- * @return true if {@link StatusBarStateController} is in transition to the KEYGUARD
- * and false otherwise.
- */
- private boolean isInTransitionToKeyguard() {
- final int currentState = mStatusBarStateController.getState();
- final int upcomingState = mStatusBarStateController.getCurrentOrUpcomingState();
- return (currentState != upcomingState && upcomingState == KEYGUARD);
- }
-
public boolean isShowingEmptyShadeView() {
return mView.isEmptyShadeViewVisible();
}
@@ -1395,10 +1410,14 @@
* Return whether there are any clearable notifications
*/
public boolean hasActiveClearableNotifications(@SelectedRows int selection) {
+ // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the footer
+ // visibility in the refactored code
return hasNotifications(selection, true /* clearable */);
}
public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) {
+ // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the footer
+ // visibility in the refactored code
boolean hasAlertingMatchingClearable = isClearable
? mNotifStats.getHasClearableAlertingNotifs()
: mNotifStats.getHasNonClearableAlertingNotifs();
@@ -1437,7 +1456,7 @@
public RemoteInputController.Delegate createDelegate() {
return new RemoteInputController.Delegate() {
public void setRemoteInputActive(NotificationEntry entry,
- boolean remoteInputActive) {
+ boolean remoteInputActive) {
mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
entry.notifyHeightChanged(true /* needsAnimation */);
updateFooter();
@@ -1556,7 +1575,7 @@
}
private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove,
- @SelectedRows int selectedRows) {
+ @SelectedRows int selectedRows) {
if (selectedRows == ROWS_ALL) {
mNotifCollection.dismissAllNotifications(
mLockscreenUserManager.getCurrentUserId());
@@ -1648,7 +1667,7 @@
* Set rounded rect clipping bounds on this view.
*/
public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius,
- int bottomRadius) {
+ int bottomRadius) {
mView.setRoundedClippingBounds(left, top, right, bottom, topRadius, bottomRadius);
}
@@ -1678,6 +1697,7 @@
@VisibleForTesting
void onKeyguardTransitionChanged(TransitionStep transitionStep) {
+ FooterViewRefactor.assertInLegacyMode();
boolean isTransitionToAod = transitionStep.getTo().equals(KeyguardState.AOD)
&& (transitionStep.getFrom().equals(KeyguardState.GONE)
|| transitionStep.getFrom().equals(KeyguardState.OCCLUDED));
@@ -2003,11 +2023,21 @@
private class NotifStackControllerImpl implements NotifStackController {
@Override
public void setNotifStats(@NonNull NotifStats notifStats) {
+ // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once footer visibility
+ // is handled in the refactored stack.
mNotifStats = notifStats;
- mView.setHasFilteredOutSeenNotifications(
- mSeenNotificationsInteractor.getHasFilteredOutSeenNotifications().getValue());
+
+ if (!FooterViewRefactor.isEnabled()) {
+ mView.setHasFilteredOutSeenNotifications(
+ mSeenNotificationsInteractor
+ .getHasFilteredOutSeenNotifications().getValue());
+ }
+
updateFooter();
- updateShowEmptyShadeView();
+
+ if (!FooterViewRefactor.isEnabled()) {
+ updateShowEmptyShadeView();
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index a5b87f0..4554085 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -17,9 +17,13 @@
package com.android.systemui.statusbar.notification.stack.ui.viewbinder
import android.view.LayoutInflater
+import androidx.lifecycle.lifecycleScope
import com.android.app.tracing.traceSection
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.nano.MetricsProto
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.common.ui.reinflateAndBindLatest
+import com.android.systemui.common.ui.view.setImportantForAccessibilityYesNo
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
@@ -36,12 +40,15 @@
import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.statusbar.policy.ConfigurationController
import javax.inject.Inject
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
/** Binds a [NotificationStackScrollLayout] to its [view model][NotificationListViewModel]. */
class NotificationListViewBinder
@Inject
constructor(
private val viewModel: NotificationListViewModel,
+ private val metricsLogger: MetricsLogger,
private val configuration: ConfigurationState,
private val configurationController: ConfigurationController,
private val falsingManager: FalsingManager,
@@ -56,7 +63,16 @@
) {
bindShelf(view)
bindFooter(view)
+ bindEmptyShade(view)
bindHideList(viewController, viewModel)
+
+ view.repeatWhenAttached {
+ lifecycleScope.launch {
+ viewModel.isImportantForAccessibility.collect { isImportantForAccessibility ->
+ view.setImportantForAccessibilityYesNo(isImportantForAccessibility)
+ }
+ }
+ }
}
private fun bindShelf(parentView: NotificationStackScrollLayout) {
@@ -87,7 +103,17 @@
attachToRoot = false,
) { footerView: FooterView ->
traceSection("bind FooterView") {
- val disposableHandle = FooterViewBinder.bind(footerView, footerViewModel)
+ val disposableHandle =
+ FooterViewBinder.bind(
+ footerView,
+ footerViewModel,
+ clearAllNotifications = {
+ metricsLogger.action(
+ MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES
+ )
+ parentView.clearAllNotifications()
+ },
+ )
parentView.setFooterView(footerView)
return@reinflateAndBindLatest disposableHandle
}
@@ -95,4 +121,26 @@
}
}
}
+
+ private fun bindEmptyShade(
+ parentView: NotificationStackScrollLayout,
+ ) {
+ parentView.repeatWhenAttached {
+ lifecycleScope.launch {
+ combine(
+ viewModel.shouldShowEmptyShadeView,
+ viewModel.areNotificationsHiddenInShade,
+ viewModel.hasFilteredOutSeenNotifications,
+ ::Triple
+ )
+ .collect { (shouldShow, areNotifsHidden, hasFilteredNotifs) ->
+ parentView.updateEmptyShadeView(
+ shouldShow,
+ areNotifsHidden,
+ hasFilteredNotifs,
+ )
+ }
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 4f76680..569ae24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -16,10 +16,22 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import java.util.Optional
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.onStart
/** ViewModel for the list of notifications. */
class NotificationListViewModel
@@ -27,5 +39,78 @@
constructor(
val shelf: NotificationShelfViewModel,
val hideListViewModel: HideListViewModel,
- val footer: Optional<FooterViewModel>
-)
+ val footer: Optional<FooterViewModel>,
+ activeNotificationsInteractor: ActiveNotificationsInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ seenNotificationsInteractor: SeenNotificationsInteractor,
+ shadeInteractor: ShadeInteractor,
+ zenModeInteractor: ZenModeInteractor,
+) {
+ /**
+ * We want the NSSL to be unimportant for accessibility when there are no notifications in it
+ * while the device is on lock screen, to avoid an unlabelled NSSL view in TalkBack. Otherwise,
+ * we want it to be important for accessibility to enable accessibility auto-scrolling in NSSL.
+ * See b/242235264 for more details.
+ */
+ val isImportantForAccessibility: Flow<Boolean> by lazy {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+ flowOf(true)
+ } else {
+ combine(
+ activeNotificationsInteractor.areAnyNotificationsPresent,
+ keyguardTransitionInteractor.isFinishedInStateWhere {
+ KeyguardState.lockscreenVisibleInState(it)
+ }
+ ) { hasNotifications, isOnKeyguard ->
+ hasNotifications || !isOnKeyguard
+ }
+ .distinctUntilChanged()
+ }
+ }
+
+ val shouldShowEmptyShadeView: Flow<Boolean> by lazy {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+ flowOf(false)
+ } else {
+ combine(
+ activeNotificationsInteractor.areAnyNotificationsPresent,
+ shadeInteractor.isQsFullscreen,
+ keyguardTransitionInteractor.isInTransitionToState(KeyguardState.AOD).onStart {
+ emit(false)
+ },
+ keyguardTransitionInteractor
+ .isFinishedInState(KeyguardState.PRIMARY_BOUNCER)
+ .onStart { emit(false) }
+ ) { hasNotifications, isQsFullScreen, transitioningToAOD, isBouncerShowing ->
+ !hasNotifications &&
+ !isQsFullScreen &&
+ // Hide empty shade view when in transition to AOD.
+ // That avoids "No Notifications" blinking when transitioning to AOD.
+ // For more details, see b/228790482.
+ !transitioningToAOD &&
+ // Don't show any notification content if the bouncer is showing. See
+ // b/267060171.
+ !isBouncerShowing
+ }
+ .distinctUntilChanged()
+ }
+ }
+
+ // TODO(b/308591475): This should be tracked separately by the empty shade.
+ val areNotificationsHiddenInShade: Flow<Boolean> by lazy {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+ flowOf(false)
+ } else {
+ zenModeInteractor.areNotificationsHiddenInShade
+ }
+ }
+
+ // TODO(b/308591475): This should be tracked separately by the empty shade.
+ val hasFilteredOutSeenNotifications: Flow<Boolean> by lazy {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+ flowOf(false)
+ } else {
+ seenNotificationsInteractor.hasFilteredOutSeenNotifications
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 146715d..13fb42c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -35,6 +35,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.power.data.repository.FakePowerRepository;
import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -70,6 +71,7 @@
@Mock protected KeyguardClockSwitch mKeyguardClockSwitch;
@Mock protected FrameLayout mMediaHostContainer;
+ @Mock protected KeyguardStatusAreaView mKeyguardStatusAreaView;
@Before
public void setup() {
@@ -109,6 +111,8 @@
when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch);
when(mKeyguardTransitionInteractor.getGoneToAodTransition()).thenReturn(emptyFlow());
+ when(mKeyguardStatusView.findViewById(R.id.keyguard_status_area))
+ .thenReturn(mKeyguardStatusAreaView);
}
protected void givenViewAttached() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 948942f..9c3288b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static junit.framework.Assert.assertEquals;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -27,6 +29,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.animation.AnimatorTestRule;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -40,6 +43,7 @@
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -51,6 +55,9 @@
@RunWith(AndroidTestingRunner.class)
public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControllerBaseTest {
+ @Rule
+ public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+
@Test
public void dozeTimeTick_updatesSlice() {
mController.dozeTimeTick();
@@ -230,4 +237,34 @@
throw new RuntimeException(e);
}
}
+
+ @Test
+ public void statusAreaHeightChange_animatesHeightOutputChange() {
+ // Init & Capture Layout Listener
+ mController.onInit();
+ mController.onViewAttached();
+
+ when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+ ArgumentCaptor<View.OnLayoutChangeListener> captor =
+ ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
+ verify(mKeyguardStatusAreaView).addOnLayoutChangeListener(captor.capture());
+ View.OnLayoutChangeListener listener = captor.getValue();
+
+ // Setup and validate initial height
+ when(mKeyguardStatusView.getHeight()).thenReturn(200);
+ when(mKeyguardClockSwitchController.getNotificationIconAreaHeight()).thenReturn(10);
+ assertEquals(190, mController.getLockscreenHeight());
+
+ // Trigger Change and validate value unchanged immediately
+ when(mKeyguardStatusAreaView.getHeight()).thenReturn(100);
+ when(mKeyguardStatusView.getHeight()).thenReturn(300); // Include child height
+ listener.onLayoutChange(mKeyguardStatusAreaView,
+ /* new layout */ 100, 300, 200, 400,
+ /* old layout */ 100, 300, 200, 300);
+ assertEquals(190, mController.getLockscreenHeight());
+
+ // Complete animation, validate height increased
+ mAnimatorTestRule.advanceTimeBy(200);
+ assertEquals(290, mController.getLockscreenHeight());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 11c5d3b..602f3dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -475,6 +475,22 @@
}
@Test
+ public void testOnAuthenticationFailedInvoked_whenBiometricReEnrollRequired() {
+ showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
+ final int modality = BiometricAuthenticator.TYPE_FACE;
+ mAuthController.onBiometricError(modality,
+ BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL,
+ 0 /* vendorCode */);
+
+ verify(mDialog1).onAuthenticationFailed(mModalityCaptor.capture(),
+ mMessageCaptor.capture());
+
+ assertThat(mModalityCaptor.getValue()).isEqualTo(modality);
+ assertThat(mMessageCaptor.getValue()).isEqualTo(mContext.getString(
+ R.string.face_recalibrate_notification_content));
+ }
+
+ @Test
public void testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected_withPaused() {
testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected(
BiometricConstants.BIOMETRIC_PAUSED_REJECTED);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 5f0d4d4..f5b6f14 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -36,13 +36,13 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
@@ -107,7 +107,6 @@
@Mock private lateinit var udfpsView: UdfpsView
@Mock private lateinit var mUdfpsKeyguardViewLegacy: UdfpsKeyguardViewLegacy
@Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
- @Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
@@ -123,47 +122,52 @@
@Before
fun setup() {
whenever(inflater.inflate(R.layout.udfps_view, null, false))
- .thenReturn(udfpsView)
+ .thenReturn(udfpsView)
whenever(inflater.inflate(R.layout.udfps_bp_view, null))
- .thenReturn(mock(UdfpsBpView::class.java))
+ .thenReturn(mock(UdfpsBpView::class.java))
whenever(inflater.inflate(R.layout.udfps_keyguard_view_legacy, null))
- .thenReturn(mUdfpsKeyguardViewLegacy)
+ .thenReturn(mUdfpsKeyguardViewLegacy)
whenever(inflater.inflate(R.layout.udfps_fpm_empty_view, null))
- .thenReturn(mock(UdfpsFpmEmptyView::class.java))
+ .thenReturn(mock(UdfpsFpmEmptyView::class.java))
}
private fun withReason(
- @ShowReason reason: Int,
- isDebuggable: Boolean = false,
- block: () -> Unit
+ @ShowReason reason: Int,
+ isDebuggable: Boolean = false,
+ enableDeviceEntryUdfpsRefactor: Boolean = false,
+ block: () -> Unit,
) {
+ if (enableDeviceEntryUdfpsRefactor) {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+ } else {
+ mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+ }
controllerOverlay = UdfpsControllerOverlay(
- context,
- inflater,
- windowManager,
- accessibilityManager,
- statusBarStateController,
- statusBarKeyguardViewManager,
- keyguardUpdateMonitor,
- dialogManager,
- dumpManager,
- transitionController,
- configurationController,
- keyguardStateController,
- unlockedScreenOffAnimationController,
- udfpsDisplayMode,
- REQUEST_ID,
- reason,
- controllerCallback,
- onTouch,
- activityLaunchAnimator,
- featureFlags,
- primaryBouncerInteractor,
- alternateBouncerInteractor,
- isDebuggable,
- udfpsKeyguardAccessibilityDelegate,
- keyguardTransitionInteractor,
- mSelectedUserInteractor,
+ context,
+ inflater,
+ windowManager,
+ accessibilityManager,
+ statusBarStateController,
+ statusBarKeyguardViewManager,
+ keyguardUpdateMonitor,
+ dialogManager,
+ dumpManager,
+ transitionController,
+ configurationController,
+ keyguardStateController,
+ unlockedScreenOffAnimationController,
+ udfpsDisplayMode,
+ REQUEST_ID,
+ reason,
+ controllerCallback,
+ onTouch,
+ activityLaunchAnimator,
+ primaryBouncerInteractor,
+ alternateBouncerInteractor,
+ isDebuggable,
+ udfpsKeyguardAccessibilityDelegate,
+ keyguardTransitionInteractor,
+ mSelectedUserInteractor,
)
block()
}
@@ -185,12 +189,12 @@
val sensorBounds = Rect(0, 0, SENSOR_WIDTH, SENSOR_HEIGHT)
val overlayBounds = Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)
overlayParams = UdfpsOverlayParams(
- sensorBounds,
- overlayBounds,
- DISPLAY_WIDTH,
- DISPLAY_HEIGHT,
- scaleFactor = 1f,
- rotation
+ sensorBounds,
+ overlayBounds,
+ DISPLAY_WIDTH,
+ DISPLAY_HEIGHT,
+ scaleFactor = 1f,
+ rotation
)
block()
}
@@ -200,8 +204,8 @@
withReason(REASON_AUTH_BP) {
controllerOverlay.show(udfpsController, overlayParams)
verify(windowManager).addView(
- eq(controllerOverlay.overlayView),
- layoutParamsCaptor.capture()
+ eq(controllerOverlay.getTouchOverlay()),
+ layoutParamsCaptor.capture()
)
// ROTATION_0 is the native orientation. Sensor should stay in the top left corner.
@@ -218,8 +222,8 @@
withReason(REASON_AUTH_BP) {
controllerOverlay.show(udfpsController, overlayParams)
verify(windowManager).addView(
- eq(controllerOverlay.overlayView),
- layoutParamsCaptor.capture()
+ eq(controllerOverlay.getTouchOverlay()),
+ layoutParamsCaptor.capture()
)
// ROTATION_180 is not supported. Sensor should stay in the top left corner.
@@ -236,8 +240,8 @@
withReason(REASON_AUTH_BP) {
controllerOverlay.show(udfpsController, overlayParams)
verify(windowManager).addView(
- eq(controllerOverlay.overlayView),
- layoutParamsCaptor.capture()
+ eq(controllerOverlay.getTouchOverlay()),
+ layoutParamsCaptor.capture()
)
// Sensor should be in the bottom left corner in ROTATION_90.
@@ -254,8 +258,8 @@
withReason(REASON_AUTH_BP) {
controllerOverlay.show(udfpsController, overlayParams)
verify(windowManager).addView(
- eq(controllerOverlay.overlayView),
- layoutParamsCaptor.capture()
+ eq(controllerOverlay.getTouchOverlay()),
+ layoutParamsCaptor.capture()
)
// Sensor should be in the top right corner in ROTATION_270.
@@ -270,7 +274,7 @@
private fun showUdfpsOverlay() {
val didShow = controllerOverlay.show(udfpsController, overlayParams)
- verify(windowManager).addView(eq(controllerOverlay.overlayView), any())
+ verify(windowManager).addView(eq(controllerOverlay.getTouchOverlay()), any())
verify(udfpsView).setUdfpsDisplayModeProvider(eq(udfpsDisplayMode))
verify(udfpsView).animationViewController = any()
verify(udfpsView).addView(any())
@@ -278,7 +282,7 @@
assertThat(didShow).isTrue()
assertThat(controllerOverlay.isShowing).isTrue()
assertThat(controllerOverlay.isHiding).isFalse()
- assertThat(controllerOverlay.overlayView).isNotNull()
+ assertThat(controllerOverlay.getTouchOverlay()).isNotNull()
}
@Test
@@ -295,14 +299,14 @@
private fun hideUdfpsOverlay() {
val didShow = controllerOverlay.show(udfpsController, overlayParams)
- val view = controllerOverlay.overlayView
+ val view = controllerOverlay.getTouchOverlay()
val didHide = controllerOverlay.hide()
verify(windowManager).removeView(eq(view))
assertThat(didShow).isTrue()
assertThat(didHide).isTrue()
- assertThat(controllerOverlay.overlayView).isNull()
+ assertThat(controllerOverlay.getTouchOverlay()).isNull()
assertThat(controllerOverlay.animationViewController).isNull()
assertThat(controllerOverlay.isShowing).isFalse()
assertThat(controllerOverlay.isHiding).isTrue()
@@ -348,8 +352,8 @@
controllerOverlay.show(udfpsController, overlayParams)
verify(windowManager).addView(
- eq(controllerOverlay.overlayView),
- layoutParamsCaptor.capture()
+ eq(controllerOverlay.getTouchOverlay()),
+ layoutParamsCaptor.capture()
)
// Layout params should use sensor bounds
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 c8c400d..e2cab29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -73,6 +73,7 @@
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
@@ -286,6 +287,7 @@
// Create a fake background executor.
mBiometricExecutor = new FakeExecutor(new FakeSystemClock());
+ mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
initUdfpsController(mOpticalProps);
}
@@ -304,7 +306,6 @@
mStatusBarKeyguardViewManager,
mDumpManager,
mKeyguardUpdateMonitor,
- mFeatureFlags,
mFalsingManager,
mPowerManager,
mAccessibilityManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
new file mode 100644
index 0000000..395d712
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.bouncer.ui.helper
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SIDE_BY_SIDE
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STACKED
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD
+import com.google.common.truth.Truth.assertThat
+import java.util.Locale
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@SmallTest
+@RunWith(Parameterized::class)
+class BouncerSceneLayoutTest : SysuiTestCase() {
+
+ data object Phone :
+ Device(
+ name = "phone",
+ width = SizeClass.COMPACT,
+ height = SizeClass.EXPANDED,
+ naturallyHeld = Vertically,
+ )
+ data object Tablet :
+ Device(
+ name = "tablet",
+ width = SizeClass.EXPANDED,
+ height = SizeClass.MEDIUM,
+ naturallyHeld = Horizontally,
+ )
+ data object Folded :
+ Device(
+ name = "folded",
+ width = SizeClass.COMPACT,
+ height = SizeClass.MEDIUM,
+ naturallyHeld = Vertically,
+ )
+ data object Unfolded :
+ Device(
+ name = "unfolded",
+ width = SizeClass.EXPANDED,
+ height = SizeClass.MEDIUM,
+ naturallyHeld = Vertically,
+ widthWhenUnnaturallyHeld = SizeClass.MEDIUM,
+ heightWhenUnnaturallyHeld = SizeClass.MEDIUM,
+ )
+ data object TallerFolded :
+ Device(
+ name = "taller folded",
+ width = SizeClass.COMPACT,
+ height = SizeClass.EXPANDED,
+ naturallyHeld = Vertically,
+ )
+ data object TallerUnfolded :
+ Device(
+ name = "taller unfolded",
+ width = SizeClass.EXPANDED,
+ height = SizeClass.EXPANDED,
+ naturallyHeld = Vertically,
+ )
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun testCases() =
+ listOf(
+ Phone to
+ Expected(
+ whenNaturallyHeld = STANDARD,
+ whenUnnaturallyHeld = SPLIT,
+ ),
+ Tablet to
+ Expected(
+ whenNaturallyHeld = SIDE_BY_SIDE,
+ whenUnnaturallyHeld = STACKED,
+ ),
+ Folded to
+ Expected(
+ whenNaturallyHeld = STANDARD,
+ whenUnnaturallyHeld = SPLIT,
+ ),
+ Unfolded to
+ Expected(
+ whenNaturallyHeld = SIDE_BY_SIDE,
+ whenUnnaturallyHeld = STANDARD,
+ ),
+ TallerFolded to
+ Expected(
+ whenNaturallyHeld = STANDARD,
+ whenUnnaturallyHeld = SPLIT,
+ ),
+ TallerUnfolded to
+ Expected(
+ whenNaturallyHeld = SIDE_BY_SIDE,
+ whenUnnaturallyHeld = SIDE_BY_SIDE,
+ ),
+ )
+ .flatMap { (device, expected) ->
+ buildList {
+ // Holding the device in its natural orientation (vertical or horizontal):
+ add(
+ TestCase(
+ device = device,
+ held = device.naturallyHeld,
+ expected = expected.layout(heldNaturally = true),
+ )
+ )
+
+ if (expected.whenNaturallyHeld == SIDE_BY_SIDE) {
+ add(
+ TestCase(
+ device = device,
+ held = device.naturallyHeld,
+ isSideBySideSupported = false,
+ expected = STANDARD,
+ )
+ )
+ }
+
+ // Holding the device the other way:
+ add(
+ TestCase(
+ device = device,
+ held = device.naturallyHeld.flip(),
+ expected = expected.layout(heldNaturally = false),
+ )
+ )
+
+ if (expected.whenUnnaturallyHeld == SIDE_BY_SIDE) {
+ add(
+ TestCase(
+ device = device,
+ held = device.naturallyHeld.flip(),
+ isSideBySideSupported = false,
+ expected = STANDARD,
+ )
+ )
+ }
+ }
+ }
+ }
+
+ @Parameterized.Parameter @JvmField var testCase: TestCase? = null
+
+ @Test
+ fun calculateLayout() {
+ testCase?.let { nonNullTestCase ->
+ with(nonNullTestCase) {
+ assertThat(
+ calculateLayoutInternal(
+ width = device.width(whenHeld = held),
+ height = device.height(whenHeld = held),
+ isSideBySideSupported = isSideBySideSupported,
+ )
+ )
+ .isEqualTo(expected)
+ }
+ }
+ }
+
+ data class TestCase(
+ val device: Device,
+ val held: Held,
+ val expected: BouncerSceneLayout,
+ val isSideBySideSupported: Boolean = true,
+ ) {
+ override fun toString(): String {
+ return buildString {
+ append(device.name)
+ append(" width: ${device.width(held).name.lowercase(Locale.US)}")
+ append(" height: ${device.height(held).name.lowercase(Locale.US)}")
+ append(" when held $held")
+ if (!isSideBySideSupported) {
+ append(" (side-by-side not supported)")
+ }
+ }
+ }
+ }
+
+ data class Expected(
+ val whenNaturallyHeld: BouncerSceneLayout,
+ val whenUnnaturallyHeld: BouncerSceneLayout,
+ ) {
+ fun layout(heldNaturally: Boolean): BouncerSceneLayout {
+ return if (heldNaturally) {
+ whenNaturallyHeld
+ } else {
+ whenUnnaturallyHeld
+ }
+ }
+ }
+
+ sealed class Device(
+ val name: String,
+ private val width: SizeClass,
+ private val height: SizeClass,
+ val naturallyHeld: Held,
+ private val widthWhenUnnaturallyHeld: SizeClass = height,
+ private val heightWhenUnnaturallyHeld: SizeClass = width,
+ ) {
+ fun width(whenHeld: Held): SizeClass {
+ return if (isHeldNaturally(whenHeld)) {
+ width
+ } else {
+ widthWhenUnnaturallyHeld
+ }
+ }
+
+ fun height(whenHeld: Held): SizeClass {
+ return if (isHeldNaturally(whenHeld)) {
+ height
+ } else {
+ heightWhenUnnaturallyHeld
+ }
+ }
+
+ private fun isHeldNaturally(whenHeld: Held): Boolean {
+ return whenHeld == naturallyHeld
+ }
+ }
+
+ sealed class Held {
+ abstract fun flip(): Held
+ }
+ data object Vertically : Held() {
+ override fun flip(): Held {
+ return Horizontally
+ }
+ }
+ data object Horizontally : Held() {
+ override fun flip(): Held {
+ return Vertically
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
new file mode 100644
index 0000000..db9e548
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
@@ -0,0 +1,86 @@
+package com.android.systemui.qs
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import android.view.KeyEvent
+import android.view.View
+import android.widget.Scroller
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class PagedTileLayoutTest : SysuiTestCase() {
+
+ @Mock private lateinit var pageIndicator: PageIndicator
+ @Captor private lateinit var captor: ArgumentCaptor<View.OnKeyListener>
+
+ private lateinit var pageTileLayout: TestPagedTileLayout
+ private lateinit var scroller: Scroller
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ pageTileLayout = TestPagedTileLayout(mContext)
+ pageTileLayout.setPageIndicator(pageIndicator)
+ verify(pageIndicator).setOnKeyListener(captor.capture())
+ setViewWidth(pageTileLayout, width = PAGE_WIDTH)
+ scroller = pageTileLayout.mScroller
+ }
+
+ private fun setViewWidth(view: View, width: Int) {
+ view.left = 0
+ view.right = width
+ }
+
+ @Test
+ fun scrollsRight_afterRightArrowPressed_whenFocusOnPagerIndicator() {
+ pageTileLayout.currentPageIndex = 0
+
+ sendUpEvent(KeyEvent.KEYCODE_DPAD_RIGHT)
+
+ assertThat(scroller.isFinished).isFalse() // aka we're scrolling
+ assertThat(scroller.finalX).isEqualTo(scroller.currX + PAGE_WIDTH)
+ }
+
+ @Test
+ fun scrollsLeft_afterLeftArrowPressed_whenFocusOnPagerIndicator() {
+ pageTileLayout.currentPageIndex = 1 // we won't scroll left if we're on the first page
+
+ sendUpEvent(KeyEvent.KEYCODE_DPAD_LEFT)
+
+ assertThat(scroller.isFinished).isFalse() // aka we're scrolling
+ assertThat(scroller.finalX).isEqualTo(scroller.currX - PAGE_WIDTH)
+ }
+
+ private fun sendUpEvent(keyCode: Int) {
+ val event = KeyEvent(KeyEvent.ACTION_UP, keyCode)
+ captor.value.onKey(pageIndicator, keyCode, event)
+ }
+
+ /**
+ * Custom PagedTileLayout to easy mock "currentItem" i.e. currently visible page. Setting this
+ * up otherwise would require setting adapter etc
+ */
+ class TestPagedTileLayout(context: Context) : PagedTileLayout(context, null) {
+
+ var currentPageIndex: Int = 0
+
+ override fun getCurrentItem(): Int {
+ return currentPageIndex
+ }
+ }
+
+ companion object {
+ const val PAGE_WIDTH = 200
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 5c325ae..42e27ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -19,7 +19,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
@@ -39,14 +38,11 @@
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class QuickSettingsSceneViewModelTest : SysuiTestCase() {
@@ -90,15 +86,8 @@
broadcastDispatcher = fakeBroadcastDispatcher,
)
- val authenticationInteractor = utils.authenticationInteractor()
-
underTest =
QuickSettingsSceneViewModel(
- deviceEntryInteractor =
- utils.deviceEntryInteractor(
- authenticationInteractor = authenticationInteractor,
- sceneInteractor = sceneInteractor,
- ),
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
notifications = utils.notificationsPlaceholderViewModel(),
@@ -106,32 +95,6 @@
}
@Test
- fun onContentClicked_deviceUnlocked_switchesToGone() =
- testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.desiredScene)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(true)
- runCurrent()
-
- underTest.onContentClicked()
-
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
- }
-
- @Test
- fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
- testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.desiredScene)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(false)
- runCurrent()
-
- underTest.onContentClicked()
-
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
- }
-
- @Test
fun destinationsNotCustomizing() =
testScope.runTest {
val destinations by collectLastValue(underTest.destinationScenes)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index ba8a666..03878b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -147,6 +147,7 @@
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger;
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -335,6 +336,7 @@
@Mock private JavaAdapter mJavaAdapter;
@Mock private CastController mCastController;
@Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
+ @Mock private ActiveNotificationsInteractor mActiveNotificationsInteractor;
@Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm;
@Mock private NaturalScrollingSettingObserver mNaturalScrollingSettingObserver;
@@ -709,6 +711,7 @@
mKeyguardInteractor,
mActivityStarter,
mSharedNotificationContainerInteractor,
+ mActiveNotificationsInteractor,
mKeyguardViewConfigurator,
mKeyguardFaceAuthInteractor,
new ResourcesSplitShadeStateController(),
@@ -783,6 +786,7 @@
mKeyguardFaceAuthInteractor,
mShadeRepository,
mShadeInteractor,
+ mActiveNotificationsInteractor,
mJavaAdapter,
mCastController,
new ResourcesSplitShadeStateController()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 26b84e3..bff47f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -80,6 +80,8 @@
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
@@ -178,6 +180,8 @@
protected SysuiStatusBarStateController mStatusBarStateController;
protected ShadeInteractor mShadeInteractor;
+ protected ActiveNotificationsInteractor mActiveNotificationsInteractor;
+
protected Handler mMainHandler;
protected LockscreenShadeTransitionController.Callback mLockscreenShadeTransitionCallback;
@@ -290,6 +294,9 @@
)
);
+ mActiveNotificationsInteractor =
+ new ActiveNotificationsInteractor(new ActiveNotificationListRepository());
+
KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
keyguardStatusView.setId(R.id.keyguard_status_view);
@@ -362,6 +369,7 @@
mock(KeyguardFaceAuthInteractor.class),
mShadeRepository,
mShadeInteractor,
+ mActiveNotificationsInteractor,
new JavaAdapter(mTestScope.getBackgroundScope()),
mCastController,
splitShadeStateController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index 428574b..fa5fad0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
@@ -57,6 +58,7 @@
@Mock private lateinit var groupExpansionManagerImpl: GroupExpansionManagerImpl
@Mock private lateinit var notificationIconAreaController: NotificationIconAreaController
@Mock private lateinit var renderListInteractor: RenderNotificationListInteractor
+ @Mock private lateinit var activeNotificationsInteractor: ActiveNotificationsInteractor
@Mock private lateinit var stackController: NotifStackController
@Mock private lateinit var section: NotifSection
@@ -75,6 +77,7 @@
groupExpansionManagerImpl,
notificationIconAreaController,
renderListInteractor,
+ activeNotificationsInteractor,
)
coordinator.attach(pipeline)
afterRenderListListener = withArgCaptor {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
new file mode 100644
index 0000000..4ab3cd4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysUITestComponent
+import com.android.systemui.SysUITestModule
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.collectLastValue
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.runCurrent
+import com.android.systemui.runTest
+import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import org.junit.Test
+
+@SmallTest
+class ActiveNotificationsInteractorTest : SysuiTestCase() {
+
+ @Component(modules = [SysUITestModule::class])
+ @SysUISingleton
+ interface TestComponent : SysUITestComponent<ActiveNotificationsInteractor> {
+ val activeNotificationListRepository: ActiveNotificationListRepository
+
+ @Component.Factory
+ interface Factory {
+ fun create(@BindsInstance test: SysuiTestCase): TestComponent
+ }
+ }
+
+ private val testComponent: TestComponent =
+ DaggerActiveNotificationsInteractorTest_TestComponent.factory().create(test = this)
+
+ @Test
+ fun testAreAnyNotificationsPresent_isTrue() =
+ testComponent.runTest {
+ val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent)
+
+ activeNotificationListRepository.setActiveNotifs(2)
+ runCurrent()
+
+ assertThat(areAnyNotificationsPresent).isTrue()
+ assertThat(underTest.areAnyNotificationsPresentValue).isTrue()
+ }
+
+ @Test
+ fun testAreAnyNotificationsPresent_isFalse() =
+ testComponent.runTest {
+ val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent)
+
+ activeNotificationListRepository.setActiveNotifs(0)
+ runCurrent()
+
+ assertThat(areAnyNotificationsPresent).isFalse()
+ assertThat(underTest.areAnyNotificationsPresentValue).isFalse()
+ }
+
+ @Test
+ fun testHasClearableNotifications_whenHasClearableAlertingNotifs() =
+ testComponent.runTest {
+ val hasClearable by collectLastValue(underTest.hasClearableNotifications)
+
+ activeNotificationListRepository.notifStats.value =
+ NotifStats(
+ numActiveNotifs = 2,
+ hasNonClearableAlertingNotifs = false,
+ hasClearableAlertingNotifs = true,
+ hasNonClearableSilentNotifs = false,
+ hasClearableSilentNotifs = false,
+ )
+ runCurrent()
+
+ assertThat(hasClearable).isTrue()
+ }
+
+ @Test
+ fun testHasClearableNotifications_whenHasClearableSilentNotifs() =
+ testComponent.runTest {
+ val hasClearable by collectLastValue(underTest.hasClearableNotifications)
+
+ activeNotificationListRepository.notifStats.value =
+ NotifStats(
+ numActiveNotifs = 2,
+ hasNonClearableAlertingNotifs = false,
+ hasClearableAlertingNotifs = false,
+ hasNonClearableSilentNotifs = false,
+ hasClearableSilentNotifs = true,
+ )
+ runCurrent()
+
+ assertThat(hasClearable).isTrue()
+ }
+
+ @Test
+ fun testHasClearableNotifications_whenHasNoClearableNotifs() =
+ testComponent.runTest {
+ val hasClearable by collectLastValue(underTest.hasClearableNotifications)
+
+ activeNotificationListRepository.notifStats.value =
+ NotifStats(
+ numActiveNotifs = 2,
+ hasNonClearableAlertingNotifs = false,
+ hasClearableAlertingNotifs = false,
+ hasNonClearableSilentNotifs = false,
+ hasClearableSilentNotifs = false,
+ )
+ runCurrent()
+
+ assertThat(hasClearable).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index a64ac67..22c5bae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -114,9 +114,46 @@
}
@Test
+ public void testSetClearAllButtonText_resourceOnlyFetchedOnce() {
+ int resId = R.string.clear_all_notifications_text;
+ mView.setClearAllButtonText(resId);
+ verify(mSpyContext).getString(eq(resId));
+
+ clearInvocations(mSpyContext);
+
+ assertThat(((TextView) mView.findViewById(R.id.dismiss_text))
+ .getText().toString()).contains("Clear all");
+
+ // Set it a few more times, it shouldn't lead to the resource being fetched again
+ mView.setClearAllButtonText(resId);
+ mView.setClearAllButtonText(resId);
+
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
+ public void testSetClearAllButtonDescription_resourceOnlyFetchedOnce() {
+ int resId = R.string.accessibility_clear_all;
+ mView.setClearAllButtonDescription(resId);
+ verify(mSpyContext).getString(eq(resId));
+
+ clearInvocations(mSpyContext);
+
+ assertThat(((TextView) mView.findViewById(R.id.dismiss_text))
+ .getContentDescription().toString()).contains("Clear all notifications");
+
+ // Set it a few more times, it shouldn't lead to the resource being fetched again
+ mView.setClearAllButtonDescription(resId);
+ mView.setClearAllButtonDescription(resId);
+
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
public void testSetMessageString_resourceOnlyFetchedOnce() {
- mView.setMessageString(R.string.unlock_to_see_notif_text);
- verify(mSpyContext).getString(eq(R.string.unlock_to_see_notif_text));
+ int resId = R.string.unlock_to_see_notif_text;
+ mView.setMessageString(resId);
+ verify(mSpyContext).getString(eq(resId));
clearInvocations(mSpyContext);
@@ -124,22 +161,23 @@
.getText().toString()).contains("Unlock");
// Set it a few more times, it shouldn't lead to the resource being fetched again
- mView.setMessageString(R.string.unlock_to_see_notif_text);
- mView.setMessageString(R.string.unlock_to_see_notif_text);
+ mView.setMessageString(resId);
+ mView.setMessageString(resId);
verify(mSpyContext, never()).getString(anyInt());
}
@Test
public void testSetMessageIcon_resourceOnlyFetchedOnce() {
- mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
- verify(mSpyContext).getDrawable(eq(R.drawable.ic_friction_lock_closed));
+ int resId = R.drawable.ic_friction_lock_closed;
+ mView.setMessageIcon(resId);
+ verify(mSpyContext).getDrawable(eq(resId));
clearInvocations(mSpyContext);
// Set it a few more times, it shouldn't lead to the resource being fetched again
- mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
- mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
+ mView.setMessageIcon(resId);
+ mView.setMessageIcon(resId);
verify(mSpyContext, never()).getDrawable(anyInt());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 57a7c3c..94dcf7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -18,37 +18,222 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysUITestComponent
+import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.TestMocksModule
+import com.android.systemui.collectLastValue
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.runCurrent
+import com.android.systemui.runTest
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.notification.collection.render.NotifStats
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
+import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.value
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
+import dagger.BindsInstance
+import dagger.Component
+import java.util.Optional
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
class FooterViewModelTest : SysuiTestCase() {
- private val repository = ActiveNotificationListRepository()
- private val interactor = SeenNotificationsInteractor(repository)
- private val underTest = FooterViewModel(interactor)
+ private lateinit var footerViewModel: FooterViewModel
- @Test
- fun testMessageVisible_whenFilteredNotifications() = runTest {
- val message by collectLastValue(underTest.message)
+ @SysUISingleton
+ @Component(
+ modules =
+ [
+ SysUITestModule::class,
+ ActivatableNotificationViewModelModule::class,
+ FooterViewModelModule::class,
+ HeadlessSystemUserModeModule::class,
+ ]
+ )
+ interface TestComponent : SysUITestComponent<Optional<FooterViewModel>> {
+ val activeNotificationListRepository: ActiveNotificationListRepository
+ val configurationRepository: FakeConfigurationRepository
+ val keyguardRepository: FakeKeyguardRepository
+ val keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ val shadeRepository: FakeShadeRepository
+ val powerRepository: FakePowerRepository
- repository.hasFilteredOutSeenNotifications.value = true
+ @Component.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance test: SysuiTestCase,
+ featureFlags: FakeFeatureFlagsClassicModule,
+ mocks: TestMocksModule,
+ ): TestComponent
+ }
+ }
- assertThat(message?.visible).isTrue()
+ private val dozeParameters: DozeParameters = mock()
+
+ private val testComponent: TestComponent =
+ DaggerFooterViewModelTest_TestComponent.factory()
+ .create(
+ test = this,
+ featureFlags =
+ FakeFeatureFlagsClassicModule {
+ set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true)
+ },
+ mocks =
+ TestMocksModule(
+ dozeParameters = dozeParameters,
+ )
+ )
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR)
+
+ // The underTest in the component is Optional, because that matches the provider we
+ // currently have for the footer view model.
+ footerViewModel = testComponent.underTest.get()
}
@Test
- fun testMessageVisible_whenNoFilteredNotifications() = runTest {
- val message by collectLastValue(underTest.message)
+ fun testMessageVisible_whenFilteredNotifications() =
+ testComponent.runTest {
+ val message by collectLastValue(footerViewModel.message)
- repository.hasFilteredOutSeenNotifications.value = false
+ activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
- assertThat(message?.visible).isFalse()
- }
+ assertThat(message?.visible).isTrue()
+ }
+
+ @Test
+ fun testMessageVisible_whenNoFilteredNotifications() =
+ testComponent.runTest {
+ val message by collectLastValue(footerViewModel.message)
+
+ activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
+
+ assertThat(message?.visible).isFalse()
+ }
+
+ @Test
+ fun testClearAllButtonVisible_whenHasClearableNotifs() =
+ testComponent.runTest {
+ val button by collectLastValue(footerViewModel.clearAllButton)
+
+ activeNotificationListRepository.notifStats.value =
+ NotifStats(
+ numActiveNotifs = 2,
+ hasNonClearableAlertingNotifs = false,
+ hasClearableAlertingNotifs = true,
+ hasNonClearableSilentNotifs = false,
+ hasClearableSilentNotifs = true,
+ )
+ runCurrent()
+
+ assertThat(button?.isVisible?.value).isTrue()
+ }
+
+ @Test
+ fun testClearAllButtonVisible_whenHasNoClearableNotifs() =
+ testComponent.runTest {
+ val button by collectLastValue(footerViewModel.clearAllButton)
+
+ activeNotificationListRepository.notifStats.value =
+ NotifStats(
+ numActiveNotifs = 2,
+ hasNonClearableAlertingNotifs = false,
+ hasClearableAlertingNotifs = false,
+ hasNonClearableSilentNotifs = false,
+ hasClearableSilentNotifs = false,
+ )
+ runCurrent()
+
+ assertThat(button?.isVisible?.value).isFalse()
+ }
+
+ @Test
+ fun testClearAllButtonAnimating_whenShadeExpandedAndTouchable() =
+ testComponent.runTest {
+ val button by collectLastValue(footerViewModel.clearAllButton)
+ runCurrent()
+
+ // WHEN shade is expanded
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ shadeRepository.setLegacyShadeExpansion(1f)
+ // AND QS not expanded
+ shadeRepository.setQsExpansion(0f)
+ // AND device is awake
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.AWAKE,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
+ runCurrent()
+
+ // AND there are clearable notifications
+ activeNotificationListRepository.notifStats.value =
+ NotifStats(
+ numActiveNotifs = 2,
+ hasNonClearableAlertingNotifs = false,
+ hasClearableAlertingNotifs = true,
+ hasNonClearableSilentNotifs = false,
+ hasClearableSilentNotifs = true,
+ )
+ runCurrent()
+
+ // THEN button visibility should animate
+ assertThat(button?.isVisible?.isAnimating).isTrue()
+ }
+
+ @Test
+ fun testClearAllButtonAnimating_whenShadeNotExpanded() =
+ testComponent.runTest {
+ val button by collectLastValue(footerViewModel.clearAllButton)
+ runCurrent()
+
+ // WHEN shade is collapsed
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ shadeRepository.setLegacyShadeExpansion(0f)
+ // AND QS not expanded
+ shadeRepository.setQsExpansion(0f)
+ // AND device is awake
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.AWAKE,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
+ runCurrent()
+
+ // AND there are clearable notifications
+ activeNotificationListRepository.notifStats.value =
+ NotifStats(
+ numActiveNotifs = 2,
+ hasNonClearableAlertingNotifs = false,
+ hasClearableAlertingNotifs = true,
+ hasNonClearableSilentNotifs = false,
+ hasClearableSilentNotifs = true,
+ )
+ runCurrent()
+
+ // THEN button visibility should not animate
+ assertThat(button?.isVisible?.isAnimating).isFalse()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
index 0341035..360a373 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -26,10 +26,10 @@
import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
import com.android.systemui.runTest
import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
-import com.android.systemui.statusbar.notification.shared.activeNotificationModel
import com.android.systemui.statusbar.notification.shared.byIsAmbient
import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply
import com.android.systemui.statusbar.notification.shared.byIsPulsing
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index c2a1519..e264fc0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -43,10 +43,10 @@
import com.android.systemui.runCurrent
import com.android.systemui.runTest
import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository
-import com.android.systemui.statusbar.notification.shared.activeNotificationModel
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher
import com.android.systemui.statusbar.phone.data.repository.FakeDarkIconRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 3e331a6..0a9bac9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -999,11 +999,25 @@
assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isFalse();
}
+ @Test
+ public void shouldNotBubbleUp_suspended() {
+ assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createSuspendedBubble()))
+ .isFalse();
+ }
+
+ private NotificationEntry createSuspendedBubble() {
+ return createBubble(null, null, true);
+ }
+
private NotificationEntry createBubble() {
- return createBubble(null, null);
+ return createBubble(null, null, false);
}
private NotificationEntry createBubble(String groupKey, Integer groupAlert) {
+ return createBubble(groupKey, groupAlert, false);
+ }
+
+ private NotificationEntry createBubble(String groupKey, Integer groupAlert, Boolean suspended) {
Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder(
PendingIntent.getActivity(mContext, 0,
new Intent().setPackage(mContext.getPackageName()),
@@ -1031,6 +1045,7 @@
.setNotification(n)
.setImportance(IMPORTANCE_HIGH)
.setCanBubble(true)
+ .setSuspended(suspended)
.build();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
index 1c7fd56..7361f6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
@@ -18,7 +18,6 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE
@@ -37,7 +36,7 @@
@RunWith(AndroidTestingRunner::class)
class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecisionProviderTestBase() {
init {
- setFlagsRule.disableFlags(Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR)
+ mSetFlagsRule.disableFlags(VisualInterruptionRefactor.FLAG_NAME)
}
override val provider by lazy {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index df6f0d7..d2c046c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -18,7 +18,6 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
@@ -30,7 +29,7 @@
@RunWith(AndroidTestingRunner::class)
class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionProviderTestBase() {
init {
- setFlagsRule.enableFlags(Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR)
+ mSetFlagsRule.enableFlags(VisualInterruptionRefactor.FLAG_NAME)
}
override val provider by lazy {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index a3b7e8c..2ac0cb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -44,7 +44,6 @@
import android.hardware.display.FakeAmbientDisplayConfiguration
import android.os.Looper
import android.os.PowerManager
-import android.platform.test.flag.junit.SetFlagsRule
import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
import android.provider.Settings.Global.HEADS_UP_OFF
import android.provider.Settings.Global.HEADS_UP_ON
@@ -84,15 +83,10 @@
import junit.framework.Assert.assertTrue
import org.junit.Assert.assertEquals
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito.`when` as whenever
abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
- @JvmField
- @Rule
- val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
-
private val fakeLogBuffer =
LogBuffer(
name = "FakeLog",
@@ -601,6 +595,13 @@
}
@Test
+ fun testShouldNotBubble_bubbleAppSuspended() {
+ ensureBubbleState()
+ assertShouldNotBubble(buildBubbleEntry { packageSuspended = true })
+ assertNoEventsLogged()
+ }
+
+ @Test
fun testShouldNotFsi_noFullScreenIntent() {
forEachFsiState {
assertShouldNotFsi(buildFsiEntry { hasFsi = false })
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
index ca105f3..16c5c8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
@@ -15,7 +15,6 @@
package com.android.systemui.statusbar.notification.shared
-import android.graphics.drawable.Icon
import com.google.common.truth.Correspondence
val byKey: Correspondence<ActiveNotificationModel, String> =
@@ -38,30 +37,3 @@
)
val byIsPulsing: Correspondence<ActiveNotificationModel, Boolean> =
Correspondence.transforming({ it?.isPulsing }, "has an isPulsing value of")
-
-fun activeNotificationModel(
- key: String,
- groupKey: String? = null,
- isAmbient: Boolean = false,
- isRowDismissed: Boolean = false,
- isSilent: Boolean = false,
- isLastMessageFromReply: Boolean = false,
- isSuppressedFromStatusBar: Boolean = false,
- isPulsing: Boolean = false,
- aodIcon: Icon? = null,
- shelfIcon: Icon? = null,
- statusBarIcon: Icon? = null,
-) =
- ActiveNotificationModel(
- key = key,
- groupKey = groupKey,
- isAmbient = isAmbient,
- isRowDismissed = isRowDismissed,
- isSilent = isSilent,
- isLastMessageFromReply = isLastMessageFromReply,
- isSuppressedFromStatusBar = isSuppressedFromStatusBar,
- isPulsing = isPulsing,
- aodIcon = aodIcon,
- shelfIcon = shelfIcon,
- statusBarIcon = statusBarIcon,
- )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 5903890..ff5c026 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -36,7 +36,6 @@
import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-import android.content.res.Resources;
import android.metrics.LogMaker;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -53,7 +52,6 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
@@ -74,7 +72,6 @@
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
@@ -84,17 +81,15 @@
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -170,8 +165,14 @@
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
+ private final ActiveNotificationListRepository mActiveNotificationsRepository =
+ new ActiveNotificationListRepository();
+
+ private final ActiveNotificationsInteractor mActiveNotificationsInteractor =
+ new ActiveNotificationsInteractor(mActiveNotificationsRepository);
+
private final SeenNotificationsInteractor mSeenNotificationsInteractor =
- new SeenNotificationsInteractor(new ActiveNotificationListRepository());
+ new SeenNotificationsInteractor(mActiveNotificationsRepository);
private NotificationStackScrollLayoutController mController;
@@ -701,6 +702,7 @@
mUiEventLogger,
mRemoteInputManager,
mVisibilityLocationProviderDelegator,
+ mActiveNotificationsInteractor,
mSeenNotificationsInteractor,
mViewBinder,
mShadeController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
index 46e8453..ac20683 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
@@ -34,9 +34,11 @@
import com.android.systemui.unfold.TestUnfoldTransitionProvider
import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractorImpl
-import com.android.systemui.util.animation.FakeAnimationStatusRepository
+import com.android.systemui.util.animation.data.repository.FakeAnimationStatusRepository
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import java.time.Duration
+import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.test.TestScope
@@ -47,8 +49,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.MockitoAnnotations
-import java.time.Duration
-import java.util.Optional
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
new file mode 100644
index 0000000..f00abc9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+
+import android.app.NotificationManager.Policy
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysUITestComponent
+import com.android.systemui.SysUITestModule
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.TestMocksModule
+import com.android.systemui.collectLastValue
+import com.android.systemui.common.domain.CommonDomainLayerModule
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.res.R
+import com.android.systemui.runCurrent
+import com.android.systemui.runTest
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
+import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.statusbar.policy.data.repository.FakeZenModeRepository
+import com.android.systemui.unfold.UnfoldTransitionModule
+import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationListViewModelTest : SysuiTestCase() {
+
+ @SysUISingleton
+ @Component(
+ modules =
+ [
+ SysUITestModule::class,
+ ActivatableNotificationViewModelModule::class,
+ CommonDomainLayerModule::class,
+ FooterViewModelModule::class,
+ HeadlessSystemUserModeModule::class,
+ UnfoldTransitionModule.Bindings::class,
+ ]
+ )
+ interface TestComponent : SysUITestComponent<NotificationListViewModel> {
+ val activeNotificationListRepository: ActiveNotificationListRepository
+ val keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ val shadeRepository: FakeShadeRepository
+ val zenModeRepository: FakeZenModeRepository
+ val configurationController: FakeConfigurationController
+
+ @Component.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance test: SysuiTestCase,
+ featureFlags: FakeFeatureFlagsClassicModule,
+ mocks: TestMocksModule,
+ ): TestComponent
+ }
+ }
+
+ private val testComponent: TestComponent =
+ DaggerNotificationListViewModelTest_TestComponent.factory()
+ .create(
+ test = this,
+ featureFlags =
+ FakeFeatureFlagsClassicModule {
+ set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true)
+ },
+ mocks = TestMocksModule()
+ )
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR)
+ }
+
+ @Test
+ fun testIsImportantForAccessibility_falseWhenNoNotifs() =
+ testComponent.runTest {
+ val important by collectLastValue(underTest.isImportantForAccessibility)
+
+ // WHEN on lockscreen
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+ // AND has no notifs
+ activeNotificationListRepository.setActiveNotifs(count = 0)
+ testScope.runCurrent()
+
+ // THEN not important
+ assertThat(important).isFalse()
+ }
+
+ @Test
+ fun testIsImportantForAccessibility_trueWhenNotifs() =
+ testComponent.runTest {
+ val important by collectLastValue(underTest.isImportantForAccessibility)
+
+ // WHEN on lockscreen
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+ // AND has notifs
+ activeNotificationListRepository.setActiveNotifs(count = 2)
+ runCurrent()
+
+ // THEN is important
+ assertThat(important).isTrue()
+ }
+
+ @Test
+ fun testIsImportantForAccessibility_trueWhenNotKeyguard() =
+ testComponent.runTest {
+ val important by collectLastValue(underTest.isImportantForAccessibility)
+
+ // WHEN not on lockscreen
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ // AND has no notifs
+ activeNotificationListRepository.setActiveNotifs(count = 0)
+ runCurrent()
+
+ // THEN is still important
+ assertThat(important).isTrue()
+ }
+
+ @Test
+ fun testShouldShowEmptyShadeView_trueWhenNoNotifs() =
+ testComponent.runTest {
+ val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
+
+ // WHEN has no notifs
+ activeNotificationListRepository.setActiveNotifs(count = 0)
+ runCurrent()
+
+ // THEN should show
+ assertThat(shouldShow).isTrue()
+ }
+
+ @Test
+ fun testShouldShowEmptyShadeView_falseWhenNotifs() =
+ testComponent.runTest {
+ val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
+
+ // WHEN has notifs
+ activeNotificationListRepository.setActiveNotifs(count = 2)
+ runCurrent()
+
+ // THEN should not show
+ assertThat(shouldShow).isFalse()
+ }
+
+ @Test
+ fun testShouldShowEmptyShadeView_falseWhenQsExpandedDefault() =
+ testComponent.runTest {
+ val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
+
+ // WHEN has no notifs
+ activeNotificationListRepository.setActiveNotifs(count = 0)
+ // AND quick settings are expanded
+ shadeRepository.legacyQsFullscreen.value = true
+ runCurrent()
+
+ // THEN should not show
+ assertThat(shouldShow).isFalse()
+ }
+
+ @Test
+ fun testShouldShowEmptyShadeView_trueWhenQsExpandedInSplitShade() =
+ testComponent.runTest {
+ val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
+
+ // WHEN has no notifs
+ activeNotificationListRepository.setActiveNotifs(count = 0)
+ // AND quick settings are expanded
+ shadeRepository.setQsExpansion(1f)
+ // AND split shade is enabled
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ configurationController.notifyConfigurationChanged()
+ runCurrent()
+
+ // THEN should show
+ assertThat(shouldShow).isTrue()
+ }
+
+ @Test
+ fun testShouldShowEmptyShadeView_falseWhenTransitioningToAOD() =
+ testComponent.runTest {
+ val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
+
+ // WHEN has no notifs
+ activeNotificationListRepository.setActiveNotifs(count = 0)
+ // AND transitioning to AOD
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 0f,
+ )
+ )
+ runCurrent()
+
+ // THEN should not show
+ assertThat(shouldShow).isFalse()
+ }
+
+ @Test
+ fun testShouldShowEmptyShadeView_falseWhenBouncerShowing() =
+ testComponent.runTest {
+ val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
+
+ // WHEN has no notifs
+ activeNotificationListRepository.setActiveNotifs(count = 0)
+ // AND is on bouncer
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ testScope,
+ )
+ runCurrent()
+
+ // THEN should not show
+ assertThat(shouldShow).isFalse()
+ }
+
+ @Test
+ fun testAreNotificationsHiddenInShade_true() =
+ testComponent.runTest {
+ val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
+
+ zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
+ zenModeRepository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+ runCurrent()
+
+ assertThat(hidden).isTrue()
+ }
+
+ @Test
+ fun testAreNotificationsHiddenInShade_false() =
+ testComponent.runTest {
+ val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
+
+ zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
+ zenModeRepository.zenMode.value = Settings.Global.ZEN_MODE_OFF
+ runCurrent()
+
+ assertThat(hidden).isFalse()
+ }
+
+ @Test
+ fun testHasFilteredOutSeenNotifications_true() =
+ testComponent.runTest {
+ val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+ activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
+ runCurrent()
+
+ assertThat(hasFilteredNotifs).isTrue()
+ }
+
+ @Test
+ fun testHasFilteredOutSeenNotifications_false() =
+ testComponent.runTest {
+ val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+ activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
+ runCurrent()
+
+ assertThat(hasFilteredNotifs).isFalse()
+ }
+}
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 4b1c7e8..4422764 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
@@ -157,6 +157,7 @@
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper;
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -347,6 +348,8 @@
mFeatureFlags.set(Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD, true);
when(mDozeParameters.getAlwaysOn()).thenReturn(true);
mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
+ // TODO: b/312476335 - Update to check flag and instantiate old or new implementation.
+ mSetFlagsRule.disableFlags(VisualInterruptionRefactor.FLAG_NAME);
IThermalService thermalService = mock(IThermalService.class);
mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 99e62ee..dbb1062 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -128,8 +128,7 @@
testComponent.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
- repository.consolidatedNotificationPolicy.value =
- policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
+ repository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
repository.zenMode.value = Settings.Global.ZEN_MODE_OFF
runCurrent()
@@ -141,8 +140,7 @@
testComponent.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
- repository.consolidatedNotificationPolicy.value =
- policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_STATUS_BAR)
+ repository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_STATUS_BAR)
repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
runCurrent()
@@ -154,19 +152,10 @@
testComponent.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
- repository.consolidatedNotificationPolicy.value =
- policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
+ repository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
runCurrent()
assertThat(hidden).isTrue()
}
}
-
-fun policyWithSuppressedVisualEffects(suppressedVisualEffects: Int) =
- Policy(
- /* priorityCategories = */ 0,
- /* priorityCallSenders = */ 0,
- /* priorityMessageSenders = */ 0,
- /* suppressedVisualEffects = */ suppressedVisualEffects
- )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index df7609c..200cfd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -146,6 +146,7 @@
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
@@ -366,6 +367,9 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ // TODO: b/312476335 - Update to check flag and instantiate old or new implementation.
+ mSetFlagsRule.disableFlags(VisualInterruptionRefactor.FLAG_NAME);
+
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
doReturn(true).when(mTransitions).isRegistered();
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/android/app/ActivityManagerKosmos.kt
similarity index 82%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/android/app/ActivityManagerKosmos.kt
index a780763..f9c920a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/app/ActivityManagerKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package android.app
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.activityManager by Kosmos.Fixture { mock<ActivityManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/android/app/admin/DevicePolicyManagerKosmos.kt
similarity index 80%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/android/app/admin/DevicePolicyManagerKosmos.kt
index a780763..b284ac0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/app/admin/DevicePolicyManagerKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package android.app.admin
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.devicePolicyManager by Kosmos.Fixture { mock<DevicePolicyManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
similarity index 69%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
index a780763..f96c508 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package android.content
+import com.android.systemui.SysuiTestableContext
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.testableContext: SysuiTestableContext by Kosmos.Fixture { testCase.context }
+var Kosmos.applicationContext: Context by Kosmos.Fixture { testableContext }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/res/ResourcesKosmos.kt
similarity index 80%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/android/content/res/ResourcesKosmos.kt
index a780763..5686764 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/content/res/ResourcesKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package android.content.res
+import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.mainResources: Resources by Kosmos.Fixture { applicationContext.resources }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/android/os/UserManagerKosmos.kt
similarity index 83%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/android/os/UserManagerKosmos.kt
index a780763..c936b91 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/os/UserManagerKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package android.os
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.userManager by Kosmos.Fixture { mock<UserManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/android/view/LayoutInflaterKosmos.kt
similarity index 79%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/android/view/LayoutInflaterKosmos.kt
index a780763..34c0a79 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/view/LayoutInflaterKosmos.kt
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package android.view
+import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.layoutInflater: LayoutInflater by
+ Kosmos.Fixture { LayoutInflater.from(applicationContext) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.kt
similarity index 72%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.kt
index a780763..9059da2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.kt
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.internal.logging
+import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.uiEventLogger: UiEventLogger by Kosmos.Fixture { uiEventLoggerFake }
+val Kosmos.uiEventLoggerFake by Kosmos.Fixture { UiEventLoggerFake() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardSecurityModelKosmos.kt
similarity index 80%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardSecurityModelKosmos.kt
index a780763..fadcecc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardSecurityModelKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.keyguard
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.keyguardSecurityModel by Kosmos.Fixture { mock<KeyguardSecurityModel>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardUpdateMonitorKosmos.kt
similarity index 80%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardUpdateMonitorKosmos.kt
index a780763..b32cbe6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardUpdateMonitorKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.keyguard
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.keyguardUpdateMonitor by Kosmos.Fixture { mock<KeyguardUpdateMonitor>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/GuestResetOrExitSessionReceiverKosmos.kt
similarity index 77%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/GuestResetOrExitSessionReceiverKosmos.kt
index a780763..4c4cfd5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/GuestResetOrExitSessionReceiverKosmos.kt
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.guestResetOrExitSessionReceiver by
+ Kosmos.Fixture { mock<GuestResetOrExitSessionReceiver>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/GuestResumeSessionReceiverKosmos.kt
similarity index 79%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/GuestResumeSessionReceiverKosmos.kt
index a780763..a9855ff 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/GuestResumeSessionReceiverKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.guestResumeSessionReceiver by Kosmos.Fixture { mock<GuestResumeSessionReceiver>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
index d0c1267..3724291 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui
+import android.content.ContentResolver
import android.content.Context
import android.content.res.Resources
import android.testing.TestableContext
@@ -80,6 +81,9 @@
test.fakeBroadcastDispatcher
@Provides
+ fun provideContentResolver(context: Context): ContentResolver = context.contentResolver
+
+ @Provides
fun provideBaseShadeInteractor(
sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 37a4f61..f57ace9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -55,7 +55,9 @@
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.ZenModeController
import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.mock
import com.android.wm.shell.bubbles.Bubbles
import dagger.Binds
@@ -100,6 +102,10 @@
@get:Provides val keyguardViewController: KeyguardViewController = mock(),
@get:Provides val dialogLaunchAnimator: DialogLaunchAnimator = mock(),
@get:Provides val sysuiState: SysUiState = mock(),
+ @get:Provides
+ val unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider> =
+ Optional.empty(),
+ @get:Provides val zenModeController: ZenModeController = mock(),
// log buffers
@get:[Provides BroadcastDispatcherLog]
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryKosmos.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryKosmos.kt
index a780763..ea93e94 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryKosmos.kt
@@ -14,8 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.authentication.data.repository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import kotlinx.coroutines.test.currentTime
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.authenticationRepository: AuthenticationRepository by
+ Kosmos.Fixture { fakeAuthenticationRepository }
+val Kosmos.fakeAuthenticationRepository by
+ Kosmos.Fixture { FakeAuthenticationRepository { testScope.currentTime } }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
new file mode 100644
index 0000000..060ca4c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.authentication.domain.interactor
+
+import com.android.systemui.authentication.data.repository.authenticationRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.util.time.fakeSystemClock
+
+val Kosmos.authenticationInteractor by
+ Kosmos.Fixture {
+ AuthenticationInteractor(
+ applicationScope = applicationCoroutineScope,
+ repository = authenticationRepository,
+ backgroundDispatcher = testDispatcher,
+ userRepository = userRepository,
+ clock = fakeSystemClock,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryKosmos.kt
similarity index 70%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryKosmos.kt
index a780763..2a87074 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryKosmos.kt
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.bouncer.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.keyguardBouncerRepository: KeyguardBouncerRepository by
+ Kosmos.Fixture { fakeKeyguardBouncerRepository }
+val Kosmos.fakeKeyguardBouncerRepository by Kosmos.Fixture { FakeKeyguardBouncerRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingCollectorKosmos.kt
similarity index 85%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingCollectorKosmos.kt
index a780763..3a72d11 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingCollectorKosmos.kt
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.classifier
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.falsingCollector by Kosmos.Fixture { FalsingCollectorFake() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/ConfigurationStateKosmos.kt
similarity index 65%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/ConfigurationStateKosmos.kt
index a780763..86a8ae5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/ConfigurationStateKosmos.kt
@@ -14,8 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.common.ui
+import android.content.applicationContext
+import android.view.layoutInflater
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.policy.configurationController
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.configurationState: ConfigurationState by
+ Kosmos.Fixture {
+ ConfigurationState(configurationController, applicationContext, layoutInflater)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryKosmos.kt
similarity index 71%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryKosmos.kt
index a780763..77b8bd4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryKosmos.kt
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.common.ui.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.configurationRepository: ConfigurationRepository by
+ Kosmos.Fixture { fakeConfigurationRepository }
+val Kosmos.fakeConfigurationRepository by Kosmos.Fixture { FakeConfigurationRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
index b27926c..faacce6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
@@ -65,6 +65,7 @@
widgetRepository,
mediaRepository,
smartspaceRepository,
+ withDeps.keyguardInteractor,
appWidgetHost,
editWidgetsActivityStarter,
),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt
index 36f0882..8c653a5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt
@@ -26,12 +26,14 @@
import com.android.systemui.statusbar.data.FakeStatusBarDataLayerModule
import com.android.systemui.telephony.data.FakeTelephonyDataLayerModule
import com.android.systemui.user.data.FakeUserDataLayerModule
+import com.android.systemui.util.animation.data.FakeAnimationUtilDataLayerModule
import dagger.Module
@Module(
includes =
[
FakeAccessibilityDataLayerModule::class,
+ FakeAnimationUtilDataLayerModule::class,
FakeAuthenticationDataLayerModule::class,
FakeBouncerDataLayerModule::class,
FakeCommonDataLayerModule::class,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryKosmos.kt
similarity index 72%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryKosmos.kt
index a780763..3da0681 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryKosmos.kt
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.deviceentry.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.deviceEntryRepository: DeviceEntryRepository by
+ Kosmos.Fixture { fakeDeviceEntryRepository }
+val Kosmos.fakeDeviceEntryRepository by Kosmos.Fixture { FakeDeviceEntryRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
new file mode 100644
index 0000000..b600b50
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
+import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.trustRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.deviceEntryInteractor by
+ Kosmos.Fixture {
+ DeviceEntryInteractor(
+ applicationScope = applicationCoroutineScope,
+ repository = deviceEntryRepository,
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ deviceEntryFaceAuthRepository = deviceEntryFaceAuthRepository,
+ trustRepository = trustRepository,
+ flags = sceneContainerFlags,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
similarity index 89%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
index a780763..e6b7f62 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
@@ -18,4 +18,4 @@
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.featureFlagsClassic by Kosmos.Fixture { FakeFeatureFlagsClassic() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryKosmos.kt
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryKosmos.kt
index a780763..3d72967 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryKosmos.kt
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.keyguard.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository by
+ Kosmos.Fixture { fakeDeviceEntryFaceAuthRepository }
+val Kosmos.fakeDeviceEntryFaceAuthRepository by
+ Kosmos.Fixture { FakeDeviceEntryFaceAuthRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/InWindowLauncherUnlockAnimationRepositoryKosmos.kt
similarity index 78%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/InWindowLauncherUnlockAnimationRepositoryKosmos.kt
index a780763..b0e4ba0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/InWindowLauncherUnlockAnimationRepositoryKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.keyguard.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.inWindowLauncherUnlockAnimationRepository by
+ Kosmos.Fixture { InWindowLauncherUnlockAnimationRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryKosmos.kt
similarity index 73%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryKosmos.kt
index a780763..453fef5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.keyguard.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.keyguardRepository: KeyguardRepository by Kosmos.Fixture { fakeKeyguardRepository }
+val Kosmos.fakeKeyguardRepository by Kosmos.Fixture { FakeKeyguardRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryKosmos.kt
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryKosmos.kt
index a780763..c900ac9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryKosmos.kt
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.keyguard.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.keyguardSurfaceBehindRepository: KeyguardSurfaceBehindRepository by
+ Kosmos.Fixture { fakeKeyguardSurfaceBehindRepository }
+val Kosmos.fakeKeyguardSurfaceBehindRepository by
+ Kosmos.Fixture { FakeKeyguardSurfaceBehindRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
similarity index 69%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
index a780763..008f79a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.keyguard.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.keyguardTransitionRepository: KeyguardTransitionRepository by
+ Kosmos.Fixture { fakeKeyguardTransitionRepository }
+val Kosmos.fakeKeyguardTransitionRepository by Kosmos.Fixture { FakeKeyguardTransitionRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/TrustRepositoryKosmos.kt
similarity index 75%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/TrustRepositoryKosmos.kt
index a780763..ca87acf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/TrustRepositoryKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.keyguard.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.trustRepository: TrustRepository by Kosmos.Fixture { fakeTrustRepository }
+val Kosmos.fakeTrustRepository by Kosmos.Fixture { FakeTrustRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..b03d0b8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.keyguard.domain.interactor
+
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.shade.data.repository.shadeRepository
+import dagger.Lazy
+
+val Kosmos.fromLockscreenTransitionInteractor by
+ Kosmos.Fixture {
+ FromLockscreenTransitionInteractor(
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ keyguardInteractor = keyguardInteractor,
+ flags = featureFlagsClassic,
+ shadeRepository = shadeRepository,
+ powerInteractor = powerInteractor,
+ inWindowLauncherUnlockAnimationInteractor =
+ Lazy { inWindowLauncherUnlockAnimationInteractor },
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..ade3e1a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.keyguard.domain.interactor
+
+import com.android.keyguard.keyguardSecurityModel
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+
+val Kosmos.fromPrimaryBouncerTransitionInteractor by
+ Kosmos.Fixture {
+ FromPrimaryBouncerTransitionInteractor(
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ keyguardInteractor = keyguardInteractor,
+ flags = featureFlagsClassic,
+ keyguardSecurityModel = keyguardSecurityModel,
+ selectedUserInteractor = selectedUserInteractor,
+ powerInteractor = powerInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorKosmos.kt
new file mode 100644
index 0000000..dbbb203
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.inWindowLauncherUnlockAnimationRepository
+import com.android.systemui.keyguard.data.repository.keyguardSurfaceBehindRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.shared.system.activityManagerWrapper
+import dagger.Lazy
+
+val Kosmos.inWindowLauncherUnlockAnimationInteractor by
+ Kosmos.Fixture {
+ InWindowLauncherUnlockAnimationInteractor(
+ repository = inWindowLauncherUnlockAnimationRepository,
+ scope = applicationCoroutineScope,
+ transitionInteractor = keyguardTransitionInteractor,
+ surfaceBehindRepository = Lazy { keyguardSurfaceBehindRepository },
+ activityManager = activityManagerWrapper,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
new file mode 100644
index 0000000..4843ae7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.keyguard.domain.interactor
+
+import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.statusbar.commandQueue
+
+val Kosmos.keyguardInteractor by
+ Kosmos.Fixture {
+ KeyguardInteractor(
+ repository = keyguardRepository,
+ commandQueue = commandQueue,
+ powerInteractor = powerInteractor,
+ featureFlags = featureFlagsClassic,
+ sceneContainerFlags = sceneContainerFlags,
+ bouncerRepository = keyguardBouncerRepository,
+ configurationRepository = configurationRepository,
+ shadeRepository = shadeRepository,
+ sceneInteractorProvider = { sceneInteractor },
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..e4d115e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import dagger.Lazy
+
+val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by
+ Kosmos.Fixture {
+ KeyguardTransitionInteractor(
+ scope = applicationCoroutineScope,
+ repository = keyguardTransitionRepository,
+ keyguardInteractor = Lazy { keyguardInteractor },
+ fromLockscreenTransitionInteractor = Lazy { fromLockscreenTransitionInteractor },
+ fromPrimaryBouncerTransitionInteractor =
+ Lazy { fromPrimaryBouncerTransitionInteractor },
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index b05915c..0b13858 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -1,16 +1,11 @@
package com.android.systemui.kosmos
-import android.content.Context
-import android.os.UserManager
+import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos.Fixture
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
-import org.mockito.Mockito
var Kosmos.testDispatcher by Fixture { StandardTestDispatcher() }
var Kosmos.testScope by Fixture { TestScope(testDispatcher) }
-var Kosmos.context by Fixture<Context>()
-var Kosmos.lifecycleScope by Fixture<CoroutineScope>()
-
-val Kosmos.userManager by Fixture { Mockito.mock(UserManager::class.java) }
+var Kosmos.applicationCoroutineScope by Fixture { testScope.backgroundScope }
+var Kosmos.testCase: SysuiTestCase by Fixture()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt
similarity index 80%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt
index a780763..0ec8d49 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.plugins
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.activityStarter by Kosmos.Fixture { mock<ActivityStarter>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
similarity index 77%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index a780763..cac2646 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.plugins.statusbar
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.statusBarStateController by Kosmos.Fixture { mock<StatusBarStateController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/PowerRepositoryKosmos.kt
similarity index 75%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/PowerRepositoryKosmos.kt
index a780763..c924579 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/PowerRepositoryKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.power.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.powerRepository: PowerRepository by Kosmos.Fixture { fakePowerRepository }
+val Kosmos.fakePowerRepository by Kosmos.Fixture { FakePowerRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.kt
new file mode 100644
index 0000000..8486691
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.power.domain.interactor
+
+import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.power.data.repository.powerRepository
+import com.android.systemui.statusbar.phone.screenOffAnimationController
+
+val Kosmos.powerInteractor by
+ Kosmos.Fixture {
+ PowerInteractor(
+ repository = powerRepository,
+ falsingCollector = falsingCollector,
+ screenOffAnimationController = screenOffAnimationController,
+ statusBarStateController = statusBarStateController,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt
similarity index 67%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt
index a780763..7c4e160 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.scene.data.repository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.scene.shared.model.sceneContainerConfig
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.sceneContainerRepository by
+ Kosmos.Fixture { SceneContainerRepository(applicationCoroutineScope, sceneContainerConfig) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
new file mode 100644
index 0000000..9989876
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.scene.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.shared.logger.sceneLogger
+
+val Kosmos.sceneInteractor by
+ Kosmos.Fixture {
+ SceneInteractor(
+ applicationScope = applicationCoroutineScope,
+ repository = sceneContainerRepository,
+ powerInteractor = powerInteractor,
+ logger = sceneLogger,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
similarity index 83%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
index a780763..a3ceef0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.scene.shared.flag
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.sceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/logger/SceneLoggerKosmos.kt
similarity index 80%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/logger/SceneLoggerKosmos.kt
index a780763..c5f24f4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/logger/SceneLoggerKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.scene.shared.logger
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.sceneLogger by Kosmos.Fixture { mock<SceneLogger>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt
similarity index 80%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt
index a780763..f9cdc1b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.scene.shared.model
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.sceneContainerConfig by
+ Kosmos.Fixture { FakeSceneContainerConfigModule().sceneContainerConfig }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeRepositoryKosmos.kt
similarity index 75%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeRepositoryKosmos.kt
index a780763..38cedbc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeRepositoryKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.shade.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.shadeRepository: ShadeRepository by Kosmos.Fixture { fakeShadeRepository }
+val Kosmos.fakeShadeRepository by Kosmos.Fixture { FakeShadeRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
new file mode 100644
index 0000000..7da57f0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.shade.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.shade.ShadeModule
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
+import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
+import com.android.systemui.statusbar.phone.dozeParameters
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.userSetupRepository
+import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
+import com.android.systemui.user.domain.interactor.userSwitcherInteractor
+
+var Kosmos.baseShadeInteractor: BaseShadeInteractor by
+ Kosmos.Fixture {
+ ShadeModule.provideBaseShadeInteractor(
+ sceneContainerFlags = sceneContainerFlags,
+ sceneContainerOn = { shadeInteractorSceneContainerImpl },
+ sceneContainerOff = { shadeInteractorLegacyImpl },
+ )
+ }
+val Kosmos.shadeInteractorSceneContainerImpl by
+ Kosmos.Fixture {
+ ShadeInteractorSceneContainerImpl(
+ scope = applicationCoroutineScope,
+ sceneInteractor = sceneInteractor,
+ sharedNotificationContainerInteractor = sharedNotificationContainerInteractor,
+ )
+ }
+val Kosmos.shadeInteractorLegacyImpl by
+ Kosmos.Fixture {
+ ShadeInteractorLegacyImpl(
+ scope = applicationCoroutineScope,
+ keyguardRepository = keyguardRepository,
+ sharedNotificationContainerInteractor = sharedNotificationContainerInteractor,
+ repository = shadeRepository
+ )
+ }
+var Kosmos.shadeInteractor: ShadeInteractor by Kosmos.Fixture { shadeInteractorImpl }
+val Kosmos.shadeInteractorImpl by
+ Kosmos.Fixture {
+ ShadeInteractorImpl(
+ scope = applicationCoroutineScope,
+ deviceProvisioningRepository = deviceProvisioningRepository,
+ disableFlagsRepository = disableFlagsRepository,
+ dozeParams = dozeParameters,
+ keyguardRepository = fakeKeyguardRepository,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
+ powerInteractor = powerInteractor,
+ userSetupRepository = userSetupRepository,
+ userSwitcherInteractor = userSwitcherInteractor,
+ baseShadeInteractor = baseShadeInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/ActivityManagerWrapperKosmos.kt
similarity index 78%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/ActivityManagerWrapperKosmos.kt
index a780763..e753593 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/ActivityManagerWrapperKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.shared.system
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.activityManagerWrapper by Kosmos.Fixture { mock<ActivityManagerWrapper>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt
similarity index 80%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt
index a780763..27f7f68 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.statusbar
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.commandQueue by Kosmos.Fixture { mock<CommandQueue>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepositoryKosmos.kt
similarity index 78%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepositoryKosmos.kt
index a780763..10151ac 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepositoryKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.statusbar.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.notificationListenerSettingsRepository by
+ Kosmos.Fixture { NotificationListenerSettingsRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryKosmos.kt
similarity index 70%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryKosmos.kt
index a780763..a373a8e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryKosmos.kt
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.statusbar.disableflags.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.disableFlagsRepository: DisableFlagsRepository by
+ Kosmos.Fixture { fakeDisableFlagsRepository }
+val Kosmos.fakeDisableFlagsRepository by Kosmos.Fixture { FakeDisableFlagsRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
new file mode 100644
index 0000000..9851b0e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.data.model
+
+import android.graphics.drawable.Icon
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+
+/** Simple ActiveNotificationModel builder for use in tests. */
+fun activeNotificationModel(
+ key: String,
+ groupKey: String? = null,
+ isAmbient: Boolean = false,
+ isRowDismissed: Boolean = false,
+ isSilent: Boolean = false,
+ isLastMessageFromReply: Boolean = false,
+ isSuppressedFromStatusBar: Boolean = false,
+ isPulsing: Boolean = false,
+ aodIcon: Icon? = null,
+ shelfIcon: Icon? = null,
+ statusBarIcon: Icon? = null,
+) =
+ ActiveNotificationModel(
+ key = key,
+ groupKey = groupKey,
+ isAmbient = isAmbient,
+ isRowDismissed = isRowDismissed,
+ isSilent = isSilent,
+ isLastMessageFromReply = isLastMessageFromReply,
+ isSuppressedFromStatusBar = isSuppressedFromStatusBar,
+ isPulsing = isPulsing,
+ aodIcon = aodIcon,
+ shelfIcon = shelfIcon,
+ statusBarIcon = statusBarIcon,
+ )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt
new file mode 100644
index 0000000..cb1ba20
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.data.repository
+
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+
+/**
+ * Make the repository hold [count] active notifications for testing. The keys of the notifications
+ * are "0", "1", ..., (count - 1).toString().
+ */
+fun ActiveNotificationListRepository.setActiveNotifs(count: Int) {
+ this.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply { repeat(count) { i -> addEntry(activeNotificationModel(key = i.toString())) } }
+ .build()
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryKosmos.kt
similarity index 79%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryKosmos.kt
index a780763..5507d6c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryKosmos.kt
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.statusbar.notification.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.activeNotificationListRepository by Kosmos.Fixture { ActiveNotificationListRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepositoryKosmos.kt
similarity index 76%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepositoryKosmos.kt
index a780763..ed62fda 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepositoryKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.statusbar.notification.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.headsUpNotificationIconViewStateRepository by
+ Kosmos.Fixture { HeadsUpNotificationIconViewStateRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt
similarity index 64%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt
index a780763..f2b9da4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.statusbar.notification.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.notificationsKeyguardViewStateRepository: NotificationsKeyguardViewStateRepository by
+ Kosmos.Fixture { fakeNotificationsKeyguardViewStateRepository }
+val Kosmos.fakeNotificationsKeyguardViewStateRepository by
+ Kosmos.Fixture { FakeNotificationsKeyguardViewStateRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorKosmos.kt
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorKosmos.kt
index a780763..3d7fb6d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorKosmos.kt
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.statusbar.notification.domain.interactor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.activeNotificationsInteractor by
+ Kosmos.Fixture { ActiveNotificationsInteractor(activeNotificationListRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractorKosmos.kt
similarity index 66%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractorKosmos.kt
index a780763..d14c854 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractorKosmos.kt
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.statusbar.notification.domain.interactor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.data.repository.headsUpNotificationIconViewStateRepository
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.headsUpNotificationIconInteractor by
+ Kosmos.Fixture { HeadsUpNotificationIconInteractor(headsUpNotificationIconViewStateRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt
new file mode 100644
index 0000000..e7bd5ea
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.icon.domain.interactor
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.data.repository.notificationListenerSettingsRepository
+import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.wm.shell.bubbles.bubblesOptional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.alwaysOnDisplayNotificationIconsInteractor by
+ Kosmos.Fixture {
+ AlwaysOnDisplayNotificationIconsInteractor(
+ deviceEntryInteractor = deviceEntryInteractor,
+ iconsInteractor = notificationIconsInteractor,
+ )
+ }
+val Kosmos.statusBarNotificationIconsInteractor by
+ Kosmos.Fixture {
+ StatusBarNotificationIconsInteractor(
+ iconsInteractor = notificationIconsInteractor,
+ settingsRepository = notificationListenerSettingsRepository,
+ )
+ }
+val Kosmos.notificationIconsInteractor by
+ Kosmos.Fixture {
+ NotificationIconsInteractor(
+ activeNotificationsInteractor = activeNotificationsInteractor,
+ bubbles = bubblesOptional,
+ keyguardViewStateRepository = notificationsKeyguardViewStateRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelKosmos.kt
new file mode 100644
index 0000000..6295b83
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+
+import android.content.res.mainResources
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.icon.domain.interactor.alwaysOnDisplayNotificationIconsInteractor
+
+val Kosmos.notificationIconContainerAlwaysOnDisplayViewModel by
+ Kosmos.Fixture {
+ NotificationIconContainerAlwaysOnDisplayViewModel(
+ iconsInteractor = alwaysOnDisplayNotificationIconsInteractor,
+ keyguardInteractor = keyguardInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
+ resources = mainResources,
+ shadeInteractor = shadeInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelKosmos.kt
new file mode 100644
index 0000000..04bb52d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+
+import android.content.res.mainResources
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.headsUpNotificationIconInteractor
+import com.android.systemui.statusbar.notification.icon.domain.interactor.statusBarNotificationIconsInteractor
+import com.android.systemui.statusbar.phone.domain.interactor.darkIconInteractor
+
+val Kosmos.notificationIconContainerStatusBarViewModel by
+ Kosmos.Fixture {
+ NotificationIconContainerStatusBarViewModel(
+ darkIconInteractor = darkIconInteractor,
+ iconsInteractor = statusBarNotificationIconsInteractor,
+ headsUpIconInteractor = headsUpNotificationIconInteractor,
+ keyguardInteractor = keyguardInteractor,
+ notificationsInteractor = activeNotificationsInteractor,
+ resources = mainResources,
+ shadeInteractor = shadeInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
new file mode 100644
index 0000000..3403227
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.policy.splitShadeStateController
+
+val Kosmos.sharedNotificationContainerInteractor by
+ Kosmos.Fixture {
+ SharedNotificationContainerInteractor(
+ configurationRepository = configurationRepository,
+ context = applicationContext,
+ splitShadeStateController = splitShadeStateController,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeParametersKosmos.kt
similarity index 80%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeParametersKosmos.kt
index a780763..9f6b181 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeParametersKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.statusbar.phone
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.dozeParameters by Kosmos.Fixture { mock<DozeParameters>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ScreenOffAnimationControllerKosmos.kt
similarity index 77%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ScreenOffAnimationControllerKosmos.kt
index a780763..d4c21f6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ScreenOffAnimationControllerKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.statusbar.phone
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.screenOffAnimationController by Kosmos.Fixture { mock<ScreenOffAnimationController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepositoryKosmos.kt
similarity index 73%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepositoryKosmos.kt
index a780763..977dcb7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepositoryKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.statusbar.phone.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.darkIconRepository: DarkIconRepository by Kosmos.Fixture { fakeDarkIconRepository }
+val Kosmos.fakeDarkIconRepository by Kosmos.Fixture { FakeDarkIconRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractorKosmos.kt
similarity index 73%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractorKosmos.kt
index a780763..db678d4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractorKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.statusbar.phone.domain.interactor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.phone.data.repository.darkIconRepository
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.darkIconInteractor by Kosmos.Fixture { DarkIconInteractor(darkIconRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
similarity index 72%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
index a780763..7b9634a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.userSetupRepository: UserSetupRepository by Kosmos.Fixture { fakeUserSetupRepository }
+val Kosmos.fakeUserSetupRepository by Kosmos.Fixture { FakeUserSetupRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
similarity index 78%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
index a780763..18a2f94 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.statusbar.policy
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.configurationController by Kosmos.Fixture { mock<ConfigurationController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerKosmos.kt
similarity index 70%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerKosmos.kt
index a780763..6a77c88 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerKosmos.kt
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.statusbar.policy
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.deviceProvisionedController: DeviceProvisionedController by
+ Kosmos.Fixture { fakeDeviceProvisionedController }
+val Kosmos.fakeDeviceProvisionedController by Kosmos.Fixture { FakeDeviceProvisionedController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SplitShadeStateControllerKosmos.kt
similarity index 69%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SplitShadeStateControllerKosmos.kt
index a780763..5e430381 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SplitShadeStateControllerKosmos.kt
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.statusbar.policy
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.splitShadeStateController: SplitShadeStateController by
+ Kosmos.Fixture { resourcesSplitShadeStateController }
+val Kosmos.resourcesSplitShadeStateController by
+ Kosmos.Fixture { ResourcesSplitShadeStateController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryKosmos.kt
similarity index 69%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryKosmos.kt
index a780763..56a0e02 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryKosmos.kt
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.statusbar.policy.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.deviceProvisioningRepository: DeviceProvisioningRepository by
+ Kosmos.Fixture { fakeDeviceProvisioningRepository }
+val Kosmos.fakeDeviceProvisioningRepository by Kosmos.Fixture { FakeDeviceProvisioningRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt
index 4059930..c4d7867 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.policy.data.repository
-import android.app.NotificationManager
+import android.app.NotificationManager.Policy
import android.provider.Settings
import com.android.systemui.dagger.SysUISingleton
import dagger.Binds
@@ -27,14 +27,24 @@
@SysUISingleton
class FakeZenModeRepository @Inject constructor() : ZenModeRepository {
override val zenMode: MutableStateFlow<Int> = MutableStateFlow(Settings.Global.ZEN_MODE_OFF)
- override val consolidatedNotificationPolicy: MutableStateFlow<NotificationManager.Policy?> =
+ override val consolidatedNotificationPolicy: MutableStateFlow<Policy?> =
MutableStateFlow(
- NotificationManager.Policy(
+ Policy(
/* priorityCategories = */ 0,
/* priorityCallSenders = */ 0,
/* priorityMessageSenders = */ 0,
)
)
+
+ fun setSuppressedVisualEffects(suppressedVisualEffects: Int) {
+ consolidatedNotificationPolicy.value =
+ Policy(
+ /* priorityCategories = */ 0,
+ /* priorityCallSenders = */ 0,
+ /* priorityMessageSenders = */ 0,
+ /* suppressedVisualEffects = */ suppressedVisualEffects,
+ )
+ }
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryKosmos.kt
similarity index 73%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryKosmos.kt
index a780763..6bb5ec5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.telephony.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.telephonyRepository: TelephonyRepository by Kosmos.Fixture { fakeTelephonyRepository }
+val Kosmos.fakeTelephonyRepository by Kosmos.Fixture { FakeTelephonyRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractorKosmos.kt
similarity index 74%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractorKosmos.kt
index a780763..02ca96e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractorKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.telephony.domain.interactor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.telephony.data.repository.telephonyRepository
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.telephonyInteractor by Kosmos.Fixture { TelephonyInteractor(telephonyRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/UserRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/UserRepositoryKosmos.kt
index 8bce9b6..9bb5262 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/UserRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/UserRepositoryKosmos.kt
@@ -18,4 +18,5 @@
import com.android.systemui.kosmos.Kosmos
-val Kosmos.userRepository by Kosmos.Fixture { FakeUserRepository() }
+var Kosmos.userRepository: UserRepository by Kosmos.Fixture { fakeUserRepository }
+val Kosmos.fakeUserRepository by Kosmos.Fixture { FakeUserRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/GuestUserInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/GuestUserInteractorKosmos.kt
index e695704..3b1c3f0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/GuestUserInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/GuestUserInteractorKosmos.kt
@@ -16,28 +16,32 @@
package com.android.systemui.user.domain.interactor
+import android.app.admin.devicePolicyManager
+import android.content.applicationContext
+import android.os.userManager
+import com.android.internal.logging.uiEventLogger
+import com.android.systemui.guestResetOrExitSessionReceiver
+import com.android.systemui.guestResumeSessionReceiver
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.context
-import com.android.systemui.kosmos.lifecycleScope
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.kosmos.userManager
+import com.android.systemui.statusbar.policy.deviceProvisionedController
import com.android.systemui.user.data.repository.userRepository
-import com.android.systemui.util.mockito.mock
val Kosmos.guestUserInteractor by
Kosmos.Fixture {
GuestUserInteractor(
- applicationContext = context,
- applicationScope = lifecycleScope,
+ applicationContext = applicationContext,
+ applicationScope = applicationCoroutineScope,
mainDispatcher = testDispatcher,
backgroundDispatcher = testDispatcher,
manager = userManager,
- deviceProvisionedController = mock(),
repository = userRepository,
- devicePolicyManager = mock(),
+ deviceProvisionedController = deviceProvisionedController,
+ devicePolicyManager = devicePolicyManager,
refreshUsersScheduler = refreshUsersScheduler,
- uiEventLogger = mock(),
- resumeSessionReceiver = mock(),
- resetOrExitSessionReceiver = mock(),
+ uiEventLogger = uiEventLogger,
+ resumeSessionReceiver = guestResumeSessionReceiver,
+ resetOrExitSessionReceiver = guestResetOrExitSessionReceiver,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserModeKosmos.kt
similarity index 82%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserModeKosmos.kt
index a780763..de9f69b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserModeKosmos.kt
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.user.domain.interactor
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.headlessSystemUserMode by Kosmos.Fixture { HeadlessSystemUserModeImpl() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerKosmos.kt
index 87a2fe0..14da8b0f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerKosmos.kt
@@ -17,15 +17,11 @@
package com.android.systemui.user.domain.interactor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.lifecycleScope
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.user.data.repository.userRepository
val Kosmos.refreshUsersScheduler by
Kosmos.Fixture {
- RefreshUsersScheduler(
- applicationScope = lifecycleScope,
- mainDispatcher = testDispatcher,
- repository = userRepository,
- )
+ RefreshUsersScheduler(applicationCoroutineScope, testDispatcher, userRepository)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt
similarity index 69%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt
index a780763..427f92a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.user.domain.interactor
+import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.data.repository.userRepository
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.selectedUserInteractor by
+ Kosmos.Fixture { SelectedUserInteractor(userRepository, featureFlagsClassic) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
index 6d6b268..42c77aa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
@@ -16,42 +16,41 @@
package com.android.systemui.user.domain.interactor
+import android.app.activityManager
+import android.content.applicationContext
+import android.os.userManager
+import com.android.internal.logging.uiEventLogger
+import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.broadcast.broadcastDispatcher
-import com.android.systemui.flags.featureFlags
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.context
-import com.android.systemui.kosmos.lifecycleScope
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.kosmos.userManager
-import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
-import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.telephony.domain.interactor.telephonyInteractor
import com.android.systemui.user.data.repository.userRepository
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.utils.userRestrictionChecker
val Kosmos.userSwitcherInteractor by
Kosmos.Fixture {
UserSwitcherInteractor(
- applicationContext = context,
+ applicationContext = applicationContext,
repository = userRepository,
- activityStarter = mock(),
- keyguardInteractor =
- KeyguardInteractorFactory.create(featureFlags = featureFlags).keyguardInteractor,
- featureFlags = featureFlags,
+ activityStarter = activityStarter,
+ keyguardInteractor = keyguardInteractor,
+ featureFlags = featureFlagsClassic,
manager = userManager,
- headlessSystemUserMode = mock(),
- applicationScope = lifecycleScope,
- telephonyInteractor =
- TelephonyInteractor(
- repository = FakeTelephonyRepository(),
- ),
+ headlessSystemUserMode = headlessSystemUserMode,
+ applicationScope = applicationCoroutineScope,
+ telephonyInteractor = telephonyInteractor,
broadcastDispatcher = broadcastDispatcher,
- keyguardUpdateMonitor = mock(),
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
- activityManager = mock(),
+ activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor = guestUserInteractor,
- uiEventLogger = mock(),
- userRestrictionChecker = mock()
+ uiEventLogger = uiEventLogger,
+ userRestrictionChecker = userRestrictionChecker,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/FakeAnimationUtilDataLayerModule.kt
similarity index 69%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/FakeAnimationUtilDataLayerModule.kt
index a780763..f7830d9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/FakeAnimationUtilDataLayerModule.kt
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.util.animation.data
-import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.animation.data.repository.FakeAnimationStatusRepositoryModule
+import dagger.Module
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+@Module(includes = [FakeAnimationStatusRepositoryModule::class])
+object FakeAnimationUtilDataLayerModule
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/FakeAnimationStatusRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/repository/FakeAnimationStatusRepository.kt
similarity index 72%
rename from packages/SystemUI/tests/src/com/android/systemui/util/animation/FakeAnimationStatusRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/repository/FakeAnimationStatusRepository.kt
index e72235c..ca6628b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/animation/FakeAnimationStatusRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/repository/FakeAnimationStatusRepository.kt
@@ -11,15 +11,17 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
-package com.android.systemui.util.animation
+package com.android.systemui.util.animation.data.repository
-import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
-class FakeAnimationStatusRepository : AnimationStatusRepository {
+class FakeAnimationStatusRepository @Inject constructor() : AnimationStatusRepository {
// Replay 1 element as real repository always emits current status as a first element
private val animationsEnabled: MutableSharedFlow<Boolean> = MutableSharedFlow(replay = 1)
@@ -30,3 +32,8 @@
animationsEnabled.tryEmit(enabled)
}
}
+
+@Module
+interface FakeAnimationStatusRepositoryModule {
+ @Binds fun bindFake(fake: FakeAnimationStatusRepository): AnimationStatusRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
similarity index 85%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
index a780763..914e654 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.util.time
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.fakeSystemClock by Kosmos.Fixture { FakeSystemClock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/UserRestrictionCheckerKosmos.kt
similarity index 84%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/utils/UserRestrictionCheckerKosmos.kt
index a780763..24d5d2f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/UserRestrictionCheckerKosmos.kt
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.systemui.utils
import com.android.systemui.kosmos.Kosmos
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.userRestrictionChecker by Kosmos.Fixture { UserRestrictionChecker() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/wm/shell/bubbles/BubblesKosmos.kt
similarity index 73%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/wm/shell/bubbles/BubblesKosmos.kt
index a780763..a7a37b2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/wm/shell/bubbles/BubblesKosmos.kt
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.flags
+package com.android.wm.shell.bubbles
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+import java.util.Optional
-val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+var Kosmos.bubblesOptional by Kosmos.Fixture { Optional.of(bubbles) }
+var Kosmos.bubbles by Kosmos.Fixture { mock<Bubbles> {} }
diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java
index 1d31579..f02f06c 100644
--- a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java
@@ -18,7 +18,6 @@
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -32,7 +31,7 @@
*
* @hide
*/
-@Target({TYPE, FIELD, METHOD, CONSTRUCTOR})
+@Target({FIELD, METHOD, CONSTRUCTOR})
@Retention(RetentionPolicy.CLASS)
public @interface RavenwoodKeep {
}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepPartialClass.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepPartialClass.java
new file mode 100644
index 0000000..7847274
--- /dev/null
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepPartialClass.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ravenwood.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * TODO: Javadoc
+ *
+ * @hide
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface RavenwoodKeepPartialClass {
+}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepStaticInitializer.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepStaticInitializer.java
new file mode 100644
index 0000000..eeebee9
--- /dev/null
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepStaticInitializer.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ravenwood.annotation;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * @hide
+ */
+@Target(TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface RavenwoodKeepStaticInitializer {
+}
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index 28639d4..48e9328 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -17,12 +17,14 @@
class android.util.Log stubclass
class android.util.Log !com.android.hoststubgen.nativesubstitution.Log_host
class android.util.LogPrinter stubclass
+class android.util.LocalLog stubclass
# String Manipulation
class android.util.Printer stubclass
class android.util.PrintStreamPrinter stubclass
class android.util.PrintWriterPrinter stubclass
class android.util.StringBuilderPrinter stubclass
+class android.util.IndentingPrintWriter stubclass
# Properties
class android.util.Property stubclass
@@ -76,6 +78,7 @@
class android.util.UtilConfig stubclass
# Internals
+class com.android.internal.util.FastPrintWriter stubclass
class com.android.internal.util.GrowingArrayUtils stubclass
class com.android.internal.util.LineBreakBufferedWriter stubclass
class com.android.internal.util.Preconditions stubclass
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index df44fde..a791f682 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -2,6 +2,12 @@
com.android.internal.util.ArrayUtils
+android.util.DataUnit
+android.util.EventLog
+android.util.IntArray
+android.util.LongArray
+android.util.Slog
+android.util.TimeUtils
android.util.Xml
android.os.Binder
diff --git a/ravenwood/ravenwood-standard-options.txt b/ravenwood/ravenwood-standard-options.txt
index 4b07ef6..f842f33 100644
--- a/ravenwood/ravenwood-standard-options.txt
+++ b/ravenwood/ravenwood-standard-options.txt
@@ -18,6 +18,9 @@
--keep-annotation
android.ravenwood.annotation.RavenwoodKeep
+--keep-annotation
+ android.ravenwood.annotation.RavenwoodKeepPartialClass
+
--keep-class-annotation
android.ravenwood.annotation.RavenwoodKeepWholeClass
@@ -35,3 +38,6 @@
--class-load-hook-annotation
android.ravenwood.annotation.RavenwoodClassLoadHook
+
+--keep-static-initializer-annotation
+ android.ravenwood.annotation.RavenwoodKeepStaticInitializer
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index b9c269c..71a1f01 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -75,7 +75,6 @@
import android.companion.IOnTransportsChangedListener;
import android.companion.ISystemDataTransferCallback;
import android.companion.datatransfer.PermissionSyncRequest;
-import android.companion.utils.FeatureUtils;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
@@ -829,11 +828,6 @@
@Override
public PendingIntent buildPermissionTransferUserConsentIntent(String packageName,
int userId, int associationId) {
- if (!FeatureUtils.isPermSyncEnabled()) {
- throw new UnsupportedOperationException("Calling"
- + " buildPermissionTransferUserConsentIntent, but this API is disabled by"
- + " the system.");
- }
return mSystemDataTransferProcessor.buildPermissionTransferUserConsentIntent(
packageName, userId, associationId);
}
@@ -841,10 +835,6 @@
@Override
public void startSystemDataTransfer(String packageName, int userId, int associationId,
ISystemDataTransferCallback callback) {
- if (!FeatureUtils.isPermSyncEnabled()) {
- throw new UnsupportedOperationException("Calling startSystemDataTransfer, but this"
- + " API is disabled by the system.");
- }
mSystemDataTransferProcessor.startSystemDataTransfer(packageName, userId,
associationId, callback);
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 1f62613..23e7ce6 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -20,6 +20,7 @@
import android.companion.AssociationInfo;
import android.companion.ContextSyncMessage;
+import android.companion.Flags;
import android.companion.Telecom;
import android.companion.datatransfer.PermissionSyncRequest;
import android.net.MacAddress;
@@ -65,7 +66,14 @@
public int onCommand(String cmd) {
final PrintWriter out = getOutPrintWriter();
final int associationId;
+
try {
+ if ("simulate-device-event".equals(cmd) && Flags.devicePresence()) {
+ associationId = getNextIntArgRequired();
+ int event = getNextIntArgRequired();
+ mDevicePresenceMonitor.simulateDeviceEvent(associationId, event);
+ return 0;
+ }
switch (cmd) {
case "list": {
final int userId = getNextIntArgRequired();
@@ -107,10 +115,15 @@
mService.loadAssociationsFromDisk();
break;
- case "simulate-device-event":
+ case "simulate-device-appeared":
associationId = getNextIntArgRequired();
- int event = getNextIntArgRequired();
- mDevicePresenceMonitor.simulateDeviceEvent(associationId, event);
+ mDevicePresenceMonitor.simulateDeviceEvent(associationId, /* event */ 0);
+ break;
+
+ case "simulate-device-disappeared":
+ associationId = getNextIntArgRequired();
+ mDevicePresenceMonitor.simulateDeviceEvent(associationId, /* event */ 1);
+ break;
case "remove-inactive-associations": {
// This command should trigger the same "clean-up" job as performed by the
@@ -346,9 +359,7 @@
pw.println(" information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
- pw.println(" simulate-device-event ASSOCIATION_ID EVENT");
- pw.println(" Simulate the companion device event changes:");
- pw.println(" Case(0): ");
+ pw.println(" simulate-device-appeared ASSOCIATION_ID");
pw.println(" Make CDM act as if the given companion device has appeared.");
pw.println(" I.e. bind the associated companion application's");
pw.println(" CompanionDeviceService(s) and trigger onDeviceAppeared() callback.");
@@ -356,18 +367,43 @@
pw.println(" will act as if device disappeared, unless 'simulate-device-disappeared'");
pw.println(" or 'simulate-device-appeared' is called again before 60 seconds run out"
+ ".");
- pw.println(" Case(1): ");
+ pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+
+ pw.println(" simulate-device-disappeared ASSOCIATION_ID");
pw.println(" Make CDM act as if the given companion device has disappeared.");
pw.println(" I.e. unbind the associated companion application's");
pw.println(" CompanionDeviceService(s) and trigger onDeviceDisappeared() callback.");
pw.println(" NOTE: This will only have effect if 'simulate-device-appeared' was");
pw.println(" invoked for the same device (same ASSOCIATION_ID) no longer than");
pw.println(" 60 seconds ago.");
- pw.println(" Case(2): ");
- pw.println(" Make CDM act as if the given companion device is BT connected ");
- pw.println(" Case(3): ");
- pw.println(" Make CDM act as if the given companion device is BT disconnected ");
- pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+
+ if (Flags.devicePresence()) {
+ pw.println(" simulate-device-event ASSOCIATION_ID EVENT");
+ pw.println(" Simulate the companion device event changes:");
+ pw.println(" Case(0): ");
+ pw.println(" Make CDM act as if the given companion device has appeared.");
+ pw.println(" I.e. bind the associated companion application's");
+ pw.println(" CompanionDeviceService(s) and trigger onDeviceAppeared() callback.");
+ pw.println(" The CDM will consider the devices as present for"
+ + "60 seconds and then");
+ pw.println(" will act as if device disappeared, unless"
+ + "'simulate-device-disappeared'");
+ pw.println(" or 'simulate-device-appeared' is called again before 60 seconds"
+ + "run out.");
+ pw.println(" Case(1): ");
+ pw.println(" Make CDM act as if the given companion device has disappeared.");
+ pw.println(" I.e. unbind the associated companion application's");
+ pw.println(" CompanionDeviceService(s) and trigger onDeviceDisappeared()"
+ + "callback.");
+ pw.println(" NOTE: This will only have effect if 'simulate-device-appeared' was");
+ pw.println(" invoked for the same device (same ASSOCIATION_ID) no longer than");
+ pw.println(" 60 seconds ago.");
+ pw.println(" Case(2): ");
+ pw.println(" Make CDM act as if the given companion device is BT connected ");
+ pw.println(" Case(3): ");
+ pw.println(" Make CDM act as if the given companion device is BT disconnected ");
+ pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+ }
pw.println(" remove-inactive-associations");
pw.println(" Remove self-managed associations that have not been active ");
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index 8fea078..e42b935 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -79,7 +79,7 @@
void onDeviceDisappeared(int associationId);
/**Invoked when device has corresponding event changes. */
- void onDeviceEvent(int associationId, int state);
+ void onDeviceEvent(int associationId, int event);
}
private final @NonNull AssociationStore mAssociationStore;
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index 8cd5ce1..ce1a875 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -158,6 +158,10 @@
@GuardedBy("this")
private ArraySet<Integer> mPinKeys;
+ // Note that we don't use the `_BOOT` namespace for anonymous pinnings, as we want
+ // them to be responsive to dynamic flag changes for experimentation.
+ private static final String DEVICE_CONFIG_NAMESPACE_ANON_SIZE =
+ DeviceConfig.NAMESPACE_RUNTIME_NATIVE;
private static final String DEVICE_CONFIG_KEY_ANON_SIZE = "pin_shared_anon_size";
private static final long DEFAULT_ANON_SIZE =
SystemProperties.getLong("pinner.pin_shared_anon_size", 0);
@@ -188,11 +192,11 @@
}
};
- private DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener =
+ private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigAnonSizeListener =
new DeviceConfig.OnPropertiesChangedListener() {
@Override
public void onPropertiesChanged(DeviceConfig.Properties properties) {
- if (DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT.equals(properties.getNamespace())
+ if (DEVICE_CONFIG_NAMESPACE_ANON_SIZE.equals(properties.getNamespace())
&& properties.getKeyset().contains(DEVICE_CONFIG_KEY_ANON_SIZE)) {
refreshPinAnonConfig();
}
@@ -246,9 +250,9 @@
registerUserSetupCompleteListener();
mDeviceConfigInterface.addOnPropertiesChangedListener(
- DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
+ DEVICE_CONFIG_NAMESPACE_ANON_SIZE,
new HandlerExecutor(mPinnerHandler),
- mDeviceConfigListener);
+ mDeviceConfigAnonSizeListener);
}
@Override
@@ -733,7 +737,7 @@
private void refreshPinAnonConfig() {
long newPinAnonSize =
mDeviceConfigInterface.getLong(
- DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
+ DEVICE_CONFIG_NAMESPACE_ANON_SIZE,
DEVICE_CONFIG_KEY_ANON_SIZE,
DEFAULT_ANON_SIZE);
newPinAnonSize = Math.max(0, Math.min(newPinAnonSize, MAX_ANON_SIZE));
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 9d8f979..f3b2ef3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -552,7 +552,8 @@
mAsync = true;
} else if (opt.equals("--splashscreen-show-icon")) {
mShowSplashScreen = true;
- } else if (opt.equals("--dismiss-keyguard-if-insecure")) {
+ } else if (opt.equals("--dismiss-keyguard-if-insecure")
+ || opt.equals("--dismiss-keyguard")) {
mDismissKeyguardIfInsecure = true;
} else {
return false;
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index b852ef5..d372108 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -67,10 +67,8 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal.OomAdjReason;
import android.content.pm.ServiceInfo;
-import android.os.IBinder;
import android.os.SystemClock;
import android.os.Trace;
-import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -80,7 +78,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Consumer;
@@ -504,6 +501,28 @@
}
}
+ /**
+ * A helper consumer for collecting processes that have not been reached yet. To avoid object
+ * allocations every OomAdjuster update, the results will be stored in
+ * {@link UnreachedProcessCollector#processList}. The process list reader is responsible
+ * for setting it before usage, as well as, clearing the reachable state of each process in the
+ * list.
+ */
+ private static class UnreachedProcessCollector implements Consumer<ProcessRecord> {
+ public ArrayList<ProcessRecord> processList = null;
+ @Override
+ public void accept(ProcessRecord process) {
+ if (process.mState.isReachable()) {
+ return;
+ }
+ process.mState.setReachable(true);
+ processList.add(process);
+ }
+ }
+
+ private final UnreachedProcessCollector mUnreachedProcessCollector =
+ new UnreachedProcessCollector();
+
OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
ActiveUids activeUids) {
this(service, processList, activeUids, createAdjusterThread());
@@ -755,23 +774,8 @@
// We'll need to collect the upstream processes of the target apps here, because those
// processes would potentially impact the procstate/adj via bindings.
if (!fullUpdate) {
- final boolean containsCycle = collectReversedReachableProcessesLocked(targetProcesses,
- clientProcesses);
+ collectExcludedClientProcessesLocked(targetProcesses, clientProcesses);
- // If any of its upstream processes are in a cycle,
- // move them into the candidate targets.
- if (containsCycle) {
- // Add all client apps to the target process list.
- for (int i = 0, size = clientProcesses.size(); i < size; i++) {
- final ProcessRecord client = clientProcesses.get(i);
- final UidRecord uidRec = client.getUidRecord();
- targetProcesses.add(client);
- if (uidRec != null) {
- uids.put(uidRec.getUid(), uidRec);
- }
- }
- clientProcesses.clear();
- }
for (int i = 0, size = targetProcesses.size(); i < size; i++) {
final ProcessRecord app = targetProcesses.valueAt(i);
app.mState.resetCachedInfo();
@@ -807,102 +811,36 @@
}
/**
- * Collect the reversed reachable processes from the given {@code apps}, the result will be
- * returned in the given {@code processes}, which will <em>NOT</em> include the processes from
- * the given {@code apps}.
+ * Collect the client processes from the given {@code apps}, the result will be returned in the
+ * given {@code clientProcesses}, which will <em>NOT</em> include the processes from the given
+ * {@code apps}.
*/
@GuardedBy("mService")
- private boolean collectReversedReachableProcessesLocked(ArraySet<ProcessRecord> apps,
+ private void collectExcludedClientProcessesLocked(ArraySet<ProcessRecord> apps,
ArrayList<ProcessRecord> clientProcesses) {
- final ArrayDeque<ProcessRecord> queue = mTmpQueue;
- queue.clear();
- clientProcesses.clear();
- for (int i = 0, size = apps.size(); i < size; i++) {
+ // Mark all of the provided apps as reachable to avoid including them in the client list.
+ final int appsSize = apps.size();
+ for (int i = 0; i < appsSize; i++) {
final ProcessRecord app = apps.valueAt(i);
app.mState.setReachable(true);
- app.mState.setReversedReachable(true);
- queue.offer(app);
}
- // Track if any of them reachables could include a cycle
- boolean containsCycle = false;
-
- // Scan upstreams of the process record
- for (ProcessRecord pr = queue.poll(); pr != null; pr = queue.poll()) {
- if (!pr.mState.isReachable()) {
- // If not in the given initial set of apps, add it.
- clientProcesses.add(pr);
- }
- final ProcessServiceRecord psr = pr.mServices;
- for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
- final ServiceRecord s = psr.getRunningServiceAt(i);
- final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
- s.getConnections();
- for (int j = serviceConnections.size() - 1; j >= 0; j--) {
- final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j);
- for (int k = clist.size() - 1; k >= 0; k--) {
- final ConnectionRecord cr = clist.get(k);
- final ProcessRecord client = cr.binding.client;
- containsCycle |= client.mState.isReversedReachable();
- if (client.mState.isReversedReachable()) {
- continue;
- }
- queue.offer(client);
- client.mState.setReversedReachable(true);
- }
- }
- }
- final ProcessProviderRecord ppr = pr.mProviders;
- for (int i = ppr.numberOfProviders() - 1; i >= 0; i--) {
- final ContentProviderRecord cpr = ppr.getProviderAt(i);
- for (int j = cpr.connections.size() - 1; j >= 0; j--) {
- final ContentProviderConnection conn = cpr.connections.get(j);
- final ProcessRecord client = conn.client;
- containsCycle |= client.mState.isReversedReachable();
- if (client.mState.isReversedReachable()) {
- continue;
- }
- queue.offer(client);
- client.mState.setReversedReachable(true);
- }
- }
- // If this process is a sandbox itself, also add the app on whose behalf
- // its running
- if (pr.isSdkSandbox) {
- for (int is = psr.numberOfRunningServices() - 1; is >= 0; is--) {
- ServiceRecord s = psr.getRunningServiceAt(is);
- ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
- s.getConnections();
- for (int conni = serviceConnections.size() - 1; conni >= 0; conni--) {
- ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(conni);
- for (int i = clist.size() - 1; i >= 0; i--) {
- ConnectionRecord cr = clist.get(i);
- ProcessRecord attributedApp = cr.binding.attributedClient;
- if (attributedApp == null || attributedApp == pr) {
- continue;
- }
- containsCycle |= attributedApp.mState.isReversedReachable();
- if (attributedApp.mState.isReversedReachable()) {
- continue;
- }
- queue.offer(attributedApp);
- attributedApp.mState.setReversedReachable(true);
- }
- }
- }
- }
+ clientProcesses.clear();
+ mUnreachedProcessCollector.processList = clientProcesses;
+ for (int i = 0; i < appsSize; i++) {
+ final ProcessRecord app = apps.valueAt(i);
+ app.forEachClient(mUnreachedProcessCollector);
}
+ mUnreachedProcessCollector.processList = null;
// Reset the temporary bits.
for (int i = clientProcesses.size() - 1; i >= 0; i--) {
- clientProcesses.get(i).mState.setReversedReachable(false);
+ clientProcesses.get(i).mState.setReachable(false);
}
for (int i = 0, size = apps.size(); i < size; i++) {
final ProcessRecord app = apps.valueAt(i);
app.mState.setReachable(false);
- app.mState.setReversedReachable(false);
}
- return containsCycle;
}
@GuardedBy({"mService", "mProcLock"})
@@ -917,10 +855,6 @@
final int procStateTarget = mProcessRecordProcStateNodes.size() - 1;
final int adjTarget = mProcessRecordAdjNodes.size() - 1;
- final int appUid = !fullUpdate && targetProcesses.size() > 0
- ? targetProcesses.valueAt(0).uid : -1;
- final int logUid = mService.mCurOomAdjUid;
-
mAdjSeq++;
// All apps to be updated will be moved to the lowest slot.
if (fullUpdate) {
@@ -974,7 +908,7 @@
// We don't update the adj list since we're resetting it below.
}
- // Now nodes are set into their slots, without facting in the bindings.
+ // Now nodes are set into their slots, without factoring in the bindings.
// The nodes between the `lastNode` pointer and the TAIL should be the new nodes.
//
// The whole rationale here is that, the bindings from client to host app, won't elevate
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 2c6e598..b2082d9 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -69,8 +69,10 @@
import com.android.server.wm.WindowProcessListener;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.function.Consumer;
/**
* Full information about a particular process that
@@ -1613,4 +1615,50 @@
public boolean wasForceStopped() {
return mWasForceStopped;
}
+
+ /**
+ * Traverses all client processes and feed them to consumer.
+ */
+ @GuardedBy("mProcLock")
+ void forEachClient(@NonNull Consumer<ProcessRecord> consumer) {
+ for (int i = mServices.numberOfRunningServices() - 1; i >= 0; i--) {
+ final ServiceRecord s = mServices.getRunningServiceAt(i);
+ final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
+ s.getConnections();
+ for (int j = serviceConnections.size() - 1; j >= 0; j--) {
+ final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j);
+ for (int k = clist.size() - 1; k >= 0; k--) {
+ final ConnectionRecord cr = clist.get(k);
+ consumer.accept(cr.binding.client);
+ }
+ }
+ }
+ for (int i = mProviders.numberOfProviders() - 1; i >= 0; i--) {
+ final ContentProviderRecord cpr = mProviders.getProviderAt(i);
+ for (int j = cpr.connections.size() - 1; j >= 0; j--) {
+ final ContentProviderConnection conn = cpr.connections.get(j);
+ consumer.accept(conn.client);
+ }
+ }
+ // If this process is a sandbox itself, also add the app on whose behalf
+ // its running
+ if (isSdkSandbox) {
+ for (int is = mServices.numberOfRunningServices() - 1; is >= 0; is--) {
+ ServiceRecord s = mServices.getRunningServiceAt(is);
+ ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
+ s.getConnections();
+ for (int conni = serviceConnections.size() - 1; conni >= 0; conni--) {
+ ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(conni);
+ for (int i = clist.size() - 1; i >= 0; i--) {
+ ConnectionRecord cr = clist.get(i);
+ ProcessRecord attributedApp = cr.binding.attributedClient;
+ if (attributedApp == null || attributedApp == this) {
+ continue;
+ }
+ consumer.accept(attributedApp);
+ }
+ }
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 8723c5d..5ad921f 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -379,12 +379,6 @@
private boolean mReachable;
/**
- * Whether or not this process is reversed reachable from given process.
- */
- @GuardedBy("mService")
- private boolean mReversedReachable;
-
- /**
* The most recent time when the last visible activity within this process became invisible.
*
* <p> It'll be set to 0 if there is never a visible activity, or Long.MAX_VALUE if there is
@@ -997,16 +991,6 @@
}
@GuardedBy("mService")
- boolean isReversedReachable() {
- return mReversedReachable;
- }
-
- @GuardedBy("mService")
- void setReversedReachable(boolean reversedReachable) {
- mReversedReachable = reversedReachable;
- }
-
- @GuardedBy("mService")
void resetCachedInfo() {
mCachedHasActivities = VALUE_INVALID;
mCachedIsHeavyWeight = VALUE_INVALID;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9031cde..1ef4333 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4269,7 +4269,8 @@
if (device == null) {
// call was already logged in setDeviceVolume()
sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
- index/*val1*/, flags/*val2*/, callingPackage));
+ index/*val1*/, flags/*val2*/, getStreamVolume(streamType) /*val3*/,
+ callingPackage));
}
setStreamVolume(streamType, index, flags, device,
callingPackage, callingPackage, attributionTag,
@@ -8784,7 +8785,7 @@
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
mStreamVolumeAlias[mStreamType]);
AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent(
- mStreamType, mStreamVolumeAlias[mStreamType], index));
+ mStreamType, mStreamVolumeAlias[mStreamType], index, oldIndex));
sendBroadcastToAll(mVolumeChanged, mVolumeChangedOptions);
}
}
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 21a7d31..f69b9f6 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -151,11 +151,13 @@
final int mStreamType;
final int mAliasStreamType;
final int mIndex;
+ final int mOldIndex;
- VolChangedBroadcastEvent(int stream, int alias, int index) {
+ VolChangedBroadcastEvent(int stream, int alias, int index, int oldIndex) {
mStreamType = stream;
mAliasStreamType = alias;
mIndex = index;
+ mOldIndex = oldIndex;
}
@Override
@@ -163,7 +165,8 @@
return new StringBuilder("sending VOLUME_CHANGED stream:")
.append(AudioSystem.streamToString(mStreamType))
.append(" index:").append(mIndex)
- .append(" alias:").append(AudioSystem.streamToString(mAliasStreamType))
+ .append(" (was:").append(mOldIndex)
+ .append(") alias:").append(AudioSystem.streamToString(mAliasStreamType))
.toString();
}
}
@@ -234,19 +237,35 @@
final int mStream;
final int mVal1;
final int mVal2;
+ final int mVal3;
final String mCaller;
final String mGroupName;
+ /** used for VOL_SET_STREAM_VOL */
+ VolumeEvent(int op, int stream, int val1, int val2, int val3, String caller) {
+ mOp = op;
+ mStream = stream;
+ mVal1 = val1;
+ mVal2 = val2;
+ mVal3 = val3;
+ mCaller = caller;
+ // unused
+ mGroupName = null;
+ logMetricEvent();
+ }
+
/** used for VOL_ADJUST_VOL_UID,
* VOL_ADJUST_SUGG_VOL,
* VOL_ADJUST_STREAM_VOL,
- * VOL_SET_STREAM_VOL */
+ */
VolumeEvent(int op, int stream, int val1, int val2, String caller) {
mOp = op;
mStream = stream;
mVal1 = val1;
mVal2 = val2;
mCaller = caller;
+ // unused
+ mVal3 = -1;
mGroupName = null;
logMetricEvent();
}
@@ -257,6 +276,7 @@
mVal1 = index;
mVal2 = gainDb;
// unused
+ mVal3 = -1;
mStream = -1;
mCaller = null;
mGroupName = null;
@@ -269,6 +289,7 @@
mVal1 = index;
// unused
mVal2 = 0;
+ mVal3 = -1;
mStream = -1;
mCaller = null;
mGroupName = null;
@@ -282,6 +303,7 @@
mVal1 = index;
mVal2 = voiceActive ? 1 : 0;
// unused
+ mVal3 = -1;
mCaller = null;
mGroupName = null;
logMetricEvent();
@@ -294,6 +316,7 @@
mVal1 = index;
mVal2 = mode;
// unused
+ mVal3 = -1;
mCaller = null;
mGroupName = null;
logMetricEvent();
@@ -308,6 +331,8 @@
mVal2 = flags;
mCaller = caller;
mGroupName = group;
+ // unused
+ mVal3 = -1;
logMetricEvent();
}
@@ -317,8 +342,10 @@
mStream = stream;
mVal1 = state ? 1 : 0;
mVal2 = 0;
+ // unused
mCaller = null;
mGroupName = null;
+ mVal3 = -1;
logMetricEvent();
}
@@ -328,6 +355,8 @@
mStream = -1;
mVal1 = state ? 1 : 0;
mVal2 = 0;
+ // unused
+ mVal3 = -1;
mCaller = null;
mGroupName = null;
logMetricEvent();
@@ -386,6 +415,7 @@
.set(MediaMetrics.Property.EVENT, "setStreamVolume")
.set(MediaMetrics.Property.FLAGS, mVal2)
.set(MediaMetrics.Property.INDEX, mVal1)
+ .set(MediaMetrics.Property.OLD_INDEX, mVal3)
.set(MediaMetrics.Property.STREAM_TYPE,
AudioSystem.streamToString(mStream))
.record();
@@ -478,6 +508,7 @@
.append(AudioSystem.streamToString(mStream))
.append(" index:").append(mVal1)
.append(" flags:0x").append(Integer.toHexString(mVal2))
+ .append(" oldIndex:").append(mVal3)
.append(") from ").append(mCaller)
.toString();
case VOL_SET_HEARING_AID_VOL:
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index b9ccbfb..c507300 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -576,7 +576,7 @@
}
void onDialogAnimatedIn(boolean startFingerprintNow) {
- if (mState != STATE_AUTH_STARTED) {
+ if (mState != STATE_AUTH_STARTED && mState != STATE_ERROR_PENDING_SYSUI) {
Slog.e(TAG, "onDialogAnimatedIn, unexpected state: " + mState);
return;
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index fac727f..dff14b5 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -1114,12 +1114,22 @@
public void notifyDeviceStateInfoAsync(@NonNull DeviceStateInfo info) {
mHandler.post(() -> {
+ boolean tracingEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER);
+ if (tracingEnabled) { // To avoid creating the string when not needed.
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
+ "notifyDeviceStateInfoAsync(pid=" + mPid + ")");
+ }
try {
mCallback.onDeviceStateInfoChanged(info);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify process " + mPid + " that device state changed.",
ex);
}
+ finally {
+ if (tracingEnabled) {
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
+ }
});
}
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index 6695801..9fcaa1e 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -38,6 +38,7 @@
private final boolean mShouldUseAutoBrightness;
private final boolean mIsSlowChange;
+ private final boolean mShouldUpdateScreenBrightnessSetting;
private final float mCustomAnimationRate;
@@ -50,6 +51,7 @@
mIsSlowChange = builder.isSlowChange();
mMaxBrightness = builder.getMaxBrightness();
mCustomAnimationRate = builder.getCustomAnimationRate();
+ mShouldUpdateScreenBrightnessSetting = builder.shouldUpdateScreenBrightnessSetting();
}
/**
@@ -109,6 +111,13 @@
return mCustomAnimationRate;
}
+ /**
+ * @return {@code true} if the screen brightness setting should be updated
+ */
+ public boolean shouldUpdateScreenBrightnessSetting() {
+ return mShouldUpdateScreenBrightnessSetting;
+ }
+
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:");
@@ -123,6 +132,8 @@
stringBuilder.append("\n isSlowChange:").append(mIsSlowChange);
stringBuilder.append("\n maxBrightness:").append(mMaxBrightness);
stringBuilder.append("\n customAnimationRate:").append(mCustomAnimationRate);
+ stringBuilder.append("\n shouldUpdateScreenBrightnessSetting:")
+ .append(mShouldUpdateScreenBrightnessSetting);
return stringBuilder.toString();
}
@@ -149,13 +160,16 @@
&& mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness()
&& mIsSlowChange == otherState.isSlowChange()
&& mMaxBrightness == otherState.getMaxBrightness()
- && mCustomAnimationRate == otherState.getCustomAnimationRate();
+ && mCustomAnimationRate == otherState.getCustomAnimationRate()
+ && mShouldUpdateScreenBrightnessSetting
+ == otherState.shouldUpdateScreenBrightnessSetting();
}
@Override
public int hashCode() {
return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason,
- mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mCustomAnimationRate);
+ mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mCustomAnimationRate,
+ mShouldUpdateScreenBrightnessSetting);
}
/**
@@ -177,6 +191,7 @@
private boolean mIsSlowChange;
private float mMaxBrightness;
private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET;
+ private boolean mShouldUpdateScreenBrightnessSetting;
/**
* Create a builder starting with the values from the specified {@link
@@ -194,6 +209,8 @@
builder.setIsSlowChange(state.isSlowChange());
builder.setMaxBrightness(state.getMaxBrightness());
builder.setCustomAnimationRate(state.getCustomAnimationRate());
+ builder.setShouldUpdateScreenBrightnessSetting(
+ state.shouldUpdateScreenBrightnessSetting());
return builder;
}
@@ -290,8 +307,8 @@
/**
* See {@link DisplayBrightnessState#isSlowChange()}.
*/
- public Builder setIsSlowChange(boolean shouldUseAutoBrightness) {
- this.mIsSlowChange = shouldUseAutoBrightness;
+ public Builder setIsSlowChange(boolean isSlowChange) {
+ this.mIsSlowChange = isSlowChange;
return this;
}
@@ -334,6 +351,22 @@
}
/**
+ * See {@link DisplayBrightnessState#shouldUpdateScreenBrightnessSetting()}.
+ */
+ public boolean shouldUpdateScreenBrightnessSetting() {
+ return mShouldUpdateScreenBrightnessSetting;
+ }
+
+ /**
+ * See {@link DisplayBrightnessState#shouldUpdateScreenBrightnessSetting()}.
+ */
+ public Builder setShouldUpdateScreenBrightnessSetting(
+ boolean shouldUpdateScreenBrightnessSetting) {
+ mShouldUpdateScreenBrightnessSetting = shouldUpdateScreenBrightnessSetting;
+ return this;
+ }
+
+ /**
* This is used to construct an immutable DisplayBrightnessState object from its builder
*/
public DisplayBrightnessState build() {
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 9f4f787..2fdf90d 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -209,7 +209,7 @@
int state,
float brightnessState,
float sdrBrightnessState,
- @Nullable DisplayOffloadSession displayOffloadSession) {
+ @Nullable DisplayOffloadSessionImpl displayOffloadSession) {
return null;
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 11f4e5f..e99f82a 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -4948,20 +4948,8 @@
return null;
}
- DisplayOffloadSession session =
- new DisplayOffloadSession() {
- @Override
- public void setDozeStateOverride(int displayState) {
- synchronized (mSyncRoot) {
- displayPowerController.overrideDozeScreenState(displayState);
- }
- }
-
- @Override
- public DisplayOffloader getDisplayOffloader() {
- return displayOffloader;
- }
- };
+ DisplayOffloadSessionImpl session = new DisplayOffloadSessionImpl(displayOffloader,
+ displayPowerController);
logicalDisplay.setDisplayOffloadSessionLocked(session);
displayPowerController.setDisplayOffloadSession(session);
return session;
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
new file mode 100644
index 0000000..1bd556b
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 android.annotation.Nullable;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+import android.os.Trace;
+
+/**
+ * An implementation of the offload session that keeps track of whether the session is active.
+ * An offload session is used to control the display's brightness using the offload chip.
+ */
+public class DisplayOffloadSessionImpl implements DisplayManagerInternal.DisplayOffloadSession {
+
+ @Nullable
+ private final DisplayManagerInternal.DisplayOffloader mDisplayOffloader;
+ private final DisplayPowerControllerInterface mDisplayPowerController;
+ private boolean mIsActive;
+
+ public DisplayOffloadSessionImpl(
+ @Nullable DisplayManagerInternal.DisplayOffloader displayOffloader,
+ DisplayPowerControllerInterface displayPowerController) {
+ mDisplayOffloader = displayOffloader;
+ mDisplayPowerController = displayPowerController;
+ }
+
+ @Override
+ public void setDozeStateOverride(int displayState) {
+ mDisplayPowerController.overrideDozeScreenState(displayState);
+ }
+
+ @Override
+ public boolean isActive() {
+ return mIsActive;
+ }
+
+ @Override
+ public void updateBrightness(float brightness) {
+ if (mIsActive) {
+ mDisplayPowerController.setBrightnessFromOffload(brightness);
+ }
+ }
+
+ /**
+ * Start the offload session. The method returns if the session is already active.
+ * @return Whether the session was started successfully
+ */
+ public boolean startOffload() {
+ if (mDisplayOffloader == null || mIsActive) {
+ return false;
+ }
+ Trace.traceBegin(Trace.TRACE_TAG_POWER, "DisplayOffloader#startOffload");
+ try {
+ return mIsActive = mDisplayOffloader.startOffload();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_POWER);
+ }
+ }
+
+ /**
+ * Stop the offload session. The method returns if the session is not active.
+ */
+ public void stopOffload() {
+ if (mDisplayOffloader == null || !mIsActive) {
+ return;
+ }
+ Trace.traceBegin(Trace.TRACE_TAG_POWER, "DisplayOffloader#stopOffload");
+ try {
+ mDisplayOffloader.stopOffload();
+ mIsActive = false;
+ mDisplayPowerController.setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_POWER);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 5761c31..f3d761a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -2224,6 +2224,11 @@
}
@Override
+ public void setBrightnessFromOffload(float brightness) {
+ // The old DPC is no longer supported
+ }
+
+ @Override
public BrightnessInfo getBrightnessInfo() {
synchronized (mCachedBrightnessInfo) {
return new BrightnessInfo(
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index a6155da..d4e0cbb 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -151,6 +151,7 @@
private static final int MSG_SET_DWBC_STRONG_MODE = 14;
private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 15;
private static final int MSG_SET_DWBC_LOGGING_ENABLED = 16;
+ private static final int MSG_SET_BRIGHTNESS_FROM_OFFLOAD = 17;
@@ -562,7 +563,7 @@
new DisplayBrightnessController(context, null,
mDisplayId, mLogicalDisplay.getDisplayInfoLocked().brightnessDefault,
brightnessSetting, () -> postBrightnessChangeRunnable(),
- new HandlerExecutor(mHandler));
+ new HandlerExecutor(mHandler), flags);
mBrightnessClamperController = mInjector.getBrightnessClamperController(
mHandler, modeChangeCallback::run,
@@ -1394,7 +1395,8 @@
? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
: AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
- boolean updateScreenBrightnessSetting = false;
+ boolean updateScreenBrightnessSetting =
+ displayBrightnessState.shouldUpdateScreenBrightnessSetting();
float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
// Apply auto-brightness.
int brightnessAdjustmentFlags = 0;
@@ -1854,6 +1856,13 @@
}
@Override
+ public void setBrightnessFromOffload(float brightness) {
+ Message msg = mHandler.obtainMessage(MSG_SET_BRIGHTNESS_FROM_OFFLOAD,
+ Float.floatToIntBits(brightness), 0 /*unused*/);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
+ }
+
+ @Override
public BrightnessInfo getBrightnessInfo() {
synchronized (mCachedBrightnessInfo) {
return new BrightnessInfo(
@@ -2886,6 +2895,11 @@
case MSG_SET_DWBC_LOGGING_ENABLED:
setDwbcLoggingEnabled(msg.arg1);
break;
+ case MSG_SET_BRIGHTNESS_FROM_OFFLOAD:
+ mDisplayBrightnessController.setBrightnessFromOffload(
+ Float.intBitsToFloat(msg.arg1));
+ updatePowerState();
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index 181386a..72079a4 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -22,6 +22,7 @@
import android.hardware.display.BrightnessConfiguration;
import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
import java.io.PrintWriter;
@@ -150,6 +151,14 @@
void setTemporaryAutoBrightnessAdjustment(float adjustment);
/**
+ * Sets temporary brightness from the offload chip until we get a brightness value from
+ * the light sensor.
+ * @param brightness The brightness value between {@link PowerManager.BRIGHTNESS_MIN} and
+ * {@link PowerManager.BRIGHTNESS_MAX}. Values outside of that range will be ignored.
+ */
+ void setBrightnessFromOffload(float brightness);
+
+ /**
* Gets the screen brightness setting
*/
float getScreenBrightnessSetting();
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index be3207d..ff9a1ab 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -19,11 +19,11 @@
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.Mode.INVALID_MODE_ID;
+import android.annotation.Nullable;
import android.app.ActivityThread;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
-import android.hardware.display.DisplayManagerInternal.DisplayOffloader;
import android.hardware.sidekick.SidekickInternal;
import android.os.Build;
import android.os.Handler;
@@ -238,7 +238,6 @@
private boolean mAllmRequested;
private boolean mGameContentTypeRequested;
private boolean mSidekickActive;
- private boolean mDisplayOffloadActive;
private SurfaceControl.StaticDisplayInfo mStaticDisplayInfo;
// The supported display modes according to SurfaceFlinger
private SurfaceControl.DisplayMode[] mSfDisplayModes;
@@ -765,7 +764,7 @@
final int state,
final float brightnessState,
final float sdrBrightnessState,
- DisplayOffloadSession displayOffloadSession) {
+ @Nullable DisplayOffloadSessionImpl displayOffloadSession) {
// Assume that the brightness is off if the display is being turned off.
assert state != Display.STATE_OFF
@@ -832,25 +831,13 @@
+ ", state=" + Display.stateToString(state) + ")");
}
- DisplayOffloader displayOffloader =
- displayOffloadSession == null
- ? null
- : displayOffloadSession.getDisplayOffloader();
-
boolean isDisplayOffloadEnabled = mFlags.isDisplayOffloadEnabled();
// We must tell sidekick/displayoffload to stop controlling the display
// before we can change its power mode, so do that first.
if (isDisplayOffloadEnabled) {
- if (mDisplayOffloadActive && displayOffloader != null) {
- Trace.traceBegin(Trace.TRACE_TAG_POWER,
- "DisplayOffloader#stopOffload");
- try {
- displayOffloader.stopOffload();
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_POWER);
- }
- mDisplayOffloadActive = false;
+ if (displayOffloadSession != null) {
+ displayOffloadSession.stopOffload();
}
} else {
if (mSidekickActive) {
@@ -881,16 +868,9 @@
// have a sidekick/displayoffload available, tell it now that it can take
// control.
if (isDisplayOffloadEnabled) {
- if (DisplayOffloadSession.isSupportedOffloadState(state) &&
- displayOffloader != null
- && !mDisplayOffloadActive) {
- Trace.traceBegin(
- Trace.TRACE_TAG_POWER, "DisplayOffloader#startOffload");
- try {
- mDisplayOffloadActive = displayOffloader.startOffload();
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_POWER);
- }
+ if (DisplayOffloadSession.isSupportedOffloadState(state)
+ && displayOffloadSession != null) {
+ displayOffloadSession.startOffload();
}
} else {
if (Display.isSuspendedState(state) && state != Display.STATE_OFF
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 3d4209e..ba321ae 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -138,7 +138,7 @@
private final Rect mTempDisplayRect = new Rect();
/** A session token that controls the offloading operations of this logical display. */
- private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
+ private DisplayOffloadSessionImpl mDisplayOffloadSession;
/**
* Name of a display group to which the display is assigned.
@@ -969,12 +969,11 @@
return mDisplayGroupName;
}
- public void setDisplayOffloadSessionLocked(
- DisplayManagerInternal.DisplayOffloadSession session) {
+ public void setDisplayOffloadSessionLocked(DisplayOffloadSessionImpl session) {
mDisplayOffloadSession = session;
}
- public DisplayManagerInternal.DisplayOffloadSession getDisplayOffloadSessionLocked() {
+ public DisplayOffloadSessionImpl getDisplayOffloadSessionLocked() {
return mDisplayOffloadSession;
}
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 90e32a6..edbd424 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -43,7 +43,6 @@
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Point;
-import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
import android.media.projection.IMediaProjection;
@@ -380,7 +379,7 @@
@Override
public Runnable requestDisplayStateLocked(int state, float brightnessState,
- float sdrBrightnessState, DisplayOffloadSession displayOffloadSession) {
+ float sdrBrightnessState, DisplayOffloadSessionImpl displayOffloadSession) {
if (state != mDisplayState) {
mDisplayState = state;
if (state == Display.STATE_OFF) {
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessReason.java b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
index d7ae269..8fe5f21 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessReason.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
@@ -39,7 +39,8 @@
public static final int REASON_BOOST = 8;
public static final int REASON_SCREEN_OFF_BRIGHTNESS_SENSOR = 9;
public static final int REASON_FOLLOWER = 10;
- public static final int REASON_MAX = REASON_FOLLOWER;
+ public static final int REASON_OFFLOAD = 11;
+ public static final int REASON_MAX = REASON_OFFLOAD;
public static final int MODIFIER_DIMMED = 0x1;
public static final int MODIFIER_LOW_POWER = 0x2;
@@ -196,6 +197,8 @@
return "screen_off_brightness_sensor";
case REASON_FOLLOWER:
return "follower";
+ case REASON_OFFLOAD:
+ return "offload";
default:
return Integer.toString(reason);
}
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index d6f0098..617befb 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -31,6 +31,7 @@
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
+import com.android.server.display.feature.DisplayManagerFlags;
import java.io.PrintWriter;
@@ -104,7 +105,8 @@
*/
public DisplayBrightnessController(Context context, Injector injector, int displayId,
float defaultScreenBrightness, BrightnessSetting brightnessSetting,
- Runnable onBrightnessChangeRunnable, HandlerExecutor brightnessChangeExecutor) {
+ Runnable onBrightnessChangeRunnable, HandlerExecutor brightnessChangeExecutor,
+ DisplayManagerFlags flags) {
if (injector == null) {
injector = new Injector();
}
@@ -116,7 +118,7 @@
mCurrentScreenBrightness = getScreenBrightnessSetting();
mOnBrightnessChangeRunnable = onBrightnessChangeRunnable;
mDisplayBrightnessStrategySelector = injector.getDisplayBrightnessStrategySelector(context,
- displayId);
+ displayId, flags);
mBrightnessChangeExecutor = brightnessChangeExecutor;
mPersistBrightnessNitsForDefaultDisplay = context.getResources().getBoolean(
com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay);
@@ -172,6 +174,18 @@
}
/**
+ * Sets the brightness from the offload session.
+ */
+ public void setBrightnessFromOffload(float brightness) {
+ synchronized (mLock) {
+ if (mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy() != null) {
+ mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()
+ .setOffloadScreenBrightness(brightness);
+ }
+ }
+ }
+
+ /**
* Returns a boolean flag indicating if the light sensor is to be used to decide the screen
* brightness when dozing
*/
@@ -423,8 +437,9 @@
@VisibleForTesting
static class Injector {
DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(Context context,
- int displayId) {
- return new DisplayBrightnessStrategySelector(context, /* injector= */ null, displayId);
+ int displayId, DisplayManagerFlags flags) {
+ return new DisplayBrightnessStrategySelector(context, /* injector= */ null, displayId,
+ flags);
}
}
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index f141c20..055f94a 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -17,6 +17,7 @@
package com.android.server.display.brightness;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.hardware.display.DisplayManagerInternal;
import android.util.IndentingPrintWriter;
@@ -31,9 +32,11 @@
import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy;
import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
+import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy;
import com.android.server.display.brightness.strategy.OverrideBrightnessStrategy;
import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrategy;
import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
+import com.android.server.display.feature.DisplayManagerFlags;
import java.io.PrintWriter;
@@ -63,6 +66,10 @@
private final InvalidBrightnessStrategy mInvalidBrightnessStrategy;
// Controls brightness when automatic (adaptive) brightness is running.
private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
+ // Controls the brightness if adaptive brightness is on and there exists an active offload
+ // session. Brightness value is provided by the offload session.
+ @Nullable
+ private final OffloadBrightnessStrategy mOffloadBrightnessStrategy;
// We take note of the old brightness strategy so that we can know when the strategy changes.
private String mOldBrightnessStrategyName;
@@ -72,7 +79,8 @@
/**
* The constructor of DozeBrightnessStrategy.
*/
- public DisplayBrightnessStrategySelector(Context context, Injector injector, int displayId) {
+ public DisplayBrightnessStrategySelector(Context context, Injector injector, int displayId,
+ DisplayManagerFlags flags) {
if (injector == null) {
injector = new Injector();
}
@@ -85,6 +93,11 @@
mFollowerBrightnessStrategy = injector.getFollowerBrightnessStrategy(displayId);
mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy();
mAutomaticBrightnessStrategy = injector.getAutomaticBrightnessStrategy(context, displayId);
+ if (flags.isDisplayOffloadEnabled()) {
+ mOffloadBrightnessStrategy = injector.getOffloadBrightnessStrategy();
+ } else {
+ mOffloadBrightnessStrategy = null;
+ }
mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean(
R.bool.config_allowAutoBrightnessWhileDozing);
mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName();
@@ -114,6 +127,9 @@
} else if (BrightnessUtils.isValidBrightnessValue(
mTemporaryBrightnessStrategy.getTemporaryScreenBrightness())) {
displayBrightnessStrategy = mTemporaryBrightnessStrategy;
+ } else if (mOffloadBrightnessStrategy != null && BrightnessUtils.isValidBrightnessValue(
+ mOffloadBrightnessStrategy.getOffloadScreenBrightness())) {
+ displayBrightnessStrategy = mOffloadBrightnessStrategy;
}
if (!mOldBrightnessStrategyName.equals(displayBrightnessStrategy.getName())) {
@@ -138,6 +154,11 @@
return mAutomaticBrightnessStrategy;
}
+ @Nullable
+ public OffloadBrightnessStrategy getOffloadBrightnessStrategy() {
+ return mOffloadBrightnessStrategy;
+ }
+
/**
* Returns a boolean flag indicating if the light sensor is to be used to decide the screen
* brightness when dozing
@@ -159,6 +180,9 @@
+ mAllowAutoBrightnessWhileDozingConfig);
IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
mTemporaryBrightnessStrategy.dump(ipw);
+ if (mOffloadBrightnessStrategy != null) {
+ mOffloadBrightnessStrategy.dump(ipw);
+ }
}
/**
@@ -210,5 +234,9 @@
AutomaticBrightnessStrategy getAutomaticBrightnessStrategy(Context context, int displayId) {
return new AutomaticBrightnessStrategy(context, displayId);
}
+
+ OffloadBrightnessStrategy getOffloadBrightnessStrategy() {
+ return new OffloadBrightnessStrategy();
+ }
}
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index bcd5259..3c23b5c 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -107,6 +107,7 @@
mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
&& (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze)
&& brightnessReason != BrightnessReason.REASON_OVERRIDE
+ && brightnessReason != BrightnessReason.REASON_OFFLOAD
&& mAutomaticBrightnessController != null;
mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
&& !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze);
diff --git a/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
new file mode 100644
index 0000000..55f8914
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 android.os.PowerManager;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import java.io.PrintWriter;
+
+/**
+ * Manages the brightness of the display when auto-brightness is on, the screen has just turned on
+ * and there is no available lux reading yet. The brightness value is read from the offload chip.
+ */
+public class OffloadBrightnessStrategy implements DisplayBrightnessStrategy {
+
+ private float mOffloadScreenBrightness;
+
+ public OffloadBrightnessStrategy() {
+ mOffloadScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ }
+
+ @Override
+ public DisplayBrightnessState updateBrightness(
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
+ BrightnessReason brightnessReason = new BrightnessReason();
+ brightnessReason.setReason(BrightnessReason.REASON_OFFLOAD);
+ return new DisplayBrightnessState.Builder()
+ .setBrightness(mOffloadScreenBrightness)
+ .setSdrBrightness(mOffloadScreenBrightness)
+ .setBrightnessReason(brightnessReason)
+ .setDisplayBrightnessStrategyName(getName())
+ .setIsSlowChange(false)
+ .setShouldUpdateScreenBrightnessSetting(true)
+ .build();
+ }
+
+ @Override
+ public String getName() {
+ return "OffloadBrightnessStrategy";
+ }
+
+ public float getOffloadScreenBrightness() {
+ return mOffloadScreenBrightness;
+ }
+
+ public void setOffloadScreenBrightness(float offloadScreenBrightness) {
+ mOffloadScreenBrightness = offloadScreenBrightness;
+ }
+
+ /**
+ * Dumps the state of this class.
+ */
+ public void dump(PrintWriter writer) {
+ writer.println("OffloadBrightnessStrategy:");
+ writer.println(" mOffloadScreenBrightness:" + mOffloadScreenBrightness);
+ }
+}
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
index 360a6a7..6bdfae2 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -110,7 +110,7 @@
@Override
@NonNull
- public synchronized MediaRoute2Info getDeviceRoute() {
+ public synchronized MediaRoute2Info getSelectedRoute() {
if (mSelectedRoute != null) {
return mSelectedRoute;
}
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
index 7876095..0fdaaa7 100644
--- a/services/core/java/com/android/server/media/DeviceRouteController.java
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -72,13 +72,9 @@
*/
boolean selectRoute(@Nullable @MediaRoute2Info.Type Integer type);
- /**
- * Returns currently selected device (built-in or wired) route.
- *
- * @return non-null device route.
- */
+ /** Returns the currently selected device (built-in or wired) route. */
@NonNull
- MediaRoute2Info getDeviceRoute();
+ MediaRoute2Info getSelectedRoute();
/**
* Updates device route volume.
diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
index 6ba40ae..65874e2 100644
--- a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
@@ -107,7 +107,7 @@
@Override
@NonNull
- public synchronized MediaRoute2Info getDeviceRoute() {
+ public synchronized MediaRoute2Info getSelectedRoute() {
return mDeviceRoute;
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index a158b18..994d3ca 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -76,6 +76,7 @@
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
+import android.util.Slog;
import android.view.KeyEvent;
import com.android.server.LocalServices;
@@ -348,16 +349,19 @@
} else {
if (mVolumeControlType == VOLUME_CONTROL_FIXED) {
if (DEBUG) {
- Log.d(TAG, "Session does not support volume adjustment");
+ Slog.d(TAG, "Session does not support volume adjustment");
}
} else if (direction == AudioManager.ADJUST_TOGGLE_MUTE
|| direction == AudioManager.ADJUST_MUTE
|| direction == AudioManager.ADJUST_UNMUTE) {
- Log.w(TAG, "Muting remote playback is not supported");
+ Slog.w(TAG, "Muting remote playback is not supported");
} else {
if (DEBUG) {
- Log.w(TAG, "adjusting volume, pkg=" + packageName + ", asSystemService="
- + asSystemService + ", dir=" + direction);
+ Slog.w(
+ TAG,
+ "adjusting volume, pkg=" + packageName
+ + ", asSystemService=" + asSystemService
+ + ", dir=" + direction);
}
mSessionCb.adjustVolume(packageName, pid, uid, asSystemService, direction);
@@ -371,8 +375,10 @@
}
if (DEBUG) {
- Log.d(TAG, "Adjusted optimistic volume to " + mOptimisticVolume + " max is "
- + mMaxVolume);
+ Slog.d(
+ TAG,
+ "Adjusted optimistic volume to " + mOptimisticVolume
+ + " max is " + mMaxVolume);
}
}
// Always notify, even if the volume hasn't changed. This is important to ensure that
@@ -388,23 +394,33 @@
if (mVolumeType == PLAYBACK_TYPE_LOCAL) {
int stream = getVolumeStream(mAudioAttrs);
final int volumeValue = value;
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- try {
- mAudioManager.setStreamVolumeForUid(stream, volumeValue, flags,
- opPackageName, uid, pid,
- mContext.getApplicationInfo().targetSdkVersion);
- } catch (IllegalArgumentException | SecurityException e) {
- Log.e(TAG, "Cannot set volume: stream=" + stream + ", value=" + volumeValue
- + ", flags=" + flags, e);
- }
- }
- });
+ mHandler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mAudioManager.setStreamVolumeForUid(
+ stream,
+ volumeValue,
+ flags,
+ opPackageName,
+ uid,
+ pid,
+ mContext.getApplicationInfo().targetSdkVersion);
+ } catch (IllegalArgumentException | SecurityException e) {
+ Slog.e(
+ TAG,
+ "Cannot set volume: stream=" + stream
+ + ", value=" + volumeValue
+ + ", flags=" + flags,
+ e);
+ }
+ }
+ });
} else {
if (mVolumeControlType != VOLUME_CONTROL_ABSOLUTE) {
if (DEBUG) {
- Log.d(TAG, "Session does not support setting volume");
+ Slog.d(TAG, "Session does not support setting volume");
}
} else {
value = Math.max(0, Math.min(value, mMaxVolume));
@@ -419,8 +435,10 @@
}
if (DEBUG) {
- Log.d(TAG, "Set optimistic volume to " + mOptimisticVolume + " max is "
- + mMaxVolume);
+ Slog.d(
+ TAG,
+ "Set optimistic volume to " + mOptimisticVolume
+ + " max is " + mMaxVolume);
}
}
// Always notify, even if the volume hasn't changed.
@@ -527,12 +545,27 @@
@Override
public boolean canHandleVolumeKey() {
if (isPlaybackTypeLocal()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Local MediaSessionRecord can handle volume key");
+ }
return true;
}
if (mVolumeControlType == VOLUME_CONTROL_FIXED) {
+ if (DEBUG) {
+ Slog.d(
+ TAG,
+ "Local MediaSessionRecord with FIXED volume control can't handle volume"
+ + " key");
+ }
return false;
}
if (mVolumeAdjustmentForRemoteGroupSessions) {
+ if (DEBUG) {
+ Slog.d(
+ TAG,
+ "Volume adjustment for remote group sessions allowed so MediaSessionRecord"
+ + " can handle volume key");
+ }
return true;
}
// See b/228021646 for details.
@@ -540,7 +573,18 @@
List<RoutingSessionInfo> sessions = mRouter2Manager.getRoutingSessions(mPackageName);
boolean foundNonSystemSession = false;
boolean remoteSessionAllowVolumeAdjustment = true;
+ if (DEBUG) {
+ Slog.d(
+ TAG,
+ "Found "
+ + sessions.size()
+ + " routing sessions for package name "
+ + mPackageName);
+ }
for (RoutingSessionInfo session : sessions) {
+ if (DEBUG) {
+ Slog.d(TAG, "Found routingSessionInfo: " + session);
+ }
if (!session.isSystemSession()) {
foundNonSystemSession = true;
if (session.getVolumeHandling() == PLAYBACK_VOLUME_FIXED) {
@@ -549,9 +593,14 @@
}
}
if (!foundNonSystemSession) {
- Log.d(TAG, "Package " + mPackageName
- + " has a remote media session but no associated routing session");
+ if (DEBUG) {
+ Slog.d(
+ TAG,
+ "Package " + mPackageName
+ + " has a remote media session but no associated routing session");
+ }
}
+
return foundNonSystemSession && remoteSessionAllowVolumeAdjustment;
}
@@ -637,8 +686,11 @@
final boolean asSystemService, final boolean useSuggested,
final int previousFlagPlaySound) {
if (DEBUG) {
- Log.w(TAG, "adjusting local volume, stream=" + stream + ", dir=" + direction
- + ", asSystemService=" + asSystemService + ", useSuggested=" + useSuggested);
+ Slog.w(
+ TAG,
+ "adjusting local volume, stream=" + stream + ", dir=" + direction
+ + ", asSystemService=" + asSystemService
+ + ", useSuggested=" + useSuggested);
}
// Must use opPackageName for adjusting volumes with UID.
final String opPackageName;
@@ -653,40 +705,61 @@
uid = callingUid;
pid = callingPid;
}
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- try {
- if (useSuggested) {
- if (AudioSystem.isStreamActive(stream, 0)) {
- mAudioManager.adjustSuggestedStreamVolumeForUid(stream,
- direction, flags, opPackageName, uid, pid,
- mContext.getApplicationInfo().targetSdkVersion);
- } else {
- mAudioManager.adjustSuggestedStreamVolumeForUid(
- AudioManager.USE_DEFAULT_STREAM_TYPE, direction,
- flags | previousFlagPlaySound, opPackageName, uid, pid,
- mContext.getApplicationInfo().targetSdkVersion);
+ mHandler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ try {
+ if (useSuggested) {
+ if (AudioSystem.isStreamActive(stream, 0)) {
+ mAudioManager.adjustSuggestedStreamVolumeForUid(
+ stream,
+ direction,
+ flags,
+ opPackageName,
+ uid,
+ pid,
+ mContext.getApplicationInfo().targetSdkVersion);
+ } else {
+ mAudioManager.adjustSuggestedStreamVolumeForUid(
+ AudioManager.USE_DEFAULT_STREAM_TYPE,
+ direction,
+ flags | previousFlagPlaySound,
+ opPackageName,
+ uid,
+ pid,
+ mContext.getApplicationInfo().targetSdkVersion);
+ }
+ } else {
+ mAudioManager.adjustStreamVolumeForUid(
+ stream,
+ direction,
+ flags,
+ opPackageName,
+ uid,
+ pid,
+ mContext.getApplicationInfo().targetSdkVersion);
+ }
+ } catch (IllegalArgumentException | SecurityException e) {
+ Slog.e(
+ TAG,
+ "Cannot adjust volume: direction=" + direction
+ + ", stream=" + stream + ", flags=" + flags
+ + ", opPackageName=" + opPackageName + ", uid=" + uid
+ + ", useSuggested=" + useSuggested
+ + ", previousFlagPlaySound=" + previousFlagPlaySound,
+ e);
}
- } else {
- mAudioManager.adjustStreamVolumeForUid(stream, direction, flags,
- opPackageName, uid, pid,
- mContext.getApplicationInfo().targetSdkVersion);
}
- } catch (IllegalArgumentException | SecurityException e) {
- Log.e(TAG, "Cannot adjust volume: direction=" + direction + ", stream="
- + stream + ", flags=" + flags + ", opPackageName=" + opPackageName
- + ", uid=" + uid + ", useSuggested=" + useSuggested
- + ", previousFlagPlaySound=" + previousFlagPlaySound, e);
- }
- }
- });
+ });
}
private void logCallbackException(
String msg, ISessionControllerCallbackHolder holder, Exception e) {
- Log.v(TAG, msg + ", this=" + this + ", callback package=" + holder.mPackageName
- + ", exception=" + e);
+ Slog.v(
+ TAG,
+ msg + ", this=" + this + ", callback package=" + holder.mPackageName
+ + ", exception=" + e);
}
private void pushPlaybackStateUpdate() {
@@ -1083,7 +1156,9 @@
throw new IllegalArgumentException(
"The media button receiver cannot be set to an activity.");
} else {
- Log.w(TAG, "Ignoring invalid media button receiver targeting an activity.");
+ Slog.w(
+ TAG,
+ "Ignoring invalid media button receiver targeting an activity.");
return;
}
}
@@ -1119,7 +1194,7 @@
if (CompatChanges.isChangeEnabled(THROW_FOR_INVALID_BROADCAST_RECEIVER, uid)) {
throw new IllegalArgumentException("Invalid component name: " + receiver);
} else {
- Log.w(
+ Slog.w(
TAG,
"setMediaButtonBroadcastReceiver(): "
+ "Ignoring invalid component name="
@@ -1258,7 +1333,7 @@
if (attributes != null) {
mAudioAttrs = attributes;
} else {
- Log.e(TAG, "Received null audio attributes, using existing attributes");
+ Slog.e(TAG, "Received null audio attributes, using existing attributes");
}
}
if (typeChanged) {
@@ -1320,7 +1395,7 @@
}
return true;
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in sendMediaRequest.", e);
+ Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
}
return false;
}
@@ -1343,7 +1418,7 @@
}
return true;
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in sendMediaRequest.", e);
+ Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
}
return false;
}
@@ -1356,7 +1431,7 @@
pid, uid, packageName, reason);
mCb.onCommand(packageName, pid, uid, command, args, cb);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in sendCommand.", e);
+ Slog.e(TAG, "Remote failure in sendCommand.", e);
}
}
@@ -1368,7 +1443,7 @@
pid, uid, packageName, reason);
mCb.onCustomAction(packageName, pid, uid, action, args);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in sendCustomAction.", e);
+ Slog.e(TAG, "Remote failure in sendCustomAction.", e);
}
}
@@ -1379,7 +1454,7 @@
pid, uid, packageName, reason);
mCb.onPrepare(packageName, pid, uid);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in prepare.", e);
+ Slog.e(TAG, "Remote failure in prepare.", e);
}
}
@@ -1391,7 +1466,7 @@
pid, uid, packageName, reason);
mCb.onPrepareFromMediaId(packageName, pid, uid, mediaId, extras);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in prepareFromMediaId.", e);
+ Slog.e(TAG, "Remote failure in prepareFromMediaId.", e);
}
}
@@ -1403,7 +1478,7 @@
pid, uid, packageName, reason);
mCb.onPrepareFromSearch(packageName, pid, uid, query, extras);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in prepareFromSearch.", e);
+ Slog.e(TAG, "Remote failure in prepareFromSearch.", e);
}
}
@@ -1414,7 +1489,7 @@
pid, uid, packageName, reason);
mCb.onPrepareFromUri(packageName, pid, uid, uri, extras);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in prepareFromUri.", e);
+ Slog.e(TAG, "Remote failure in prepareFromUri.", e);
}
}
@@ -1425,7 +1500,7 @@
pid, uid, packageName, reason);
mCb.onPlay(packageName, pid, uid);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in play.", e);
+ Slog.e(TAG, "Remote failure in play.", e);
}
}
@@ -1437,7 +1512,7 @@
pid, uid, packageName, reason);
mCb.onPlayFromMediaId(packageName, pid, uid, mediaId, extras);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in playFromMediaId.", e);
+ Slog.e(TAG, "Remote failure in playFromMediaId.", e);
}
}
@@ -1449,7 +1524,7 @@
pid, uid, packageName, reason);
mCb.onPlayFromSearch(packageName, pid, uid, query, extras);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in playFromSearch.", e);
+ Slog.e(TAG, "Remote failure in playFromSearch.", e);
}
}
@@ -1460,7 +1535,7 @@
pid, uid, packageName, reason);
mCb.onPlayFromUri(packageName, pid, uid, uri, extras);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in playFromUri.", e);
+ Slog.e(TAG, "Remote failure in playFromUri.", e);
}
}
@@ -1471,7 +1546,7 @@
pid, uid, packageName, reason);
mCb.onSkipToTrack(packageName, pid, uid, id);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in skipToTrack", e);
+ Slog.e(TAG, "Remote failure in skipToTrack", e);
}
}
@@ -1482,7 +1557,7 @@
pid, uid, packageName, reason);
mCb.onPause(packageName, pid, uid);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in pause.", e);
+ Slog.e(TAG, "Remote failure in pause.", e);
}
}
@@ -1493,7 +1568,7 @@
pid, uid, packageName, reason);
mCb.onStop(packageName, pid, uid);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in stop.", e);
+ Slog.e(TAG, "Remote failure in stop.", e);
}
}
@@ -1504,7 +1579,7 @@
pid, uid, packageName, reason);
mCb.onNext(packageName, pid, uid);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in next.", e);
+ Slog.e(TAG, "Remote failure in next.", e);
}
}
@@ -1515,7 +1590,7 @@
pid, uid, packageName, reason);
mCb.onPrevious(packageName, pid, uid);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in previous.", e);
+ Slog.e(TAG, "Remote failure in previous.", e);
}
}
@@ -1526,7 +1601,7 @@
pid, uid, packageName, reason);
mCb.onFastForward(packageName, pid, uid);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in fastForward.", e);
+ Slog.e(TAG, "Remote failure in fastForward.", e);
}
}
@@ -1537,7 +1612,7 @@
pid, uid, packageName, reason);
mCb.onRewind(packageName, pid, uid);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in rewind.", e);
+ Slog.e(TAG, "Remote failure in rewind.", e);
}
}
@@ -1548,7 +1623,7 @@
pid, uid, packageName, reason);
mCb.onSeekTo(packageName, pid, uid, pos);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in seekTo.", e);
+ Slog.e(TAG, "Remote failure in seekTo.", e);
}
}
@@ -1559,7 +1634,7 @@
pid, uid, packageName, reason);
mCb.onRate(packageName, pid, uid, rating);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in rate.", e);
+ Slog.e(TAG, "Remote failure in rate.", e);
}
}
@@ -1570,7 +1645,7 @@
pid, uid, packageName, reason);
mCb.onSetPlaybackSpeed(packageName, pid, uid, speed);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in setPlaybackSpeed.", e);
+ Slog.e(TAG, "Remote failure in setPlaybackSpeed.", e);
}
}
@@ -1587,7 +1662,7 @@
mCb.onAdjustVolume(packageName, pid, uid, direction);
}
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in adjustVolume.", e);
+ Slog.e(TAG, "Remote failure in adjustVolume.", e);
}
}
@@ -1598,7 +1673,7 @@
pid, uid, packageName, reason);
mCb.onSetVolumeTo(packageName, pid, uid, value);
} catch (RemoteException e) {
- Log.e(TAG, "Remote failure in setVolumeTo.", e);
+ Slog.e(TAG, "Remote failure in setVolumeTo.", e);
}
}
@@ -1641,8 +1716,10 @@
cb, packageName, Binder.getCallingUid(), () -> unregisterCallback(cb));
mControllerCallbackHolders.add(holder);
if (DEBUG) {
- Log.d(TAG, "registering controller callback " + cb + " from controller"
- + packageName);
+ Slog.d(
+ TAG,
+ "registering controller callback " + cb
+ + " from controller" + packageName);
}
// Avoid callback leaks
try {
@@ -1651,7 +1728,7 @@
cb.asBinder().linkToDeath(holder.mDeathMonitor, 0);
} catch (RemoteException e) {
unregisterCallback(cb);
- Log.w(TAG, "registerCallback failed to linkToDeath", e);
+ Slog.w(TAG, "registerCallback failed to linkToDeath", e);
}
}
}
@@ -1666,12 +1743,12 @@
cb.asBinder().unlinkToDeath(
mControllerCallbackHolders.get(index).mDeathMonitor, 0);
} catch (NoSuchElementException e) {
- Log.w(TAG, "error unlinking to binder death", e);
+ Slog.w(TAG, "error unlinking to binder death", e);
}
mControllerCallbackHolders.remove(index);
}
if (DEBUG) {
- Log.d(TAG, "unregistering callback " + cb.asBinder());
+ Slog.d(TAG, "unregistering callback " + cb.asBinder());
}
}
}
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 78077a8..c8dba80 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -228,8 +228,8 @@
return;
}
- MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute();
- if (TextUtils.equals(routeId, deviceRoute.getId())) {
+ MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
+ if (TextUtils.equals(routeId, selectedDeviceRoute.getId())) {
mBluetoothRouteController.transferTo(null);
} else {
mBluetoothRouteController.transferTo(routeId);
@@ -278,11 +278,11 @@
return null;
}
- MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute();
+ MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
SYSTEM_SESSION_ID, packageName).setSystemSession(true);
- builder.addSelectedRoute(deviceRoute.getId());
+ builder.addSelectedRoute(selectedDeviceRoute.getId());
for (MediaRoute2Info route : mBluetoothRouteController.getAllBluetoothRoutes()) {
builder.addTransferableRoute(route.getId());
}
@@ -314,7 +314,7 @@
MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
// We must have a device route in the provider info.
- builder.addRoute(mDeviceRouteController.getDeviceRoute());
+ builder.addRoute(mDeviceRouteController.getSelectedRoute());
for (MediaRoute2Info route : mBluetoothRouteController.getAllBluetoothRoutes()) {
builder.addRoute(route);
@@ -338,12 +338,12 @@
SYSTEM_SESSION_ID, "" /* clientPackageName */)
.setSystemSession(true);
- MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute();
- MediaRoute2Info selectedRoute = deviceRoute;
+ MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
+ MediaRoute2Info selectedRoute = selectedDeviceRoute;
MediaRoute2Info selectedBtRoute = mBluetoothRouteController.getSelectedRoute();
if (selectedBtRoute != null) {
selectedRoute = selectedBtRoute;
- builder.addTransferableRoute(deviceRoute.getId());
+ builder.addTransferableRoute(selectedDeviceRoute.getId());
}
mSelectedRouteId = selectedRoute.getId();
mDefaultRoute =
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index bae06347..c2a1b6b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4021,11 +4021,8 @@
Slog.e(TAG, "Failed to getApplicationInfo() in canUseFullScreenIntent()", e);
return false;
}
- final boolean showStickyHunIfDenied = mFlagResolver.isEnabled(
- SystemUiSystemPropertiesFlags.NotificationFlags
- .SHOW_STICKY_HUN_FOR_DENIED_FSI);
return checkUseFullScreenIntentPermission(attributionSource, applicationInfo,
- showStickyHunIfDenied /* isAppOpPermission */, false /* forDataDelivery */);
+ false /* forDataDelivery */);
}
@Override
@@ -7274,28 +7271,12 @@
notification.flags &= ~FLAG_FSI_REQUESTED_BUT_DENIED;
if (notification.fullScreenIntent != null) {
- final boolean forceDemoteFsiToStickyHun = mFlagResolver.isEnabled(
- SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE);
- if (forceDemoteFsiToStickyHun) {
+ final AttributionSource attributionSource =
+ new AttributionSource.Builder(notificationUid).setPackageName(pkg).build();
+ final boolean canUseFullScreenIntent = checkUseFullScreenIntentPermission(
+ attributionSource, ai, true /* forDataDelivery */);
+ if (!canUseFullScreenIntent) {
makeStickyHun(notification, pkg, userId);
- } else {
- final AttributionSource attributionSource =
- new AttributionSource.Builder(notificationUid).setPackageName(pkg).build();
- final boolean showStickyHunIfDenied = mFlagResolver.isEnabled(
- SystemUiSystemPropertiesFlags.NotificationFlags
- .SHOW_STICKY_HUN_FOR_DENIED_FSI);
- final boolean canUseFullScreenIntent = checkUseFullScreenIntentPermission(
- attributionSource, ai, showStickyHunIfDenied /* isAppOpPermission */,
- true /* forDataDelivery */);
- if (!canUseFullScreenIntent) {
- if (showStickyHunIfDenied) {
- makeStickyHun(notification, pkg, userId);
- } else {
- notification.fullScreenIntent = null;
- Slog.w(TAG, "Package " + pkg + ": Use of fullScreenIntent requires the"
- + "USE_FULL_SCREEN_INTENT permission");
- }
- }
}
}
@@ -7402,27 +7383,20 @@
}
private boolean checkUseFullScreenIntentPermission(@NonNull AttributionSource attributionSource,
- @NonNull ApplicationInfo applicationInfo, boolean isAppOpPermission,
+ @NonNull ApplicationInfo applicationInfo,
boolean forDataDelivery) {
if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q) {
return true;
}
- if (isAppOpPermission) {
- final int permissionResult;
- if (forDataDelivery) {
- permissionResult = mPermissionManager.checkPermissionForDataDelivery(
- permission.USE_FULL_SCREEN_INTENT, attributionSource, /* message= */ null);
- } else {
- permissionResult = mPermissionManager.checkPermissionForPreflight(
- permission.USE_FULL_SCREEN_INTENT, attributionSource);
- }
- return permissionResult == PermissionManager.PERMISSION_GRANTED;
+ final int permissionResult;
+ if (forDataDelivery) {
+ permissionResult = mPermissionManager.checkPermissionForDataDelivery(
+ permission.USE_FULL_SCREEN_INTENT, attributionSource, /* message= */ null);
} else {
- final int permissionResult = getContext().checkPermission(
- permission.USE_FULL_SCREEN_INTENT, attributionSource.getPid(),
- attributionSource.getUid());
- return permissionResult == PERMISSION_GRANTED;
+ permissionResult = mPermissionManager.checkPermissionForPreflight(
+ permission.USE_FULL_SCREEN_INTENT, attributionSource);
}
+ return permissionResult == PermissionManager.PERMISSION_GRANTED;
}
private void checkRemoteViews(String pkg, String tag, int id, Notification notification) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index d2e980b..9a6ea2c 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -530,16 +530,13 @@
this.timeout_millis = p.r.getSbn().getNotification().getTimeoutAfter();
this.is_non_dismissible = NotificationRecordLogger.isNonDismissible(p.r);
- final boolean isStickyHunFlagEnabled = SystemUiSystemPropertiesFlags.getResolver()
- .isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI);
-
final boolean hasFullScreenIntent =
p.r.getSbn().getNotification().fullScreenIntent != null;
final boolean hasFsiRequestedButDeniedFlag = (p.r.getSbn().getNotification().flags
& Notification.FLAG_FSI_REQUESTED_BUT_DENIED) != 0;
- this.fsi_state = NotificationRecordLogger.getFsiState(isStickyHunFlagEnabled,
+ this.fsi_state = NotificationRecordLogger.getFsiState(
hasFullScreenIntent, hasFsiRequestedButDeniedFlag, eventType);
this.is_locked = p.r.isLocked();
@@ -587,13 +584,10 @@
* @return FrameworkStatsLog enum of the state of the full screen intent posted with this
* notification.
*/
- static int getFsiState(boolean isStickyHunFlagEnabled,
- boolean hasFullScreenIntent,
+ static int getFsiState(boolean hasFullScreenIntent,
boolean hasFsiRequestedButDeniedFlag,
NotificationReportedEvent eventType) {
-
- if (!isStickyHunFlagEnabled
- || eventType == NotificationReportedEvent.NOTIFICATION_UPDATED) {
+ if (eventType == NotificationReportedEvent.NOTIFICATION_UPDATED) {
// Zeroes in protos take zero bandwidth, but non-zero numbers take bandwidth,
// so we should log 0 when possible.
return 0;
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 783e9bb..252664a 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -2143,10 +2143,7 @@
* @return State of the full screen intent permission for this package.
*/
@VisibleForTesting
- int getFsiState(String pkg, int uid, boolean requestedFSIPermission, boolean isFlagEnabled) {
- if (!isFlagEnabled) {
- return 0;
- }
+ int getFsiState(String pkg, int uid, boolean requestedFSIPermission) {
if (!requestedFSIPermission) {
return PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
}
@@ -2167,10 +2164,8 @@
* the user.
*/
@VisibleForTesting
- boolean isFsiPermissionUserSet(String pkg, int uid, int fsiState, int currentPermissionFlags,
- boolean isStickyHunFlagEnabled) {
- if (!isStickyHunFlagEnabled
- || fsiState == PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED) {
+ boolean isFsiPermissionUserSet(String pkg, int uid, int fsiState, int currentPermissionFlags) {
+ if (fsiState == PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED) {
return false;
}
return (currentPermissionFlags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
@@ -2213,22 +2208,18 @@
pkgsWithPermissionsToHandle.remove(key);
}
- final boolean isStickyHunFlagEnabled = SystemUiSystemPropertiesFlags.getResolver()
- .isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI);
-
final boolean requestedFSIPermission = mPermissionHelper.hasRequestedPermission(
android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg, r.uid);
- final int fsiState = getFsiState(r.pkg, r.uid, requestedFSIPermission,
- isStickyHunFlagEnabled);
+ final int fsiState = getFsiState(r.pkg, r.uid, requestedFSIPermission);
final int currentPermissionFlags = mPm.getPermissionFlags(
android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg,
UserHandle.getUserHandleForUid(r.uid));
final boolean fsiIsUserSet =
- isFsiPermissionUserSet(r.pkg, r.uid, fsiState, currentPermissionFlags,
- isStickyHunFlagEnabled);
+ isFsiPermissionUserSet(r.pkg, r.uid, fsiState,
+ currentPermissionFlags);
events.add(FrameworkStatsLog.buildStatsEvent(
PACKAGE_NOTIFICATION_PREFERENCES,
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 79cd2a0..92d469c 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -259,6 +259,19 @@
*/
boolean shouldFilterApplicationIncludingUninstalled(@Nullable PackageStateInternal ps,
int callingUid, int userId);
+
+ /**
+ * Different from
+ * {@link #shouldFilterApplicationIncludingUninstalled(PackageStateInternal, int, int)}, the
+ * function returns {@code true} if:
+ * <ul>
+ * <li>The target package is not archived.
+ * <li>The package cannot be found in the device or has been uninstalled in the current user.
+ * </ul>
+ */
+ boolean shouldFilterApplicationIncludingUninstalledNotArchived(
+ @Nullable PackageStateInternal ps,
+ int callingUid, int userId);
/**
* Different from {@link #shouldFilterApplication(SharedUserSetting, int, int)}, the function
* returns {@code true} if packages with the same shared user are all uninstalled in the current
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 11a6d1b..e5c4ccc 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2455,7 +2455,8 @@
*/
public final boolean shouldFilterApplication(@Nullable PackageStateInternal ps,
int callingUid, @Nullable ComponentName component,
- @PackageManager.ComponentType int componentType, int userId, boolean filterUninstall) {
+ @PackageManager.ComponentType int componentType, int userId, boolean filterUninstall,
+ boolean filterArchived) {
if (Process.isSdkSandboxUid(callingUid)) {
int clientAppUid = Process.getAppUidForSdkSandboxUid(callingUid);
// SDK sandbox should be able to see it's client app
@@ -2469,14 +2470,20 @@
}
final String instantAppPkgName = getInstantAppPackageName(callingUid);
final boolean callerIsInstantApp = instantAppPkgName != null;
+ final boolean packageArchivedForUser = ps != null && PackageArchiver.isArchived(
+ ps.getUserStateOrDefault(userId));
// Don't treat hiddenUntilInstalled as an uninstalled state, phone app needs to access
// these hidden application details to customize carrier apps. Also, allowing the system
// caller accessing to application across users.
if (ps == null
|| (filterUninstall
- && !isSystemOrRootOrShell(callingUid)
- && !ps.isHiddenUntilInstalled()
- && !ps.getUserStateOrDefault(userId).isInstalled())) {
+ && !isSystemOrRootOrShell(callingUid)
+ && !ps.isHiddenUntilInstalled()
+ && !ps.getUserStateOrDefault(userId).isInstalled()
+ // Archived packages behave like uninstalled packages. So if filterUninstall is
+ // set to true, we dismiss filtering some uninstalled package only if it is
+ // archived and filterArchived is set as false.
+ && (!packageArchivedForUser || filterArchived))) {
// If caller is instant app or sdk sandbox and ps is null, pretend the application
// exists, but, needs to be filtered
return (callerIsInstantApp || filterUninstall || Process.isSdkSandboxUid(callingUid));
@@ -2524,7 +2531,20 @@
}
/**
- * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean)
+ * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+ * boolean)
+ */
+ public final boolean shouldFilterApplication(@Nullable PackageStateInternal ps,
+ int callingUid, @Nullable ComponentName component,
+ @PackageManager.ComponentType int componentType, int userId, boolean filterUninstall) {
+ return shouldFilterApplication(
+ ps, callingUid, component, componentType, userId, filterUninstall,
+ true /* filterArchived */);
+ }
+
+ /**
+ * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+ * boolean)
*/
public final boolean shouldFilterApplication(@Nullable PackageStateInternal ps,
int callingUid, @Nullable ComponentName component,
@@ -2534,7 +2554,8 @@
}
/**
- * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean)
+ * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+ * boolean)
*/
public final boolean shouldFilterApplication(
@Nullable PackageStateInternal ps, int callingUid, int userId) {
@@ -2543,7 +2564,8 @@
}
/**
- * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean)
+ * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+ * boolean)
*/
public final boolean shouldFilterApplication(@NonNull SharedUserSetting sus,
int callingUid, int userId) {
@@ -2558,7 +2580,8 @@
}
/**
- * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean)
+ * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+ * boolean)
*/
public final boolean shouldFilterApplicationIncludingUninstalled(
@Nullable PackageStateInternal ps, int callingUid, int userId) {
@@ -2567,7 +2590,19 @@
}
/**
- * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean)
+ * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+ * boolean)
+ */
+ public final boolean shouldFilterApplicationIncludingUninstalledNotArchived(
+ @Nullable PackageStateInternal ps, int callingUid, int userId) {
+ return shouldFilterApplication(
+ ps, callingUid, null, TYPE_UNKNOWN, userId, true /* filterUninstall */,
+ false /* filterArchived */);
+ }
+
+ /**
+ * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+ * boolean)
*/
public final boolean shouldFilterApplicationIncludingUninstalled(
@NonNull SharedUserSetting sus, int callingUid, int userId) {
@@ -5015,7 +5050,7 @@
String installerPackageName = installSource.mInstallerPackageName;
if (installerPackageName != null) {
final PackageStateInternal ps = mSettings.getPackage(installerPackageName);
- if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid,
+ if (ps == null || shouldFilterApplicationIncludingUninstalledNotArchived(ps, callingUid,
UserHandle.getUserId(callingUid))) {
installerPackageName = null;
}
@@ -5033,7 +5068,8 @@
return InstallSource.EMPTY;
}
- if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)) {
+ if (ps == null || shouldFilterApplicationIncludingUninstalledNotArchived(ps, callingUid,
+ userId)) {
return null;
}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 65c6329..3b3d79e 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -440,7 +440,7 @@
if (outInfo != null) {
// Remember which users are affected, before the installed states are modified
outInfo.mRemovedUsers = (systemApp || userId == UserHandle.USER_ALL)
- ? ps.queryInstalledUsers(allUserHandles, /* installed= */true)
+ ? ps.queryUsersInstalledOrHasData(allUserHandles)
: new int[]{userId};
}
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index eff6157..d2a4c27 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -37,6 +37,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.pm.ApplicationInfo;
import android.content.pm.ArchivedActivityParcel;
import android.content.pm.ArchivedPackageParcel;
import android.content.pm.LauncherActivityInfo;
@@ -140,6 +141,8 @@
}
snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
"archiveApp");
+ verifyUninstallPermissions();
+
CompletableFuture<ArchiveState> archiveStateFuture;
try {
archiveStateFuture = createArchiveState(packageName, userId);
@@ -182,6 +185,7 @@
throws PackageManager.NameNotFoundException {
PackageStateInternal ps = getPackageState(packageName, mPm.snapshotComputer(),
Binder.getCallingUid(), userId);
+ verifyNotSystemApp(ps.getFlags());
String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
verifyInstaller(responsibleInstallerPackage, userId);
verifyOptOutStatus(packageName,
@@ -316,6 +320,13 @@
return intentReceivers != null && !intentReceivers.getList().isEmpty();
}
+ private void verifyNotSystemApp(int flags) throws PackageManager.NameNotFoundException {
+ if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0 || (
+ (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) {
+ throw new PackageManager.NameNotFoundException("System apps cannot be archived.");
+ }
+ }
+
/**
* Returns true if the app is archivable.
*/
@@ -337,6 +348,11 @@
throw new ParcelableException(e);
}
+ if ((ps.getFlags() & ApplicationInfo.FLAG_SYSTEM) != 0 || (
+ (ps.getFlags() & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) {
+ return false;
+ }
+
if (isAppOptedOutOfArchiving(packageName, ps.getAppId())) {
return false;
}
@@ -372,9 +388,11 @@
void requestUnarchive(
@NonNull String packageName,
@NonNull String callerPackageName,
+ @NonNull IntentSender statusReceiver,
@NonNull UserHandle userHandle) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(callerPackageName);
+ Objects.requireNonNull(statusReceiver);
Objects.requireNonNull(userHandle);
Computer snapshot = mPm.snapshotComputer();
@@ -385,6 +403,8 @@
}
snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
"unarchiveApp");
+ verifyInstallPermissions();
+
PackageStateInternal ps;
try {
ps = getPackageState(packageName, snapshot, binderUid, userId);
@@ -400,9 +420,12 @@
packageName)));
}
+ // TODO(b/305902395) Introduce a confirmation dialog if the requestor only holds
+ // REQUEST_INSTALL permission.
int draftSessionId;
try {
- draftSessionId = createDraftSession(packageName, installerPackage, userId);
+ draftSessionId = createDraftSession(packageName, installerPackage, statusReceiver,
+ userId);
} catch (RuntimeException e) {
if (e.getCause() instanceof IOException) {
throw ExceptionUtils.wrap((IOException) e.getCause());
@@ -415,11 +438,36 @@
() -> unarchiveInternal(packageName, userHandle, installerPackage, draftSessionId));
}
- private int createDraftSession(String packageName, String installerPackage, int userId) {
+ private void verifyInstallPermissions() {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
+ != PackageManager.PERMISSION_GRANTED && mContext.checkCallingOrSelfPermission(
+ Manifest.permission.REQUEST_INSTALL_PACKAGES)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("You need the com.android.permission.INSTALL_PACKAGES "
+ + "or com.android.permission.REQUEST_INSTALL_PACKAGES permission to request "
+ + "an unarchival.");
+ }
+ }
+
+ private void verifyUninstallPermissions() {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES)
+ != PackageManager.PERMISSION_GRANTED && mContext.checkCallingOrSelfPermission(
+ Manifest.permission.REQUEST_DELETE_PACKAGES)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("You need the com.android.permission.DELETE_PACKAGES "
+ + "or com.android.permission.REQUEST_DELETE_PACKAGES permission to request "
+ + "an archival.");
+ }
+ }
+
+ private int createDraftSession(String packageName, String installerPackage,
+ IntentSender statusReceiver, int userId) {
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
sessionParams.setAppPackageName(packageName);
sessionParams.installFlags = INSTALL_UNARCHIVE_DRAFT;
+ sessionParams.unarchiveIntentSender = statusReceiver;
+
int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
// Handles case of repeated unarchival calls for the same package.
int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index af43a8b..c9663fc 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -16,8 +16,14 @@
package com.android.server.pm;
+import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO;
import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_NO_CONNECTIVITY;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_GENERIC_ERROR;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
import static android.os.Process.INVALID_UID;
import static android.os.Process.SYSTEM_UID;
@@ -37,6 +43,7 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PackageDeleteObserver;
+import android.app.PendingIntent;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
@@ -56,6 +63,7 @@
import android.content.pm.PackageInstaller.InstallConstraintsResult;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.PackageInstaller.UnarchivalStatus;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
@@ -71,6 +79,7 @@
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelableException;
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteCallbackList;
@@ -1630,8 +1639,10 @@
public void requestUnarchive(
@NonNull String packageName,
@NonNull String callerPackageName,
+ @NonNull IntentSender statusReceiver,
@NonNull UserHandle userHandle) {
- mPackageArchiver.requestUnarchive(packageName, callerPackageName, userHandle);
+ mPackageArchiver.requestUnarchive(packageName, callerPackageName, statusReceiver,
+ userHandle);
}
@Override
@@ -1689,6 +1700,102 @@
}
}
+ // TODO(b/307299702) Implement error dialog and propagate userActionIntent.
+ @Override
+ public void reportUnarchivalStatus(
+ int unarchiveId,
+ @UnarchivalStatus int status,
+ long requiredStorageBytes,
+ @Nullable PendingIntent userActionIntent,
+ @NonNull UserHandle userHandle) {
+ verifyReportUnarchiveStatusInput(
+ status, requiredStorageBytes, userActionIntent, userHandle);
+
+ int userId = userHandle.getIdentifier();
+ int binderUid = Binder.getCallingUid();
+
+ synchronized (mSessions) {
+ PackageInstallerSession session = mSessions.get(unarchiveId);
+ if (session == null || session.userId != userId
+ || session.params.appPackageName == null) {
+ throw new ParcelableException(new PackageManager.NameNotFoundException(
+ TextUtils.formatSimple(
+ "No valid session with unarchival ID %s found for user %s.",
+ unarchiveId, userId)));
+ }
+
+ if (!isCallingUidOwner(session)) {
+ throw new SecurityException(TextUtils.formatSimple(
+ "The caller UID %s does not have access to the session with unarchiveId "
+ + "%d.",
+ binderUid, unarchiveId));
+ }
+
+ IntentSender unarchiveIntentSender = session.params.unarchiveIntentSender;
+ if (unarchiveIntentSender == null) {
+ throw new IllegalStateException(
+ TextUtils.formatSimple(
+ "Unarchival status for ID %s has already been set or a "
+ + "session has been created for it already by the "
+ + "caller.",
+ unarchiveId));
+ }
+
+ // Execute expensive calls outside the sync block.
+ mPm.mHandler.post(
+ () -> notifyUnarchivalListener(status, session.params.appPackageName,
+ unarchiveIntentSender));
+ session.params.unarchiveIntentSender = null;
+ if (status != UNARCHIVAL_OK) {
+ Binder.withCleanCallingIdentity(session::abandon);
+ }
+ }
+ }
+
+ private static void verifyReportUnarchiveStatusInput(int status, long requiredStorageBytes,
+ @Nullable PendingIntent userActionIntent,
+ @NonNull UserHandle userHandle) {
+ Objects.requireNonNull(userHandle);
+ if (status == UNARCHIVAL_ERROR_USER_ACTION_NEEDED) {
+ Objects.requireNonNull(userActionIntent);
+ }
+ if (status == UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE && requiredStorageBytes <= 0) {
+ throw new IllegalStateException(
+ "Insufficient storage error set, but requiredStorageBytes unspecified.");
+ }
+ if (status != UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE && requiredStorageBytes > 0) {
+ throw new IllegalStateException(
+ TextUtils.formatSimple("requiredStorageBytes set, but error is %s.", status)
+ );
+ }
+ if (!List.of(
+ UNARCHIVAL_OK,
+ UNARCHIVAL_ERROR_USER_ACTION_NEEDED,
+ UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE,
+ UNARCHIVAL_ERROR_NO_CONNECTIVITY,
+ UNARCHIVAL_GENERIC_ERROR).contains(status)) {
+ throw new IllegalStateException("Invalid status code passed " + status);
+ }
+ }
+
+ private void notifyUnarchivalListener(int status, String packageName,
+ IntentSender unarchiveIntentSender) {
+ final Intent fillIn = new Intent();
+ fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName);
+ fillIn.putExtra(PackageInstaller.EXTRA_UNARCHIVE_STATUS, status);
+ // TODO(b/307299702) Attach failure dialog with EXTRA_INTENT and requiredStorageBytes here.
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_DENIED);
+ try {
+ unarchiveIntentSender.sendIntent(mContext, 0, fillIn, /* onFinished= */ null,
+ /* handler= */ null, /* requiredPermission= */ null,
+ options.toBundle());
+ } catch (SendIntentException e) {
+ Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e);
+ }
+ }
+
private static int getSessionCount(SparseArray<PackageInstallerSession> sessions,
int installerUid) {
int count = 0;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index d38c0a0..47d1df5 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -3586,21 +3586,30 @@
params.setDontKillApp(false);
}
+ boolean existingSplitReplacedOrRemoved = false;
// Inherit splits if not overridden.
if (!ArrayUtils.isEmpty(existing.getSplitNames())) {
for (int i = 0; i < existing.getSplitNames().length; i++) {
final String splitName = existing.getSplitNames()[i];
final File splitFile = new File(existing.getSplitApkPaths()[i]);
final boolean splitRemoved = removeSplitList.contains(splitName);
- if (!stagedSplits.contains(splitName) && !splitRemoved) {
+ final boolean splitReplaced = stagedSplits.contains(splitName);
+ if (!splitReplaced && !splitRemoved) {
inheritFileLocked(splitFile);
// Collect the requiredSplitTypes and staged splitTypes from splits
CollectionUtils.addAll(requiredSplitTypes,
existing.getRequiredSplitTypes()[i]);
CollectionUtils.addAll(stagedSplitTypes, existing.getSplitTypes()[i]);
+ } else {
+ existingSplitReplacedOrRemoved = true;
}
}
}
+ if (existingSplitReplacedOrRemoved
+ && (params.installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) {
+ // Some splits are being replaced or removed. Make sure the app is restarted.
+ params.setDontKillApp(false);
+ }
// Inherit compiled oat directory.
final File packageInstallDir = (new File(appInfo.getBaseCodePath())).getParentFile();
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 6f45d2b..f992bd8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -4689,10 +4689,12 @@
final int translatedUserId =
translateUserId(userId, UserHandle.USER_SYSTEM, "runArchive");
+ final LocalIntentReceiver receiver = new LocalIntentReceiver();
try {
mInterface.getPackageInstaller().requestUnarchive(packageName,
- /* callerPackageName= */ "", new UserHandle(translatedUserId));
+ /* callerPackageName= */ "", receiver.getIntentSender(),
+ new UserHandle(translatedUserId));
} catch (Exception e) {
pw.println("Failure [" + e.getMessage() + "]");
return 1;
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 72090f2..b50d0a0 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -779,6 +779,10 @@
return readUserState(userId).isInstalled();
}
+ boolean isArchived(int userId) {
+ return PackageArchiver.isArchived(readUserState(userId));
+ }
+
int getInstallReason(int userId) {
return readUserState(userId).getInstallReason();
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4e14c90..8556317 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1592,6 +1592,19 @@
*/
private void showConfirmCredentialToDisableQuietMode(
@UserIdInt int userId, @Nullable IntentSender target) {
+ if (android.app.admin.flags.Flags.quietModeCredentialBugFix()) {
+ // TODO (b/308121702) It may be brittle to rely on user states to check profile state
+ int state;
+ synchronized (mUserStates) {
+ state = mUserStates.get(userId, UserState.STATE_NONE);
+ }
+ if (state != UserState.STATE_NONE) {
+ Slog.i(LOG_TAG,
+ "showConfirmCredentialToDisableQuietMode() called too early, user " + userId
+ + " is still alive.");
+ return;
+ }
+ }
// otherwise, we show a profile challenge to trigger decryption of the user
final KeyguardManager km = (KeyguardManager) mContext.getSystemService(
Context.KEYGUARD_SERVICE);
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index ee3b746..dd39fb0 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -32,8 +32,6 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
-import android.os.WorkDuration;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseIntArray;
@@ -197,9 +195,6 @@
private static native void nativeSetMode(long halPtr, int mode, boolean enabled);
- private static native void nativeReportActualWorkDuration(long halPtr,
- WorkDuration[] workDurations);
-
/** Wrapper for HintManager.nativeInit */
public void halInit() {
nativeInit();
@@ -257,10 +252,6 @@
nativeSetMode(halPtr, mode, enabled);
}
- /** Wrapper for HintManager.nativeReportActualWorkDuration */
- public void halReportActualWorkDuration(long halPtr, WorkDuration[] workDurations) {
- nativeReportActualWorkDuration(halPtr, workDurations);
- }
}
@VisibleForTesting
@@ -633,52 +624,6 @@
}
}
- @Override
- public void reportActualWorkDuration2(WorkDuration[] workDurations) {
- synchronized (this) {
- if (mHalSessionPtr == 0 || !mUpdateAllowed) {
- return;
- }
- Preconditions.checkArgument(workDurations.length != 0, "the count"
- + " of work durations shouldn't be 0.");
- for (WorkDuration workDuration : workDurations) {
- validateWorkDuration(workDuration);
- }
- mNativeWrapper.halReportActualWorkDuration(mHalSessionPtr, workDurations);
- }
- }
-
- void validateWorkDuration(WorkDuration workDuration) {
- if (DEBUG) {
- Slogf.d(TAG, "WorkDuration(" + workDuration.getTimestampNanos() + ", "
- + workDuration.getWorkPeriodStartTimestampNanos() + ", "
- + workDuration.getActualTotalDurationNanos() + ", "
- + workDuration.getActualCpuDurationNanos() + ", "
- + workDuration.getActualGpuDurationNanos() + ")");
- }
- if (workDuration.getWorkPeriodStartTimestampNanos() <= 0) {
- throw new IllegalArgumentException(
- TextUtils.formatSimple(
- "Work period start timestamp (%d) should be greater than 0",
- workDuration.getWorkPeriodStartTimestampNanos()));
- }
- if (workDuration.getActualTotalDurationNanos() <= 0) {
- throw new IllegalArgumentException(
- TextUtils.formatSimple("Actual total duration (%d) should be greater than 0",
- workDuration.getActualTotalDurationNanos()));
- }
- if (workDuration.getActualCpuDurationNanos() <= 0) {
- throw new IllegalArgumentException(
- TextUtils.formatSimple("Actual CPU duration (%d) should be greater than 0",
- workDuration.getActualCpuDurationNanos()));
- }
- if (workDuration.getActualGpuDurationNanos() < 0) {
- throw new IllegalArgumentException(
- TextUtils.formatSimple("Actual GPU duration (%d) should be non negative",
- workDuration.getActualGpuDurationNanos()));
- }
- }
-
private void onProcStateChanged(boolean updateAllowed) {
updateHintAllowed(updateAllowed);
}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 17499bb7..940feb5 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -24,12 +24,14 @@
import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_UNSUPPORTED;
import static android.hardware.graphics.common.Hdr.DOLBY_VISION;
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkStats.METERED_YES;
import static android.net.NetworkTemplate.MATCH_ETHERNET;
import static android.net.NetworkTemplate.MATCH_MOBILE;
+import static android.net.NetworkTemplate.MATCH_PROXY;
import static android.net.NetworkTemplate.MATCH_WIFI;
import static android.net.NetworkTemplate.OEM_MANAGED_ALL;
import static android.net.NetworkTemplate.OEM_MANAGED_PAID;
@@ -488,6 +490,7 @@
case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG:
case FrameworkStatsLog.MOBILE_BYTES_TRANSFER:
case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG:
+ case FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG:
case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED:
case FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER:
case FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER:
@@ -973,21 +976,33 @@
if (DEBUG) {
Slog.d(TAG, "Registering NetworkStats pullers with statsd");
}
+
+ boolean canQueryTypeProxy = canQueryNetworkStatsForTypeProxy();
+
// Initialize NetworkStats baselines.
- mNetworkStatsBaselines.addAll(
- collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER));
- mNetworkStatsBaselines.addAll(
- collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG));
- mNetworkStatsBaselines.addAll(
- collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.MOBILE_BYTES_TRANSFER));
- mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
- FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG));
- mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
- FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED));
- mNetworkStatsBaselines.addAll(
- collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER));
- mNetworkStatsBaselines.addAll(
- collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER));
+ synchronized (mDataBytesTransferLock) {
+ mNetworkStatsBaselines.addAll(
+ collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER));
+ mNetworkStatsBaselines.addAll(
+ collectNetworkStatsSnapshotForAtom(
+ FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG));
+ mNetworkStatsBaselines.addAll(
+ collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.MOBILE_BYTES_TRANSFER));
+ mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
+ FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG));
+ mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
+ FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED));
+ mNetworkStatsBaselines.addAll(
+ collectNetworkStatsSnapshotForAtom(
+ FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER));
+ mNetworkStatsBaselines.addAll(
+ collectNetworkStatsSnapshotForAtom(
+ FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER));
+ if (canQueryTypeProxy) {
+ mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
+ FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG));
+ }
+ }
// Listen to subscription changes to record historical subscriptions that activated before
// pulling, this is used by {@code DATA_USAGE_BYTES_TRANSFER}.
@@ -1001,6 +1016,9 @@
registerBytesTransferByTagAndMetered();
registerDataUsageBytesTransfer();
registerOemManagedBytesTransfer();
+ if (canQueryTypeProxy) {
+ registerProxyBytesTransferBackground();
+ }
}
private void initAndRegisterDeferredPullers() {
@@ -1171,6 +1189,18 @@
}
break;
}
+ case FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG: {
+ final NetworkStats stats = getUidNetworkStatsSnapshotForTemplate(
+ new NetworkTemplate.Builder(MATCH_PROXY).build(), /*includeTags=*/true);
+ if (stats != null) {
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUidTagAndMetered(stats),
+ new int[]{TRANSPORT_BLUETOOTH},
+ /*slicedByFgbg=*/true, /*slicedByTag=*/false,
+ /*slicedByMetered=*/false, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ /*subInfo=*/null, OEM_MANAGED_ALL, /*isTypeProxy=*/true));
+ }
+ break;
+ }
case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED: {
final NetworkStats wifiStats = getUidNetworkStatsSnapshotForTemplate(
new NetworkTemplate.Builder(MATCH_WIFI).build(), /*includeTags=*/true);
@@ -1183,7 +1213,7 @@
new int[]{TRANSPORT_WIFI, TRANSPORT_CELLULAR},
/*slicedByFgbg=*/false, /*slicedByTag=*/true,
/*slicedByMetered=*/true, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- /*subInfo=*/null, OEM_MANAGED_ALL));
+ /*subInfo=*/null, OEM_MANAGED_ALL, /*isTypeProxy=*/false));
}
break;
}
@@ -1225,7 +1255,7 @@
final NetworkStatsExt diff = new NetworkStatsExt(
removeEmptyEntries(item.stats.subtract(baseline.stats)), item.transports,
item.slicedByFgbg, item.slicedByTag, item.slicedByMetered, item.ratType,
- item.subInfo, item.oemManaged);
+ item.subInfo, item.oemManaged, item.isTypeProxy);
// If no diff, skip.
if (!diff.stats.iterator().hasNext()) continue;
@@ -1363,7 +1393,7 @@
ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
new int[]{transport}, /*slicedByFgbg=*/true, /*slicedByTag=*/false,
/*slicedByMetered=*/false, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- /*subInfo=*/null, oemManaged));
+ /*subInfo=*/null, oemManaged, /*isTypeProxy=*/false));
}
}
}
@@ -1392,6 +1422,21 @@
}
/**
+ * Check if it is possible to query NetworkStats for TYPE_PROXY. This should only be possible
+ * if the build includes r.android.com/2828315
+ * @return true if querying for TYPE_PROXY is allowed
+ */
+ private static boolean canQueryNetworkStatsForTypeProxy() {
+ try {
+ new NetworkTemplate.Builder(MATCH_PROXY).build();
+ return true;
+ } catch (IllegalArgumentException e) {
+ Slog.w(TAG, "Querying network stats for TYPE_PROXY is not allowed");
+ return false;
+ }
+ }
+
+ /**
* Create a snapshot of NetworkStats since boot for the given template, but add 1 bucket
* duration before boot as a buffer to ensure at least one full bucket will be included.
* Note that this should be only used to calculate diff since the snapshot might contains
@@ -1450,7 +1495,7 @@
ret.add(new NetworkStatsExt(sliceNetworkStatsByFgbg(stats),
new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true,
/*slicedByTag=*/false, /*slicedByMetered=*/false, ratType, subInfo,
- OEM_MANAGED_ALL));
+ OEM_MANAGED_ALL, /*isTypeProxy=*/false));
}
}
return ret;
@@ -1600,6 +1645,19 @@
);
}
+ private void registerProxyBytesTransferBackground() {
+ int tagId = FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG;
+ PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+ .setAdditiveFields(new int[]{3, 4, 5, 6})
+ .build();
+ mStatsManager.setPullAtomCallback(
+ tagId,
+ metadata,
+ DIRECT_EXECUTOR,
+ mStatsCallbackImpl
+ );
+ }
+
private void registerBytesTransferByTagAndMetered() {
int tagId = FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED;
PullAtomMetadata metadata = new PullAtomMetadata.Builder()
diff --git a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsExt.java b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsExt.java
index 7dbba0d..512f0bf 100644
--- a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsExt.java
+++ b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsExt.java
@@ -42,15 +42,17 @@
public final int oemManaged;
@Nullable
public final SubInfo subInfo;
+ public final boolean isTypeProxy; // True if matching ConnectivityManager#TYPE_PROXY
public NetworkStatsExt(@NonNull NetworkStats stats, int[] transports, boolean slicedByFgbg) {
this(stats, transports, slicedByFgbg, /*slicedByTag=*/false, /*slicedByMetered=*/false,
- TelephonyManager.NETWORK_TYPE_UNKNOWN, /*subInfo=*/null, OEM_MANAGED_ALL);
+ TelephonyManager.NETWORK_TYPE_UNKNOWN, /*subInfo=*/null,
+ OEM_MANAGED_ALL, /*isTypeProxy=*/false);
}
public NetworkStatsExt(@NonNull NetworkStats stats, int[] transports, boolean slicedByFgbg,
boolean slicedByTag, boolean slicedByMetered, int ratType,
- @Nullable SubInfo subInfo, int oemManaged) {
+ @Nullable SubInfo subInfo, int oemManaged, boolean isTypeProxy) {
this.stats = stats;
// Sort transports array so that we can test for equality without considering order.
@@ -63,6 +65,7 @@
this.ratType = ratType;
this.subInfo = subInfo;
this.oemManaged = oemManaged;
+ this.isTypeProxy = isTypeProxy;
}
/**
@@ -72,6 +75,6 @@
return Arrays.equals(transports, other.transports) && slicedByFgbg == other.slicedByFgbg
&& slicedByTag == other.slicedByTag && slicedByMetered == other.slicedByMetered
&& ratType == other.ratType && Objects.equals(subInfo, other.subInfo)
- && oemManaged == other.oemManaged;
+ && oemManaged == other.oemManaged && isTypeProxy == other.isTypeProxy;
}
}
diff --git a/services/core/java/com/android/server/timedetector/TEST_MAPPING b/services/core/java/com/android/server/timedetector/TEST_MAPPING
index 5c37680..17d327e 100644
--- a/services/core/java/com/android/server/timedetector/TEST_MAPPING
+++ b/services/core/java/com/android/server/timedetector/TEST_MAPPING
@@ -7,10 +7,7 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
- }
- ],
- // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
- "postsubmit": [
+ },
{
"name": "FrameworksTimeServicesTests"
}
diff --git a/services/core/java/com/android/server/timezonedetector/TEST_MAPPING b/services/core/java/com/android/server/timezonedetector/TEST_MAPPING
index 63dd7b4..358618a 100644
--- a/services/core/java/com/android/server/timezonedetector/TEST_MAPPING
+++ b/services/core/java/com/android/server/timezonedetector/TEST_MAPPING
@@ -7,15 +7,15 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "FrameworksTimeServicesTests"
}
],
// TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
"postsubmit": [
{
"name": "CtsLocationTimeZoneManagerHostTest"
- },
- {
- "name": "FrameworksTimeServicesTests"
}
]
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index ee46ce1..b3672ec 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -16,6 +16,8 @@
package com.android.server.webkit;
+import static android.webkit.Flags.updateServiceV2;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -51,7 +53,7 @@
private static final String TAG = "WebViewUpdateService";
private BroadcastReceiver mWebViewUpdatedReceiver;
- private WebViewUpdateServiceImpl mImpl;
+ private WebViewUpdateServiceInterface mImpl;
static final int PACKAGE_CHANGED = 0;
static final int PACKAGE_ADDED = 1;
@@ -60,7 +62,11 @@
public WebViewUpdateService(Context context) {
super(context);
- mImpl = new WebViewUpdateServiceImpl(context, SystemImpl.getInstance());
+ if (updateServiceV2()) {
+ mImpl = new WebViewUpdateServiceImpl2(context, SystemImpl.getInstance());
+ } else {
+ mImpl = new WebViewUpdateServiceImpl(context, SystemImpl.getInstance());
+ }
}
@Override
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 43d62aa..cfdef14 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -63,7 +63,7 @@
*
* @hide
*/
-class WebViewUpdateServiceImpl {
+class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface {
private static final String TAG = WebViewUpdateServiceImpl.class.getSimpleName();
private static class WebViewPackageMissingException extends Exception {
@@ -112,7 +112,8 @@
mSystemInterface = systemInterface;
}
- void packageStateChanged(String packageName, int changedState, int userId) {
+ @Override
+ public void packageStateChanged(String packageName, int changedState, int userId) {
// We don't early out here in different cases where we could potentially early-out (e.g. if
// we receive PACKAGE_CHANGED for another user than the system user) since that would
// complicate this logic further and open up for more edge cases.
@@ -163,7 +164,8 @@
}
}
- void prepareWebViewInSystemServer() {
+ @Override
+ public void prepareWebViewInSystemServer() {
mSystemInterface.notifyZygote(isMultiProcessEnabled());
try {
synchronized (mLock) {
@@ -210,7 +212,8 @@
mSystemInterface.ensureZygoteStarted();
}
- void handleNewUser(int userId) {
+ @Override
+ public void handleNewUser(int userId) {
// The system user is always started at boot, and by that point we have already run one
// round of the package-changing logic (through prepareWebViewInSystemServer()), so early
// out here.
@@ -218,7 +221,8 @@
handleUserChange();
}
- void handleUserRemoved(int userId) {
+ @Override
+ public void handleUserRemoved(int userId) {
handleUserChange();
}
@@ -232,14 +236,16 @@
updateCurrentWebViewPackage(null);
}
- void notifyRelroCreationCompleted() {
+ @Override
+ public void notifyRelroCreationCompleted() {
synchronized (mLock) {
mNumRelroCreationsFinished++;
checkIfRelrosDoneLocked();
}
}
- WebViewProviderResponse waitForAndGetProvider() {
+ @Override
+ public WebViewProviderResponse waitForAndGetProvider() {
PackageInfo webViewPackage = null;
final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
boolean webViewReady = false;
@@ -284,7 +290,8 @@
* replacing that provider it will not be in use directly, but will be used when the relros
* or the replacement are done).
*/
- String changeProviderAndSetting(String newProviderName) {
+ @Override
+ public String changeProviderAndSetting(String newProviderName) {
PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName);
if (newPackage == null) return "";
return newPackage.packageName;
@@ -367,7 +374,8 @@
/**
* Fetch only the currently valid WebView packages.
**/
- WebViewProviderInfo[] getValidWebViewPackages() {
+ @Override
+ public WebViewProviderInfo[] getValidWebViewPackages() {
ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
WebViewProviderInfo[] providers =
new WebViewProviderInfo[providersAndPackageInfos.length];
@@ -464,11 +472,13 @@
return true;
}
- WebViewProviderInfo[] getWebViewPackages() {
+ @Override
+ public WebViewProviderInfo[] getWebViewPackages() {
return mSystemInterface.getWebViewPackages();
}
- PackageInfo getCurrentWebViewPackage() {
+ @Override
+ public PackageInfo getCurrentWebViewPackage() {
synchronized (mLock) {
return mCurrentWebViewPackage;
}
@@ -620,7 +630,8 @@
return null;
}
- boolean isMultiProcessEnabled() {
+ @Override
+ public boolean isMultiProcessEnabled() {
int settingValue = mSystemInterface.getMultiProcessSetting(mContext);
if (mSystemInterface.isMultiProcessDefaultEnabled()) {
// Multiprocess should be enabled unless the user has turned it off manually.
@@ -631,7 +642,8 @@
}
}
- void enableMultiProcess(boolean enable) {
+ @Override
+ public void enableMultiProcess(boolean enable) {
PackageInfo current = getCurrentWebViewPackage();
mSystemInterface.setMultiProcessSetting(mContext,
enable ? MULTIPROCESS_SETTING_ON_VALUE : MULTIPROCESS_SETTING_OFF_VALUE);
@@ -644,7 +656,8 @@
/**
* Dump the state of this Service.
*/
- void dumpState(PrintWriter pw) {
+ @Override
+ public void dumpState(PrintWriter pw) {
pw.println("Current WebView Update Service state");
pw.println(String.format(" Multiprocess enabled: %b", isMultiProcessEnabled()));
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
new file mode 100644
index 0000000..e618c7e
--- /dev/null
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -0,0 +1,747 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.webkit;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
+import android.os.AsyncTask;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.webkit.UserPackage;
+import android.webkit.WebViewFactory;
+import android.webkit.WebViewProviderInfo;
+import android.webkit.WebViewProviderResponse;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of the WebViewUpdateService.
+ * This class doesn't depend on the android system like the actual Service does and can be used
+ * directly by tests (as long as they implement a SystemInterface).
+ *
+ * This class keeps track of and prepares the current WebView implementation, and needs to keep
+ * track of a couple of different things such as what package is used as WebView implementation.
+ *
+ * The package-visible methods in this class are accessed from WebViewUpdateService either on the UI
+ * thread or on one of multiple Binder threads. The WebView preparation code shares state between
+ * threads meaning that code that chooses a new WebView implementation or checks which
+ * implementation is being used needs to hold a lock.
+ *
+ * The WebViewUpdateService can be accessed in a couple of different ways.
+ * 1. It is started from the SystemServer at boot - at that point we just initiate some state such
+ * as the WebView preparation class.
+ * 2. The SystemServer calls WebViewUpdateService.prepareWebViewInSystemServer. This happens at boot
+ * and the WebViewUpdateService should not have been accessed before this call. In this call we
+ * choose WebView implementation for the first time.
+ * 3. The update service listens for Intents related to package installs and removals. These intents
+ * are received and processed on the UI thread. Each intent can result in changing WebView
+ * implementation.
+ * 4. The update service can be reached through Binder calls which are handled on specific binder
+ * threads. These calls can be made from any process. Generally they are used for changing WebView
+ * implementation (from Settings), getting information about the current WebView implementation (for
+ * loading WebView into an app process), or notifying the service about Relro creation being
+ * completed.
+ *
+ * @hide
+ */
+class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
+ private static final String TAG = WebViewUpdateServiceImpl2.class.getSimpleName();
+
+ private static class WebViewPackageMissingException extends Exception {
+ WebViewPackageMissingException(String message) {
+ super(message);
+ }
+
+ WebViewPackageMissingException(Exception e) {
+ super(e);
+ }
+ }
+
+ private static final int WAIT_TIMEOUT_MS = 1000; // KEY_DISPATCHING_TIMEOUT is 5000.
+ private static final long NS_PER_MS = 1000000;
+
+ private static final int VALIDITY_OK = 0;
+ private static final int VALIDITY_INCORRECT_SDK_VERSION = 1;
+ private static final int VALIDITY_INCORRECT_VERSION_CODE = 2;
+ private static final int VALIDITY_INCORRECT_SIGNATURE = 3;
+ private static final int VALIDITY_NO_LIBRARY_FLAG = 4;
+
+ private static final int MULTIPROCESS_SETTING_ON_VALUE = Integer.MAX_VALUE;
+ private static final int MULTIPROCESS_SETTING_OFF_VALUE = Integer.MIN_VALUE;
+
+ private final SystemInterface mSystemInterface;
+ private final Context mContext;
+
+ private long mMinimumVersionCode = -1;
+
+ // Keeps track of the number of running relro creations
+ private int mNumRelroCreationsStarted = 0;
+ private int mNumRelroCreationsFinished = 0;
+ // Implies that we need to rerun relro creation because we are using an out-of-date package
+ private boolean mWebViewPackageDirty = false;
+ private boolean mAnyWebViewInstalled = false;
+
+ private static final int NUMBER_OF_RELROS_UNKNOWN = Integer.MAX_VALUE;
+
+ // The WebView package currently in use (or the one we are preparing).
+ private PackageInfo mCurrentWebViewPackage = null;
+
+ private final Object mLock = new Object();
+
+ WebViewUpdateServiceImpl2(Context context, SystemInterface systemInterface) {
+ mContext = context;
+ mSystemInterface = systemInterface;
+ }
+
+ @Override
+ public void packageStateChanged(String packageName, int changedState, int userId) {
+ // We don't early out here in different cases where we could potentially early-out (e.g. if
+ // we receive PACKAGE_CHANGED for another user than the system user) since that would
+ // complicate this logic further and open up for more edge cases.
+ for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
+ String webviewPackage = provider.packageName;
+
+ if (webviewPackage.equals(packageName)) {
+ boolean updateWebView = false;
+ boolean removedOrChangedOldPackage = false;
+ String oldProviderName = null;
+ PackageInfo newPackage = null;
+ synchronized (mLock) {
+ try {
+ newPackage = findPreferredWebViewPackage();
+ if (mCurrentWebViewPackage != null) {
+ oldProviderName = mCurrentWebViewPackage.packageName;
+ }
+ // Only trigger update actions if the updated package is the one
+ // that will be used, or the one that was in use before the
+ // update, or if we haven't seen a valid WebView package before.
+ updateWebView =
+ provider.packageName.equals(newPackage.packageName)
+ || provider.packageName.equals(oldProviderName)
+ || mCurrentWebViewPackage == null;
+ // We removed the old package if we received an intent to remove
+ // or replace the old package.
+ removedOrChangedOldPackage =
+ provider.packageName.equals(oldProviderName);
+ if (updateWebView) {
+ onWebViewProviderChanged(newPackage);
+ }
+ } catch (WebViewPackageMissingException e) {
+ mCurrentWebViewPackage = null;
+ Slog.e(TAG, "Could not find valid WebView package to create relro with "
+ + e);
+ }
+ }
+ if (updateWebView && !removedOrChangedOldPackage
+ && oldProviderName != null) {
+ // If the provider change is the result of adding or replacing a
+ // package that was not the previous provider then we must kill
+ // packages dependent on the old package ourselves. The framework
+ // only kills dependents of packages that are being removed.
+ mSystemInterface.killPackageDependents(oldProviderName);
+ }
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void prepareWebViewInSystemServer() {
+ mSystemInterface.notifyZygote(isMultiProcessEnabled());
+ try {
+ synchronized (mLock) {
+ mCurrentWebViewPackage = findPreferredWebViewPackage();
+ String userSetting = mSystemInterface.getUserChosenWebViewProvider(mContext);
+ if (userSetting != null
+ && !userSetting.equals(mCurrentWebViewPackage.packageName)) {
+ // Don't persist the user-chosen setting across boots if the package being
+ // chosen is not used (could be disabled or uninstalled) so that the user won't
+ // be surprised by the device switching to using a certain webview package,
+ // that was uninstalled/disabled a long time ago, if it is installed/enabled
+ // again.
+ mSystemInterface.updateUserSetting(mContext,
+ mCurrentWebViewPackage.packageName);
+ }
+ onWebViewProviderChanged(mCurrentWebViewPackage);
+ }
+ } catch (Throwable t) {
+ // Log and discard errors at this stage as we must not crash the system server.
+ Slog.e(TAG, "error preparing webview provider from system server", t);
+ }
+
+ if (getCurrentWebViewPackage() == null) {
+ // We didn't find a valid WebView implementation. Try explicitly re-enabling the
+ // fallback package for all users in case it was disabled, even if we already did the
+ // one-time migration before. If this actually changes the state, we will see the
+ // PackageManager broadcast shortly and try again.
+ WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
+ WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
+ if (fallbackProvider != null) {
+ Slog.w(TAG, "No valid provider, trying to enable " + fallbackProvider.packageName);
+ mSystemInterface.enablePackageForAllUsers(mContext, fallbackProvider.packageName,
+ true);
+ } else {
+ Slog.e(TAG, "No valid provider and no fallback available.");
+ }
+ }
+ }
+
+ private void startZygoteWhenReady() {
+ // Wait on a background thread for RELRO creation to be done. We ignore the return value
+ // because even if RELRO creation failed we still want to start the zygote.
+ waitForAndGetProvider();
+ mSystemInterface.ensureZygoteStarted();
+ }
+
+ @Override
+ public void handleNewUser(int userId) {
+ // The system user is always started at boot, and by that point we have already run one
+ // round of the package-changing logic (through prepareWebViewInSystemServer()), so early
+ // out here.
+ if (userId == UserHandle.USER_SYSTEM) return;
+ handleUserChange();
+ }
+
+ @Override
+ public void handleUserRemoved(int userId) {
+ handleUserChange();
+ }
+
+ /**
+ * Called when a user was added or removed to ensure WebView preparation is triggered.
+ * This has to be done since the WebView package we use depends on the enabled-state
+ * of packages for all users (so adding or removing a user might cause us to change package).
+ */
+ private void handleUserChange() {
+ // Potentially trigger package-changing logic.
+ updateCurrentWebViewPackage(null);
+ }
+
+ @Override
+ public void notifyRelroCreationCompleted() {
+ synchronized (mLock) {
+ mNumRelroCreationsFinished++;
+ checkIfRelrosDoneLocked();
+ }
+ }
+
+ @Override
+ public WebViewProviderResponse waitForAndGetProvider() {
+ PackageInfo webViewPackage = null;
+ final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
+ boolean webViewReady = false;
+ int webViewStatus = WebViewFactory.LIBLOAD_SUCCESS;
+ synchronized (mLock) {
+ webViewReady = webViewIsReadyLocked();
+ while (!webViewReady) {
+ final long timeNowMs = System.nanoTime() / NS_PER_MS;
+ if (timeNowMs >= timeoutTimeMs) break;
+ try {
+ mLock.wait(timeoutTimeMs - timeNowMs);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ webViewReady = webViewIsReadyLocked();
+ }
+ // Make sure we return the provider that was used to create the relro file
+ webViewPackage = mCurrentWebViewPackage;
+ if (webViewReady) {
+ // success
+ } else if (!mAnyWebViewInstalled) {
+ webViewStatus = WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
+ } else {
+ // Either the current relro creation isn't done yet, or the new relro creatioin
+ // hasn't kicked off yet (the last relro creation used an out-of-date WebView).
+ webViewStatus = WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO;
+ String timeoutError = "Timed out waiting for relro creation, relros started "
+ + mNumRelroCreationsStarted
+ + " relros finished " + mNumRelroCreationsFinished
+ + " package dirty? " + mWebViewPackageDirty;
+ Slog.e(TAG, timeoutError);
+ Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, timeoutError);
+ }
+ }
+ if (!webViewReady) Slog.w(TAG, "creating relro file timed out");
+ return new WebViewProviderResponse(webViewPackage, webViewStatus);
+ }
+
+ /**
+ * Change WebView provider and provider setting and kill packages using the old provider.
+ * Return the new provider (in case we are in the middle of creating relro files, or
+ * replacing that provider it will not be in use directly, but will be used when the relros
+ * or the replacement are done).
+ */
+ @Override
+ public String changeProviderAndSetting(String newProviderName) {
+ PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName);
+ if (newPackage == null) return "";
+ return newPackage.packageName;
+ }
+
+ /**
+ * Update the current WebView package.
+ * @param newProviderName the package to switch to, null if no package has been explicitly
+ * chosen.
+ */
+ private PackageInfo updateCurrentWebViewPackage(@Nullable String newProviderName) {
+ PackageInfo oldPackage = null;
+ PackageInfo newPackage = null;
+ boolean providerChanged = false;
+ synchronized (mLock) {
+ oldPackage = mCurrentWebViewPackage;
+
+ if (newProviderName != null) {
+ mSystemInterface.updateUserSetting(mContext, newProviderName);
+ }
+
+ try {
+ newPackage = findPreferredWebViewPackage();
+ providerChanged = (oldPackage == null)
+ || !newPackage.packageName.equals(oldPackage.packageName);
+ } catch (WebViewPackageMissingException e) {
+ // If updated the Setting but don't have an installed WebView package, the
+ // Setting will be used when a package is available.
+ mCurrentWebViewPackage = null;
+ Slog.e(TAG, "Couldn't find WebView package to use " + e);
+ return null;
+ }
+ // Perform the provider change if we chose a new provider
+ if (providerChanged) {
+ onWebViewProviderChanged(newPackage);
+ }
+ }
+ // Kill apps using the old provider only if we changed provider
+ if (providerChanged && oldPackage != null) {
+ mSystemInterface.killPackageDependents(oldPackage.packageName);
+ }
+ // Return the new provider, this is not necessarily the one we were asked to switch to,
+ // but the persistent setting will now be pointing to the provider we were asked to
+ // switch to anyway.
+ return newPackage;
+ }
+
+ /**
+ * This is called when we change WebView provider, either when the current provider is
+ * updated or a new provider is chosen / takes precedence.
+ */
+ private void onWebViewProviderChanged(PackageInfo newPackage) {
+ synchronized (mLock) {
+ mAnyWebViewInstalled = true;
+ if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
+ mCurrentWebViewPackage = newPackage;
+
+ // The relro creations might 'finish' (not start at all) before
+ // WebViewFactory.onWebViewProviderChanged which means we might not know the
+ // number of started creations before they finish.
+ mNumRelroCreationsStarted = NUMBER_OF_RELROS_UNKNOWN;
+ mNumRelroCreationsFinished = 0;
+ mNumRelroCreationsStarted =
+ mSystemInterface.onWebViewProviderChanged(newPackage);
+ // If the relro creations finish before we know the number of started creations
+ // we will have to do any cleanup/notifying here.
+ checkIfRelrosDoneLocked();
+ } else {
+ mWebViewPackageDirty = true;
+ }
+ }
+
+ // Once we've notified the system that the provider has changed and started RELRO creation,
+ // try to restart the zygote so that it will be ready when apps use it.
+ if (isMultiProcessEnabled()) {
+ AsyncTask.THREAD_POOL_EXECUTOR.execute(this::startZygoteWhenReady);
+ }
+ }
+
+ /**
+ * Fetch only the currently valid WebView packages.
+ **/
+ @Override
+ public WebViewProviderInfo[] getValidWebViewPackages() {
+ ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
+ WebViewProviderInfo[] providers =
+ new WebViewProviderInfo[providersAndPackageInfos.length];
+ for (int n = 0; n < providersAndPackageInfos.length; n++) {
+ providers[n] = providersAndPackageInfos[n].provider;
+ }
+ return providers;
+ }
+
+ private static class ProviderAndPackageInfo {
+ public final WebViewProviderInfo provider;
+ public final PackageInfo packageInfo;
+
+ ProviderAndPackageInfo(WebViewProviderInfo provider, PackageInfo packageInfo) {
+ this.provider = provider;
+ this.packageInfo = packageInfo;
+ }
+ }
+
+ private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() {
+ WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
+ List<ProviderAndPackageInfo> providers = new ArrayList<>();
+ for (int n = 0; n < allProviders.length; n++) {
+ try {
+ PackageInfo packageInfo =
+ mSystemInterface.getPackageInfoForProvider(allProviders[n]);
+ if (validityResult(allProviders[n], packageInfo) == VALIDITY_OK) {
+ providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo));
+ }
+ } catch (NameNotFoundException e) {
+ // Don't add non-existent packages
+ }
+ }
+ return providers.toArray(new ProviderAndPackageInfo[providers.size()]);
+ }
+
+ /**
+ * Returns either the package info of the WebView provider determined in the following way:
+ * If the user has chosen a provider then use that if it is valid,
+ * otherwise use the first package in the webview priority list that is valid.
+ *
+ */
+ private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException {
+ ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
+
+ String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext);
+
+ // If the user has chosen provider, use that (if it's installed and enabled for all
+ // users).
+ for (ProviderAndPackageInfo providerAndPackage : providers) {
+ if (providerAndPackage.provider.packageName.equals(userChosenProvider)) {
+ // userPackages can contain null objects.
+ List<UserPackage> userPackages =
+ mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
+ providerAndPackage.provider);
+ if (isInstalledAndEnabledForAllUsers(userPackages)) {
+ return providerAndPackage.packageInfo;
+ }
+ }
+ }
+
+ // User did not choose, or the choice failed; use the most stable provider that is
+ // installed and enabled for all users, and available by default (not through
+ // user choice).
+ for (ProviderAndPackageInfo providerAndPackage : providers) {
+ if (providerAndPackage.provider.availableByDefault) {
+ // userPackages can contain null objects.
+ List<UserPackage> userPackages =
+ mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
+ providerAndPackage.provider);
+ if (isInstalledAndEnabledForAllUsers(userPackages)) {
+ return providerAndPackage.packageInfo;
+ }
+ }
+ }
+
+ // This should never happen during normal operation (only with modified system images).
+ mAnyWebViewInstalled = false;
+ throw new WebViewPackageMissingException("Could not find a loadable WebView package");
+ }
+
+ /**
+ * Return true iff {@param packageInfos} point to only installed and enabled packages.
+ * The given packages {@param packageInfos} should all be pointing to the same package, but each
+ * PackageInfo representing a different user's package.
+ */
+ private static boolean isInstalledAndEnabledForAllUsers(
+ List<UserPackage> userPackages) {
+ for (UserPackage userPackage : userPackages) {
+ if (!userPackage.isInstalledPackage() || !userPackage.isEnabledPackage()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public WebViewProviderInfo[] getWebViewPackages() {
+ return mSystemInterface.getWebViewPackages();
+ }
+
+ @Override
+ public PackageInfo getCurrentWebViewPackage() {
+ synchronized (mLock) {
+ return mCurrentWebViewPackage;
+ }
+ }
+
+ /**
+ * Returns whether WebView is ready and is not going to go through its preparation phase
+ * again directly.
+ */
+ private boolean webViewIsReadyLocked() {
+ return !mWebViewPackageDirty
+ && (mNumRelroCreationsStarted == mNumRelroCreationsFinished)
+ // The current package might be replaced though we haven't received an intent
+ // declaring this yet, the following flag makes anyone loading WebView to wait in
+ // this case.
+ && mAnyWebViewInstalled;
+ }
+
+ private void checkIfRelrosDoneLocked() {
+ if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
+ if (mWebViewPackageDirty) {
+ mWebViewPackageDirty = false;
+ // If we have changed provider since we started the relro creation we need to
+ // redo the whole process using the new package instead.
+ try {
+ PackageInfo newPackage = findPreferredWebViewPackage();
+ onWebViewProviderChanged(newPackage);
+ } catch (WebViewPackageMissingException e) {
+ mCurrentWebViewPackage = null;
+ // If we can't find any valid WebView package we are now in a state where
+ // mAnyWebViewInstalled is false, so loading WebView will be blocked and we
+ // should simply wait until we receive an intent declaring a new package was
+ // installed.
+ }
+ } else {
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ private int validityResult(WebViewProviderInfo configInfo, PackageInfo packageInfo) {
+ // Ensure the provider targets this framework release (or a later one).
+ if (!UserPackage.hasCorrectTargetSdkVersion(packageInfo)) {
+ return VALIDITY_INCORRECT_SDK_VERSION;
+ }
+ if (!versionCodeGE(packageInfo.getLongVersionCode(), getMinimumVersionCode())
+ && !mSystemInterface.systemIsDebuggable()) {
+ // Webview providers may be downgraded arbitrarily low, prevent that by enforcing
+ // minimum version code. This check is only enforced for user builds.
+ return VALIDITY_INCORRECT_VERSION_CODE;
+ }
+ if (!providerHasValidSignature(configInfo, packageInfo, mSystemInterface)) {
+ return VALIDITY_INCORRECT_SIGNATURE;
+ }
+ if (WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo) == null) {
+ return VALIDITY_NO_LIBRARY_FLAG;
+ }
+ return VALIDITY_OK;
+ }
+
+ /**
+ * Both versionCodes should be from a WebView provider package implemented by Chromium.
+ * VersionCodes from other kinds of packages won't make any sense in this method.
+ *
+ * An introduction to Chromium versionCode scheme:
+ * "BBBBPPPXX"
+ * BBBB: 4 digit branch number. It monotonically increases over time.
+ * PPP: patch number in the branch. It is padded with zeroes to the left. These three digits
+ * may change their meaning in the future.
+ * XX: Digits to differentiate different APK builds of the same source version.
+ *
+ * This method takes the "BBBB" of versionCodes and compare them.
+ *
+ * https://www.chromium.org/developers/version-numbers describes general Chromium versioning;
+ * https://source.chromium.org/chromium/chromium/src/+/master:build/util/android_chrome_version.py
+ * is the canonical source for how Chromium versionCodes are calculated.
+ *
+ * @return true if versionCode1 is higher than or equal to versionCode2.
+ */
+ private static boolean versionCodeGE(long versionCode1, long versionCode2) {
+ long v1 = versionCode1 / 100000;
+ long v2 = versionCode2 / 100000;
+
+ return v1 >= v2;
+ }
+
+ /**
+ * Gets the minimum version code allowed for a valid provider. It is the minimum versionCode
+ * of all available-by-default WebView provider packages. If there is no such WebView provider
+ * package on the system, then return -1, which means all positive versionCode WebView packages
+ * are accepted.
+ *
+ * Note that this is a private method that handles a variable (mMinimumVersionCode) which is
+ * shared between threads. Furthermore, this method does not hold mLock meaning that we must
+ * take extra care to ensure this method is thread-safe.
+ */
+ private long getMinimumVersionCode() {
+ if (mMinimumVersionCode > 0) {
+ return mMinimumVersionCode;
+ }
+
+ long minimumVersionCode = -1;
+ for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
+ if (provider.availableByDefault) {
+ try {
+ long versionCode =
+ mSystemInterface.getFactoryPackageVersion(provider.packageName);
+ if (minimumVersionCode < 0 || versionCode < minimumVersionCode) {
+ minimumVersionCode = versionCode;
+ }
+ } catch (NameNotFoundException e) {
+ // Safe to ignore.
+ }
+ }
+ }
+
+ mMinimumVersionCode = minimumVersionCode;
+ return mMinimumVersionCode;
+ }
+
+ private static boolean providerHasValidSignature(WebViewProviderInfo provider,
+ PackageInfo packageInfo, SystemInterface systemInterface) {
+ // Skip checking signatures on debuggable builds, for development purposes.
+ if (systemInterface.systemIsDebuggable()) return true;
+
+ // Allow system apps to be valid providers regardless of signature.
+ if (packageInfo.applicationInfo.isSystemApp()) return true;
+
+ // We don't support packages with multiple signatures.
+ if (packageInfo.signatures.length != 1) return false;
+
+ // If any of the declared signatures match the package signature, it's valid.
+ for (Signature signature : provider.signatures) {
+ if (signature.equals(packageInfo.signatures[0])) return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the only fallback provider in the set of given packages, or null if there is none.
+ */
+ private static WebViewProviderInfo getFallbackProvider(WebViewProviderInfo[] webviewPackages) {
+ for (WebViewProviderInfo provider : webviewPackages) {
+ if (provider.isFallback) {
+ return provider;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isMultiProcessEnabled() {
+ int settingValue = mSystemInterface.getMultiProcessSetting(mContext);
+ if (mSystemInterface.isMultiProcessDefaultEnabled()) {
+ // Multiprocess should be enabled unless the user has turned it off manually.
+ return settingValue > MULTIPROCESS_SETTING_OFF_VALUE;
+ } else {
+ // Multiprocess should not be enabled, unless the user has turned it on manually.
+ return settingValue >= MULTIPROCESS_SETTING_ON_VALUE;
+ }
+ }
+
+ @Override
+ public void enableMultiProcess(boolean enable) {
+ PackageInfo current = getCurrentWebViewPackage();
+ mSystemInterface.setMultiProcessSetting(mContext,
+ enable ? MULTIPROCESS_SETTING_ON_VALUE : MULTIPROCESS_SETTING_OFF_VALUE);
+ mSystemInterface.notifyZygote(enable);
+ if (current != null) {
+ mSystemInterface.killPackageDependents(current.packageName);
+ }
+ }
+
+ /**
+ * Dump the state of this Service.
+ */
+ @Override
+ public void dumpState(PrintWriter pw) {
+ pw.println("Current WebView Update Service state");
+ pw.println(String.format(" Multiprocess enabled: %b", isMultiProcessEnabled()));
+ synchronized (mLock) {
+ if (mCurrentWebViewPackage == null) {
+ pw.println(" Current WebView package is null");
+ } else {
+ pw.println(String.format(" Current WebView package (name, version): (%s, %s)",
+ mCurrentWebViewPackage.packageName,
+ mCurrentWebViewPackage.versionName));
+ }
+ pw.println(String.format(" Minimum targetSdkVersion: %d",
+ UserPackage.MINIMUM_SUPPORTED_SDK));
+ pw.println(String.format(" Minimum WebView version code: %d",
+ mMinimumVersionCode));
+ pw.println(String.format(" Number of relros started: %d",
+ mNumRelroCreationsStarted));
+ pw.println(String.format(" Number of relros finished: %d",
+ mNumRelroCreationsFinished));
+ pw.println(String.format(" WebView package dirty: %b", mWebViewPackageDirty));
+ pw.println(String.format(" Any WebView package installed: %b",
+ mAnyWebViewInstalled));
+
+ try {
+ PackageInfo preferredWebViewPackage = findPreferredWebViewPackage();
+ pw.println(String.format(
+ " Preferred WebView package (name, version): (%s, %s)",
+ preferredWebViewPackage.packageName,
+ preferredWebViewPackage.versionName));
+ } catch (WebViewPackageMissingException e) {
+ pw.println(String.format(" Preferred WebView package: none"));
+ }
+
+ dumpAllPackageInformationLocked(pw);
+ }
+ }
+
+ private void dumpAllPackageInformationLocked(PrintWriter pw) {
+ WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
+ pw.println(" WebView packages:");
+ for (WebViewProviderInfo provider : allProviders) {
+ List<UserPackage> userPackages =
+ mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider);
+ PackageInfo systemUserPackageInfo =
+ userPackages.get(UserHandle.USER_SYSTEM).getPackageInfo();
+ if (systemUserPackageInfo == null) {
+ pw.println(String.format(" %s is NOT installed.", provider.packageName));
+ continue;
+ }
+
+ int validity = validityResult(provider, systemUserPackageInfo);
+ String packageDetails = String.format(
+ "versionName: %s, versionCode: %d, targetSdkVersion: %d",
+ systemUserPackageInfo.versionName,
+ systemUserPackageInfo.getLongVersionCode(),
+ systemUserPackageInfo.applicationInfo.targetSdkVersion);
+ if (validity == VALIDITY_OK) {
+ boolean installedForAllUsers = isInstalledAndEnabledForAllUsers(
+ mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider));
+ pw.println(String.format(
+ " Valid package %s (%s) is %s installed/enabled for all users",
+ systemUserPackageInfo.packageName,
+ packageDetails,
+ installedForAllUsers ? "" : "NOT"));
+ } else {
+ pw.println(String.format(" Invalid package %s (%s), reason: %s",
+ systemUserPackageInfo.packageName,
+ packageDetails,
+ getInvalidityReason(validity)));
+ }
+ }
+ }
+
+ private static String getInvalidityReason(int invalidityReason) {
+ switch (invalidityReason) {
+ case VALIDITY_INCORRECT_SDK_VERSION:
+ return "SDK version too low";
+ case VALIDITY_INCORRECT_VERSION_CODE:
+ return "Version code too low";
+ case VALIDITY_INCORRECT_SIGNATURE:
+ return "Incorrect signature";
+ case VALIDITY_NO_LIBRARY_FLAG:
+ return "No WebView-library manifest flag";
+ default:
+ return "Unexcepted validity-reason";
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java
new file mode 100644
index 0000000..a9c3dc4
--- /dev/null
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.webkit;
+
+import android.content.pm.PackageInfo;
+import android.webkit.WebViewProviderInfo;
+import android.webkit.WebViewProviderResponse;
+
+import java.io.PrintWriter;
+
+interface WebViewUpdateServiceInterface {
+ void packageStateChanged(String packageName, int changedState, int userId);
+
+ void handleNewUser(int userId);
+
+ void handleUserRemoved(int userId);
+
+ WebViewProviderInfo[] getWebViewPackages();
+
+ void prepareWebViewInSystemServer();
+
+ void notifyRelroCreationCompleted();
+
+ WebViewProviderResponse waitForAndGetProvider();
+
+ String changeProviderAndSetting(String newProviderName);
+
+ WebViewProviderInfo[] getValidWebViewPackages();
+
+ PackageInfo getCurrentWebViewPackage();
+
+ boolean isMultiProcessEnabled();
+
+ void enableMultiProcess(boolean enable);
+
+ void dumpState(PrintWriter pw);
+}
diff --git a/services/core/java/com/android/server/webkit/flags.aconfig b/services/core/java/com/android/server/webkit/flags.aconfig
new file mode 100644
index 0000000..1411acc
--- /dev/null
+++ b/services/core/java/com/android/server/webkit/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.webkit"
+
+flag {
+ name: "update_service_v2"
+ namespace: "webview"
+ description: "Using a new version of the WebView update service"
+ bug: "308907090"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index faccca8..315e7d8 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -55,6 +55,7 @@
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
import static com.android.server.wm.ActivityTaskManagerService.TAG_SWITCH;
import static com.android.server.wm.ActivityTaskManagerService.enforceNotIsolatedCaller;
+import static com.android.window.flags.Flags.allowDisableActivityRecordInputSink;
import android.Manifest;
import android.annotation.ColorInt;
@@ -1688,4 +1689,20 @@
return r.mRequestedLaunchingTaskFragmentToken == taskFragmentToken;
}
}
+
+ @Override
+ public void setActivityRecordInputSinkEnabled(IBinder activityToken, boolean enabled) {
+ if (!allowDisableActivityRecordInputSink()) {
+ return;
+ }
+
+ mService.mAmInternal.enforceCallingPermission(
+ Manifest.permission.INTERNAL_SYSTEM_WINDOW, "setActivityRecordInputSinkEnabled");
+ synchronized (mGlobalLock) {
+ final ActivityRecord r = ActivityRecord.forTokenLocked(activityToken);
+ if (r != null) {
+ r.mActivityRecordInputSinkEnabled = enabled;
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 5ecbc2b..d90d4ff 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -115,7 +115,6 @@
import static android.view.Display.INVALID_DISPLAY;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
-import static android.view.SurfaceControl.getGlobalTransaction;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -971,6 +970,8 @@
boolean mWaitForEnteringPinnedMode;
final ActivityRecordInputSink mActivityRecordInputSink;
+ // System activities with INTERNAL_SYSTEM_WINDOW can disable ActivityRecordInputSink.
+ boolean mActivityRecordInputSinkEnabled = true;
// Activities with this uid are allowed to not create an input sink while being in the same
// task and directly above this ActivityRecord. This field is updated whenever a new activity
@@ -5699,14 +5700,10 @@
// can be synchronized with showing the next surface in the transition.
if (!usingShellTransitions && !isVisible() && !delayed
&& !displayContent.mAppTransition.isTransitionSet()) {
- SurfaceControl.openTransaction();
- try {
- forAllWindows(win -> {
- win.mWinAnimator.hide(getGlobalTransaction(), "immediately hidden");
- }, true);
- } finally {
- SurfaceControl.closeTransaction();
- }
+ forAllWindows(win -> {
+ win.mWinAnimator.hide(getPendingTransaction(), "immediately hidden");
+ }, true);
+ scheduleAnimation();
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
index be7d9b6..c61d863 100644
--- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -86,7 +86,8 @@
final boolean allowPassthrough = activityBelowInTask != null && (
activityBelowInTask.mAllowedTouchUid == mActivityRecord.getUid()
|| activityBelowInTask.isUid(mActivityRecord.getUid()));
- if (allowPassthrough || !mIsCompatEnabled || mActivityRecord.isInTransition()) {
+ if (allowPassthrough || !mIsCompatEnabled || mActivityRecord.isInTransition()
+ || !mActivityRecord.mActivityRecordInputSinkEnabled) {
mInputWindowHandleWrapper.setInputConfigMasked(InputConfig.NOT_TOUCHABLE,
InputConfig.NOT_TOUCHABLE);
} else {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 009b8e0..5e0a449 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -133,6 +133,7 @@
import com.android.server.uri.NeededUriGrants;
import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
import com.android.server.wm.BackgroundActivityStartController.BalCode;
+import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
import com.android.server.wm.LaunchParamsController.LaunchParams;
import com.android.server.wm.TaskFragment.EmbeddingCheckResult;
@@ -1090,14 +1091,14 @@
ActivityOptions checkedOptions = options != null
? options.getOptions(intent, aInfo, callerApp, mSupervisor) : null;
- @BalCode int balCode = BAL_ALLOW_DEFAULT;
+ final BalVerdict balVerdict;
if (!abort) {
try {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER,
"shouldAbortBackgroundActivityStart");
BackgroundActivityStartController balController =
mSupervisor.getBackgroundActivityLaunchController();
- BackgroundActivityStartController.BalVerdict balVerdict =
+ balVerdict =
balController.checkBackgroundActivityStart(
callingUid,
callingPid,
@@ -1109,13 +1110,13 @@
request.forcedBalByPiSender,
intent,
checkedOptions);
- balCode = balVerdict.getCode();
- request.logMessage.append(" (").append(
- BackgroundActivityStartController.balCodeToString(balCode))
- .append(")");
+ request.logMessage.append(" (").append(balVerdict).append(")");
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
+ } else {
+ // Sets ALLOW_BY_DEFAULT as default value as the activity launch will be aborted anyway.
+ balVerdict = BalVerdict.ALLOW_BY_DEFAULT;
}
if (request.allowPendingRemoteAnimationRegistryLookup) {
@@ -1293,13 +1294,13 @@
WindowProcessController homeProcess = mService.mHomeProcess;
boolean isHomeProcess = homeProcess != null
&& aInfo.applicationInfo.uid == homeProcess.mUid;
- if (balCode != BAL_BLOCK && !isHomeProcess) {
+ if (balVerdict.allows() && !isHomeProcess) {
mService.resumeAppSwitches();
}
mLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession,
request.voiceInteractor, startFlags, checkedOptions,
- inTask, inTaskFragment, balCode, intentGrants, realCallingUid);
+ inTask, inTaskFragment, balVerdict, intentGrants, realCallingUid);
if (request.outActivity != null) {
request.outActivity[0] = mLastStartActivityRecord;
@@ -1449,7 +1450,8 @@
private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, ActivityOptions options, Task inTask,
- TaskFragment inTaskFragment, @BalCode int balCode,
+ TaskFragment inTaskFragment,
+ BalVerdict balVerdict,
NeededUriGrants intentGrants, int realCallingUid) {
int result = START_CANCELED;
final Task startedActivityRootTask;
@@ -1468,7 +1470,7 @@
try {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
- startFlags, options, inTask, inTaskFragment, balCode,
+ startFlags, options, inTask, inTaskFragment, balVerdict,
intentGrants, realCallingUid);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
@@ -1615,10 +1617,10 @@
int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, ActivityOptions options, Task inTask,
- TaskFragment inTaskFragment, @BalCode int balCode,
+ TaskFragment inTaskFragment, BalVerdict balVerdict,
NeededUriGrants intentGrants, int realCallingUid) {
setInitialState(r, options, inTask, inTaskFragment, startFlags, sourceRecord,
- voiceSession, voiceInteractor, balCode, realCallingUid);
+ voiceSession, voiceInteractor, balVerdict.getCode(), realCallingUid);
computeLaunchingTaskFlags();
mIntent.setFlags(mLaunchFlags);
@@ -1696,7 +1698,8 @@
}
recordTransientLaunchIfNeeded(targetTaskTop);
// Recycle the target task for this launch.
- startResult = recycleTask(targetTask, targetTaskTop, reusedTask, intentGrants);
+ startResult =
+ recycleTask(targetTask, targetTaskTop, reusedTask, intentGrants, balVerdict);
if (startResult != START_SUCCESS) {
return startResult;
}
@@ -1730,6 +1733,7 @@
recordTransientLaunchIfNeeded(mLastStartActivityRecord);
if (!mAvoidMoveToFront && mDoResume) {
+ logOnlyCreatorAllowsBAL(balVerdict, realCallingUid, newTask);
mTargetRootTask.getRootTask().moveToFront("reuseOrNewTask", targetTask);
if (!mTargetRootTask.isTopRootTaskInDisplayArea() && mService.isDreaming()
&& !dreamStopping) {
@@ -1800,6 +1804,7 @@
// now update the focused root-task accordingly.
if (!mAvoidMoveToFront && mTargetRootTask.isTopActivityFocusable()
&& !mRootWindowContainer.isTopDisplayFocusedRootTask(mTargetRootTask)) {
+ logOnlyCreatorAllowsBAL(balVerdict, realCallingUid, newTask);
mTargetRootTask.moveToFront("startActivityInner");
}
mRootWindowContainer.resumeFocusedTasksTopActivities(
@@ -1817,7 +1822,7 @@
// Note that mStartActivity and source should be in the same Task at this point.
if (mOptions != null && mOptions.isLaunchIntoPip()
&& sourceRecord != null && sourceRecord.getTask() == mStartActivity.getTask()
- && balCode != BAL_BLOCK) {
+ && balVerdict.allows()) {
mRootWindowContainer.moveActivityToPinnedRootTask(mStartActivity,
sourceRecord, "launch-into-pip");
}
@@ -1828,6 +1833,24 @@
return START_SUCCESS;
}
+ private void logOnlyCreatorAllowsBAL(BalVerdict balVerdict,
+ int realCallingUid, boolean newTask) {
+ // TODO (b/296478675) eventually, we will prevent such case from happening
+ // and probably also log that a BAL is prevented by android V.
+ if (!newTask && balVerdict.onlyCreatorAllows()) {
+ String realCallingPackage =
+ mService.mContext.getPackageManager().getNameForUid(realCallingUid);
+ if (realCallingPackage == null) {
+ realCallingPackage = "uid=" + realCallingUid;
+ }
+ Slog.wtf(TAG, "A background app is brought to the foreground due to a "
+ + "PendingIntent. However, only the creator of the PendingIntent allows BAL, "
+ + "while the sender does not allow BAL. realCallingPackage: "
+ + realCallingPackage + "; callingPackage: " + mRequest.callingPackage
+ + "; mTargetRootTask:" + mTargetRootTask);
+ }
+ }
+
private void recordTransientLaunchIfNeeded(ActivityRecord r) {
if (r == null || !mTransientLaunch) return;
final TransitionController controller = r.mTransitionController;
@@ -1995,7 +2018,7 @@
*/
@VisibleForTesting
int recycleTask(Task targetTask, ActivityRecord targetTaskTop, Task reusedTask,
- NeededUriGrants intentGrants) {
+ NeededUriGrants intentGrants, BalVerdict balVerdict) {
// Should not recycle task which is from a different user, just adding the starting
// activity to the task.
if (targetTask.mUserId != mStartActivity.mUserId) {
@@ -2024,7 +2047,7 @@
mRootWindowContainer.startPowerModeLaunchIfNeeded(false /* forceSend */,
targetTaskTop);
- setTargetRootTaskIfNeeded(targetTaskTop);
+ setTargetRootTaskIfNeeded(targetTaskTop, balVerdict);
// When there is a reused activity and the current result is a trampoline activity,
// set the reused activity as the result.
@@ -2040,6 +2063,7 @@
if (!mMovedToFront && mDoResume) {
ProtoLog.d(WM_DEBUG_TASKS, "Bring to front target: %s from %s", mTargetRootTask,
targetTaskTop);
+ logOnlyCreatorAllowsBAL(balVerdict, mRealCallingUid, false);
mTargetRootTask.moveToFront("intentActivityFound");
}
resumeTargetRootTaskIfNeeded();
@@ -2068,6 +2092,7 @@
targetTaskTop.showStartingWindow(true /* taskSwitch */);
} else if (mDoResume) {
// Make sure the root task and its belonging display are moved to topmost.
+ logOnlyCreatorAllowsBAL(balVerdict, mRealCallingUid, false);
mTargetRootTask.moveToFront("intentActivityFound");
}
// We didn't do anything... but it was needed (a.k.a., client don't use that intent!)
@@ -2663,7 +2688,7 @@
* @param intentActivity Existing matching activity.
* @return {@link ActivityRecord} brought to front.
*/
- private void setTargetRootTaskIfNeeded(ActivityRecord intentActivity) {
+ private void setTargetRootTaskIfNeeded(ActivityRecord intentActivity, BalVerdict balVerdict) {
intentActivity.getTaskFragment().clearLastPausedActivity();
Task intentTask = intentActivity.getTask();
// The intent task might be reparented while in getOrCreateRootTask, caches the original
@@ -2730,6 +2755,7 @@
// task on top there.
// Defer resuming the top activity while moving task to top, since the
// current task-top activity may not be the activity that should be resumed.
+ logOnlyCreatorAllowsBAL(balVerdict, mRealCallingUid, false);
mTargetRootTask.moveTaskToFront(intentTask, mNoAnimation, mOptions,
mStartActivity.appTimeTracker, DEFER_RESUME,
"bringingFoundTaskToFront");
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a9f11e4..34c7eee 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -281,6 +281,7 @@
import com.android.server.uri.NeededUriGrants;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.wallpaper.WallpaperManagerInternal;
+import com.android.wm.shell.Flags;
import java.io.BufferedReader;
import java.io.File;
@@ -316,8 +317,6 @@
public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
private static final String GRAMMATICAL_GENDER_PROPERTY = "persist.sys.grammatical_gender";
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityTaskManagerService" : TAG_ATM;
- private static final String ENABLE_PIP2_IMPLEMENTATION =
- "persist.wm.debug.enable_pip2_implementation";
static final String TAG_ROOT_TASK = TAG + POSTFIX_ROOT_TASK;
static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
@@ -7262,6 +7261,6 @@
}
static boolean isPip2ExperimentEnabled() {
- return SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false);
+ return Flags.enablePip2Implementation();
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index b125dbd..90eeed2 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1628,7 +1628,12 @@
}
}
- private void removeRootTaskInSurfaceTransaction(Task rootTask) {
+ /**
+ * Removes the root task associated with the given {@param rootTask}. If the {@param rootTask}
+ * is the pinned task, then its child tasks are not explicitly removed when the root task is
+ * destroyed, but instead moved back onto the TaskDisplayArea.
+ */
+ void removeRootTask(Task rootTask) {
if (rootTask.getWindowingMode() == WINDOWING_MODE_PINNED) {
removePinnedRootTaskInSurfaceTransaction(rootTask);
} else {
@@ -1639,15 +1644,6 @@
}
/**
- * Removes the root task associated with the given {@param task}. If the {@param task} is the
- * pinned task, then its child tasks are not explicitly removed when the root task is
- * destroyed, but instead moved back onto the TaskDisplayArea.
- */
- void removeRootTask(Task task) {
- mWindowManager.inSurfaceTransaction(() -> removeRootTaskInSurfaceTransaction(task));
- }
-
- /**
* Removes the task with the specified task id.
*
* @param taskId Identifier of the task to be removed.
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 805bff2..05087f8 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -71,7 +71,6 @@
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WallpaperAnimationAdapter.shouldStartWallpaperAnimation;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -82,7 +81,6 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
-import android.util.Slog;
import android.view.Display;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
@@ -1179,16 +1177,7 @@
}
app.updateReportedVisibilityLocked();
app.waitingToShow = false;
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
- ">>> OPEN TRANSACTION handleAppTransitionReady()");
- mService.openSurfaceTransaction();
- try {
- app.showAllWindowsLocked();
- } finally {
- mService.closeSurfaceTransaction("handleAppTransitionReady");
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
- "<<< CLOSE TRANSACTION handleAppTransitionReady()");
- }
+ app.showAllWindowsLocked();
if (mDisplayContent.mAppTransition.isNextAppTransitionThumbnailUp()) {
app.attachThumbnailAnimation();
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index b66c4c3..8cc197c 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -46,7 +46,6 @@
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
-import android.compat.annotation.Overridable;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -88,7 +87,6 @@
/** If enabled the creator will not allow BAL on its behalf by default. */
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @Overridable
private static final long DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_CREATOR =
296478951;
public static final ActivityOptions ACTIVITY_OPTIONS_SYSTEM_DEFINED =
@@ -433,10 +431,15 @@
static class BalVerdict {
static final BalVerdict BLOCK = new BalVerdict(BAL_BLOCK, false, "Blocked");
+ static final BalVerdict ALLOW_BY_DEFAULT =
+ new BalVerdict(BAL_ALLOW_DEFAULT, false, "Default");
private final @BalCode int mCode;
private final boolean mBackground;
private final String mMessage;
private String mProcessInfo;
+ // indicates BAL would be blocked because only creator of the PI has the privilege to allow
+ // BAL, the sender does not have the privilege to allow BAL.
+ private boolean mOnlyCreatorAllows;
BalVerdict(@BalCode int balCode, boolean background, String message) {
this.mBackground = background;
@@ -457,6 +460,15 @@
return !blocks();
}
+ BalVerdict setOnlyCreatorAllows(boolean onlyCreatorAllows) {
+ mOnlyCreatorAllows = onlyCreatorAllows;
+ return this;
+ }
+
+ boolean onlyCreatorAllows() {
+ return mOnlyCreatorAllows;
+ }
+
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(balCodeToString(mCode));
@@ -566,6 +578,10 @@
BalVerdict resultForRealCaller = state.callerIsRealCaller() && resultForCaller.allows()
? resultForCaller
: checkBackgroundActivityStartAllowedBySender(state, checkedOptions);
+ if (state.isPendingIntent()) {
+ resultForCaller.setOnlyCreatorAllows(
+ resultForCaller.allows() && resultForRealCaller.blocks());
+ }
if (resultForCaller.allows()
&& checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 243c3f2..2c224e4 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5584,12 +5584,7 @@
void prepareSurfaces() {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "prepareSurfaces");
try {
- final Transaction transaction = getPendingTransaction();
super.prepareSurfaces();
-
- // TODO: Once we totally eliminate global transaction we will pass transaction in here
- // rather than merging to global.
- SurfaceControl.mergeToGlobalTransaction(transaction);
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index d0d7f49..e6bbd52 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -101,10 +101,8 @@
mPolicy = displayContent.getDisplayPolicy();
final Resources r = mPolicy.getContext().getResources();
mHideNavBarForKeyboard = r.getBoolean(R.bool.config_hideNavBarForKeyboard);
- mTransientControlTarget = new ControlTarget(
- stateController, displayContent.mWmService.mH, "TransientControlTarget");
- mPermanentControlTarget = new ControlTarget(
- stateController, displayContent.mWmService.mH, "PermanentControlTarget");
+ mTransientControlTarget = new ControlTarget(displayContent, "TransientControlTarget");
+ mPermanentControlTarget = new ControlTarget(displayContent, "PermanentControlTarget");
}
/** Updates the target which can control system bars. */
@@ -699,24 +697,35 @@
}
}
- private static class ControlTarget implements InsetsControlTarget {
+ private static class ControlTarget implements InsetsControlTarget, Runnable {
+ private final Handler mHandler;
+ private final Object mGlobalLock;
private final InsetsState mState = new InsetsState();
- private final InsetsController mInsetsController;
private final InsetsStateController mStateController;
+ private final InsetsController mInsetsController;
private final String mName;
- ControlTarget(InsetsStateController stateController, Handler handler, String name) {
- mStateController = stateController;
- mInsetsController = new InsetsController(new Host(handler, name));
+ ControlTarget(DisplayContent displayContent, String name) {
+ mHandler = displayContent.mWmService.mH;
+ mGlobalLock = displayContent.mWmService.mGlobalLock;
+ mStateController = displayContent.getInsetsStateController();
+ mInsetsController = new InsetsController(new Host(mHandler, name));
mName = name;
}
@Override
public void notifyInsetsControlChanged() {
- mState.set(mStateController.getRawInsetsState(), true /* copySources */);
- mInsetsController.onStateChanged(mState);
- mInsetsController.onControlsChanged(mStateController.getControlsForDispatch(this));
+ mHandler.post(this);
+ }
+
+ @Override
+ public void run() {
+ synchronized (mGlobalLock) {
+ mState.set(mStateController.getRawInsetsState(), true /* copySources */);
+ mInsetsController.onStateChanged(mState);
+ mInsetsController.onControlsChanged(mStateController.getControlsForDispatch(this));
+ }
}
@Override
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index b738c1c..5269d35 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -307,7 +307,7 @@
mService.stopAppSwitches();
}
- mWindowManager.inSurfaceTransaction(() -> {
+ inSurfaceTransaction(() -> {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"RecentsAnimation#onAnimationFinished_inSurfaceTransaction");
mService.deferWindowLayout();
@@ -419,6 +419,11 @@
}
}
+ // No-op wrapper to keep legacy code.
+ private static void inSurfaceTransaction(Runnable exec) {
+ exec.run();
+ }
+
/** Gives the owner of recents animation higher priority. */
private void setProcessAnimating(boolean animating) {
if (mCaller == null) return;
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index eb639b6..a98b9f7 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -307,7 +307,6 @@
mIsFinishing = true;
unlinkToDeathOfRunner();
releaseFinishedCallback();
- mService.openSurfaceTransaction();
try {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS,
"onAnimationFinished(): Notify animation finished:");
@@ -348,7 +347,6 @@
Slog.e(TAG, "Failed to finish remote animation", e);
throw e;
} finally {
- mService.closeSurfaceTransaction("RemoteAnimationController#finished");
mIsFinishing = false;
}
// Reset input for all activities when the remote animation is finished.
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index fe2c250..0c235ba 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -75,7 +75,6 @@
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.WINDOW_FREEZE_TIMEOUT;
@@ -788,23 +787,13 @@
final DisplayContent defaultDisplay = mWmService.getDefaultDisplayContentLocked();
final WindowSurfacePlacer surfacePlacer = mWmService.mWindowPlacerLocked;
- if (SHOW_LIGHT_TRANSACTIONS) {
- Slog.i(TAG,
- ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
- }
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applySurfaceChanges");
- mWmService.openSurfaceTransaction();
try {
applySurfaceChangesTransaction();
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
- mWmService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- if (SHOW_LIGHT_TRANSACTIONS) {
- Slog.i(TAG,
- "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
- }
}
// Send any pending task-info changes that were queued-up during a layout deferment
@@ -998,9 +987,6 @@
// Give the display manager a chance to adjust properties like display rotation if it needs
// to.
mWmService.mDisplayManagerInternal.performTraversal(t);
- if (t != defaultDc.mSyncTransaction) {
- SurfaceControl.mergeToGlobalTransaction(t);
- }
}
/**
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 33f61fe..7375512 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4545,12 +4545,6 @@
* @param creating {@code true} if this is being run during task construction.
*/
void setWindowingMode(int preferredWindowingMode, boolean creating) {
- mWmService.inSurfaceTransaction(() -> setWindowingModeInSurfaceTransaction(
- preferredWindowingMode, creating));
- }
-
- private void setWindowingModeInSurfaceTransaction(int preferredWindowingMode,
- boolean creating) {
final TaskDisplayArea taskDisplayArea = getDisplayArea();
if (taskDisplayArea == null) {
Slog.d(TAG, "taskDisplayArea is null, bail early");
@@ -5976,18 +5970,16 @@
"Can't exit pinned mode if it's not pinned already.");
}
- mWmService.inSurfaceTransaction(() -> {
- final Task task = getBottomMostTask();
- setWindowingMode(WINDOWING_MODE_UNDEFINED);
+ final Task task = getBottomMostTask();
+ setWindowingMode(WINDOWING_MODE_UNDEFINED);
- // Task could have been removed from the hierarchy due to windowing mode change
- // where its only child is reparented back to their original parent task.
- if (isAttached()) {
- getDisplayArea().positionChildAt(POSITION_TOP, this, false /* includingParents */);
- }
+ // Task could have been removed from the hierarchy due to windowing mode change
+ // where its only child is reparented back to their original parent task.
+ if (isAttached()) {
+ getDisplayArea().positionChildAt(POSITION_TOP, this, false /* includingParents */);
+ }
- mTaskSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(task, this);
- });
+ mTaskSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(task, this);
}
private int setBounds(Rect existing, Rect bounds) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index e95d265..fd22f15 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -128,7 +128,6 @@
}
ProtoLog.i(WM_SHOW_TRANSACTIONS, ">>> OPEN TRANSACTION animate");
- mService.openSurfaceTransaction();
try {
// Remove all deferred displays, tasks, and activities.
root.handleCompleteDeferredRemoval();
@@ -163,6 +162,7 @@
dc.mLastContainsRunningSurfaceAnimator = false;
dc.enableHighFrameRate(false);
}
+ mTransaction.merge(dc.getPendingTransaction());
}
cancelAnimation();
@@ -196,8 +196,8 @@
updateRunningExpensiveAnimationsLegacy();
}
- SurfaceControl.mergeToGlobalTransaction(mTransaction);
- mService.closeSurfaceTransaction("WindowAnimator");
+ mTransaction.apply();
+ mService.mWindowTracing.logState("WindowAnimator");
ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION animate");
mService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
index b3a3650..89a70e5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerFlags.java
+++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java
@@ -43,8 +43,6 @@
/* Start Available Flags */
- final boolean mSyncWindowConfigUpdateFlag = Flags.syncWindowConfigUpdateFlag();
-
final boolean mWindowStateResizeItemFlag = Flags.windowStateResizeItemFlag();
final boolean mWallpaperOffsetAsync = Flags.wallpaperOffsetAsync();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 08acd24..a69a07f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1048,29 +1048,6 @@
SystemPerformanceHinter mSystemPerformanceHinter;
- void openSurfaceTransaction() {
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "openSurfaceTransaction");
- SurfaceControl.openTransaction();
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
- }
-
- /**
- * Closes a surface transaction.
- * @param where debug string indicating where the transaction originated
- */
- void closeSurfaceTransaction(String where) {
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "closeSurfaceTransaction");
- SurfaceControl.closeTransaction();
- mWindowTracing.logState(where);
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
- }
-
/** Listener to notify activity manager about app transitions. */
final WindowManagerInternal.AppTransitionListener mActivityManagerAppTransitionNotifier
= new WindowManagerInternal.AppTransitionListener() {
@@ -8591,39 +8568,6 @@
mAppFreezeListeners.remove(listener);
}
- /**
- * WARNING: This interrupts surface updates, be careful! Don't
- * execute within the transaction for longer than you would
- * execute on an animation thread.
- * WARNING: This method contains locks known to the State of California
- * to cause Deadlocks and other conditions.
- *
- * Begins a surface transaction with which the AM can batch operations.
- * All Surface updates performed by the WindowManager following this
- * will not appear on screen until after the call to
- * closeSurfaceTransaction.
- *
- * ActivityManager can use this to ensure multiple 'commands' will all
- * be reflected in a single frame. For example when reparenting a window
- * which was previously hidden due to it's parent properties, we may
- * need to ensure it is hidden in the same frame that the properties
- * from the new parent are inherited, otherwise it could be revealed
- * mistakenly.
- *
- * TODO(b/36393204): We can investigate totally replacing #deferSurfaceLayout
- * with something like this but it seems that some existing cases of
- * deferSurfaceLayout may be a little too broad, in particular the total
- * enclosure of startActivityUnchecked which could run for quite some time.
- */
- void inSurfaceTransaction(Runnable exec) {
- SurfaceControl.openTransaction();
- try {
- exec.run();
- } finally {
- SurfaceControl.closeTransaction();
- }
- }
-
/** Called to inform window manager if non-Vr UI shoul be disabled or not. */
public void disableNonVrUi(boolean disable) {
synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5293292..3e43908 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -29,7 +29,6 @@
import static android.os.PowerManager.DRAW_WAKE_LOCK;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.SurfaceControl.Transaction;
-import static android.view.SurfaceControl.getGlobalTransaction;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
@@ -2794,7 +2793,7 @@
clearPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY);
}
if (!isVisibleByPolicy()) {
- mWinAnimator.hide(getGlobalTransaction(), "checkPolicyVisibilityChange");
+ mWinAnimator.hide(getPendingTransaction(), "checkPolicyVisibilityChange");
if (isFocused()) {
ProtoLog.i(WM_DEBUG_FOCUS_LIGHT,
"setAnimationLocked: setting mFocusMayChange true");
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 6c15c22..d348491 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -20,7 +20,6 @@
import static android.view.SurfaceControl.METADATA_OWNER_PID;
import static android.view.SurfaceControl.METADATA_OWNER_UID;
import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
-import static android.view.SurfaceControl.getGlobalTransaction;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
@@ -148,14 +147,9 @@
if (mSurfaceControl == null) {
return;
}
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setOpaqueLocked");
- mService.openSurfaceTransaction();
- try {
- getGlobalTransaction().setOpaque(mSurfaceControl, isOpaque);
- } finally {
- mService.closeSurfaceTransaction("setOpaqueLocked");
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setOpaqueLocked");
- }
+
+ mAnimator.mWin.getPendingTransaction().setOpaque(mSurfaceControl, isOpaque);
+ mService.scheduleAnimationLocked();
}
void setSecure(boolean isSecure) {
@@ -165,18 +159,15 @@
return;
}
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setSecureLocked");
- mService.openSurfaceTransaction();
- try {
- getGlobalTransaction().setSecure(mSurfaceControl, isSecure);
- final DisplayContent dc = mAnimator.mWin.mDisplayContent;
- if (dc != null) {
- dc.refreshImeSecureFlag(getGlobalTransaction());
- }
- } finally {
- mService.closeSurfaceTransaction("setSecure");
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setSecureLocked");
+ final SurfaceControl.Transaction t = mAnimator.mWin.getPendingTransaction();
+ t.setSecure(mSurfaceControl, isSecure);
+
+ final DisplayContent dc = mAnimator.mWin.mDisplayContent;
+ if (dc != null) {
+ dc.refreshImeSecureFlag(t);
}
+ mService.scheduleAnimationLocked();
}
void setColorSpaceAgnostic(SurfaceControl.Transaction t, boolean agnostic) {
diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp
index ccd9bd0..7edf445 100644
--- a/services/core/jni/com_android_server_hint_HintManagerService.cpp
+++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp
@@ -20,7 +20,6 @@
#include <aidl/android/hardware/power/IPower.h>
#include <android-base/stringprintf.h>
-#include <inttypes.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <powermanager/PowerHalController.h>
@@ -39,15 +38,6 @@
namespace android {
-static struct {
- jclass clazz{};
- jfieldID workPeriodStartTimestampNanos{};
- jfieldID actualTotalDurationNanos{};
- jfieldID actualCpuDurationNanos{};
- jfieldID actualGpuDurationNanos{};
- jfieldID timestampNanos{};
-} gWorkDurationInfo;
-
static power::PowerHalController gPowerHalController;
static std::unordered_map<jlong, std::shared_ptr<IPowerHintSession>> gSessionMap;
static std::mutex gSessionMapLock;
@@ -190,26 +180,6 @@
setMode(session_ptr, static_cast<SessionMode>(mode), enabled);
}
-static void nativeReportActualWorkDuration2(JNIEnv* env, jclass /* clazz */, jlong session_ptr,
- jobjectArray jWorkDurations) {
- int size = env->GetArrayLength(jWorkDurations);
- std::vector<WorkDuration> workDurations(size);
- for (int i = 0; i < size; i++) {
- jobject workDuration = env->GetObjectArrayElement(jWorkDurations, i);
- workDurations[i].workPeriodStartTimestampNanos =
- env->GetLongField(workDuration, gWorkDurationInfo.workPeriodStartTimestampNanos);
- workDurations[i].durationNanos =
- env->GetLongField(workDuration, gWorkDurationInfo.actualTotalDurationNanos);
- workDurations[i].cpuDurationNanos =
- env->GetLongField(workDuration, gWorkDurationInfo.actualCpuDurationNanos);
- workDurations[i].gpuDurationNanos =
- env->GetLongField(workDuration, gWorkDurationInfo.actualGpuDurationNanos);
- workDurations[i].timeStampNanos =
- env->GetLongField(workDuration, gWorkDurationInfo.timestampNanos);
- }
- reportActualWorkDuration(session_ptr, workDurations);
-}
-
// ----------------------------------------------------------------------------
static const JNINativeMethod sHintManagerServiceMethods[] = {
/* name, signature, funcPtr */
@@ -224,23 +194,9 @@
{"nativeSendHint", "(JI)V", (void*)nativeSendHint},
{"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads},
{"nativeSetMode", "(JIZ)V", (void*)nativeSetMode},
- {"nativeReportActualWorkDuration", "(J[Landroid/os/WorkDuration;)V",
- (void*)nativeReportActualWorkDuration2},
};
int register_android_server_HintManagerService(JNIEnv* env) {
- gWorkDurationInfo.clazz = env->FindClass("android/os/WorkDuration");
- gWorkDurationInfo.workPeriodStartTimestampNanos =
- env->GetFieldID(gWorkDurationInfo.clazz, "mWorkPeriodStartTimestampNanos", "J");
- gWorkDurationInfo.actualTotalDurationNanos =
- env->GetFieldID(gWorkDurationInfo.clazz, "mActualTotalDurationNanos", "J");
- gWorkDurationInfo.actualCpuDurationNanos =
- env->GetFieldID(gWorkDurationInfo.clazz, "mActualCpuDurationNanos", "J");
- gWorkDurationInfo.actualGpuDurationNanos =
- env->GetFieldID(gWorkDurationInfo.clazz, "mActualGpuDurationNanos", "J");
- gWorkDurationInfo.timestampNanos =
- env->GetFieldID(gWorkDurationInfo.clazz, "mTimestampNanos", "J");
-
return jniRegisterNativeMethods(env,
"com/android/server/power/hint/"
"HintManagerService$NativeWrapper",
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
index 6c24d6d..820628c 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
@@ -21,8 +21,8 @@
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="install-arg" value="-t" />
- <option name="test-file-name" value="FrameworksImeTests.apk" />
<option name="test-file-name" value="SimpleTestIme.apk" />
+ <option name="test-file-name" value="FrameworksImeTests.apk" />
</target_preparer>
<option name="test-tag" value="FrameworksImeTests" />
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
index a400f12..eb6e8b4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -44,6 +44,7 @@
float brightness = 0.3f;
float sdrBrightness = 0.2f;
boolean shouldUseAutoBrightness = true;
+ boolean shouldUpdateScreenBrightnessSetting = true;
BrightnessReason brightnessReason = new BrightnessReason();
brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC);
brightnessReason.setModifier(BrightnessReason.MODIFIER_DIMMED);
@@ -52,12 +53,15 @@
.setSdrBrightness(sdrBrightness)
.setBrightnessReason(brightnessReason)
.setShouldUseAutoBrightness(shouldUseAutoBrightness)
+ .setShouldUpdateScreenBrightnessSetting(shouldUpdateScreenBrightnessSetting)
.build();
assertEquals(displayBrightnessState.getBrightness(), brightness, FLOAT_DELTA);
assertEquals(displayBrightnessState.getSdrBrightness(), sdrBrightness, FLOAT_DELTA);
assertEquals(displayBrightnessState.getBrightnessReason(), brightnessReason);
assertEquals(displayBrightnessState.getShouldUseAutoBrightness(), shouldUseAutoBrightness);
+ assertEquals(shouldUpdateScreenBrightnessSetting,
+ displayBrightnessState.shouldUpdateScreenBrightnessSetting());
assertEquals(displayBrightnessState.toString(), getString(displayBrightnessState));
}
@@ -71,6 +75,7 @@
.setBrightness(0.26f)
.setSdrBrightness(0.23f)
.setShouldUseAutoBrightness(false)
+ .setShouldUpdateScreenBrightnessSetting(true)
.build();
DisplayBrightnessState state2 = DisplayBrightnessState.Builder.from(state1).build();
assertEquals(state1, state2);
@@ -92,7 +97,9 @@
.append("\n maxBrightness:")
.append(displayBrightnessState.getMaxBrightness())
.append("\n customAnimationRate:")
- .append(displayBrightnessState.getCustomAnimationRate());
+ .append(displayBrightnessState.getCustomAnimationRate())
+ .append("\n shouldUpdateScreenBrightnessSetting:")
+ .append(displayBrightnessState.shouldUpdateScreenBrightnessSetting());
return sb.toString();
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 0bf4654..353a7bb 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -2754,8 +2754,7 @@
DisplayOffloader mockDisplayOffloader = mock(DisplayOffloader.class);
localService.registerDisplayOffloader(displayId, mockDisplayOffloader);
- assertThat(display.getDisplayOffloadSessionLocked().getDisplayOffloader()).isEqualTo(
- mockDisplayOffloader);
+ assertThat(display.getDisplayOffloadSessionLocked()).isNotNull();
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
new file mode 100644
index 0000000..dea838d
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyFloat;
+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.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class DisplayOffloadSessionImplTest {
+
+ @Mock
+ private DisplayManagerInternal.DisplayOffloader mDisplayOffloader;
+
+ @Mock
+ private DisplayPowerControllerInterface mDisplayPowerController;
+
+ private DisplayOffloadSessionImpl mSession;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mDisplayOffloader.startOffload()).thenReturn(true);
+ mSession = new DisplayOffloadSessionImpl(mDisplayOffloader, mDisplayPowerController);
+ }
+
+ @Test
+ public void testStartOffload() {
+ mSession.startOffload();
+ assertTrue(mSession.isActive());
+
+ // An active session shouldn't be started again
+ mSession.startOffload();
+ verify(mDisplayOffloader, times(1)).startOffload();
+ }
+
+ @Test
+ public void testStopOffload() {
+ mSession.startOffload();
+ mSession.stopOffload();
+
+ assertFalse(mSession.isActive());
+ verify(mDisplayPowerController).setBrightnessFromOffload(
+ PowerManager.BRIGHTNESS_INVALID_FLOAT);
+
+ // An inactive session shouldn't be stopped again
+ mSession.stopOffload();
+ verify(mDisplayOffloader, times(1)).stopOffload();
+ }
+
+ @Test
+ public void testUpdateBrightness_sessionInactive() {
+ mSession.updateBrightness(0.3f);
+ verify(mDisplayPowerController, never()).setBrightnessFromOffload(anyFloat());
+ }
+
+ @Test
+ public void testUpdateBrightness_sessionActive() {
+ float brightness = 0.3f;
+
+ mSession.startOffload();
+ mSession.updateBrightness(brightness);
+
+ verify(mDisplayPowerController).setBrightnessFromOffload(brightness);
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index 57f392a..693cafe 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -127,8 +127,6 @@
private Handler mHandler;
private DisplayPowerControllerHolder mHolder;
private Sensor mProxSensor;
- private DisplayManagerInternal.DisplayOffloader mDisplayOffloader;
- private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
@Mock
private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
@@ -148,6 +146,8 @@
private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock;
@Mock
private DisplayManagerFlags mDisplayManagerFlagsMock;
+ @Mock
+ private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
@Captor
private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
@@ -1160,19 +1160,19 @@
any(AutomaticBrightnessController.Callbacks.class),
any(Looper.class),
eq(mSensorManagerMock),
- any(),
+ /* lightSensor= */ any(),
eq(mHolder.brightnessMappingStrategy),
- anyInt(),
- anyFloat(),
- anyFloat(),
- anyFloat(),
- anyInt(),
- anyInt(),
- anyLong(),
- anyLong(),
- anyLong(),
- anyLong(),
- anyBoolean(),
+ /* lightSensorWarmUpTime= */ anyInt(),
+ /* brightnessMin= */ anyFloat(),
+ /* brightnessMax= */ anyFloat(),
+ /* dozeScaleFactor */ anyFloat(),
+ /* lightSensorRate= */ anyInt(),
+ /* initialLightSensorRate= */ anyInt(),
+ /* brighteningLightDebounceConfig */ anyLong(),
+ /* darkeningLightDebounceConfig */ anyLong(),
+ /* brighteningLightDebounceConfigIdle= */ anyLong(),
+ /* darkeningLightDebounceConfigIdle= */ anyLong(),
+ /* resetAmbientLuxAfterWarmUpConfig= */ anyBoolean(),
any(HysteresisLevels.class),
any(HysteresisLevels.class),
any(HysteresisLevels.class),
@@ -1180,9 +1180,9 @@
eq(mContext),
any(BrightnessRangeController.class),
any(BrightnessThrottler.class),
- isNull(),
- anyInt(),
- anyInt(),
+ /* idleModeBrightnessMapper= */ isNull(),
+ /* ambientLightHorizonShort= */ anyInt(),
+ /* ambientLightHorizonLong= */ anyInt(),
eq(lux),
eq(brightness)
);
@@ -1294,9 +1294,8 @@
public void testRampRateForHdrContent_HdrClamperOn() {
float clampedBrightness = 0.6f;
float transitionRate = 1.5f;
- DisplayManagerFlags flags = mock(DisplayManagerFlags.class);
- when(flags.isHdrClamperEnabled()).thenReturn(true);
- mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true, flags);
+ when(mDisplayManagerFlagsMock.isHdrClamperEnabled()).thenReturn(true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true);
DisplayPowerRequest dpr = new DisplayPowerRequest();
when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
@@ -1392,9 +1391,8 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
public void testRampMaxTimeInteractiveThenIdle_DifferentValues() {
- DisplayManagerFlags flags = mock(DisplayManagerFlags.class);
- when(flags.isAdaptiveTone1Enabled()).thenReturn(true);
- mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true, flags);
+ when(mDisplayManagerFlagsMock.isAdaptiveTone1Enabled()).thenReturn(true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true);
// Send a display power request
DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -1447,9 +1445,8 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
public void testRampMaxTimeIdle_DifferentValues() {
- DisplayManagerFlags flags = mock(DisplayManagerFlags.class);
- when(flags.isAdaptiveTone1Enabled()).thenReturn(true);
- mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true, flags);
+ when(mDisplayManagerFlagsMock.isAdaptiveTone1Enabled()).thenReturn(true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true);
// Send a display power request
DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -1481,8 +1478,6 @@
when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
return null;
}).when(mHolder.displayPowerState).setScreenState(anyInt());
- // init displayoffload session and support offloading.
- initDisplayOffloadSession();
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
// start with DOZE.
@@ -1509,8 +1504,6 @@
when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
return null;
}).when(mHolder.displayPowerState).setScreenState(anyInt());
- // init displayoffload session and support offloading.
- initDisplayOffloadSession();
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
// start with DOZE.
@@ -1536,8 +1529,6 @@
when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
return null;
}).when(mHolder.displayPowerState).setScreenState(anyInt());
- // init displayoffload session and support offloading.
- initDisplayOffloadSession();
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
// start with OFF.
@@ -1553,26 +1544,29 @@
verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
}
- private void initDisplayOffloadSession() {
- mDisplayOffloader = spy(new DisplayManagerInternal.DisplayOffloader() {
- @Override
- public boolean startOffload() {
- return true;
- }
+ @Test
+ public void testBrightnessFromOffload() {
+ when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ float brightness = 0.34f;
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
- @Override
- public void stopOffload() {}
- });
+ mHolder.dpc.setBrightnessFromOffload(brightness);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
- mDisplayOffloadSession = new DisplayManagerInternal.DisplayOffloadSession() {
- @Override
- public void setDozeStateOverride(int displayState) {}
-
- @Override
- public DisplayManagerInternal.DisplayOffloader getDisplayOffloader() {
- return mDisplayOffloader;
- }
- };
+ // One triggered by handleBrightnessModeChange, another triggered by
+ // setBrightnessFromOffload
+ verify(mHolder.animator, times(2)).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
}
/**
@@ -1659,12 +1653,6 @@
private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
String uniqueId, boolean isEnabled) {
- return createDisplayPowerController(displayId, uniqueId, isEnabled,
- mock(DisplayManagerFlags.class));
- }
-
- private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
- String uniqueId, boolean isEnabled, DisplayManagerFlags flags) {
final DisplayPowerState displayPowerState = mock(DisplayPowerState.class);
final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
final AutomaticBrightnessController automaticBrightnessController =
@@ -1690,7 +1678,7 @@
TestInjector injector = spy(new TestInjector(displayPowerState, animator,
automaticBrightnessController, wakelockController, brightnessMappingStrategy,
hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper,
- clamperController, flags));
+ clamperController, mDisplayManagerFlagsMock));
final LogicalDisplay display = mock(LogicalDisplay.class);
final DisplayDevice device = mock(DisplayDevice.class);
@@ -1705,7 +1693,7 @@
mSensorManagerMock, mDisplayBlankerMock, display,
mBrightnessTrackerMock, brightnessSetting, () -> {
},
- hbmMetadata, /* bootCompleted= */ false, flags);
+ hbmMetadata, /* bootCompleted= */ false, mDisplayManagerFlagsMock);
return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
animator, automaticBrightnessController, wakelockController,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 9617bd0..b227993 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -125,8 +125,6 @@
private Handler mHandler;
private DisplayPowerControllerHolder mHolder;
private Sensor mProxSensor;
- private DisplayManagerInternal.DisplayOffloader mDisplayOffloader;
- private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
@Mock
private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
@@ -146,6 +144,8 @@
private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock;
@Mock
private DisplayManagerFlags mDisplayManagerFlagsMock;
+ @Mock
+ private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
@Captor
private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
@@ -1094,19 +1094,19 @@
any(AutomaticBrightnessController.Callbacks.class),
any(Looper.class),
eq(mSensorManagerMock),
- any(),
+ /* lightSensor= */ any(),
eq(mHolder.brightnessMappingStrategy),
- anyInt(),
- anyFloat(),
- anyFloat(),
- anyFloat(),
- anyInt(),
- anyInt(),
- anyLong(),
- anyLong(),
- anyLong(),
- anyLong(),
- anyBoolean(),
+ /* lightSensorWarmUpTime= */ anyInt(),
+ /* brightnessMin= */ anyFloat(),
+ /* brightnessMax= */ anyFloat(),
+ /* dozeScaleFactor */ anyFloat(),
+ /* lightSensorRate= */ anyInt(),
+ /* initialLightSensorRate= */ anyInt(),
+ /* brighteningLightDebounceConfig */ anyLong(),
+ /* darkeningLightDebounceConfig */ anyLong(),
+ /* brighteningLightDebounceConfigIdle= */ anyLong(),
+ /* darkeningLightDebounceConfigIdle= */ anyLong(),
+ /* resetAmbientLuxAfterWarmUpConfig= */ anyBoolean(),
any(HysteresisLevels.class),
any(HysteresisLevels.class),
any(HysteresisLevels.class),
@@ -1114,9 +1114,9 @@
eq(mContext),
any(BrightnessRangeController.class),
any(BrightnessThrottler.class),
- isNull(),
- anyInt(),
- anyInt(),
+ /* idleModeBrightnessMapper= */ isNull(),
+ /* ambientLightHorizonShort= */ anyInt(),
+ /* ambientLightHorizonLong= */ anyInt(),
eq(lux),
eq(brightness)
);
@@ -1386,8 +1386,6 @@
when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
return null;
}).when(mHolder.displayPowerState).setScreenState(anyInt());
- // init displayoffload session and support offloading.
- initDisplayOffloadSession();
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
// start with DOZE.
@@ -1414,8 +1412,6 @@
when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
return null;
}).when(mHolder.displayPowerState).setScreenState(anyInt());
- // init displayoffload session and support offloading.
- initDisplayOffloadSession();
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
// start with DOZE.
@@ -1441,8 +1437,6 @@
when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
return null;
}).when(mHolder.displayPowerState).setScreenState(anyInt());
- // init displayoffload session and support offloading.
- initDisplayOffloadSession();
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
// start with OFF.
@@ -1458,28 +1452,6 @@
verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
}
- private void initDisplayOffloadSession() {
- mDisplayOffloader = spy(new DisplayManagerInternal.DisplayOffloader() {
- @Override
- public boolean startOffload() {
- return true;
- }
-
- @Override
- public void stopOffload() {}
- });
-
- mDisplayOffloadSession = new DisplayManagerInternal.DisplayOffloadSession() {
- @Override
- public void setDozeStateOverride(int displayState) {}
-
- @Override
- public DisplayManagerInternal.DisplayOffloader getDisplayOffloader() {
- return mDisplayOffloader;
- }
- };
- }
-
private void advanceTime(long timeMs) {
mClock.fastForward(timeMs);
mTestLooper.dispatchAll();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index a77a958..8270845 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -25,6 +25,7 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
@@ -40,12 +41,12 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
-import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
import android.hardware.display.DisplayManagerInternal.DisplayOffloader;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.PowerManager;
import android.view.Display;
import android.view.DisplayAddress;
import android.view.SurfaceControl;
@@ -118,10 +119,12 @@
private DisplayNotificationManager mMockedDisplayNotificationManager;
@Mock
private DisplayManagerFlags mFlags;
+ @Mock
+ private DisplayPowerControllerInterface mMockedDisplayPowerController;
private Handler mHandler;
- private DisplayOffloadSession mDisplayOffloadSession;
+ private DisplayOffloadSessionImpl mDisplayOffloadSession;
private DisplayOffloader mDisplayOffloader;
@@ -1195,6 +1198,7 @@
}
verify(mDisplayOffloader, times(mDisplayOffloadSupportedStates.size())).startOffload();
+ assertTrue(mDisplayOffloadSession.isActive());
}
@Test
@@ -1217,6 +1221,9 @@
changeStateToDozeRunnable.run();
verify(mDisplayOffloader).stopOffload();
+ assertFalse(mDisplayOffloadSession.isActive());
+ verify(mMockedDisplayPowerController).setBrightnessFromOffload(
+ PowerManager.BRIGHTNESS_INVALID_FLOAT);
}
private void initDisplayOffloadSession() {
@@ -1230,15 +1237,8 @@
public void stopOffload() {}
});
- mDisplayOffloadSession = new DisplayOffloadSession() {
- @Override
- public void setDozeStateOverride(int displayState) {}
-
- @Override
- public DisplayOffloader getDisplayOffloader() {
- return mDisplayOffloader;
- }
- };
+ mDisplayOffloadSession = new DisplayOffloadSessionImpl(mDisplayOffloader,
+ mMockedDisplayPowerController);
}
private void setupCutoutAndRoundedCorners() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index c4f4838..52fa91f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -42,7 +42,9 @@
import com.android.server.display.AutomaticBrightnessController;
import com.android.server.display.BrightnessSetting;
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
+import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy;
import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
+import com.android.server.display.feature.DisplayManagerFlags;
import org.junit.Before;
import org.junit.Test;
@@ -66,6 +68,8 @@
private BrightnessSetting mBrightnessSetting;
@Mock
private Runnable mOnBrightnessChangeRunnable;
+ @Mock
+ private DisplayManagerFlags mDisplayManagerFlags;
@Mock
private HandlerExecutor mBrightnessChangeExecutor;
@@ -74,7 +78,7 @@
DisplayBrightnessController.Injector() {
@Override
DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(
- Context context, int displayId) {
+ Context context, int displayId, DisplayManagerFlags flags) {
return mDisplayBrightnessStrategySelector;
}
};
@@ -92,7 +96,7 @@
.thenReturn(true);
mDisplayBrightnessController = new DisplayBrightnessController(mContext, mInjector,
DISPLAY_ID, DEFAULT_BRIGHTNESS, mBrightnessSetting, mOnBrightnessChangeRunnable,
- mBrightnessChangeExecutor);
+ mBrightnessChangeExecutor, mDisplayManagerFlags);
}
@Test
@@ -350,7 +354,7 @@
int nonDefaultDisplayId = 1;
mDisplayBrightnessController = new DisplayBrightnessController(mContext, mInjector,
nonDefaultDisplayId, DEFAULT_BRIGHTNESS, mBrightnessSetting,
- mOnBrightnessChangeRunnable, mBrightnessChangeExecutor);
+ mOnBrightnessChangeRunnable, mBrightnessChangeExecutor, mDisplayManagerFlags);
brightness = 0.5f;
when(mBrightnessSetting.getBrightness()).thenReturn(brightness);
mDisplayBrightnessController.setAutomaticBrightnessController(
@@ -384,4 +388,14 @@
verify(mBrightnessSetting).setBrightness(brightnessValue2);
verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits2);
}
+
+ @Test
+ public void setBrightnessFromOffload() {
+ float brightness = 0.4f;
+ OffloadBrightnessStrategy offloadBrightnessStrategy = mock(OffloadBrightnessStrategy.class);
+ when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(
+ offloadBrightnessStrategy);
+ mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+ verify(offloadBrightnessStrategy).setOffloadScreenBrightness(brightness);
+ }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index 8497dab..37958fa 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -17,6 +17,7 @@
package com.android.server.display.brightness;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -35,13 +36,16 @@
import com.android.internal.R;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy;
import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
+import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy;
import com.android.server.display.brightness.strategy.OverrideBrightnessStrategy;
import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrategy;
import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
+import com.android.server.display.feature.DisplayManagerFlags;
import org.junit.Before;
import org.junit.Rule;
@@ -71,10 +75,64 @@
@Mock
private FollowerBrightnessStrategy mFollowerBrightnessStrategy;
@Mock
+ private AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
+ @Mock
+ private OffloadBrightnessStrategy mOffloadBrightnessStrategy;
+ @Mock
private Resources mResources;
+ @Mock
+ private DisplayManagerFlags mDisplayManagerFlags;
private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
private Context mContext;
+ private DisplayBrightnessStrategySelector.Injector mInjector =
+ new DisplayBrightnessStrategySelector.Injector() {
+ @Override
+ ScreenOffBrightnessStrategy getScreenOffBrightnessStrategy() {
+ return mScreenOffBrightnessModeStrategy;
+ }
+
+ @Override
+ DozeBrightnessStrategy getDozeBrightnessStrategy() {
+ return mDozeBrightnessModeStrategy;
+ }
+
+ @Override
+ OverrideBrightnessStrategy getOverrideBrightnessStrategy() {
+ return mOverrideBrightnessStrategy;
+ }
+
+ @Override
+ TemporaryBrightnessStrategy getTemporaryBrightnessStrategy() {
+ return mTemporaryBrightnessStrategy;
+ }
+
+ @Override
+ BoostBrightnessStrategy getBoostBrightnessStrategy() {
+ return mBoostBrightnessStrategy;
+ }
+
+ @Override
+ FollowerBrightnessStrategy getFollowerBrightnessStrategy(int displayId) {
+ return mFollowerBrightnessStrategy;
+ }
+
+ @Override
+ InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
+ return mInvalidBrightnessStrategy;
+ }
+
+ @Override
+ AutomaticBrightnessStrategy getAutomaticBrightnessStrategy(Context context,
+ int displayId) {
+ return mAutomaticBrightnessStrategy;
+ }
+
+ @Override
+ OffloadBrightnessStrategy getOffloadBrightnessStrategy() {
+ return mOffloadBrightnessStrategy;
+ }
+ };
@Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@@ -87,45 +145,8 @@
when(mContext.getContentResolver()).thenReturn(contentResolver);
when(mContext.getResources()).thenReturn(mResources);
when(mInvalidBrightnessStrategy.getName()).thenReturn("InvalidBrightnessStrategy");
- DisplayBrightnessStrategySelector.Injector injector =
- new DisplayBrightnessStrategySelector.Injector() {
- @Override
- ScreenOffBrightnessStrategy getScreenOffBrightnessStrategy() {
- return mScreenOffBrightnessModeStrategy;
- }
-
- @Override
- DozeBrightnessStrategy getDozeBrightnessStrategy() {
- return mDozeBrightnessModeStrategy;
- }
-
- @Override
- OverrideBrightnessStrategy getOverrideBrightnessStrategy() {
- return mOverrideBrightnessStrategy;
- }
-
- @Override
- TemporaryBrightnessStrategy getTemporaryBrightnessStrategy() {
- return mTemporaryBrightnessStrategy;
- }
-
- @Override
- BoostBrightnessStrategy getBoostBrightnessStrategy() {
- return mBoostBrightnessStrategy;
- }
-
- @Override
- FollowerBrightnessStrategy getFollowerBrightnessStrategy(int displayId) {
- return mFollowerBrightnessStrategy;
- }
-
- @Override
- InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
- return mInvalidBrightnessStrategy;
- }
- };
mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
- injector, DISPLAY_ID);
+ mInjector, DISPLAY_ID, mDisplayManagerFlags);
}
@@ -188,6 +209,7 @@
displayPowerRequest.screenBrightnessOverride = Float.NaN;
when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+ when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(Float.NaN);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
Display.STATE_ON), mInvalidBrightnessStrategy);
}
@@ -200,4 +222,35 @@
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
Display.STATE_ON), mFollowerBrightnessStrategy);
}
+
+ @Test
+ public void selectStrategySelectsOffloadStrategyWhenValid() {
+ when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true);
+ mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
+ mInjector, DISPLAY_ID, mDisplayManagerFlags);
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ displayPowerRequest.screenBrightnessOverride = Float.NaN;
+ when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
+ when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+ when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f);
+ assertEquals(mOffloadBrightnessStrategy, mDisplayBrightnessStrategySelector.selectStrategy(
+ displayPowerRequest, Display.STATE_ON));
+ }
+
+ @Test
+ public void selectStrategyDoesNotSelectOffloadStrategyWhenFeatureFlagDisabled() {
+ when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(false);
+ mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
+ mInjector, DISPLAY_ID, mDisplayManagerFlags);
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ displayPowerRequest.screenBrightnessOverride = Float.NaN;
+ when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
+ when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+ when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f);
+ assertNotEquals(mOffloadBrightnessStrategy,
+ mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+ Display.STATE_ON));
+ }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index b652576..78ec2ff3 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -188,6 +188,29 @@
}
@Test
+ public void testAutoBrightnessState_BrightnessReasonIsOffload() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+ int targetDisplayState = Display.STATE_ON;
+ boolean allowAutoBrightnessWhileDozing = false;
+ int brightnessReason = BrightnessReason.REASON_OFFLOAD;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ float lastUserSetBrightness = 0.2f;
+ boolean userSetBrightnessChanged = true;
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+ allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+ userSetBrightnessChanged);
+ verify(mAutomaticBrightnessController)
+ .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+ mBrightnessConfiguration,
+ lastUserSetBrightness,
+ userSetBrightnessChanged, 0.5f,
+ false, policy, true);
+ assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+ assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+ }
+
+ @Test
public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesAllow() {
mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
int targetDisplayState = Display.STATE_DOZE;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java
new file mode 100644
index 0000000..36719af
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 static org.junit.Assert.assertEquals;
+
+import android.hardware.display.DisplayManagerInternal;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.DisplayBrightnessState;
+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 OffloadBrightnessStrategyTest {
+
+ private OffloadBrightnessStrategy mOffloadBrightnessStrategy;
+
+ @Before
+ public void before() {
+ mOffloadBrightnessStrategy = new OffloadBrightnessStrategy();
+ }
+
+ @Test
+ public void testUpdateBrightnessWhenOffloadBrightnessIsSet() {
+ DisplayManagerInternal.DisplayPowerRequest
+ displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest();
+ float brightness = 0.2f;
+ mOffloadBrightnessStrategy.setOffloadScreenBrightness(brightness);
+ BrightnessReason brightnessReason = new BrightnessReason();
+ brightnessReason.setReason(BrightnessReason.REASON_OFFLOAD);
+ DisplayBrightnessState expectedDisplayBrightnessState =
+ new DisplayBrightnessState.Builder()
+ .setBrightness(brightness)
+ .setBrightnessReason(brightnessReason)
+ .setSdrBrightness(brightness)
+ .setDisplayBrightnessStrategyName(mOffloadBrightnessStrategy.getName())
+ .setShouldUpdateScreenBrightnessSetting(true)
+ .build();
+ DisplayBrightnessState updatedDisplayBrightnessState =
+ mOffloadBrightnessStrategy.updateBrightness(displayPowerRequest);
+ assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index a9f5b14..18a2acc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -37,6 +37,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.Manifest;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.ComponentName;
@@ -171,6 +172,12 @@
when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager);
when(mActivityManager.getLauncherLargeIconDensity()).thenReturn(100);
+ when(mContext.checkCallingOrSelfPermission(
+ eq(Manifest.permission.REQUEST_INSTALL_PACKAGES))).thenReturn(
+ PackageManager.PERMISSION_DENIED);
+ when(mContext.checkCallingOrSelfPermission(
+ eq(Manifest.permission.REQUEST_DELETE_PACKAGES))).thenReturn(
+ PackageManager.PERMISSION_DENIED);
when(mAppOpsManager.checkOp(
eq(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED),
@@ -386,7 +393,7 @@
Exception e = assertThrows(
SecurityException.class,
() -> mArchiveManager.requestUnarchive(PACKAGE, "different",
- UserHandle.CURRENT));
+ mIntentSender, UserHandle.CURRENT));
assertThat(e).hasMessageThat().isEqualTo(
String.format(
"The UID %s of callerPackageName set by the caller doesn't match the "
@@ -404,7 +411,7 @@
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
- UserHandle.CURRENT));
+ mIntentSender, UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("Package %s not found.", PACKAGE));
@@ -416,7 +423,7 @@
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
- UserHandle.CURRENT));
+ mIntentSender, UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("Package %s is not currently archived.", PACKAGE));
@@ -428,7 +435,7 @@
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
- UserHandle.CURRENT));
+ mIntentSender, UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("Package %s is not currently archived.", PACKAGE));
@@ -452,7 +459,7 @@
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
- UserHandle.CURRENT));
+ mIntentSender, UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("No installer found to unarchive app %s.", PACKAGE));
@@ -462,7 +469,8 @@
public void unarchiveApp_success() {
mUserState.setArchiveState(createArchiveState()).setInstalled(false);
- mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT);
+ mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+ UserHandle.CURRENT);
rule.mocks().getHandler().flush();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
diff --git a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
index 2d760a7..1002fba 100644
--- a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
@@ -220,7 +220,7 @@
private void setDeviceConfigPinnedAnonSize(long size) {
mFakeDeviceConfigInterface.setProperty(
- DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
+ DeviceConfig.NAMESPACE_RUNTIME_NATIVE,
"pin_shared_anon_size",
String.valueOf(size),
/*makeDefault=*/false);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 0f3daec..74eb79d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -28,6 +28,8 @@
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED_UI_SHOWING;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_ERROR_PENDING_SYSUI;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -248,6 +250,45 @@
}
@Test
+ public void testOnErrorReceivedBeforeOnDialogAnimatedIn() throws RemoteException {
+ final int fingerprintId = 0;
+ final int faceId = 1;
+ setupFingerprint(fingerprintId, FingerprintSensorProperties.TYPE_REAR);
+ setupFace(faceId, true /* confirmationAlwaysRequired */,
+ mock(IBiometricAuthenticator.class));
+ final AuthSession session = createAuthSession(mSensors,
+ false /* checkDevicePolicyManager */,
+ Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
+ 0 /* operationId */,
+ 0 /* userId */);
+ session.goToInitialState();
+
+ for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+ assertThat(sensor.getSensorState()).isEqualTo(BiometricSensor.STATE_WAITING_FOR_COOKIE);
+ session.onCookieReceived(
+ session.mPreAuthInfo.eligibleSensors.get(sensor.id).getCookie());
+ }
+ assertThat(session.allCookiesReceived()).isTrue();
+ assertThat(session.getState()).isEqualTo(STATE_AUTH_STARTED);
+
+ final BiometricSensor faceSensor = session.mPreAuthInfo.eligibleSensors.get(faceId);
+ final BiometricSensor fingerprintSensor = session.mPreAuthInfo.eligibleSensors.get(
+ fingerprintId);
+ final int cookie = faceSensor.getCookie();
+ session.onErrorReceived(0, cookie, BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL, 0);
+
+ assertThat(faceSensor.getSensorState()).isEqualTo(BiometricSensor.STATE_STOPPED);
+ assertThat(session.getState()).isEqualTo(STATE_ERROR_PENDING_SYSUI);
+
+ session.onDialogAnimatedIn(true);
+
+ assertThat(session.getState()).isEqualTo(STATE_AUTH_STARTED_UI_SHOWING);
+ assertThat(fingerprintSensor.getSensorState()).isEqualTo(
+ BiometricSensor.STATE_AUTHENTICATING);
+ }
+
+ @Test
public void testCancelReducesAppetiteForCookies() throws Exception {
setupFace(0 /* id */, false /* confirmationAlwaysRequired */,
mock(IBiometricAuthenticator.class));
diff --git a/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
index 1e73a45..5aef7a3 100644
--- a/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
@@ -90,7 +90,7 @@
@Test
public void getDeviceRoute_noSelectedRoutes_returnsDefaultDevice() {
- MediaRoute2Info route2Info = mController.getDeviceRoute();
+ MediaRoute2Info route2Info = mController.getSelectedRoute();
assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DEFAULT);
assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
@@ -105,7 +105,7 @@
audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
callAudioRoutesObserver(audioRoutesInfo);
- MediaRoute2Info route2Info = mController.getDeviceRoute();
+ MediaRoute2Info route2Info = mController.getSelectedRoute();
assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES);
assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
}
@@ -117,7 +117,7 @@
mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
- MediaRoute2Info route2Info = mController.getDeviceRoute();
+ MediaRoute2Info route2Info = mController.getSelectedRoute();
assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DOCK);
assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK);
}
@@ -135,7 +135,7 @@
mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
- MediaRoute2Info route2Info = mController.getDeviceRoute();
+ MediaRoute2Info route2Info = mController.getSelectedRoute();
assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DOCK);
assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK);
}
@@ -155,7 +155,7 @@
mController.selectRoute(null);
- MediaRoute2Info route2Info = mController.getDeviceRoute();
+ MediaRoute2Info route2Info = mController.getSelectedRoute();
assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES);
assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
}
@@ -171,7 +171,7 @@
mController.selectRoute(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
- MediaRoute2Info route2Info = mController.getDeviceRoute();
+ MediaRoute2Info route2Info = mController.getSelectedRoute();
assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES);
assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
}
@@ -202,7 +202,7 @@
mController.updateVolume(VOLUME_SAMPLE_1);
- MediaRoute2Info route2Info = mController.getDeviceRoute();
+ MediaRoute2Info route2Info = mController.getSelectedRoute();
assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
assertThat(route2Info.getVolume()).isEqualTo(VOLUME_SAMPLE_1);
}
@@ -222,7 +222,7 @@
mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
- MediaRoute2Info route2Info = mController.getDeviceRoute();
+ MediaRoute2Info route2Info = mController.getSelectedRoute();
assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK);
assertThat(route2Info.getVolume()).isEqualTo(VOLUME_SAMPLE_1);
}
diff --git a/services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java
index 24e4851..aed68a5 100644
--- a/services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java
@@ -104,7 +104,7 @@
mOnDeviceRouteChangedListener
);
- MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute();
+ MediaRoute2Info actualMediaRoute = deviceRouteController.getSelectedRoute();
assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME))
@@ -129,7 +129,7 @@
mOnDeviceRouteChangedListener
);
- MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute();
+ MediaRoute2Info actualMediaRoute = deviceRouteController.getSelectedRoute();
assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME))
@@ -243,7 +243,7 @@
mOnDeviceRouteChangedListener
);
- MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute();
+ MediaRoute2Info actualMediaRoute = deviceRouteController.getSelectedRoute();
assertThat(actualMediaRoute.getType()).isEqualTo(mExpectedRouteType);
assertThat(TextUtils.equals(actualMediaRoute.getName(), mExpectedRouteNameValue))
@@ -310,7 +310,7 @@
// Simulating wired device being connected.
callAudioRoutesObserver(audioRoutesInfo);
- MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute();
+ MediaRoute2Info actualMediaRoute = mDeviceRouteController.getSelectedRoute();
assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_HEADPHONES_NAME))
@@ -324,7 +324,7 @@
AudioRoutesInfo fakeBluetoothAudioRoute = createFakeBluetoothAudioRoute();
callAudioRoutesObserver(fakeBluetoothAudioRoute);
- MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute();
+ MediaRoute2Info actualMediaRoute = mDeviceRouteController.getSelectedRoute();
assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME))
@@ -334,12 +334,12 @@
@Test
public void updateVolume_differentValue_updatesDeviceRouteVolume() {
- MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute();
+ MediaRoute2Info actualMediaRoute = mDeviceRouteController.getSelectedRoute();
assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE);
assertThat(mDeviceRouteController.updateVolume(VOLUME_VALUE_SAMPLE_1)).isTrue();
- actualMediaRoute = mDeviceRouteController.getDeviceRoute();
+ actualMediaRoute = mDeviceRouteController.getSelectedRoute();
assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_VALUE_SAMPLE_1);
}
diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index 3748527..d09aa89 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -44,7 +44,6 @@
import android.os.IHintSession;
import android.os.PerformanceHintManager;
import android.os.Process;
-import android.os.WorkDuration;
import android.util.Log;
import com.android.server.FgThread;
@@ -90,11 +89,6 @@
private static final long[] DURATIONS_ZERO = new long[] {};
private static final long[] TIMESTAMPS_ZERO = new long[] {};
private static final long[] TIMESTAMPS_TWO = new long[] {1L, 2L};
- private static final WorkDuration[] WORK_DURATIONS_THREE = new WorkDuration[] {
- new WorkDuration(1L, 11L, 8L, 4L, 1L),
- new WorkDuration(2L, 13L, 8L, 6L, 2L),
- new WorkDuration(3L, 333333333L, 8L, 333333333L, 3L),
- };
@Mock private Context mContext;
@Mock private HintManagerService.NativeWrapper mNativeWrapperMock;
@@ -599,55 +593,4 @@
}
a.close();
}
-
- @Test
- public void testReportActualWorkDuration2() throws Exception {
- HintManagerService service = createService();
- IBinder token = new Binder();
-
- AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
- .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
-
- a.updateTargetWorkDuration(100L);
- a.reportActualWorkDuration2(WORK_DURATIONS_THREE);
- verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration(anyLong(),
- eq(WORK_DURATIONS_THREE));
-
- assertThrows(IllegalArgumentException.class, () -> {
- a.reportActualWorkDuration2(new WorkDuration[] {});
- });
-
- assertThrows(IllegalArgumentException.class, () -> {
- a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(0L, 11L, 8L, 4L, 1L)});
- });
-
- assertThrows(IllegalArgumentException.class, () -> {
- a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 0L, 8L, 4L, 1L)});
- });
-
- assertThrows(IllegalArgumentException.class, () -> {
- a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 11L, 0L, 4L, 1L)});
- });
-
- assertThrows(IllegalArgumentException.class, () -> {
- a.reportActualWorkDuration2(
- new WorkDuration[] {new WorkDuration(1L, 11L, 8L, -1L, 1L)});
- });
-
- reset(mNativeWrapperMock);
- // Set session to background, then the duration would not be updated.
- service.mUidObserver.onUidStateChanged(
- a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
-
- // Using CountDownLatch to ensure above onUidStateChanged() job was digested.
- final CountDownLatch latch = new CountDownLatch(1);
- FgThread.getHandler().post(() -> {
- latch.countDown();
- });
- latch.await();
-
- assertFalse(service.mUidObserver.isUidForeground(a.mUid));
- a.reportActualWorkDuration2(WORK_DURATIONS_THREE);
- verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any());
- }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 7fb8b30..a47fbce 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -87,8 +87,6 @@
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE;
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
@@ -739,9 +737,6 @@
clearInvocations(mRankingHandler);
when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
- mTestFlagResolver.setFlagOverride(FSI_FORCE_DEMOTE, false);
- mTestFlagResolver.setFlagOverride(SHOW_STICKY_HUN_FOR_DENIED_FSI, false);
-
var checker = mock(TestableNotificationManagerService.ComponentPermissionChecker.class);
mService.permissionChecker = checker;
when(checker.check(anyString(), anyInt(), anyInt(), anyBoolean()))
@@ -11472,14 +11467,12 @@
verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class));
}
- private void verifyStickyHun(Flag flag, int permissionState, boolean appRequested,
+ private void verifyStickyHun(int permissionState, boolean appRequested,
boolean isSticky) throws Exception {
when(mPermissionHelper.hasRequestedPermission(Manifest.permission.USE_FULL_SCREEN_INTENT,
PKG, mUserId)).thenReturn(appRequested);
- mTestFlagResolver.setFlagOverride(flag, true);
-
when(mPermissionManager.checkPermissionForDataDelivery(
eq(Manifest.permission.USE_FULL_SCREEN_INTENT), any(), any()))
.thenReturn(permissionState);
@@ -11503,8 +11496,7 @@
public void testFixNotification_flagEnableStickyHun_fsiPermissionHardDenied_showStickyHun()
throws Exception {
- verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
- /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true,
+ verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true,
/* isSticky= */ true);
}
@@ -11512,16 +11504,14 @@
public void testFixNotification_flagEnableStickyHun_fsiPermissionSoftDenied_showStickyHun()
throws Exception {
- verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
- /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true,
+ verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true,
/* isSticky= */ true);
}
@Test
public void testFixNotification_fsiPermissionSoftDenied_appNotRequest_noShowStickyHun()
throws Exception {
- verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
- /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, false,
+ verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, false,
/* isSticky= */ false);
}
@@ -11530,39 +11520,11 @@
public void testFixNotification_flagEnableStickyHun_fsiPermissionGranted_showFsi()
throws Exception {
- verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
- /* permissionState= */ PermissionManager.PERMISSION_GRANTED, true,
+ verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_GRANTED, true,
/* isSticky= */ false);
}
@Test
- public void testFixNotification_flagForceStickyHun_fsiPermissionHardDenied_showStickyHun()
- throws Exception {
-
- verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
- /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true,
- /* isSticky= */ true);
- }
-
- @Test
- public void testFixNotification_flagForceStickyHun_fsiPermissionSoftDenied_showStickyHun()
- throws Exception {
-
- verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
- /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true,
- /* isSticky= */ true);
- }
-
- @Test
- public void testFixNotification_flagForceStickyHun_fsiPermissionGranted_showStickyHun()
- throws Exception {
-
- verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
- /* permissionState= */ PermissionManager.PERMISSION_GRANTED, true,
- /* isSticky= */ true);
- }
-
- @Test
public void fixNotification_withFgsFlag_butIsNotFgs() throws Exception {
final ApplicationInfo applicationInfo = new ApplicationInfo();
when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
index 5147a08..29848d0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
@@ -171,19 +171,8 @@
}
@Test
- public void testGetFsiState_stickyHunFlagDisabled_zero() {
- final int fsiState = NotificationRecordLogger.getFsiState(
- /* isStickyHunFlagEnabled= */ false,
- /* hasFullScreenIntent= */ true,
- /* hasFsiRequestedButDeniedFlag= */ true,
- /* eventType= */ NOTIFICATION_POSTED);
- assertEquals(0, fsiState);
- }
-
- @Test
public void testGetFsiState_isUpdate_zero() {
final int fsiState = NotificationRecordLogger.getFsiState(
- /* isStickyHunFlagEnabled= */ true,
/* hasFullScreenIntent= */ true,
/* hasFsiRequestedButDeniedFlag= */ true,
/* eventType= */ NOTIFICATION_UPDATED);
@@ -193,7 +182,6 @@
@Test
public void testGetFsiState_hasFsi_allowedEnum() {
final int fsiState = NotificationRecordLogger.getFsiState(
- /* isStickyHunFlagEnabled= */ true,
/* hasFullScreenIntent= */ true,
/* hasFsiRequestedButDeniedFlag= */ false,
/* eventType= */ NOTIFICATION_POSTED);
@@ -203,7 +191,6 @@
@Test
public void testGetFsiState_fsiPermissionDenied_deniedEnum() {
final int fsiState = NotificationRecordLogger.getFsiState(
- /* isStickyHunFlagEnabled= */ true,
/* hasFullScreenIntent= */ false,
/* hasFsiRequestedButDeniedFlag= */ true,
/* eventType= */ NOTIFICATION_POSTED);
@@ -213,7 +200,6 @@
@Test
public void testGetFsiState_noFsi_noFsiEnum() {
final int fsiState = NotificationRecordLogger.getFsiState(
- /* isStickyHunFlagEnabled= */ true,
/* hasFullScreenIntent= */ false,
/* hasFsiRequestedButDeniedFlag= */ false,
/* eventType= */ NOTIFICATION_POSTED);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index c156e37..fe21103 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -5733,17 +5733,9 @@
}
@Test
- public void testGetFsiState_flagDisabled_zero() {
- final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
- /* requestedFsiPermission */ true, /* isFlagEnabled= */ false);
-
- assertEquals(0, fsiState);
- }
-
- @Test
public void testGetFsiState_appDidNotRequest_enumNotRequested() {
final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
- /* requestedFsiPermission */ false, /* isFlagEnabled= */ true);
+ /* requestedFsiPermission */ false);
assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED, fsiState);
}
@@ -5754,7 +5746,7 @@
.thenReturn(PermissionManager.PERMISSION_GRANTED);
final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
- /* requestedFsiPermission= */ true, /* isFlagEnabled= */ true);
+ /* requestedFsiPermission= */ true);
assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED, fsiState);
}
@@ -5765,7 +5757,7 @@
.thenReturn(PermissionManager.PERMISSION_SOFT_DENIED);
final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
- /* requestedFsiPermission = */ true, /* isFlagEnabled= */ true);
+ /* requestedFsiPermission = */ true);
assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED, fsiState);
}
@@ -5776,7 +5768,7 @@
.thenReturn(PermissionManager.PERMISSION_HARD_DENIED);
final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
- /* requestedFsiPermission = */ true, /* isFlagEnabled= */ true);
+ /* requestedFsiPermission = */ true);
assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED, fsiState);
}
@@ -5785,18 +5777,7 @@
public void testIsFsiPermissionUserSet_appDidNotRequest_false() {
final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
/* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED,
- /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET,
- /* isStickyHunFlagEnabled= */ true);
-
- assertFalse(isUserSet);
- }
-
- @Test
- public void testIsFsiPermissionUserSet_flagDisabled_false() {
- final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
- /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED,
- /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET,
- /* isStickyHunFlagEnabled= */ false);
+ /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET);
assertFalse(isUserSet);
}
@@ -5805,8 +5786,7 @@
public void testIsFsiPermissionUserSet_userSet_true() {
final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
/* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED,
- /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET,
- /* isStickyHunFlagEnabled= */ true);
+ /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET);
assertTrue(isUserSet);
}
@@ -5815,8 +5795,7 @@
public void testIsFsiPermissionUserSet_notUserSet_false() {
final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
/* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED,
- /* currentPermissionFlags= */ ~PackageManager.FLAG_PERMISSION_USER_SET,
- /* isStickyHunFlagEnabled= */ true);
+ /* currentPermissionFlags= */ ~PackageManager.FLAG_PERMISSION_USER_SET);
assertFalse(isUserSet);
}
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index 1b8d746..e83f03d 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -99,4 +99,7 @@
enabled: false,
},
+ data: [
+ ":OverlayTestApp",
+ ],
}
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 762e23c..f2a1fe8 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -119,6 +119,9 @@
</intent-filter>
</activity>
+ <activity android:name="com.android.server.wm.ActivityRecordInputSinkTests$TestActivity"
+ android:exported="true">
+ </activity>
</application>
<instrumentation
diff --git a/services/tests/wmtests/AndroidTest.xml b/services/tests/wmtests/AndroidTest.xml
index 2717ef90..f8ebead 100644
--- a/services/tests/wmtests/AndroidTest.xml
+++ b/services/tests/wmtests/AndroidTest.xml
@@ -21,6 +21,7 @@
<option name="cleanup-apks" value="true" />
<option name="install-arg" value="-t" />
<option name="test-file-name" value="WmTests.apk" />
+ <option name="test-file-name" value="OverlayTestApp.apk" />
</target_preparer>
<option name="test-tag" value="WmTests" />
diff --git a/services/tests/wmtests/OverlayApp/Android.bp b/services/tests/wmtests/OverlayApp/Android.bp
new file mode 100644
index 0000000..77d5b22
--- /dev/null
+++ b/services/tests/wmtests/OverlayApp/Android.bp
@@ -0,0 +1,19 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+ name: "OverlayTestApp",
+
+ srcs: ["**/*.java"],
+
+ resource_dirs: ["res"],
+
+ certificate: "platform",
+ platform_apis: true,
+}
diff --git a/services/tests/wmtests/OverlayApp/AndroidManifest.xml b/services/tests/wmtests/OverlayApp/AndroidManifest.xml
new file mode 100644
index 0000000..5b4ef57
--- /dev/null
+++ b/services/tests/wmtests/OverlayApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.wm.overlay_app">
+ <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
+
+ <application>
+ <activity android:name=".OverlayApp"
+ android:exported="true"
+ android:theme="@style/TranslucentFloatingTheme">
+ </activity>
+ </application>
+</manifest>
diff --git a/services/tests/wmtests/OverlayApp/res/values/styles.xml b/services/tests/wmtests/OverlayApp/res/values/styles.xml
new file mode 100644
index 0000000..fff10a3
--- /dev/null
+++ b/services/tests/wmtests/OverlayApp/res/values/styles.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <style name="TranslucentFloatingTheme" >
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowIsFloating">true</item>
+ <item name="android:windowNoTitle">true</item>
+
+ <!-- Disables starting window. -->
+ <item name="android:windowDisablePreview">true</item>
+ </style>
+</resources>
diff --git a/services/tests/wmtests/OverlayApp/src/com/android/server/wm/overlay_app/OverlayApp.java b/services/tests/wmtests/OverlayApp/src/com/android/server/wm/overlay_app/OverlayApp.java
new file mode 100644
index 0000000..89161c5
--- /dev/null
+++ b/services/tests/wmtests/OverlayApp/src/com/android/server/wm/overlay_app/OverlayApp.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.overlay_app;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+
+/**
+ * Test app that is translucent not touchable modal.
+ * If launched with "disableInputSink" extra boolean value, this activity disables
+ * ActivityRecordInputSinkEnabled as long as the permission is granted.
+ */
+public class OverlayApp extends Activity {
+ private static final String KEY_DISABLE_INPUT_SINK = "disableInputSink";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ LinearLayout tv = new LinearLayout(this);
+ tv.setBackgroundColor(Color.GREEN);
+ tv.setPadding(50, 50, 50, 50);
+ tv.setGravity(Gravity.CENTER);
+ setContentView(tv);
+
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+
+ if (getIntent().getBooleanExtra(KEY_DISABLE_INPUT_SINK, false)) {
+ setActivityRecordInputSinkEnabled(false);
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordInputSinkTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordInputSinkTests.java
new file mode 100644
index 0000000..3b280d9
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordInputSinkTests.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.UiAutomation;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.window.WindowInfosListenerForTest;
+
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.window.flags.Flags;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Internal variant of {@link android.server.wm.window.ActivityRecordInputSinkTests}.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ActivityRecordInputSinkTests {
+ private static final String OVERLAY_APP_PKG = "com.android.server.wm.overlay_app";
+ private static final String OVERLAY_ACTIVITY = OVERLAY_APP_PKG + "/.OverlayApp";
+ private static final String KEY_DISABLE_INPUT_SINK = "disableInputSink";
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Rule
+ public final ActivityScenarioRule<TestActivity> mActivityRule =
+ new ActivityScenarioRule<>(TestActivity.class);
+
+ private UiAutomation mUiAutomation;
+
+ @Before
+ public void setUp() {
+ mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ }
+
+ @After
+ public void tearDown() {
+ ActivityManager am =
+ InstrumentationRegistry.getInstrumentation().getContext().getSystemService(
+ ActivityManager.class);
+ mUiAutomation.adoptShellPermissionIdentity();
+ try {
+ am.forceStopPackage(OVERLAY_APP_PKG);
+ } finally {
+ mUiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ @Test
+ public void testSimpleButtonPress() {
+ injectTapOnButton();
+
+ mActivityRule.getScenario().onActivity(a -> {
+ assertEquals(1, a.mNumClicked);
+ });
+ }
+
+ @Test
+ public void testSimpleButtonPress_withOverlay() throws InterruptedException {
+ startOverlayApp(false);
+ waitForOverlayApp();
+
+ injectTapOnButton();
+
+ mActivityRule.getScenario().onActivity(a -> {
+ assertEquals(0, a.mNumClicked);
+ });
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK)
+ public void testSimpleButtonPress_withOverlayDisableInputSink() throws InterruptedException {
+ startOverlayApp(true);
+ waitForOverlayApp();
+
+ injectTapOnButton();
+
+ mActivityRule.getScenario().onActivity(a -> {
+ assertEquals(1, a.mNumClicked);
+ });
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK)
+ public void testSimpleButtonPress_withOverlayDisableInputSink_flagDisabled()
+ throws InterruptedException {
+ startOverlayApp(true);
+ waitForOverlayApp();
+
+ injectTapOnButton();
+
+ mActivityRule.getScenario().onActivity(a -> {
+ assertEquals(0, a.mNumClicked);
+ });
+ }
+
+ private void startOverlayApp(boolean disableInputSink) {
+ String launchCommand = "am start -n " + OVERLAY_ACTIVITY;
+ if (disableInputSink) {
+ launchCommand += " --ez " + KEY_DISABLE_INPUT_SINK + " true";
+ }
+
+ mUiAutomation.adoptShellPermissionIdentity();
+ try {
+ mUiAutomation.executeShellCommand(launchCommand);
+ } finally {
+ mUiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ private void waitForOverlayApp() throws InterruptedException {
+ final var listenerHost = new WindowInfosListenerForTest();
+ final var latch = new CountDownLatch(1);
+ final Consumer<List<WindowInfosListenerForTest.WindowInfo>> listener = windowInfos -> {
+ final boolean inputSinkReady = windowInfos.stream().anyMatch(info ->
+ info.isVisible
+ && info.name.contains("ActivityRecordInputSink " + OVERLAY_ACTIVITY));
+ if (inputSinkReady) {
+ latch.countDown();
+ }
+ };
+
+ listenerHost.addWindowInfosListener(listener);
+ try {
+ assertTrue(latch.await(5, TimeUnit.SECONDS));
+ } finally {
+ listenerHost.removeWindowInfosListener(listener);
+ }
+ }
+
+ private void injectTapOnButton() {
+ Rect buttonBounds = new Rect();
+ mActivityRule.getScenario().onActivity(a -> {
+ a.mButton.getBoundsOnScreen(buttonBounds);
+ });
+ final int x = buttonBounds.centerX();
+ final int y = buttonBounds.centerY();
+
+ MotionEvent down = MotionEvent.obtain(SystemClock.uptimeMillis(),
+ SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, x, y, 0);
+ mUiAutomation.injectInputEvent(down, true);
+
+ SystemClock.sleep(10);
+
+ MotionEvent up = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_UP, x, y, 0);
+ mUiAutomation.injectInputEvent(up, true);
+
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ public static class TestActivity extends Activity {
+ int mNumClicked = 0;
+ Button mButton;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mButton = new Button(this);
+ mButton.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ setContentView(mButton);
+ mButton.setOnClickListener(v -> mNumClicked++);
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index e2bb115..98055fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -118,6 +118,7 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.am.PendingIntentRecord;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
import com.android.server.wm.utils.MockTracker;
@@ -1378,7 +1379,8 @@
.setUserId(10)
.build();
- final int result = starter.recycleTask(task, null, null, null);
+ final int result = starter.recycleTask(task, null, null, null,
+ BalVerdict.ALLOW_BY_DEFAULT);
assertThat(result == START_SUCCESS).isTrue();
assertThat(starter.mAddingToTask).isTrue();
}
@@ -1892,7 +1894,7 @@
starter.startActivityInner(target, source, null /* voiceSession */,
null /* voiceInteractor */, 0 /* startFlags */,
options, inTask, inTaskFragment,
- BackgroundActivityStartController.BAL_ALLOW_DEFAULT, null /* intentGrants */,
- -1 /* realCallingUid */);
+ BalVerdict.ALLOW_BY_DEFAULT,
+ null /* intentGrants */, -1 /* realCallingUid */);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
index cf620fe..c404c77 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
@@ -21,6 +21,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.server.wm.BackgroundActivityStartController.BalVerdict;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -186,7 +187,7 @@
/* options */null,
/* inTask */null,
/* inTaskFragment */ null,
- /* balCode */ BackgroundActivityStartController.BAL_ALLOW_DEFAULT,
+ BalVerdict.ALLOW_BY_DEFAULT,
/* intentGrants */null,
/* realCaiingUid */ -1);
@@ -216,7 +217,7 @@
/* options= */null,
/* inTask= */null,
/* inTaskFragment= */ null,
- /* balCode= */ BackgroundActivityStartController.BAL_ALLOW_DEFAULT,
+ BalVerdict.ALLOW_BY_DEFAULT,
/* intentGrants= */null,
/* realCaiingUid */ -1);
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 d08ab51..f99b489 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -273,7 +273,6 @@
assertFalse(win.mHasSurface);
assertNull(win.mWinAnimator.mSurfaceController);
- doReturn(mSystemServicesTestRule.mTransaction).when(SurfaceControl::getGlobalTransaction);
// Invisible requested activity should not get the last config even if its view is visible.
mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0,
outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 63de41f..4c56f33 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1953,6 +1953,13 @@
}
}
+ // Flags status.
+ pw.println("Flags:");
+ pw.println(" " + Flags.FLAG_USER_INTERACTION_TYPE_API
+ + ": " + Flags.userInteractionTypeApi());
+ pw.println(" " + Flags.FLAG_USE_PARCELED_LIST
+ + ": " + Flags.useParceledList());
+
final int[] userIds;
synchronized (mLock) {
final int userCount = mUserState.size();
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index a584fc9..b775963 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -2437,8 +2437,6 @@
synchronized (VoiceInteractionManagerServiceStub.this) {
Slog.i(TAG, "Force stopping current voice recognizer: "
+ getCurRecognizer(userHandle));
- // TODO: Figure out why the interactor was being cleared and document it.
- setCurInteractor(null, userHandle);
initRecognizer(userHandle);
}
}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index f8608b8..e12a815 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -23,6 +23,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.ColorInt;
import android.annotation.DurationMillisLong;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -70,6 +71,7 @@
import com.android.internal.telephony.ISetOpportunisticDataCallback;
import com.android.internal.telephony.ISub;
import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.util.HandlerExecutor;
import com.android.internal.util.FunctionalUtils;
import com.android.internal.util.Preconditions;
@@ -95,7 +97,22 @@
import java.util.stream.Collectors;
/**
- * Subscription manager provides the mobile subscription information.
+ * Subscription manager provides the mobile subscription information that are associated with the
+ * calling user profile {@link UserHandle} for Android SDK 35(V) and above, while Android SDK 34(U)
+ * and below can see all subscriptions as it does today.
+ *
+ * <p>For example, if we have
+ * <ul>
+ * <li> Subscription 1 associated with personal profile.
+ * <li> Subscription 2 associated with work profile.
+ * </ul>
+ * Then for SDK 35+, if the caller identity is personal profile, then
+ * {@link #getActiveSubscriptionInfoList} will return subscription 1 only and vice versa.
+ *
+ * <p>If the caller needs to see all subscriptions across user profiles,
+ * use {@link #createForAllUserProfiles} to convert the instance to see all. Additional permission
+ * may be required as documented on the each API.
+ *
*/
@SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -1446,6 +1463,16 @@
}
}
+ /**
+ * {@code true} if the SubscriptionManager instance should see all subscriptions regardless its
+ * association with particular user profile.
+ *
+ * <p> It only applies to Android SDK 35(V) and above. For Android SDK 34(U) and below, the
+ * caller can see all subscription across user profiles as it does today today even if it's
+ * {@code false}.
+ */
+ private boolean mIsForAllUserProfiles = false;
+
/** @hide */
@UnsupportedAppUsage
public SubscriptionManager(Context context) {
@@ -1776,8 +1803,23 @@
}
/**
- * Get the SubscriptionInfo(s) of the currently active SIM(s). The records will be sorted
- * by {@link SubscriptionInfo#getSimSlotIndex} then by {@link SubscriptionInfo#getSubscriptionId}.
+ * Get the SubscriptionInfo(s) of the currently active SIM(s) associated with the current caller
+ * user profile {@link UserHandle} for Android SDK 35(V) and above, while Android SDK 34(U)
+ * and below can see all subscriptions as it does today.
+ *
+ * <p>For example, if we have
+ * <ul>
+ * <li> Subscription 1 associated with personal profile.
+ * <li> Subscription 2 associated with work profile.
+ * </ul>
+ * Then for SDK 35+, if the caller identity is personal profile, then this will return
+ * subscription 1 only and vice versa.
+ *
+ * <p>If the caller needs to see all subscriptions across user profiles,
+ * use {@link #createForAllUserProfiles} to convert this instance to see all.
+ *
+ * <p> The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by
+ * {@link SubscriptionInfo#getSubscriptionId}.
*
* <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
* or that the calling app has carrier privileges (see
@@ -1800,8 +1842,25 @@
* </ul>
*/
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ // @RequiresPermission(TODO(b/308809058))
public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
- return getActiveSubscriptionInfoList(/* userVisibleonly */true);
+ List<SubscriptionInfo> activeList = null;
+
+ try {
+ ISub iSub = TelephonyManager.getSubscriptionService();
+ if (iSub != null) {
+ activeList = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName(),
+ mContext.getAttributionTag(), mIsForAllUserProfiles);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ if (activeList != null) {
+ activeList = activeList.stream().filter(subInfo -> isSubscriptionVisible(subInfo))
+ .collect(Collectors.toList());
+ }
+ return activeList;
}
/**
@@ -1835,6 +1894,26 @@
}
/**
+ * Convert this subscription manager instance into one that can see all subscriptions across
+ * user profiles.
+ *
+ * @return a SubscriptionManager that can see all subscriptions regardless its user profile
+ * association.
+ *
+ * @see #getActiveSubscriptionInfoList
+ * @see #getActiveSubscriptionInfoCount
+ * @see UserHandle
+ */
+ @FlaggedApi(Flags.FLAG_WORK_PROFILE_API_SPLIT)
+ // @RequiresPermission(TODO(b/308809058))
+ // The permission check for accessing all subscriptions will be enforced upon calling the
+ // individual APIs linked above.
+ @NonNull public SubscriptionManager createForAllUserProfiles() {
+ mIsForAllUserProfiles = true;
+ return this;
+ }
+
+ /**
* This is similar to {@link #getActiveSubscriptionInfoList()}, but if userVisibleOnly
* is true, it will filter out the hidden subscriptions.
*
@@ -1847,7 +1926,7 @@
ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
activeList = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName(),
- mContext.getAttributionTag());
+ mContext.getAttributionTag(), true /*isForAllUserProfiles*/);
}
} catch (RemoteException ex) {
// ignore it
@@ -2002,13 +2081,19 @@
}
/**
- * Get the active subscription count.
+ * Get the active subscription count associated with the current caller user profile for
+ * Android SDK 35(V) and above, while Android SDK 34(U) and below can see all subscriptions as
+ * it does today.
+ *
+ * <p>If the caller needs to see all subscriptions across user profiles,
+ * use {@link #createForAllUserProfiles} to convert this instance to see all.
*
* @return The current number of active subscriptions.
*
* @see #getActiveSubscriptionInfoList()
*/
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ // @RequiresPermission(TODO(b/308809058))
public int getActiveSubscriptionInfoCount() {
int result = 0;
@@ -2016,7 +2101,7 @@
ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
result = iSub.getActiveSubInfoCount(mContext.getOpPackageName(),
- mContext.getAttributionTag());
+ mContext.getAttributionTag(), mIsForAllUserProfiles);
}
} catch (RemoteException ex) {
// ignore it
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index d2dbeb7..3f41d56 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -66,6 +66,8 @@
*
* @param callingPackage The package maing the call.
* @param callingFeatureId The feature in the package
+ * @param isForAllProfiles whether the caller intends to see all subscriptions regardless
+ * association.
* @return Sorted list of the currently {@link SubscriptionInfo} records available on the device.
* <ul>
* <li>
@@ -83,14 +85,17 @@
* </ul>
*/
List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage,
- String callingFeatureId);
+ String callingFeatureId, boolean isForAllProfiles);
/**
* @param callingPackage The package making the call.
* @param callingFeatureId The feature in the package.
+ * @param isForAllProfile whether the caller intends to see all subscriptions regardless
+ * association.
* @return the number of active subscriptions
*/
- int getActiveSubInfoCount(String callingPackage, String callingFeatureId);
+ int getActiveSubInfoCount(String callingPackage, String callingFeatureId,
+ boolean isForAllProfile);
/**
* @return the maximum number of subscriptions this device will support at any one time.
diff --git a/tests/InputScreenshotTest/Android.bp b/tests/InputScreenshotTest/Android.bp
index 15aaa46..83ced2c 100644
--- a/tests/InputScreenshotTest/Android.bp
+++ b/tests/InputScreenshotTest/Android.bp
@@ -7,12 +7,27 @@
default_applicable_licenses: ["frameworks_base_license"],
}
+filegroup {
+ name: "InputScreenshotTestRNGFiles",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ exclude_srcs: [
+ "src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt",
+ "src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt",
+ ],
+}
+
android_test {
name: "InputScreenshotTests",
srcs: [
"src/**/*.java",
"src/**/*.kt",
],
+ exclude_srcs: [
+ "src/android/input/screenshot/package-info.java",
+ ],
platform_apis: true,
certificate: "platform",
static_libs: [
@@ -43,6 +58,7 @@
"hamcrest-library",
"kotlin-test",
"flag-junit",
+ "platform-parametric-runner-lib",
"platform-test-annotations",
"services.core.unboosted",
"testables",
diff --git a/tests/InputScreenshotTest/robotests/Android.bp b/tests/InputScreenshotTest/robotests/Android.bp
new file mode 100644
index 0000000..912f4b80
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/Android.bp
@@ -0,0 +1,71 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_library {
+ name: "InputRoboRNGTestsAssetsLib",
+ asset_dirs: ["assets"],
+ sdk_version: "current",
+ platform_apis: true,
+ manifest: "AndroidManifest.xml",
+ optimize: {
+ enabled: false,
+ },
+ use_resource_processor: true,
+}
+
+android_app {
+ name: "InputRoboApp",
+ srcs: [],
+ static_libs: [
+ "androidx.test.espresso.core",
+ "androidx.appcompat_appcompat",
+ "flag-junit",
+ "guava",
+ "InputRoboRNGTestsAssetsLib",
+ "platform-screenshot-diff-core",
+ "PlatformComposeSceneTransitionLayoutTestsUtils",
+ ],
+ manifest: "robo-manifest.xml",
+ aaptflags: [
+ "--extra-packages",
+ "com.android.input.screenshot",
+ ],
+ dont_merge_manifests: true,
+ platform_apis: true,
+ system_ext_specific: true,
+ certificate: "platform",
+ privileged: true,
+ resource_dirs: [],
+ kotlincflags: ["-Xjvm-default=all"],
+
+ plugins: ["dagger2-compiler"],
+ use_resource_processor: true,
+}
+
+android_robolectric_test {
+ name: "InputRoboRNGTests",
+ srcs: [
+ ":InputScreenshotTestRNGFiles",
+ ":flag-junit",
+ ":platform-test-screenshot-rules",
+ ],
+ // Do not add any new libraries here, they should be added to SystemUIGoogleRobo above.
+ static_libs: [
+ "androidx.compose.runtime_runtime",
+ "androidx.test.uiautomator_uiautomator",
+ "androidx.test.ext.junit",
+ "inline-mockito-robolectric-prebuilt",
+ "platform-parametric-runner-lib",
+ "uiautomator-helpers",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ "android.test.mock",
+ "truth",
+ ],
+ upstream: true,
+ java_resource_dirs: ["config"],
+ instrumentation_for: "InputRoboApp",
+}
diff --git a/tests/InputScreenshotTest/robotests/AndroidManifest.xml b/tests/InputScreenshotTest/robotests/AndroidManifest.xml
new file mode 100644
index 0000000..5689311
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.input.screenshot">
+ <uses-sdk android:minSdkVersion="21"/>
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+</manifest>
diff --git a/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png
new file mode 100644
index 0000000..baf204a
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png
new file mode 100644
index 0000000..deb3cee
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png
new file mode 100644
index 0000000..34e25f7
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/robotests/config/robolectric.properties b/tests/InputScreenshotTest/robotests/config/robolectric.properties
new file mode 100644
index 0000000..83d7549
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/config/robolectric.properties
@@ -0,0 +1,15 @@
+# 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.
+#
+sdk=NEWEST_SDK
\ No newline at end of file
diff --git a/tests/InputScreenshotTest/robotests/robo-manifest.xml b/tests/InputScreenshotTest/robotests/robo-manifest.xml
new file mode 100644
index 0000000..e86f58e
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/robo-manifest.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--- Include all the namespaces we will ever need anywhere, because this is the source the manifest merger uses for namespaces -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.input.screenshot"
+ coreApp="true">
+ <application>
+ <activity
+ android:name="androidx.activity.ComponentActivity"
+ android:exported="true">
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
index c2c3d55..75dab41 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.graphics.Bitmap
+import android.os.Build
import androidx.activity.ComponentActivity
import androidx.compose.foundation.Image
import androidx.compose.ui.platform.ViewRootForTest
@@ -49,15 +50,17 @@
)
)
private val composeRule = createAndroidComposeRule<ComponentActivity>()
- private val delegateRule =
- RuleChain.outerRule(colorsRule)
- .around(deviceEmulationRule)
+ private val roboRule =
+ RuleChain.outerRule(deviceEmulationRule)
.around(screenshotRule)
.around(composeRule)
+ private val delegateRule = RuleChain.outerRule(colorsRule).around(roboRule)
private val matcher = UnitTestBitmapMatcher
+ private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
override fun apply(base: Statement, description: Description): Statement {
- return delegateRule.apply(base, description)
+ val ruleToApply = if (isRobolectric) roboRule else delegateRule
+ return ruleToApply.apply(base, description)
}
/**
@@ -84,4 +87,4 @@
val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view
screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher)
}
-}
\ No newline at end of file
+}
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
index 8ae6dfd..ab7bb4e 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
@@ -26,14 +26,15 @@
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
import platform.test.screenshot.DeviceEmulationSpec
/** A screenshot test for Keyboard layout preview for Iso physical layout. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
class KeyboardLayoutPreviewIsoScreenshotTest(emulationSpec: DeviceEmulationSpec) {
companion object {
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
@JvmStatic
fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
}
@@ -55,4 +56,4 @@
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/package-info.java b/tests/InputScreenshotTest/src/android/input/screenshot/package-info.java
new file mode 100644
index 0000000..4b5a56d
--- /dev/null
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/package-info.java
@@ -0,0 +1,4 @@
+@GraphicsMode(GraphicsMode.Mode.NATIVE)
+package com.android.input.screenshot;
+
+import org.robolectric.annotation.GraphicsMode;
diff --git a/tests/SmokeTestApps/Android.bp b/tests/SmokeTestApps/Android.bp
index 3505fe1..38ee8ac 100644
--- a/tests/SmokeTestApps/Android.bp
+++ b/tests/SmokeTestApps/Android.bp
@@ -11,4 +11,7 @@
name: "SmokeTestTriggerApps",
srcs: ["src/**/*.java"],
sdk_version: "current",
+ errorprone: {
+ enabled: false,
+ },
}
diff --git a/tools/hoststubgen/hoststubgen/framework-policy-override.txt b/tools/hoststubgen/hoststubgen/framework-policy-override.txt
index ff0fe32..493ad56 100644
--- a/tools/hoststubgen/hoststubgen/framework-policy-override.txt
+++ b/tools/hoststubgen/hoststubgen/framework-policy-override.txt
@@ -78,6 +78,9 @@
class com.android.internal.util.FastPrintWriter keepclass
class com.android.internal.util.LineBreakBufferedWriter keepclass
+class android.util.EventLog stubclass
+class android.util.EventLog !com.android.hoststubgen.nativesubstitution.EventLog_host
+class android.util.EventLog$Event stubclass
# Expose Context because it's referred to by AndroidTestCase, but don't need to expose any of
# its members.
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java
new file mode 100644
index 0000000..292e8da
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.hoststubgen.nativesubstitution;
+
+import android.util.Log;
+import android.util.Log.Level;
+
+import java.util.Collection;
+
+public class EventLog_host {
+ public static int writeEvent(int tag, int value) {
+ return writeEvent(tag, (Object) value);
+ }
+
+ public static int writeEvent(int tag, long value) {
+ return writeEvent(tag, (Object) value);
+ }
+
+ public static int writeEvent(int tag, float value) {
+ return writeEvent(tag, (Object) value);
+ }
+
+ public static int writeEvent(int tag, String str) {
+ return writeEvent(tag, (Object) str);
+ }
+
+ public static int writeEvent(int tag, Object... list) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("logd: [event] ");
+ final String tagName = android.util.EventLog.getTagName(tag);
+ if (tagName != null) {
+ sb.append(tagName);
+ } else {
+ sb.append(tag);
+ }
+ sb.append(": [");
+ for (int i = 0; i < list.length; i++) {
+ sb.append(String.valueOf(list[i]));
+ if (i < list.length - 1) {
+ sb.append(',');
+ }
+ }
+ sb.append(']');
+ System.out.println(sb.toString());
+ return sb.length();
+ }
+
+ public static void readEvents(int[] tags, Collection<android.util.EventLog.Event> output) {
+ throw new UnsupportedOperationException();
+ }
+
+ public static void readEventsOnWrapping(int[] tags, long timestamp,
+ Collection<android.util.EventLog.Event> output) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
index 4a3a798..668c94c 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
@@ -27,9 +27,10 @@
/**
* Tentative, partial implementation of the Parcel native methods, using Java's
- * {@link ByteBuffer}. It turned out there's enough semantics differences between Parcel
- * and {@link ByteBuffer}, so it didn't actually work.
- * (e.g. Parcel seems to allow moving the data position to be beyond its size? Which
+ * {@code byte[]}.
+ * (We don't use a {@link ByteBuffer} because there's enough semantics differences between Parcel
+ * and {@link ByteBuffer}, and it didn't work out.
+ * e.g. Parcel seems to allow moving the data position to be beyond its size? Which
* {@link ByteBuffer} wouldn't allow...)
*/
public class Parcel_host {
diff --git a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
index 89daa20..85038be 100755
--- a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
+++ b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
@@ -13,6 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# This command is expected to be executed with: atest hoststubgen-invoke-test
+
set -e # Exit when any command files
# This script runs HostStubGen directly with various arguments and make sure
@@ -35,6 +37,12 @@
mkdir -p $TEMP
fi
+cleanup_temp() {
+ rm -fr $TEMP/*
+}
+
+cleanup_temp
+
JAR=hoststubgen-test-tiny-framework.jar
STUB=$TEMP/stub.jar
IMPL=$TEMP/impl.jar
@@ -47,12 +55,10 @@
# HostStubGen result in it.
HOSTSTUBGEN_RC=0
-# Define the functions to
-
-
# Note, because the build rule will only install hoststubgen.jar, but not the wrapper script,
# we need to execute it manually with the java command.
hoststubgen() {
+ echo "Running hoststubgen with: $*"
java -jar ./hoststubgen.jar "$@"
}
@@ -62,7 +68,7 @@
echo "# Test: $test_name"
- rm -f $HOSTSTUBGEN_OUT
+ cleanup_temp
local filter_arg=""
@@ -73,11 +79,21 @@
cat $ANNOTATION_FILTER
fi
+ local stub_arg=""
+ local impl_arg=""
+
+ if [[ "$STUB" != "" ]] ; then
+ stub_arg="--out-stub-jar $STUB"
+ fi
+ if [[ "$IMPL" != "" ]] ; then
+ impl_arg="--out-impl-jar $IMPL"
+ fi
+
hoststubgen \
--debug \
--in-jar $JAR \
- --out-stub-jar $STUB \
- --out-impl-jar $IMPL \
+ $stub_arg \
+ $impl_arg \
--stub-annotation \
android.hosttest.annotation.HostSideTestStub \
--keep-annotation \
@@ -105,6 +121,21 @@
return 0
}
+assert_file_generated() {
+ local file="$1"
+ if [[ "$file" == "" ]] ; then
+ if [[ -f "$file" ]] ; then
+ echo "HostStubGen shouldn't have generated $file"
+ return 1
+ fi
+ else
+ if ! [[ -f "$file" ]] ; then
+ echo "HostStubGen didn't generate $file"
+ return 1
+ fi
+ fi
+}
+
run_hoststubgen_for_success() {
run_hoststubgen "$@"
@@ -112,6 +143,9 @@
echo "HostStubGen expected to finish successfully, but failed with $rc"
return 1
fi
+
+ assert_file_generated "$STUB"
+ assert_file_generated "$IMPL"
}
run_hoststubgen_for_failure() {
@@ -189,6 +223,11 @@
* # All other classes allowed
"
+STUB="" run_hoststubgen_for_success "No stub generation" ""
+
+IMPL="" run_hoststubgen_for_success "No impl generation" ""
+
+STUB="" IMPL="" run_hoststubgen_for_success "No stub, no impl generation" ""
echo "All tests passed"
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 07bd2dc..3cdddc2 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -237,8 +237,8 @@
*/
private fun convert(
inJar: String,
- outStubJar: String,
- outImplJar: String,
+ outStubJar: String?,
+ outImplJar: String?,
filter: OutputFilter,
enableChecker: Boolean,
classes: ClassNodes,
@@ -254,8 +254,8 @@
log.withIndent {
// Open the input jar file and process each entry.
ZipFile(inJar).use { inZip ->
- ZipOutputStream(FileOutputStream(outStubJar)).use { stubOutStream ->
- ZipOutputStream(FileOutputStream(outImplJar)).use { implOutStream ->
+ maybeWithZipOutputStream(outStubJar) { stubOutStream ->
+ maybeWithZipOutputStream(outImplJar) { implOutStream ->
val inEntries = inZip.entries()
while (inEntries.hasMoreElements()) {
val entry = inEntries.nextElement()
@@ -265,22 +265,29 @@
log.i("Converted all entries.")
}
}
- log.i("Created stub: $outStubJar")
- log.i("Created impl: $outImplJar")
+ outStubJar?.let { log.i("Created stub: $it") }
+ outImplJar?.let { log.i("Created impl: $it") }
}
}
val end = System.currentTimeMillis()
log.v("Done transforming the jar in %.1f second(s).", (end - start) / 1000.0)
}
+ private fun <T> maybeWithZipOutputStream(filename: String?, block: (ZipOutputStream?) -> T): T {
+ if (filename == null) {
+ return block(null)
+ }
+ return ZipOutputStream(FileOutputStream(filename)).use(block)
+ }
+
/**
* Convert a single ZIP entry, which may or may not be a class file.
*/
private fun convertSingleEntry(
inZip: ZipFile,
entry: ZipEntry,
- stubOutStream: ZipOutputStream,
- implOutStream: ZipOutputStream,
+ stubOutStream: ZipOutputStream?,
+ implOutStream: ZipOutputStream?,
filter: OutputFilter,
packageRedirector: PackageRedirectRemapper,
enableChecker: Boolean,
@@ -316,8 +323,8 @@
// Unknown type, we just copy it to both output zip files.
// TODO: We probably shouldn't do it for stub jar?
log.v("Copying: %s", entry.name)
- copyZipEntry(inZip, entry, stubOutStream)
- copyZipEntry(inZip, entry, implOutStream)
+ stubOutStream?.let { copyZipEntry(inZip, entry, it) }
+ implOutStream?.let { copyZipEntry(inZip, entry, it) }
}
}
@@ -346,8 +353,8 @@
private fun processSingleClass(
inZip: ZipFile,
entry: ZipEntry,
- stubOutStream: ZipOutputStream,
- implOutStream: ZipOutputStream,
+ stubOutStream: ZipOutputStream?,
+ implOutStream: ZipOutputStream?,
filter: OutputFilter,
packageRedirector: PackageRedirectRemapper,
enableChecker: Boolean,
@@ -361,7 +368,7 @@
return
}
// Generate stub first.
- if (classPolicy.policy.needsInStub) {
+ if (stubOutStream != null && classPolicy.policy.needsInStub) {
log.v("Creating stub class: %s Policy: %s", classInternalName, classPolicy)
log.withIndent {
BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
@@ -374,8 +381,8 @@
}
}
}
- log.v("Creating impl class: %s Policy: %s", classInternalName, classPolicy)
- if (classPolicy.policy.needsInImpl) {
+ if (implOutStream != null && classPolicy.policy.needsInImpl) {
+ log.v("Creating impl class: %s Policy: %s", classInternalName, classPolicy)
log.withIndent {
BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
val newEntry = ZipEntry(entry.name)
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index da53487..83f873d 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -28,10 +28,10 @@
var inJar: String = "",
/** Output stub jar file */
- var outStubJar: String = "",
+ var outStubJar: String? = null,
/** Output implementation jar file */
- var outImplJar: String = "",
+ var outImplJar: String? = null,
var inputJarDumpFile: String? = null,
@@ -71,7 +71,7 @@
var enablePreTrace: Boolean = false,
var enablePostTrace: Boolean = false,
- var enableNonStubMethodCallDetection: Boolean = true,
+ var enableNonStubMethodCallDetection: Boolean = false,
) {
companion object {
@@ -209,11 +209,14 @@
if (ret.inJar.isEmpty()) {
throw ArgumentsException("Required option missing: --in-jar")
}
- if (ret.outStubJar.isEmpty()) {
- throw ArgumentsException("Required option missing: --out-stub-jar")
+ if (ret.outStubJar == null && ret.outImplJar == null) {
+ log.w("Neither --out-stub-jar nor --out-impl-jar is set." +
+ " $COMMAND_NAME will not generate jar files.")
}
- if (ret.outImplJar.isEmpty()) {
- throw ArgumentsException("Required option missing: --out-impl-jar")
+
+ if (ret.enableNonStubMethodCallDetection) {
+ log.w("--enable-non-stub-method-check is not fully implemented yet." +
+ " See the todo in doesMethodNeedNonStubCallCheck().")
}
return ret
diff --git a/tools/hoststubgen/hoststubgen/test-framework/README.md b/tools/hoststubgen/hoststubgen/test-framework/README.md
index 20e2f87..f616ad6 100644
--- a/tools/hoststubgen/hoststubgen/test-framework/README.md
+++ b/tools/hoststubgen/hoststubgen/test-framework/README.md
@@ -14,12 +14,6 @@
$ atest --no-bazel-mode HostStubGenTest-framework-test-host-test
```
-- With `run-ravenwood-test`
-
-```
-$ run-ravenwood-test HostStubGenTest-framework-test-host-test
-```
-
- Advanced option: `run-test-without-atest.sh` runs the test without using `atest` or `run-ravenwood-test`
```
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md b/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md
index f3c0450..3bfad9b 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md
@@ -13,12 +13,6 @@
$ atest hoststubgen-test-tiny-test
```
-- With `run-ravenwood-test` should work too. This is the proper way to run it.
-
-```
-$ run-ravenwood-test hoststubgen-test-tiny-test
-```
-
- `run-test-manually.sh` also run the test, but it builds the stub/impl jars and the test without
using the build system. This is useful for debugging the tool.
diff --git a/tools/hoststubgen/scripts/Android.bp b/tools/hoststubgen/scripts/Android.bp
index 5da805e..b1ba07e 100644
--- a/tools/hoststubgen/scripts/Android.bp
+++ b/tools/hoststubgen/scripts/Android.bp
@@ -18,9 +18,3 @@
tools: ["dump-jar"],
cmd: "$(location dump-jar) -s -o $(out) $(in)",
}
-
-sh_binary_host {
- name: "run-ravenwood-test",
- src: "run-ravenwood-test",
- visibility: ["//visibility:public"],
-}
diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh
index 2dac089..82faa91 100755
--- a/tools/hoststubgen/scripts/run-all-tests.sh
+++ b/tools/hoststubgen/scripts/run-all-tests.sh
@@ -22,10 +22,10 @@
READY_TEST_MODULES=(
HostStubGenTest-framework-all-test-host-test
hoststubgen-test-tiny-test
+ CtsUtilTestCasesRavenwood
)
MUST_BUILD_MODULES=(
- run-ravenwood-test
"${NOT_READY_TEST_MODULES[*]}"
HostStubGenTest-framework-test
)
@@ -51,8 +51,6 @@
# run ./scripts/build-framework-hostside-jars-without-genrules.sh
# These tests should all pass.
-run-ravenwood-test ${READY_TEST_MODULES[*]}
-
-run atest CtsUtilTestCasesRavenwood
+run atest ${READY_TEST_MODULES[*]}
echo ""${0##*/}" finished, with no unexpected failures. Ready to submit!"
\ No newline at end of file
diff --git a/tools/hoststubgen/scripts/run-ravenwood-test b/tools/hoststubgen/scripts/run-ravenwood-test
deleted file mode 100755
index 9bbb859..0000000
--- a/tools/hoststubgen/scripts/run-ravenwood-test
+++ /dev/null
@@ -1,129 +0,0 @@
-#!/bin/bash
-# Copyright (C) 2023 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -e
-
-# Script to run a "Ravenwood" host side test.
-#
-# A proper way to run these tests is to use `atest`, but `atest` has a known issue of loading
-# unrelated jar files as the class path, so for now we use this script to run host side tests.
-
-# Copy (with some changes) some functions from ../common.sh, so this script can be used without it.
-
-m() {
- if (( $SKIP_BUILD )) ; then
- echo "Skipping build: $*" 1>&2
- return 0
- fi
- run ${ANDROID_BUILD_TOP}/build/soong/soong_ui.bash --make-mode "$@"
-}
-
-run() {
- echo "Running: $*" 1>&2
- "$@"
-}
-
-run_junit_test_jar() {
- local jar="$1"
- echo "Starting test: $jar ..."
- run cd "${jar%/*}"
-
- run ${JAVA:-java} $JAVA_OPTS \
- -cp $jar \
- org.junit.runner.JUnitCore \
- com.android.hoststubgen.hosthelper.HostTestSuite || return 1
- return 0
-}
-
-help() {
- cat <<'EOF'
-
- run-ravenwood-test -- Run Ravenwood host tests
-
- Usage:
- run-ravenwood-test [options] MODULE-NAME ...
-
- Options:
- -h: Help
- -t: Run test only, without building
- -b: Build only, without running
-
- Example:
- run-ravenwood-test HostStubGenTest-framework-test-host-test
-
-EOF
-}
-
-#-------------------------------------------------------------------------
-# Parse options
-#-------------------------------------------------------------------------
-build=0
-test=0
-
-while getopts "htb" opt; do
- case "$opt" in
- h) help; exit 0 ;;
- t)
- test=1
- ;;
- b)
- build=1
- ;;
- esac
-done
-shift $(($OPTIND - 1))
-
-# If neither -t nor -b is provided, then build and run./
-if (( ( $build + $test ) == 0 )) ; then
- build=1
- test=1
-fi
-
-
-modules=("${@}")
-
-if (( "${#modules[@]}" == 0 )); then
- help
- exit 1
-fi
-
-#-------------------------------------------------------------------------
-# Build
-#-------------------------------------------------------------------------
-if (( $build )) ; then
- run m "${modules[@]}"
-fi
-
-#-------------------------------------------------------------------------
-# Run
-#-------------------------------------------------------------------------
-
-failures=0
-if (( test )) ; then
- for module in "${modules[@]}"; do
- if run_junit_test_jar "$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/${module}/${module}.jar"; then
- : # passed.
- else
- failures=$(( failures + 1 ))
- fi
- done
-
- if (( $failures > 0 )) ; then
- echo "$failures test jar(s) failed." 1>&2
- exit 2
- fi
-fi
-
-exit 0
\ No newline at end of file