Merge "Edit trace to start with "draw"" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index b924ac8..c8046ab 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -152,6 +152,11 @@
java_aconfig_library {
name: "android.nfc.flags-aconfig-java",
aconfig_declarations: "android.nfc.flags-aconfig",
+ min_sdk_version: "VanillaIceCream",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.nfcservices",
+ ],
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
@@ -289,6 +294,12 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+rust_aconfig_library {
+ name: "libandroid_security_flags_rust",
+ crate_name: "android_security_flags",
+ aconfig_declarations: "android.security.flags-aconfig",
+}
+
// Package Manager
aconfig_declarations {
name: "android.content.pm.flags-aconfig",
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index 45bb161..e7adf20 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -77,6 +77,42 @@
output_extension: "proto.h",
}
+// ==== nfc framework java library ==============================
+gensrcs {
+ name: "framework-nfc-javastream-protos",
+
+ tools: [
+ "aprotoc",
+ "protoc-gen-javastream",
+ "soong_zip",
+ ],
+
+ cmd: "mkdir -p $(genDir)/$(in) " +
+ "&& $(location aprotoc) " +
+ " --plugin=$(location protoc-gen-javastream) " +
+ " --javastream_out=$(genDir)/$(in) " +
+ " -Iexternal/protobuf/src " +
+ " -I . " +
+ " $(in) " +
+ "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
+
+ srcs: [
+ "core/proto/android/app/pendingintent.proto",
+ "core/proto/android/content/component_name.proto",
+ "core/proto/android/content/intent.proto",
+ "core/proto/android/nfc/*.proto",
+ "core/proto/android/os/patternmatcher.proto",
+ "core/proto/android/os/persistablebundle.proto",
+ "core/proto/android/privacy.proto",
+ ],
+
+ data: [
+ ":libprotobuf-internal-protos",
+ ],
+
+ output_extension: "srcjar",
+}
+
// ==== java proto host library ==============================
java_library_host {
name: "platformprotos",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index a143d6f..bbe1485 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1555,7 +1555,7 @@
private final Predicate<Integer> mIsUidActivePredicate = this::isUidActive;
- public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
+ public int scheduleAsPackage(JobInfo job, JobWorkItem work, int callingUid, String packageName,
int userId, @Nullable String namespace, String tag) {
// Rate limit excessive schedule() calls.
final String servicePkg = job.getService().getPackageName();
@@ -1608,12 +1608,12 @@
mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG);
}
- if (mActivityManagerInternal.isAppStartModeDisabled(uId, servicePkg)) {
- Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
+ if (mActivityManagerInternal.isAppStartModeDisabled(callingUid, servicePkg)) {
+ Slog.w(TAG, "Not scheduling job for " + callingUid + ":" + job.toString()
+ " -- package not allowed to start");
Counter.logIncrementWithUid(
"job_scheduler.value_cntr_w_uid_schedule_failure_app_start_mode_disabled",
- uId);
+ callingUid);
return JobScheduler.RESULT_FAILURE;
}
@@ -1623,7 +1623,7 @@
job.getEstimatedNetworkDownloadBytes()));
sInitialJobEstimatedNetworkUploadKBLogger.logSample(
safelyScaleBytesToKBForHistogram(job.getEstimatedNetworkUploadBytes()));
- sJobMinimumChunkKBLogger.logSampleWithUid(uId,
+ sJobMinimumChunkKBLogger.logSampleWithUid(callingUid,
safelyScaleBytesToKBForHistogram(job.getMinimumNetworkChunkBytes()));
if (work != null) {
sInitialJwiEstimatedNetworkDownloadKBLogger.logSample(
@@ -1632,7 +1632,7 @@
sInitialJwiEstimatedNetworkUploadKBLogger.logSample(
safelyScaleBytesToKBForHistogram(
work.getEstimatedNetworkUploadBytes()));
- sJwiMinimumChunkKBLogger.logSampleWithUid(uId,
+ sJwiMinimumChunkKBLogger.logSampleWithUid(callingUid,
safelyScaleBytesToKBForHistogram(
work.getMinimumNetworkChunkBytes()));
}
@@ -1640,11 +1640,12 @@
if (work != null) {
Counter.logIncrementWithUid(
- "job_scheduler.value_cntr_w_uid_job_work_items_enqueued", uId);
+ "job_scheduler.value_cntr_w_uid_job_work_items_enqueued", callingUid);
}
synchronized (mLock) {
- final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, namespace, job.getId());
+ final JobStatus toCancel =
+ mJobs.getJobByUidAndJobId(callingUid, namespace, job.getId());
if (work != null && toCancel != null) {
// Fast path: we are adding work to an existing job, and the JobInfo is not
@@ -1664,7 +1665,7 @@
// TODO(273758274): improve JobScheduler's resilience and memory management
if (toCancel.getWorkCount() >= mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS
&& toCancel.isPersisted()) {
- Slog.w(TAG, "Too many JWIs for uid " + uId);
+ Slog.w(TAG, "Too many JWIs for uid " + callingUid);
throw new IllegalStateException("Apps may not persist more than "
+ mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS
+ " JobWorkItems per job");
@@ -1682,7 +1683,8 @@
| JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ);
}
mJobs.touchJob(toCancel);
- sEnqueuedJwiHighWaterMarkLogger.logSampleWithUid(uId, toCancel.getWorkCount());
+ sEnqueuedJwiHighWaterMarkLogger
+ .logSampleWithUid(callingUid, toCancel.getWorkCount());
// If any of work item is enqueued when the source is in the foreground,
// exempt the entire job.
@@ -1692,8 +1694,8 @@
}
}
- JobStatus jobStatus =
- JobStatus.createFromJobInfo(job, uId, packageName, userId, namespace, tag);
+ JobStatus jobStatus = JobStatus.createFromJobInfo(
+ job, callingUid, packageName, userId, namespace, tag);
// Return failure early if expedited job quota used up.
if (jobStatus.isRequestedExpeditedJob()) {
@@ -1702,7 +1704,7 @@
&& !mQuotaController.isWithinEJQuotaLocked(jobStatus))) {
Counter.logIncrementWithUid(
"job_scheduler.value_cntr_w_uid_schedule_failure_ej_out_of_quota",
- uId);
+ callingUid);
return JobScheduler.RESULT_FAILURE;
}
}
@@ -1716,10 +1718,10 @@
if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
// Jobs on behalf of others don't apply to the per-app job cap
if (packageName == null) {
- if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
- Slog.w(TAG, "Too many jobs for uid " + uId);
+ if (mJobs.countJobsForUid(callingUid) > MAX_JOBS_PER_APP) {
+ Slog.w(TAG, "Too many jobs for uid " + callingUid);
Counter.logIncrementWithUid(
- "job_scheduler.value_cntr_w_uid_max_scheduling_limit_hit", uId);
+ "job_scheduler.value_cntr_w_uid_max_scheduling_limit_hit", callingUid);
throw new IllegalStateException("Apps may not schedule more than "
+ MAX_JOBS_PER_APP + " distinct jobs");
}
@@ -1743,7 +1745,7 @@
// TODO(273758274): improve JobScheduler's resilience and memory management
if (work != null && toCancel.isPersisted()
&& toCancel.getWorkCount() >= mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS) {
- Slog.w(TAG, "Too many JWIs for uid " + uId);
+ Slog.w(TAG, "Too many JWIs for uid " + callingUid);
throw new IllegalStateException("Apps may not persist more than "
+ mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS
+ " JobWorkItems per job");
@@ -1759,13 +1761,14 @@
if (work != null) {
// If work has been supplied, enqueue it into the new job.
jobStatus.enqueueWorkLocked(work);
- sEnqueuedJwiHighWaterMarkLogger.logSampleWithUid(uId, jobStatus.getWorkCount());
+ sEnqueuedJwiHighWaterMarkLogger
+ .logSampleWithUid(callingUid, jobStatus.getWorkCount());
}
- final int sourceUid = uId;
+ final int sourceUid = jobStatus.getSourceUid();
FrameworkStatsLog.write(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
jobStatus.isProxyJob()
- ? new int[]{sourceUid, jobStatus.getUid()} : new int[]{sourceUid},
+ ? new int[]{sourceUid, callingUid} : new int[]{sourceUid},
// Given that the source tag is set by the calling app, it should be connected
// to the calling app in the attribution for a proxied job.
jobStatus.isProxyJob()
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index d48d84b..1fdf906 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -75,6 +75,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;
import java.util.function.Predicate;
+import java.util.regex.Pattern;
/**
* Maintains the master list of jobs that the job scheduler is tracking. These jobs are compared by
@@ -99,6 +100,8 @@
private static final long SCHEDULED_JOB_HIGH_WATER_MARK_PERIOD_MS = 30 * 60_000L;
@VisibleForTesting
static final String JOB_FILE_SPLIT_PREFIX = "jobs_";
+ private static final Pattern SPLIT_FILE_PATTERN =
+ Pattern.compile("^" + JOB_FILE_SPLIT_PREFIX + "\\d+.xml$");
private static final int ALL_UIDS = -1;
@VisibleForTesting
static final int INVALID_UID = -2;
@@ -1121,6 +1124,11 @@
int numDuplicates = 0;
synchronized (mLock) {
for (File file : files) {
+ if (!file.getName().equals("jobs.xml")
+ && !SPLIT_FILE_PATTERN.matcher(file.getName()).matches()) {
+ // Skip temporary or other files.
+ continue;
+ }
final AtomicFile aFile = createJobFile(file);
try (FileInputStream fis = aFile.openRead()) {
jobs = readJobMapImpl(fis, rtcGood, nowElapsed);
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index e162100..e086bfe 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -139,9 +139,22 @@
// using droiddoc
/////////////////////////////////////////////////////////////////////
-framework_docs_only_args = " -android -manifest $(location :frameworks-base-core-AndroidManifest.xml) " +
+// doclava contains checks for a few issues that are have been migrated to metalava.
+// disable them in doclava, to avoid mistriggering or double triggering.
+ignore_doclava_errors_checked_by_metalava = "" +
+ "-hide 111 " + // HIDDEN_SUPERCLASS
+ "-hide 113 " + // DEPRECATION_MISMATCH
+ "-hide 125 " + // REQUIRES_PERMISSION
+ "-hide 126 " + // BROADCAST_BEHAVIOR
+ "-hide 127 " + // SDK_CONSTANT
+ "-hide 128 " // TODO
+
+framework_docs_only_args = "-android " +
+ "-manifest $(location :frameworks-base-core-AndroidManifest.xml) " +
"-metalavaApiSince " +
- "-werror -lerror -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 " +
+ "-werror " +
+ "-lerror " +
+ ignore_doclava_errors_checked_by_metalava +
"-overview $(location :frameworks-base-java-overview) " +
// Federate Support Library references against local API file.
"-federate SupportLib https://developer.android.com " +
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 7e41660..7e78185 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -739,7 +739,9 @@
"android_stubs_current_contributions",
"android_system_stubs_current_contributions",
"android_test_frameworks_core_stubs_current_contributions",
- "stub-annotation-defaults",
+ ],
+ libs: [
+ "stub-annotations",
],
api_contributions: [
"api-stubs-docs-non-updatable.api.contribution",
diff --git a/core/api/current.txt b/core/api/current.txt
index 8a08c02..639a991 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9533,6 +9533,7 @@
method public int getId();
method public int getSystemDataSyncFlags();
method @FlaggedApi("android.companion.association_tag") @Nullable public String getTag();
+ method public boolean isSelfManaged();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationInfo> CREATOR;
}
@@ -17576,7 +17577,7 @@
ctor public FontFamily.Builder(@NonNull android.graphics.fonts.Font);
method @NonNull public android.graphics.fonts.FontFamily.Builder addFont(@NonNull android.graphics.fonts.Font);
method @NonNull public android.graphics.fonts.FontFamily build();
- method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") @Nullable public android.graphics.fonts.FontFamily buildVariableFamily();
+ method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") @Nullable public android.graphics.fonts.FontFamily buildVariableFamily();
}
public final class FontStyle {
@@ -17764,18 +17765,18 @@
method public float getAdvance();
method public float getAscent();
method public float getDescent();
- method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public boolean getFakeBold(@IntRange(from=0) int);
- method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public boolean getFakeItalic(@IntRange(from=0) int);
+ method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public boolean getFakeBold(@IntRange(from=0) int);
+ method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public boolean getFakeItalic(@IntRange(from=0) int);
method @NonNull public android.graphics.fonts.Font getFont(@IntRange(from=0) int);
method @IntRange(from=0) public int getGlyphId(@IntRange(from=0) int);
method public float getGlyphX(@IntRange(from=0) int);
method public float getGlyphY(@IntRange(from=0) int);
- method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public float getItalicOverride(@IntRange(from=0) int);
+ method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public float getItalicOverride(@IntRange(from=0) int);
method public float getOffsetX();
method public float getOffsetY();
- method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public float getWeightOverride(@IntRange(from=0) int);
+ method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public float getWeightOverride(@IntRange(from=0) int);
method @IntRange(from=0) public int glyphCount();
- field @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public static final float NO_OVERRIDE = 1.4E-45f;
+ field @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public static final float NO_OVERRIDE = 1.4E-45f;
}
public class TextRunShaper {
@@ -45708,6 +45709,7 @@
field public static final int TYPE_IMS = 64; // 0x40
field public static final int TYPE_MCX = 1024; // 0x400
field public static final int TYPE_MMS = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int TYPE_RCS = 32768; // 0x8000
field public static final int TYPE_SUPL = 4; // 0x4
field public static final int TYPE_VSIM = 4096; // 0x1000
field public static final int TYPE_XCAP = 2048; // 0x800
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c3c63b5..ac61107 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3149,7 +3149,6 @@
public final class AssociationInfo implements android.os.Parcelable {
method @NonNull public String getPackageName();
- method public boolean isSelfManaged();
}
public final class CompanionDeviceManager {
@@ -10483,7 +10482,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public boolean isUserNameSet();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isUserOfType(@NonNull String);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isUserUnlockingOrUnlocked(@NonNull android.os.UserHandle);
- method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public boolean isUserVisible();
+ method public boolean isUserVisible();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public int removeUserWhenPossible(@NonNull android.os.UserHandle, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public void setBootUser(@NonNull android.os.UserHandle);
@@ -14718,6 +14717,7 @@
field public static final String TYPE_IMS_STRING = "ims";
field public static final String TYPE_MCX_STRING = "mcx";
field public static final String TYPE_MMS_STRING = "mms";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String TYPE_RCS_STRING = "rcs";
field public static final String TYPE_SUPL_STRING = "supl";
field public static final String TYPE_VSIM_STRING = "vsim";
field public static final String TYPE_XCAP_STRING = "xcap";
diff --git a/core/java/android/app/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
index 06bff5d..b34f678 100644
--- a/core/java/android/app/servertransaction/ActivityLifecycleItem.java
+++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
@@ -58,11 +58,6 @@
super(in);
}
- @Override
- boolean isActivityLifecycleItem() {
- return true;
- }
-
/** A final lifecycle state that an activity should reach. */
@LifecycleState
public abstract int getTargetState();
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index f7f901b..8617386 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -45,13 +45,6 @@
*/
public class ClientTransaction implements Parcelable, ObjectPoolItem {
- /**
- * List of transaction items that should be executed in order. Including both
- * {@link ActivityLifecycleItem} and other {@link ClientTransactionItem}.
- */
- @Nullable
- private List<ClientTransactionItem> mTransactionItems;
-
/** A list of individual callbacks to a client. */
@UnsupportedAppUsage
private List<ClientTransactionItem> mActivityCallbacks;
@@ -71,32 +64,9 @@
}
/**
- * 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) {
- mTransactionItems = new ArrayList<>();
- }
- mTransactionItems.add(item);
- }
-
- /**
- * Gets the list of client window requests/callbacks.
- * TODO(b/260873529): must be non null after remove the deprecated methods.
- */
- @Nullable
- public List<ClientTransactionItem> getTransactionItems() {
- return mTransactionItems;
- }
-
- /**
- * Adds a message to the end of the sequence of callbacks.
+ * Add a message to the end of the sequence of callbacks.
* @param activityCallback A single message that can contain a lifecycle request/callback.
- * @deprecated use {@link #addTransactionItem(ClientTransactionItem)} instead.
*/
- @Deprecated
public void addCallback(@NonNull ClientTransactionItem activityCallback) {
if (mActivityCallbacks == null) {
mActivityCallbacks = new ArrayList<>();
@@ -104,35 +74,25 @@
mActivityCallbacks.add(activityCallback);
}
- /**
- * Gets the list of callbacks.
- * @deprecated use {@link #getTransactionItems()} instead.
- */
+ /** Get the list of callbacks. */
@Nullable
@VisibleForTesting
@UnsupportedAppUsage
- @Deprecated
public List<ClientTransactionItem> getCallbacks() {
return mActivityCallbacks;
}
- /**
- * Gets the target state lifecycle request.
- * @deprecated use {@link #getTransactionItems()} instead.
- */
+ /** Get the target state lifecycle request. */
@VisibleForTesting(visibility = PACKAGE)
@UnsupportedAppUsage
- @Deprecated
public ActivityLifecycleItem getLifecycleStateRequest() {
return mLifecycleStateRequest;
}
/**
- * Sets the lifecycle state in which the client should be after executing the transaction.
+ * Set the lifecycle state in which the client should be after executing the transaction.
* @param stateRequest A lifecycle request initialized with right parameters.
- * @deprecated use {@link #addTransactionItem(ClientTransactionItem)} instead.
*/
- @Deprecated
public void setLifecycleStateRequest(@NonNull ActivityLifecycleItem stateRequest) {
mLifecycleStateRequest = stateRequest;
}
@@ -143,14 +103,6 @@
* requested by transaction items.
*/
public void preExecute(@NonNull ClientTransactionHandler clientTransactionHandler) {
- if (mTransactionItems != null) {
- final int size = mTransactionItems.size();
- for (int i = 0; i < size; ++i) {
- mTransactionItems.get(i).preExecute(clientTransactionHandler);
- }
- return;
- }
-
if (mActivityCallbacks != null) {
final int size = mActivityCallbacks.size();
for (int i = 0; i < size; ++i) {
@@ -195,19 +147,12 @@
@Override
public void recycle() {
- if (mTransactionItems != null) {
- int size = mTransactionItems.size();
- for (int i = 0; i < size; i++) {
- mTransactionItems.get(i).recycle();
- }
- mTransactionItems = null;
- }
if (mActivityCallbacks != null) {
int size = mActivityCallbacks.size();
for (int i = 0; i < size; i++) {
mActivityCallbacks.get(i).recycle();
}
- mActivityCallbacks = null;
+ mActivityCallbacks.clear();
}
if (mLifecycleStateRequest != null) {
mLifecycleStateRequest.recycle();
@@ -220,15 +165,8 @@
// Parcelable implementation
/** Write to Parcel. */
- @SuppressWarnings("AndroidFrameworkEfficientParcelable") // Item class is not final.
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- final boolean writeTransactionItems = mTransactionItems != null;
- dest.writeBoolean(writeTransactionItems);
- if (writeTransactionItems) {
- dest.writeParcelableList(mTransactionItems, flags);
- }
-
dest.writeParcelable(mLifecycleStateRequest, flags);
final boolean writeActivityCallbacks = mActivityCallbacks != null;
dest.writeBoolean(writeActivityCallbacks);
@@ -239,20 +177,11 @@
/** Read from Parcel. */
private ClientTransaction(@NonNull Parcel in) {
- final boolean readTransactionItems = in.readBoolean();
- if (readTransactionItems) {
- mTransactionItems = new ArrayList<>();
- in.readParcelableList(mTransactionItems, getClass().getClassLoader(),
- ClientTransactionItem.class);
- }
-
- mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(),
- ActivityLifecycleItem.class);
+ mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(), android.app.servertransaction.ActivityLifecycleItem.class);
final boolean readActivityCallbacks = in.readBoolean();
if (readActivityCallbacks) {
mActivityCallbacks = new ArrayList<>();
- in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(),
- ClientTransactionItem.class);
+ in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(), android.app.servertransaction.ClientTransactionItem.class);
}
}
@@ -280,8 +209,7 @@
return false;
}
final ClientTransaction other = (ClientTransaction) o;
- return Objects.equals(mTransactionItems, other.mTransactionItems)
- && Objects.equals(mActivityCallbacks, other.mActivityCallbacks)
+ return Objects.equals(mActivityCallbacks, other.mActivityCallbacks)
&& Objects.equals(mLifecycleStateRequest, other.mLifecycleStateRequest)
&& mClient == other.mClient;
}
@@ -289,7 +217,6 @@
@Override
public int hashCode() {
int result = 17;
- result = 31 * result + Objects.hashCode(mTransactionItems);
result = 31 * result + Objects.hashCode(mActivityCallbacks);
result = 31 * result + Objects.hashCode(mLifecycleStateRequest);
result = 31 * result + Objects.hashCode(mClient);
@@ -300,22 +227,6 @@
void dump(@NonNull String prefix, @NonNull PrintWriter pw,
@NonNull ClientTransactionHandler transactionHandler) {
pw.append(prefix).println("ClientTransaction{");
- if (mTransactionItems != null) {
- pw.append(prefix).print(" transactionItems=[");
- final String itemPrefix = prefix + " ";
- final int size = mTransactionItems.size();
- if (size > 0) {
- pw.println();
- for (int i = 0; i < size; i++) {
- mTransactionItems.get(i).dump(itemPrefix, pw, transactionHandler);
- }
- pw.append(prefix).println(" ]");
- } else {
- pw.println("]");
- }
- pw.append(prefix).println("}");
- return;
- }
pw.append(prefix).print(" callbacks=[");
final String itemPrefix = prefix + " ";
final int size = mActivityCallbacks != null ? mActivityCallbacks.size() : 0;
diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java
index f94e22d..07e5a7d 100644
--- a/core/java/android/app/servertransaction/ClientTransactionItem.java
+++ b/core/java/android/app/servertransaction/ClientTransactionItem.java
@@ -72,13 +72,6 @@
return null;
}
- /**
- * Whether this is a {@link ActivityLifecycleItem}.
- */
- boolean isActivityLifecycleItem() {
- return false;
- }
-
/** Dumps this transaction item. */
void dump(@NonNull String prefix, @NonNull PrintWriter pw,
@NonNull ClientTransactionHandler transactionHandler) {
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 9f5e0dc..066f9fe 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -28,7 +28,6 @@
import static android.app.servertransaction.TransactionExecutorHelper.getShortActivityName;
import static android.app.servertransaction.TransactionExecutorHelper.getStateName;
import static android.app.servertransaction.TransactionExecutorHelper.lastCallbackRequestingState;
-import static android.app.servertransaction.TransactionExecutorHelper.shouldExcludeLastLifecycleState;
import static android.app.servertransaction.TransactionExecutorHelper.tId;
import static android.app.servertransaction.TransactionExecutorHelper.transactionToString;
@@ -62,9 +61,6 @@
private final PendingTransactionActions mPendingActions = new PendingTransactionActions();
private final TransactionExecutorHelper mHelper = new TransactionExecutorHelper();
- /** Keeps track of display ids whose Configuration got updated within a transaction. */
- private final ArraySet<Integer> mConfigUpdatedDisplayIds = new ArraySet<>();
-
/** Initialize an instance with transaction handler, that will execute all requested actions. */
public TransactionExecutor(@NonNull ClientTransactionHandler clientTransactionHandler) {
mTransactionHandler = clientTransactionHandler;
@@ -83,52 +79,15 @@
Slog.d(TAG, transactionToString(transaction, mTransactionHandler));
}
- if (transaction.getTransactionItems() != null) {
- executeTransactionItems(transaction);
- } else {
- // TODO(b/260873529): cleanup after launch.
- executeCallbacks(transaction);
- executeLifecycleState(transaction);
- }
-
- if (!mConfigUpdatedDisplayIds.isEmpty()) {
- // Whether this transaction should trigger DisplayListener#onDisplayChanged.
- final ClientTransactionListenerController controller =
- ClientTransactionListenerController.getInstance();
- final int displayCount = mConfigUpdatedDisplayIds.size();
- for (int i = 0; i < displayCount; i++) {
- final int displayId = mConfigUpdatedDisplayIds.valueAt(i);
- controller.onDisplayChanged(displayId);
- }
- mConfigUpdatedDisplayIds.clear();
- }
+ executeCallbacks(transaction);
+ executeLifecycleState(transaction);
mPendingActions.clear();
if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "End resolving transaction");
}
- /** Cycles through all transaction items and execute them at proper times. */
+ /** Cycle through all states requested by callbacks and execute them at proper times. */
@VisibleForTesting
- public void executeTransactionItems(@NonNull ClientTransaction transaction) {
- final List<ClientTransactionItem> items = transaction.getTransactionItems();
- final int size = items.size();
- for (int i = 0; i < size; i++) {
- final ClientTransactionItem item = items.get(i);
- if (item.isActivityLifecycleItem()) {
- executeLifecycleItem(transaction, (ActivityLifecycleItem) item);
- } else {
- executeNonLifecycleItem(transaction, item,
- shouldExcludeLastLifecycleState(items, i));
- }
- }
- }
-
- /**
- * Cycle through all states requested by callbacks and execute them at proper times.
- * @deprecated use {@link #executeTransactionItems} instead.
- */
- @VisibleForTesting
- @Deprecated
public void executeCallbacks(@NonNull ClientTransaction transaction) {
final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
if (callbacks == null || callbacks.isEmpty()) {
@@ -146,78 +105,83 @@
// Index of the last callback that requests some post-execution state.
final int lastCallbackRequestingState = lastCallbackRequestingState(transaction);
+ // Keep track of display ids whose Configuration got updated with this transaction.
+ ArraySet<Integer> configUpdatedDisplays = null;
+
final int size = callbacks.size();
for (int i = 0; i < size; ++i) {
final ClientTransactionItem item = callbacks.get(i);
+ final IBinder token = item.getActivityToken();
+ ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
- // Skip the very last transition and perform it by explicit state request instead.
+ if (token != null && r == null
+ && mTransactionHandler.getActivitiesToBeDestroyed().containsKey(token)) {
+ // The activity has not been created but has been requested to destroy, so all
+ // transactions for the token are just like being cancelled.
+ Slog.w(TAG, "Skip pre-destroyed transaction item:\n" + item);
+ continue;
+ }
+
+ if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
final int postExecutionState = item.getPostExecutionState();
- final boolean shouldExcludeLastLifecycleState = postExecutionState != UNDEFINED
- && i == lastCallbackRequestingState && finalState == postExecutionState;
- executeNonLifecycleItem(transaction, item, shouldExcludeLastLifecycleState);
- }
- }
- private void executeNonLifecycleItem(@NonNull ClientTransaction transaction,
- @NonNull ClientTransactionItem item, boolean shouldExcludeLastLifecycleState) {
- final IBinder token = item.getActivityToken();
- ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
+ if (item.shouldHaveDefinedPreExecutionState()) {
+ final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
+ item.getPostExecutionState());
+ if (closestPreExecutionState != UNDEFINED) {
+ cycleToPath(r, closestPreExecutionState, transaction);
+ }
+ }
- if (token != null && r == null
- && mTransactionHandler.getActivitiesToBeDestroyed().containsKey(token)) {
- // The activity has not been created but has been requested to destroy, so all
- // transactions for the token are just like being cancelled.
- Slog.w(TAG, "Skip pre-destroyed transaction item:\n" + item);
- return;
- }
+ // Can't read flag from isolated process.
+ final boolean isSyncWindowConfigUpdateFlagEnabled = !Process.isIsolated()
+ && syncWindowConfigUpdateFlag();
+ final Context configUpdatedContext = isSyncWindowConfigUpdateFlagEnabled
+ ? item.getContextToUpdate(mTransactionHandler)
+ : null;
+ final Configuration preExecutedConfig = configUpdatedContext != null
+ ? new Configuration(configUpdatedContext.getResources().getConfiguration())
+ : null;
- if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
- final int postExecutionState = item.getPostExecutionState();
+ item.execute(mTransactionHandler, mPendingActions);
- if (item.shouldHaveDefinedPreExecutionState()) {
- final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
- postExecutionState);
- if (closestPreExecutionState != UNDEFINED) {
- cycleToPath(r, closestPreExecutionState, transaction);
+ if (configUpdatedContext != null) {
+ final Configuration postExecutedConfig = configUpdatedContext.getResources()
+ .getConfiguration();
+ if (!areConfigurationsEqualForDisplay(postExecutedConfig, preExecutedConfig)) {
+ if (configUpdatedDisplays == null) {
+ configUpdatedDisplays = new ArraySet<>();
+ }
+ configUpdatedDisplays.add(configUpdatedContext.getDisplayId());
+ }
+ }
+
+ item.postExecute(mTransactionHandler, mPendingActions);
+ if (r == null) {
+ // Launch activity request will create an activity record.
+ r = mTransactionHandler.getActivityClient(token);
+ }
+
+ if (postExecutionState != UNDEFINED && r != null) {
+ // Skip the very last transition and perform it by explicit state request instead.
+ final boolean shouldExcludeLastTransition =
+ i == lastCallbackRequestingState && finalState == postExecutionState;
+ cycleToPath(r, postExecutionState, shouldExcludeLastTransition, transaction);
}
}
- // Can't read flag from isolated process.
- final boolean isSyncWindowConfigUpdateFlagEnabled = !Process.isIsolated()
- && syncWindowConfigUpdateFlag();
- final Context configUpdatedContext = isSyncWindowConfigUpdateFlagEnabled
- ? item.getContextToUpdate(mTransactionHandler)
- : null;
- final Configuration preExecutedConfig = configUpdatedContext != null
- ? new Configuration(configUpdatedContext.getResources().getConfiguration())
- : null;
-
- item.execute(mTransactionHandler, mPendingActions);
-
- if (configUpdatedContext != null) {
- final Configuration postExecutedConfig = configUpdatedContext.getResources()
- .getConfiguration();
- if (!areConfigurationsEqualForDisplay(postExecutedConfig, preExecutedConfig)) {
- mConfigUpdatedDisplayIds.add(configUpdatedContext.getDisplayId());
+ if (configUpdatedDisplays != null) {
+ final ClientTransactionListenerController controller =
+ ClientTransactionListenerController.getInstance();
+ final int displayCount = configUpdatedDisplays.size();
+ for (int i = 0; i < displayCount; i++) {
+ final int displayId = configUpdatedDisplays.valueAt(i);
+ controller.onDisplayChanged(displayId);
}
}
-
- item.postExecute(mTransactionHandler, mPendingActions);
- if (r == null) {
- // Launch activity request will create an activity record.
- r = mTransactionHandler.getActivityClient(token);
- }
-
- if (postExecutionState != UNDEFINED && r != null) {
- cycleToPath(r, postExecutionState, shouldExcludeLastLifecycleState, transaction);
- }
}
- /**
- * Transition to the final state if requested by the transaction.
- * @deprecated use {@link #executeTransactionItems} instead
- */
- @Deprecated
+ /** Transition to the final state if requested by the transaction. */
private void executeLifecycleState(@NonNull ClientTransaction transaction) {
final ActivityLifecycleItem lifecycleItem = transaction.getLifecycleStateRequest();
if (lifecycleItem == null) {
@@ -225,11 +189,6 @@
return;
}
- executeLifecycleItem(transaction, lifecycleItem);
- }
-
- private void executeLifecycleItem(@NonNull ClientTransaction transaction,
- @NonNull ActivityLifecycleItem lifecycleItem) {
final IBinder token = lifecycleItem.getActivityToken();
final ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
if (DEBUG_RESOLVER) {
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index dfbccb4..7e89a5b 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -236,39 +236,21 @@
* 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.
*/
- static int lastCallbackRequestingState(@NonNull ClientTransaction transaction) {
+ static int lastCallbackRequestingState(ClientTransaction transaction) {
final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
- if (callbacks == null || callbacks.isEmpty()
- || transaction.getLifecycleStateRequest() == null) {
+ if (callbacks == null || callbacks.size() == 0) {
return -1;
}
- return lastCallbackRequestingStateIndex(callbacks, 0, callbacks.size() - 1,
- transaction.getLifecycleStateRequest().getActivityToken());
- }
- /**
- * Returns the index of the last callback between the start index and last index that requests
- * the state for the given activity token in which that activity will be after execution.
- * If there is a group of callbacks in the end that requests the same specific state or doesn't
- * request any - we will find the first one from such group.
- *
- * E.g. ActivityResult requests RESUMED post-execution state, Configuration does not request any
- * specific state. If there is a sequence
- * 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.
- */
- private static int lastCallbackRequestingStateIndex(@NonNull List<ClientTransactionItem> items,
- int startIndex, int lastIndex, @NonNull IBinder activityToken) {
// Go from the back of the list to front, look for the request closes to the beginning that
// requests the state in which activity will end after all callbacks are executed.
int lastRequestedState = UNDEFINED;
int lastRequestingCallback = -1;
- for (int i = lastIndex; i >= startIndex; i--) {
- final ClientTransactionItem item = items.get(i);
- final int postExecutionState = item.getPostExecutionState();
- if (postExecutionState != UNDEFINED && activityToken.equals(item.getActivityToken())) {
- // Found a callback that requests some post-execution state for the given activity.
+ for (int i = callbacks.size() - 1; i >= 0; i--) {
+ final ClientTransactionItem callback = callbacks.get(i);
+ final int postExecutionState = callback.getPostExecutionState();
+ if (postExecutionState != UNDEFINED) {
+ // Found a callback that requests some post-execution state.
if (lastRequestedState == UNDEFINED || lastRequestedState == postExecutionState) {
// It's either a first-from-end callback that requests state or it requests
// the same state as the last one. In both cases, we will use it as the new
@@ -284,53 +266,6 @@
return lastRequestingCallback;
}
- /**
- * For the transaction item at {@code currentIndex}, if it is requesting post execution state,
- * whether or not to exclude the last state. This only returns {@code true} when there is a
- * following explicit {@link ActivityLifecycleItem} requesting the same state for the same
- * activity, so that last state will be covered by the following {@link ActivityLifecycleItem}.
- */
- static boolean shouldExcludeLastLifecycleState(@NonNull List<ClientTransactionItem> items,
- int currentIndex) {
- final ClientTransactionItem item = items.get(currentIndex);
- final IBinder activityToken = item.getActivityToken();
- final int postExecutionState = item.getPostExecutionState();
- if (activityToken == null || postExecutionState == UNDEFINED) {
- // Not a transaction item requesting post execution state.
- return false;
- }
- final int nextLifecycleItemIndex = findNextLifecycleItemIndex(items, currentIndex + 1,
- activityToken);
- if (nextLifecycleItemIndex == -1) {
- // No following ActivityLifecycleItem for this activity token.
- return false;
- }
- final ActivityLifecycleItem lifecycleItem =
- (ActivityLifecycleItem) items.get(nextLifecycleItemIndex);
- if (postExecutionState != lifecycleItem.getTargetState()) {
- // The explicit ActivityLifecycleItem is not requesting the same state.
- return false;
- }
- // Only exclude for the first non-lifecycle item that requests the same specific state.
- return currentIndex == lastCallbackRequestingStateIndex(items, currentIndex,
- nextLifecycleItemIndex - 1, activityToken);
- }
-
- /**
- * Finds the index of the next {@link ActivityLifecycleItem} for the given activity token.
- */
- private static int findNextLifecycleItemIndex(@NonNull List<ClientTransactionItem> items,
- int startIndex, @NonNull IBinder activityToken) {
- final int size = items.size();
- for (int i = startIndex; i < size; i++) {
- final ClientTransactionItem item = items.get(i);
- if (item.isActivityLifecycleItem() && item.getActivityToken().equals(activityToken)) {
- return i;
- }
- }
- return -1;
- }
-
/** Dump transaction to string. */
static String transactionToString(@NonNull ClientTransaction transaction,
@NonNull ClientTransactionHandler transactionHandler) {
diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
index f401a76..4f1c65b 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -13,3 +13,11 @@
description: "Feature flag for the new REPORT_USAGE_STATS permission."
bug: "296056771"
}
+
+flag {
+ name: "use_dedicated_handler_thread"
+ namespace: "backstage_power"
+ description: "Flag to use a dedicated thread for usage event process"
+ is_fixed_read_only: true
+ bug: "299336442"
+}
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 8b09bdf..161fa79 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -206,9 +206,8 @@
/**
* @return whether the association is managed by the companion application it belongs to.
* @see AssociationRequest.Builder#setSelfManaged(boolean)
- * @hide
*/
- @SystemApi
+ @SuppressLint("UnflaggedApi") // promoting from @SystemApi
public boolean isSelfManaged() {
return mSelfManaged;
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index bb9cc0b..7b6bad3 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4292,6 +4292,14 @@
"com.android.intent.action.SHOW_BRIGHTNESS_DIALOG";
/**
+ * Intent Extra: holds boolean that determines whether brightness dialog is full width when
+ * in landscape mode.
+ * @hide
+ */
+ public static final String EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH =
+ "android.intent.extra.BRIGHTNESS_DIALOG_IS_FULL_WIDTH";
+
+ /**
* Activity Action: Shows the contrast setting dialog.
* @hide
*/
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 563ed7d..e9f419e 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -22,6 +22,7 @@
import android.content.Intent;
import android.content.IntentSender;
import android.content.LocusId;
+import android.content.pm.LauncherUserInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IOnAppsChangedListener;
import android.content.pm.LauncherActivityInfoInternal;
@@ -62,6 +63,7 @@
in Bundle opts, in UserHandle user);
PendingIntent getActivityLaunchIntent(String callingPackage, in ComponentName component,
in UserHandle user);
+ LauncherUserInfo getLauncherUserInfo(in UserHandle user);
void showAppDetailsAsUser(in IApplicationThread caller, String callingPackage,
String callingFeatureId, in ComponentName component, in Rect sourceBounds,
in Bundle opts, in UserHandle user);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index dbaa4c9..0cd4358 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -20,6 +20,7 @@
import static android.Manifest.permission.READ_FRAME_BUFFER;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -55,6 +56,7 @@
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.os.Flags;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -776,6 +778,28 @@
}
/**
+ * Returns information related to a user which is useful for displaying UI elements
+ * to distinguish it from other users (eg, badges). Only system launchers should
+ * call this API.
+ *
+ * @param userHandle user handle of the user for which LauncherUserInfo is requested
+ * @return the LauncherUserInfo object related to the user specified.
+ * @hide
+ */
+ @Nullable
+ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+ public final LauncherUserInfo getLauncherUserInfo(@NonNull UserHandle userHandle) {
+ if (DEBUG) {
+ Log.i(TAG, "getLauncherUserInfo " + userHandle);
+ }
+ try {
+ return mService.getLauncherUserInfo(userHandle);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns the activity info for a given intent and user handle, if it resolves. Otherwise it
* returns null.
*
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt b/core/java/android/content/pm/LauncherUserInfo.aidl
similarity index 68%
copy from packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt
copy to core/java/android/content/pm/LauncherUserInfo.aidl
index 9b0c3fa..f875f1e 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt
+++ b/core/java/android/content/pm/LauncherUserInfo.aidl
@@ -11,15 +11,9 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.ui.data.repository
+package android.content.pm;
-import dagger.Binds
-import dagger.Module
-
-@Module
-interface CommonRepositoryModule {
- @Binds fun bindRepository(impl: ConfigurationRepositoryImpl): ConfigurationRepository
-}
+parcelable LauncherUserInfo;
diff --git a/core/java/android/content/pm/LauncherUserInfo.java b/core/java/android/content/pm/LauncherUserInfo.java
new file mode 100644
index 0000000..214c3e4
--- /dev/null
+++ b/core/java/android/content/pm/LauncherUserInfo.java
@@ -0,0 +1,126 @@
+/*
+ * 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.content.pm;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+/**
+ * The LauncherUserInfo object holds information about an Android user that is required to display
+ * the Launcher related UI elements specific to the user (like badges).
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+public final class LauncherUserInfo implements Parcelable {
+
+ private final String mUserType;
+
+ // Serial number for the user, should be same as in the {@link UserInfo} object.
+ private final int mUserSerialNumber;
+
+ /**
+ * Returns type of the user as defined in {@link UserManager}. e.g.,
+ * {@link UserManager.USER_TYPE_PROFILE_MANAGED} or {@link UserManager.USER_TYPE_PROFILE_ClONE}
+ * TODO(b/303812736): Make the return type public and update javadoc here once the linked bug
+ * is resolved.
+ *
+ * @return the userType for the user whose LauncherUserInfo this is
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+ @NonNull
+ public String getUserType() {
+ return mUserType;
+ }
+
+ /**
+ * Returns serial number of user as returned by
+ * {@link UserManager#getSerialNumberForUser(UserHandle)}
+ *
+ * @return the serial number associated with the user
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+ public int getUserSerialNumber() {
+ return mUserSerialNumber;
+ }
+
+ private LauncherUserInfo(@NonNull Parcel in) {
+ mUserType = in.readString16NoHelper();
+ mUserSerialNumber = in.readInt();
+ }
+
+ @Override
+ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString16NoHelper(mUserType);
+ dest.writeInt(mUserSerialNumber);
+ }
+
+ @Override
+ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+ public int describeContents() {
+ return 0;
+ }
+
+ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+ public static final @android.annotation.NonNull Creator<LauncherUserInfo> CREATOR =
+ new Creator<LauncherUserInfo>() {
+ @Override
+ public LauncherUserInfo createFromParcel(Parcel in) {
+ return new LauncherUserInfo(in);
+ }
+
+ @Override
+ public LauncherUserInfo[] newArray(int size) {
+ return new LauncherUserInfo[size];
+ }
+ };
+
+ /**
+ * @hide
+ */
+ public static final class Builder {
+ private final String mUserType;
+
+ private final int mUserSerialNumber;
+
+ public Builder(@NonNull String userType, int userSerialNumber) {
+ this.mUserType = userType;
+ this.mUserSerialNumber = userSerialNumber;
+ }
+
+ /**
+ * Builds the LauncherUserInfo object
+ */
+ @NonNull public LauncherUserInfo build() {
+ return new LauncherUserInfo(this.mUserType, this.mUserSerialNumber);
+ }
+
+ } // End builder
+
+ private LauncherUserInfo(@NonNull String userType, int userSerialNumber) {
+ this.mUserType = userType;
+ this.mUserSerialNumber = userSerialNumber;
+ }
+}
diff --git a/core/java/android/nfc/cardemulation/AidGroup.java b/core/java/android/nfc/cardemulation/AidGroup.java
index 958669e..ae3e333 100644
--- a/core/java/android/nfc/cardemulation/AidGroup.java
+++ b/core/java/android/nfc/cardemulation/AidGroup.java
@@ -34,6 +34,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
+import java.util.regex.Pattern;
/**********************************************************************
* This file is not a part of the NFC mainline module *
@@ -79,7 +80,7 @@
throw new IllegalArgumentException("Too many AIDs in AID group.");
}
for (String aid : aids) {
- if (!CardEmulation.isValidAid(aid)) {
+ if (!isValidAid(aid)) {
throw new IllegalArgumentException("AID " + aid + " is not a valid AID.");
}
}
@@ -264,4 +265,34 @@
return CardEmulation.CATEGORY_PAYMENT.equals(category) ||
CardEmulation.CATEGORY_OTHER.equals(category);
}
+
+ private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
+ /**
+ * Copied over from {@link CardEmulation#isValidAid(String)}
+ * @hide
+ */
+ private static boolean isValidAid(String aid) {
+ if (aid == null)
+ return false;
+
+ // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
+ if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ // If not a prefix/subset AID, the total length must be even (even # of AID chars)
+ if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ // Verify hex characters
+ if (!AID_PATTERN.matcher(aid).matches()) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index 18ec914..665b753 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -52,6 +52,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.regex.Pattern;
/**
* Class holding APDU service info.
@@ -307,7 +308,7 @@
com.android.internal.R.styleable.AidFilter);
String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
toUpperCase();
- if (CardEmulation.isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+ if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
currentGroup.getAids().add(aid);
} else {
Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
@@ -321,7 +322,7 @@
toUpperCase();
// Add wildcard char to indicate prefix
aid = aid.concat("*");
- if (CardEmulation.isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+ if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
currentGroup.getAids().add(aid);
} else {
Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
@@ -335,7 +336,7 @@
toUpperCase();
// Add wildcard char to indicate suffix
aid = aid.concat("#");
- if (CardEmulation.isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+ if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
currentGroup.getAids().add(aid);
} else {
Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
@@ -806,7 +807,7 @@
*/
@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
public void dumpDebug(@NonNull ProtoOutputStream proto) {
- Utils.dumpDebugComponentName(getComponent(), proto, ApduServiceInfoProto.COMPONENT_NAME);
+ getComponent().dumpDebug(proto, ApduServiceInfoProto.COMPONENT_NAME);
proto.write(ApduServiceInfoProto.DESCRIPTION, getDescription());
proto.write(ApduServiceInfoProto.ON_HOST, mOnHost);
if (!mOnHost) {
@@ -825,4 +826,34 @@
}
proto.write(ApduServiceInfoProto.SETTINGS_ACTIVITY_NAME, mSettingsActivityName);
}
+
+ private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
+ /**
+ * Copied over from {@link CardEmulation#isValidAid(String)}
+ * @hide
+ */
+ private static boolean isValidAid(String aid) {
+ if (aid == null)
+ return false;
+
+ // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
+ if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ // If not a prefix/subset AID, the total length must be even (even # of AID chars)
+ if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ // Verify hex characters
+ if (!AID_PATTERN.matcher(aid).matches()) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
index ec919e4..33bc169 100644
--- a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
@@ -173,7 +173,7 @@
com.android.internal.R.styleable.SystemCodeFilter);
systemCode = a.getString(
com.android.internal.R.styleable.SystemCodeFilter_name).toUpperCase();
- if (!NfcFCardEmulation.isValidSystemCode(systemCode) &&
+ if (!isValidSystemCode(systemCode) &&
!systemCode.equalsIgnoreCase("NULL")) {
Log.e(TAG, "Invalid System Code: " + systemCode);
systemCode = null;
@@ -187,7 +187,7 @@
com.android.internal.R.styleable.Nfcid2Filter_name).toUpperCase();
if (!nfcid2.equalsIgnoreCase("RANDOM") &&
!nfcid2.equalsIgnoreCase("NULL") &&
- !NfcFCardEmulation.isValidNfcid2(nfcid2)) {
+ !isValidNfcid2(nfcid2)) {
Log.e(TAG, "Invalid NFCID2: " + nfcid2);
nfcid2 = null;
}
@@ -436,10 +436,62 @@
*/
@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
public void dumpDebug(@NonNull ProtoOutputStream proto) {
- Utils.dumpDebugComponentName(getComponent(), proto, NfcFServiceInfoProto.COMPONENT_NAME);
+ getComponent().dumpDebug(proto, NfcFServiceInfoProto.COMPONENT_NAME);
proto.write(NfcFServiceInfoProto.DESCRIPTION, getDescription());
proto.write(NfcFServiceInfoProto.SYSTEM_CODE, getSystemCode());
proto.write(NfcFServiceInfoProto.NFCID2, getNfcid2());
proto.write(NfcFServiceInfoProto.T3T_PMM, getT3tPmm());
}
+
+ /**
+ * Copied over from {@link NfcFCardEmulation#isValidSystemCode(String)}
+ * @hide
+ */
+ private static boolean isValidSystemCode(String systemCode) {
+ if (systemCode == null) {
+ return false;
+ }
+ if (systemCode.length() != 4) {
+ Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+ return false;
+ }
+ // check if the value is between "4000" and "4FFF" (excluding "4*FF")
+ if (!systemCode.startsWith("4") || systemCode.toUpperCase().endsWith("FF")) {
+ Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+ return false;
+ }
+ try {
+ Integer.parseInt(systemCode, 16);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Copied over from {@link NfcFCardEmulation#isValidNfcid2(String)}
+ * @hide
+ */
+ private static boolean isValidNfcid2(String nfcid2) {
+ if (nfcid2 == null) {
+ return false;
+ }
+ if (nfcid2.length() != 16) {
+ Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+ return false;
+ }
+ // check if the the value starts with "02FE"
+ if (!nfcid2.toUpperCase().startsWith("02FE")) {
+ Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+ return false;
+ }
+ try {
+ Long.parseLong(nfcid2, 16);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+ return false;
+ }
+ return true;
+ }
}
diff --git a/core/java/android/os/OomKillRecord.java b/core/java/android/os/OomKillRecord.java
index 151a65f..ca1d49a 100644
--- a/core/java/android/os/OomKillRecord.java
+++ b/core/java/android/os/OomKillRecord.java
@@ -15,10 +15,15 @@
*/
package android.os;
+import com.android.internal.util.FrameworkStatsLog;
/**
+ * Activity manager communication with kernel out-of-memory (OOM) data handling
+ * and statsd atom logging.
+ *
* Expected data to get back from the OOM event's file.
- * Note that this should be equivalent to the struct <b>OomKill</b> inside
+ * Note that this class fields' should be equivalent to the struct
+ * <b>OomKill</b> inside
* <pre>
* system/memory/libmeminfo/libmemevents/include/memevents.h
* </pre>
@@ -41,6 +46,18 @@
this.mOomScoreAdj = oomScoreAdj;
}
+ /**
+ * Logs the event when the kernel OOM killer claims a victims to reduce
+ * memory pressure.
+ * KernelOomKillOccurred = 754
+ */
+ public void logKillOccurred() {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.KERNEL_OOM_KILL_OCCURRED,
+ mUid, mPid, mOomScoreAdj, mTimeStampInMillis,
+ mProcessName);
+ }
+
public long getTimestampMilli() {
return mTimeStampInMillis;
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 4c8ef97..9034ff1 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3330,7 +3330,10 @@
*
* @return whether the context user is running in the foreground.
*/
- @UserHandleAware
+ @UserHandleAware(
+ requiresAnyOfPermissionsIfNotCaller = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
public boolean isUserForeground() {
try {
return mService.isUserForeground(mUserId);
@@ -3404,11 +3407,10 @@
* @hide
*/
@SystemApi
- @UserHandleAware
- @RequiresPermission(anyOf = {
- "android.permission.INTERACT_ACROSS_USERS",
- "android.permission.MANAGE_USERS"
- })
+ @UserHandleAware(
+ requiresAnyOfPermissionsIfNotCaller = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
public boolean isUserVisible() {
try {
return mService.isUserVisible(mUserId);
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index a391571..27ad45d 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -3196,6 +3196,16 @@
public static final String ALWAYS_ON = "always_on";
/**
+ * The infrastructure bitmask which the APN can be used on. For example, some APNs can only
+ * be used when the device is on cellular, on satellite, or both. The default value is
+ * 1 (INFRASTRUCTURE_CELLULAR).
+ *
+ * <P>Type: INTEGER</P>
+ * @hide
+ */
+ public static final String INFRASTRUCTURE_BITMASK = "infrastructure_bitmask";
+
+ /**
* MVNO type:
* {@code SPN (Service Provider Name), IMSI, GID (Group Identifier Level 1)}.
* <P>Type: TEXT</P>
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index 46fa501..e17a955 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -27,14 +27,6 @@
* @hide
*/
public class ClientFlags {
-
- /**
- * @see Flags#deprecateFontsXml()
- */
- public static boolean deprecateFontsXml() {
- return TextFlags.isFeatureEnabled(Flags.FLAG_DEPRECATE_FONTS_XML);
- }
-
/**
* @see Flags#noBreakNoHyphenationSpan()
*/
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 9edf298..b8b30c23 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -55,7 +55,6 @@
* List of text flags to be transferred to the application process.
*/
public static final String[] TEXT_ACONFIGS_FLAGS = {
- Flags.FLAG_DEPRECATE_FONTS_XML,
Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN,
Flags.FLAG_PHRASE_STRICT_FALLBACK,
Flags.FLAG_USE_BOUNDS_FOR_WIDTH,
@@ -67,7 +66,6 @@
* The order must be the same to the TEXT_ACONFIG_FLAGS.
*/
public static final boolean[] TEXT_ACONFIG_DEFAULT_VALUE = {
- Flags.deprecateFontsXml(),
Flags.noBreakNoHyphenationSpan(),
Flags.phraseStrictFallback(),
Flags.useBoundsForWidth(),
diff --git a/core/java/android/text/flags/custom_locale_fallback.aconfig b/core/java/android/text/flags/custom_locale_fallback.aconfig
deleted file mode 100644
index 52fe883..0000000
--- a/core/java/android/text/flags/custom_locale_fallback.aconfig
+++ /dev/null
@@ -1,9 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "custom_locale_fallback"
- namespace: "text"
- description: "A feature flag that adds custom locale fallback to the vendor customization XML. This enables vendors to add their locale specific fonts, e.g. Japanese font."
- is_fixed_read_only: true
- bug: "278768958"
-}
diff --git a/core/java/android/text/flags/deprecate_fonts_xml.aconfig b/core/java/android/text/flags/deprecate_fonts_xml.aconfig
deleted file mode 100644
index 5362138..0000000
--- a/core/java/android/text/flags/deprecate_fonts_xml.aconfig
+++ /dev/null
@@ -1,10 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "deprecate_fonts_xml"
- namespace: "text"
- description: "Feature flag for deprecating fonts.xml. By setting true for this feature flag, the new font configuration XML, /system/etc/font_fallback.xml is used. The new XML has a new syntax and flexibility of variable font declarations, but it is not compatible with the apps that reads fonts.xml. So, fonts.xml is maintained as a subset of the font_fallback.xml"
- # Make read only, as it could be used before the Settings provider is initialized.
- is_fixed_read_only: true
- bug: "281769620"
-}
diff --git a/core/java/android/text/flags/fix_double_underline.aconfig b/core/java/android/text/flags/fix_double_underline.aconfig
deleted file mode 100644
index b0aa72a..0000000
--- a/core/java/android/text/flags/fix_double_underline.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "fix_double_underline"
- namespace: "text"
- description: "Feature flag for fixing double underline because of the multiple font used in the single line."
- bug: "297336724"
-}
diff --git a/core/java/android/text/flags/fix_line_height_for_locale.aconfig b/core/java/android/text/flags/fix_line_height_for_locale.aconfig
deleted file mode 100644
index 8696bfa..0000000
--- a/core/java/android/text/flags/fix_line_height_for_locale.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "fix_line_height_for_locale"
- namespace: "text"
- description: "Feature flag that preserve the line height of the TextView and EditText even if the the locale is different from Latin"
- bug: "303326708"
-}
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
new file mode 100644
index 0000000..201f680
--- /dev/null
+++ b/core/java/android/text/flags/flags.aconfig
@@ -0,0 +1,63 @@
+package: "com.android.text.flags"
+
+flag {
+ name: "vendor_custom_locale_fallback"
+ namespace: "text"
+ description: "A feature flag that adds custom locale fallback to the vendor customization XML. This enables vendors to add their locale specific fonts, e.g. Japanese font."
+ is_fixed_read_only: true
+ bug: "278768958"
+}
+
+flag {
+ name: "new_fonts_fallback_xml"
+ namespace: "text"
+ description: "Feature flag for deprecating fonts.xml. By setting true for this feature flag, the new font configuration XML, /system/etc/font_fallback.xml is used. The new XML has a new syntax and flexibility of variable font declarations, but it is not compatible with the apps that reads fonts.xml. So, fonts.xml is maintained as a subset of the font_fallback.xml"
+ # Make read only, as it could be used before the Settings provider is initialized.
+ is_fixed_read_only: true
+ bug: "281769620"
+}
+
+flag {
+ name: "fix_double_underline"
+ namespace: "text"
+ description: "Feature flag for fixing double underline because of the multiple font used in the single line."
+ bug: "297336724"
+}
+
+flag {
+ name: "fix_line_height_for_locale"
+ namespace: "text"
+ description: "Feature flag that preserve the line height of the TextView and EditText even if the the locale is different from Latin"
+ bug: "303326708"
+}
+
+flag {
+ name: "no_break_no_hyphenation_span"
+ namespace: "text"
+ description: "A feature flag that adding new spans that prevents line breaking and hyphenation."
+ bug: "283193586"
+}
+
+flag {
+ name: "use_optimized_boottime_font_loading"
+ namespace: "text"
+ description: "Feature flag ensuring that font is loaded once and asynchronously."
+ # Make read only, as font loading is in the critical boot path which happens before the read-write
+ # flags propagate to the device.
+ is_fixed_read_only: true
+ bug: "304406888"
+}
+
+flag {
+ name: "phrase_strict_fallback"
+ namespace: "text"
+ description: "Feature flag for automatic fallback from phrase based line break to strict line break."
+ bug: "281970875"
+}
+
+flag {
+ name: "use_bounds_for_width"
+ namespace: "text"
+ description: "Feature flag for preventing horizontal clipping."
+ bug: "63938206"
+}
diff --git a/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig b/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig
deleted file mode 100644
index 60f1e88..0000000
--- a/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "no_break_no_hyphenation_span"
- namespace: "text"
- description: "A feature flag that adding new spans that prevents line breaking and hyphenation."
- bug: "283193586"
-}
diff --git a/core/java/android/text/flags/optimized_font_loading.aconfig b/core/java/android/text/flags/optimized_font_loading.aconfig
deleted file mode 100644
index 0e4a69f..0000000
--- a/core/java/android/text/flags/optimized_font_loading.aconfig
+++ /dev/null
@@ -1,11 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "use_optimized_boottime_font_loading"
- namespace: "text"
- description: "Feature flag ensuring that font is loaded once and asynchronously."
- # Make read only, as font loading is in the critical boot path which happens before the read-write
- # flags propagate to the device.
- is_fixed_read_only: true
- bug: "304406888"
-}
diff --git a/core/java/android/text/flags/phrase_strict_fallback.aconfig b/core/java/android/text/flags/phrase_strict_fallback.aconfig
deleted file mode 100644
index c67a21b..0000000
--- a/core/java/android/text/flags/phrase_strict_fallback.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "phrase_strict_fallback"
- namespace: "text"
- description: "Feature flag for automatic fallback from phrase based line break to strict line break."
- bug: "281970875"
-}
diff --git a/core/java/android/text/flags/use_bounds_for_width.aconfig b/core/java/android/text/flags/use_bounds_for_width.aconfig
deleted file mode 100644
index d89d5f4..0000000
--- a/core/java/android/text/flags/use_bounds_for_width.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "use_bounds_for_width"
- namespace: "text"
- description: "Feature flag for preventing horizontal clipping."
- bug: "63938206"
-}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 2f3d73a..42a0c9a 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -70,6 +70,7 @@
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
@@ -796,7 +797,7 @@
if (nativeObject != 0) {
// Only add valid surface controls to the registry. This is called at the end of this
// method since its information is dumped if the process threshold is reached.
- addToRegistry();
+ SurfaceControlRegistry.getProcessInstance().add(this);
}
}
@@ -1501,7 +1502,7 @@
if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
}
- removeFromRegistry();
+ SurfaceControlRegistry.getProcessInstance().remove(this);
} finally {
super.finalize();
}
@@ -1519,6 +1520,10 @@
*/
public void release() {
if (mNativeObject != 0) {
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "release", null, this, null);
+ }
mFreeNativeResources.run();
mNativeObject = 0;
mNativeHandle = 0;
@@ -1532,7 +1537,7 @@
mChoreographer = null;
}
}
- removeFromRegistry();
+ SurfaceControlRegistry.getProcessInstance().remove(this);
}
}
@@ -2765,8 +2770,10 @@
private Transaction(long nativeObject) {
mNativeObject = nativeObject;
- mFreeNativeResources =
- sRegistry.registerNativeAllocation(this, mNativeObject);
+ mFreeNativeResources = sRegistry.registerNativeAllocation(this, mNativeObject);
+ if (!SurfaceControlRegistry.sCallStackDebuggingInitialized) {
+ SurfaceControlRegistry.initializeCallStackDebugging();
+ }
}
private Transaction(Parcel in) {
@@ -2845,6 +2852,11 @@
applyResizedSurfaces();
notifyReparentedSurfaces();
nativeApplyTransaction(mNativeObject, sync, oneWay);
+
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "apply", this, null, null);
+ }
}
/**
@@ -2920,6 +2932,10 @@
@UnsupportedAppUsage
public Transaction show(SurfaceControl sc) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "show", this, sc, null);
+ }
nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN);
return this;
}
@@ -2934,6 +2950,10 @@
@UnsupportedAppUsage
public Transaction hide(SurfaceControl sc) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "hide", this, sc, null);
+ }
nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
return this;
}
@@ -2950,6 +2970,10 @@
@NonNull
public Transaction setPosition(@NonNull SurfaceControl sc, float x, float y) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setPosition", this, sc, "x=" + x + " y=" + y);
+ }
nativeSetPosition(mNativeObject, sc.mNativeObject, x, y);
return this;
}
@@ -2968,6 +2992,10 @@
checkPreconditions(sc);
Preconditions.checkArgument(scaleX >= 0, "Negative value passed in for scaleX");
Preconditions.checkArgument(scaleY >= 0, "Negative value passed in for scaleY");
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setScale", this, sc, "sx=" + scaleX + " sy=" + scaleY);
+ }
nativeSetScale(mNativeObject, sc.mNativeObject, scaleX, scaleY);
return this;
}
@@ -2985,6 +3013,10 @@
public Transaction setBufferSize(@NonNull SurfaceControl sc,
@IntRange(from = 0) int w, @IntRange(from = 0) int h) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setBufferSize", this, sc, "w=" + w + " h=" + h);
+ }
mResizedSurfaces.put(sc, new Point(w, h));
return this;
}
@@ -3005,6 +3037,10 @@
public Transaction setFixedTransformHint(@NonNull SurfaceControl sc,
@Surface.Rotation int transformHint) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setFixedTransformHint", this, sc, "hint=" + transformHint);
+ }
nativeSetFixedTransformHint(mNativeObject, sc.mNativeObject, transformHint);
return this;
}
@@ -3018,6 +3054,10 @@
@NonNull
public Transaction unsetFixedTransformHint(@NonNull SurfaceControl sc) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "unsetFixedTransformHint", this, sc, null);
+ }
nativeSetFixedTransformHint(mNativeObject, sc.mNativeObject, -1/* INVALID_ROTATION */);
return this;
}
@@ -3035,6 +3075,10 @@
public Transaction setLayer(@NonNull SurfaceControl sc,
@IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int z) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setLayer", this, sc, "z=" + z);
+ }
nativeSetLayer(mNativeObject, sc.mNativeObject, z);
return this;
}
@@ -3044,6 +3088,10 @@
*/
public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setRelativeLayer", this, sc, "relTo=" + relativeTo + " z=" + z);
+ }
nativeSetRelativeLayer(mNativeObject, sc.mNativeObject, relativeTo.mNativeObject, z);
return this;
}
@@ -3053,6 +3101,10 @@
*/
public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "unsetFixedTransformHint", this, sc, "region=" + transparentRegion);
+ }
nativeSetTransparentRegionHint(mNativeObject,
sc.mNativeObject, transparentRegion);
return this;
@@ -3069,6 +3121,10 @@
public Transaction setAlpha(@NonNull SurfaceControl sc,
@FloatRange(from = 0.0, to = 1.0) float alpha) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setAlpha", this, sc, "alpha=" + alpha);
+ }
nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha);
return this;
}
@@ -3124,6 +3180,11 @@
public Transaction setMatrix(SurfaceControl sc,
float dsdx, float dtdx, float dtdy, float dsdy) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setMatrix", this, sc,
+ "dsdx=" + dsdx + " dtdx=" + dtdx + " dtdy=" + dtdy + " dsdy=" + dsdy);
+ }
nativeSetMatrix(mNativeObject, sc.mNativeObject,
dsdx, dtdx, dtdy, dsdy);
return this;
@@ -3189,6 +3250,10 @@
@UnsupportedAppUsage
public Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setWindowCrop", this, sc, "crop=" + crop);
+ }
if (crop != null) {
nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
crop.left, crop.top, crop.right, crop.bottom);
@@ -3211,6 +3276,10 @@
*/
public @NonNull Transaction setCrop(@NonNull SurfaceControl sc, @Nullable Rect crop) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setCrop", this, sc, "crop=" + crop);
+ }
if (crop != null) {
Preconditions.checkArgument(crop.isValid(), "Crop isn't valid.");
nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
@@ -3233,6 +3302,10 @@
*/
public Transaction setWindowCrop(SurfaceControl sc, int width, int height) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setCornerRadius", this, sc, "w=" + width + " h=" + height);
+ }
nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, width, height);
return this;
}
@@ -3247,6 +3320,10 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public Transaction setCornerRadius(SurfaceControl sc, float cornerRadius) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setCornerRadius", this, sc, "cornerRadius=" + cornerRadius);
+ }
nativeSetCornerRadius(mNativeObject, sc.mNativeObject, cornerRadius);
return this;
@@ -3262,6 +3339,10 @@
*/
public Transaction setBackgroundBlurRadius(SurfaceControl sc, int radius) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setBackgroundBlurRadius", this, sc, "radius=" + radius);
+ }
nativeSetBackgroundBlurRadius(mNativeObject, sc.mNativeObject, radius);
return this;
}
@@ -3318,6 +3399,10 @@
public Transaction reparent(@NonNull SurfaceControl sc,
@Nullable SurfaceControl newParent) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "reparent", this, sc, "newParent=" + newParent);
+ }
long otherObject = 0;
if (newParent != null) {
newParent.checkNotReleased();
@@ -3337,6 +3422,11 @@
@UnsupportedAppUsage
public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "reparent", this, sc,
+ "r=" + color[0] + " g=" + color[1] + " b=" + color[2]);
+ }
nativeSetColor(mNativeObject, sc.mNativeObject, color);
return this;
}
@@ -3347,6 +3437,10 @@
*/
public Transaction unsetColor(SurfaceControl sc) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "unsetColor", this, sc, null);
+ }
nativeSetColor(mNativeObject, sc.mNativeObject, INVALID_COLOR);
return this;
}
@@ -3358,6 +3452,10 @@
*/
public Transaction setSecure(SurfaceControl sc, boolean isSecure) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setSecure", this, sc, "secure=" + isSecure);
+ }
if (isSecure) {
nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE);
} else {
@@ -3411,6 +3509,10 @@
@NonNull
public Transaction setOpaque(@NonNull SurfaceControl sc, boolean isOpaque) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setOpaque", this, sc, "opaque=" + isOpaque);
+ }
if (isOpaque) {
nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
} else {
@@ -3580,6 +3682,10 @@
*/
public Transaction setShadowRadius(SurfaceControl sc, float shadowRadius) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setShadowRadius", this, sc, "radius=" + shadowRadius);
+ }
nativeSetShadowRadius(mNativeObject, sc.mNativeObject, shadowRadius);
return this;
}
@@ -4463,26 +4569,6 @@
return -1;
}
- /**
- * Adds this surface control to the registry for this process if it is created.
- */
- private void addToRegistry() {
- final SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance();
- if (registry != null) {
- registry.add(this);
- }
- }
-
- /**
- * Removes this surface control from the registry for this process.
- */
- private void removeFromRegistry() {
- final SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance();
- if (registry != null) {
- registry.remove(this);
- }
- }
-
// Called by native
private static void invokeReleaseCallback(Consumer<SyncFence> callback, long nativeFencePtr) {
SyncFence fence = new SyncFence(nativeFencePtr);
diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java
index 0f35048..52be8f6 100644
--- a/core/java/android/view/SurfaceControlRegistry.java
+++ b/core/java/android/view/SurfaceControlRegistry.java
@@ -23,7 +23,9 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.content.Context;
+import android.os.Build;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -104,6 +106,9 @@
// Number of surface controls to dump when the max threshold is exceeded
private static final int DUMP_LIMIT = 256;
+ // An instance of a registry that is a no-op
+ private static final SurfaceControlRegistry NO_OP_REGISTRY = new NoOpRegistry();
+
// Static lock, must be held for all registry operations
private static final Object sLock = new Object();
@@ -113,6 +118,22 @@
// The registry for a given process
private static volatile SurfaceControlRegistry sProcessRegistry;
+ // Whether call stack debugging has been initialized. This is evaluated only once per process
+ // instance when the first SurfaceControl.Transaction object is created
+ static boolean sCallStackDebuggingInitialized;
+
+ // Whether call stack debugging is currently enabled, ie. whether there is a valid match string
+ // for either a specific surface control name or surface control transaction method
+ static boolean sCallStackDebuggingEnabled;
+
+ // The name of the surface control to log stack traces for. Always non-null if
+ // sCallStackDebuggingEnabled is true. Can be combined with the match call.
+ private static String sCallStackDebuggingMatchName;
+
+ // The surface control transaction method name to log stack traces for. Always non-null if
+ // sCallStackDebuggingEnabled is true. Can be combined with the match name.
+ private static String sCallStackDebuggingMatchCall;
+
// Mapping of the active SurfaceControls to the elapsed time when they were registered
@GuardedBy("sLock")
private final WeakHashMap<SurfaceControl, Long> mSurfaceControls;
@@ -160,6 +181,12 @@
}
}
+ @VisibleForTesting
+ public void setCallStackDebuggingParams(String matchName, String matchCall) {
+ sCallStackDebuggingMatchName = matchName.toLowerCase();
+ sCallStackDebuggingMatchCall = matchCall.toLowerCase();
+ }
+
/**
* Creates and initializes the registry for all SurfaceControls in this process. The caller must
* hold the READ_FRAME_BUFFER permission.
@@ -196,11 +223,9 @@
* createProcessInstance(Context) was previously called from a valid caller.
* @hide
*/
- @Nullable
- @VisibleForTesting
public static SurfaceControlRegistry getProcessInstance() {
synchronized (sLock) {
- return sProcessRegistry;
+ return sProcessRegistry != null ? sProcessRegistry : NO_OP_REGISTRY;
}
}
@@ -248,6 +273,91 @@
}
/**
+ * Initializes global call stack debugging if this is a debug build and a filter is specified.
+ * This is a no-op if
+ *
+ * Usage:
+ * adb shell setprop persist.wm.debug.sc.tx.log_match_call <call or \"\" to unset>
+ * adb shell setprop persist.wm.debug.sc.tx.log_match_name <name or \"\" to unset>
+ * adb reboot
+ */
+ final static void initializeCallStackDebugging() {
+ if (sCallStackDebuggingInitialized || !Build.IS_DEBUGGABLE) {
+ // Return early if already initialized or this is not a debug build
+ return;
+ }
+
+ sCallStackDebuggingInitialized = true;
+ sCallStackDebuggingMatchCall =
+ SystemProperties.get("persist.wm.debug.sc.tx.log_match_call", null)
+ .toLowerCase();
+ sCallStackDebuggingMatchName =
+ SystemProperties.get("persist.wm.debug.sc.tx.log_match_name", null)
+ .toLowerCase();
+ // Only enable stack debugging if any of the match filters are set
+ sCallStackDebuggingEnabled = (!sCallStackDebuggingMatchCall.isEmpty()
+ || !sCallStackDebuggingMatchName.isEmpty());
+ if (sCallStackDebuggingEnabled) {
+ Log.d(TAG, "Enabling transaction call stack debugging:"
+ + " matchCall=" + sCallStackDebuggingMatchCall
+ + " matchName=" + sCallStackDebuggingMatchName);
+ }
+ }
+
+ /**
+ * Dumps the callstack if it matches the global debug properties. Caller should first verify
+ * {@link #sCallStackDebuggingEnabled} is true.
+ *
+ * @param call the name of the call
+ * @param tx (optional) the transaction associated with this call
+ * @param sc the affected surface
+ * @param details additional details to print with the stack track
+ */
+ final void checkCallStackDebugging(@NonNull String call,
+ @Nullable SurfaceControl.Transaction tx, @Nullable SurfaceControl sc,
+ @Nullable String details) {
+ if (!sCallStackDebuggingEnabled) {
+ return;
+ }
+ if (!matchesForCallStackDebugging(sc != null ? sc.getName() : null, call)) {
+ return;
+ }
+ final String txMsg = tx != null ? "tx=" + tx.getId() + " ": "";
+ final String scMsg = sc != null ? " sc=" + sc.getName() + "": "";
+ final String msg = details != null
+ ? call + " (" + txMsg + scMsg + ") " + details
+ : call + " (" + txMsg + scMsg + ")";
+ Log.e(TAG, msg, new Throwable());
+ }
+
+ /**
+ * Tests whether the given surface control name/method call matches the filters set for the
+ * call stack debugging.
+ */
+ @VisibleForTesting
+ public final boolean matchesForCallStackDebugging(@Nullable String name, @NonNull String call) {
+ final boolean matchCall = !sCallStackDebuggingMatchCall.isEmpty();
+ if (matchCall && !call.toLowerCase().contains(sCallStackDebuggingMatchCall)) {
+ // Skip if target call doesn't match requested caller
+ return false;
+ }
+ final boolean matchName = !sCallStackDebuggingMatchName.isEmpty();
+ if (matchName && (name == null
+ || !name.toLowerCase().contains(sCallStackDebuggingMatchName))) {
+ // Skip if target surface doesn't match requested surface
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns whether call stack debugging is enabled for this process.
+ */
+ final static boolean isCallStackDebuggingEnabled() {
+ return sCallStackDebuggingEnabled;
+ }
+
+ /**
* Forces the gc and finalizers to run, used prior to dumping to ensure we only dump strongly
* referenced surface controls.
*/
@@ -272,7 +382,27 @@
synchronized (sLock) {
if (sProcessRegistry != null) {
sDefaultReporter.onMaxLayersExceeded(sProcessRegistry.mSurfaceControls, limit, pw);
+ pw.println("sCallStackDebuggingInitialized=" + sCallStackDebuggingInitialized);
+ pw.println("sCallStackDebuggingEnabled=" + sCallStackDebuggingEnabled);
+ pw.println("sCallStackDebuggingMatchName=" + sCallStackDebuggingMatchName);
+ pw.println("sCallStackDebuggingMatchCall=" + sCallStackDebuggingMatchCall);
}
}
}
+
+ /**
+ * A no-op implementation of the registry.
+ */
+ private static class NoOpRegistry extends SurfaceControlRegistry {
+
+ @Override
+ public void setReportingThresholds(int maxLayersReportingThreshold,
+ int resetReportingThreshold, Reporter reporter) {}
+
+ @Override
+ void add(SurfaceControl sc) {}
+
+ @Override
+ void remove(SurfaceControl sc) {}
+ }
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 49b16c7..e9d0e4c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -19,8 +19,6 @@
import static android.content.res.Resources.ID_NULL;
import static android.os.Trace.TRACE_TAG_APP;
import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP;
-import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
-import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
@@ -29,7 +27,6 @@
import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH;
import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE;
import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
-import static android.view.flags.Flags.toolkitSetFrameRate;
import static android.view.flags.Flags.viewVelocityApi;
import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS;
@@ -117,7 +114,6 @@
import android.text.InputType;
import android.text.TextUtils;
import android.util.AttributeSet;
-import android.util.DisplayMetrics;
import android.util.FloatProperty;
import android.util.LayoutDirection;
import android.util.Log;
@@ -5513,11 +5509,6 @@
private ViewTranslationResponse mViewTranslationResponse;
/**
- * A threshold value to determine the frame rate category of the View based on the size.
- */
- private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
-
- /**
* Simple constructor to use when creating a view from code.
*
* @param context The Context the view is running in, through which it can
@@ -20192,9 +20183,6 @@
return;
}
- // For VRR to vote the preferred frame rate
- votePreferredFrameRate();
-
// Reset content capture caches
mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
mContentCaptureSessionCached = false;
@@ -20297,8 +20285,6 @@
*/
protected void damageInParent() {
if (mParent != null && mAttachInfo != null) {
- // For VRR to vote the preferred frame rate
- votePreferredFrameRate();
mParent.onDescendantInvalidated(this, this);
}
}
@@ -32995,40 +32981,6 @@
return null;
}
- private float getSizePercentage() {
- if (mResources == null || getAlpha() == 0 || getVisibility() != VISIBLE) {
- return 0;
- }
-
- DisplayMetrics displayMetrics = mResources.getDisplayMetrics();
- int screenSize = displayMetrics.widthPixels
- * displayMetrics.heightPixels;
- int viewSize = getWidth() * getHeight();
-
- if (screenSize == 0 || viewSize == 0) {
- return 0f;
- }
- return (float) viewSize / screenSize;
- }
-
- private int calculateFrameRateCategory() {
- float sizePercentage = getSizePercentage();
-
- if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
- return FRAME_RATE_CATEGORY_LOW;
- } else {
- return FRAME_RATE_CATEGORY_NORMAL;
- }
- }
-
- private void votePreferredFrameRate() {
- // use toolkitSetFrameRate flag to gate the change
- ViewRootImpl viewRootImpl = getViewRootImpl();
- if (toolkitSetFrameRate() && viewRootImpl != null && getSizePercentage() > 0) {
- viewRootImpl.votePreferredFrameRateCategory(calculateFrameRateCategory());
- }
- }
-
/**
* Set the current velocity of the View, we only track positive value.
* We will use the velocity information to adjust the frame rate when applicable.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8df022a..dfada58 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -24,8 +24,6 @@
import static android.view.Display.INVALID_DISPLAY;
import static android.view.InputDevice.SOURCE_CLASS_NONE;
import static android.view.InsetsSource.ID_IME;
-import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
-import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -76,10 +74,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
@@ -92,7 +87,6 @@
import static android.view.accessibility.Flags.forceInvertColor;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
-import static android.view.flags.Flags.toolkitSetFrameRate;
import android.Manifest;
import android.accessibilityservice.AccessibilityService;
@@ -742,7 +736,6 @@
private SurfaceControl mBoundsLayer;
private final SurfaceSession mSurfaceSession = new SurfaceSession();
private final Transaction mTransaction = new Transaction();
- private final Transaction mFrameRateTransaction = new Transaction();
@UnsupportedAppUsage
boolean mAdded;
@@ -966,34 +959,6 @@
private AccessibilityWindowAttributes mAccessibilityWindowAttributes;
- /*
- * for Variable Refresh Rate project
- */
-
- // The preferred frame rate category of the view that
- // could be updated on a frame-by-frame basis.
- private int mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
- // The preferred frame rate category of the last frame that
- // could be used to lower frame rate after touch boost
- private int mLastPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
- // The preferred frame rate of the view that is mainly used for
- // touch boosting, view velocity handling, and TextureView.
- private float mPreferredFrameRate = 0;
- // Used to check if there were any view invalidations in
- // the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME).
- private boolean mHasInvalidation = false;
- // Used to check if it is in the touch boosting period.
- private boolean mIsFrameRateBoosting = false;
- // Used to check if there is a message in the message queue
- // for idleness handling.
- private boolean mHasIdledMessage = false;
- // time for touch boost period.
- private static final int FRAME_RATE_TOUCH_BOOST_TIME = 1500;
- // time for checking idle status periodically.
- private static final int FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS = 500;
- // time for revaluating the idle status before lowering the frame rate.
- private static final int FRAME_RATE_IDLENESS_REEVALUATE_TIME = 500;
-
/**
* A temporary object used so relayoutWindow can return the latest SyncSeqId
* system. The SyncSeqId system was designed to work without synchronous relayout
@@ -3981,12 +3946,6 @@
mWmsRequestSyncGroupState = WMS_SYNC_NONE;
}
}
-
- // For the variable refresh rate project.
- setPreferredFrameRate(mPreferredFrameRate);
- setPreferredFrameRateCategory(mPreferredFrameRateCategory);
- mLastPreferredFrameRateCategory = mPreferredFrameRateCategory;
- mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
}
private void createSyncIfNeeded() {
@@ -6045,8 +6004,6 @@
private static final int MSG_REPORT_KEEP_CLEAR_RECTS = 36;
private static final int MSG_PAUSED_FOR_SYNC_TIMEOUT = 37;
private static final int MSG_DECOR_VIEW_GESTURE_INTERCEPTION = 38;
- private static final int MSG_TOUCH_BOOST_TIMEOUT = 39;
- private static final int MSG_CHECK_INVALIDATION_IDLE = 40;
final class ViewRootHandler extends Handler {
@Override
@@ -6342,32 +6299,6 @@
mNumPausedForSync = 0;
scheduleTraversals();
break;
- case MSG_TOUCH_BOOST_TIMEOUT:
- /**
- * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME).
- */
- mIsFrameRateBoosting = false;
- setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory,
- mLastPreferredFrameRateCategory));
- break;
- case MSG_CHECK_INVALIDATION_IDLE:
- if (!mHasInvalidation && !mIsFrameRateBoosting) {
- mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
- setPreferredFrameRateCategory(mPreferredFrameRateCategory);
- mHasIdledMessage = false;
- } else {
- /**
- * If there is no invalidation within a certain period,
- * we consider the display is idled.
- * We then set the frame rate catetogry to NO_PREFERENCE.
- * Note that SurfaceFlinger also has a mechanism to lower the refresh rate
- * if there is no updates of the buffer.
- */
- mHasInvalidation = false;
- mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE,
- FRAME_RATE_IDLENESS_REEVALUATE_TIME);
- }
- break;
}
}
}
@@ -7310,7 +7241,6 @@
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
- final int action = event.getAction();
boolean handled = mHandwritingInitiator.onTouchEvent(event);
if (handled) {
// If handwriting is started, toolkit doesn't receive ACTION_UP.
@@ -7331,22 +7261,6 @@
scheduleConsumeBatchedInputImmediately();
}
}
-
- // For the variable refresh rate project
- if (handled && shouldTouchBoost(action, mWindowAttributes.type)) {
- // set the frame rate to the maximum value.
- mIsFrameRateBoosting = true;
- setPreferredFrameRateCategory(mPreferredFrameRateCategory);
- }
- /**
- * We want to lower the refresh rate when MotionEvent.ACTION_UP,
- * MotionEvent.ACTION_CANCEL is detected.
- * Not using ACTION_MOVE to avoid checking and sending messages too frequently.
- */
- if (mIsFrameRateBoosting && (action == MotionEvent.ACTION_UP
- || action == MotionEvent.ACTION_CANCEL)) {
- sendDelayedEmptyMessage(MSG_TOUCH_BOOST_TIMEOUT, FRAME_RATE_TOUCH_BOOST_TIME);
- }
return handled ? FINISH_HANDLED : FORWARD;
}
@@ -11934,96 +11848,6 @@
t.clearTrustedPresentationCallback(getSurfaceControl());
}
- private void setPreferredFrameRateCategory(int preferredFrameRateCategory) {
- if (!shouldSetFrameRateCategory()) {
- return;
- }
-
- int frameRateCategory = mIsFrameRateBoosting
- ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory;
-
- try {
- mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
- frameRateCategory, false).apply();
- } catch (Exception e) {
- Log.e(mTag, "Unable to set frame rate category", e);
- }
-
- if (mPreferredFrameRateCategory != FRAME_RATE_CATEGORY_NO_PREFERENCE && !mHasIdledMessage) {
- // Check where the display is idled periodically.
- // If so, set the frame rate category to NO_PREFERENCE
- mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE,
- FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS);
- mHasIdledMessage = true;
- }
- }
-
- private void setPreferredFrameRate(float preferredFrameRate) {
- if (!shouldSetFrameRate()) {
- return;
- }
-
- try {
- mFrameRateTransaction.setFrameRate(mSurfaceControl,
- preferredFrameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT).apply();
- } catch (Exception e) {
- Log.e(mTag, "Unable to set frame rate", e);
- }
- }
-
- private void sendDelayedEmptyMessage(int message, int delayedTime) {
- mHandler.removeMessages(message);
-
- mHandler.sendEmptyMessageDelayed(message, delayedTime);
- }
-
- private boolean shouldSetFrameRateCategory() {
- // use toolkitSetFrameRate flag to gate the change
- return mSurface.isValid() && toolkitSetFrameRate();
- }
-
- private boolean shouldSetFrameRate() {
- // use toolkitSetFrameRate flag to gate the change
- return mPreferredFrameRate > 0 && toolkitSetFrameRate();
- }
-
- private boolean shouldTouchBoost(int motionEventAction, int windowType) {
- boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN
- || motionEventAction == MotionEvent.ACTION_MOVE
- || motionEventAction == MotionEvent.ACTION_UP;
- boolean desiredType = windowType == TYPE_BASE_APPLICATION || windowType == TYPE_APPLICATION
- || windowType == TYPE_APPLICATION_STARTING || windowType == TYPE_DRAWN_APPLICATION;
- // use toolkitSetFrameRate flag to gate the change
- return desiredAction && desiredType && toolkitSetFrameRate();
- }
-
- /**
- * Allow Views to vote for the preferred frame rate category
- *
- * @param frameRateCategory the preferred frame rate category of a View
- */
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
- public void votePreferredFrameRateCategory(int frameRateCategory) {
- mPreferredFrameRateCategory = Math.max(mPreferredFrameRateCategory, frameRateCategory);
- mHasInvalidation = true;
- }
-
- /**
- * Get the value of mPreferredFrameRateCategory
- */
- @VisibleForTesting
- public int getPreferredFrameRateCategory() {
- return mPreferredFrameRateCategory;
- }
-
- /**
- * Get the value of mPreferredFrameRate
- */
- @VisibleForTesting
- public float getPreferredFrameRate() {
- return mPreferredFrameRate;
- }
-
private void logAndTrace(String msg) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.instant(Trace.TRACE_TAG_VIEW, mTag + "-" + msg);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 17c82b6..a0628c4 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9854,7 +9854,10 @@
outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
outAttrs.setInitialSurroundingText(mText);
outAttrs.contentMimeTypes = getReceiveContentMimeTypes();
-
+ if (android.view.inputmethod.Flags.editorinfoHandwritingEnabled()
+ && isAutoHandwritingEnabled()) {
+ outAttrs.setStylusHandwritingEnabled(true);
+ }
ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>();
gestures.add(SelectGesture.class);
gestures.add(SelectRangeGesture.class);
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index b1d22e0..77e1502 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -81,11 +81,6 @@
public static final Flag PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS = releasedFlag(
"persist.sysui.notification.propagate_channel_updates_to_conversations");
- // TODO: b/291907312 - remove feature flags
- /** Gating the NMS->NotificationAttentionHelper buzzBeepBlink refactor */
- public static final Flag ENABLE_ATTENTION_HELPER_REFACTOR = devFlag(
- "persist.debug.sysui.notification.enable_attention_helper_refactor");
-
// TODO b/291899544: for released flags, use resource config values
/** Value used by polite notif. feature */
public static final Flag NOTIF_COOLDOWN_T1 = devFlag(
diff --git a/core/proto/android/nfc/Android.bp b/core/proto/android/nfc/Android.bp
deleted file mode 100644
index 6a62c91..0000000
--- a/core/proto/android/nfc/Android.bp
+++ /dev/null
@@ -1,43 +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: ["Android-Apache-2.0"],
-}
-
-filegroup {
- name: "srcs_nfc_proto",
- srcs: [
- "*.proto",
- ],
-}
-
-// Will be statically linked by `framework-nfc`.
-java_library {
- name: "nfc-proto-java-gen",
- installable: false,
- proto: {
- type: "stream",
- include_dirs: [
- "external/protobuf/src",
- ],
- },
- srcs: [
- ":srcs_nfc_proto",
- ],
- sdk_version: "current",
- min_sdk_version: "current",
-}
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 445ddf5..2993a0e 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -65,7 +65,6 @@
"device-time-shell-utils",
"testables",
"com.android.text.flags-aconfig-java",
- "flag-junit",
],
libs: [
@@ -76,7 +75,6 @@
"framework",
"ext",
"framework-res",
- "android.view.flags-aconfig-java",
],
jni_libs: [
"libpowermanagertest_jni",
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
index d10cf16..531404b 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
@@ -62,24 +62,4 @@
verify(callback2, times(1)).preExecute(clientTransactionHandler);
verify(stateRequest, times(1)).preExecute(clientTransactionHandler);
}
-
- @Test
- public void testPreExecuteTransactionItems() {
- final ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
- final ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
- final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
- final ClientTransactionHandler clientTransactionHandler =
- mock(ClientTransactionHandler.class);
-
- final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.addTransactionItem(callback1);
- transaction.addTransactionItem(callback2);
- transaction.addTransactionItem(stateRequest);
-
- transaction.preExecute(clientTransactionHandler);
-
- verify(callback1, times(1)).preExecute(clientTransactionHandler);
- verify(callback2, times(1)).preExecute(clientTransactionHandler);
- verify(stateRequest, times(1)).preExecute(clientTransactionHandler);
- }
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index f2b0f2e..44a4d58 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -247,31 +247,6 @@
}
@Test
- public void testExecuteTransactionItems_transactionResolution() {
- ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
- when(callback1.getPostExecutionState()).thenReturn(UNDEFINED);
- ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
- when(callback2.getPostExecutionState()).thenReturn(UNDEFINED);
- ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
- IBinder token = mock(IBinder.class);
- when(stateRequest.getActivityToken()).thenReturn(token);
- when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
-
- ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.addTransactionItem(callback1);
- transaction.addTransactionItem(callback2);
- transaction.addTransactionItem(stateRequest);
-
- transaction.preExecute(mTransactionHandler);
- mExecutor.execute(transaction);
-
- InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2, stateRequest);
- inOrder.verify(callback1).execute(eq(mTransactionHandler), any());
- inOrder.verify(callback2).execute(eq(mTransactionHandler), any());
- inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
- }
-
- @Test
public void testDoNotLaunchDestroyedActivity() {
final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>();
when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
@@ -304,43 +279,12 @@
}
@Test
- public void testExecuteTransactionItems_doNotLaunchDestroyedActivity() {
- final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>();
- when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
- // Assume launch transaction is still in queue, so there is no client record.
- when(mTransactionHandler.getActivityClient(any())).thenReturn(null);
-
- // An incoming destroy transaction enters binder thread (preExecute).
- final IBinder token = mock(IBinder.class);
- final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */);
- destroyTransaction.addTransactionItem(
- DestroyActivityItem.obtain(token, false /* finished */, 0 /* configChanges */));
- destroyTransaction.preExecute(mTransactionHandler);
- // The activity should be added to to-be-destroyed container.
- assertEquals(1, activitiesToBeDestroyed.size());
-
- // A previous queued launch transaction runs on main thread (execute).
- final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */);
- final LaunchActivityItem launchItem =
- spy(new LaunchActivityItemBuilder().setActivityToken(token).build());
- launchTransaction.addTransactionItem(launchItem);
- mExecutor.execute(launchTransaction);
-
- // The launch transaction should not be executed because its token is in the
- // to-be-destroyed container.
- verify(launchItem, never()).execute(any(), any());
-
- // After the destroy transaction has been executed, the token should be removed.
- mExecutor.execute(destroyTransaction);
- assertTrue(activitiesToBeDestroyed.isEmpty());
- }
-
- @Test
public void testActivityResultRequiredStateResolution() {
when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
PostExecItem postExecItem = new PostExecItem(ON_RESUME);
+ IBinder token = mock(IBinder.class);
ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
transaction.addCallback(postExecItem);
@@ -356,26 +300,6 @@
}
@Test
- public void testExecuteTransactionItems_activityResultRequiredStateResolution() {
- when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
-
- PostExecItem postExecItem = new PostExecItem(ON_RESUME);
-
- ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.addTransactionItem(postExecItem);
-
- // Verify resolution that should get to onPause
- mClientRecord.setState(ON_RESUME);
- mExecutor.executeTransactionItems(transaction);
- verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_PAUSE), eq(transaction));
-
- // Verify resolution that should get to onStart
- mClientRecord.setState(ON_STOP);
- mExecutor.executeTransactionItems(transaction);
- verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_START), eq(transaction));
- }
-
- @Test
public void testClosestStateResolutionForSameState() {
final int[] allStates = new int[] {
ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY};
@@ -520,18 +444,6 @@
mExecutor.executeCallbacks(transaction);
}
- @Test(expected = IllegalArgumentException.class)
- public void testExecuteTransactionItems_activityItemNullRecordThrowsException() {
- final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
- when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
- final IBinder token = mock(IBinder.class);
- final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.addTransactionItem(activityItem);
- when(mTransactionHandler.getActivityClient(token)).thenReturn(null);
-
- mExecutor.executeTransactionItems(transaction);
- }
-
@Test
public void testActivityItemExecute() {
final IBinder token = mock(IBinder.class);
@@ -552,26 +464,6 @@
inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
}
- @Test
- public void testExecuteTransactionItems_activityItemExecute() {
- final IBinder token = mock(IBinder.class);
- final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
- when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
- when(activityItem.getActivityToken()).thenReturn(token);
- transaction.addTransactionItem(activityItem);
- final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
- transaction.addTransactionItem(stateRequest);
- when(stateRequest.getActivityToken()).thenReturn(token);
- when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
-
- mExecutor.execute(transaction);
-
- final InOrder inOrder = inOrder(activityItem, stateRequest);
- inOrder.verify(activityItem).execute(eq(mTransactionHandler), eq(mClientRecord), any());
- inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
- }
-
private static int[] shuffledArray(int[] inputArray) {
final List<Integer> list = Arrays.stream(inputArray).boxed().collect(Collectors.toList());
Collections.shuffle(list);
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 4aa62c5..7d047c9 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -285,10 +285,6 @@
78 /* configChanges */);
ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.addTransactionItem(callback1);
- transaction.addTransactionItem(callback2);
- transaction.addTransactionItem(lifecycleRequest);
-
transaction.addCallback(callback1);
transaction.addCallback(callback2);
transaction.setLifecycleStateRequest(lifecycleRequest);
diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index 3f78396..0d687b2 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -16,7 +16,7 @@
package android.graphics;
-import static com.android.text.flags.Flags.FLAG_CUSTOM_LOCALE_FALLBACK;
+import static com.android.text.flags.Flags.FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -931,7 +931,7 @@
return String.format(xml, op, lang, font);
}
- @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+ @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
@Test
public void testBuildSystemFallback__Customization_locale_prepend() {
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
@@ -947,7 +947,7 @@
assertB3emFontIsUsed(typeface);
}
- @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+ @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
@Test
public void testBuildSystemFallback__Customization_locale_replace() {
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
@@ -963,7 +963,7 @@
assertB3emFontIsUsed(typeface);
}
- @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+ @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
@Test
public void testBuildSystemFallback__Customization_locale_append() {
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
@@ -979,7 +979,7 @@
assertA3emFontIsUsed(typeface);
}
- @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+ @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
@Test
public void testBuildSystemFallback__Customization_locale_ScriptMismatch() {
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
@@ -995,7 +995,7 @@
assertA3emFontIsUsed(typeface);
}
- @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+ @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
@Test
public void testBuildSystemFallback__Customization_locale_SubscriptMatch() {
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
diff --git a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
index e117051..71bdce4 100644
--- a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
+++ b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
@@ -23,6 +23,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.eq;
@@ -148,6 +149,28 @@
reporter.assertLastReportedSetEquals(sc5, sc6, sc7, sc8);
}
+ @Test
+ public void testCallStackDebugging_matchesFilters() {
+ SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance();
+
+ // Specific name, any call
+ registry.setCallStackDebuggingParams("com.android.app1", "");
+ assertFalse(registry.matchesForCallStackDebugging("com.android.noMatchApp", "setAlpha"));
+ assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha"));
+
+ // Any name, specific call
+ registry.setCallStackDebuggingParams("", "setAlpha");
+ assertFalse(registry.matchesForCallStackDebugging("com.android.app1", "setLayer"));
+ assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha"));
+ assertTrue(registry.matchesForCallStackDebugging("com.android.app2", "setAlpha"));
+
+ // Specific name, specific call
+ registry.setCallStackDebuggingParams("com.android.app1", "setAlpha");
+ assertFalse(registry.matchesForCallStackDebugging("com.android.app1", "setLayer"));
+ assertFalse(registry.matchesForCallStackDebugging("com.android.app2", "setAlpha"));
+ assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha"));
+ }
+
private SurfaceControl buildTestSurface() {
return new SurfaceControl.Builder()
.setContainerLayer()
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 1a38dec..6a9fc04 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -17,11 +17,6 @@
package android.view;
import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
-import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE;
-import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
-import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
-import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
-import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
@@ -53,12 +48,8 @@
import android.os.Binder;
import android.os.SystemProperties;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
-import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowInsets.Side;
import android.view.WindowInsets.Type;
@@ -106,10 +97,6 @@
// state after the test completes.
private static boolean sOriginalTouchMode;
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
@BeforeClass
public static void setUpClass() {
sContext = sInstrumentation.getTargetContext();
@@ -440,129 +427,6 @@
assertThat(result).isFalse();
}
- /**
- * Test the default values are properly set
- */
- @UiThreadTest
- @Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
- public void votePreferredFrameRate_getDefaultValues() {
- ViewRootImpl viewRootImpl = new ViewRootImpl(sContext,
- sContext.getDisplayNoVerify());
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE);
- assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
- }
-
- /**
- * Test the value of the frame rate cateogry based on the visibility of a view
- * Invsible: FRAME_RATE_CATEGORY_NO_PREFERENCE
- * Visible: FRAME_RATE_CATEGORY_NORMAL
- */
- @Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
- public void votePreferredFrameRate_voteFrameRateCategory_visibility() {
- View view = new View(sContext);
- attachViewToWindow(view);
- ViewRootImpl viewRootImpl = view.getViewRootImpl();
- sInstrumentation.runOnMainSync(() -> {
- view.setVisibility(View.INVISIBLE);
- view.invalidate();
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE);
- });
-
- sInstrumentation.runOnMainSync(() -> {
- view.setVisibility(View.VISIBLE);
- view.invalidate();
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NORMAL);
- });
- }
-
- /**
- * Test the value of the frame rate cateogry based on the size of a view.
- * The current threshold value is 7% of the screen size
- * <7%: FRAME_RATE_CATEGORY_LOW
- */
- @Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
- public void votePreferredFrameRate_voteFrameRateCategory_smallSize() {
- View view = new View(sContext);
- WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
- wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
- wmlp.width = 1;
- wmlp.height = 1;
-
- sInstrumentation.runOnMainSync(() -> {
- WindowManager wm = sContext.getSystemService(WindowManager.class);
- wm.addView(view, wmlp);
- });
- sInstrumentation.waitForIdleSync();
-
- ViewRootImpl viewRootImpl = view.getViewRootImpl();
- sInstrumentation.runOnMainSync(() -> {
- view.invalidate();
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
- });
- }
-
- /**
- * Test the value of the frame rate cateogry based on the size of a view.
- * The current threshold value is 7% of the screen size
- * >=7% : FRAME_RATE_CATEGORY_NORMAL
- */
- @Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
- public void votePreferredFrameRate_voteFrameRateCategory_normalSize() {
- View view = new View(sContext);
- WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
- wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
-
- sInstrumentation.runOnMainSync(() -> {
- WindowManager wm = sContext.getSystemService(WindowManager.class);
- Display display = wm.getDefaultDisplay();
- DisplayMetrics metrics = new DisplayMetrics();
- display.getMetrics(metrics);
- wmlp.width = (int) (metrics.widthPixels * 0.9);
- wmlp.height = (int) (metrics.heightPixels * 0.9);
- wm.addView(view, wmlp);
- });
- sInstrumentation.waitForIdleSync();
-
- ViewRootImpl viewRootImpl = view.getViewRootImpl();
- sInstrumentation.runOnMainSync(() -> {
- view.invalidate();
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
- });
- }
-
- /**
- * Test how values of the frame rate cateogry are aggregated.
- * It should take the max value among all of the voted categories per frame.
- */
- @Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
- public void votePreferredFrameRate_voteFrameRateCategory_aggregate() {
- View view = new View(sContext);
- attachViewToWindow(view);
- sInstrumentation.runOnMainSync(() -> {
- ViewRootImpl viewRootImpl = view.getViewRootImpl();
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
- });
- }
-
@Test
public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index b71aaf3..cc73ece 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2887,6 +2887,12 @@
"group": "WM_DEBUG_RESIZE",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "419378610": {
+ "message": "Content Recording: Apply transformations of shift %d x %d, scale %f x %f, crop (aka recorded content size) %d x %d for display %d; display has size %d x %d; surface has size %d x %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"422634333": {
"message": "First draw done in potential wallpaper target %s",
"level": "VERBOSE",
@@ -4339,12 +4345,6 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
- "1936800105": {
- "message": "Content Recording: Apply transformations of shift %d x %d, scale %f, crop (aka recorded content size) %d x %d for display %d; display has size %d x %d; surface has size %d x %d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"1945495497": {
"message": "Focused window didn't have a valid surface drawn.",
"level": "DEBUG",
diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
index 6e04a2f..ba5628c 100644
--- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java
+++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
@@ -182,7 +182,7 @@
// For ignoring the customization, consume the new-locale-family element but don't
// register any customizations.
- if (com.android.text.flags.Flags.customLocaleFallback()) {
+ if (com.android.text.flags.Flags.vendorCustomLocaleFallback()) {
outCustomization.add(new FontConfig.Customization.LocaleFallback(
Locale.forLanguageTag(lang), intOp, family));
}
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index 4c75356..685fd82 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -16,7 +16,7 @@
package android.graphics.fonts;
-import static com.android.text.flags.Flags.FLAG_DEPRECATE_FONTS_XML;
+import static com.android.text.flags.Flags.FLAG_NEW_FONTS_FALLBACK_XML;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -145,7 +145,7 @@
* @return A variable font family. null if a variable font cannot be built from the given
* fonts.
*/
- @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
public @Nullable FontFamily buildVariableFamily() {
int variableFamilyType = analyzeAndResolveVariableType(mFonts);
if (variableFamilyType == VARIABLE_FONT_FAMILY_TYPE_UNKNOWN) {
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 618aa5b..3ef714ed 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -241,7 +241,7 @@
int configVersion
) {
final String fontsXml;
- if (com.android.text.flags.Flags.deprecateFontsXml()) {
+ if (com.android.text.flags.Flags.newFontsFallbackXml()) {
fontsXml = FONTS_XML;
} else {
fontsXml = LEGACY_FONTS_XML;
@@ -272,7 +272,7 @@
*/
public static @NonNull FontConfig getSystemPreinstalledFontConfig() {
final String fontsXml;
- if (com.android.text.flags.Flags.deprecateFontsXml()) {
+ if (com.android.text.flags.Flags.newFontsFallbackXml()) {
fontsXml = FONTS_XML;
} else {
fontsXml = LEGACY_FONTS_XML;
diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java
index 569f9b6..7932e33 100644
--- a/graphics/java/android/graphics/text/PositionedGlyphs.java
+++ b/graphics/java/android/graphics/text/PositionedGlyphs.java
@@ -16,7 +16,7 @@
package android.graphics.text;
-import static com.android.text.flags.Flags.FLAG_DEPRECATE_FONTS_XML;
+import static com.android.text.flags.Flags.FLAG_NEW_FONTS_FALLBACK_XML;
import android.annotation.FlaggedApi;
import android.annotation.IntRange;
@@ -173,7 +173,7 @@
* @param index the glyph index
* @return true if the fake bold option is on, otherwise off.
*/
- @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
public boolean getFakeBold(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
return nGetFakeBold(mLayoutPtr, index);
@@ -185,7 +185,7 @@
* @param index the glyph index
* @return true if the fake italic option is on, otherwise off.
*/
- @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
public boolean getFakeItalic(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
return nGetFakeItalic(mLayoutPtr, index);
@@ -195,7 +195,7 @@
* A special value returned by {@link #getWeightOverride(int)} and
* {@link #getItalicOverride(int)} that indicates no font variation setting is overridden.
*/
- @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
public static final float NO_OVERRIDE = Float.MIN_VALUE;
/**
@@ -205,7 +205,7 @@
* @param index the glyph index
* @return overridden weight value or {@link #NO_OVERRIDE}.
*/
- @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
public float getWeightOverride(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
float value = nGetWeightOverride(mLayoutPtr, index);
@@ -223,7 +223,7 @@
* @param index the glyph index
* @return overridden weight value or {@link #NO_OVERRIDE}.
*/
- @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
public float getItalicOverride(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
float value = nGetItalicOverride(mLayoutPtr, index);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
new file mode 100644
index 0000000..ff49cdc
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
@@ -0,0 +1,119 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static java.util.Objects.requireNonNull;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * The parameter to create an overlay container that retrieved from
+ * {@link android.app.ActivityOptions} bundle.
+ */
+class OverlayCreateParams {
+
+ // TODO(b/295803704): Move them to WM Extensions so that we can reuse in WM Jetpack.
+ @VisibleForTesting
+ static final String KEY_OVERLAY_CREATE_PARAMS =
+ "androidx.window.extensions.OverlayCreateParams";
+
+ @VisibleForTesting
+ static final String KEY_OVERLAY_CREATE_PARAMS_TASK_ID =
+ "androidx.window.extensions.OverlayCreateParams.taskId";
+
+ @VisibleForTesting
+ static final String KEY_OVERLAY_CREATE_PARAMS_TAG =
+ "androidx.window.extensions.OverlayCreateParams.tag";
+
+ @VisibleForTesting
+ static final String KEY_OVERLAY_CREATE_PARAMS_BOUNDS =
+ "androidx.window.extensions.OverlayCreateParams.bounds";
+
+ private final int mTaskId;
+
+ @NonNull
+ private final String mTag;
+
+ @NonNull
+ private final Rect mBounds;
+
+ OverlayCreateParams(int taskId, @NonNull String tag, @NonNull Rect bounds) {
+ mTaskId = taskId;
+ mTag = requireNonNull(tag);
+ mBounds = requireNonNull(bounds);
+ }
+
+ int getTaskId() {
+ return mTaskId;
+ }
+
+ @NonNull
+ String getTag() {
+ return mTag;
+ }
+
+ @NonNull
+ Rect getBounds() {
+ return mBounds;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mTaskId;
+ result = 31 * result + mTag.hashCode();
+ result = 31 * result + mBounds.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) return true;
+ if (!(obj instanceof OverlayCreateParams thatParams)) return false;
+ return mTaskId == thatParams.mTaskId
+ && mTag.equals(thatParams.mTag)
+ && mBounds.equals(thatParams.mBounds);
+ }
+
+ @Override
+ public String toString() {
+ return OverlayCreateParams.class.getSimpleName() + ": {"
+ + "taskId=" + mTaskId
+ + ", tag=" + mTag
+ + ", bounds=" + mBounds
+ + "}";
+ }
+
+ /** Retrieves the {@link OverlayCreateParams} from {@link android.app.ActivityOptions} bundle */
+ @Nullable
+ static OverlayCreateParams fromBundle(@NonNull Bundle bundle) {
+ final Bundle paramsBundle = bundle.getBundle(KEY_OVERLAY_CREATE_PARAMS);
+ if (paramsBundle == null) {
+ return null;
+ }
+ final int taskId = paramsBundle.getInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID);
+ final String tag = requireNonNull(paramsBundle.getString(KEY_OVERLAY_CREATE_PARAMS_TAG));
+ final Rect bounds = requireNonNull(paramsBundle.getParcelable(
+ KEY_OVERLAY_CREATE_PARAMS_BOUNDS, Rect.class));
+
+ return new OverlayCreateParams(taskId, tag, bounds);
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index cdfc4c8..2f1eec1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -40,9 +40,10 @@
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
+import static androidx.window.extensions.embedding.SplitPresenter.boundsSmallerThanMinDimensions;
import static androidx.window.extensions.embedding.SplitPresenter.getActivitiesMinDimensionsPair;
import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
-import static androidx.window.extensions.embedding.SplitPresenter.getTaskWindowMetrics;
+import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
import android.app.Activity;
@@ -87,6 +88,7 @@
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import java.util.ArrayList;
import java.util.Collections;
@@ -123,8 +125,7 @@
* and unregistered via {@link #clearSplitAttributesCalculator()}.
* This is called when:
* <ul>
- * <li>{@link SplitPresenter#updateSplitContainer(SplitContainer, TaskFragmentContainer,
- * WindowContainerTransaction)}</li>
+ * <li>{@link SplitPresenter#updateSplitContainer}</li>
* <li>There's a started Activity which matches {@link SplitPairRule} </li>
* <li>Checking whether the place holder should be launched if there's a Activity matches
* {@link SplitPlaceholderRule} </li>
@@ -759,6 +760,8 @@
if (targetContainer == null) {
// When there is no embedding rule matched, try to place it in the top container
// like a normal launch.
+ // TODO(b/301034784): Check if it makes sense to place the activity in overlay
+ // container.
targetContainer = taskContainer.getTopNonFinishingTaskFragmentContainer();
}
if (targetContainer == null) {
@@ -1007,6 +1010,7 @@
if (taskContainer == null) {
return;
}
+ // TODO(b/301034784): Check if it makes sense to place the activity in overlay container.
final TaskFragmentContainer targetContainer =
taskContainer.getTopNonFinishingTaskFragmentContainer();
if (targetContainer == null) {
@@ -1166,7 +1170,7 @@
getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity));
if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
&& canReuseContainer(splitRule, splitContainer.getSplitRule(),
- getTaskWindowMetrics(taskProperties.getConfiguration()),
+ taskProperties.getTaskMetrics(),
calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) {
// Can launch in the existing secondary container if the rules share the same
// presentation.
@@ -1408,6 +1412,22 @@
private TaskFragmentContainer createEmptyExpandedContainer(
@NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
@Nullable Activity launchingActivity) {
+ return createEmptyContainer(wct, intent, taskId, new Rect(), launchingActivity,
+ null /* overlayTag */);
+ }
+
+ /**
+ * Returns an empty {@link TaskFragmentContainer} that we can launch an activity into.
+ * If {@code overlayTag} is set, it means the created {@link TaskFragmentContainer} is an
+ * overlay container.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ @Nullable
+ TaskFragmentContainer createEmptyContainer(
+ @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
+ @NonNull Rect bounds, @Nullable Activity launchingActivity,
+ @Nullable String overlayTag) {
// We need an activity in the organizer process in the same Task to use as the owner
// activity, as well as to get the Task window info.
final Activity activityInTask;
@@ -1423,13 +1443,46 @@
// Can't find any activity in the Task that we can use as the owner activity.
return null;
}
- final TaskFragmentContainer expandedContainer = newContainer(intent, activityInTask,
- taskId);
- mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(),
- activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED);
- mPresenter.updateAnimationParams(wct, expandedContainer.getTaskFragmentToken(),
+ final TaskFragmentContainer container = newContainer(null /* pendingAppearedActivity */,
+ intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag);
+ final IBinder taskFragmentToken = container.getTaskFragmentToken();
+ // Note that taskContainer will not exist before calling #newContainer if the container
+ // is the first embedded TF in the task.
+ final TaskContainer taskContainer = container.getTaskContainer();
+ final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics().getBounds();
+ final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+ final int windowingMode = taskContainer
+ .getWindowingModeForSplitTaskFragment(sanitizedBounds);
+ mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(),
+ sanitizedBounds, windowingMode);
+ mPresenter.updateAnimationParams(wct, taskFragmentToken,
TaskFragmentAnimationParams.DEFAULT);
- return expandedContainer;
+ mPresenter.setTaskFragmentIsolatedNavigation(wct, taskFragmentToken,
+ overlayTag != null && !sanitizedBounds.isEmpty());
+
+ return container;
+ }
+
+ /**
+ * Returns the expanded bounds if the {@code bounds} violate minimum dimension or are not fully
+ * covered by the task bounds. Otherwise, returns {@code bounds}.
+ */
+ @NonNull
+ private static Rect sanitizeBounds(@NonNull Rect bounds, @NonNull Intent intent,
+ @NonNull Rect taskBounds) {
+ if (bounds.isEmpty()) {
+ // Don't need to check if the bounds follows the task bounds.
+ return bounds;
+ }
+ if (boundsSmallerThanMinDimensions(bounds, getMinDimensions(intent))) {
+ // Expand the bounds if the bounds are smaller than minimum dimensions.
+ return new Rect();
+ }
+ if (!taskBounds.contains(bounds)) {
+ // Expand the bounds if the bounds exceed the task bounds.
+ return new Rect();
+ }
+ return bounds;
}
/**
@@ -1449,8 +1502,7 @@
final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer);
final TaskContainer.TaskProperties taskProperties = mPresenter
.getTaskProperties(primaryActivity);
- final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(
- taskProperties.getConfiguration());
+ final WindowMetrics taskWindowMetrics = taskProperties.getTaskMetrics();
final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes(
taskProperties, splitRule, splitRule.getDefaultSplitAttributes(),
getActivityIntentMinDimensionsPair(primaryActivity, intent));
@@ -1519,14 +1571,22 @@
TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
@NonNull Activity activityInTask, int taskId) {
return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
- activityInTask, taskId, null /* pairedPrimaryContainer */);
+ activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
}
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
@NonNull Activity activityInTask, int taskId) {
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
- activityInTask, taskId, null /* pairedPrimaryContainer */);
+ activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
+ }
+
+ @GuardedBy("mLock")
+ TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
+ @NonNull Activity activityInTask, int taskId,
+ @NonNull TaskFragmentContainer pairedPrimaryContainer) {
+ return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
+ activityInTask, taskId, pairedPrimaryContainer, null /* tag */);
}
/**
@@ -1540,11 +1600,14 @@
* @param taskId parent Task of the new TaskFragment.
* @param pairedPrimaryContainer the paired primary {@link TaskFragmentContainer}. When it is
* set, the new container will be added right above it.
+ * @param overlayTag The tag for the new created overlay container. It must be
+ * needed if {@code isOverlay} is {@code true}. Otherwise,
+ * it should be {@code null}.
*/
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId,
- @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+ @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
if (activityInTask == null) {
throw new IllegalArgumentException("activityInTask must not be null,");
}
@@ -1553,7 +1616,7 @@
}
final TaskContainer taskContainer = mTaskContainers.get(taskId);
final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
- pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer);
+ pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag);
return container;
}
@@ -1754,13 +1817,12 @@
* Updates {@link SplitContainer} with the given {@link SplitAttributes} if the
* {@link SplitContainer} is the top most and not finished. If passed {@link SplitAttributes}
* are {@code null}, the {@link SplitAttributes} will be calculated with
- * {@link SplitPresenter#computeSplitAttributes(TaskContainer.TaskProperties, SplitRule, Pair)}.
+ * {@link SplitPresenter#computeSplitAttributes}.
*
* @param splitContainer The {@link SplitContainer} to update
* @param splitAttributes Update with this {@code splitAttributes} if it is not {@code null}.
* Otherwise, use the value calculated by
- * {@link SplitPresenter#computeSplitAttributes(
- * TaskContainer.TaskProperties, SplitRule, Pair)}
+ * {@link SplitPresenter#computeSplitAttributes}
*
* @return {@code true} if the update succeed. Otherwise, returns {@code false}.
*/
@@ -2255,6 +2317,96 @@
return shouldRetainAssociatedContainer(finishingContainer, associatedContainer);
}
+ /**
+ * Gets all overlay containers from all tasks in this process, or an empty list if there's
+ * no overlay container.
+ * <p>
+ * Note that we only support one overlay container for each task, but an app could have multiple
+ * tasks.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ @NonNull
+ List<TaskFragmentContainer> getAllOverlayTaskFragmentContainers() {
+ final List<TaskFragmentContainer> overlayContainers = new ArrayList<>();
+ for (int i = 0; i < mTaskContainers.size(); i++) {
+ final TaskContainer taskContainer = mTaskContainers.valueAt(i);
+ final TaskFragmentContainer overlayContainer = taskContainer.getOverlayContainer();
+ if (overlayContainer != null) {
+ overlayContainers.add(overlayContainer);
+ }
+ }
+ return overlayContainers;
+ }
+
+ @VisibleForTesting
+ // Suppress GuardedBy warning because lint ask to mark this method as
+ // @GuardedBy(container.mController.mLock), which is mLock itself
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mLock")
+ @Nullable
+ TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
+ @NonNull WindowContainerTransaction wct,
+ @NonNull OverlayCreateParams overlayCreateParams, int launchTaskId,
+ @NonNull Intent intent, @NonNull Activity launchActivity) {
+ final int taskId = overlayCreateParams.getTaskId();
+ if (taskId != launchTaskId) {
+ // The task ID doesn't match the launch activity's. Cannot determine the host task
+ // to launch the overlay.
+ throw new IllegalArgumentException("The task ID of "
+ + "OverlayCreateParams#launchingActivity must match the task ID of "
+ + "the activity to #startActivity with the activity options that takes "
+ + "OverlayCreateParams.");
+ }
+ final List<TaskFragmentContainer> overlayContainers =
+ getAllOverlayTaskFragmentContainers();
+ final String overlayTag = overlayCreateParams.getTag();
+
+ // If the requested bounds of OverlayCreateParams are smaller than minimum dimensions
+ // specified by Intent, expand the overlay container to fill the parent task instead.
+ final Rect bounds = overlayCreateParams.getBounds();
+ final Size minDimensions = getMinDimensions(intent);
+ final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(bounds,
+ minDimensions);
+ if (!overlayContainers.isEmpty()) {
+ for (final TaskFragmentContainer overlayContainer : overlayContainers) {
+ if (!overlayTag.equals(overlayContainer.getOverlayTag())
+ && taskId == overlayContainer.getTaskId()) {
+ // If there's an overlay container with different tag shown in the same
+ // task, dismiss the existing overlay container.
+ overlayContainer.finish(false /* shouldFinishDependant */, mPresenter,
+ wct, SplitController.this);
+ }
+ if (overlayTag.equals(overlayContainer.getOverlayTag())
+ && taskId != overlayContainer.getTaskId()) {
+ // If there's an overlay container with same tag in a different task,
+ // dismiss the overlay container since the tag must be unique per process.
+ overlayContainer.finish(false /* shouldFinishDependant */, mPresenter,
+ wct, SplitController.this);
+ }
+ if (overlayTag.equals(overlayContainer.getOverlayTag())
+ && taskId == overlayContainer.getTaskId()) {
+ // If there's an overlay container with the same tag and task ID, we treat
+ // the OverlayCreateParams as the update to the container.
+ final Rect taskBounds = overlayContainer.getTaskContainer().getTaskProperties()
+ .getTaskMetrics().getBounds();
+ final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+ final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+ mPresenter.resizeTaskFragment(wct, overlayToken, sanitizedBounds);
+ mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayToken,
+ !sanitizedBounds.isEmpty());
+ // We can just return the updated overlay container and don't need to
+ // check other condition since we only have one OverlayCreateParams, and
+ // if the tag and task are matched, it's impossible to match another task
+ // or tag since tags and tasks are all unique.
+ return overlayContainer;
+ }
+ }
+ }
+ return createEmptyContainer(wct, intent, taskId,
+ (shouldExpandContainer ? new Rect() : bounds), launchActivity, overlayTag);
+ }
+
private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
@Override
@@ -2417,8 +2569,16 @@
final TaskFragmentContainer launchedInTaskFragment;
if (launchingActivity != null) {
final int taskId = getTaskId(launchingActivity);
- launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
- launchingActivity);
+ final OverlayCreateParams overlayCreateParams =
+ OverlayCreateParams.fromBundle(options);
+ if (Flags.activityEmbeddingOverlayPresentationFlag()
+ && overlayCreateParams != null) {
+ launchedInTaskFragment = createOrUpdateOverlayTaskFragmentIfNeeded(wct,
+ overlayCreateParams, taskId, intent, launchingActivity);
+ } else {
+ launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
+ launchingActivity);
+ }
} else {
launchedInTaskFragment = resolveStartActivityIntentFromNonActivityContext(wct,
intent);
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 d894487..66e76c5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -30,12 +30,10 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
-import android.util.DisplayMetrics;
import android.util.LayoutDirection;
import android.util.Pair;
import android.util.Size;
import android.view.View;
-import android.view.WindowInsets;
import android.view.WindowMetrics;
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentCreationParams;
@@ -307,8 +305,8 @@
}
final int taskId = primaryContainer.getTaskId();
- final TaskFragmentContainer secondaryContainer = mController.newContainer(
- null /* pendingAppearedActivity */, activityIntent, launchingActivity, taskId,
+ final TaskFragmentContainer secondaryContainer = mController.newContainer(activityIntent,
+ launchingActivity, taskId,
// Pass in the primary container to make sure it is added right above the primary.
primaryContainer);
final TaskContainer taskContainer = mController.getTaskContainer(taskId);
@@ -618,7 +616,7 @@
@NonNull SplitRule rule, @NonNull SplitAttributes defaultSplitAttributes,
@Nullable Pair<Size, Size> minDimensionsPair) {
final Configuration taskConfiguration = taskProperties.getConfiguration();
- final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration);
+ final WindowMetrics taskWindowMetrics = taskProperties.getTaskMetrics();
final Function<SplitAttributesCalculatorParams, SplitAttributes> calculator =
mController.getSplitAttributesCalculator();
final boolean areDefaultConstraintsSatisfied = rule.checkParentMetrics(taskWindowMetrics);
@@ -713,11 +711,15 @@
return new Size(windowLayout.minWidth, windowLayout.minHeight);
}
- private static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds,
+ static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds,
@Nullable Size minDimensions) {
if (minDimensions == null) {
return false;
}
+ // Empty bounds mean the bounds follow the parent host task's bounds. Skip the check.
+ if (bounds.isEmpty()) {
+ return false;
+ }
return bounds.width() < minDimensions.getWidth()
|| bounds.height() < minDimensions.getHeight();
}
@@ -1066,14 +1068,6 @@
@NonNull
WindowMetrics getTaskWindowMetrics(@NonNull Activity activity) {
- return getTaskWindowMetrics(getTaskProperties(activity).getConfiguration());
- }
-
- @NonNull
- static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) {
- final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
- // TODO(b/190433398): Supply correct insets.
- final float density = taskConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
- return new WindowMetrics(taskBounds, WindowInsets.CONSUMED, density);
+ return getTaskProperties(activity).getTaskMetrics();
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 9a0769a..f4427aa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -30,7 +30,10 @@
import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArraySet;
+import android.util.DisplayMetrics;
import android.util.Log;
+import android.view.WindowInsets;
+import android.view.WindowMetrics;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentParentInfo;
import android.window.WindowContainerTransaction;
@@ -61,6 +64,10 @@
@Nullable
private SplitPinContainer mSplitPinContainer;
+ /** The overlay container in this Task. */
+ @Nullable
+ private TaskFragmentContainer mOverlayContainer;
+
@NonNull
private final Configuration mConfiguration;
@@ -221,6 +228,12 @@
return null;
}
+ /** Returns the overlay container in the task, or {@code null} if it doesn't exist. */
+ @Nullable
+ TaskFragmentContainer getOverlayContainer() {
+ return mOverlayContainer;
+ }
+
int indexOf(@NonNull TaskFragmentContainer child) {
return mContainers.indexOf(child);
}
@@ -311,8 +324,8 @@
onTaskFragmentContainerUpdated();
}
- void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainer) {
- mContainers.removeAll(taskFragmentContainer);
+ void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainers) {
+ mContainers.removeAll(taskFragmentContainers);
onTaskFragmentContainerUpdated();
}
@@ -332,6 +345,15 @@
}
private void onTaskFragmentContainerUpdated() {
+ // TODO(b/300211704): Find a better mechanism to handle the z-order in case we introduce
+ // another special container that should also be on top in the future.
+ updateSplitPinContainerIfNecessary();
+ // Update overlay container after split pin container since the overlay should be on top of
+ // pin container.
+ updateOverlayContainerIfNecessary();
+ }
+
+ private void updateSplitPinContainerIfNecessary() {
if (mSplitPinContainer == null) {
return;
}
@@ -344,10 +366,7 @@
}
// Ensure the pinned container is top-most.
- if (pinnedContainerIndex != mContainers.size() - 1) {
- mContainers.remove(pinnedContainer);
- mContainers.add(pinnedContainer);
- }
+ moveContainerToLastIfNecessary(pinnedContainer);
// Update the primary container adjacent to the pinned container if needed.
final TaskFragmentContainer adjacentContainer =
@@ -359,6 +378,31 @@
}
}
+ private void updateOverlayContainerIfNecessary() {
+ final List<TaskFragmentContainer> overlayContainers = mContainers.stream()
+ .filter(TaskFragmentContainer::isOverlay).toList();
+ if (overlayContainers.size() > 1) {
+ throw new IllegalStateException("There must be at most one overlay container per Task");
+ }
+ mOverlayContainer = overlayContainers.isEmpty() ? null : overlayContainers.get(0);
+ if (mOverlayContainer != null) {
+ moveContainerToLastIfNecessary(mOverlayContainer);
+ }
+ }
+
+ /** Moves the {@code container} to the last to align taskFragments' z-order. */
+ private void moveContainerToLastIfNecessary(@NonNull TaskFragmentContainer container) {
+ final int index = mContainers.indexOf(container);
+ if (index < 0) {
+ Log.w(TAG, "The container:" + container + " is not in the container list!");
+ return;
+ }
+ if (index != mContainers.size() - 1) {
+ mContainers.remove(container);
+ mContainers.add(container);
+ }
+ }
+
/**
* Gets the descriptors of split states in this Task.
*
@@ -398,6 +442,15 @@
return mConfiguration;
}
+ /** A helper method to return task {@link WindowMetrics} from this {@link TaskProperties} */
+ @NonNull
+ WindowMetrics getTaskMetrics() {
+ final Rect taskBounds = mConfiguration.windowConfiguration.getBounds();
+ // TODO(b/190433398): Supply correct insets.
+ final float density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ return new WindowMetrics(taskBounds, WindowInsets.CONSUMED, density);
+ }
+
/** Translates the given absolute bounds to relative bounds in this Task coordinate. */
void translateAbsoluteBoundsToRelativeBounds(@NonNull Rect inOutBounds) {
if (inOutBounds.isEmpty()) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 0a694b5..2ba5c9b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -102,6 +102,9 @@
*/
private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
+ @Nullable
+ private final String mOverlayTag;
+
/** Indicates whether the container was cleaned up after the last activity was removed. */
private boolean mIsFinished;
@@ -158,14 +161,28 @@
private boolean mHasCrossProcessActivities;
/**
+ * @see #TaskFragmentContainer(Activity, Intent, TaskContainer, SplitController,
+ * TaskFragmentContainer, String)
+ */
+ TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
+ @Nullable Intent pendingAppearedIntent,
+ @NonNull TaskContainer taskContainer,
+ @NonNull SplitController controller,
+ @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+ this(pendingAppearedActivity, pendingAppearedIntent, taskContainer,
+ controller, pairedPrimaryContainer, null /* overlayTag */);
+ }
+
+ /**
* Creates a container with an existing activity that will be re-parented to it in a window
* container transaction.
* @param pairedPrimaryContainer when it is set, the new container will be add right above it
+ * @param overlayTag Sets to indicate this taskFragment is an overlay container
*/
TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
@NonNull SplitController controller,
- @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+ @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
|| (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
throw new IllegalArgumentException(
@@ -174,6 +191,8 @@
mController = controller;
mToken = new Binder("TaskFragmentContainer");
mTaskContainer = taskContainer;
+ mOverlayTag = overlayTag;
+
if (pairedPrimaryContainer != null) {
// The TaskFragment will be positioned right above the paired container.
if (pairedPrimaryContainer.getTaskContainer() != taskContainer) {
@@ -863,6 +882,20 @@
return mTaskContainer.indexOf(this) > mTaskContainer.indexOf(other);
}
+ /** Returns whether this taskFragment container is an overlay container. */
+ boolean isOverlay() {
+ return mOverlayTag != null;
+ }
+
+ /**
+ * Returns the tag specified in {@link OverlayCreateParams#getTag()}. {@code null} if this
+ * taskFragment container is not an overlay container.
+ */
+ @Nullable
+ String getOverlayTag() {
+ return mOverlayTag;
+ }
+
@Override
public String toString() {
return toString(true /* includeContainersToFinishOnExit */);
@@ -881,6 +914,7 @@
+ " topNonFinishingActivity=" + getTopNonFinishingActivity()
+ " runningActivityCount=" + getRunningActivityCount()
+ " isFinished=" + mIsFinished
+ + " overlayTag=" + mOverlayTag
+ " lastRequestedBounds=" + mLastRequestedBounds
+ " pendingAppearedActivities=" + mPendingAppearedActivities
+ (includeContainersToFinishOnExit ? " containersToFinishOnExit="
diff --git a/libs/WindowManager/Jetpack/tests/unittest/Android.bp b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
index ed2ff2d..4ddbd13 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/Android.bp
+++ b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
@@ -36,6 +36,7 @@
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
+ "flag-junit",
"mockito-target-extended-minus-junit4",
"truth",
"testables",
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
new file mode 100644
index 0000000..af8017a
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -0,0 +1,393 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_BOUNDS;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TAG;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TASK_ID;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.window.TaskFragmentInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
+import androidx.window.extensions.layout.WindowLayoutComponentImpl;
+import androidx.window.extensions.layout.WindowLayoutInfo;
+
+import com.android.window.flags.Flags;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test class for overlay presentation feature.
+ *
+ * Build/Install/Run:
+ * atest WMJetpackUnitTests:OverlayPresentationTest
+ */
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class OverlayPresentationTest {
+
+ @Rule
+ public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
+
+ private static final OverlayCreateParams TEST_OVERLAY_CREATE_PARAMS =
+ new OverlayCreateParams(TASK_ID, "test,", new Rect(0, 0, 200, 200));
+
+ private SplitController.ActivityStartMonitor mMonitor;
+
+ private Intent mIntent;
+
+ private TaskFragmentContainer mOverlayContainer1;
+
+ private TaskFragmentContainer mOverlayContainer2;
+
+ private Activity mActivity;
+ @Mock
+ private Resources mActivityResources;
+
+ @Mock
+ private WindowContainerTransaction mTransaction;
+ @Mock
+ private Handler mHandler;
+ @Mock
+ private WindowLayoutComponentImpl mWindowLayoutComponent;
+
+ private SplitController mSplitController;
+ private SplitPresenter mSplitPresenter;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
+ .getCurrentWindowLayoutInfo(anyInt(), any());
+ DeviceStateManagerFoldingFeatureProducer producer =
+ mock(DeviceStateManagerFoldingFeatureProducer.class);
+ mSplitController = new SplitController(mWindowLayoutComponent, producer);
+ mSplitPresenter = mSplitController.mPresenter;
+ mMonitor = mSplitController.getActivityStartMonitor();
+ mIntent = new Intent();
+
+ spyOn(mSplitController);
+ spyOn(mSplitPresenter);
+ spyOn(mMonitor);
+
+ doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
+ final Configuration activityConfig = new Configuration();
+ activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
+ activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS);
+ doReturn(activityConfig).when(mActivityResources).getConfiguration();
+ doReturn(mHandler).when(mSplitController).getHandler();
+ mActivity = createMockActivity();
+
+ mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
+ }
+
+ /** Creates a mock activity in the organizer process. */
+ @NonNull
+ private Activity createMockActivity() {
+ final Activity activity = mock(Activity.class);
+ doReturn(mActivityResources).when(activity).getResources();
+ final IBinder activityToken = new Binder();
+ doReturn(activityToken).when(activity).getActivityToken();
+ doReturn(activity).when(mSplitController).getActivity(activityToken);
+ doReturn(TASK_ID).when(activity).getTaskId();
+ doReturn(new ActivityInfo()).when(activity).getActivityInfo();
+ doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
+ return activity;
+ }
+
+ @Test
+ public void testOverlayCreateParamsFromBundle() {
+ assertThat(OverlayCreateParams.fromBundle(new Bundle())).isNull();
+
+ assertThat(OverlayCreateParams.fromBundle(createOverlayCreateParamsTestBundle()))
+ .isEqualTo(TEST_OVERLAY_CREATE_PARAMS);
+ }
+
+ @Test
+ public void testStartActivity_overlayFeatureDisabled_notInvokeCreateOverlayContainer() {
+ mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
+
+ mMonitor.onStartActivity(mActivity, mIntent, createOverlayCreateParamsTestBundle());
+
+ verify(mSplitController, never()).createOrUpdateOverlayTaskFragmentIfNeeded(any(), any(),
+ anyInt(), any(), any());
+ }
+
+ @NonNull
+ private static Bundle createOverlayCreateParamsTestBundle() {
+ final Bundle bundle = new Bundle();
+
+ final Bundle paramsBundle = new Bundle();
+ paramsBundle.putInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID,
+ TEST_OVERLAY_CREATE_PARAMS.getTaskId());
+ paramsBundle.putString(KEY_OVERLAY_CREATE_PARAMS_TAG, TEST_OVERLAY_CREATE_PARAMS.getTag());
+ paramsBundle.putObject(KEY_OVERLAY_CREATE_PARAMS_BOUNDS,
+ TEST_OVERLAY_CREATE_PARAMS.getBounds());
+
+ bundle.putBundle(KEY_OVERLAY_CREATE_PARAMS, paramsBundle);
+
+ return bundle;
+ }
+
+ @Test
+ public void testGetOverlayContainers() {
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers()).isEmpty();
+
+ final TaskFragmentContainer overlayContainer1 =
+ createTestOverlayContainer(TASK_ID, "test1");
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer1);
+
+ assertThrows(
+ "The exception must throw if there are two overlay containers in the same task.",
+ IllegalStateException.class,
+ () -> createTestOverlayContainer(TASK_ID, "test2"));
+
+ final TaskFragmentContainer overlayContainer3 =
+ createTestOverlayContainer(TASK_ID + 1, "test3");
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer1, overlayContainer3);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_taskIdNotMatch_throwException() {
+ assertThrows("The method must return null due to task mismatch between"
+ + " launchingActivity and OverlayCreateParams", IllegalArgumentException.class,
+ () -> createOrUpdateOverlayTaskFragmentIfNeeded(
+ TEST_OVERLAY_CREATE_PARAMS, TASK_ID + 1));
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_anotherTagInTask_dismissOverlay() {
+ createExistingOverlayContainers();
+
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ new OverlayCreateParams(TASK_ID, "test3", new Rect(0, 0, 100, 100)), TASK_ID);
+
+ assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+ + " is launched to the same task")
+ .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(mOverlayContainer2, overlayContainer);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAnotherTask_dismissOverlay() {
+ createExistingOverlayContainers();
+
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ new OverlayCreateParams(TASK_ID + 2, "test1", new Rect(0, 0, 100, 100)),
+ TASK_ID + 2);
+
+ assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+ + " is launched with the same tag as an existing overlay container in a different "
+ + "task")
+ .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(mOverlayContainer2, overlayContainer);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAndTask_updateOverlay() {
+ createExistingOverlayContainers();
+
+ final Rect bounds = new Rect(0, 0, 100, 100);
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ new OverlayCreateParams(TASK_ID, "test1", bounds),
+ TASK_ID);
+
+ assertWithMessage("overlayContainer1 must be updated since the new overlay container"
+ + " is launched with the same tag and task")
+ .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(mOverlayContainer1, mOverlayContainer2);
+
+ assertThat(overlayContainer).isEqualTo(mOverlayContainer1);
+ verify(mSplitPresenter).resizeTaskFragment(eq(mTransaction),
+ eq(mOverlayContainer1.getTaskFragmentToken()), eq(bounds));
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_dismissMultipleOverlays() {
+ createExistingOverlayContainers();
+
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ new OverlayCreateParams(TASK_ID, "test2", new Rect(0, 0, 100, 100)),
+ TASK_ID);
+
+ // OverlayContainer1 is dismissed since new container is launched in the same task with
+ // different tag. OverlayContainer2 is dismissed since new container is launched with the
+ // same tag in different task.
+ assertWithMessage("overlayContainer1 and overlayContainer2 must be dismissed")
+ .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ }
+
+ private void createExistingOverlayContainers() {
+ mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1");
+ mOverlayContainer2 = createTestOverlayContainer(TASK_ID + 1, "test2");
+ List<TaskFragmentContainer> overlayContainers = mSplitController
+ .getAllOverlayTaskFragmentContainers();
+ assertThat(overlayContainers).containsExactly(mOverlayContainer1, mOverlayContainer2);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_smallerThanMinDimens_expandOverlay() {
+ mIntent.setComponent(new ComponentName(ApplicationProvider.getApplicationContext(),
+ MinimumDimensionActivity.class));
+
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+ final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+ false);
+
+ // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
+ clearInvocations(mSplitPresenter);
+ createOrUpdateOverlayTaskFragmentIfNeeded(TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+
+ verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+ false);
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_notInTaskBounds_expandOverlay() {
+ final Rect bounds = new Rect(TASK_BOUNDS);
+ bounds.offset(10, 10);
+ final OverlayCreateParams paramsOutsideTaskBounds = new OverlayCreateParams(TASK_ID,
+ "test", bounds);
+
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ paramsOutsideTaskBounds, TASK_ID);
+ final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+ false);
+
+ // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
+ clearInvocations(mSplitPresenter);
+ createOrUpdateOverlayTaskFragmentIfNeeded(paramsOutsideTaskBounds, TASK_ID);
+
+ verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+ false);
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_createOverlay() {
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ assertThat(overlayContainer.getTaskId()).isEqualTo(TASK_ID);
+ assertThat(overlayContainer
+ .areLastRequestedBoundsEqual(TEST_OVERLAY_CREATE_PARAMS.getBounds())).isTrue();
+ assertThat(overlayContainer.getOverlayTag()).isEqualTo(TEST_OVERLAY_CREATE_PARAMS.getTag());
+ }
+
+ /**
+ * A simplified version of {@link SplitController.ActivityStartMonitor
+ * #createOrUpdateOverlayTaskFragmentIfNeeded}
+ */
+ @Nullable
+ private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
+ @NonNull OverlayCreateParams params, int taskId) {
+ return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction, params,
+ taskId, mIntent, mActivity);
+ }
+
+ @NonNull
+ private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) {
+ TaskFragmentContainer overlayContainer = mSplitController.newContainer(
+ null /* pendingAppearedActivity */, mIntent, mActivity, taskId,
+ null /* pairedPrimaryContainer */, tag);
+ setupTaskFragmentInfo(overlayContainer, mActivity);
+ return overlayContainer;
+ }
+
+ private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container,
+ @NonNull Activity activity) {
+ final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity);
+ container.setInfo(mTransaction, info);
+ mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info);
+ }
+}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index d440a3e..6c0b3cf 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -60,6 +60,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.clearInvocations;
@@ -634,7 +635,8 @@
false /* isOnReparent */);
assertFalse(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+ anyString());
}
@Test
@@ -796,7 +798,8 @@
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+ anyString());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
@@ -838,7 +841,8 @@
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+ anyString());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index 738c94e..79f306e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -19,6 +19,7 @@
import static android.view.View.LAYOUT_DIRECTION_RTL;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.bubbles.animation.FlingToDismissUtils.getFlingToDismissTargetWidth;
import android.content.res.Resources;
import android.graphics.Path;
@@ -375,6 +376,9 @@
mMagnetizedBubbleDraggingOut.setMagnetListener(listener);
mMagnetizedBubbleDraggingOut.setHapticsEnabled(true);
mMagnetizedBubbleDraggingOut.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
+ int screenWidthPx = mLayout.getContext().getResources().getDisplayMetrics().widthPixels;
+ mMagnetizedBubbleDraggingOut.setFlingToTargetWidthPercent(
+ getFlingToDismissTargetWidth(screenWidthPx));
}
private void springBubbleTo(View bubble, float x, float y) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt
new file mode 100644
index 0000000..2a44f04
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.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.
+ */
+
+package com.android.wm.shell.bubbles.animation
+
+/** Utils related to the fling to dismiss animation. */
+object FlingToDismissUtils {
+
+ /** The target width surrounding the dismiss target on a small width screen, e.g. phone. */
+ private const val FLING_TO_DISMISS_TARGET_WIDTH_SMALL = 3f
+ /**
+ * The target width surrounding the dismiss target on a medium width screen, e.g. tablet in
+ * portrait.
+ */
+ private const val FLING_TO_DISMISS_TARGET_WIDTH_MEDIUM = 4.5f
+ /**
+ * The target width surrounding the dismiss target on a large width screen, e.g. tablet in
+ * landscape.
+ */
+ private const val FLING_TO_DISMISS_TARGET_WIDTH_LARGE = 6f
+
+ /** Returns the dismiss target width for the specified [screenWidthPx]. */
+ @JvmStatic
+ fun getFlingToDismissTargetWidth(screenWidthPx: Int) = when {
+ screenWidthPx >= 2000 -> FLING_TO_DISMISS_TARGET_WIDTH_LARGE
+ screenWidthPx >= 1500 -> FLING_TO_DISMISS_TARGET_WIDTH_MEDIUM
+ else -> FLING_TO_DISMISS_TARGET_WIDTH_SMALL
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index aad2683..e487328 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.bubbles.animation;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.bubbles.animation.FlingToDismissUtils.getFlingToDismissTargetWidth;
import android.content.ContentResolver;
import android.content.res.Resources;
@@ -851,6 +852,15 @@
if (mLayout != null) {
Resources res = mLayout.getContext().getResources();
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+ updateFlingToDismissTargetWidth();
+ }
+ }
+
+ private void updateFlingToDismissTargetWidth() {
+ if (mLayout != null && mMagnetizedStack != null) {
+ int screenWidthPx = mLayout.getResources().getDisplayMetrics().widthPixels;
+ mMagnetizedStack.setFlingToTargetWidthPercent(
+ getFlingToDismissTargetWidth(screenWidthPx));
}
}
@@ -1022,23 +1032,8 @@
};
mMagnetizedStack.setHapticsEnabled(true);
mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
+ updateFlingToDismissTargetWidth();
}
-
- final ContentResolver contentResolver = mLayout.getContext().getContentResolver();
- final float minVelocity = Settings.Secure.getFloat(contentResolver,
- "bubble_dismiss_fling_min_velocity",
- mMagnetizedStack.getFlingToTargetMinVelocity() /* default */);
- final float maxVelocity = Settings.Secure.getFloat(contentResolver,
- "bubble_dismiss_stick_max_velocity",
- mMagnetizedStack.getStickToTargetMaxXVelocity() /* default */);
- final float targetWidth = Settings.Secure.getFloat(contentResolver,
- "bubble_dismiss_target_width_percent",
- mMagnetizedStack.getFlingToTargetWidthPercent() /* default */);
-
- mMagnetizedStack.setFlingToTargetMinVelocity(minVelocity);
- mMagnetizedStack.setStickToTargetMaxXVelocity(maxVelocity);
- mMagnetizedStack.setFlingToTargetWidthPercent(targetWidth);
-
return mMagnetizedStack;
}
@@ -1053,7 +1048,7 @@
* property directly to move the first bubble and cause the stack to 'follow' to the new
* location.
*
- * This could also be achieved by simply animating the first bubble view and adding an update
+ * <p>This could also be achieved by simply animating the first bubble view and adding an update
* listener to dispatch movement to the rest of the stack. However, this would require
* duplication of logic in that update handler - it's simpler to keep all logic contained in the
* {@link #moveFirstBubbleWithStackFollowing} method.
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index d0e860c..4c8f02d 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -193,4 +193,17 @@
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
oneway void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource);
+
+ /**
+ * Notifies system server that the permission request was displayed.
+ *
+ * <p>Only used for emitting atoms.
+ *
+ * @param hostUid The uid of the process requesting consent to capture, may be an app or
+ * SystemUI.
+ */
+ @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
+ oneway void notifyPermissionRequestDisplayed(int hostUid);
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 211af90..0567528 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -31,6 +31,13 @@
}
flag {
+ name: "notification_async_hybrid_view_inflation"
+ namespace: "systemui"
+ description: "Inflates the hybrid (single-line) notification views form the background thread."
+ bug: "217799515"
+}
+
+flag {
name: "scene_container"
namespace: "systemui"
description: "Enables the scene container framework go/flexiglass."
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 6c4b695..af35ea4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -68,6 +68,7 @@
override var launchContainer = ghostedView.rootView as ViewGroup
private val launchContainerOverlay: ViewGroupOverlay
get() = launchContainer.overlay
+
private val launchContainerLocation = IntArray(2)
/** The ghost view that is drawn and animated instead of the ghosted view. */
@@ -206,9 +207,8 @@
return
}
- backgroundView = FrameLayout(launchContainer.context).also {
- launchContainerOverlay.add(it)
- }
+ backgroundView =
+ FrameLayout(launchContainer.context).also { launchContainerOverlay.add(it) }
// We wrap the ghosted view background and use it to draw the expandable background. Its
// alpha will be set to 0 as soon as we start drawing the expanding background.
@@ -226,6 +226,17 @@
// the content before fading out the background.
ghostView = GhostView.addGhost(ghostedView, launchContainer)
+ // [GhostView.addGhost], the result of which is our [ghostView], creates a [GhostView], and
+ // adds it first to a [FrameLayout] container. It then adds _that_ container to an
+ // [OverlayViewGroup]. We need to turn off clipping for that container view. Currently,
+ // however, the only way to get a reference to that overlay is by going through our
+ // [ghostView]. The [OverlayViewGroup] will always be its grandparent view.
+ // TODO(b/306652954) reference the overlay view group directly if we can
+ (ghostView?.parent?.parent as? ViewGroup)?.let {
+ it.clipChildren = false
+ it.clipToPadding = false
+ }
+
val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
matrix.getValues(initialGhostViewMatrixValues)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
index a9d2ee3..403c7c5 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
@@ -19,7 +19,6 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.view.View;
-import android.widget.ImageView;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.plugins.annotations.DependsOn;
@@ -51,21 +50,11 @@
void addDarkReceiver(DarkReceiver receiver);
/**
- * Adds a receiver to receive callbacks onDarkChanged
- */
- void addDarkReceiver(ImageView imageView);
-
- /**
* Must have been previously been added through one of the addDarkReceive methods above.
*/
void removeDarkReceiver(DarkReceiver object);
/**
- * Must have been previously been added through one of the addDarkReceive methods above.
- */
- void removeDarkReceiver(ImageView object);
-
- /**
* Used to reapply darkness on an object, must have previously been added through
* addDarkReceiver.
*/
@@ -104,8 +93,8 @@
}
/**
- * @return true if more than half of the view area are in any of the given
- * areas, false otherwise
+ * @return true if more than half of the view's area is in any of the given area Rects, false
+ * otherwise
*/
static boolean isInAreas(Collection<Rect> areas, View view) {
if (areas.isEmpty()) {
@@ -120,9 +109,40 @@
}
/**
- * @return true if more than half of the view area are in area, false
+ * @return true if more than half of the viewBounds are in any of the given area Rects, false
* otherwise
*/
+ static boolean isInAreas(Collection<Rect> areas, Rect viewBounds) {
+ if (areas.isEmpty()) {
+ return true;
+ }
+ for (Rect area : areas) {
+ if (isInArea(area, viewBounds)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** @return true if more than half of the viewBounds are in the area Rect, false otherwise */
+ static boolean isInArea(Rect area, Rect viewBounds) {
+ if (area.isEmpty()) {
+ return true;
+ }
+ sTmpRect.set(area);
+ int left = viewBounds.left;
+ int width = viewBounds.width();
+
+ int intersectStart = Math.max(left, area.left);
+ int intersectEnd = Math.min(left + width, area.right);
+ int intersectAmount = Math.max(0, intersectEnd - intersectStart);
+
+ boolean coversFullStatusBar = area.top <= 0;
+ boolean majorityOfWidth = 2 * intersectAmount > width;
+ return majorityOfWidth && coversFullStatusBar;
+ }
+
+ /** @return true if more than half of the view's area is in the area Rect, false otherwise */
static boolean isInArea(Rect area, View view) {
if (area.isEmpty()) {
return true;
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 5a70b79..452bc31 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -66,7 +66,7 @@
<FrameLayout
android:id="@+id/status_bar_start_side_content"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:layout_gravity="center_vertical|start"
android:clipChildren="false">
@@ -77,7 +77,7 @@
and DISABLE_NOTIFICATION_ICONS, respectively -->
<LinearLayout
android:id="@+id/status_bar_start_side_except_heads_up"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:layout_width="match_parent"
android:layout_gravity="center_vertical|start"
android:clipChildren="false">
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 0a2bd63..7e03bd9 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -45,7 +45,8 @@
<com.android.systemui.statusbar.LightRevealScrim
android:id="@+id/light_reveal_scrim"
android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:layout_height="match_parent"
+ sysui:ignoreRightInset="true" />
<include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7a6d29a..9e2ebf6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3224,6 +3224,9 @@
<!--- Label of the dismiss button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
<string name="dismiss_dialog">Dismiss</string>
+ <!--- Content description of the connected display status bar icon that appears every time a display is connected [CHAR LIMIT=NONE]-->
+ <string name="connected_display_icon_desc">Display connected</string>
+
<!-- Title of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=30] -->
<string name="privacy_dialog_title">Microphone & Camera</string>
<!-- Subtitle of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java
index fd84543..494efb7 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java
@@ -25,21 +25,24 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.os.UserHandle;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.UserSwitcherController;
-import javax.inject.Inject;
-
+import dagger.Lazy;
import dagger.assisted.Assisted;
import dagger.assisted.AssistedFactory;
import dagger.assisted.AssistedInject;
+import javax.inject.Inject;
+
/**
* Manages handling of guest session persistent notification
* and actions to reset guest or exit guest session
@@ -70,14 +73,14 @@
public AlertDialog mResetSessionDialog;
private final UserTracker mUserTracker;
private final BroadcastDispatcher mBroadcastDispatcher;
- private final ResetSessionDialog.Factory mResetSessionDialogFactory;
- private final ExitSessionDialog.Factory mExitSessionDialogFactory;
+ private final ResetSessionDialogFactory mResetSessionDialogFactory;
+ private final ExitSessionDialogFactory mExitSessionDialogFactory;
@Inject
public GuestResetOrExitSessionReceiver(UserTracker userTracker,
BroadcastDispatcher broadcastDispatcher,
- ResetSessionDialog.Factory resetSessionDialogFactory,
- ExitSessionDialog.Factory exitSessionDialogFactory) {
+ ResetSessionDialogFactory resetSessionDialogFactory,
+ ExitSessionDialogFactory exitSessionDialogFactory) {
mUserTracker = userTracker;
mBroadcastDispatcher = broadcastDispatcher;
mResetSessionDialogFactory = resetSessionDialogFactory;
@@ -111,8 +114,8 @@
mResetSessionDialog = mResetSessionDialogFactory.create(currentUser.id);
mResetSessionDialog.show();
} else if (ACTION_GUEST_EXIT.equals(action)) {
- mExitSessionDialog = mExitSessionDialogFactory.create(currentUser.id,
- currentUser.isEphemeral());
+ mExitSessionDialog = mExitSessionDialogFactory.create(
+ currentUser.isEphemeral(), currentUser.id);
mExitSessionDialog.show();
}
}
@@ -132,43 +135,69 @@
}
/**
+ * Factory class to create guest reset dialog instance
+ *
* Dialog shown when asking for confirmation before
* reset and restart of guest user.
*/
- public static final class ResetSessionDialog extends SystemUIDialog implements
- DialogInterface.OnClickListener {
+ public static final class ResetSessionDialogFactory {
+ private final Lazy<SystemUIDialog> mDialogLazy;
+ private final Resources mResources;
+ private final ResetSessionDialogClickListener.Factory mClickListenerFactory;
+ @Inject
+ public ResetSessionDialogFactory(
+ Lazy<SystemUIDialog> dialogLazy,
+ @Main Resources resources,
+ ResetSessionDialogClickListener.Factory clickListenerFactory) {
+ mDialogLazy = dialogLazy;
+ mResources = resources;
+ mClickListenerFactory = clickListenerFactory;
+ }
+
+ /** Create a guest reset dialog instance */
+ public AlertDialog create(int userId) {
+ SystemUIDialog dialog = mDialogLazy.get();
+ ResetSessionDialogClickListener listener = mClickListenerFactory.create(
+ userId, dialog);
+ dialog.setTitle(com.android.settingslib.R.string.guest_reset_and_restart_dialog_title);
+ dialog.setMessage(mResources.getString(
+ com.android.settingslib.R.string.guest_reset_and_restart_dialog_message));
+ dialog.setButton(
+ DialogInterface.BUTTON_NEUTRAL,
+ mResources.getString(android.R.string.cancel),
+ listener);
+ dialog.setButton(DialogInterface.BUTTON_POSITIVE,
+ mResources.getString(
+ com.android.settingslib.R.string.guest_reset_guest_confirm_button),
+ listener);
+ dialog.setCanceledOnTouchOutside(false);
+ return dialog;
+ }
+ }
+
+ public static class ResetSessionDialogClickListener implements DialogInterface.OnClickListener {
private final UserSwitcherController mUserSwitcherController;
private final UiEventLogger mUiEventLogger;
private final int mUserId;
+ private final DialogInterface mDialog;
- /** Factory class to create guest reset dialog instance */
@AssistedFactory
public interface Factory {
- /** Create a guest reset dialog instance */
- ResetSessionDialog create(int userId);
+ ResetSessionDialogClickListener create(int userId, DialogInterface dialog);
}
@AssistedInject
- ResetSessionDialog(Context context,
+ public ResetSessionDialogClickListener(
UserSwitcherController userSwitcherController,
UiEventLogger uiEventLogger,
- @Assisted int userId) {
- super(context);
-
- setTitle(com.android.settingslib.R.string.guest_reset_and_restart_dialog_title);
- setMessage(context.getString(
- com.android.settingslib.R.string.guest_reset_and_restart_dialog_message));
- setButton(DialogInterface.BUTTON_NEUTRAL,
- context.getString(android.R.string.cancel), this);
- setButton(DialogInterface.BUTTON_POSITIVE,
- context.getString(
- com.android.settingslib.R.string.guest_reset_guest_confirm_button), this);
- setCanceledOnTouchOutside(false);
-
+ @Assisted int userId,
+ @Assisted DialogInterface dialog
+ ) {
mUserSwitcherController = userSwitcherController;
mUiEventLogger = uiEventLogger;
mUserId = userId;
+ mDialog = dialog;
}
@Override
@@ -177,7 +206,7 @@
mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
mUserSwitcherController.removeGuestUser(mUserId, UserHandle.USER_NULL);
} else if (which == DialogInterface.BUTTON_NEUTRAL) {
- cancel();
+ mDialog.cancel();
}
}
}
@@ -186,58 +215,93 @@
* Dialog shown when asking for confirmation before
* exit of guest user.
*/
- public static final class ExitSessionDialog extends SystemUIDialog implements
- DialogInterface.OnClickListener {
+ public static final class ExitSessionDialogFactory {
+ private final Lazy<SystemUIDialog> mDialogLazy;
+ private final ExitSessionDialogClickListener.Factory mClickListenerFactory;
+ private final Resources mResources;
+ @Inject
+ public ExitSessionDialogFactory(
+ Lazy<SystemUIDialog> dialogLazy,
+ ExitSessionDialogClickListener.Factory clickListenerFactory,
+ @Main Resources resources) {
+ mDialogLazy = dialogLazy;
+ mClickListenerFactory = clickListenerFactory;
+ mResources = resources;
+ }
+
+ public AlertDialog create(boolean isEphemeral, int userId) {
+ SystemUIDialog dialog = mDialogLazy.get();
+ ExitSessionDialogClickListener clickListener = mClickListenerFactory.create(
+ isEphemeral, userId, dialog);
+ if (isEphemeral) {
+ dialog.setTitle(mResources.getString(
+ com.android.settingslib.R.string.guest_exit_dialog_title));
+ dialog.setMessage(mResources.getString(
+ com.android.settingslib.R.string.guest_exit_dialog_message));
+ dialog.setButton(
+ DialogInterface.BUTTON_NEUTRAL,
+ mResources.getString(android.R.string.cancel),
+ clickListener);
+ dialog.setButton(
+ DialogInterface.BUTTON_POSITIVE,
+ mResources.getString(
+ com.android.settingslib.R.string.guest_exit_dialog_button),
+ clickListener);
+ } else {
+ dialog.setTitle(mResources.getString(
+ com.android.settingslib
+ .R.string.guest_exit_dialog_title_non_ephemeral));
+ dialog.setMessage(mResources.getString(
+ com.android.settingslib
+ .R.string.guest_exit_dialog_message_non_ephemeral));
+ dialog.setButton(
+ DialogInterface.BUTTON_NEUTRAL,
+ mResources.getString(android.R.string.cancel),
+ clickListener);
+ dialog.setButton(
+ DialogInterface.BUTTON_NEGATIVE,
+ mResources.getString(
+ com.android.settingslib.R.string.guest_exit_clear_data_button),
+ clickListener);
+ dialog.setButton(
+ DialogInterface.BUTTON_POSITIVE,
+ mResources.getString(
+ com.android.settingslib.R.string.guest_exit_save_data_button),
+ clickListener);
+ }
+ dialog.setCanceledOnTouchOutside(false);
+
+ return dialog;
+ }
+
+ }
+
+ public static class ExitSessionDialogClickListener implements DialogInterface.OnClickListener {
private final UserSwitcherController mUserSwitcherController;
+ private final boolean mIsEphemeral;
private final int mUserId;
- private boolean mIsEphemeral;
+ private final DialogInterface mDialog;
- /** Factory class to create guest exit dialog instance */
@AssistedFactory
public interface Factory {
- /** Create a guest exit dialog instance */
- ExitSessionDialog create(int userId, boolean isEphemeral);
+ ExitSessionDialogClickListener create(
+ boolean isEphemeral,
+ int userId,
+ DialogInterface dialog);
}
@AssistedInject
- ExitSessionDialog(Context context,
+ public ExitSessionDialogClickListener(
UserSwitcherController userSwitcherController,
+ @Assisted boolean isEphemeral,
@Assisted int userId,
- @Assisted boolean isEphemeral) {
- super(context);
-
- if (isEphemeral) {
- setTitle(context.getString(
- com.android.settingslib.R.string.guest_exit_dialog_title));
- setMessage(context.getString(
- com.android.settingslib.R.string.guest_exit_dialog_message));
- setButton(DialogInterface.BUTTON_NEUTRAL,
- context.getString(android.R.string.cancel), this);
- setButton(DialogInterface.BUTTON_POSITIVE,
- context.getString(
- com.android.settingslib.R.string.guest_exit_dialog_button), this);
- } else {
- setTitle(context.getString(
- com.android.settingslib
- .R.string.guest_exit_dialog_title_non_ephemeral));
- setMessage(context.getString(
- com.android.settingslib
- .R.string.guest_exit_dialog_message_non_ephemeral));
- setButton(DialogInterface.BUTTON_NEUTRAL,
- context.getString(android.R.string.cancel), this);
- setButton(DialogInterface.BUTTON_NEGATIVE,
- context.getString(
- com.android.settingslib.R.string.guest_exit_clear_data_button), this);
- setButton(DialogInterface.BUTTON_POSITIVE,
- context.getString(
- com.android.settingslib.R.string.guest_exit_save_data_button), this);
- }
- setCanceledOnTouchOutside(false);
-
+ @Assisted DialogInterface dialog
+ ) {
mUserSwitcherController = userSwitcherController;
- mUserId = userId;
mIsEphemeral = isEphemeral;
+ mUserId = userId;
+ mDialog = dialog;
}
@Override
@@ -249,7 +313,7 @@
mUserSwitcherController.exitGuestUser(mUserId, UserHandle.USER_NULL, false);
} else if (which == DialogInterface.BUTTON_NEUTRAL) {
// Cancel clicked, do nothing
- cancel();
+ mDialog.cancel();
}
} else {
if (which == DialogInterface.BUTTON_POSITIVE) {
@@ -261,7 +325,7 @@
mUserSwitcherController.exitGuestUser(mUserId, UserHandle.USER_NULL, true);
} else if (which == DialogInterface.BUTTON_NEUTRAL) {
// Cancel clicked, do nothing
- cancel();
+ mDialog.cancel();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index b573fad..0f5f869 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -27,6 +27,7 @@
import com.android.systemui.res.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.GuestResetOrExitSessionReceiver.ResetSessionDialogFactory;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
@@ -36,14 +37,14 @@
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.settings.SecureSettings;
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-
import dagger.assisted.Assisted;
import dagger.assisted.AssistedFactory;
import dagger.assisted.AssistedInject;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
/**
* Manages notification when a guest session is resumed.
*/
@@ -58,7 +59,7 @@
private final Executor mMainExecutor;
private final UserTracker mUserTracker;
private final SecureSettings mSecureSettings;
- private final ResetSessionDialog.Factory mResetSessionDialogFactory;
+ private final ResetSessionDialogFactory mResetSessionDialogFactory;
private final GuestSessionNotification mGuestSessionNotification;
@VisibleForTesting
@@ -104,7 +105,7 @@
UserTracker userTracker,
SecureSettings secureSettings,
GuestSessionNotification guestSessionNotification,
- ResetSessionDialog.Factory resetSessionDialogFactory) {
+ ResetSessionDialogFactory resetSessionDialogFactory) {
mMainExecutor = mainExecutor;
mUserTracker = userTracker;
mSecureSettings = secureSettings;
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
new file mode 100644
index 0000000..8d5b84f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
@@ -0,0 +1,60 @@
+/*
+ * 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
+
+import android.content.Context
+import androidx.annotation.AttrRes
+import androidx.annotation.ColorInt
+import androidx.annotation.DimenRes
+import com.android.settingslib.Utils
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged
+import com.android.systemui.statusbar.policy.onThemeChanged
+import com.android.systemui.util.kotlin.emitOnStart
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** Configuration-aware-state-tracking utilities. */
+class ConfigurationState
+@Inject
+constructor(
+ private val configurationController: ConfigurationController,
+ @Application private val context: Context,
+) {
+ /**
+ * Returns a [Flow] that emits a dimension pixel size that is kept in sync with the device
+ * configuration.
+ *
+ * @see android.content.res.Resources.getDimensionPixelSize
+ */
+ fun getDimensionPixelSize(@DimenRes id: Int): Flow<Int> {
+ return configurationController.onDensityOrFontScaleChanged.emitOnStart().map {
+ context.resources.getDimensionPixelSize(id)
+ }
+ }
+
+ /**
+ * Returns a [Flow] that emits a color that is kept in sync with the device theme.
+ *
+ * @see Utils.getColorAttrDefaultColor
+ */
+ fun getColorAttr(@AttrRes id: Int, @ColorInt defaultValue: Int): Flow<Int> {
+ return configurationController.onThemeChanged.emitOnStart().map {
+ Utils.getColorAttrDefaultColor(context, id, defaultValue)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/CommonUiDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/CommonUiDataLayerModule.kt
new file mode 100644
index 0000000..b0e6931
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/CommonUiDataLayerModule.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.data
+
+import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryModule
+import dagger.Module
+
+@Module(includes = [ConfigurationRepositoryModule::class]) object CommonUiDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
index b8de8d8..e449274 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -13,18 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
package com.android.systemui.common.ui.data.repository
import android.content.Context
import android.content.res.Configuration
import android.view.DisplayInfo
+import androidx.annotation.DimenRes
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.wrapper.DisplayUtilsWrapper
+import dagger.Binds
+import dagger.Module
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -48,7 +52,6 @@
fun getDimensionPixelSize(id: Int): Int
}
-@ExperimentalCoroutinesApi
@SysUISingleton
class ConfigurationRepositoryImpl
@Inject
@@ -119,7 +122,12 @@
return 1f
}
- override fun getDimensionPixelSize(id: Int): Int {
+ override fun getDimensionPixelSize(@DimenRes id: Int): Int {
return context.resources.getDimensionPixelSize(id)
}
}
+
+@Module
+interface ConfigurationRepositoryModule {
+ @Binds fun bindImpl(impl: ConfigurationRepositoryImpl): ConfigurationRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index 53b6879..485e512 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -1,5 +1,6 @@
package com.android.systemui.communal.data.repository
+import com.android.systemui.FeatureFlags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
@@ -15,10 +16,11 @@
class CommunalRepositoryImpl
@Inject
constructor(
- private val featureFlags: FeatureFlagsClassic,
+ private val featureFlags: FeatureFlags,
+ private val featureFlagsClassic: FeatureFlagsClassic,
) : CommunalRepository {
override val isCommunalEnabled: Boolean
get() =
- featureFlags.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) &&
- featureFlags.isEnabled(Flags.COMMUNAL_HUB)
+ featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) &&
+ featureFlags.communalHub()
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index f0d7592..04b2852d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -41,7 +41,7 @@
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.ui.data.repository.CommonRepositoryModule;
+import com.android.systemui.common.ui.data.CommonUiDataLayerModule;
import com.android.systemui.communal.dagger.CommunalModule;
import com.android.systemui.complication.dagger.ComplicationComponent;
import com.android.systemui.controls.dagger.ControlsModule;
@@ -170,7 +170,7 @@
BouncerViewModule.class,
ClipboardOverlayModule.class,
ClockRegistryModule.class,
- CommonRepositoryModule.class,
+ CommonUiDataLayerModule.class,
CommunalModule.class,
ConnectivityModule.class,
ControlsModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
index 7ccc26c..4a5cc64 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
@@ -16,7 +16,6 @@
package com.android.systemui.flags
-import android.util.Log
import com.android.systemui.Dependency
/**
@@ -65,8 +64,7 @@
* }
* ````
*/
- fun assertInLegacyMode() =
- check(!isEnabled) { "Legacy code path not supported when $flagName is enabled." }
+ fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, flagName)
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
@@ -81,13 +79,8 @@
* }
* ```
*/
- fun isUnexpectedlyInLegacyMode(): Boolean {
- if (!isEnabled) {
- val message = "New code path expects $flagName to be enabled."
- Log.wtf(TAG, message, Exception(message))
- }
- return !isEnabled
- }
+ fun isUnexpectedlyInLegacyMode(): Boolean =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, flagName)
companion object {
private const val TAG = "RefactorFlag"
diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt
new file mode 100644
index 0000000..2aa397f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt
@@ -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.systemui.flags
+
+import android.util.Log
+
+/**
+ * Utilities for writing your own objects to uphold refactor flag conventions.
+ *
+ * Example usage:
+ * ```
+ * object SomeRefactor {
+ * const val FLAG_NAME = Flags.SOME_REFACTOR
+ * @JvmStatic inline val isEnabled get() = Flags.someRefactor()
+ * @JvmStatic inline fun isUnexpectedlyInLegacyMode() =
+ * RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+ * @JvmStatic inline fun assertInLegacyMode() =
+ * RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+ * }
+ * ```
+ */
+@Suppress("NOTHING_TO_INLINE")
+object RefactorFlagUtils {
+ /**
+ * 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.
+ *
+ * Example usage:
+ * ```
+ * public void setNewController(SomeController someController) {
+ * if (SomeRefactor.isUnexpectedlyInLegacyMode()) return;
+ * mSomeController = someController;
+ * }
+ * ```
+ */
+ inline fun isUnexpectedlyInLegacyMode(isEnabled: Boolean, flagName: Any): Boolean {
+ val inLegacyMode = !isEnabled
+ if (inLegacyMode) {
+ val message = "New code path expects $flagName to be enabled."
+ Log.wtf("RefactorFlag", message, IllegalStateException(message))
+ }
+ return inLegacyMode
+ }
+
+ /**
+ * 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.
+ *
+ * Example usage:
+ * ```
+ * public void setSomeLegacyController(SomeController someController) {
+ * SomeRefactor.assertInLegacyMode();
+ * mSomeController = someController;
+ * }
+ * ````
+ */
+ inline fun assertInLegacyMode(isEnabled: Boolean, flagName: Any) =
+ check(!isEnabled) { "Legacy code path not supported when $flagName is enabled." }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
index d8cab6a..a382056 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
@@ -23,7 +23,6 @@
import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST as METRICS_CREATION_SOURCE_CAST
import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER as METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER
import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN as METRICS_CREATION_SOURCE_UNKNOWN
-import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED as METRICS_STATE_PERMISSION_REQUEST_DISPLAYED
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
@@ -53,8 +52,17 @@
}
}
- fun notifyPermissionRequestDisplayed() {
- notifyToServer(METRICS_STATE_PERMISSION_REQUEST_DISPLAYED, SessionCreationSource.UNKNOWN)
+ /**
+ * Request to log that the permission request was displayed.
+ *
+ * @param hostUid The UID of the package that initiates MediaProjection.
+ */
+ fun notifyPermissionRequestDisplayed(hostUid: Int) {
+ try {
+ service.notifyPermissionRequestDisplayed(hostUid)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Error notifying server of projection displayed", e)
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 922f7e0..72720f8 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -252,7 +252,7 @@
mDialog.show();
if (savedInstanceState == null) {
- mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed();
+ mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(mUid);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index e27a59c..f37f58d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -47,6 +47,7 @@
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -71,6 +72,7 @@
private final FeatureFlags mFlags;
private final PanelInteractor mPanelInteractor;
private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+ private final UserContextProvider mUserContextProvider;
private long mMillisUntilFinished = 0;
@@ -91,7 +93,8 @@
KeyguardStateController keyguardStateController,
DialogLaunchAnimator dialogLaunchAnimator,
PanelInteractor panelInteractor,
- MediaProjectionMetricsLogger mediaProjectionMetricsLogger
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger,
+ UserContextProvider userContextProvider
) {
super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
@@ -103,6 +106,7 @@
mDialogLaunchAnimator = dialogLaunchAnimator;
mPanelInteractor = panelInteractor;
mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
+ mUserContextProvider = userContextProvider;
}
@Override
@@ -195,7 +199,8 @@
dialog.show();
}
- mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed();
+ int uid = mUserContextProvider.getUserContext().getUserId();
+ mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(uid);
return false;
};
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 1ecb127..ead10d6 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -16,6 +16,7 @@
package com.android.systemui.settings.brightness;
+import static android.content.Intent.EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY;
@@ -83,7 +84,7 @@
private void setWindowAttributes() {
final Window window = getWindow();
- window.setGravity(Gravity.TOP | Gravity.LEFT);
+ window.setGravity(Gravity.TOP | Gravity.START);
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
window.requestFeature(Window.FEATURE_NO_TITLE);
@@ -130,13 +131,14 @@
Configuration configuration = getResources().getConfiguration();
int orientation = configuration.orientation;
+ int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
- lp.width = getWindowManager().getDefaultDisplay().getWidth() / 2
- - lp.leftMargin * 2;
+ boolean shouldBeFullWidth = getIntent()
+ .getBooleanExtra(EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH, false);
+ lp.width = (shouldBeFullWidth ? screenWidth : screenWidth / 2) - horizontalMargin * 2;
} else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
- lp.width = getWindowManager().getDefaultDisplay().getWidth()
- - lp.leftMargin * 2;
+ lp.width = screenWidth - horizontalMargin * 2;
}
frame.setLayoutParams(lp);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index d869239..3d3447b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -35,7 +35,6 @@
import com.android.keyguard.LockIconViewController;
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.Dumpable;
-import com.android.systemui.FeatureFlags;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
@@ -43,6 +42,7 @@
import com.android.systemui.bouncer.ui.binder.KeyguardBouncerViewBinder;
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.communal.data.repository.CommunalRepository;
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
import com.android.systemui.compose.ComposeFacade;
import com.android.systemui.dagger.SysUISingleton;
@@ -107,8 +107,8 @@
private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener;
private final NotificationInsetsController mNotificationInsetsController;
private final CommunalViewModel mCommunalViewModel;
+ private final CommunalRepository mCommunalRepository;
private final boolean mIsTrackpadCommonEnabled;
- private final FeatureFlags mFeatureFlags;
private final FeatureFlagsClassic mFeatureFlagsClassic;
private final SysUIKeyEventHandler mSysUIKeyEventHandler;
private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@@ -181,9 +181,9 @@
KeyguardTransitionInteractor keyguardTransitionInteractor,
PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
CommunalViewModel communalViewModel,
+ CommunalRepository communalRepository,
NotificationExpansionRepository notificationExpansionRepository,
FeatureFlagsClassic featureFlagsClassic,
- FeatureFlags featureFlags,
SystemClock clock,
BouncerMessageInteractor bouncerMessageInteractor,
BouncerLogger bouncerLogger,
@@ -214,8 +214,8 @@
mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener;
mNotificationInsetsController = notificationInsetsController;
mCommunalViewModel = communalViewModel;
+ mCommunalRepository = communalRepository;
mIsTrackpadCommonEnabled = featureFlagsClassic.isEnabled(TRACKPAD_GESTURE_COMMON);
- mFeatureFlags = featureFlags;
mFeatureFlagsClassic = featureFlagsClassic;
mSysUIKeyEventHandler = sysUIKeyEventHandler;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
@@ -575,7 +575,8 @@
* The layout lives in {@link R.id.communal_ui_container}.
*/
public void setupCommunalHubLayout() {
- if (!mFeatureFlags.communalHub() || !ComposeFacade.INSTANCE.isComposeAvailable()) {
+ if (!mCommunalRepository.isCommunalEnabled()
+ || !ComposeFacade.INSTANCE.isComposeAvailable()) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index e2de37f..22912df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -17,18 +17,13 @@
package com.android.systemui.statusbar.dagger
import com.android.systemui.CoreStartable
-import com.android.systemui.statusbar.core.StatusBarInitializer
-import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepository
-import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryImpl
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepository
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryImpl
+import com.android.systemui.statusbar.data.StatusBarDataLayerModule
import com.android.systemui.statusbar.phone.LightBarController
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
-import dagger.multibindings.IntoSet
/**
* A module for **only** classes related to the status bar **UI element**. This module specifically
@@ -38,24 +33,9 @@
* ([com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule],
* [com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule], etc.).
*/
-@Module
+@Module(includes = [StatusBarDataLayerModule::class])
abstract class StatusBarModule {
@Binds
- abstract fun bindStatusBarModeRepository(
- impl: StatusBarModeRepositoryImpl
- ): StatusBarModeRepository
-
- @Binds
- @IntoMap
- @ClassKey(StatusBarModeRepositoryImpl::class)
- abstract fun bindStatusBarModeRepositoryStart(impl: StatusBarModeRepositoryImpl): CoreStartable
-
- @Binds
- abstract fun bindKeyguardStatusBarRepository(
- impl: KeyguardStatusBarRepositoryImpl
- ): KeyguardStatusBarRepository
-
- @Binds
@IntoMap
@ClassKey(OngoingCallController::class)
abstract fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
@@ -64,10 +44,4 @@
@IntoMap
@ClassKey(LightBarController::class)
abstract fun bindLightBarController(impl: LightBarController): CoreStartable
-
- @Binds
- @IntoSet
- abstract fun statusBarInitializedListener(
- statusBarModeRepository: StatusBarModeRepository,
- ): StatusBarInitializer.OnStatusBarViewInitializedListener
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
new file mode 100644
index 0000000..29d53fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.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.data
+
+import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryModule
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryModule
+import com.android.systemui.statusbar.phone.data.StatusBarPhoneDataLayerModule
+import dagger.Module
+
+@Module(
+ includes =
+ [
+ KeyguardStatusBarRepositoryModule::class,
+ StatusBarModeRepositoryModule::class,
+ StatusBarPhoneDataLayerModule::class
+ ]
+)
+object StatusBarDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
index 8136de9..d1594ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
@@ -22,6 +22,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.user.data.repository.UserSwitcherRepository
+import dagger.Binds
+import dagger.Module
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
@@ -78,3 +80,8 @@
isEnabled && isKeyguardEnabled
}
}
+
+@Module
+interface KeyguardStatusBarRepositoryModule {
+ @Binds fun bindImpl(impl: KeyguardStatusBarRepositoryImpl): KeyguardStatusBarRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
index 2b05994..47994d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
@@ -30,7 +30,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.DisplayId
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.core.StatusBarInitializer
+import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
import com.android.systemui.statusbar.data.model.StatusBarAppearance
import com.android.systemui.statusbar.data.model.StatusBarMode
import com.android.systemui.statusbar.phone.BoundsPair
@@ -38,6 +38,11 @@
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -56,7 +61,7 @@
* Note: These status bar modes are status bar *window* states that are sent to us from
* WindowManager, not determined internally.
*/
-interface StatusBarModeRepository : StatusBarInitializer.OnStatusBarViewInitializedListener {
+interface StatusBarModeRepository {
/**
* True if the status bar window is showing transiently and will disappear soon, and false
* otherwise. ("Otherwise" in this case means the status bar is persistently hidden OR
@@ -112,7 +117,7 @@
private val commandQueue: CommandQueue,
private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
ongoingCallRepository: OngoingCallRepository,
-) : StatusBarModeRepository, CoreStartable {
+) : StatusBarModeRepository, CoreStartable, OnStatusBarViewInitializedListener {
private val commandQueueCallback =
object : CommandQueue.Callbacks {
@@ -334,3 +339,17 @@
val statusBarBounds: BoundsPair,
)
}
+
+@Module
+interface StatusBarModeRepositoryModule {
+ @Binds fun bindImpl(impl: StatusBarModeRepositoryImpl): StatusBarModeRepository
+
+ @Binds
+ @IntoMap
+ @ClassKey(StatusBarModeRepositoryImpl::class)
+ fun bindCoreStartable(impl: StatusBarModeRepositoryImpl): CoreStartable
+
+ @Binds
+ @IntoSet
+ fun bindViewInitListener(impl: StatusBarModeRepositoryImpl): OnStatusBarViewInitializedListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
index bde298d..ea1d782 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
@@ -36,6 +36,11 @@
val showAnimation: Boolean
val viewCreator: ViewCreator
var contentDescription: String?
+ /**
+ * When true, an accessibility event with [contentDescription] is announced when the view
+ * becomes visible.
+ */
+ val shouldAnnounceAccessibilityEvent: Boolean
// Update this event with values from another event.
fun updateFromEvent(other: StatusEvent?) {
@@ -76,6 +81,7 @@
override var forceVisible = false
override val showAnimation = true
override var contentDescription: String? = ""
+ override val shouldAnnounceAccessibilityEvent: Boolean = false
override val viewCreator: ViewCreator = { context ->
BatteryStatusChip(context).apply {
@@ -95,6 +101,7 @@
override var forceVisible = false
override val showAnimation = true
override var contentDescription: String? = ""
+ override val shouldAnnounceAccessibilityEvent: Boolean = true
override val viewCreator: ViewCreator = { context ->
ConnectedDisplayChip(context)
@@ -110,6 +117,7 @@
override var contentDescription: String? = null
override val priority = 100
override var forceVisible = true
+ override val shouldAnnounceAccessibilityEvent: Boolean = false
var privacyItems: List<PrivacyItem> = listOf()
private var privacyChip: OngoingPrivacyChip? = null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index dccc23f..73c0bfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -273,6 +273,11 @@
})
}
+ /** Announces [contentDescriptions] for accessibility. */
+ fun announceForAccessibility(contentDescriptions: String) {
+ currentAnimatedView?.view?.announceForAccessibility(contentDescriptions)
+ }
+
private fun updateDimens(contentArea: Rect) {
val lp = animationWindowView.layoutParams as FrameLayout.LayoutParams
lp.height = contentArea.height()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
index 7d866df..8ee1ade 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
@@ -100,9 +100,12 @@
}
private fun startConnectedDisplayCollection() {
+ val connectedDisplayEvent = ConnectedDisplayEvent().apply {
+ contentDescription = context.getString(R.string.connected_display_icon_desc)
+ }
connectedDisplayCollectionJob =
onDisplayConnectedFlow
- .onEach { scheduler.onStatusEvent(ConnectedDisplayEvent()) }
+ .onEach { scheduler.onStatusEvent(connectedDisplayEvent) }
.launchIn(appScope)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
index a3bc002..f0e60dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -254,11 +254,18 @@
currentlyRunningAnimationJob =
coroutineScope.launch {
runChipAppearAnimation()
+ announceForAccessibilityIfNeeded(event)
delay(APPEAR_ANIMATION_DURATION + DISPLAY_LENGTH)
runChipDisappearAnimation()
}
}
+ private fun announceForAccessibilityIfNeeded(event: StatusEvent) {
+ val description = event.contentDescription ?: return
+ if (!event.shouldAnnounceAccessibilityEvent) return
+ chipAnimationController.announceForAccessibility(description)
+ }
+
/**
* 1. Define a total budget for the chip animation (1500ms)
* 2. Send out callbacks to listeners so that they can generate animations locally
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 8957f29..f98f39e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -20,6 +20,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
+import android.util.Log;
import android.util.Property;
import android.view.View;
import android.view.animation.Interpolator;
@@ -33,6 +34,7 @@
* An animator to animate properties
*/
public class PropertyAnimator {
+ private static final String TAG = "PropertyAnimator";
/**
* Set a property on a view, updating its value, even if it's already animating.
@@ -123,6 +125,8 @@
view.setTag(animatorTag, null);
view.setTag(animationStartTag, null);
view.setTag(animationEndTag, null);
+ } else {
+ Log.e(TAG, "Unexpected Animator set during onAnimationEnd. Not cleaning up.");
}
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 2d83970..0c69a65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -37,8 +37,8 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationListInteractor
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.headsUpEvents
import com.android.systemui.util.asIndenting
@@ -85,7 +85,7 @@
@Application private val scope: CoroutineScope,
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
private val secureSettings: SecureSettings,
- private val notificationListInteractor: NotificationListInteractor,
+ private val seenNotificationsInteractor: SeenNotificationsInteractor,
private val statusBarStateController: StatusBarStateController,
) : Coordinator, Dumpable {
@@ -351,7 +351,7 @@
override fun onCleanup() {
logger.logProviderHasFilteredOutSeenNotifs(hasFilteredAnyNotifs)
- notificationListInteractor.setHasFilteredOutSeenNotifications(hasFilteredAnyNotifs)
+ seenNotificationsInteractor.setHasFilteredOutSeenNotifications(hasFilteredAnyNotifs)
hasFilteredAnyNotifs = false
}
}
@@ -389,7 +389,7 @@
with(pw.asIndenting()) {
println(
"notificationListInteractor.hasFilteredOutSeenNotifications.value=" +
- notificationListInteractor.hasFilteredOutSeenNotifications.value
+ seenNotificationsInteractor.hasFilteredOutSeenNotifications.value
)
println("unseen notifications:")
indentIfPossible {
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 62a0d13..c2a021d 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,25 +16,32 @@
package com.android.systemui.statusbar.notification.collection.coordinator
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
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.RenderNotificationListInteractor
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.util.traceSection
import javax.inject.Inject
/**
- * A small coordinator which updates the notif stack (the view layer which holds notifications)
- * with high-level data after the stack is populated with the final entries.
+ * A small coordinator which updates the notif stack (the view layer which holds notifications) with
+ * high-level data after the stack is populated with the final entries.
*/
@CoordinatorScope
-class StackCoordinator @Inject internal constructor(
+class StackCoordinator
+@Inject
+internal constructor(
+ private val featureFlags: FeatureFlagsClassic,
private val groupExpansionManagerImpl: GroupExpansionManagerImpl,
- private val notificationIconAreaController: NotificationIconAreaController
+ private val notificationIconAreaController: NotificationIconAreaController,
+ private val renderListInteractor: RenderNotificationListInteractor,
) : Coordinator {
override fun attach(pipeline: NotifPipeline) {
@@ -46,6 +53,9 @@
traceSection("StackCoordinator.onAfterRenderList") {
controller.setNotifStats(calculateNotifStats(entries))
notificationIconAreaController.updateNotificationIcons(entries)
+ if (featureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ renderListInteractor.setRenderedList(entries)
+ }
}
private fun calculateNotifStats(entries: List<ListEntry>): NotifStats {
@@ -75,4 +85,4 @@
hasClearableSilentNotifs = hasClearableSilentNotifs
)
}
-}
\ 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
new file mode 100644
index 0000000..8064f04
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/**
+ * Repository of "active" notifications in the notification list.
+ *
+ * This repository serves as the boundary between the
+ * [com.android.systemui.statusbar.notification.collection.NotifPipeline] and the modern
+ * notifications presentation codebase.
+ */
+@SysUISingleton
+class ActiveNotificationListRepository @Inject constructor() {
+ /**
+ * Notifications actively presented to the user in the notification stack.
+ *
+ * @see com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
+ */
+ val activeNotifications = MutableStateFlow(emptyMap<String, ActiveNotificationModel>())
+
+ /** Are any already-seen notifications currently filtered out of the active list? */
+ val hasFilteredOutSeenNotifications = MutableStateFlow(false)
+}
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
new file mode 100644
index 0000000..bfec60bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -0,0 +1,32 @@
+/*
+ * 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 com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class ActiveNotificationsInteractor
+@Inject
+constructor(
+ repository: ActiveNotificationListRepository,
+) {
+ /** Notifications actively presented to the user in the notification stack, in order. */
+ val notifications: Flow<Collection<ActiveNotificationModel>> =
+ repository.activeNotifications.map { it.values }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractor.kt
similarity index 82%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractor.kt
index 8f7e269..8079ce5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractor.kt
@@ -20,15 +20,14 @@
import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
import javax.inject.Inject
-/** Interactor for notifications in general. */
+/** Interactor for notification alerting. */
@SysUISingleton
-class NotificationsInteractor
+class NotificationAlertsInteractor
@Inject
constructor(
private val disableFlagsRepository: DisableFlagsRepository,
) {
/** Returns true if notification alerts are allowed. */
- fun areNotificationAlertsEnabled(): Boolean {
- return disableFlagsRepository.disableFlags.value.areNotificationAlertsEnabled()
- }
+ fun areNotificationAlertsEnabled(): Boolean =
+ disableFlagsRepository.disableFlags.value.areNotificationAlertsEnabled()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
new file mode 100644
index 0000000..c5396dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -0,0 +1,58 @@
+/*
+ * 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 com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.update
+
+/**
+ * Logic for passing information from the
+ * [com.android.systemui.statusbar.notification.collection.NotifPipeline] to the presentation
+ * layers.
+ */
+class RenderNotificationListInteractor
+@Inject
+constructor(
+ private val repository: ActiveNotificationListRepository,
+) {
+ /**
+ * Sets the current list of rendered notification entries as displayed in the notification
+ * stack.
+ *
+ * @see com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository.activeNotifications
+ */
+ fun setRenderedList(entries: List<ListEntry>) {
+ repository.activeNotifications.update { modelsByKey ->
+ entries.associateBy(
+ keySelector = { it.key },
+ valueTransform = { it.toModel(modelsByKey[it.key]) }
+ )
+ }
+ }
+
+ private fun ListEntry.toModel(existing: ActiveNotificationModel?): ActiveNotificationModel {
+ val isCurrent =
+ when {
+ existing == null -> false
+ key == existing.key -> true
+ else -> false
+ }
+ return if (isCurrent) existing!! else ActiveNotificationModel(key = key)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
similarity index 67%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationListInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
index 3fd68a8..f3e122c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
@@ -14,25 +14,25 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.domain.interactor
+package com.android.systemui.statusbar.notification.domain.interactor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.stack.data.repository.NotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow
/** Interactor for business logic associated with the notification stack. */
@SysUISingleton
-class NotificationListInteractor
+class SeenNotificationsInteractor
@Inject
constructor(
- private val notificationListRepository: NotificationListRepository,
+ private val notificationListRepository: ActiveNotificationListRepository,
) {
/** Are any already-seen notifications currently filtered out of the shade? */
- val hasFilteredOutSeenNotifications: StateFlow<Boolean>
- get() = notificationListRepository.hasFilteredOutSeenNotifications
+ val hasFilteredOutSeenNotifications: StateFlow<Boolean> =
+ notificationListRepository.hasFilteredOutSeenNotifications
fun setHasFilteredOutSeenNotifications(value: Boolean) {
- notificationListRepository.setHasFilteredOutSeenNotifications(value)
+ notificationListRepository.hasFilteredOutSeenNotifications.value = value
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt
new file mode 100644
index 0000000..94e70e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.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.systemui.statusbar.notification.footer.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the FooterView refactor flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object FooterViewRefactor {
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationsFooterViewRefactor()
+
+ /**
+ * 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/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
index eb5c1fa..de011db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
@@ -16,32 +16,27 @@
package com.android.systemui.statusbar.notification.icon.ui.viewbinder
import android.content.Context
-import android.graphics.Color
import android.graphics.Rect
import android.os.Bundle
import android.os.Trace
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
-import androidx.annotation.ColorInt
import androidx.annotation.VisibleForTesting
import androidx.collection.ArrayMap
import com.android.internal.statusbar.StatusBarIcon
-import com.android.internal.util.ContrastColorUtil
-import com.android.settingslib.Utils
+import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.flags.RefactorFlag
-import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.res.R
import com.android.systemui.statusbar.NotificationListener
import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.NotificationShelfController
import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.notification.NotificationUtils
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -55,7 +50,6 @@
import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
@@ -75,44 +69,34 @@
@Inject
constructor(
private val context: Context,
+ private val configuration: ConfigurationState,
private val wakeUpCoordinator: NotificationWakeUpCoordinator,
private val bypassController: KeyguardBypassController,
- private val configurationController: ConfigurationController,
private val mediaManager: NotificationMediaManager,
notificationListener: NotificationListener,
private val dozeParameters: DozeParameters,
private val sectionStyleProvider: SectionStyleProvider,
private val bubblesOptional: Optional<Bubbles>,
demoModeController: DemoModeController,
- darkIconDispatcher: DarkIconDispatcher,
private val featureFlags: FeatureFlagsClassic,
private val statusBarWindowController: StatusBarWindowController,
private val screenOffAnimationController: ScreenOffAnimationController,
private val shelfIconsViewModel: NotificationIconContainerShelfViewModel,
private val statusBarIconsViewModel: NotificationIconContainerStatusBarViewModel,
private val aodIconsViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
-) :
- NotificationIconAreaController,
- DarkIconDispatcher.DarkReceiver,
- NotificationWakeUpCoordinator.WakeUpListener,
- DemoMode {
+) : NotificationIconAreaController, NotificationWakeUpCoordinator.WakeUpListener, DemoMode {
- private val contrastColorUtil: ContrastColorUtil = ContrastColorUtil.getInstance(context)
private val updateStatusBarIcons = Runnable { updateStatusBarIcons() }
private val shelfRefactor = RefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR)
- private val tintAreas = ArrayList<Rect>()
private var iconSize = 0
private var iconHPadding = 0
- private var iconTint = Color.WHITE
private var notificationEntries = listOf<ListEntry>()
private var notificationIconArea: View? = null
private var notificationIcons: NotificationIconContainer? = null
private var shelfIcons: NotificationIconContainer? = null
private var aodIcons: NotificationIconContainer? = null
private var aodBindJob: DisposableHandle? = null
- private var aodIconAppearTranslation = 0
- private var aodIconTint = 0
private var showLowPriority = true
@VisibleForTesting
@@ -129,8 +113,6 @@
demoModeController.addCallback(this)
notificationListener.addNotificationSettingsListener(settingsListener)
initializeNotificationAreaViews(context)
- reloadAodColor()
- darkIconDispatcher.addDarkReceiver(this)
}
@VisibleForTesting
@@ -152,7 +134,7 @@
NotificationIconContainerViewBinder.bind(
aodIcons,
aodIconsViewModel,
- configurationController,
+ configuration,
dozeParameters,
featureFlags,
screenOffAnimationController,
@@ -167,16 +149,17 @@
NotificationShelfViewBinderWrapperControllerImpl.unsupported
override fun setShelfIcons(icons: NotificationIconContainer) {
- if (shelfRefactor.isUnexpectedlyInLegacyMode()) return
- NotificationIconContainerViewBinder.bind(
- icons,
- shelfIconsViewModel,
- configurationController,
- dozeParameters,
- featureFlags,
- screenOffAnimationController,
- )
- shelfIcons = icons
+ if (shelfRefactor.isUnexpectedlyInLegacyMode()) {
+ NotificationIconContainerViewBinder.bind(
+ icons,
+ shelfIconsViewModel,
+ configuration,
+ dozeParameters,
+ featureFlags,
+ screenOffAnimationController,
+ )
+ shelfIcons = icons
+ }
}
override fun onDensityOrFontScaleChanged(context: Context) {
@@ -188,22 +171,6 @@
return notificationIconArea
}
- /**
- * See [com.android.systemui.statusbar.policy.DarkIconDispatcher.setIconsDarkArea]. Sets the
- * color that should be used to tint any icons in the notification area.
- *
- * @param tintAreas the areas in which to tint the icons, specified in screen coordinates
- * @param darkIntensity
- */
- override fun onDarkChanged(tintAreas: ArrayList<Rect>, darkIntensity: Float, iconTint: Int) {
- this.tintAreas.clear()
- this.tintAreas.addAll(tintAreas)
- if (DarkIconDispatcher.isInAreas(tintAreas, notificationIconArea)) {
- this.iconTint = iconTint
- }
- applyNotificationIconsTint()
- }
-
/** Updates the notifications with the given list of notifications to display. */
override fun updateNotificationIcons(entries: List<ListEntry>) {
notificationEntries = entries
@@ -249,10 +216,7 @@
override fun setAnimationsEnabled(enabled: Boolean) = unsupported
- override fun onThemeChanged() {
- reloadAodColor()
- updateAodIconColors()
- }
+ override fun onThemeChanged() = unsupported
override fun getHeight(): Int {
return if (aodIcons == null) 0 else aodIcons!!.height
@@ -260,7 +224,6 @@
override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
updateAodNotificationIcons()
- updateAodIconColors()
}
override fun demoCommands(): List<String> {
@@ -296,7 +259,7 @@
NotificationIconContainerViewBinder.bind(
notificationIcons!!,
statusBarIconsViewModel,
- configurationController,
+ configuration,
dozeParameters,
featureFlags,
screenOffAnimationController,
@@ -335,7 +298,6 @@
val res = context.resources
iconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size_sp)
iconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin)
- aodIconAppearTranslation = res.getDimensionPixelSize(R.dimen.shelf_appear_translation)
}
private fun shouldShowNotificationIcon(
@@ -383,7 +345,6 @@
updateStatusBarIcons()
updateShelfIcons()
updateAodNotificationIcons()
- applyNotificationIconsTint()
Trace.endSection()
}
@@ -526,55 +487,7 @@
hostLayout.setReplacingIcons(null)
}
- /** Applies [.mIconTint] to the notification icons. */
- private fun applyNotificationIconsTint() {
- for (i in 0 until notificationIcons!!.childCount) {
- val iv = notificationIcons!!.getChildAt(i) as StatusBarIconView
- if (iv.width != 0) {
- updateTintForIcon(iv, iconTint)
- } else {
- iv.executeOnLayout { updateTintForIcon(iv, iconTint) }
- }
- }
- updateAodIconColors()
- }
-
- private fun updateTintForIcon(v: StatusBarIconView, tint: Int) {
- val isPreL = java.lang.Boolean.TRUE == v.getTag(R.id.icon_is_pre_L)
- var color = StatusBarIconView.NO_COLOR
- val colorize = !isPreL || NotificationUtils.isGrayscale(v, contrastColorUtil)
- if (colorize) {
- color = DarkIconDispatcher.getTint(tintAreas, v, tint)
- }
- v.staticDrawableColor = color
- v.setDecorColor(tint)
- }
-
- private fun reloadAodColor() {
- aodIconTint =
- Utils.getColorAttrDefaultColor(
- context,
- R.attr.wallpaperTextColor,
- DEFAULT_AOD_ICON_COLOR
- )
- }
-
- private fun updateAodIconColors() {
- if (aodIcons != null) {
- for (i in 0 until aodIcons!!.childCount) {
- val iv = aodIcons!!.getChildAt(i) as StatusBarIconView
- if (iv.width != 0) {
- updateTintForIcon(iv, aodIconTint)
- } else {
- iv.executeOnLayout { updateTintForIcon(iv, aodIconTint) }
- }
- }
- }
- }
-
companion object {
- @ColorInt private val DEFAULT_AOD_ICON_COLOR = -0x1
-
val unsupported: Nothing
get() =
error(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index 0d2f00a..079004c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -15,27 +15,30 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewbinder
-import android.content.res.Resources
+import android.graphics.Rect
import android.view.View
-import androidx.annotation.DimenRes
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
+import com.android.internal.util.ContrastColorUtil
+import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.statusbar.CrossFadeHelper
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.NotificationUtils
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconColors
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged
-import com.android.systemui.util.kotlin.stateFlow
-import kotlinx.coroutines.CoroutineScope
+import com.android.systemui.util.children
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/** Binds a [NotificationIconContainer] to its [view model][NotificationIconContainerViewModel]. */
@@ -43,11 +46,12 @@
fun bind(
view: NotificationIconContainer,
viewModel: NotificationIconContainerViewModel,
- configurationController: ConfigurationController,
+ configuration: ConfigurationState,
dozeParameters: DozeParameters,
featureFlags: FeatureFlagsClassic,
screenOffAnimationController: ScreenOffAnimationController,
): DisposableHandle {
+ val contrastColorUtil = ContrastColorUtil.getInstance(view.context)
return view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch { viewModel.animationsEnabled.collect(view::setAnimationsEnabled) }
@@ -59,15 +63,13 @@
}
}
}
- // TODO(278765923): this should live where AOD is bound, not inside of the NIC
+ // TODO(b/278765923): this should live where AOD is bound, not inside of the NIC
// view-binder
launch {
val iconAppearTranslation =
- view.resources.getConfigAwareDimensionPixelSize(
- this,
- configurationController,
- R.dimen.shelf_appear_translation,
- )
+ configuration
+ .getDimensionPixelSize(R.dimen.shelf_appear_translation)
+ .stateIn(this)
bindVisibility(
viewModel,
view,
@@ -78,9 +80,40 @@
viewModel.completeVisibilityAnimation()
}
}
+ launch {
+ viewModel.iconColors
+ .mapNotNull { lookup -> lookup.iconColors(view.viewBounds) }
+ .collect { iconLookup -> applyTint(view, iconLookup, contrastColorUtil) }
+ }
}
}
}
+
+ // TODO(b/305739416): Once SBIV has its own Recommended Architecture stack, this can be moved
+ // there and cleaned up.
+ private fun applyTint(
+ view: NotificationIconContainer,
+ iconColors: IconColors,
+ contrastColorUtil: ContrastColorUtil,
+ ) {
+ view.children.filterIsInstance<StatusBarIconView>().forEach { iv ->
+ if (iv.width != 0) {
+ updateTintForIcon(iv, iconColors, contrastColorUtil)
+ }
+ }
+ }
+
+ private fun updateTintForIcon(
+ v: StatusBarIconView,
+ iconColors: IconColors,
+ contrastColorUtil: ContrastColorUtil,
+ ) {
+ val isPreL = java.lang.Boolean.TRUE == v.getTag(R.id.icon_is_pre_L)
+ val isColorized = !isPreL || NotificationUtils.isGrayscale(v, contrastColorUtil)
+ v.staticDrawableColor = iconColors.staticDrawableColor(v.viewBounds, isColorized)
+ v.setDecorColor(iconColors.tint)
+ }
+
private suspend fun bindVisibility(
viewModel: NotificationIconContainerViewModel,
view: NotificationIconContainer,
@@ -173,14 +206,16 @@
}
private const val AOD_ICONS_APPEAR_DURATION: Long = 200
-}
-fun Resources.getConfigAwareDimensionPixelSize(
- scope: CoroutineScope,
- configurationController: ConfigurationController,
- @DimenRes id: Int,
-): StateFlow<Int> =
- scope.stateFlow(
- changedSignals = configurationController.onDensityOrFontScaleChanged,
- getValue = { getDimensionPixelSize(id) }
- )
+ private val View.viewBounds: Rect
+ get() {
+ val tmpArray = intArrayOf(0, 0)
+ getLocationOnScreen(tmpArray)
+ return Rect(
+ /* left = */ tmpArray[0],
+ /* top = */ tmpArray[1],
+ /* right = */ left + width,
+ /* bottom = */ top + height,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
index 3289a3c..e9de4bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
@@ -15,6 +15,10 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.graphics.Color
+import android.graphics.Rect
+import androidx.annotation.ColorInt
+import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.flags.FeatureFlagsClassic
@@ -23,8 +27,11 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.ColorLookup
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconColors
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.util.kotlin.pairwise
@@ -44,6 +51,7 @@
class NotificationIconContainerAlwaysOnDisplayViewModel
@Inject
constructor(
+ configuration: ConfigurationState,
private val deviceEntryInteractor: DeviceEntryInteractor,
private val dozeParameters: DozeParameters,
private val featureFlags: FeatureFlagsClassic,
@@ -57,6 +65,11 @@
private val onDozeAnimationComplete = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
private val onVisAnimationComplete = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+ override val iconColors: Flow<ColorLookup> =
+ configuration.getColorAttr(R.attr.wallpaperTextColor, DEFAULT_AOD_ICON_COLOR).map { tint ->
+ ColorLookup { IconColorsImpl(tint) }
+ }
+
override val animationsEnabled: Flow<Boolean> =
combine(
shadeInteractor.isShadeTouchable,
@@ -157,4 +170,12 @@
}
.toAnimatedValueFlow(completionEvents = onVisAnimationComplete)
}
+
+ private class IconColorsImpl(override val tint: Int) : IconColors {
+ override fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int = tint
+ }
+
+ companion object {
+ @ColorInt private val DEFAULT_AOD_ICON_COLOR = Color.WHITE
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
index c44a2b6..f305155 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.ColorLookup
import com.android.systemui.util.ui.AnimatedValue
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -29,4 +30,5 @@
override val isVisible: Flow<AnimatedValue<Boolean>> = emptyFlow()
override fun completeDozeAnimation() {}
override fun completeVisibilityAnimation() {}
+ override val iconColors: Flow<ColorLookup> = emptyFlow()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
index 035687a..ee01fcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
@@ -15,8 +15,14 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.graphics.Rect
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.ColorLookup
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconColors
+import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor
import com.android.systemui.util.ui.AnimatedValue
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -27,7 +33,9 @@
class NotificationIconContainerStatusBarViewModel
@Inject
constructor(
+ darkIconInteractor: DarkIconInteractor,
keyguardInteractor: KeyguardInteractor,
+ notificationsInteractor: ActiveNotificationsInteractor,
shadeInteractor: ShadeInteractor,
) : NotificationIconContainerViewModel {
override val animationsEnabled: Flow<Boolean> =
@@ -37,9 +45,36 @@
) { panelTouchesEnabled, isKeyguardShowing ->
panelTouchesEnabled && !isKeyguardShowing
}
-
+ override val iconColors: Flow<ColorLookup> =
+ combine(
+ darkIconInteractor.tintAreas,
+ darkIconInteractor.tintColor,
+ // Included so that tints are re-applied after entries are changed.
+ notificationsInteractor.notifications,
+ ) { areas, tint, _ ->
+ ColorLookup { viewBounds: Rect ->
+ if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
+ IconColorsImpl(tint, areas)
+ } else {
+ null
+ }
+ }
+ }
override val isDozing: Flow<AnimatedValue<Boolean>> = emptyFlow()
override val isVisible: Flow<AnimatedValue<Boolean>> = emptyFlow()
override fun completeDozeAnimation() {}
override fun completeVisibilityAnimation() {}
+
+ private class IconColorsImpl(
+ override val tint: Int,
+ private val areas: Collection<Rect>,
+ ) : IconColors {
+ override fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int {
+ return if (isColorized && DarkIconDispatcher.isInAreas(areas, viewBounds)) {
+ tint
+ } else {
+ DarkIconDispatcher.DEFAULT_ICON_TINT
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
index 65eb220..c98811b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.graphics.Rect
import com.android.systemui.util.ui.AnimatedValue
import kotlinx.coroutines.flow.Flow
@@ -23,6 +24,7 @@
* AOD.
*/
interface NotificationIconContainerViewModel {
+
/** Are changes to the icon container animated? */
val animationsEnabled: Flow<Boolean>
@@ -32,15 +34,39 @@
/** Is the icon container visible? */
val isVisible: Flow<AnimatedValue<Boolean>>
+ /** The colors with which to display the notification icons. */
+ val iconColors: Flow<ColorLookup>
+
/**
* Signal completion of the [isDozing] animation; if [isDozing]'s [AnimatedValue.isAnimating]
- * property was `true`, calling this method will update it to `false.
+ * property was `true`, calling this method will update it to `false`.
*/
fun completeDozeAnimation()
/**
* Signal completion of the [isVisible] animation; if [isVisible]'s [AnimatedValue.isAnimating]
- * property was `true`, calling this method will update it to `false.
+ * property was `true`, calling this method will update it to `false`.
*/
fun completeVisibilityAnimation()
+
+ /**
+ * Lookup the colors to use for the notification icons based on the bounds of the icon
+ * container. A result of `null` indicates that no color changes should be applied.
+ */
+ fun interface ColorLookup {
+ fun iconColors(viewBounds: Rect): IconColors?
+ }
+
+ /** Colors to apply to notification icons. */
+ interface IconColors {
+
+ /** A tint to apply to the icons. */
+ val tint: Int
+
+ /**
+ * Returns the color to be applied to an icon, based on that icon's view bounds and whether
+ * or not the notification icon is colorized.
+ */
+ fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
new file mode 100644
index 0000000..ea29cab
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.shared
+
+/** Model for entries in the notification stack. */
+data class ActiveNotificationModel(
+ /** Notification key associated with this entry. */
+ val key: String,
+)
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 6a70815..3354582 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
@@ -106,6 +106,7 @@
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.SeenNotificationsInteractor;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -114,7 +115,6 @@
import com.android.systemui.statusbar.notification.row.NotificationGuts;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationSnooze;
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationListInteractor;
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.HeadsUpAppearanceController;
@@ -194,7 +194,7 @@
private final GroupExpansionManager mGroupExpansionManager;
private final NotifPipelineFlags mNotifPipelineFlags;
- private final NotificationListInteractor mNotificationListInteractor;
+ private final SeenNotificationsInteractor mSeenNotificationsInteractor;
private final KeyguardTransitionRepository mKeyguardTransitionRepo;
private NotificationStackScrollLayout mView;
@@ -662,7 +662,7 @@
UiEventLogger uiEventLogger,
NotificationRemoteInputManager remoteInputManager,
VisibilityLocationProviderDelegator visibilityLocationProviderDelegator,
- NotificationListInteractor notificationListInteractor,
+ SeenNotificationsInteractor seenNotificationsInteractor,
ShadeController shadeController,
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
@@ -715,7 +715,7 @@
mUiEventLogger = uiEventLogger;
mRemoteInputManager = remoteInputManager;
mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
- mNotificationListInteractor = notificationListInteractor;
+ mSeenNotificationsInteractor = seenNotificationsInteractor;
mShadeController = shadeController;
mNotifIconAreaController = notifIconAreaController;
mFeatureFlags = featureFlags;
@@ -2006,7 +2006,7 @@
public void setNotifStats(@NonNull NotifStats notifStats) {
mNotifStats = notifStats;
mView.setHasFilteredOutSeenNotifications(
- mNotificationListInteractor.getHasFilteredOutSeenNotifications().getValue());
+ mSeenNotificationsInteractor.getHasFilteredOutSeenNotifications().getValue());
updateFooter();
updateShowEmptyShadeView();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationListRepository.kt
deleted file mode 100644
index f6ed8c8..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationListRepository.kt
+++ /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 com.android.systemui.statusbar.notification.stack.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-/** Repository for information about the current notification list. */
-@SysUISingleton
-class NotificationListRepository @Inject constructor() {
- private val _hasFilteredOutSeenNotifications = MutableStateFlow(false)
- val hasFilteredOutSeenNotifications: StateFlow<Boolean> =
- _hasFilteredOutSeenNotifications.asStateFlow()
-
- fun setHasFilteredOutSeenNotifications(value: Boolean) {
- _hasFilteredOutSeenNotifications.value = value
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 7cd32f9..cb85966 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -3124,7 +3124,9 @@
if (mAmbientIndicationContainer instanceof AutoReinflateContainer) {
((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout();
}
- mNotificationIconAreaController.onThemeChanged();
+ if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mNotificationIconAreaController.onThemeChanged();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 54d81b8..5a8b636 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -300,7 +300,8 @@
mIconController.setIconVisibility(mSlotCast, false);
// connected display
- mIconController.setIcon(mSlotConnectedDisplay, R.drawable.stat_sys_connected_display, null);
+ mIconController.setIcon(mSlotConnectedDisplay, R.drawable.stat_sys_connected_display,
+ mResources.getString(R.string.connected_display_icon_desc));
mIconController.setIconVisibility(mSlotConnectedDisplay, false);
// hotspot
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index a5cd062..07e2571 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -52,7 +52,7 @@
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
-import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -80,7 +80,7 @@
private final HeadsUpManager mHeadsUpManager;
private final AboveShelfObserver mAboveShelfObserver;
private final DozeScrimController mDozeScrimController;
- private final NotificationsInteractor mNotificationsInteractor;
+ private final NotificationAlertsInteractor mNotificationAlertsInteractor;
private final NotificationStackScrollLayoutController mNsslController;
private final LockscreenShadeTransitionController mShadeTransitionController;
private final PowerInteractor mPowerInteractor;
@@ -107,7 +107,7 @@
NotificationShadeWindowController notificationShadeWindowController,
DynamicPrivacyController dynamicPrivacyController,
KeyguardStateController keyguardStateController,
- NotificationsInteractor notificationsInteractor,
+ NotificationAlertsInteractor notificationAlertsInteractor,
LockscreenShadeTransitionController shadeTransitionController,
PowerInteractor powerInteractor,
CommandQueue commandQueue,
@@ -127,7 +127,7 @@
mQsController = quickSettingsController;
mHeadsUpManager = headsUp;
mDynamicPrivacyController = dynamicPrivacyController;
- mNotificationsInteractor = notificationsInteractor;
+ mNotificationAlertsInteractor = notificationAlertsInteractor;
mNsslController = stackScrollerController;
mShadeTransitionController = shadeTransitionController;
mPowerInteractor = powerInteractor;
@@ -303,7 +303,7 @@
@Override
public boolean suppressInterruptions(NotificationEntry entry) {
- return !mNotificationsInteractor.areNotificationAlertsEnabled();
+ return !mNotificationAlertsInteractor.areNotificationAlertsEnabled();
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 9d627af..2558645 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -45,6 +45,7 @@
import com.android.systemui.res.R;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.model.SysUiState;
@@ -54,8 +55,14 @@
import java.util.ArrayList;
import java.util.List;
+import javax.inject.Inject;
+
/**
- * Base class for dialogs that should appear over panels and keyguard.
+ * Class for dialogs that should appear over panels and keyguard.
+ *
+ * DO NOT SUBCLASS THIS. See {@link SystemUIDialog.Delegate} for an interface that enables
+ * customizing behavior via composition instead of inheritance. Clients should implement the
+ * Delegate class and then pass their implementation into the SystemUIDialog constructor.
*
* Optionally provide a {@link SystemUIDialogManager} to its constructor to send signals to
* listeners on whether this dialog is showing.
@@ -72,6 +79,7 @@
private final Context mContext;
private final FeatureFlags mFeatureFlags;
+ private final Delegate mDelegate;
@Nullable private final DismissReceiver mDismissReceiver;
private final Handler mHandler = new Handler();
private final SystemUIDialogManager mDialogManager;
@@ -101,18 +109,102 @@
Dependency.get(SystemUIDialogManager.class),
Dependency.get(SysUiState.class),
Dependency.get(BroadcastDispatcher.class),
- Dependency.get(DialogLaunchAnimator.class));
+ Dependency.get(DialogLaunchAnimator.class),
+ new Delegate() {});
}
- public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock,
+ @Inject
+ public SystemUIDialog(
+ @Application Context context,
+ FeatureFlags featureFlags,
+ SystemUIDialogManager systemUIDialogManager,
+ SysUiState sysUiState,
+ BroadcastDispatcher broadcastDispatcher,
+ DialogLaunchAnimator dialogLaunchAnimator) {
+ this(context,
+ DEFAULT_THEME,
+ DEFAULT_DISMISS_ON_DEVICE_LOCK,
+ featureFlags,
+ systemUIDialogManager,
+ sysUiState,
+ broadcastDispatcher,
+ dialogLaunchAnimator,
+ new Delegate(){});
+ }
+
+ public static class Factory {
+ private final Context mContext;
+ private final FeatureFlags mFeatureFlags;
+ private final SystemUIDialogManager mSystemUIDialogManager;
+ private final SysUiState mSysUiState;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+ private final DialogLaunchAnimator mDialogLaunchAnimator;
+
+ @Inject
+ public Factory(
+ @Application Context context,
+ FeatureFlags featureFlags,
+ SystemUIDialogManager systemUIDialogManager,
+ SysUiState sysUiState,
+ BroadcastDispatcher broadcastDispatcher,
+ DialogLaunchAnimator dialogLaunchAnimator) {
+ mContext = context;
+ mFeatureFlags = featureFlags;
+ mSystemUIDialogManager = systemUIDialogManager;
+ mSysUiState = sysUiState;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mDialogLaunchAnimator = dialogLaunchAnimator;
+ }
+
+ public SystemUIDialog create(Delegate delegate) {
+ return new SystemUIDialog(
+ mContext,
+ DEFAULT_THEME,
+ DEFAULT_DISMISS_ON_DEVICE_LOCK,
+ mFeatureFlags,
+ mSystemUIDialogManager,
+ mSysUiState,
+ mBroadcastDispatcher,
+ mDialogLaunchAnimator,
+ delegate);
+ }
+ }
+
+ public SystemUIDialog(
+ Context context,
+ int theme,
+ boolean dismissOnDeviceLock,
FeatureFlags featureFlags,
SystemUIDialogManager dialogManager,
SysUiState sysUiState,
BroadcastDispatcher broadcastDispatcher,
DialogLaunchAnimator dialogLaunchAnimator) {
+ this(
+ context,
+ theme,
+ dismissOnDeviceLock,
+ featureFlags,
+ dialogManager,
+ sysUiState,
+ broadcastDispatcher,
+ dialogLaunchAnimator,
+ new Delegate(){});
+ }
+
+ public SystemUIDialog(
+ Context context,
+ int theme,
+ boolean dismissOnDeviceLock,
+ FeatureFlags featureFlags,
+ SystemUIDialogManager dialogManager,
+ SysUiState sysUiState,
+ BroadcastDispatcher broadcastDispatcher,
+ DialogLaunchAnimator dialogLaunchAnimator,
+ Delegate delegate) {
super(context, theme);
mContext = context;
mFeatureFlags = featureFlags;
+ mDelegate = delegate;
applyFlags(this);
WindowManager.LayoutParams attrs = getWindow().getAttributes();
@@ -127,7 +219,9 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
+ mDelegate.beforeCreate(this, savedInstanceState);
super.onCreate(savedInstanceState);
+ mDelegate.onCreate(this, savedInstanceState);
Configuration config = getContext().getResources().getConfiguration();
mLastConfigurationWidthDp = config.screenWidthDp;
@@ -172,6 +266,8 @@
updateWindowSize();
}
+
+ mDelegate.onConfigurationChanged(this, configuration);
}
/**
@@ -212,7 +308,9 @@
* Called when {@link #onStart} is called. Subclasses wishing to override {@link #onStart()}
* should override this method instead.
*/
- protected void start() {}
+ protected void start() {
+ mDelegate.start(this);
+ }
@Override
protected final void onStop() {
@@ -234,7 +332,15 @@
* Called when {@link #onStop} is called. Subclasses wishing to override {@link #onStop()}
* should override this method instead.
*/
- protected void stop() {}
+ protected void stop() {
+ mDelegate.stop(this);
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ mDelegate.onWindowFocusChanged(this, hasFocus);
+ }
public void setShowForAllUsers(boolean show) {
setShowForAllUsers(this, show);
@@ -353,7 +459,6 @@
registerDismissListener(dialog, null);
}
-
/**
* Registers a listener that dismisses the given dialog when it receives
* the screen off / close system dialogs broadcast.
@@ -480,4 +585,42 @@
}
}
+ /**
+ * A delegate class that should be implemented in place of sublcassing {@link SystemUIDialog}.
+ *
+ * Implement this interface and then pass an instance of your implementation to
+ * {@link SystemUIDialog.Factory#create(Delegate)}.
+ */
+ public interface Delegate {
+ /**
+ * Called before {@link AlertDialog#onCreate} is called.
+ */
+ default void beforeCreate(SystemUIDialog dialog, Bundle savedInstanceState) {}
+
+ /**
+ * Called after {@link AlertDialog#onCreate} is called.
+ */
+ default void onCreate(SystemUIDialog dialog, Bundle savedInstanceState) {}
+
+ /**
+ * Called after {@link AlertDialog#onStart} is called.
+ */
+ default void start(SystemUIDialog dialog) {}
+
+ /**
+ * Called after {@link AlertDialog#onStop} is called.
+ */
+ default void stop(SystemUIDialog dialog) {}
+
+ /**
+ * Called after {@link AlertDialog#onWindowFocusChanged(boolean)} is called.
+ */
+ default void onWindowFocusChanged(SystemUIDialog dialog, boolean hasFocus) {}
+
+ /**
+ * Called as part of
+ * {@link ViewRootImpl.ConfigChangedCallback#onConfigurationChanged(Configuration)}.
+ */
+ default void onConfigurationChanged(SystemUIDialog dialog, Configuration configuration) {}
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/StatusBarPhoneDataLayerModule.kt
similarity index 73%
rename from packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/data/StatusBarPhoneDataLayerModule.kt
index 9b0c3fa..9645c69 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/StatusBarPhoneDataLayerModule.kt
@@ -13,13 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.systemui.statusbar.phone.data
-package com.android.systemui.common.ui.data.repository
-
-import dagger.Binds
+import com.android.systemui.statusbar.phone.data.repository.DarkIconRepositoryModule
import dagger.Module
-@Module
-interface CommonRepositoryModule {
- @Binds fun bindRepository(impl: ConfigurationRepositoryImpl): ConfigurationRepository
-}
+@Module(includes = [DarkIconRepositoryModule::class]) object StatusBarPhoneDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt
new file mode 100644
index 0000000..ba377497
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.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.statusbar.phone.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+/** Dark-mode state for tinting icons. */
+interface DarkIconRepository {
+ val darkState: StateFlow<DarkChange>
+}
+
+@SysUISingleton
+class DarkIconRepositoryImpl
+@Inject
+constructor(
+ darkIconDispatcher: SysuiDarkIconDispatcher,
+) : DarkIconRepository {
+ override val darkState: StateFlow<DarkChange> = darkIconDispatcher.darkChangeFlow()
+}
+
+@Module
+interface DarkIconRepositoryModule {
+ @Binds fun bindImpl(impl: DarkIconRepositoryImpl): DarkIconRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt
new file mode 100644
index 0000000..246645e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.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.statusbar.phone.domain.interactor
+
+import android.graphics.Rect
+import com.android.systemui.statusbar.phone.data.repository.DarkIconRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** States pertaining to calculating colors for icons in dark mode. */
+class DarkIconInteractor @Inject constructor(repository: DarkIconRepository) {
+ /** @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.areas */
+ val tintAreas: Flow<Collection<Rect>> = repository.darkState.map { it.areas }
+ /**
+ * @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.darkIntensity
+ */
+ val darkIntensity: Flow<Float> = repository.darkState.map { it.darkIntensity }
+ /** @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.tint */
+ val tintColor: Flow<Int> = repository.darkState.map { it.tint }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt
index 21acfb4..25d67af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt
@@ -13,7 +13,7 @@
*/
package com.android.systemui.statusbar.policy
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
@@ -23,14 +23,30 @@
* @see ConfigurationController.ConfigurationListener.onDensityOrFontScaleChanged
*/
val ConfigurationController.onDensityOrFontScaleChanged: Flow<Unit>
- get() =
- ConflatedCallbackFlow.conflatedCallbackFlow {
- val listener =
- object : ConfigurationController.ConfigurationListener {
- override fun onDensityOrFontScaleChanged() {
- trySend(Unit)
- }
+ get() = conflatedCallbackFlow {
+ val listener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onDensityOrFontScaleChanged() {
+ trySend(Unit)
}
- addCallback(listener)
- awaitClose { removeCallback(listener) }
- }
+ }
+ addCallback(listener)
+ awaitClose { removeCallback(listener) }
+ }
+
+/**
+ * A [Flow] that emits whenever the theme has changed.
+ *
+ * @see ConfigurationController.ConfigurationListener.onThemeChanged
+ */
+val ConfigurationController.onThemeChanged: Flow<Unit>
+ get() = conflatedCallbackFlow {
+ val listener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onThemeChanged() {
+ trySend(Unit)
+ }
+ }
+ addCallback(listener)
+ awaitClose { removeCallback(listener) }
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
index b3834f5..31b90ba 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
@@ -373,4 +373,11 @@
args[6] as T9,
)
}
-}
\ No newline at end of file
+}
+
+/**
+ * Returns a [Flow] that immediately emits [Unit] when started, then emits from the given upstream
+ * [Flow] as normal.
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun Flow<Unit>.emitOnStart(): Flow<Unit> = onStart { emit(Unit) }
diff --git a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt b/packages/SystemUI/tests/src/com/android/SysUITestModule.kt
index ea74510..c4b43e1 100644
--- a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt
+++ b/packages/SystemUI/tests/src/com/android/SysUITestModule.kt
@@ -16,10 +16,17 @@
package com.android
import android.content.Context
+import android.content.res.Resources
+import android.testing.TestableContext
+import android.testing.TestableResources
import com.android.systemui.FakeSystemUiModule
import com.android.systemui.SysuiTestCase
+import com.android.systemui.SysuiTestableContext
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.broadcast.FakeBroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -31,12 +38,29 @@
FakeSystemUiModule::class,
]
)
-class SysUITestModule {
- @Provides fun provideContext(test: SysuiTestCase): Context = test.context
+interface SysUITestModule {
- @Provides @Application fun provideAppContext(test: SysuiTestCase): Context = test.context
+ @Binds fun bindTestableContext(sysuiTestableContext: SysuiTestableContext): TestableContext
+ @Binds fun bindContext(testableContext: TestableContext): Context
+ @Binds @Application fun bindAppContext(context: Context): Context
+ @Binds @Application fun bindAppResources(resources: Resources): Resources
+ @Binds @Main fun bindMainResources(resources: Resources): Resources
+ @Binds fun bindBroadcastDispatcher(fake: FakeBroadcastDispatcher): BroadcastDispatcher
- @Provides
- fun provideBroadcastDispatcher(test: SysuiTestCase): BroadcastDispatcher =
- test.fakeBroadcastDispatcher
+ companion object {
+ @Provides
+ fun provideSysuiTestableContext(test: SysuiTestCase): SysuiTestableContext = test.context
+
+ @Provides
+ fun provideTestableResources(context: TestableContext): TestableResources =
+ context.getOrCreateTestableResources()
+
+ @Provides
+ fun provideResources(testableResources: TestableResources): Resources =
+ testableResources.resources
+
+ @Provides
+ fun provideFakeBroadcastDispatcher(test: SysuiTestCase): FakeBroadcastDispatcher =
+ test.fakeBroadcastDispatcher
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt b/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
index 167e341..ff1d5b2 100644
--- a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
+++ b/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
@@ -22,7 +22,9 @@
import com.android.internal.logging.MetricsLogger
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardViewController
import com.android.systemui.GuestResumeSessionReceiver
+import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.ScreenLifecycle
@@ -31,6 +33,7 @@
import com.android.systemui.log.dagger.BroadcastDispatcherLog
import com.android.systemui.log.dagger.SceneFrameworkLog
import com.android.systemui.media.controls.ui.MediaHierarchyManager
+import com.android.systemui.model.SysUiState
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -86,6 +89,9 @@
@get:Provides val statusBarStateController: SysuiStatusBarStateController = mock(),
@get:Provides val statusBarWindowController: StatusBarWindowController = mock(),
@get:Provides val wakefulnessLifecycle: WakefulnessLifecycle = mock(),
+ @get:Provides val keyguardViewController: KeyguardViewController = mock(),
+ @get:Provides val dialogLaunchAnimator: DialogLaunchAnimator = mock(),
+ @get:Provides val sysuiState: SysUiState = mock(),
// log buffers
@get:[Provides BroadcastDispatcherLog]
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index df7d1b7..aed795a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -222,14 +222,14 @@
@Test
public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() {
- final float menuTop = IME_TOP + 100;
- mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
+ mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
+ final PointF beforePosition = mMenuView.getMenuPosition();
dispatchShowingImeInsets();
final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight();
- assertThat(mMenuView.getTranslationX()).isEqualTo(0);
- assertThat(menuBottom).isLessThan(IME_TOP);
+ assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
+ assertThat(menuBottom).isLessThan(beforePosition.y);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
index bb6786a..e16b8d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
@@ -18,6 +18,17 @@
import android.platform.test.flag.junit.SetFlagsRule
+/**
+ * Set the given flag's value to the real value for the current build configuration.
+ * This prevents test code from crashing because it is reading an unspecified flag value.
+ *
+ * REMINDER: You should always test your code with your flag in both configurations, so
+ * generally you should be explicitly enabling or disabling your flag. This method is for
+ * situations where the flag needs to be read (e.g. in the class constructor), but its value
+ * shouldn't affect the actual test cases. In those cases, it's mildly safer to use this method
+ * than to hard-code `false` or `true` because then at least if you're wrong, and the flag value
+ * *does* matter, you'll notice when the flag is flipped and tests start failing.
+ */
fun SetFlagsRule.setFlagDefault(flagName: String) {
if (getFlagDefault(flagName)) {
enableFlags(flagName)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
index 429aad1..347ed56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
@@ -63,4 +63,13 @@
verify(service).notifyPermissionRequestInitiated(hostUid, METRICS_CREATION_SOURCE_UNKNOWN)
}
+
+ @Test
+ fun notifyPermissionRequestDisplayed_forwardsToService() {
+ val hostUid = 987
+
+ logger.notifyPermissionRequestDisplayed(hostUid)
+
+ verify(service).notifyPermissionRequestDisplayed(hostUid)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index ac03073..c6d156f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -52,6 +52,7 @@
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -96,6 +97,8 @@
private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
@Mock
private Dialog mPermissionDialogPrompt;
+ @Mock
+ private UserContextProvider mUserContextProvider;
private TestableLooper mTestableLooper;
private ScreenRecordTile mTile;
@@ -106,6 +109,7 @@
mTestableLooper = TestableLooper.get(this);
+ when(mUserContextProvider.getUserContext()).thenReturn(mContext);
when(mHost.getContext()).thenReturn(mContext);
mTile = new ScreenRecordTile(
@@ -124,7 +128,8 @@
mKeyguardStateController,
mDialogLaunchAnimator,
mPanelInteractor,
- mMediaProjectionMetricsLogger
+ mMediaProjectionMetricsLogger,
+ mUserContextProvider
);
mTile.initialize();
@@ -308,7 +313,8 @@
onDismissAction.getValue().onDismiss();
verify(mPermissionDialogPrompt).show();
- verify(mMediaProjectionMetricsLogger).notifyPermissionRequestDisplayed();
+ verify(mMediaProjectionMetricsLogger)
+ .notifyPermissionRequestDisplayed(mContext.getUserId());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index da49230..b421e1b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -23,7 +23,6 @@
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
-import androidx.core.view.contains
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardMessageAreaController
import com.android.keyguard.KeyguardSecurityContainerController
@@ -31,8 +30,6 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.LockIconViewController
import com.android.keyguard.dagger.KeyguardBouncerComponent
-import com.android.systemui.FakeFeatureFlagsImpl
-import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.back.domain.interactor.BackActionInteractor
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
@@ -47,6 +44,7 @@
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.compose.ComposeFacade.isComposeAvailable
import com.android.systemui.dock.DockManager
@@ -150,6 +148,7 @@
private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
@Mock private lateinit var notificationInsetsController: NotificationInsetsController
@Mock private lateinit var mCommunalViewModel: CommunalViewModel
+ private lateinit var mCommunalRepository: FakeCommunalRepository
@Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
@@ -175,7 +174,6 @@
private lateinit var testScope: TestScope
private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic
- private lateinit var featureFlags: FakeFeatureFlagsImpl
@Before
fun setUp() {
@@ -199,7 +197,7 @@
featureFlagsClassic.set(MIGRATE_NSSL, false)
featureFlagsClassic.set(ALTERNATE_BOUNCER_VIEW, false)
- featureFlags = FakeFeatureFlagsImpl()
+ mCommunalRepository = FakeCommunalRepository()
testScope = TestScope()
fakeClock = FakeSystemClock()
@@ -235,9 +233,9 @@
keyguardTransitionInteractor,
primaryBouncerToGoneTransitionViewModel,
mCommunalViewModel,
+ mCommunalRepository,
notificationExpansionRepository,
featureFlagsClassic,
- featureFlags,
fakeClock,
BouncerMessageInteractor(
repository = BouncerMessageRepositoryImpl(),
@@ -499,7 +497,7 @@
return
}
- featureFlags.setFlag(FLAG_COMMUNAL_HUB, true)
+ mCommunalRepository.setIsCommunalEnabled(true)
val mockCommunalPlaceholder = mock(View::class.java)
val fakeViewIndex = 20
@@ -520,7 +518,7 @@
return
}
- featureFlags.setFlag(FLAG_COMMUNAL_HUB, false)
+ mCommunalRepository.setIsCommunalEnabled(false)
val mockCommunalPlaceholder = mock(View::class.java)
val fakeViewIndex = 20
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index c94741f..9c57101 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -28,7 +28,6 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.LockIconViewController
import com.android.keyguard.dagger.KeyguardBouncerComponent
-import com.android.systemui.FakeFeatureFlagsImpl
import com.android.systemui.SysuiTestCase
import com.android.systemui.back.domain.interactor.BackActionInteractor
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
@@ -43,6 +42,7 @@
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.dock.DockManager
import com.android.systemui.dump.DumpManager
@@ -145,6 +145,7 @@
Optional<UnfoldTransitionProgressProvider>
@Mock private lateinit var notificationInsetsController: NotificationInsetsController
@Mock private lateinit var mCommunalViewModel: CommunalViewModel
+ private lateinit var mCommunalRepository: FakeCommunalRepository
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@@ -181,6 +182,8 @@
whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
.thenReturn(emptyFlow())
+ mCommunalRepository = FakeCommunalRepository()
+
val featureFlags = FakeFeatureFlags()
featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
@@ -222,9 +225,9 @@
keyguardTransitionInteractor,
primaryBouncerToGoneTransitionViewModel,
mCommunalViewModel,
+ mCommunalRepository,
NotificationExpansionRepository(),
featureFlags,
- FakeFeatureFlagsImpl(),
FakeSystemClock(),
BouncerMessageInteractor(
repository = BouncerMessageRepositoryImpl(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
index 8397702..df8afde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
@@ -26,6 +26,7 @@
override var forceVisible: Boolean = false,
override val showAnimation: Boolean = true,
override var contentDescription: String? = "",
+ override val shouldAnnounceAccessibilityEvent: Boolean = false
) : StatusEvent
class FakePrivacyStatusEvent(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 4fcccf8..fee8b82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -33,6 +33,8 @@
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import junit.framework.Assert.assertEquals
@@ -370,6 +372,63 @@
}
@Test
+ fun testAccessibilityAnnouncement_announced() = runTest {
+ // Instantiate class under test with TestScope from runTest
+ initializeSystemStatusAnimationScheduler(testScope = this)
+ val accessibilityDesc = "Some desc"
+ val mockView = mock<View>()
+ val mockAnimatableView =
+ mock<BackgroundAnimatableView> { whenever(view).thenReturn(mockView) }
+
+ scheduleFakeEventWithView(
+ accessibilityDesc,
+ mockAnimatableView,
+ shouldAnnounceAccessibilityEvent = true
+ )
+ fastForwardAnimationToState(ANIMATING_OUT)
+
+ verify(mockView).announceForAccessibility(eq(accessibilityDesc))
+ }
+
+ @Test
+ fun testAccessibilityAnnouncement_nullDesc_noAnnouncement() = runTest {
+ // Instantiate class under test with TestScope from runTest
+ initializeSystemStatusAnimationScheduler(testScope = this)
+ val accessibilityDesc = null
+ val mockView = mock<View>()
+ val mockAnimatableView =
+ mock<BackgroundAnimatableView> { whenever(view).thenReturn(mockView) }
+
+ scheduleFakeEventWithView(
+ accessibilityDesc,
+ mockAnimatableView,
+ shouldAnnounceAccessibilityEvent = true
+ )
+ fastForwardAnimationToState(ANIMATING_OUT)
+
+ verify(mockView, never()).announceForAccessibility(any())
+ }
+
+ @Test
+ fun testAccessibilityAnnouncement_notNeeded_noAnnouncement() = runTest {
+ // Instantiate class under test with TestScope from runTest
+ initializeSystemStatusAnimationScheduler(testScope = this)
+ val accessibilityDesc = "something"
+ val mockView = mock<View>()
+ val mockAnimatableView =
+ mock<BackgroundAnimatableView> { whenever(view).thenReturn(mockView) }
+
+ scheduleFakeEventWithView(
+ accessibilityDesc,
+ mockAnimatableView,
+ shouldAnnounceAccessibilityEvent = false
+ )
+ fastForwardAnimationToState(ANIMATING_OUT)
+
+ verify(mockView, never()).announceForAccessibility(any())
+ }
+
+ @Test
fun testPrivacyDot_isRemovedDuringChipAnimation() = runTest {
// Instantiate class under test with TestScope from runTest
initializeSystemStatusAnimationScheduler(testScope = this)
@@ -572,6 +631,20 @@
return privacyChip
}
+ private fun scheduleFakeEventWithView(
+ desc: String?,
+ view: BackgroundAnimatableView,
+ shouldAnnounceAccessibilityEvent: Boolean
+ ) {
+ val fakeEvent =
+ FakeStatusEvent(
+ viewCreator = { view },
+ contentDescription = desc,
+ shouldAnnounceAccessibilityEvent = shouldAnnounceAccessibilityEvent
+ )
+ systemStatusAnimationScheduler.onStatusEvent(fakeEvent)
+ }
+
private fun createAndScheduleFakeBatteryEvent(): BatteryStatusChip {
val batteryChip = BatteryStatusChip(mContext)
val fakeBatteryEvent =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 546abd4..6c1f537 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -39,10 +39,10 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.stack.data.repository.NotificationListRepository
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationListInteractor
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
import com.android.systemui.util.mockito.any
@@ -246,7 +246,7 @@
unseenFilter.onCleanup()
// THEN: The SeenNotificationProvider has been updated to reflect the suppression
- assertThat(notificationListInteractor.hasFilteredOutSeenNotifications.value).isTrue()
+ assertThat(seenNotificationsInteractor.hasFilteredOutSeenNotifications.value).isTrue()
}
}
@@ -597,7 +597,8 @@
FakeSettings().apply {
putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
}
- val notificationListInteractor = NotificationListInteractor(NotificationListRepository())
+ val seenNotificationsInteractor =
+ SeenNotificationsInteractor(ActiveNotificationListRepository())
val keyguardCoordinator =
KeyguardCoordinator(
testDispatcher,
@@ -610,7 +611,7 @@
testScope.backgroundScope,
sectionHeaderVisibilityProvider,
fakeSettings,
- notificationListInteractor,
+ seenNotificationsInteractor,
statusBarStateController,
)
keyguardCoordinator.attach(notifPipeline)
@@ -618,7 +619,7 @@
KeyguardCoordinatorTestScope(
keyguardCoordinator,
testScope,
- notificationListInteractor,
+ seenNotificationsInteractor,
fakeSettings,
)
.testBlock()
@@ -628,7 +629,7 @@
private inner class KeyguardCoordinatorTestScope(
private val keyguardCoordinator: KeyguardCoordinator,
private val scope: TestScope,
- val notificationListInteractor: NotificationListInteractor,
+ val seenNotificationsInteractor: SeenNotificationsInteractor,
private val fakeSettings: FakeSettings,
) : CoroutineScope by scope {
val testScheduler: TestCoroutineScheduler
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 655bd72..a736182 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
@@ -19,6 +19,8 @@
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -27,6 +29,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.RenderNotificationListInteractor
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.phone.NotificationIconAreaController
@@ -37,8 +40,8 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations.initMocks
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations.initMocks
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -52,13 +55,23 @@
@Mock private lateinit var pipeline: NotifPipeline
@Mock private lateinit var groupExpansionManagerImpl: GroupExpansionManagerImpl
@Mock private lateinit var notificationIconAreaController: NotificationIconAreaController
+ @Mock private lateinit var renderListInteractor: RenderNotificationListInteractor
@Mock private lateinit var stackController: NotifStackController
@Mock private lateinit var section: NotifSection
+ val featureFlags =
+ FakeFeatureFlagsClassic().apply { setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR) }
+
@Before
fun setUp() {
initMocks(this)
- coordinator = StackCoordinator(groupExpansionManagerImpl, notificationIconAreaController)
+ coordinator =
+ StackCoordinator(
+ featureFlags,
+ groupExpansionManagerImpl,
+ notificationIconAreaController,
+ renderListInteractor,
+ )
coordinator.attach(pipeline)
afterRenderListListener = withArgCaptor {
verify(pipeline).addOnAfterRenderListListener(capture())
@@ -68,11 +81,19 @@
@Test
fun testUpdateNotificationIcons() {
+ featureFlags.set(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR, false)
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
verify(notificationIconAreaController).updateNotificationIcons(eq(listOf(entry)))
}
@Test
+ fun testSetRenderedListOnInteractor() {
+ featureFlags.set(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR, true)
+ afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+ verify(renderListInteractor).setRenderedList(eq(listOf(entry)))
+ }
+
+ @Test
fun testSetNotificationStats_clearableAlerting() {
whenever(section.bucket).thenReturn(BUCKET_ALERTING)
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt
new file mode 100644
index 0000000..683d0aa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt
@@ -0,0 +1,68 @@
+/*
+ * 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 android.app.StatusBarManager
+import androidx.test.filters.SmallTest
+import com.android.SysUITestModule
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import org.junit.Test
+
+@SmallTest
+class NotificationAlertsInteractorTest : SysuiTestCase() {
+
+ @Component(modules = [SysUITestModule::class])
+ @SysUISingleton
+ interface TestComponent {
+ val underTest: NotificationAlertsInteractor
+ val disableFlags: FakeDisableFlagsRepository
+
+ @Component.Factory
+ interface Factory {
+ fun create(@BindsInstance test: SysuiTestCase): TestComponent
+ }
+ }
+
+ private val testComponent: TestComponent =
+ DaggerNotificationAlertsInteractorTest_TestComponent.factory().create(test = this)
+
+ @Test
+ fun disableFlags_notifAlertsNotDisabled_notifAlertsEnabledTrue() =
+ with(testComponent) {
+ disableFlags.disableFlags.value =
+ DisableFlagsModel(
+ StatusBarManager.DISABLE_NONE,
+ StatusBarManager.DISABLE2_NONE,
+ )
+ assertThat(underTest.areNotificationAlertsEnabled()).isTrue()
+ }
+
+ @Test
+ fun disableFlags_notifAlertsDisabled_notifAlertsEnabledFalse() =
+ with(testComponent) {
+ disableFlags.disableFlags.value =
+ DisableFlagsModel(
+ StatusBarManager.DISABLE_NOTIFICATION_ALERTS,
+ StatusBarManager.DISABLE2_NONE,
+ )
+ assertThat(underTest.areNotificationAlertsEnabled()).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt
deleted file mode 100644
index fe49016..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt
+++ /dev/null
@@ -1,99 +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.statusbar.notification.domain.interactor
-
-import android.app.StatusBarManager
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
-import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
-import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepositoryImpl
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mockito.verify
-
-@SmallTest
-@OptIn(ExperimentalCoroutinesApi::class)
-class NotificationsInteractorTest : SysuiTestCase() {
-
- private lateinit var underTest: NotificationsInteractor
-
- private val testScope = TestScope(UnconfinedTestDispatcher())
- private val commandQueue: CommandQueue = mock()
- private val logBuffer = LogBufferFactory(DumpManager(), mock()).create("buffer", 10)
- private val disableFlagsLogger = DisableFlagsLogger()
- private lateinit var disableFlagsRepository: DisableFlagsRepository
-
- @Before
- fun setUp() {
- disableFlagsRepository =
- DisableFlagsRepositoryImpl(
- commandQueue,
- DISPLAY_ID,
- testScope.backgroundScope,
- mock(),
- logBuffer,
- disableFlagsLogger,
- )
- underTest = NotificationsInteractor(disableFlagsRepository)
- }
-
- @Test
- fun disableFlags_notifAlertsNotDisabled_notifAlertsEnabledTrue() {
- val callback = getCommandQueueCallback()
-
- callback.disable(
- DISPLAY_ID,
- StatusBarManager.DISABLE_NONE,
- StatusBarManager.DISABLE2_NONE,
- /* animate= */ false
- )
-
- assertThat(underTest.areNotificationAlertsEnabled()).isTrue()
- }
-
- @Test
- fun disableFlags_notifAlertsDisabled_notifAlertsEnabledFalse() {
- val callback = getCommandQueueCallback()
-
- callback.disable(
- DISPLAY_ID,
- StatusBarManager.DISABLE_NOTIFICATION_ALERTS,
- StatusBarManager.DISABLE2_NONE,
- /* animate= */ false
- )
-
- assertThat(underTest.areNotificationAlertsEnabled()).isFalse()
- }
-
- private fun getCommandQueueCallback(): CommandQueue.Callbacks {
- val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>()
- verify(commandQueue).addCallback(callbackCaptor.capture())
- return callbackCaptor.value
- }
-
- private companion object {
- const val DISPLAY_ID = 1
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
new file mode 100644
index 0000000..8c5c439
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.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.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.byKey
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+@SmallTest
+class RenderNotificationsListInteractorTest : SysuiTestCase() {
+
+ private val notifsRepository = ActiveNotificationListRepository()
+ private val notifsInteractor = ActiveNotificationsInteractor(notifsRepository)
+ private val underTest =
+ RenderNotificationListInteractor(
+ notifsRepository,
+ )
+
+ @Test
+ fun setRenderedList_preservesOrdering() = runTest {
+ val notifs by collectLastValue(notifsInteractor.notifications)
+ val keys = (1..50).shuffled().map { "$it" }
+ val entries = keys.map { mock<ListEntry> { whenever(key).thenReturn(it) } }
+ underTest.setRenderedList(entries)
+ assertThat(notifs).comparingElementsUsing(byKey).containsExactlyElementsIn(keys).inOrder()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
new file mode 100644
index 0000000..2a3c1a5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
@@ -0,0 +1,54 @@
+/*
+ * 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 android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class SeenNotificationsInteractorTest : SysuiTestCase() {
+
+ private val repository = ActiveNotificationListRepository()
+ private val underTest = SeenNotificationsInteractor(repository)
+
+ @Test
+ fun testNoFilteredOutSeenNotifications() = runTest {
+ val hasFilteredOutSeenNotifications by
+ collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+ underTest.setHasFilteredOutSeenNotifications(false)
+
+ assertThat(hasFilteredOutSeenNotifications).isFalse()
+ }
+
+ @Test
+ fun testHasFilteredOutSeenNotifications() = runTest {
+ val hasFilteredOutSeenNotifications by
+ collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+ underTest.setHasFilteredOutSeenNotifications(true)
+
+ assertThat(hasFilteredOutSeenNotifications).isTrue()
+ }
+}
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 e1e7f92..e21ebeb 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
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.graphics.Rect
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.SysUITestModule
@@ -34,10 +35,13 @@
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.plugins.DarkIconDispatcher
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.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher
+import com.android.systemui.statusbar.phone.data.repository.FakeDarkIconRepository
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
import com.android.systemui.user.domain.UserDomainLayerModule
import com.android.systemui.util.mockito.whenever
@@ -61,18 +65,6 @@
@Mock lateinit var dozeParams: DozeParameters
private lateinit var testComponent: TestComponent
- private val underTest: NotificationIconContainerStatusBarViewModel
- get() = testComponent.underTest
- private val deviceProvisioningRepository
- get() = testComponent.deviceProvisioningRepository
- private val keyguardTransitionRepository
- get() = testComponent.keyguardTransitionRepository
- private val keyguardRepository
- get() = testComponent.keyguardRepository
- private val powerRepository
- get() = testComponent.powerRepository
- private val scope
- get() = testComponent.scope
@Before
fun setup() {
@@ -82,7 +74,6 @@
DaggerNotificationIconContainerStatusBarViewModelTest_TestComponent.factory()
.create(
test = this,
- // Configurable bindings
featureFlags =
FakeFeatureFlagsClassicModule {
set(Flags.FACE_AUTH_REFACTOR, value = false)
@@ -93,155 +84,247 @@
dozeParameters = dozeParams,
),
)
-
- keyguardRepository.setKeyguardShowing(false)
- deviceProvisioningRepository.setFactoryResetProtectionActive(false)
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.AWAKE,
- lastWakeReason = WakeSleepReason.OTHER,
- lastSleepReason = WakeSleepReason.OTHER,
- )
+ .apply {
+ keyguardRepository.setKeyguardShowing(false)
+ deviceProvisioningRepository.setFactoryResetProtectionActive(false)
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.AWAKE,
+ lastWakeReason = WakeSleepReason.OTHER,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
+ }
}
@Test
fun animationsEnabled_isFalse_whenFrpIsActive() =
- scope.runTest {
- deviceProvisioningRepository.setFactoryResetProtectionActive(true)
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
+ with(testComponent) {
+ scope.runTest {
+ deviceProvisioningRepository.setFactoryResetProtectionActive(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ )
)
- )
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isFalse()
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
+ assertThat(animationsEnabled).isFalse()
+ }
}
@Test
fun animationsEnabled_isFalse_whenDeviceAsleepAndNotPulsing() =
- scope.runTest {
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.ASLEEP,
- lastWakeReason = WakeSleepReason.POWER_BUTTON,
- lastSleepReason = WakeSleepReason.OTHER,
- )
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
+ with(testComponent) {
+ scope.runTest {
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.ASLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
)
- )
- keyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(
- to = DozeStateModel.DOZE_AOD,
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ )
)
- )
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isFalse()
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(
+ to = DozeStateModel.DOZE_AOD,
+ )
+ )
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
+ assertThat(animationsEnabled).isFalse()
+ }
}
@Test
fun animationsEnabled_isTrue_whenDeviceAsleepAndPulsing() =
- scope.runTest {
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.ASLEEP,
- lastWakeReason = WakeSleepReason.POWER_BUTTON,
- lastSleepReason = WakeSleepReason.OTHER,
- )
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
+ with(testComponent) {
+ scope.runTest {
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.ASLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
)
- )
- keyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(
- to = DozeStateModel.DOZE_PULSING,
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ )
)
- )
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isTrue()
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(
+ to = DozeStateModel.DOZE_PULSING,
+ )
+ )
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
+ assertThat(animationsEnabled).isTrue()
+ }
}
@Test
fun animationsEnabled_isFalse_whenStartingToSleepAndNotControlScreenOff() =
- scope.runTest {
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.STARTING_TO_SLEEP,
- lastWakeReason = WakeSleepReason.POWER_BUTTON,
- lastSleepReason = WakeSleepReason.OTHER,
- )
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- transitionState = TransitionState.STARTED,
+ with(testComponent) {
+ scope.runTest {
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.STARTING_TO_SLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
)
- )
- whenever(dozeParams.shouldControlScreenOff()).thenReturn(false)
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isFalse()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ whenever(dozeParams.shouldControlScreenOff()).thenReturn(false)
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
+ assertThat(animationsEnabled).isFalse()
+ }
}
@Test
fun animationsEnabled_isTrue_whenStartingToSleepAndControlScreenOff() =
- scope.runTest {
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.STARTING_TO_SLEEP,
- lastWakeReason = WakeSleepReason.POWER_BUTTON,
- lastSleepReason = WakeSleepReason.OTHER,
- )
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- transitionState = TransitionState.STARTED,
+ with(testComponent) {
+ scope.runTest {
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.STARTING_TO_SLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
)
- )
- whenever(dozeParams.shouldControlScreenOff()).thenReturn(true)
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isTrue()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ whenever(dozeParams.shouldControlScreenOff()).thenReturn(true)
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
+ assertThat(animationsEnabled).isTrue()
+ }
}
@Test
fun animationsEnabled_isTrue_whenNotAsleep() =
- scope.runTest {
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.AWAKE,
- lastWakeReason = WakeSleepReason.POWER_BUTTON,
- lastSleepReason = WakeSleepReason.OTHER,
- )
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
+ with(testComponent) {
+ scope.runTest {
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.AWAKE,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
)
- )
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isTrue()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
+ assertThat(animationsEnabled).isTrue()
+ }
}
@Test
fun animationsEnabled_isTrue_whenKeyguardIsNotShowing() =
- scope.runTest {
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ with(testComponent) {
+ scope.runTest {
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ )
)
- )
- keyguardRepository.setKeyguardShowing(true)
- runCurrent()
+ keyguardRepository.setKeyguardShowing(true)
+ runCurrent()
- assertThat(animationsEnabled).isFalse()
+ assertThat(animationsEnabled).isFalse()
- keyguardRepository.setKeyguardShowing(false)
- runCurrent()
+ keyguardRepository.setKeyguardShowing(false)
+ runCurrent()
- assertThat(animationsEnabled).isTrue()
+ assertThat(animationsEnabled).isTrue()
+ }
+ }
+
+ @Test
+ fun iconColors_testsDarkBounds() =
+ with(testComponent) {
+ scope.runTest {
+ darkIconRepository.darkState.value =
+ SysuiDarkIconDispatcher.DarkChange(
+ emptyList(),
+ 0f,
+ 0xAABBCC,
+ )
+ val iconColorsLookup by collectLastValue(underTest.iconColors)
+ assertThat(iconColorsLookup).isNotNull()
+
+ val iconColors = iconColorsLookup?.iconColors(Rect())
+ assertThat(iconColors).isNotNull()
+ iconColors!!
+
+ assertThat(iconColors.tint).isEqualTo(0xAABBCC)
+
+ val staticDrawableColor = iconColors.staticDrawableColor(Rect(), isColorized = true)
+
+ assertThat(staticDrawableColor).isEqualTo(0xAABBCC)
+ }
+ }
+
+ @Test
+ fun iconColors_staticDrawableColor_nonColorized() =
+ with(testComponent) {
+ scope.runTest {
+ darkIconRepository.darkState.value =
+ SysuiDarkIconDispatcher.DarkChange(
+ emptyList(),
+ 0f,
+ 0xAABBCC,
+ )
+ val iconColorsLookup by collectLastValue(underTest.iconColors)
+ val iconColors = iconColorsLookup?.iconColors(Rect())
+ val staticDrawableColor =
+ iconColors?.staticDrawableColor(Rect(), isColorized = false)
+ assertThat(staticDrawableColor).isEqualTo(DarkIconDispatcher.DEFAULT_ICON_TINT)
+ }
+ }
+
+ @Test
+ fun iconColors_staticDrawableColor_isColorized_notInDarkTintArea() =
+ with(testComponent) {
+ scope.runTest {
+ darkIconRepository.darkState.value =
+ SysuiDarkIconDispatcher.DarkChange(
+ listOf(Rect(0, 0, 5, 5)),
+ 0f,
+ 0xAABBCC,
+ )
+ val iconColorsLookup by collectLastValue(underTest.iconColors)
+ val iconColors = iconColorsLookup?.iconColors(Rect(1, 1, 4, 4))
+ val staticDrawableColor =
+ iconColors?.staticDrawableColor(Rect(6, 6, 7, 7), isColorized = true)
+ assertThat(staticDrawableColor).isEqualTo(DarkIconDispatcher.DEFAULT_ICON_TINT)
+ }
+ }
+
+ @Test
+ fun iconColors_notInDarkTintArea() =
+ with(testComponent) {
+ scope.runTest {
+ darkIconRepository.darkState.value =
+ SysuiDarkIconDispatcher.DarkChange(
+ listOf(Rect(0, 0, 5, 5)),
+ 0f,
+ 0xAABBCC,
+ )
+ val iconColorsLookup by collectLastValue(underTest.iconColors)
+ val iconColors = iconColorsLookup?.iconColors(Rect(6, 6, 7, 7))
+ assertThat(iconColors).isNull()
+ }
}
@SysUISingleton
@@ -249,7 +332,6 @@
modules =
[
SysUITestModule::class,
- // Real impls
BiometricsDomainLayerModule::class,
UserDomainLayerModule::class,
]
@@ -258,6 +340,7 @@
val underTest: NotificationIconContainerStatusBarViewModel
+ val darkIconRepository: FakeDarkIconRepository
val deviceProvisioningRepository: FakeDeviceProvisioningRepository
val keyguardTransitionRepository: FakeKeyguardTransitionRepository
val keyguardRepository: FakeKeyguardRepository
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
new file mode 100644
index 0000000..ed94058
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.shared
+
+import com.google.common.truth.Correspondence
+
+val byKey: Correspondence<ActiveNotificationModel, String> =
+ Correspondence.transforming({ it?.key }, "has a key of")
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 20197e3..3dafb23 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
@@ -82,13 +82,13 @@
import com.android.systemui.statusbar.notification.collection.render.NotifStats;
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.SeenNotificationsInteractor;
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.data.repository.NotificationListRepository;
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationListInteractor;
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
@@ -171,8 +171,8 @@
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
- private final NotificationListInteractor mNotificationListInteractor =
- new NotificationListInteractor(new NotificationListRepository());
+ private final SeenNotificationsInteractor mSeenNotificationsInteractor =
+ new SeenNotificationsInteractor(new ActiveNotificationListRepository());
private NotificationStackScrollLayoutController mController;
@@ -504,7 +504,7 @@
@Test
public void testSetNotifStats_updatesHasFilteredOutSeenNotifications() {
initController(/* viewIsAttached= */ true);
- mNotificationListInteractor.setHasFilteredOutSeenNotifications(true);
+ mSeenNotificationsInteractor.setHasFilteredOutSeenNotifications(true);
mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
verify(mNotificationStackScrollLayout).setHasFilteredOutSeenNotifications(true);
verify(mNotificationStackScrollLayout).updateFooter();
@@ -704,7 +704,7 @@
mUiEventLogger,
mRemoteInputManager,
mVisibilityLocationProviderDelegator,
- mNotificationListInteractor,
+ mSeenNotificationsInteractor,
mShadeController,
mJankMonitor,
mStackLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index ee4f208..53c621d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -53,7 +53,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
-import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -79,8 +79,8 @@
private CommandQueue mCommandQueue;
private FakeMetricsLogger mMetricsLogger;
private final ShadeController mShadeController = mock(ShadeController.class);
- private final NotificationsInteractor mNotificationsInteractor =
- mock(NotificationsInteractor.class);
+ private final NotificationAlertsInteractor mNotificationAlertsInteractor =
+ mock(NotificationAlertsInteractor.class);
private final KeyguardStateController mKeyguardStateController =
mock(KeyguardStateController.class);
private final InitController mInitController = new InitController();
@@ -116,7 +116,7 @@
mock(NotificationShadeWindowController.class),
mock(DynamicPrivacyController.class),
mKeyguardStateController,
- mNotificationsInteractor,
+ mNotificationAlertsInteractor,
mock(LockscreenShadeTransitionController.class),
mock(PowerInteractor.class),
mCommandQueue,
@@ -226,7 +226,7 @@
.setTag("a")
.setNotification(n)
.build();
- when(mNotificationsInteractor.areNotificationAlertsEnabled()).thenReturn(false);
+ when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false);
assertTrue("When alerts aren't enabled, interruptions are suppressed",
mInterruptSuppressor.suppressInterruptions(entry));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
index aaf8d07..6e3a732 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
@@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.util.ui
import android.testing.AndroidTestingRunner
@@ -20,6 +22,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.launchIn
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
index 5dcc742..8353cf7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
@@ -34,7 +34,10 @@
private val _scaleForResolution = MutableStateFlow(1f)
override val scaleForResolution: Flow<Float> = _scaleForResolution.asStateFlow()
- suspend fun onAnyConfigurationChange() {
+ private val pixelSizes = mutableMapOf<Int, MutableStateFlow<Int>>()
+ private val colors = mutableMapOf<Int, MutableStateFlow<Int>>()
+
+ fun onAnyConfigurationChange() {
_onAnyConfigurationChange.tryEmit(Unit)
}
@@ -42,12 +45,12 @@
_scaleForResolution.value = scale
}
- override fun getResolutionScale(): Float {
- return _scaleForResolution.value
- }
+ override fun getResolutionScale(): Float = _scaleForResolution.value
- override fun getDimensionPixelSize(id: Int): Int {
- return 0
+ override fun getDimensionPixelSize(id: Int): Int = pixelSizes[id]?.value ?: 0
+
+ fun setDimensionPixelSize(id: Int, pixelSize: Int) {
+ pixelSizes.getOrPut(id) { MutableStateFlow(pixelSize) }.value = pixelSize
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt
index e59f642..8f18e13 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt
@@ -15,8 +15,10 @@
*/
package com.android.systemui.statusbar.data
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepositoryModule
import com.android.systemui.statusbar.disableflags.data.FakeStatusBarDisableFlagsDataLayerModule
import com.android.systemui.statusbar.notification.data.FakeStatusBarNotificationsDataLayerModule
+import com.android.systemui.statusbar.phone.data.FakeStatusBarPhoneDataLayerModule
import com.android.systemui.statusbar.pipeline.data.FakeStatusBarPipelineDataLayerModule
import com.android.systemui.statusbar.policy.data.FakeStatusBarPolicyDataLayerModule
import dagger.Module
@@ -25,7 +27,9 @@
includes =
[
FakeStatusBarDisableFlagsDataLayerModule::class,
+ FakeStatusBarModeRepositoryModule::class,
FakeStatusBarNotificationsDataLayerModule::class,
+ FakeStatusBarPhoneDataLayerModule::class,
FakeStatusBarPipelineDataLayerModule::class,
FakeStatusBarPolicyDataLayerModule::class,
]
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
similarity index 81%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
index 61ba464..f25d282 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
@@ -18,17 +18,17 @@
import com.android.systemui.statusbar.data.model.StatusBarAppearance
import com.android.systemui.statusbar.data.model.StatusBarMode
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeStatusBarModeRepository : StatusBarModeRepository {
+class FakeStatusBarModeRepository @Inject constructor() : StatusBarModeRepository {
override val isTransientShown = MutableStateFlow(false)
override val isInFullscreenMode = MutableStateFlow(false)
override val statusBarAppearance = MutableStateFlow<StatusBarAppearance?>(null)
override val statusBarMode = MutableStateFlow(StatusBarMode.TRANSPARENT)
- override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {}
-
override fun showTransient() {
isTransientShown.value = true
}
@@ -36,3 +36,8 @@
isTransientShown.value = false
}
}
+
+@Module
+interface FakeStatusBarModeRepositoryModule {
+ @Binds fun bindFake(fake: FakeStatusBarModeRepository): StatusBarModeRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/FakeStatusBarPhoneDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/FakeStatusBarPhoneDataLayerModule.kt
new file mode 100644
index 0000000..d2c3b7a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/FakeStatusBarPhoneDataLayerModule.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.phone.data
+
+import com.android.systemui.statusbar.phone.data.repository.FakeDarkIconRepositoryModule
+import dagger.Module
+
+@Module(includes = [FakeDarkIconRepositoryModule::class]) object FakeStatusBarPhoneDataLayerModule
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt
new file mode 100644
index 0000000..50d3f0a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.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.statusbar.phone.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+@SysUISingleton
+class FakeDarkIconRepository @Inject constructor() : DarkIconRepository {
+ override val darkState = MutableStateFlow(DarkChange(emptyList(), 0f, 0))
+}
+
+@Module
+interface FakeDarkIconRepositoryModule {
+ @Binds fun bindFake(fake: FakeDarkIconRepository): DarkIconRepository
+}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index b696998..4dca5a5 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -136,7 +136,6 @@
":display-device-config",
":display-layout-config",
":device-state-config",
- ":framework-core-nfc-infcadapter-sources",
"java/com/android/server/EventLogTags.logtags",
"java/com/android/server/am/EventLogTags.logtags",
"java/com/android/server/wm/EventLogTags.logtags",
@@ -149,6 +148,7 @@
"android.hardware.light-V2.0-java",
"android.hardware.gnss-V2-java",
"android.hardware.vibrator-V2-java",
+ "android.nfc.flags-aconfig-java",
"app-compat-annotations",
"framework-tethering.stubs.module_lib",
"service-art.stubs.system_server",
@@ -196,7 +196,6 @@
"android.hardware.rebootescrow-V1-java",
"android.hardware.power.stats-V2-java",
"android.hidl.manager-V1.2-java",
- "android.nfc.flags-aconfig-java",
"cbor-java",
"icu4j_calendar_astronomer",
"android.security.aaid_aidl-java",
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index beea063..8624dd5 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1236,7 +1236,7 @@
private void onUserStopped(int userId) {
Slog.d(TAG, "onUserStopped " + userId);
- Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ Watchdog.getInstance().pauseWatchingMonitorsFor(
SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#onUserStopped might be slow");
try {
mVold.onUserStopped(userId);
@@ -1320,7 +1320,7 @@
unlockedUsers.add(userId);
}
}
- Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ Watchdog.getInstance().pauseWatchingMonitorsFor(
SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#onUserStopped might be slow");
for (Integer userId : unlockedUsers) {
try {
@@ -2341,7 +2341,7 @@
try {
// TODO(b/135341433): Remove cautious logging when FUSE is stable
Slog.i(TAG, "Mounting volume " + vol);
- Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ Watchdog.getInstance().pauseWatchingMonitorsFor(
SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#mount might be slow");
mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() {
@Override
@@ -2472,7 +2472,7 @@
final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
- Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ Watchdog.getInstance().pauseWatchingMonitorsFor(
PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
try {
mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
@@ -2491,7 +2491,7 @@
final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
- Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ Watchdog.getInstance().pauseWatchingMonitorsFor(
PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
try {
mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1);
@@ -2510,7 +2510,7 @@
final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
- Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ Watchdog.getInstance().pauseWatchingMonitorsFor(
PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
try {
mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio);
@@ -3620,7 +3620,7 @@
@Override
public ParcelFileDescriptor open() throws AppFuseMountException {
- Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ Watchdog.getInstance().pauseWatchingMonitorsFor(
SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#open might be slow");
try {
final FileDescriptor fd = mVold.mountAppFuse(uid, mountId);
@@ -3634,7 +3634,7 @@
@Override
public ParcelFileDescriptor openFile(int mountId, int fileId, int flags)
throws AppFuseMountException {
- Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ Watchdog.getInstance().pauseWatchingMonitorsFor(
SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#openFile might be slow");
try {
return new ParcelFileDescriptor(
@@ -3646,7 +3646,7 @@
@Override
public void close() throws Exception {
- Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ Watchdog.getInstance().pauseWatchingMonitorsFor(
SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#close might be slow");
if (mMounted) {
mVold.unmountAppFuse(uid, mountId);
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 55aa716..003046a 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -250,7 +250,7 @@
private Monitor mCurrentMonitor;
private long mStartTimeMillis;
private int mPauseCount;
- private long mOneOffTimeoutMillis;
+ private long mPauseEndTimeMillis;
HandlerChecker(Handler handler, String name) {
mHandler = handler;
@@ -270,20 +270,19 @@
* @param handlerCheckerTimeoutMillis the timeout to use for this run
*/
public void scheduleCheckLocked(long handlerCheckerTimeoutMillis) {
- if (mOneOffTimeoutMillis > 0) {
- mWaitMaxMillis = mOneOffTimeoutMillis;
- mOneOffTimeoutMillis = 0;
- } else {
- mWaitMaxMillis = handlerCheckerTimeoutMillis;
- }
+ mWaitMaxMillis = handlerCheckerTimeoutMillis;
if (mCompleted) {
// Safe to update monitors in queue, Handler is not in the middle of work
mMonitors.addAll(mMonitorQueue);
mMonitorQueue.clear();
}
+
+ long nowMillis = SystemClock.uptimeMillis();
+ boolean isPaused = mPauseCount > 0
+ || (mPauseEndTimeMillis > 0 && mPauseEndTimeMillis < nowMillis);
if ((mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling())
- || (mPauseCount > 0)) {
+ || isPaused) {
// Don't schedule until after resume OR
// If the target looper has recently been polling, then
// there is no reason to enqueue our checker on it since that
@@ -301,7 +300,8 @@
mCompleted = false;
mCurrentMonitor = null;
- mStartTimeMillis = SystemClock.uptimeMillis();
+ mStartTimeMillis = nowMillis;
+ mPauseEndTimeMillis = 0;
mHandler.postAtFrontOfQueue(this);
}
@@ -360,20 +360,19 @@
}
/**
- * Sets the timeout of the HandlerChecker for one run.
+ * Pauses the checks for the given time.
*
- * <p>The current run will be ignored and the next run will be set to this timeout.
- *
- * <p>If a one off timeout is already set, the maximum timeout will be used.
+ * <p>The current run will be ignored and another run will be scheduled after
+ * the given time.
*/
- public void setOneOffTimeoutLocked(int temporaryTimeoutMillis, String reason) {
- mOneOffTimeoutMillis = Math.max(temporaryTimeoutMillis, mOneOffTimeoutMillis);
+ public void pauseForLocked(int pauseMillis, String reason) {
+ mPauseEndTimeMillis = SystemClock.uptimeMillis() + pauseMillis;
// Mark as completed, because there's a chance we called this after the watchog
// thread loop called Object#wait after 'WAITED_HALF'. In that case we want to ensure
// the next call to #getCompletionStateLocked for this checker returns 'COMPLETED'
mCompleted = true;
- Slog.i(TAG, "Extending timeout of HandlerChecker: " + mName + " for reason: "
- + reason + ". New timeout: " + mOneOffTimeoutMillis);
+ Slog.i(TAG, "Pausing of HandlerChecker: " + mName + " for reason: "
+ + reason + ". Pause end time: " + mPauseEndTimeMillis);
}
/** Pause the HandlerChecker. */
@@ -623,34 +622,32 @@
}
/**
- * Sets a one-off timeout for the next run of the watchdog for this thread. This is useful
+ * Pauses the checks of the watchdog for this thread. This is useful
* to run a slow operation on one of the monitored thread.
*
- * <p>After the next run, the timeout will go back to the default value.
- *
- * <p>If the current thread has not been added to the Watchdog, this call is a no-op.
- *
- * <p>If a one-off timeout for the current thread is already, the max value will be used.
+ * <p>After the given time, the timeout will go back to the default value.
+ * <p>This method does not require resume to be called.
*/
- public void setOneOffTimeoutForCurrentThread(int oneOffTimeoutMillis, String reason) {
+ public void pauseWatchingCurrentThreadFor(int pauseMillis, String reason) {
synchronized (mLock) {
for (HandlerCheckerAndTimeout hc : mHandlerCheckers) {
HandlerChecker checker = hc.checker();
if (Thread.currentThread().equals(checker.getThread())) {
- checker.setOneOffTimeoutLocked(oneOffTimeoutMillis, reason);
+ checker.pauseForLocked(pauseMillis, reason);
}
}
}
}
/**
- * Sets a one-off timeout for the next run of the watchdog for the monitor thread.
+ * Pauses the checks of the watchdog for the monitor thread for the given time
*
- * <p>Simiar to {@link setOneOffTimeoutForCurrentThread} but used for monitors added through
- * {@link #addMonitor}
+ * <p>Similar to {@link pauseWatchingCurrentThreadFor} but used for monitors added
+ * through {@link #addMonitor}
+ * <p>This method does not require resume to be called.
*/
- public void setOneOffTimeoutForMonitors(int oneOffTimeoutMillis, String reason) {
- mMonitorChecker.setOneOffTimeoutLocked(oneOffTimeoutMillis, reason);
+ public void pauseWatchingMonitorsFor(int pauseMillis, String reason) {
+ mMonitorChecker.pauseForLocked(pauseMillis, reason);
}
/**
@@ -664,7 +661,7 @@
* adds another pause and will require an additional {@link #resumeCurrentThread} to resume.
*
* <p>Note: Use with care, as any deadlocks on the current thread will be undetected until all
- * pauses have been resumed. Prefer to use #setOneOffTimeoutForCurrentThread.
+ * pauses have been resumed. Prefer to use #pauseWatchingCurrentThreadFor.
*/
public void pauseWatchingCurrentThread(String reason) {
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index e0e6cad..59d8e7e 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -869,6 +869,8 @@
ApplicationExitInfo.REASON_LOW_MEMORY,
ApplicationExitInfo.SUBREASON_OOM_KILL,
"oom");
+
+ oomKill.logKillOccurred();
}
}
}
diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java
index ba43c8d..292fc14 100644
--- a/services/core/java/com/android/server/audio/AdiDeviceState.java
+++ b/services/core/java/com/android/server/audio/AdiDeviceState.java
@@ -188,7 +188,7 @@
* {@link AdiDeviceState#toPersistableString()}.
*/
public static int getPeristedMaxSize() {
- return 36; /* (mDeviceType)2 + (mDeviceAddresss)17 + (mInternalDeviceType)9 + (mSAEnabled)1
+ return 36; /* (mDeviceType)2 + (mDeviceAddress)17 + (mInternalDeviceType)9 + (mSAEnabled)1
+ (mHasHeadTracker)1 + (mHasHeadTrackerEnabled)1
+ (SETTINGS_FIELD_SEPARATOR)5 */
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index d97c8e7..8c39d7d 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -16,6 +16,13 @@
package com.android.server.display;
+import static android.view.Display.TYPE_EXTERNAL;
+import static android.view.Display.TYPE_INTERNAL;
+import static android.view.Display.TYPE_OVERLAY;
+import static android.view.Display.TYPE_UNKNOWN;
+import static android.view.Display.TYPE_VIRTUAL;
+import static android.view.Display.TYPE_WIFI;
+
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
@@ -26,7 +33,10 @@
import com.android.server.display.feature.DisplayManagerFlags;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
class DisplayManagerShellCommand extends ShellCommand {
private static final String TAG = "DisplayManagerShellCommand";
@@ -153,9 +163,12 @@
pw.println(" Sets the user disabled HDR types as TYPES");
pw.println(" get-user-disabled-hdr-types");
pw.println(" Returns the user disabled HDR types");
- pw.println(" get-displays [CATEGORY]");
+ pw.println(" get-displays [-c|--category CATEGORY] [-i|--ids-only] [-t|--type TYPE]");
+ pw.println(" [CATEGORY]");
pw.println(" Returns the current displays. Can specify string category among");
pw.println(" DisplayManager.DISPLAY_CATEGORY_*; must use the actual string value.");
+ pw.println(" Can choose to print only the ids of the displays. " + "Can filter by");
+ pw.println(" display types. For example, '--type external'");
pw.println(" dock");
pw.println(" Sets brightness to docked + idle screen brightness mode");
pw.println(" undock");
@@ -171,17 +184,94 @@
}
private int getDisplays() {
- String category = getNextArg();
+ String opt = "", requestedType, category = null;
+ PrintWriter out = getOutPrintWriter();
+
+ List<Integer> displayTypeList = new ArrayList<>();
+ boolean showIdsOnly = false, filterByType = false;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-i":
+ case "--ids-only":
+ showIdsOnly = true;
+ break;
+ case "-t":
+ case "--type":
+ requestedType = getNextArgRequired();
+ int displayType = getType(requestedType, out);
+ if (displayType == -1) {
+ return 1;
+ }
+ displayTypeList.add(displayType);
+ filterByType = true;
+ break;
+ case "-c":
+ case "--category":
+ if (category != null) {
+ out.println("Error: the category has been specified more than one time. "
+ + "Please select only one category.");
+ return 1;
+ }
+ category = getNextArgRequired();
+ break;
+ case "":
+ break;
+ default:
+ out.println("Error: unknown option '" + opt + "'");
+ return 1;
+ }
+ }
+
+ String lastCategoryArgument = getNextArg();
+ if (lastCategoryArgument != null) {
+ if (category != null) {
+ out.println("Error: the category has been specified both with the -c option and "
+ + "the positional argument. Please select only one category.");
+ return 1;
+ }
+ category = lastCategoryArgument;
+ }
+
DisplayManager dm = mService.getContext().getSystemService(DisplayManager.class);
Display[] displays = dm.getDisplays(category);
- PrintWriter out = getOutPrintWriter();
- out.println("Displays:");
+
+ if (filterByType) {
+ displays = Arrays.stream(displays).filter(d -> displayTypeList.contains(d.getType()))
+ .toArray(Display[]::new);
+ }
+
+ if (!showIdsOnly) {
+ out.println("Displays:");
+ }
for (int i = 0; i < displays.length; i++) {
- out.println(" " + displays[i]);
+ out.println((showIdsOnly ? displays[i].getDisplayId() : displays[i]));
}
return 0;
}
+ private int getType(String type, PrintWriter out) {
+ type = type.toUpperCase(Locale.ENGLISH);
+ switch (type) {
+ case "UNKNOWN":
+ return TYPE_UNKNOWN;
+ case "INTERNAL":
+ return TYPE_INTERNAL;
+ case "EXTERNAL":
+ return TYPE_EXTERNAL;
+ case "WIFI":
+ return TYPE_WIFI;
+ case "OVERLAY":
+ return TYPE_OVERLAY;
+ case "VIRTUAL":
+ return TYPE_VIRTUAL;
+ default:
+ out.println("Error: argument for display type should be "
+ + "one of 'UNKNOWN', 'INTERNAL', 'EXTERNAL', 'WIFI', 'OVERLAY', 'VIRTUAL', "
+ + "but got '" + type + "' instead.");
+ return -1;
+ }
+ }
+
private int showNotification() {
final String notificationType = getNextArg();
if (notificationType == null) {
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index ce35a61..da1724d835 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -76,7 +76,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.Watchdog;
@@ -385,10 +384,12 @@
if (mProjectionGrant != null) {
// Cache the session details.
mProjectionGrant.mSession = incomingSession;
- mMediaProjectionMetricsLogger.notifyProjectionStateChange(
- mProjectionGrant.uid,
- FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS,
- FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ if (incomingSession != null) {
+ // Only log in progress when session is not null.
+ // setContentRecordingSession is called with a null session for the stop case.
+ mMediaProjectionMetricsLogger.logInProgress(
+ mProjectionGrant.uid, incomingSession.getTargetUid());
+ }
dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession);
}
return true;
@@ -463,6 +464,11 @@
mMediaProjectionMetricsLogger.logInitiated(hostUid, sessionCreationSource);
}
+ @VisibleForTesting
+ void notifyPermissionRequestDisplayed(int hostUid) {
+ mMediaProjectionMetricsLogger.logPermissionRequestDisplayed(hostUid);
+ }
+
/**
* Handles result of dialog shown from
* {@link BinderService#buildReviewGrantedConsentIntentLocked()}.
@@ -866,6 +872,18 @@
}
@Override // Binder call
+ @EnforcePermission(MANAGE_MEDIA_PROJECTION)
+ public void notifyPermissionRequestDisplayed(int hostUid) {
+ notifyPermissionRequestDisplayed_enforcePermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ MediaProjectionManagerService.this.notifyPermissionRequestDisplayed(hostUid);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
index c8b932a..aee0b31 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
@@ -16,6 +16,12 @@
package com.android.server.media.projection;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED;
+
import android.content.Context;
import com.android.internal.util.FrameworkStatsLog;
@@ -82,25 +88,59 @@
: (int) durationSinceLastActiveSession.toSeconds();
write(
mSessionIdGenerator.createAndGetNewSessionId(),
- FrameworkStatsLog
- .MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED,
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED,
hostUid,
TARGET_UID_UNKNOWN,
timeSinceLastActiveInSeconds,
sessionCreationSource);
}
- /** Logs that the capturing stopped, either normally or because of error. */
- public void logStopped(int hostUid, int targetUid) {
+ /**
+ * Logs that the user entered the setup flow and permission dialog is displayed. This state is
+ * not sent when the permission is already granted and we skipped showing the permission dialog.
+ *
+ * @param hostUid UID of the package that initiates MediaProjection.
+ */
+ public void logPermissionRequestDisplayed(int hostUid) {
write(
mSessionIdGenerator.getCurrentSessionId(),
- FrameworkStatsLog
- .MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED,
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED,
+ hostUid,
+ TARGET_UID_UNKNOWN,
+ TIME_SINCE_LAST_ACTIVE_UNKNOWN,
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ }
+
+ /**
+ * Logs that the virtual display is created and capturing the selected region begins.
+ *
+ * @param hostUid UID of the package that initiates MediaProjection.
+ * @param targetUid UID of the package that is captured if selected.
+ */
+ public void logInProgress(int hostUid, int targetUid) {
+ write(
+ mSessionIdGenerator.getCurrentSessionId(),
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS,
hostUid,
targetUid,
TIME_SINCE_LAST_ACTIVE_UNKNOWN,
- FrameworkStatsLog
- .MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ }
+
+ /**
+ * Logs that the capturing stopped, either normally or because of error.
+ *
+ * @param hostUid UID of the package that initiates MediaProjection.
+ * @param targetUid UID of the package that is captured if selected.
+ */
+ public void logStopped(int hostUid, int targetUid) {
+ write(
+ mSessionIdGenerator.getCurrentSessionId(),
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED,
+ hostUid,
+ targetUid,
+ TIME_SINCE_LAST_ACTIVE_UNKNOWN,
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
mTimestampStore.registerActiveSessionEnded();
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 452be2f4..7ca5699 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -290,7 +290,6 @@
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.MetricsLogger;
@@ -1180,7 +1179,7 @@
@Override
public void onSetDisabled(int status) {
synchronized (mNotificationLock) {
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.updateDisableNotificationEffectsLocked(status);
} else {
mDisableNotificationEffects =
@@ -1326,7 +1325,7 @@
public void clearEffects() {
synchronized (mNotificationLock) {
if (DBG) Slog.d(TAG, "clearEffects");
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.clearAttentionEffects();
} else {
clearSoundLocked();
@@ -1555,8 +1554,7 @@
int changedFlags = data.getFlags() ^ flags;
if ((changedFlags & FLAG_SUPPRESS_NOTIFICATION) != 0) {
// Suppress notification flag changed, clear any effects
- if (mFlagResolver.isEnabled(
- NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.clearEffectsLocked(key);
} else {
clearEffectsLocked(key);
@@ -1905,7 +1903,7 @@
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (!Flags.refactorAttentionHelper()) {
if (action.equals(Intent.ACTION_SCREEN_ON)) {
// Keep track of screen on/off state, but do not turn off the notification light
// until user passes through the lock screen or views the notification.
@@ -2019,7 +2017,7 @@
ContentResolver resolver = getContext().getContentResolver();
resolver.registerContentObserver(NOTIFICATION_BADGING_URI,
false, this, UserHandle.USER_ALL);
- if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (!Flags.refactorAttentionHelper()) {
resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
false, this, UserHandle.USER_ALL);
}
@@ -2045,7 +2043,7 @@
public void update(Uri uri) {
ContentResolver resolver = getContext().getContentResolver();
- if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (!Flags.refactorAttentionHelper()) {
if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) {
boolean pulseEnabled = Settings.System.getIntForUser(resolver,
Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT)
@@ -2538,7 +2536,7 @@
mToastRateLimiter = toastRateLimiter;
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper = new NotificationAttentionHelper(getContext(), lightsManager,
mAccessibilityManager, mPackageManagerClient, userManager, usageStats,
mNotificationManagerPrivate, mZenModeHelper, flagResolver);
@@ -2548,7 +2546,7 @@
// If this is called within a test, make sure to unregister the intent receivers by
// calling onDestroy()
IntentFilter filter = new IntentFilter();
- if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (!Flags.refactorAttentionHelper()) {
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
@@ -2876,7 +2874,7 @@
}
registerNotificationPreferencesPullers();
new LockPatternUtils(getContext()).registerStrongAuthTracker(mStrongAuthTracker);
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.onSystemReady();
}
} else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
@@ -6501,7 +6499,7 @@
pw.println(" mMaxPackageEnqueueRate=" + mMaxPackageEnqueueRate);
pw.println(" hideSilentStatusBar="
+ mPreferencesHelper.shouldHideSilentStatusIcons());
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.dump(pw, " ", filter);
}
}
@@ -7767,7 +7765,7 @@
boolean wasPosted = removeFromNotificationListsLocked(r);
cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null,
SystemClock.elapsedRealtime());
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.updateLightsLocked();
} else {
updateLightsLocked();
@@ -7900,7 +7898,7 @@
cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
mSendDelete, childrenFlagChecker, mReason,
mCancellationElapsedTimeMs);
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.updateLightsLocked();
} else {
updateLightsLocked();
@@ -8197,7 +8195,7 @@
int buzzBeepBlinkLoggingCode = 0;
if (!r.isHidden()) {
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
buzzBeepBlinkLoggingCode = mAttentionHelper.buzzBeepBlinkLocked(r,
new NotificationAttentionHelper.Signals(
mUserProfiles.isCurrentProfile(r.getUserId()),
@@ -9184,7 +9182,7 @@
|| interruptiveChanged;
if (interceptBefore && !record.isIntercepted()
&& record.isNewEnoughForAlerting(System.currentTimeMillis())) {
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.buzzBeepBlinkLocked(record,
new NotificationAttentionHelper.Signals(
mUserProfiles.isCurrentProfile(record.getUserId()), mListenerHints));
@@ -9564,7 +9562,7 @@
});
}
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.clearEffectsLocked(canceledKey);
} else {
// sound
@@ -9928,7 +9926,7 @@
cancellationElapsedTimeMs);
}
}
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.updateLightsLocked();
} else {
updateLightsLocked();
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index a161e8c..1135466 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -65,6 +65,7 @@
import android.content.pm.LauncherActivityInfoInternal;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.LauncherUserInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
@@ -1377,6 +1378,25 @@
}
@Override
+ public @Nullable LauncherUserInfo getLauncherUserInfo(@NonNull UserHandle user) {
+ // Only system launchers, which have access to recents should have access to this API.
+ // TODO(b/303803157): Add the new permission check if we decide to have one.
+ if (!mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())) {
+ throw new SecurityException("Caller is not the recents app");
+ }
+ if (!canAccessProfile(user.getIdentifier(),
+ "Can't access LauncherUserInfo for another user")) {
+ return null;
+ }
+ long ident = injectClearCallingIdentity();
+ try {
+ return mUserManagerInternal.getLauncherUserInfo(user.getIdentifier());
+ } finally {
+ injectRestoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public void startActivityAsUser(IApplicationThread caller, String callingPackage,
String callingFeatureId, ComponentName component, Rect sourceBounds,
Bundle opts, UserHandle user) throws RemoteException {
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 04cd183..0e7ce2e 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
+import android.content.pm.LauncherUserInfo;
import android.content.pm.UserInfo;
import android.content.pm.UserProperties;
import android.graphics.Bitmap;
@@ -407,6 +408,11 @@
public abstract @NonNull UserInfo[] getUserInfos();
/**
+ * Gets a {@link LauncherUserInfo} for the given {@code userId}, or {@code null} if not found.
+ */
+ public abstract @Nullable LauncherUserInfo getLauncherUserInfo(@UserIdInt int userId);
+
+ /**
* Sets all default cross profile intent filters between {@code parentUserId} and
* {@code profileUserId}.
*/
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7331bc1..154ee6e 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -62,6 +62,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
+import android.content.pm.LauncherUserInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
@@ -7153,6 +7154,24 @@
}
@Override
+ public @Nullable LauncherUserInfo getLauncherUserInfo(@UserIdInt int userId) {
+ UserInfo userInfo;
+ synchronized (mUsersLock) {
+ userInfo = getUserInfoLU(userId);
+ }
+ if (userInfo != null) {
+ final UserTypeDetails userDetails = getUserTypeDetails(userInfo);
+ final LauncherUserInfo uiInfo = new LauncherUserInfo.Builder(
+ userDetails.getName(),
+ userInfo.serialNumber)
+ .build();
+ return uiInfo;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
int state;
synchronized (mUserStates) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3a6664a..7c0fc99 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -139,6 +139,7 @@
import android.os.FactoryTest;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager.WakeReason;
@@ -715,6 +716,11 @@
private static final int MSG_LOG_KEYBOARD_SYSTEM_EVENT = 26;
private class PolicyHandler extends Handler {
+
+ private PolicyHandler(Looper looper) {
+ super(looper);
+ }
+
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -2166,10 +2172,12 @@
static class Injector {
private final Context mContext;
private final WindowManagerFuncs mWindowManagerFuncs;
+ private final Looper mLooper;
- Injector(Context context, WindowManagerFuncs funcs) {
+ Injector(Context context, WindowManagerFuncs funcs, Looper looper) {
mContext = context;
mWindowManagerFuncs = funcs;
+ mLooper = looper;
}
Context getContext() {
@@ -2180,6 +2188,10 @@
return mWindowManagerFuncs;
}
+ Looper getLooper() {
+ return mLooper;
+ }
+
AccessibilityShortcutController getAccessibilityShortcutController(
Context context, Handler handler, int initialUserId) {
return new AccessibilityShortcutController(context, handler, initialUserId);
@@ -2208,7 +2220,7 @@
/** {@inheritDoc} */
@Override
public void init(Context context, WindowManagerFuncs funcs) {
- init(new Injector(context, funcs));
+ init(new Injector(context, funcs, Looper.myLooper()));
}
@VisibleForTesting
@@ -2284,7 +2296,7 @@
mContext, minHorizontal, maxHorizontal, minVertical, maxVertical, maxRadius);
}
- mHandler = new PolicyHandler();
+ mHandler = new PolicyHandler(injector.getLooper());
mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler);
mSettingsObserver = new SettingsObserver(mHandler);
mSettingsObserver.observe();
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 06448d0..022ef61 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -28,6 +28,7 @@
import android.annotation.Nullable;
import android.content.res.Configuration;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.media.projection.IMediaProjectionManager;
import android.os.IBinder;
@@ -36,10 +37,12 @@
import android.view.ContentRecordingSession;
import android.view.ContentRecordingSession.RecordContent;
import android.view.Display;
+import android.view.DisplayInfo;
import android.view.SurfaceControl;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.display.feature.DisplayManagerFlags;
/**
* Manages content recording for a particular {@link DisplayContent}.
@@ -47,6 +50,16 @@
final class ContentRecorder implements WindowContainerListener {
/**
+ * Maximum acceptable anisotropy for the output image.
+ *
+ * Necessary to avoid unnecessary scaling when the anisotropy is almost the same, as it is not
+ * exact anyway. For external displays, we expect an anisoptry of about 2% even if the pixels
+ * are, in fact, square due to the imprecision of the display's actual size (rounded to the
+ * nearest cm).
+ */
+ private static final float MAX_ANISOTROPY = 0.025f;
+
+ /**
* The display content this class is handling recording for.
*/
@NonNull
@@ -87,15 +100,20 @@
@Configuration.Orientation
private int mLastOrientation = ORIENTATION_UNDEFINED;
+ private final boolean mCorrectForAnisotropicPixels;
+
ContentRecorder(@NonNull DisplayContent displayContent) {
- this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId));
+ this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId),
+ new DisplayManagerFlags().isConnectedDisplayManagementEnabled());
}
@VisibleForTesting
ContentRecorder(@NonNull DisplayContent displayContent,
- @NonNull MediaProjectionManagerWrapper mediaProjectionManager) {
+ @NonNull MediaProjectionManagerWrapper mediaProjectionManager,
+ boolean correctForAnisotropicPixels) {
mDisplayContent = displayContent;
mMediaProjectionManager = mediaProjectionManager;
+ mCorrectForAnisotropicPixels = correctForAnisotropicPixels;
}
/**
@@ -460,6 +478,33 @@
}
}
+ private void computeScaling(int inputSizeX, int inputSizeY,
+ float inputDpiX, float inputDpiY,
+ int outputSizeX, int outputSizeY,
+ float outputDpiX, float outputDpiY,
+ PointF scaleOut) {
+ float relAnisotropy = (inputDpiY / inputDpiX) / (outputDpiY / outputDpiX);
+ if (!mCorrectForAnisotropicPixels
+ || (relAnisotropy > (1 - MAX_ANISOTROPY) && relAnisotropy < (1 + MAX_ANISOTROPY))) {
+ // Calculate the scale to apply to the root mirror SurfaceControl to fit the size of the
+ // output surface.
+ float scaleX = outputSizeX / (float) inputSizeX;
+ float scaleY = outputSizeY / (float) inputSizeY;
+ float scale = Math.min(scaleX, scaleY);
+ scaleOut.x = scale;
+ scaleOut.y = scale;
+ return;
+ }
+
+ float relDpiX = outputDpiX / inputDpiX;
+ float relDpiY = outputDpiY / inputDpiY;
+
+ float scale = Math.min(outputSizeX / relDpiX / inputSizeX,
+ outputSizeY / relDpiY / inputSizeY);
+ scaleOut.x = scale * relDpiX;
+ scaleOut.y = scale * relDpiY;
+ }
+
/**
* Apply transformations to the mirrored surface to ensure the captured contents are scaled to
* fit and centred in the output surface.
@@ -473,13 +518,19 @@
*/
@VisibleForTesting void updateMirroredSurface(SurfaceControl.Transaction transaction,
Rect recordedContentBounds, Point surfaceSize) {
- // Calculate the scale to apply to the root mirror SurfaceControl to fit the size of the
- // output surface.
- float scaleX = surfaceSize.x / (float) recordedContentBounds.width();
- float scaleY = surfaceSize.y / (float) recordedContentBounds.height();
- float scale = Math.min(scaleX, scaleY);
- int scaledWidth = Math.round(scale * (float) recordedContentBounds.width());
- int scaledHeight = Math.round(scale * (float) recordedContentBounds.height());
+
+ DisplayInfo inputDisplayInfo = mRecordedWindowContainer.mDisplayContent.getDisplayInfo();
+ DisplayInfo outputDisplayInfo = mDisplayContent.getDisplayInfo();
+
+ PointF scale = new PointF();
+ computeScaling(recordedContentBounds.width(), recordedContentBounds.height(),
+ inputDisplayInfo.physicalXDpi, inputDisplayInfo.physicalYDpi,
+ surfaceSize.x, surfaceSize.y,
+ outputDisplayInfo.physicalXDpi, outputDisplayInfo.physicalYDpi,
+ scale);
+
+ int scaledWidth = Math.round(scale.x * (float) recordedContentBounds.width());
+ int scaledHeight = Math.round(scale.y * (float) recordedContentBounds.height());
// Calculate the shift to apply to the root mirror SurfaceControl to centre the mirrored
// contents in the output surface.
@@ -493,10 +544,10 @@
}
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Content Recording: Apply transformations of shift %d x %d, scale %f, crop (aka "
- + "recorded content size) %d x %d for display %d; display has size %d x "
- + "%d; surface has size %d x %d",
- shiftedX, shiftedY, scale, recordedContentBounds.width(),
+ "Content Recording: Apply transformations of shift %d x %d, scale %f x %f, crop "
+ + "(aka recorded content size) %d x %d for display %d; display has size "
+ + "%d x %d; surface has size %d x %d",
+ shiftedX, shiftedY, scale.x, scale.y, recordedContentBounds.width(),
recordedContentBounds.height(), mDisplayContent.getDisplayId(),
mDisplayContent.getConfiguration().screenWidthDp,
mDisplayContent.getConfiguration().screenHeightDp, surfaceSize.x, surfaceSize.y);
@@ -508,7 +559,7 @@
recordedContentBounds.height())
// Scale the root mirror SurfaceControl, based upon the size difference between the
// source (DisplayArea to capture) and output (surface the app reads images from).
- .setMatrix(mRecordedSurface, scale, 0 /* dtdx */, 0 /* dtdy */, scale)
+ .setMatrix(mRecordedSurface, scale.x, 0 /* dtdx */, 0 /* dtdy */, scale.y)
// Position needs to be updated when the mirrored DisplayArea has changed, since
// the content will no longer be centered in the output surface.
.setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */);
diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp
index 6eacef7..c617ec4 100644
--- a/services/tests/PackageManagerServiceTests/host/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/Android.bp
@@ -58,6 +58,7 @@
":PackageManagerTestOverlayTarget",
":PackageManagerTestOverlayTargetNoOverlayable",
":PackageManagerTestAppDeclaresStaticLibrary",
+ ":PackageManagerTestAppDifferentPkgName",
":PackageManagerTestAppStub",
":PackageManagerTestAppUsesStaticLibrary",
":PackageManagerTestAppVersion1",
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt
index c490604..304f605 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt
@@ -44,6 +44,10 @@
private const val VERSION_TWO_ALT_KEY = "PackageManagerTestAppVersion2AltKey.apk"
private const val VERSION_TWO_ALT_KEY_IDSIG =
"PackageManagerTestAppVersion2AltKey.apk.idsig"
+
+ private const val ANOTHER_PKG_NAME = "com.android.server.pm.test.test_app2"
+ private const val ANOTHER_PKG = "PackageManagerTestAppDifferentPkgName.apk"
+
private const val STRICT_SIGNATURE_CONFIG_PATH =
"/system/etc/sysconfig/preinstalled-packages-strict-signature.xml"
private const val TIMESTAMP_REFERENCE_FILE_PATH = "/data/local/tmp/timestamp.ref"
@@ -74,6 +78,7 @@
@After
fun removeApk() {
device.uninstallPackage(TEST_PKG_NAME)
+ device.uninstallPackage(ANOTHER_PKG_NAME)
}
@Before
@@ -90,7 +95,9 @@
.readText()
.replace(
"</config>",
- "<require-strict-signature package=\"${TEST_PKG_NAME}\"/></config>"
+ "<require-strict-signature package=\"${TEST_PKG_NAME}\"/>" +
+ "<require-strict-signature package=\"${ANOTHER_PKG_NAME}\"/>" +
+ "</config>"
)
writeText(newConfigText)
}
@@ -146,10 +153,7 @@
tempFolder.newFile()
)
assertThat(device.installPackage(versionTwoFile, true)).isNull()
- val baseApkPath = device.executeShellCommand("pm path ${TEST_PKG_NAME}")
- .lineSequence()
- .first()
- .replace("package:", "")
+ val baseApkPath = getBaseApkPath(TEST_PKG_NAME)
assertThat(baseApkPath).doesNotContain(productPath.toString())
preparer.pushResourceFile(VERSION_TWO_ALT_KEY_IDSIG, baseApkPath.toString() + ".idsig")
@@ -175,4 +179,23 @@
assertThat(device.executeShellCommand("pm path ${TEST_PKG_NAME}"))
.contains(productPath.toString())
}
+
+ @Test
+ fun allowlistedPackageIsNotASystemApp() {
+ // If an allowlisted package isn't a system app, make sure install and boot still works
+ // normally.
+ assertThat(device.installJavaResourceApk(tempFolder, ANOTHER_PKG, /* reinstall */ false))
+ .isNull()
+ assertThat(getBaseApkPath(ANOTHER_PKG_NAME)).startsWith("/data/app/")
+
+ preparer.reboot()
+ assertThat(getBaseApkPath(ANOTHER_PKG_NAME)).startsWith("/data/app/")
+ }
+
+ private fun getBaseApkPath(pkgName: String): String {
+ return device.executeShellCommand("pm path $pkgName")
+ .lineSequence()
+ .first()
+ .replace("package:", "")
+ }
}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
index bee7c40..b826590 100644
--- a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
@@ -76,3 +76,11 @@
certificate: ":FrameworksServicesTests_keyset_A_cert",
v4_signature: true,
}
+
+android_test_helper_app {
+ name: "PackageManagerTestAppDifferentPkgName",
+ manifest: "AndroidManifestDifferentPkgName.xml",
+ srcs: [
+ "src/**/*.kt",
+ ],
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/AndroidManifestDifferentPkgName.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/AndroidManifestDifferentPkgName.xml
new file mode 100644
index 0000000..0c5d36e
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/AndroidManifestDifferentPkgName.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.pm.test.test_app2"
+ android:versionCode="1"
+ >
+
+ <permission
+ android:name="com.android.server.pm.test.test_app.TEST_PERMISSION"
+ android:protectionLevel="normal"
+ />
+
+ <application>
+ <activity android:name="com.android.server.pm.test.test_app.TestActivity"
+ android:label="PackageManagerTestApp" />
+ </application>
+
+</manifest>
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 6bfd93b..4bb7d63 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -4,6 +4,8 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static com.android.server.job.JobStore.JOB_FILE_SPLIT_PREFIX;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -46,6 +48,7 @@
import org.junit.runner.RunWith;
import java.io.File;
+import java.nio.file.Files;
import java.time.Clock;
import java.time.ZoneOffset;
import java.util.ArrayList;
@@ -209,6 +212,43 @@
assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size());
}
+ @Test
+ public void testSkipExtraFiles() throws Exception {
+ setUseSplitFiles(true);
+ final JobInfo task1 = new Builder(8, mComponent)
+ .setRequiresDeviceIdle(true)
+ .setPeriodic(10000L)
+ .setRequiresCharging(true)
+ .setPersisted(true)
+ .build();
+ final JobInfo task2 = new Builder(12, mComponent)
+ .setMinimumLatency(5000L)
+ .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR)
+ .setOverrideDeadline(30000L)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+ .setPersisted(true)
+ .build();
+ final int uid1 = SOME_UID;
+ final int uid2 = uid1 + 1;
+ final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null, null);
+ final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null, null);
+ runWritingJobsToDisk(JobStatus1, JobStatus2);
+
+ final File rootDir = new File(mTestContext.getFilesDir(), "system/job");
+ final File file1 = new File(rootDir, JOB_FILE_SPLIT_PREFIX + uid1 + ".xml");
+ final File file2 = new File(rootDir, JOB_FILE_SPLIT_PREFIX + uid2 + ".xml");
+
+ Files.copy(file1.toPath(),
+ new File(rootDir, JOB_FILE_SPLIT_PREFIX + uid1 + ".xml.bak").toPath());
+ Files.copy(file1.toPath(), new File(rootDir, "random.xml").toPath());
+ Files.copy(file2.toPath(),
+ new File(rootDir, "blah" + JOB_FILE_SPLIT_PREFIX + uid1 + ".xml").toPath());
+
+ JobSet jobStatusSet = new JobSet();
+ mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+ assertEquals("Incorrect # of persisted tasks.", 2, jobStatusSet.size());
+ }
+
/**
* Test that dynamic constraints aren't written to disk.
*/
@@ -254,22 +294,22 @@
file = new File(mTestContext.getFilesDir(), "10000");
assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
- file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX);
+ file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX);
assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
- file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "text.xml");
+ file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + "text.xml");
assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
- file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + ".xml");
+ file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + ".xml");
assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
- file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "-10123.xml");
+ file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + "-10123.xml");
assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
- file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "1.xml");
+ file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + "1.xml");
assertEquals(1, JobStore.extractUidFromJobFileName(file));
- file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "101023.xml");
+ file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + "101023.xml");
assertEquals(101023, JobStore.extractUidFromJobFileName(file));
}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index 9f6d7f2..fbf21c5 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -26,11 +26,10 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
-
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
@@ -69,7 +68,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.testutils.OffsettableClock;
import com.android.server.wm.WindowManagerInternal;
@@ -664,6 +662,17 @@
verify(mMediaProjectionMetricsLogger).logInitiated(hostUid, sessionCreationSource);
}
+ @Test
+ public void notifyPermissionRequestDisplayed_forwardsToLogger() {
+ int hostUid = 123;
+ mService =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+
+ mService.notifyPermissionRequestDisplayed(hostUid);
+
+ verify(mMediaProjectionMetricsLogger).logPermissionRequestDisplayed(hostUid);
+ }
+
/**
* Executes and validates scenario where the consent result indicates the projection ends.
*/
@@ -837,10 +846,69 @@
service.setContentRecordingSession(DISPLAY_SESSION);
- verify(mMediaProjectionMetricsLogger).notifyProjectionStateChange(
+ verify(mMediaProjectionMetricsLogger).logInProgress(
projection.uid,
- MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS,
- FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN
+ DISPLAY_SESSION.getTargetUid()
+ );
+ }
+
+ @Test
+ public void setContentRecordingSession_taskSession_logsCaptureInProgressWithTargetUid()
+ throws Exception {
+ mService.addCallback(mWatcherCallback);
+ MediaProjectionManagerService service =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+ MediaProjectionManagerService.MediaProjection projection =
+ startProjectionPreconditions(service);
+ projection.start(mIMediaProjectionCallback);
+ doReturn(true)
+ .when(mWindowManagerInternal)
+ .setContentRecordingSession(any(ContentRecordingSession.class));
+ int targetUid = 123455;
+
+ ContentRecordingSession taskSession =
+ ContentRecordingSession.createTaskSession(mock(IBinder.class), targetUid);
+ service.setContentRecordingSession(taskSession);
+
+ verify(mMediaProjectionMetricsLogger).logInProgress(projection.uid, targetUid);
+ }
+
+ @Test
+ public void setContentRecordingSession_failure_doesNotLogCaptureInProgress() throws Exception {
+ mService.addCallback(mWatcherCallback);
+ MediaProjectionManagerService service =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+ MediaProjectionManagerService.MediaProjection projection =
+ startProjectionPreconditions(service);
+ projection.start(mIMediaProjectionCallback);
+ doReturn(false).when(mWindowManagerInternal).setContentRecordingSession(
+ any(ContentRecordingSession.class));
+
+ service.setContentRecordingSession(DISPLAY_SESSION);
+
+ verify(mMediaProjectionMetricsLogger, never()).logInProgress(
+ anyInt(),
+ anyInt()
+ );
+ }
+
+ @Test
+ public void setContentRecordingSession_sessionNull_doesNotLogCaptureInProgress()
+ throws Exception {
+ mService.addCallback(mWatcherCallback);
+ MediaProjectionManagerService service =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+ MediaProjectionManagerService.MediaProjection projection =
+ startProjectionPreconditions(service);
+ projection.start(mIMediaProjectionCallback);
+ doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+ any(ContentRecordingSession.class));
+
+ service.setContentRecordingSession(null);
+
+ verify(mMediaProjectionMetricsLogger, never()).logInProgress(
+ anyInt(),
+ anyInt()
);
}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java
index 73b4cc8..8ba3ac9 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java
@@ -17,7 +17,9 @@
package com.android.server.media.projection;
import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED;
import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED;
import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN;
@@ -233,6 +235,152 @@
inOrder.verify(mTimestampStore).registerActiveSessionEnded();
}
+ @Test
+ public void logInProgress_logsStateChangedAtomId() {
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyStateChangedAtomIdLogged();
+ }
+
+ @Test
+ public void logInProgress_logsCurrentSessionId() {
+ int currentSessionId = 987;
+ when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(currentSessionId);
+
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifySessionIdLogged(currentSessionId);
+ }
+
+ @Test
+ public void logInProgress_logsStateInProgress() {
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS);
+ }
+
+ @Test
+ public void logInProgress_logsHostUid() {
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyHostUidLogged(TEST_HOST_UID);
+ }
+
+ @Test
+ public void logInProgress_logsTargetUid() {
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyTargetUidLogged(TEST_TARGET_UID);
+ }
+
+ @Test
+ public void logInProgress_logsUnknownTimeSinceLastActive() {
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyTimeSinceLastActiveSessionLogged(-1);
+ }
+
+ @Test
+ public void logInProgress_logsUnknownSessionCreationSource() {
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyCreationSourceLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ }
+
+ @Test
+ public void logInProgress_logsPreviousState() {
+ mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN);
+
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+
+ mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS);
+
+ mLogger.logInProgress(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsStateChangedAtomId() {
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifyStateChangedAtomIdLogged();
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsCurrentSessionId() {
+ int currentSessionId = 765;
+ when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(currentSessionId);
+
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifySessionIdLogged(currentSessionId);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsStateDisplayed() {
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifyStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsHostUid() {
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifyHostUidLogged(TEST_HOST_UID);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsUnknownTargetUid() {
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifyTargetUidLogged(-2);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsUnknownTimeSinceLastActive() {
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifyTimeSinceLastActiveSessionLogged(-1);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsUnknownSessionCreationSource() {
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifyCreationSourceLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsPreviousState() {
+ mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN);
+
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+
+ mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED);
+
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED);
+ }
+
private void verifyStateChangedAtomIdLogged() {
verify(mFrameworkStatsLogWrapper)
.write(
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 5dfce06..89c6a220 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -974,7 +974,6 @@
assertThrows(SecurityException.class, userProps::getAlwaysVisible);
}
-
// Make sure only max managed profiles can be created
@MediumTest
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 9bd938f..cf8548c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -80,7 +80,9 @@
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
import android.view.accessibility.IAccessibilityManagerClient;
+
import androidx.test.runner.AndroidJUnit4;
+
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
import com.android.internal.config.sysui.TestableFlagResolver;
import com.android.internal.logging.InstanceIdSequence;
@@ -93,6 +95,7 @@
import java.util.List;
import java.util.Objects;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -190,7 +193,7 @@
assertTrue(mAccessibilityManager.isEnabled());
// TODO (b/291907312): remove feature flag
- mTestFlagResolver.setFlagOverride(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR, true);
+ mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER);
// Disable feature flags by default. Tests should enable as needed.
mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_EXPIRE_BITMAPS);
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 16f97f0..6792cfe 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -85,7 +85,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.ENABLE_ATTENTION_HELPER_REFACTOR;
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;
@@ -614,7 +613,8 @@
});
// TODO (b/291907312): remove feature flag
- mTestFlagResolver.setFlagOverride(ENABLE_ATTENTION_HELPER_REFACTOR, false);
+ mSetFlagsRule.disableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER,
+ Flags.FLAG_POLITE_NOTIFICATIONS);
initNMS();
}
@@ -655,7 +655,7 @@
verify(mHistoryManager).onBootPhaseAppsCanStart();
// TODO b/291907312: remove feature flag
- if (mTestFlagResolver.isEnabled(ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mService.mAttentionHelper.setAudioManager(mAudioManager);
} else {
mService.setAudioManager(mAudioManager);
@@ -1692,7 +1692,7 @@
@Test
public void testEnqueueNotificationWithTag_WritesExpectedLogs_NAHRefactor() throws Exception {
// TODO b/291907312: remove feature flag
- mTestFlagResolver.setFlagOverride(ENABLE_ATTENTION_HELPER_REFACTOR, true);
+ mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER);
// Cleanup NMS before re-initializing
if (mService != null) {
try {
@@ -9155,7 +9155,7 @@
public void testOnBubbleMetadataChangedToSuppressNotification_soundStopped_NAHRefactor()
throws Exception {
// TODO b/291907312: remove feature flag
- mTestFlagResolver.setFlagOverride(ENABLE_ATTENTION_HELPER_REFACTOR, true);
+ mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER);
// Cleanup NMS before re-initializing
if (mService != null) {
try {
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 8db09f9..61c4d06 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -46,7 +46,6 @@
import static java.util.Collections.unmodifiableMap;
import android.content.Context;
-import android.os.Looper;
import android.os.SystemClock;
import android.util.ArrayMap;
import android.view.InputDevice;
@@ -99,10 +98,6 @@
* settings values.
*/
protected final void setUpPhoneWindowManager(boolean supportSettingsUpdate) {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
doReturn(mSettingsProviderRule.mockContentResolver(mContext))
.when(mContext).getContentResolver();
mPhoneWindowManager = new TestPhoneWindowManager(mContext, supportSettingsUpdate);
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 2244dbe..261d3cc 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -78,6 +78,7 @@
import android.os.UserHandle;
import android.os.Vibrator;
import android.os.VibratorInfo;
+import android.os.test.TestLooper;
import android.service.dreams.DreamManagerInternal;
import android.telecom.TelecomManager;
import android.util.FeatureFlagUtils;
@@ -160,12 +161,13 @@
@Mock private KeyguardServiceDelegate mKeyguardServiceDelegate;
private StaticMockitoSession mMockitoSession;
+ private TestLooper mTestLooper = new TestLooper();
private HandlerThread mHandlerThread;
private Handler mHandler;
private class TestInjector extends PhoneWindowManager.Injector {
TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) {
- super(context, funcs);
+ super(context, funcs, mTestLooper.getLooper());
}
AccessibilityShortcutController getAccessibilityShortcutController(
@@ -184,12 +186,10 @@
TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) {
MockitoAnnotations.initMocks(this);
- mHandlerThread = new HandlerThread("fake window manager");
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
+ mHandler = new Handler(mTestLooper.getLooper());
mContext = mockingDetails(context).isSpy() ? context : spy(context);
- mHandler.runWithScissors(() -> setUp(supportSettingsUpdate), 0 /* timeout */);
- waitForIdle();
+ mHandler.post(() -> setUp(supportSettingsUpdate));
+ mTestLooper.dispatchAll();
}
private void setUp(boolean supportSettingsUpdate) {
@@ -301,7 +301,6 @@
}
void tearDown() {
- mHandlerThread.quitSafely();
LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
Mockito.reset(mPhoneWindowManager);
mMockitoSession.finishMocking();
@@ -327,10 +326,6 @@
mPhoneWindowManager.dispatchUnhandledKey(null /*focusedToken*/, event, FLAG_INTERACTIVE);
}
- void waitForIdle() {
- mHandler.runWithScissors(() -> { }, 0 /* timeout */);
- }
-
/**
* Below functions will override the setting or the policy behavior.
*/
@@ -504,13 +499,13 @@
* Below functions will check the policy behavior could be invoked.
*/
void assertTakeScreenshotCalled() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mDisplayPolicy, timeout(SHORTCUT_KEY_DELAY_MILLIS))
.takeScreenshot(anyInt(), anyInt());
}
void assertShowGlobalActionsCalled() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mPhoneWindowManager).showGlobalActions();
verify(mGlobalActions, timeout(SHORTCUT_KEY_DELAY_MILLIS))
.showDialog(anyBoolean(), anyBoolean());
@@ -519,53 +514,53 @@
}
void assertVolumeMute() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mAudioManagerInternal, timeout(SHORTCUT_KEY_DELAY_MILLIS))
.silenceRingerModeInternal(eq("volume_hush"));
}
void assertAccessibilityKeychordCalled() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mAccessibilityShortcutController,
timeout(SHORTCUT_KEY_DELAY_MILLIS)).performAccessibilityShortcut();
}
void assertDreamRequest() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mDreamManagerInternal).requestDream();
}
void assertPowerSleep() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mPowerManager,
timeout(SHORTCUT_KEY_DELAY_MILLIS)).goToSleep(anyLong(), anyInt(), anyInt());
}
void assertPowerWakeUp() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mPowerManager,
timeout(SHORTCUT_KEY_DELAY_MILLIS)).wakeUp(anyLong(), anyInt(), anyString());
}
void assertNoPowerSleep() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mPowerManager, never()).goToSleep(anyLong(), anyInt(), anyInt());
}
void assertCameraLaunch() {
- waitForIdle();
+ mTestLooper.dispatchAll();
// GestureLauncherService should receive interceptPowerKeyDown twice.
verify(mGestureLauncherService, times(2))
.interceptPowerKeyDown(any(), anyBoolean(), any());
}
void assertSearchManagerLaunchAssist() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mSearchManager, timeout(SHORTCUT_KEY_DELAY_MILLIS)).launchAssist(any());
}
void assertLaunchCategory(String category) {
- waitForIdle();
+ mTestLooper.dispatchAll();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
try {
verify(mContext).startActivityAsUser(intentCaptor.capture(), any());
@@ -578,17 +573,17 @@
}
void assertShowRecentApps() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mStatusBarManagerInternal).showRecentApps(anyBoolean());
}
void assertStatusBarStartAssist() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mStatusBarManagerInternal).startAssist(any());
}
void assertSwitchKeyboardLayout(int direction) {
- waitForIdle();
+ mTestLooper.dispatchAll();
if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
verify(mInputMethodManagerInternal).switchKeyboardLayout(eq(direction));
verify(mWindowManagerFuncsImpl, never()).switchKeyboardLayout(anyInt(), anyInt());
@@ -599,7 +594,7 @@
}
void assertTakeBugreport() {
- waitForIdle();
+ mTestLooper.dispatchAll();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mContext).sendOrderedBroadcastAsUser(intentCaptor.capture(), any(), any(), any(),
any(), anyInt(), any(), any());
@@ -607,17 +602,17 @@
}
void assertTogglePanel() throws RemoteException {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mPhoneWindowManager.mStatusBarService).togglePanel();
}
void assertToggleShortcutsMenu() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mStatusBarManagerInternal).toggleKeyboardShortcutsMenu(anyInt());
}
void assertToggleCapsLock() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mInputManagerInternal).toggleCapsLock(anyInt());
}
@@ -642,12 +637,12 @@
}
void assertGoToHomescreen() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mPhoneWindowManager).launchHomeFromHotKey(anyInt());
}
void assertOpenAllAppView() {
- waitForIdle();
+ mTestLooper.dispatchAll();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS))
.startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class));
@@ -655,13 +650,13 @@
}
void assertNotOpenAllAppView() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mContext, after(TEST_SINGLE_KEY_DELAY_MILLIS).never())
.startActivityAsUser(any(Intent.class), any(), any(UserHandle.class));
}
void assertActivityTargetLaunched(ComponentName targetActivity) {
- waitForIdle();
+ mTestLooper.dispatchAll();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS))
.startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class));
@@ -670,7 +665,7 @@
void assertShortcutLogged(int vendorId, int productId, KeyboardLogEvent logEvent,
int expectedKey, int expectedModifierState, String errorMsg) {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
vendorId, productId, logEvent.getIntValue(), new int[]{expectedKey},
expectedModifierState), description(errorMsg));
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index d2eb1cc..78566fb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -84,31 +84,49 @@
private final ContentRecordingSession mWaitingDisplaySession =
ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
private ContentRecordingSession mTaskSession;
- private static Point sSurfaceSize;
+ private Point mSurfaceSize;
private ContentRecorder mContentRecorder;
@Mock private MediaProjectionManagerWrapper mMediaProjectionManagerWrapper;
private SurfaceControl mRecordedSurface;
+ private boolean mHandleAnisotropicDisplayMirroring = false;
+
@Before public void setUp() {
MockitoAnnotations.initMocks(this);
- // GIVEN SurfaceControl can successfully mirror the provided surface.
- sSurfaceSize = new Point(
- mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
- mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
- mRecordedSurface = surfaceControlMirrors(sSurfaceSize);
-
doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
- // GIVEN the VirtualDisplay associated with the session (so the display has state ON).
+ // Skip unnecessary operations of relayout.
+ spyOn(mWm.mWindowPlacerLocked);
+ doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean());
+ }
+
+ private void defaultInit() {
+ createContentRecorder(createDefaultDisplayInfo());
+ }
+
+ private DisplayInfo createDefaultDisplayInfo() {
+ return createDisplayInfo(mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
+ mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
+ }
+
+ private DisplayInfo createDisplayInfo(int width, int height) {
+ // GIVEN SurfaceControl can successfully mirror the provided surface.
+ mSurfaceSize = new Point(width, height);
+ mRecordedSurface = surfaceControlMirrors(mSurfaceSize);
+
DisplayInfo displayInfo = mDisplayInfo;
- displayInfo.logicalWidth = sSurfaceSize.x;
- displayInfo.logicalHeight = sSurfaceSize.y;
+ displayInfo.logicalWidth = width;
+ displayInfo.logicalHeight = height;
displayInfo.state = STATE_ON;
+ return displayInfo;
+ }
+
+ private void createContentRecorder(DisplayInfo displayInfo) {
mVirtualDisplayContent = createNewDisplay(displayInfo);
final int displayId = mVirtualDisplayContent.getDisplayId();
mContentRecorder = new ContentRecorder(mVirtualDisplayContent,
- mMediaProjectionManagerWrapper);
+ mMediaProjectionManagerWrapper, mHandleAnisotropicDisplayMirroring);
spyOn(mVirtualDisplayContent);
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
@@ -124,14 +142,11 @@
// GIVEN a session is waiting for the user to review consent.
mWaitingDisplaySession.setVirtualDisplayId(displayId);
mWaitingDisplaySession.setWaitingForConsent(true);
-
- // Skip unnecessary operations of relayout.
- spyOn(mWm.mWindowPlacerLocked);
- doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean());
}
@Test
public void testIsCurrentlyRecording() {
+ defaultInit();
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
mContentRecorder.updateRecording();
@@ -140,6 +155,7 @@
@Test
public void testUpdateRecording_display() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
@@ -147,6 +163,7 @@
@Test
public void testUpdateRecording_display_invalidDisplayIdToMirror() {
+ defaultInit();
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
INVALID_DISPLAY);
mContentRecorder.setContentRecordingSession(session);
@@ -156,6 +173,7 @@
@Test
public void testUpdateRecording_display_noDisplayContentToMirror() {
+ defaultInit();
doReturn(null).when(
mWm.mRoot).getDisplayContent(anyInt());
mContentRecorder.setContentRecordingSession(mDisplaySession);
@@ -165,6 +183,7 @@
@Test
public void testUpdateRecording_task_nullToken() {
+ defaultInit();
ContentRecordingSession session = mTaskSession;
session.setVirtualDisplayId(mDisplaySession.getVirtualDisplayId());
session.setTokenToRecord(null);
@@ -176,6 +195,7 @@
@Test
public void testUpdateRecording_task_noWindowContainer() {
+ defaultInit();
// Use the window container token of the DisplayContent, rather than task.
ContentRecordingSession invalidTaskSession = ContentRecordingSession.createTaskSession(
new WindowContainer.RemoteToken(mDisplayContent));
@@ -187,6 +207,7 @@
@Test
public void testUpdateRecording_wasPaused() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
@@ -197,6 +218,7 @@
@Test
public void testUpdateRecording_waitingForConsent() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mWaitingDisplaySession);
mContentRecorder.updateRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
@@ -209,6 +231,7 @@
@Test
public void testOnConfigurationChanged_neverRecording() {
+ defaultInit();
mContentRecorder.onConfigurationChanged(ORIENTATION_PORTRAIT);
verify(mTransaction, never()).setPosition(eq(mRecordedSurface), anyFloat(), anyFloat());
@@ -218,6 +241,7 @@
@Test
public void testOnConfigurationChanged_resizesSurface() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
// Ensure a different orientation when we check if something has changed.
@@ -234,13 +258,14 @@
@Test
public void testOnConfigurationChanged_resizesVirtualDisplay() {
+ defaultInit();
final int newWidth = 55;
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
// The user rotates the device, so the host app resizes the virtual display for the capture.
- resizeDisplay(mDisplayContent, newWidth, sSurfaceSize.y);
- resizeDisplay(mVirtualDisplayContent, newWidth, sSurfaceSize.y);
+ resizeDisplay(mDisplayContent, newWidth, mSurfaceSize.y);
+ resizeDisplay(mVirtualDisplayContent, newWidth, mSurfaceSize.y);
mContentRecorder.onConfigurationChanged(mDisplayContent.getConfiguration().orientation);
verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
@@ -251,6 +276,7 @@
@Test
public void testOnConfigurationChanged_rotateVirtualDisplay() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
@@ -271,12 +297,13 @@
*/
@Test
public void testOnConfigurationChanged_resizeSurface() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
// Resize the output surface.
- final Point newSurfaceSize = new Point(Math.round(sSurfaceSize.x / 2f),
- Math.round(sSurfaceSize.y * 2));
+ final Point newSurfaceSize = new Point(Math.round(mSurfaceSize.x / 2f),
+ Math.round(mSurfaceSize.y * 2));
doReturn(newSurfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize(
anyInt());
mContentRecorder.onConfigurationChanged(
@@ -292,6 +319,7 @@
@Test
public void testOnTaskOrientationConfigurationChanged_resizesSurface() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
@@ -314,6 +342,7 @@
@Test
public void testOnTaskBoundsConfigurationChanged_notifiesCallback() {
+ defaultInit();
mTask.getRootTask().setWindowingMode(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
final int minWidth = 222;
@@ -351,6 +380,7 @@
@Test
public void testTaskWindowingModeChanged_pip_stopsRecording() {
+ defaultInit();
// WHEN a recording is ongoing.
mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
mContentRecorder.setContentRecordingSession(mTaskSession);
@@ -368,6 +398,7 @@
@Test
public void testTaskWindowingModeChanged_fullscreen_startsRecording() {
+ defaultInit();
// WHEN a recording is ongoing.
mTask.setWindowingMode(WINDOWING_MODE_PINNED);
mContentRecorder.setContentRecordingSession(mTaskSession);
@@ -384,6 +415,7 @@
@Test
public void testStartRecording_notifiesCallback_taskSession() {
+ defaultInit();
// WHEN a recording is ongoing.
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
@@ -396,6 +428,7 @@
@Test
public void testStartRecording_notifiesCallback_displaySession() {
+ defaultInit();
// WHEN a recording is ongoing.
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
@@ -408,6 +441,7 @@
@Test
public void testStartRecording_taskInPIP_recordingNotStarted() {
+ defaultInit();
// GIVEN a task is in PIP.
mContentRecorder.setContentRecordingSession(mTaskSession);
mTask.setWindowingMode(WINDOWING_MODE_PINNED);
@@ -421,6 +455,7 @@
@Test
public void testStartRecording_taskInSplit_recordingStarted() {
+ defaultInit();
// GIVEN a task is in PIP.
mContentRecorder.setContentRecordingSession(mTaskSession);
mTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
@@ -434,6 +469,7 @@
@Test
public void testStartRecording_taskInFullscreen_recordingStarted() {
+ defaultInit();
// GIVEN a task is in PIP.
mContentRecorder.setContentRecordingSession(mTaskSession);
mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -447,6 +483,7 @@
@Test
public void testOnVisibleRequestedChanged_notifiesCallback() {
+ defaultInit();
// WHEN a recording is ongoing.
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
@@ -471,6 +508,7 @@
@Test
public void testOnVisibleRequestedChanged_noRecording_doesNotNotifyCallback() {
+ defaultInit();
// WHEN a recording is not ongoing.
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
@@ -493,6 +531,7 @@
@Test
public void testPauseRecording_pausesRecording() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
@@ -502,12 +541,14 @@
@Test
public void testPauseRecording_neverRecording() {
+ defaultInit();
mContentRecorder.pauseRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
}
@Test
public void testStopRecording_stopsRecording() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
@@ -517,12 +558,14 @@
@Test
public void testStopRecording_neverRecording() {
+ defaultInit();
mContentRecorder.stopRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
}
@Test
public void testRemoveTask_stopsRecording() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
@@ -533,6 +576,7 @@
@Test
public void testRemoveTask_stopsRecording_nullSessionShouldNotThrowExceptions() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
mContentRecorder.setContentRecordingSession(null);
@@ -541,6 +585,7 @@
@Test
public void testUpdateMirroredSurface_capturedAreaResized() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
@@ -548,9 +593,9 @@
// WHEN attempting to mirror on the virtual display, and the captured content is resized.
float xScale = 0.7f;
float yScale = 2f;
- Rect displayAreaBounds = new Rect(0, 0, Math.round(sSurfaceSize.x * xScale),
- Math.round(sSurfaceSize.y * yScale));
- mContentRecorder.updateMirroredSurface(mTransaction, displayAreaBounds, sSurfaceSize);
+ Rect displayAreaBounds = new Rect(0, 0, Math.round(mSurfaceSize.x * xScale),
+ Math.round(mSurfaceSize.y * yScale));
+ mContentRecorder.updateMirroredSurface(mTransaction, displayAreaBounds, mSurfaceSize);
assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
// THEN content in the captured DisplayArea is scaled to fit the surface size.
@@ -558,7 +603,7 @@
1.0f / yScale);
// THEN captured content is positioned in the centre of the output surface.
int scaledWidth = Math.round((float) displayAreaBounds.width() / xScale);
- int xInset = (sSurfaceSize.x - scaledWidth) / 2;
+ int xInset = (mSurfaceSize.x - scaledWidth) / 2;
verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, xInset, 0);
// THEN the resize callback is notified.
verify(mMediaProjectionManagerWrapper).notifyActiveProjectionCapturedContentResized(
@@ -566,7 +611,131 @@
}
@Test
+ public void testUpdateMirroredSurface_isotropicPixel() {
+ mHandleAnisotropicDisplayMirroring = false;
+ DisplayInfo displayInfo = createDefaultDisplayInfo();
+ createContentRecorder(displayInfo);
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, 1, 0, 0, 1);
+ }
+
+ @Test
+ public void testUpdateMirroredSurface_anisotropicPixel_compressY() {
+ mHandleAnisotropicDisplayMirroring = true;
+ DisplayInfo displayInfo = createDefaultDisplayInfo();
+ DisplayInfo inputDisplayInfo =
+ mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
+ displayInfo.physicalXDpi = 2.0f * inputDisplayInfo.physicalXDpi;
+ displayInfo.physicalYDpi = inputDisplayInfo.physicalYDpi;
+ createContentRecorder(displayInfo);
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ float xScale = 1f;
+ float yScale = 0.5f;
+ verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
+ yScale);
+ verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, 0,
+ Math.round(0.25 * mSurfaceSize.y));
+ }
+
+ @Test
+ public void testUpdateMirroredSurface_anisotropicPixel_compressX() {
+ mHandleAnisotropicDisplayMirroring = true;
+ DisplayInfo displayInfo = createDefaultDisplayInfo();
+ DisplayInfo inputDisplayInfo =
+ mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
+ displayInfo.physicalXDpi = inputDisplayInfo.physicalXDpi;
+ displayInfo.physicalYDpi = 2.0f * inputDisplayInfo.physicalYDpi;
+ createContentRecorder(displayInfo);
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ float xScale = 0.5f;
+ float yScale = 1f;
+ verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
+ yScale);
+ verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface,
+ Math.round(0.25 * mSurfaceSize.x), 0);
+ }
+
+ @Test
+ public void testUpdateMirroredSurface_anisotropicPixel_scaleOnX() {
+ mHandleAnisotropicDisplayMirroring = true;
+ int width = 2 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width();
+ int height = 6 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height();
+ DisplayInfo displayInfo = createDisplayInfo(width, height);
+ DisplayInfo inputDisplayInfo =
+ mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
+ displayInfo.physicalXDpi = inputDisplayInfo.physicalXDpi;
+ displayInfo.physicalYDpi = 2.0f * inputDisplayInfo.physicalYDpi;
+ createContentRecorder(displayInfo);
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ float xScale = 2f;
+ float yScale = 4f;
+ verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
+ yScale);
+ verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, 0,
+ inputDisplayInfo.logicalHeight);
+ }
+
+ @Test
+ public void testUpdateMirroredSurface_anisotropicPixel_scaleOnY() {
+ mHandleAnisotropicDisplayMirroring = true;
+ int width = 6 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width();
+ int height = 2 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height();
+ DisplayInfo displayInfo = createDisplayInfo(width, height);
+ DisplayInfo inputDisplayInfo =
+ mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
+ displayInfo.physicalXDpi = 2.0f * inputDisplayInfo.physicalXDpi;
+ displayInfo.physicalYDpi = inputDisplayInfo.physicalYDpi;
+ createContentRecorder(displayInfo);
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ float xScale = 4f;
+ float yScale = 2f;
+ verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
+ yScale);
+ verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface,
+ inputDisplayInfo.logicalWidth, 0);
+ }
+
+ @Test
+ public void testUpdateMirroredSurface_anisotropicPixel_shrinkCanvas() {
+ mHandleAnisotropicDisplayMirroring = true;
+ int width = mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width() / 2;
+ int height = mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height() / 2;
+ DisplayInfo displayInfo = createDisplayInfo(width, height);
+ DisplayInfo inputDisplayInfo =
+ mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
+ displayInfo.physicalXDpi = 2f * inputDisplayInfo.physicalXDpi;
+ displayInfo.physicalYDpi = inputDisplayInfo.physicalYDpi;
+ createContentRecorder(displayInfo);
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ float xScale = 0.5f;
+ float yScale = 0.25f;
+ verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
+ yScale);
+ verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, 0,
+ (mSurfaceSize.y - height / 2) / 2);
+ }
+
+ @Test
public void testDisplayContentUpdatesRecording_withoutSurface() {
+ defaultInit();
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
// mirror.
setUpDefaultTaskDisplayAreaWindowToken();
@@ -585,6 +754,7 @@
@Test
public void testDisplayContentUpdatesRecording_withSurface() {
+ defaultInit();
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
// mirror.
setUpDefaultTaskDisplayAreaWindowToken();
@@ -602,12 +772,13 @@
@Test
public void testDisplayContentUpdatesRecording_displayMirroring() {
+ defaultInit();
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
// mirror.
setUpDefaultTaskDisplayAreaWindowToken();
// GIVEN SurfaceControl can successfully mirror the provided surface.
- surfaceControlMirrors(sSurfaceSize);
+ surfaceControlMirrors(mSurfaceSize);
// Initially disable getDisplayIdToMirror since the DMS may create the DC outside the direct
// call in the test. We need to spy on the DC before updateRecording is called or we can't
// verify setDisplayMirroring is called
diff --git a/services/usage/java/com/android/server/usage/UsageStatsHandlerThread.java b/services/usage/java/com/android/server/usage/UsageStatsHandlerThread.java
new file mode 100644
index 0000000..31418d6
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/UsageStatsHandlerThread.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.usage;
+
+import android.os.Looper;
+import android.os.Trace;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.ServiceThread;
+
+/**
+ * Shared singleton default priority thread for usage stats message handling.
+ */
+public class UsageStatsHandlerThread extends ServiceThread {
+ private static final long SLOW_DISPATCH_THRESHOLD_MS = 10_000;
+ private static final long SLOW_DELIVERY_THRESHOLD_MS = 30_000;
+
+ private static final Object sLock = new Object();
+
+ @GuardedBy("sLock")
+ private static UsageStatsHandlerThread sInstance;
+
+ private UsageStatsHandlerThread() {
+ super("android.usagestats", android.os.Process.THREAD_PRIORITY_DEFAULT,
+ /* allowIo= */ true);
+ }
+
+ @GuardedBy("sLock")
+ private static void ensureThreadLocked() {
+ if (sInstance != null) {
+ return;
+ }
+
+ sInstance = new UsageStatsHandlerThread();
+ sInstance.start();
+ final Looper looper = sInstance.getLooper();
+ looper.setTraceTag(Trace.TRACE_TAG_SYSTEM_SERVER);
+ looper.setSlowLogThresholdMs(
+ SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS);
+ }
+
+ /**
+ * Obtain a singleton instance of the UsageStatsHandlerThread.
+ */
+ public static UsageStatsHandlerThread get() {
+ synchronized (sLock) {
+ ensureThreadLocked();
+ return sInstance;
+ }
+ }
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 55b5d11..e413663 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -202,6 +202,7 @@
static final int MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK = 8;
static final int MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED = 9;
static final int MSG_UID_REMOVED = 10;
+ static final int MSG_USER_STARTED = 11;
private final Object mLock = new Object();
private Handler mHandler;
@@ -334,7 +335,7 @@
mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
mPackageManager = getContext().getPackageManager();
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
- mHandler = new H(BackgroundThread.get().getLooper());
+ mHandler = getUsageEventProcessingHandler();
mIoHandler = new Handler(IoThread.get().getLooper(), mIoHandlerCallback);
mAppStandby = mInjector.getAppStandbyController(getContext());
@@ -380,10 +381,12 @@
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_STARTED);
- getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL,
- filter, null, /* Handler scheduler */ null);
+ getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
+ null, /* scheduler= */ Flags.useDedicatedHandlerThread() ? mHandler : null);
+
getContext().registerReceiverAsUser(new UidRemovedReceiver(), UserHandle.ALL,
- new IntentFilter(ACTION_UID_REMOVED), null, /* Handler scheduler */ null);
+ new IntentFilter(ACTION_UID_REMOVED), null,
+ /* scheduler= */ Flags.useDedicatedHandlerThread() ? mHandler : null);
mRealTimeSnapshot = SystemClock.elapsedRealtime();
mSystemTimeSnapshot = System.currentTimeMillis();
@@ -471,6 +474,14 @@
}
}
+ private Handler getUsageEventProcessingHandler() {
+ if (Flags.useDedicatedHandlerThread()) {
+ return new H(UsageStatsHandlerThread.get().getLooper());
+ } else {
+ return new H(BackgroundThread.get().getLooper());
+ }
+ }
+
private void onUserUnlocked(int userId) {
// fetch the installed packages outside the lock so it doesn't block package manager.
final HashMap<String, Long> installedPackages = getInstalledPackages(userId);
@@ -618,7 +629,7 @@
}
} else if (Intent.ACTION_USER_STARTED.equals(action)) {
if (userId >= 0) {
- mAppStandby.postCheckIdleStates(userId);
+ mHandler.obtainMessage(MSG_USER_STARTED, userId, 0).sendToTarget();
}
}
}
@@ -1554,8 +1565,7 @@
synchronized (mLaunchTimeAlarmQueues) {
LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
if (alarmQueue == null) {
- alarmQueue = new LaunchTimeAlarmQueue(
- userId, getContext(), BackgroundThread.get().getLooper());
+ alarmQueue = new LaunchTimeAlarmQueue(userId, getContext(), mHandler.getLooper());
mLaunchTimeAlarmQueues.put(userId, alarmQueue);
}
@@ -2040,6 +2050,9 @@
case MSG_UID_REMOVED:
mResponseStatsTracker.onUidRemoved(msg.arg1);
break;
+ case MSG_USER_STARTED:
+ mAppStandby.postCheckIdleStates(msg.arg1);
+ break;
case MSG_PACKAGE_REMOVED:
onPackageRemoved(msg.arg1, (String) msg.obj);
break;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7a0bf90..540cecf 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1056,6 +1056,14 @@
"carrier_use_ims_first_for_emergency_bool";
/**
+ * When {@code true}, this carrier will preferentially dial normal routed emergency calls over
+ * an in-service SIM if one is available.
+ * @hide
+ */
+ public static final String KEY_PREFER_IN_SERVICE_SIM_FOR_NORMAL_ROUTED_EMERGENCY_CALLS_BOOL =
+ "prefer_in_service_sim_for_normal_routed_emergency_calls_bool";
+
+ /**
* When {@code true}, the determination of whether to place a call as an emergency call will be
* based on the known {@link android.telephony.emergency.EmergencyNumber}s for the SIM on which
* the call is being placed. In a dual SIM scenario, if Sim A has the emergency numbers
@@ -9874,6 +9882,8 @@
sDefaults.putBoolean(KEY_CARRIER_IMS_GBA_REQUIRED_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL, true);
+ sDefaults.putBoolean(KEY_PREFER_IN_SERVICE_SIM_FOR_NORMAL_ROUTED_EMERGENCY_CALLS_BOOL,
+ false);
sDefaults.putBoolean(KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL, false);
sDefaults.putString(KEY_CARRIER_NETWORK_SERVICE_WWAN_PACKAGE_OVERRIDE_STRING, "");
sDefaults.putString(KEY_CARRIER_NETWORK_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING, "");
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index e9af486..11cbcb1 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -15,6 +15,7 @@
*/
package android.telephony.data;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -35,6 +36,7 @@
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.telephony.flags.Flags;
import com.android.telephony.Rlog;
import java.lang.annotation.Retention;
@@ -121,6 +123,9 @@
public static final int TYPE_BIP = ApnTypes.BIP;
/** APN type for ENTERPRISE. */
public static final int TYPE_ENTERPRISE = ApnTypes.ENTERPRISE;
+ /** APN type for RCS (Rich Communication Services). */
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public static final int TYPE_RCS = ApnTypes.RCS;
/** @hide */
@IntDef(flag = true, prefix = {"TYPE_"}, value = {
@@ -139,6 +144,7 @@
TYPE_BIP,
TYPE_VSIM,
TYPE_ENTERPRISE,
+ TYPE_RCS
})
@Retention(RetentionPolicy.SOURCE)
public @interface ApnType {
@@ -356,6 +362,17 @@
@SystemApi
public static final String TYPE_ENTERPRISE_STRING = "enterprise";
+ /**
+ * APN type for RCS (Rich Communication Services)
+ *
+ * Note: String representations of APN types are intended for system apps to communicate with
+ * modem components or carriers. Non-system apps should use the integer variants instead.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @SystemApi
+ public static final String TYPE_RCS_STRING = "rcs";
+
/** @hide */
@IntDef(prefix = { "AUTH_TYPE_" }, value = {
@@ -424,6 +441,26 @@
@Retention(RetentionPolicy.SOURCE)
public @interface MvnoType {}
+ /**
+ * Indicating this APN can be used when the device is using terrestrial cellular networks.
+ * @hide
+ */
+ public static final int INFRASTRUCTURE_CELLULAR = 1 << 0;
+
+ /**
+ * Indicating this APN can be used when the device is attached to satellites.
+ * @hide
+ */
+ public static final int INFRASTRUCTURE_SATELLITE = 1 << 1;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "INFRASTRUCTURE_" }, value = {
+ INFRASTRUCTURE_CELLULAR,
+ INFRASTRUCTURE_SATELLITE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface InfrastructureBitmask {}
+
private static final Map<String, Integer> APN_TYPE_STRING_MAP;
private static final Map<Integer, String> APN_TYPE_INT_MAP;
private static final Map<String, Integer> PROTOCOL_STRING_MAP;
@@ -449,6 +486,7 @@
APN_TYPE_STRING_MAP.put(TYPE_ENTERPRISE_STRING, TYPE_ENTERPRISE);
APN_TYPE_STRING_MAP.put(TYPE_VSIM_STRING, TYPE_VSIM);
APN_TYPE_STRING_MAP.put(TYPE_BIP_STRING, TYPE_BIP);
+ APN_TYPE_STRING_MAP.put(TYPE_RCS_STRING, TYPE_RCS);
APN_TYPE_INT_MAP = new ArrayMap<>();
APN_TYPE_INT_MAP.put(TYPE_DEFAULT, TYPE_DEFAULT_STRING);
@@ -466,6 +504,7 @@
APN_TYPE_INT_MAP.put(TYPE_ENTERPRISE, TYPE_ENTERPRISE_STRING);
APN_TYPE_INT_MAP.put(TYPE_VSIM, TYPE_VSIM_STRING);
APN_TYPE_INT_MAP.put(TYPE_BIP, TYPE_BIP_STRING);
+ APN_TYPE_INT_MAP.put(TYPE_RCS, TYPE_RCS_STRING);
PROTOCOL_STRING_MAP = new ArrayMap<>();
PROTOCOL_STRING_MAP.put("IP", PROTOCOL_IP);
@@ -528,6 +567,7 @@
private final int mCarrierId;
private final int mSkip464Xlat;
private final boolean mAlwaysOn;
+ private final @InfrastructureBitmask int mInfrastructureBitmask;
/**
* Returns the default MTU (Maximum Transmission Unit) size in bytes of the IPv4 routes brought
@@ -916,6 +956,29 @@
return mAlwaysOn;
}
+ /**
+ * Check if this APN can be used when the device is using certain infrastructure(s).
+ *
+ * @param infrastructures The infrastructure(s) the device is using.
+ *
+ * @return {@code true} if this APN can be used.
+ * @hide
+ */
+ public boolean isForInfrastructure(@InfrastructureBitmask int infrastructures) {
+ return (mInfrastructureBitmask & infrastructures) != 0;
+ }
+
+ /**
+ * @return The infrastructure bitmask of which the APN can be used on. For example, some APNs
+ * can only be used when the device is on cellular, on satellite, or both.
+ *
+ * @hide
+ */
+ @InfrastructureBitmask
+ public int getInfrastructureBitmask() {
+ return mInfrastructureBitmask;
+ }
+
private ApnSetting(Builder builder) {
this.mEntryName = builder.mEntryName;
this.mApnName = builder.mApnName;
@@ -952,6 +1015,7 @@
this.mCarrierId = builder.mCarrierId;
this.mSkip464Xlat = builder.mSkip464Xlat;
this.mAlwaysOn = builder.mAlwaysOn;
+ this.mInfrastructureBitmask = builder.mInfrastructureBitmask;
}
/**
@@ -1031,6 +1095,8 @@
cursor.getColumnIndexOrThrow(Telephony.Carriers.CARRIER_ID)))
.setSkip464Xlat(cursor.getInt(cursor.getColumnIndexOrThrow(Carriers.SKIP_464XLAT)))
.setAlwaysOn(cursor.getInt(cursor.getColumnIndexOrThrow(Carriers.ALWAYS_ON)) == 1)
+ .setInfrastructureBitmask(cursor.getInt(cursor.getColumnIndexOrThrow(
+ Telephony.Carriers.INFRASTRUCTURE_BITMASK)))
.buildWithoutCheck();
}
@@ -1070,6 +1136,7 @@
.setCarrierId(apn.mCarrierId)
.setSkip464Xlat(apn.mSkip464Xlat)
.setAlwaysOn(apn.mAlwaysOn)
+ .setInfrastructureBitmask(apn.mInfrastructureBitmask)
.buildWithoutCheck();
}
@@ -1115,6 +1182,7 @@
sb.append(", ").append(mCarrierId);
sb.append(", ").append(mSkip464Xlat);
sb.append(", ").append(mAlwaysOn);
+ sb.append(", ").append(mInfrastructureBitmask);
sb.append(", ").append(Objects.hash(mUser, mPassword));
return sb.toString();
}
@@ -1179,7 +1247,7 @@
mProtocol, mRoamingProtocol, mMtuV4, mMtuV6, mCarrierEnabled, mNetworkTypeBitmask,
mLingeringNetworkTypeBitmask, mProfileId, mPersistent, mMaxConns, mWaitTime,
mMaxConnsTime, mMvnoType, mMvnoMatchData, mApnSetId, mCarrierId, mSkip464Xlat,
- mAlwaysOn);
+ mAlwaysOn, mInfrastructureBitmask);
}
@Override
@@ -1191,36 +1259,37 @@
ApnSetting other = (ApnSetting) o;
return mEntryName.equals(other.mEntryName)
- && Objects.equals(mId, other.mId)
+ && mId == other.mId
&& Objects.equals(mOperatorNumeric, other.mOperatorNumeric)
&& Objects.equals(mApnName, other.mApnName)
&& Objects.equals(mProxyAddress, other.mProxyAddress)
&& Objects.equals(mMmsc, other.mMmsc)
&& Objects.equals(mMmsProxyAddress, other.mMmsProxyAddress)
- && Objects.equals(mMmsProxyPort, other.mMmsProxyPort)
- && Objects.equals(mProxyPort, other.mProxyPort)
+ && mMmsProxyPort == other.mMmsProxyPort
+ && mProxyPort == other.mProxyPort
&& Objects.equals(mUser, other.mUser)
&& Objects.equals(mPassword, other.mPassword)
- && Objects.equals(mAuthType, other.mAuthType)
- && Objects.equals(mApnTypeBitmask, other.mApnTypeBitmask)
- && Objects.equals(mProtocol, other.mProtocol)
- && Objects.equals(mRoamingProtocol, other.mRoamingProtocol)
- && Objects.equals(mCarrierEnabled, other.mCarrierEnabled)
- && Objects.equals(mProfileId, other.mProfileId)
- && Objects.equals(mPersistent, other.mPersistent)
- && Objects.equals(mMaxConns, other.mMaxConns)
- && Objects.equals(mWaitTime, other.mWaitTime)
- && Objects.equals(mMaxConnsTime, other.mMaxConnsTime)
- && Objects.equals(mMtuV4, other.mMtuV4)
- && Objects.equals(mMtuV6, other.mMtuV6)
- && Objects.equals(mMvnoType, other.mMvnoType)
+ && mAuthType == other.mAuthType
+ && mApnTypeBitmask == other.mApnTypeBitmask
+ && mProtocol == other.mProtocol
+ && mRoamingProtocol == other.mRoamingProtocol
+ && mCarrierEnabled == other.mCarrierEnabled
+ && mProfileId == other.mProfileId
+ && mPersistent == other.mPersistent
+ && mMaxConns == other.mMaxConns
+ && mWaitTime == other.mWaitTime
+ && mMaxConnsTime == other.mMaxConnsTime
+ && mMtuV4 == other.mMtuV4
+ && mMtuV6 == other.mMtuV6
+ && mMvnoType == other.mMvnoType
&& Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
- && Objects.equals(mNetworkTypeBitmask, other.mNetworkTypeBitmask)
- && Objects.equals(mLingeringNetworkTypeBitmask, other.mLingeringNetworkTypeBitmask)
- && Objects.equals(mApnSetId, other.mApnSetId)
- && Objects.equals(mCarrierId, other.mCarrierId)
- && Objects.equals(mSkip464Xlat, other.mSkip464Xlat)
- && Objects.equals(mAlwaysOn, other.mAlwaysOn);
+ && mNetworkTypeBitmask == other.mNetworkTypeBitmask
+ && mLingeringNetworkTypeBitmask == other.mLingeringNetworkTypeBitmask
+ && mApnSetId == other.mApnSetId
+ && mCarrierId == other.mCarrierId
+ && mSkip464Xlat == other.mSkip464Xlat
+ && mAlwaysOn == other.mAlwaysOn
+ && mInfrastructureBitmask == other.mInfrastructureBitmask;
}
/**
@@ -1270,7 +1339,8 @@
&& Objects.equals(mApnSetId, other.mApnSetId)
&& Objects.equals(mCarrierId, other.mCarrierId)
&& Objects.equals(mSkip464Xlat, other.mSkip464Xlat)
- && Objects.equals(mAlwaysOn, other.mAlwaysOn);
+ && Objects.equals(mAlwaysOn, other.mAlwaysOn)
+ && Objects.equals(mInfrastructureBitmask, other.mInfrastructureBitmask);
}
/**
@@ -1307,7 +1377,8 @@
&& Objects.equals(this.mApnSetId, other.mApnSetId)
&& Objects.equals(this.mCarrierId, other.mCarrierId)
&& Objects.equals(this.mSkip464Xlat, other.mSkip464Xlat)
- && Objects.equals(this.mAlwaysOn, other.mAlwaysOn);
+ && Objects.equals(this.mAlwaysOn, other.mAlwaysOn)
+ && Objects.equals(this.mInfrastructureBitmask, other.mInfrastructureBitmask);
}
// Equal or one is null.
@@ -1379,6 +1450,7 @@
apnValue.put(Telephony.Carriers.CARRIER_ID, mCarrierId);
apnValue.put(Telephony.Carriers.SKIP_464XLAT, mSkip464Xlat);
apnValue.put(Telephony.Carriers.ALWAYS_ON, mAlwaysOn);
+ apnValue.put(Telephony.Carriers.INFRASTRUCTURE_BITMASK, mInfrastructureBitmask);
return apnValue;
}
@@ -1651,6 +1723,7 @@
dest.writeInt(mCarrierId);
dest.writeInt(mSkip464Xlat);
dest.writeBoolean(mAlwaysOn);
+ dest.writeInt(mInfrastructureBitmask);
}
private static ApnSetting readFromParcel(Parcel in) {
@@ -1686,6 +1759,7 @@
.setCarrierId(in.readInt())
.setSkip464Xlat(in.readInt())
.setAlwaysOn(in.readBoolean())
+ .setInfrastructureBitmask(in.readInt())
.buildWithoutCheck();
}
@@ -1767,6 +1841,7 @@
private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
private int mSkip464Xlat = Carriers.SKIP_464XLAT_DEFAULT;
private boolean mAlwaysOn;
+ private int mInfrastructureBitmask = INFRASTRUCTURE_CELLULAR;
/**
* Default constructor for Builder.
@@ -2189,6 +2264,22 @@
}
/**
+ * Set the infrastructure bitmask.
+ *
+ * @param infrastructureBitmask The infrastructure bitmask of which the APN can be used on.
+ * For example, some APNs can only be used when the device is on cellular, on satellite, or
+ * both.
+ *
+ * @return The builder.
+ * @hide
+ */
+ @NonNull
+ public Builder setInfrastructureBitmask(@InfrastructureBitmask int infrastructureBitmask) {
+ this.mInfrastructureBitmask = infrastructureBitmask;
+ return this;
+ }
+
+ /**
* Builds {@link ApnSetting} from this builder.
*
* @return {@code null} if {@link #setApnName(String)} or {@link #setEntryName(String)}
@@ -2198,7 +2289,7 @@
public ApnSetting build() {
if ((mApnTypeBitmask & (TYPE_DEFAULT | TYPE_MMS | TYPE_SUPL | TYPE_DUN | TYPE_HIPRI
| TYPE_FOTA | TYPE_IMS | TYPE_CBS | TYPE_IA | TYPE_EMERGENCY | TYPE_MCX
- | TYPE_XCAP | TYPE_VSIM | TYPE_BIP | TYPE_ENTERPRISE)) == 0
+ | TYPE_XCAP | TYPE_VSIM | TYPE_BIP | TYPE_ENTERPRISE | TYPE_RCS)) == 0
|| TextUtils.isEmpty(mApnName) || TextUtils.isEmpty(mEntryName)) {
return null;
}
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index f346b92..88a32d1 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -431,6 +431,8 @@
return ApnSetting.TYPE_VSIM;
case NetworkCapabilities.NET_CAPABILITY_ENTERPRISE:
return ApnSetting.TYPE_ENTERPRISE;
+ case NetworkCapabilities.NET_CAPABILITY_RCS:
+ return ApnSetting.TYPE_RCS;
default:
return ApnSetting.TYPE_NONE;
}
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 82aa85d..f4f2be6 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -53,7 +53,17 @@
}
filegroup {
- name: "FlickerTestsAppLaunch-src",
+ name: "FlickerTestsAppLaunchCommon-src",
+ srcs: ["src/**/launch/common/*.kt"],
+}
+
+filegroup {
+ name: "FlickerTestsAppLaunch1-src",
+ srcs: ["src/**/launch/OpenApp*.kt"],
+}
+
+filegroup {
+ name: "FlickerTestsAppLaunch2-src",
srcs: ["src/**/launch/*.kt"],
}
@@ -119,7 +129,8 @@
exclude_srcs: [
":FlickerTestsAppClose-src",
":FlickerTestsIme-src",
- ":FlickerTestsAppLaunch-src",
+ ":FlickerTestsAppLaunch1-src",
+ ":FlickerTestsAppLaunch2-src",
":FlickerTestsQuickswitch-src",
":FlickerTestsRotation-src",
":FlickerTestsNotification-src",
@@ -162,7 +173,8 @@
instrumentation_target_package: "com.android.server.wm.flicker.launch",
srcs: [
":FlickerTestsBase-src",
- ":FlickerTestsAppLaunch-src",
+ ":FlickerTestsAppLaunchCommon-src",
+ ":FlickerTestsAppLaunch2-src",
],
exclude_srcs: [
":FlickerTestsActivityEmbedding-src",
@@ -170,6 +182,39 @@
}
android_test {
+ name: "FlickerTestsAppLaunch1",
+ defaults: ["FlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestAppLaunch.xml"],
+ package_name: "com.android.server.wm.flicker.launch",
+ instrumentation_target_package: "com.android.server.wm.flicker.launch",
+ srcs: [
+ ":FlickerTestsBase-src",
+ ":FlickerTestsAppLaunchCommon-src",
+ ":FlickerTestsAppLaunch1-src",
+ ],
+ exclude_srcs: [
+ ":FlickerTestsActivityEmbedding-src",
+ ],
+}
+
+android_test {
+ name: "FlickerTestsAppLaunch2",
+ defaults: ["FlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestAppLaunch.xml"],
+ package_name: "com.android.server.wm.flicker.launch",
+ instrumentation_target_package: "com.android.server.wm.flicker.launch",
+ srcs: [
+ ":FlickerTestsBase-src",
+ ":FlickerTestsAppLaunchCommon-src",
+ ":FlickerTestsAppLaunch2-src",
+ ],
+ exclude_srcs: [
+ ":FlickerTestsActivityEmbedding-src",
+ ":FlickerTestsAppLaunch1-src",
+ ],
+}
+
+android_test {
name: "FlickerTestsNotification",
defaults: ["FlickerTestsDefault"],
additional_manifests: ["manifests/AndroidManifestNotification.xml"],
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
index 48d5041..f788efa 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
@@ -16,13 +16,11 @@
package com.android.server.wm.flicker.launch
-import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
import androidx.test.filters.FlakyTest
+import com.android.server.wm.flicker.launch.common.OpenAppFromIconTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,28 +52,7 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class OpenAppFromIconColdTest(flicker: LegacyFlickerTest) :
- OpenAppFromLauncherTransition(flicker) {
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- setup {
- if (flicker.scenario.isTablet) {
- tapl.setExpectedRotation(flicker.scenario.startRotation.value)
- } else {
- tapl.setExpectedRotation(Rotation.ROTATION_0.value)
- }
- RemoveAllTasksButHomeRule.removeAllTasksButHome()
- }
- transitions {
- tapl
- .goHome()
- .switchToAllApps()
- .getAppIcon(testApp.appName)
- .launch(testApp.packageName)
- }
- teardown { testApp.exit(wmHelper) }
- }
+ OpenAppFromIconTransition(flicker) {
@FlakyTest(bugId = 240916028)
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
index f575fcc..d86dc50 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
@@ -21,6 +21,7 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
index 93d0520..be07053 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
@@ -25,6 +25,7 @@
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
index 78b58f4..f66eff9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
@@ -24,6 +24,7 @@
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
index 3f931c4..65214764 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
@@ -27,6 +27,7 @@
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.launch.common.OpenAppFromLockscreenTransition
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Ignore
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index b85362a..4d31c28 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -25,6 +25,7 @@
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
index 1bdb6e71..42e34b3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
@@ -30,6 +30,7 @@
import android.view.KeyEvent
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition
import org.junit.FixMethodOrder
import org.junit.Ignore
import org.junit.Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt
deleted file mode 100644
index 3d9c067..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt
+++ /dev/null
@@ -1,87 +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.server.wm.flicker.launch
-
-import android.platform.test.annotations.Presubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.TransferSplashscreenAppHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launching an app from launcher
- *
- * To run this test: `atest FlickerTests:OpenTransferSplashscreenAppFromLauncherTransition`
- *
- * Actions:
- * ```
- * Inherit from OpenAppFromIconColdTest, Launch an app [testApp] with an animated splash screen
- * by clicking it's icon on all apps, and wait for transfer splash screen complete
- * ```
- *
- * Notes:
- * ```
- * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
- * are inherited [OpenAppTransition]
- * 2. Verify no flickering when transfer splash screen to app window.
- * ```
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenTransferSplashscreenAppFromLauncherTransition(flicker: LegacyFlickerTest) :
- OpenAppFromIconColdTest(flicker) {
- override val testApp = TransferSplashscreenAppHelper(instrumentation)
-
- /**
- * Checks that [ComponentNameMatcher.LAUNCHER] window is the top window at the start of the
- * transition, and is replaced by [ComponentNameMatcher.SPLASH_SCREEN], then [testApp] remains
- * visible until the end
- */
- @Presubmit
- @Test
- fun appWindowAfterSplash() {
- flicker.assertWm {
- this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
- .then()
- .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN)
- .then()
- .isAppWindowOnTop(testApp)
- .isAppWindowInvisible(ComponentNameMatcher.SPLASH_SCREEN)
- }
- }
-
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
- * navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
- }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt
new file mode 100644
index 0000000..c854701
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.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.server.wm.flicker.launch.common
+
+import android.tools.common.Rotation
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+
+abstract class OpenAppFromIconTransition(flicker: LegacyFlickerTest) :
+ OpenAppFromLauncherTransition(flicker) {
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ if (flicker.scenario.isTablet) {
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+ } else {
+ tapl.setExpectedRotation(Rotation.ROTATION_0.value)
+ }
+ RemoveAllTasksButHomeRule.removeAllTasksButHome()
+ }
+ transitions {
+ tapl
+ .goHome()
+ .switchToAllApps()
+ .getAppIcon(testApp.appName)
+ .launch(testApp.packageName)
+ }
+ teardown { testApp.exit(wmHelper) }
+ }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLauncherTransition.kt
similarity index 95%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLauncherTransition.kt
index 4fc9bcb..9d7096e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLauncherTransition.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 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,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.launch.common
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt
similarity index 97%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt
index cc501e6..7b08843 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 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,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.launch.common
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppTransition.kt
similarity index 98%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppTransition.kt
index bb11be5..989619e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppTransition.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.launch.common
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenTransferSplashscreenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenTransferSplashscreenAppFromLauncherTransition.kt
new file mode 100644
index 0000000..2e9620b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenTransferSplashscreenAppFromLauncherTransition.kt
@@ -0,0 +1,214 @@
+/*
+ * 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.flicker.launch.common
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.TransferSplashscreenAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from launcher
+ *
+ * To run this test: `atest FlickerTests:OpenTransferSplashscreenAppFromLauncherTransition`
+ *
+ * Actions:
+ * ```
+ * Inherit from OpenAppFromIconColdTest, Launch an app [testApp] with an animated splash screen
+ * by clicking it's icon on all apps, and wait for transfer splash screen complete
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [OpenAppTransition]
+ * 2. Verify no flickering when transfer splash screen to app window.
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenTransferSplashscreenAppFromLauncherTransition(flicker: LegacyFlickerTest) :
+ OpenAppFromIconTransition(flicker) {
+ override val testApp = TransferSplashscreenAppHelper(instrumentation)
+
+ /**
+ * Checks that [ComponentNameMatcher.LAUNCHER] window is the top window at the start of the
+ * transition, and is replaced by [ComponentNameMatcher.SPLASH_SCREEN], then [testApp] remains
+ * visible until the end
+ */
+ @Presubmit
+ @Test
+ fun appWindowAfterSplash() {
+ flicker.assertWm {
+ this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
+ .then()
+ .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN)
+ .then()
+ .isAppWindowOnTop(testApp)
+ .isAppWindowInvisible(ComponentNameMatcher.SPLASH_SCREEN)
+ }
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun focusChanges() {
+ super.focusChanges()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowReplacesLauncherAsTopWindow() {
+ super.appWindowReplacesLauncherAsTopWindow()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowAsTopWindowAtEnd() {
+ super.appWindowAsTopWindowAtEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowBecomesTopWindow() {
+ super.appWindowBecomesTopWindow()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowBecomesVisible() {
+ super.appWindowBecomesVisible()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowIsTopWindowAtEnd() {
+ super.appWindowIsTopWindowAtEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appLayerBecomesVisible() {
+ super.appLayerBecomesVisible()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appLayerReplacesLauncher() {
+ super.appLayerReplacesLauncher()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun cujCompleted() {
+ super.cujCompleted()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun entireScreenCovered() {
+ super.entireScreenCovered()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() {
+ super.navBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() {
+ super.navBarLayerPositionAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun navBarWindowIsAlwaysVisible() {
+ super.navBarWindowIsAlwaysVisible()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun navBarWindowIsVisibleAtStartAndEnd() {
+ super.navBarWindowIsVisibleAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() {
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() {
+ super.statusBarLayerPositionAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() {
+ super.statusBarWindowIsAlwaysVisible()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {
+ super.taskBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() {
+ super.taskBarWindowIsAlwaysVisible()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+ }
+}